diff --git a/external/Vulkan/.gitattributes b/external/Vulkan/.gitattributes
new file mode 100644
index 0000000000000000000000000000000000000000..26e532971eed57f7bec7e3251d4d240e8f338070
--- /dev/null
+++ b/external/Vulkan/.gitattributes
@@ -0,0 +1,67 @@
+###############################################################################
+# Set default behavior to automatically normalize line endings.
+###############################################################################
+* text=auto
+
+###############################################################################
+# Set default behavior for command prompt diff.
+#
+# This is need for earlier builds of msysgit that does not have it on by
+# default for csharp files.
+# Note: This is only used by command line
+###############################################################################
+#*.cs     diff=csharp
+
+###############################################################################
+# Set the merge driver for project and solution files
+#
+# Merging from the command prompt will add diff markers to the files if there
+# are conflicts (Merging from VS is not affected by the settings below, in VS
+# the diff markers are never inserted). Diff markers may cause the following 
+# file extensions to fail to load in VS. An alternative would be to treat
+# these files as binary and thus will always conflict and require user
+# intervention with every merge. To do so, just uncomment the entries below
+###############################################################################
+#*.sln       merge=binary
+#*.csproj    merge=binary
+#*.vbproj    merge=binary
+#*.vcxproj   merge=binary
+#*.vcproj    merge=binary
+#*.dbproj    merge=binary
+#*.fsproj    merge=binary
+#*.lsproj    merge=binary
+#*.wixproj   merge=binary
+#*.modelproj merge=binary
+#*.sqlproj   merge=binary
+#*.wwaproj   merge=binary
+
+###############################################################################
+# behavior for image files
+#
+# image files are treated as binary by default.
+###############################################################################
+#*.jpg   binary
+#*.png   binary
+#*.gif   binary
+
+###############################################################################
+# diff behavior for common document formats
+# 
+# Convert binary document formats to text before diffing them. This feature
+# is only available from the command line. Turn it on by uncommenting the 
+# entries below.
+###############################################################################
+#*.doc   diff=astextplain
+#*.DOC   diff=astextplain
+#*.docx  diff=astextplain
+#*.DOCX  diff=astextplain
+#*.dot   diff=astextplain
+#*.DOT   diff=astextplain
+#*.pdf   diff=astextplain
+#*.PDF   diff=astextplain
+#*.rtf   diff=astextplain
+#*.RTF   diff=astextplain
+
+# Exclude dirs from githug language statis
+data/* linguist-vendored=true
+external/* linguist-vendored=true
\ No newline at end of file
diff --git a/external/Vulkan/.github/FUNDING.yml b/external/Vulkan/.github/FUNDING.yml
new file mode 100644
index 0000000000000000000000000000000000000000..517b9a99064fd9f8822a224f1a15202c86415c15
--- /dev/null
+++ b/external/Vulkan/.github/FUNDING.yml
@@ -0,0 +1 @@
+custom: ["paypal.me/saschawillemsde"]
diff --git a/external/Vulkan/.github/workflows/build.yml b/external/Vulkan/.github/workflows/build.yml
new file mode 100644
index 0000000000000000000000000000000000000000..34d2d5bdbaa2377a8ffa7addeccb665fae1c6ff4
--- /dev/null
+++ b/external/Vulkan/.github/workflows/build.yml
@@ -0,0 +1,42 @@
+name: Build Project
+
+on:
+  push:
+    branches:
+      - master
+  pull_request:
+    branches:
+      - master
+
+jobs:
+  build_ubuntu_x11:
+    name: Build Ubuntu
+    runs-on: ubuntu-18.04
+
+    steps:
+      - uses: actions/checkout@v2
+        with:
+          submodules: "recursive"
+
+      - name: Build
+        run: |
+          cmake .
+          make
+
+  build_windows:
+    name: Build Windows
+    runs-on: windows-latest
+
+    steps:
+      - uses: ilammy/msvc-dev-cmd@v1
+
+      - uses: actions/checkout@v2
+        with:
+          submodules: "recursive"
+
+      - name: Build
+        env:
+          TARGET_PLATFORM: windows
+        run: |
+          cmake .
+          cmake --build .
diff --git a/external/Vulkan/.github/workflows/cmake.yml b/external/Vulkan/.github/workflows/cmake.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f9b33cbd42ab39d0f156b0dc7dee2947b8587c88
--- /dev/null
+++ b/external/Vulkan/.github/workflows/cmake.yml
@@ -0,0 +1,41 @@
+name: CMake
+
+on: [push]
+
+env:
+  # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
+  BUILD_TYPE: Release
+
+jobs:
+  build:
+    # The CMake configure and build commands are platform agnostic and should work equally
+    # well on Windows or Mac.  You can convert this to a matrix build if you need
+    # cross-platform coverage.
+    # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
+    runs-on: ubuntu-latest
+
+    steps:
+    - uses: actions/checkout@v2
+      with:
+        submodules: true
+
+    - name: Create Build Environment
+      # Some projects don't allow in-source building, so create a separate build directory
+      # We'll use this as our working directory for all subsequent commands
+      run: cmake -E make_directory ${{runner.workspace}}/build
+
+    - name: Configure CMake
+      # Use a bash shell so we can use the same syntax for environment variable
+      # access regardless of the host operating system
+      shell: bash
+      working-directory: ${{runner.workspace}}/build
+      # Note the current convention is to use the -S and -B options here to specify source 
+      # and build directories, but this is only available with CMake 3.13 and higher.  
+      # The CMake binaries on the Github Actions machines are (as of this writing) 3.12
+      run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE
+
+    - name: Build
+      working-directory: ${{runner.workspace}}/build
+      shell: bash
+      # Execute the build.  You can specify a specific target with "--target <NAME>"
+      run: cmake --build . --config $BUILD_TYPE
diff --git a/external/Vulkan/.gitignore b/external/Vulkan/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..3c8933fcc880be5906977249cdb3ac056a9bde59
--- /dev/null
+++ b/external/Vulkan/.gitignore
@@ -0,0 +1,236 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+
+# User-specific files
+*.suo
+*.user
+*.sln.docstates
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+build/
+bld/
+[Bb]in/
+[Oo]bj/
+
+# CMake
+CMakeCache.txt
+CMakeFiles
+CMakeScripts
+Makefile
+cmake_install.cmake
+install_manifest.txt
+
+# Roslyn cache directories
+*.ide/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+#NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opensdf
+*.opendb
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding addin-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+## TODO: Comment the next line if you want to checkin your
+## web deploy settings but do note that will include unencrypted
+## passwords
+*.pubxml
+
+# NuGet Packages
+packages/*
+*.nupkg
+## TODO: If the tool you use requires repositories.config
+## uncomment the next line
+#!packages/repositories.config
+
+# Enable "build/" folder in the NuGet Packages folder since
+# NuGet packages use it for MSBuild targets.
+# This line needs to be after the ignore of the build folder
+# (and the packages folder if the line above has been uncommented)
+!packages/build/
+
+# Windows Azure Build Output
+csx/
+*.build.csdef
+
+# Windows Store app package directory
+AppPackages/
+
+# Others
+sql/
+*.Cache
+ClientBin/
+[Ss]tyle[Cc]op.*
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.pfx
+*.publishsettings
+node_modules/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+
+# SQL Server files
+*.mdf
+*.ldf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+
+# Microsoft Fakes
+FakesAssemblies/
+
+.vs/
+
+# Don't ignore 3d models in obj format
+!data/models/*.obj
+!data/models/lowpoly/*.obj
+!data/models/voyager/*.obj
+
+# DLLs in bin
+!bin/*.dll
+
+# Android validation layer libraries
+android/layers/*/*.so
+android/**/jni/
+
+# Downloadable assets
+vulkan_asset_pack_gltf.*
+data/textures/hdr/*.*
+data/readme.txt
+data/models/cerberus/*.*
+!data/textures/hdr/README.md
+
+*.args.json
+
+# Ignore VS project files
+*.sln
+*.vcxproj
+*.vcxproj.filters
+*.vcxproj.user
+
+# Ignore macOS .DS_Store
+.DS_Store
+
+# Assets that are part of the asset pack and should not be stored in the repo
+data/models/**
+data/textures/**
+data/README_asset_pack.md
+data/font*.*
+data/roboto*.*
\ No newline at end of file
diff --git a/external/Vulkan/.gitmodules b/external/Vulkan/.gitmodules
new file mode 100644
index 0000000000000000000000000000000000000000..c070acb0a2d79975ef3f20c0abfce134ff88e012
--- /dev/null
+++ b/external/Vulkan/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "external/glm"]
+	path = external/glm
+	url = https://github.com/g-truc/glm
diff --git a/external/Vulkan/BUILD.md b/external/Vulkan/BUILD.md
new file mode 100644
index 0000000000000000000000000000000000000000..103acb3765915a0d9f2c2042b0e47818f0ba3f24
--- /dev/null
+++ b/external/Vulkan/BUILD.md
@@ -0,0 +1,48 @@
+# Building
+
+The repository contains everything required to compile and build the examples on Windows, Linux and Android using a C++ compiler that supports C++11. All required dependencies are included.
+
+## <img src="./images/windowslogo.png" alt="" height="32px"> Windows
+
+[![Build status](https://ci.appveyor.com/api/projects/status/abylymfyil0mhpx8?svg=true)](https://ci.appveyor.com/project/SaschaWillems/vulkan)
+
+Use the provided CMakeLists.txt with [CMake](https://cmake.org) to generate a build configuration for your favorite IDE or compiler, e.g.:
+```
+cmake -G "Visual Studio 14 2015 Win64"
+```
+
+## <img src="./images/linuxlogo.png" alt="" height="32px"> Linux
+
+[![Build Status](https://travis-ci.org/SaschaWillems/Vulkan.svg?branch=master)](https://travis-ci.org/SaschaWillems/Vulkan)
+
+Use the provided CMakeLists.txt with [CMake](https://cmake.org) to generate a build configuration for your favorite IDE or compiler.
+
+##### [Window system integration](https://www.khronos.org/registry/vulkan/specs/1.0-wsi_extensions/html/vkspec.html#wsi)
+- **XCB**: Default WSI (if no cmake option is specified)
+- **Wayland**: Use cmake option ```USE_WAYLAND_WSI``` (```-DUSE_WAYLAND_WSI=ON```)
+- **DirectFB**: Use cmake option ```USE_DIRECTFB_WSI``` (```-DUSE_DIRECTFB_WSI=ON```)
+- **DirectToDisplay**: Use cmake option ```USE_D2D_WSI``` (```-DUSE_D2D_WSI=ON```)
+
+## <img src="./images/androidlogo.png" alt="" height="32px"> [Android](android/)
+
+Building on Android is done using the [Gradle Build Tool](https://gradle.org/):
+
+```
+cd android
+./gradlew assembleDebug
+```
+This will download gradle locally, build all samples and output the apks to ```android\examples\bin```.
+
+On Windows execute `gradlew.bat assembleDebug`.
+
+If you want to build and install on a connected device or emulator image, run ```gradle installDebug``` instead.
+
+## <img src="./images/applelogo.png" alt="" height="32px"> [iOS and macOS](xcode/)
+
+Building for *iOS* and *macOS* is done using the [examples](xcode/examples.xcodeproj) *Xcode* project found in the [xcode](xcode) directory. These examples use the [**MoltenVK**](https://moltengl.com/moltenvk) Vulkan driver to provide Vulkan support on *iOS* and *macOS*, and require an *iOS* or *macOS* device that supports *Metal*. Please see the [MoltenVK Examples readme](xcode/README_MoltenVK_Examples.md) for more info on acquiring **MoltenVK** and building and deploying the examples on *iOS* and *macOS*.
+
+##### MacOS
+Use the provided CMakeLists.txt with [CMake](https://cmake.org) to generate a build configuration for your favorite IDE or compiler, e.g.:
+```
+cmake -G "Xcode"
+```
\ No newline at end of file
diff --git a/external/Vulkan/CMakeLists.txt b/external/Vulkan/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..b71d1aeda8d016d69936f49378328d54a502250f
--- /dev/null
+++ b/external/Vulkan/CMakeLists.txt
@@ -0,0 +1,151 @@
+cmake_minimum_required(VERSION 3.4 FATAL_ERROR)
+cmake_policy(VERSION 2.8)
+set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake")
+
+set(NAME vulkanExamples)
+
+project(${NAME})
+
+include_directories(external)
+include_directories(external/glm)
+include_directories(external/gli)
+include_directories(external/imgui)
+include_directories(external/tinygltf)
+include_directories(external/ktx/include)
+include_directories(external/ktx/other_include)
+include_directories(base)
+
+OPTION(USE_D2D_WSI "Build the project using Direct to Display swapchain" OFF)
+OPTION(USE_DIRECTFB_WSI "Build the project using DirectFB swapchain" OFF)
+OPTION(USE_WAYLAND_WSI "Build the project using Wayland swapchain" OFF)
+OPTION(USE_HEADLESS "Build the project using headless extension swapchain" OFF)
+
+set(RESOURCE_INSTALL_DIR "" CACHE PATH "Path to install resources to (leave empty for running uninstalled)")
+
+# Use FindVulkan module added with CMAKE 3.7
+if (NOT CMAKE_VERSION VERSION_LESS 3.7.0)
+	message(STATUS "Using module to find Vulkan")
+	find_package(Vulkan)
+endif()
+
+IF(UNIX AND NOT APPLE)
+	set(LINUX TRUE)
+ENDIF()
+
+IF(WIN32)
+	IF (NOT Vulkan_FOUND)
+		find_library(Vulkan_LIBRARY NAMES vulkan-1 vulkan PATHS ${CMAKE_SOURCE_DIR}/libs/vulkan)
+		IF (Vulkan_LIBRARY)
+			set(Vulkan_FOUND ON)
+			MESSAGE("Using bundled Vulkan library version")
+		ENDIF()
+	ENDIF()
+	set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DVK_USE_PLATFORM_WIN32_KHR")
+ELSEIF(LINUX)
+	IF (NOT Vulkan_FOUND)
+		find_library(Vulkan_LIBRARY NAMES vulkan HINTS "$ENV{VULKAN_SDK}/lib" "${CMAKE_SOURCE_DIR}/libs/vulkan" REQUIRED)
+		IF (Vulkan_LIBRARY)
+			set(Vulkan_FOUND ON)
+			MESSAGE("Using bundled Vulkan library version")
+		ENDIF()
+	ENDIF()
+	find_package(Threads REQUIRED)
+	IF(USE_D2D_WSI)
+		MESSAGE("Using direct to display extension...")
+		add_definitions(-D_DIRECT2DISPLAY)
+	ELSEIF(USE_DIRECTFB_WSI)
+		find_package(DirectFB REQUIRED)
+		set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DVK_USE_PLATFORM_DIRECTFB_EXT")
+		include_directories(${DIRECTFB_INCLUDE_DIR})
+	ELSEIF(USE_WAYLAND_WSI)
+		find_program(PKG_CONFIG pkg-config)
+		if (NOT PKG_CONFIG)
+			message(FATAL_ERROR "pkg-config binary not found")
+		endif ()
+		find_package(Wayland REQUIRED)
+		if (NOT WAYLAND_FOUND)
+			message(FATAL_ERROR "Wayland development package not found")
+		endif ()
+		pkg_check_modules(WAYLAND_PROTOCOLS REQUIRED wayland-protocols)
+		if (NOT WAYLAND_PROTOCOLS_FOUND)
+			message(FATAL_ERROR "Wayland protocols package not found")
+		endif ()
+		find_program(WAYLAND_SCANNER wayland-scanner)
+		if (NOT WAYLAND_SCANNER)
+			message(FATAL_ERROR "wayland-scanner binary not found")
+		endif ()
+		set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DVK_USE_PLATFORM_WAYLAND_KHR")
+		include_directories(${WAYLAND_INCLUDE_DIR})
+		pkg_get_variable(protocol_dir wayland-protocols pkgdatadir)
+		execute_process(COMMAND ${WAYLAND_SCANNER} client-header ${protocol_dir}/stable/xdg-shell/xdg-shell.xml ${CMAKE_BINARY_DIR}/xdg-shell-client-protocol.h
+				COMMAND ${WAYLAND_SCANNER} private-code ${protocol_dir}/stable/xdg-shell/xdg-shell.xml ${CMAKE_BINARY_DIR}/xdg-shell-protocol.c)
+		include_directories(${CMAKE_BINARY_DIR})
+	ELSEIF(USE_HEADLESS)
+		set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DVK_USE_PLATFORM_HEADLESS_EXT")
+	ELSE(USE_D2D_WSI)
+		find_package(XCB REQUIRED)
+		set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DVK_USE_PLATFORM_XCB_KHR")
+	ENDIF(USE_D2D_WSI)
+ELSEIF(APPLE)
+	set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DVK_USE_PLATFORM_MACOS_MVK -DVK_EXAMPLE_XCODE_GENERATED")
+	# Todo : android?
+ENDIF(WIN32)
+
+IF (NOT Vulkan_FOUND)
+	message(FATAL_ERROR "Could not find Vulkan library!")
+ELSE()
+	message(STATUS ${Vulkan_LIBRARY})
+ENDIF()
+
+# Set preprocessor defines
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DNOMINMAX -D_USE_MATH_DEFINES")
+
+# Clang specific stuff
+if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
+  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-switch-enum")
+endif()
+
+
+add_definitions(-D_CRT_SECURE_NO_WARNINGS)
+set(CMAKE_CXX_STANDARD 11)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+file(GLOB SOURCE *.cpp )
+
+# Build all examples
+function(buildExamples)
+	foreach(EXAMPLE ${EXAMPLES})
+		buildExample(${EXAMPLE})
+	endforeach(EXAMPLE)
+endfunction(buildExamples)
+
+if(RESOURCE_INSTALL_DIR)
+	add_definitions(-DVK_EXAMPLE_DATA_DIR=\"${RESOURCE_INSTALL_DIR}/\")
+	install(DIRECTORY data/ DESTINATION ${RESOURCE_INSTALL_DIR}/)
+else()
+	add_definitions(-DVK_EXAMPLE_DATA_DIR=\"${CMAKE_SOURCE_DIR}/data/\")
+endif()
+
+# Compiler specific stuff
+IF(MSVC)
+	SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHsc")
+ELSEIF(APPLE)
+	if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
+		SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fobjc-arc -ObjC++")
+	ELSE()
+		SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fobjc-arc -xobjective-c++")
+	ENDIF()
+ENDIF(MSVC)
+
+IF(WIN32)
+	# Nothing here (yet)
+ELSEIF(APPLE)
+	link_libraries(${Vulkan_LIBRARY} "-framework AppKit" "-framework QuartzCore")
+ELSE(WIN32)
+	link_libraries(${XCB_LIBRARIES} ${Vulkan_LIBRARY} ${Vulkan_LIBRARY} ${DIRECTFB_LIBRARIES} ${WAYLAND_CLIENT_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
+ENDIF(WIN32)
+
+set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/")
+
+add_subdirectory(base)
+add_subdirectory(examples)
diff --git a/external/Vulkan/CREDITS.md b/external/Vulkan/CREDITS.md
new file mode 100644
index 0000000000000000000000000000000000000000..a6cd790fdbd4ac555ed0e37c0e4d84d61a9fb05e
--- /dev/null
+++ b/external/Vulkan/CREDITS.md
@@ -0,0 +1,19 @@
+## Credits
+Thanks to the authors of these libraries :
+- [OpenGL Mathematics (GLM)](https://github.com/g-truc/glm)
+
+Thanks to Ben Clayton from Google LLC for contributing the [HLSL shaders](data/hlsl).
+
+And a huge thanks to the Vulkan Working Group, Vulkan Advisory Panel, the fine people at [LunarG](http://www.lunarg.com), Baldur Karlsson ([RenderDoc](https://github.com/baldurk/renderdoc)) and everyone from the different IHVs that helped me get the examples up and working on their hardware!
+
+## Attributions / Licenses
+Please note that (some) models and textures use separate licenses. Please comply to these when redistributing or using them in your own projects :
+- Cubemap used in cubemap example by [Emil Persson(aka Humus)](http://www.humus.name/)
+- Armored knight model used in deferred example by [Gabriel Piacenti](http://opengameart.org/users/piacenti)
+- Old deer model used in tessellation example by [Čestmír Dammer](http://opengameart.org/users/cdmir)
+- Hidden treasure scene used in pipeline and debug marker examples by [Laurynas Jurgila](http://www.blendswap.com/user/PigArt)
+- Textures used in some examples by [Hugues Muller](http://www.yughues-folio.com)
+- Cerberus gun model used in PBR sample by [Andrew Maximov](http://artisaverb.info/Cerberus.html)
+- Updated compute particle system shader by [Lukas Bergdoll](https://github.com/Voultapher)
+- Vulkan scene model (and derived models) by [Dominic Agoro-Ombaka](http://www.agorodesign.com/) and [Sascha Willems](http://www.saschawillems.de)
+- Vulkan and the Vulkan logo are trademarks of the [Khronos Group Inc.](http://www.khronos.org)
\ No newline at end of file
diff --git a/external/Vulkan/LICENSE.md b/external/Vulkan/LICENSE.md
new file mode 100644
index 0000000000000000000000000000000000000000..2c4cfd5b79da02f28b91dc25819205196ee1819f
--- /dev/null
+++ b/external/Vulkan/LICENSE.md
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 Sascha Willems
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/external/Vulkan/README.md b/external/Vulkan/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..dd9ff82d385d0304c58a2302e2175e0bc411eceb
--- /dev/null
+++ b/external/Vulkan/README.md
@@ -0,0 +1,439 @@
+# Vulkan C++ examples and demos
+
+A comprehensive collection of open source C++ examples for [Vulkan®](https://www.khronos.org/vulkan/), the new generation graphics and compute API from Khronos.
+
+[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=BHXPMV6ZKPH9E)
+
+## Table of Contents
++ [Official Khronos Vulkan Samples](#official-khronos-vulkan-samples)
++ [Cloning](#Cloning)
++ [Assets](#Assets)
++ [Building](#Building)
++ [Running](#Running)
++ [Shaders](#Shaders)
++ [Examples](#Examples)
+    + [Basics](#Basics)
+    + [glTF](#glTF)
+    + [Advanced](#Advanced)
+    + [Performance](#Performance)
+    + [Physically Based Rendering](#physically-based-rendering)
+    + [Deferred](#Deferred)
+    + [Compute Shader](#compute-shader)
+    + [Geometry Shader](#geometry-shader)
+    + [Tessellation Shader](#tessellation-shader)
+    + [Hardware accelerated ray tracing](#hardware-accelerated-ray-tracing)
+    + [Headless](#Headless)
+    + [User Interface](#user-interface)
+    + [Effects](#Effects)
+    + [Extensions](#Extensions)
+    + [Misc](#Misc)
++ [Credits and Attributions](#credits-and-attributions)
+
+## Official Khronos Vulkan Samples
+
+Khronos recently made an official Vulkan Samples repository available to the public ([press release](https://www.khronos.org/blog/vulkan-releases-unified-samples-repository?utm_source=Khronos%20Blog&utm_medium=Twitter&utm_campaign=Vulkan%20Repository)).
+
+You can find this repository at https://github.com/KhronosGroup/Vulkan-Samples
+
+As I've been involved with getting the official repository up and running, I'll be mostly contributing to that repository from now, but may still add samples that don't fit there in here and I'll of course continue to maintain these samples.
+
+## Cloning
+This repository contains submodules for external dependencies, so when doing a fresh clone you need to clone recursively:
+
+```
+git clone --recursive https://github.com/SaschaWillems/Vulkan.git
+```
+
+Existing repositories can be updated manually:
+
+```
+git submodule init
+git submodule update
+```
+
+## Assets
+Many examples require assets from the asset pack that is not part of this repository due to file size. A python script is included to download the asset pack that. Run
+
+    python download_assets.py
+
+from the root of the repository after cloning or see [this](data/README.md) for manual download.
+
+## Building
+
+The repository contains everything required to compile and build the examples on <img src="./images/windowslogo.png" alt="" height="22px" valign="bottom"> Windows, <img src="./images/linuxlogo.png" alt="" height="24px" valign="bottom"> Linux, <img src="./images/androidlogo.png" alt="" height="24px" valign="bottom"> Android, <img src="./images/applelogo.png" alt="" valign="bottom" height="24px"> iOS and macOS (using MoltenVK) using a C++ compiler that supports C++11.
+
+See [BUILD.md](BUILD.md) for details on how to build for the different platforms.
+
+## Running
+
+Once built, examples can be run from the bin directory. The list of available command line options can be brought up with `--help`:
+```
+ -v, --validation: Enable validation layers
+ -br, --benchruntime: Set duration time for benchmark mode in seconds
+ -vs, --vsync: Enable V-Sync
+ -w, --width: Set window width
+ -f, --fullscreen: Start in fullscreen mode
+ --help: Show help
+ -h, --height: Set window height
+ -bt, --benchframetimes: Save frame times to benchmark results file
+ -s, --shaders: Select shader type to use (glsl or hlsl)
+ -b, --benchmark: Run example in benchmark mode
+ -g, --gpu: Select GPU to run on
+ -bf, --benchfilename: Set file name for benchmark results
+ -gl, --listgpus: Display a list of available Vulkan devices
+ -bw, --benchwarmup: Set warmup time for benchmark mode in seconds
+```
+
+Note that some examples require specific device features, and if you are on a multi-gpu system you might need to use the `-gl` and `-g` to select a gpu that supports them.
+
+## Shaders
+
+Vulkan consumes shaders in an intermediate representation called SPIR-V. This makes it possible to use different shader languages by compiling them to that bytecode format. The primary shader language used here is [GLSL](data/shaders/glsl) but thanks to an external contribution you'll also find [HLSL](data/shaders/hlsl) shader sources.
+
+## Examples
+
+### Basics
+
+#### [First triangle](examples/triangle/)
+Basic and verbose example for getting a colored triangle rendered to the screen using Vulkan. This is meant as a starting point for learning Vulkan from the ground up. A huge part of the code is boilerplate that is abstracted away in later examples.
+
+#### [Pipelines](examples/pipelines/)
+
+Using pipeline state objects (pso) that bake state information (rasterization states, culling modes, etc.) along with the shaders into a single object, making it easy for an implementation to optimize usage (compared to OpenGL's dynamic state machine). Also demonstrates the use of pipeline derivatives.
+
+#### [Descriptor sets](examples/descriptorsets)
+
+Descriptors are used to pass data to shader binding points. Sets up descriptor sets, layouts, pools, creates a single pipeline based on the set layout and renders multiple objects with different descriptor sets.
+
+#### [Dynamic uniform buffers](examples/dynamicuniformbuffer/)
+
+Dynamic uniform buffers are used for rendering multiple objects with multiple matrices stored in a single uniform buffer object. Individual matrices are dynamically addressed upon descriptor binding time, minimizing the number of required descriptor sets.
+
+#### [Push constants](examples/pushconstants/)
+
+Uses push constants, small blocks of uniform data stored within a command buffer, to pass data to a shader without the need for uniform buffers.
+
+#### [Specialization constants](examples/specializationconstants/)
+
+Uses SPIR-V specialization constants to create multiple pipelines with different lighting paths from a single "uber" shader.
+
+#### [Texture mapping](examples/texture/)
+
+Loads a 2D texture from disk (including all mip levels), uses staging to upload it into video memory and samples from it using combined image samplers.
+
+#### [Texture arrays](examples/texturearray/)
+
+Loads a 2D texture array containing multiple 2D texture slices (each with its own mip chain) and renders multiple meshes each sampling from a different layer of the texture. 2D texture arrays don't do any interpolation between the slices.
+
+#### [Cube map textures](examples/texturecubemap/)
+
+Loads a cube map texture from disk containing six different faces. All faces and mip levels are uploaded into video memory, and the cubemap is displayed on a skybox as a backdrop and on a 3D model as a reflection.
+
+#### [Cube map arrays](examples/texturecubemaparray/)
+
+Loads an array of cube map textures from a single file. All cube maps are uploaded into video memory with their faces and mip levels, and the selected cubemap is displayed on a skybox as a backdrop and on a 3D model as a reflection.
+
+#### [3D textures](examples/texture3d/)
+
+Generates a 3D texture on the cpu (using perlin noise), uploads it to the device and samples it to render an animation. 3D textures store volumetric data and interpolate in all three dimensions.
+
+#### [Input attachments](examples/inputattachments)
+
+Uses input attachments to read framebuffer contents from a previous sub pass at the same pixel position within a single render pass. This can be used for basic post processing or image composition ([blog entry](https://www.saschawillems.de/tutorials/vulkan/input_attachments_subpasses)).
+
+#### [Sub passes](examples/subpasses/)
+
+Advanced example that uses sub passes and input attachments to write and read back data from framebuffer attachments (same location only) in single render pass. This is used to implement deferred render composition with added forward transparency in a single pass.
+
+#### [Offscreen rendering](examples/offscreen/)
+
+Basic offscreen rendering in two passes. First pass renders the mirrored scene to a separate framebuffer with color and depth attachments, second pass samples from that color attachment for rendering a mirror surface.
+
+#### [CPU particle system](examples/particlefire/)
+
+Implements a simple CPU based particle system. Particle data is stored in host memory, updated on the CPU per-frame and synchronized with the device before it's rendered using pre-multiplied alpha.
+
+#### [Stencil buffer](examples/stencilbuffer/)
+
+Uses the stencil buffer and its compare functionality for rendering a 3D model with dynamic outlines.
+
+### glTF
+
+These samples show how implement different features of the [glTF 2.0 3D format](https://www.khronos.org/gltf/) 3D transmission file format in detail.
+
+#### [glTF model loading and rendering](examples/gltfloading/)
+
+Shows how to load a complete scene from a [glTF 2.0](https://github.com/KhronosGroup/glTF) file. The structure of the glTF 2.0 scene is converted into the data structures required to render the scene with Vulkan.
+
+#### [glTF vertex skinning](examples/gltfskinning/)
+
+Demonstrates how to do GPU vertex skinning from animation data stored in a [glTF 2.0](https://github.com/KhronosGroup/glTF) model. Along with reading all the data structures required for doing vertex skinning, the sample also shows how to upload animation data to the GPU and how to render it using shaders.
+
+#### [glTF scene rendering](examples/gltfscenerendering/)
+
+Renders a complete scene loaded from an [glTF 2.0](https://github.com/KhronosGroup/glTF) file. The sample is based on the glTF model loading sample, and adds data structures, functions and shaders required to render a more complex scene using Crytek's Sponza model with per-material pipelines and normal mapping.
+
+### Advanced
+
+#### [Multi sampling](examples/multisampling/)
+
+Implements multisample anti-aliasing (MSAA) using a renderpass with multisampled attachments and resolve attachments that get resolved into the visible frame buffer.
+
+#### [High dynamic range](examples/hdr/)
+
+Implements a high dynamic range rendering pipeline using 16/32 bit floating point precision for all internal formats, textures and calculations, including a bloom pass, manual exposure and tone mapping.
+
+#### [Shadow mapping](examples/shadowmapping/)
+
+Rendering shadows for a directional light source. First pass stores depth values from the light's pov, second pass compares against these to check if a fragment is shadowed. Uses depth bias to avoid shadow artifacts and applies a PCF filter to smooth shadow edges.
+
+#### [Cascaded shadow mapping](examples/shadowmappingcascade/)
+
+Uses multiple shadow maps (stored as a layered texture) to increase shadow resolution for larger scenes. The camera frustum is split up into multiple cascades with corresponding layers in the shadow map. Layer selection for shadowing depth compare is then done by comparing fragment depth with the cascades' depths ranges.
+
+#### [Omnidirectional shadow mapping](examples/shadowmappingomni/)
+
+Uses a dynamic floating point cube map to implement shadowing for a point light source that casts shadows in all directions. The cube map is updated every frame and stores distance to the light source for each fragment used to determine if a fragment is shadowed.
+
+#### [Run-time mip-map generation](examples/texturemipmapgen/)
+
+Generating a complete mip-chain at runtime instead of loading it from a file, by blitting from one mip level, starting with the actual texture image, down to the next smaller size until the lower 1x1 pixel end of the mip chain.
+
+#### [Capturing screenshots](examples/screenshot/)
+
+Capturing and saving an image after a scene has been rendered using blits to copy the last swapchain image from optimal device to host local linear memory, so that it can be stored into a ppm image.
+
+#### [Order Independent Transparency](examples/oit)
+
+Implements order independent transparency based on linked lists. To achieve this, the sample uses storage buffers in combination with image load and store atomic operations in the fragment shader.
+
+### Performance
+
+#### [Multi threaded command buffer generation](examples/multithreading/)
+
+Multi threaded parallel command buffer generation. Instead of prebuilding and reusing the same command buffers this sample uses multiple hardware threads to demonstrate parallel per-frame recreation of secondary command buffers that are executed and submitted in a primary buffer once all threads have finished.
+
+#### [Instancing](examples/instancing/)
+
+Uses the instancing feature for rendering many instances of the same mesh from a single vertex buffer with variable parameters and textures (indexing a layered texture). Instanced data is passed using a secondary vertex buffer.
+
+#### [Indirect drawing](examples/indirectdraw/)
+
+Rendering thousands of instanced objects with different geometry using one single indirect draw call instead of issuing separate draws. All draw commands to be executed are stored in a dedicated indirect draw buffer object (storing index count, offset, instance count, etc.) that is uploaded to the device and sourced by the indirect draw command for rendering.
+
+#### [Occlusion queries](examples/occlusionquery/)
+
+Using query pool objects to get number of passed samples for rendered primitives got determining on-screen visibility.
+
+#### [Pipeline statistics](examples/pipelinestatistics/)
+
+Using query pool objects to gather statistics from different stages of the pipeline like vertex, fragment shader and tessellation evaluation shader invocations depending on payload.
+
+### Physically Based Rendering
+
+Physical based rendering as a lighting technique that achieves a more realistic and dynamic look by applying approximations of bidirectional reflectance distribution functions based on measured real-world material parameters and environment lighting.
+
+#### [PBR basics](examples/pbrbasic/)
+
+Demonstrates a basic specular BRDF implementation with solid materials and fixed light sources on a grid of objects with varying material parameters, demonstrating how metallic reflectance and surface roughness affect the appearance of pbr lit objects.
+
+#### [PBR image based lighting](examples/pbribl/)
+
+Adds image based lighting from an hdr environment cubemap to the PBR equation, using the surrounding environment as the light source. This adds an even more realistic look the scene as the light contribution used by the materials is now controlled by the environment. Also shows how to generate the BRDF 2D-LUT and irradiance and filtered cube maps from the environment map.
+
+#### [Textured PBR with IBL](examples/pbrtexture/)
+
+Renders a model specially crafted for a metallic-roughness PBR workflow with textures defining material parameters for the PRB equation (albedo, metallic, roughness, baked ambient occlusion, normal maps) in an image based lighting environment.
+
+### Deferred
+
+These examples use a [deferred shading](https://en.wikipedia.org/wiki/Deferred_shading) setup.
+
+#### [Deferred shading basics](examples/deferred/)
+
+Uses multiple render targets to fill all attachments (albedo, normals, position, depth) required for a G-Buffer in a single pass. A deferred pass then uses these to calculate shading and lighting in screen space, so that calculations only have to be done for visible fragments independent of no. of lights.
+
+#### [Deferred multi sampling](examples/deferredmultisampling/)
+
+Adds multi sampling to a deferred renderer using manual resolve in the fragment shader.
+
+#### [Deferred shading shadow mapping](examples/deferredshadows/)
+
+Adds shadows from multiple spotlights to a deferred renderer using a layered depth attachment filled in one pass using multiple geometry shader invocations.
+
+#### [Screen space ambient occlusion](examples/ssao/)
+
+Adds ambient occlusion in screen space to a 3D scene. Depth values from a previous deferred pass are used to generate an ambient occlusion texture that is blurred before being applied to the scene in a final composition path.
+
+### Compute Shader
+
+#### [Image processing](examples/computeshader/)
+
+Uses a compute shader along with a separate compute queue to apply different convolution kernels (and effects) on an input image in realtime.
+
+#### [GPU particle system](examples/computeparticles/)
+
+Attraction based 2D GPU particle system using compute shaders. Particle data is stored in a shader storage buffer and only modified on the GPU using memory barriers for synchronizing compute particle updates with graphics pipeline vertex access.
+
+#### [N-body simulation](examples/computenbody/)
+
+N-body simulation based particle system with multiple attractors and particle-to-particle interaction using two passes separating particle movement calculation and final integration. Shared compute shader memory is used to speed up compute calculations.
+
+#### [Ray tracing](examples/computeraytracing/)
+
+Simple GPU ray tracer with shadows and reflections using a compute shader. No scene geometry is rendered in the graphics pass.
+
+#### [ Cloth simulation](examples/computecloth/)
+
+Mass-spring based cloth system on the GPU using a compute shader to calculate and integrate spring forces, also implementing basic collision with a fixed scene object.
+
+#### [Cull and LOD](examples/computecullandlod/)
+
+Purely GPU based frustum visibility culling and level-of-detail system. A compute shader is used to modify draw commands stored in an indirect draw commands buffer to toggle model visibility and select its level-of-detail based on camera distance, no calculations have to be done on and synced with the CPU.
+
+### Geometry Shader
+
+#### [Normal debugging](examples/geometryshader/)
+
+Visualizing per-vertex model normals (for debugging). First pass renders the plain model, second pass uses a geometry shader to generate colored lines based on per-vertex model normals,
+
+#### [Viewport arrays](examples/viewportarray/)
+
+Renders a scene to multiple viewports in one pass using a geometry shader to apply different matrices per viewport to simulate stereoscopic rendering (left/right). Requires a device with support for ```multiViewport```.
+
+### Tessellation Shader
+
+#### [Displacement mapping](examples/displacement/)
+
+Uses a height map to dynamically generate and displace additional geometric detail for a low-poly mesh.
+
+#### [Dynamic terrain tessellation](examples/terraintessellation/)
+
+Renders a terrain using tessellation shaders for height displacement (based on a 16-bit height map), dynamic level-of-detail (based on triangle screen space size) and per-patch frustum culling.
+
+#### [Model tessellation](examples/tessellation/)
+
+Uses curved PN-triangles ([paper](http://alex.vlachos.com/graphics/CurvedPNTriangles.pdf)) for adding details to a low-polygon model.
+
+### Hardware accelerated ray tracing
+
+#### [Basic ray tracing](examples/raytracingbasic)
+
+Basic example for doing hardware accelerated ray tracing using the ```VK_KHR_acceleration_structure``` and ```VK_KHR_ray_tracing_pipeline``` extensions. Shows how to setup acceleration structures, ray tracing pipelines and the shader binding table needed to do the actual ray tracing.
+
+#### [Ray traced shadows](examples/raytracingshadows)
+
+Adds ray traced shadows casting using the new ray tracing extensions to a more complex scene. Shows how to add multiple hit and miss shaders and how to modify existing shaders to add shadow calculations.
+
+#### [Ray traced reflections](examples/raytracingreflections)
+
+Renders a complex scene with reflective surfaces using the new ray tracing extensions. Shows how to do recursion inside of the ray tracing shaders for implementing real time reflections.
+
+#### [Callable ray tracing shaders](examples/raytracingcallable)
+
+Callable shaders can be dynamically invoked from within other ray tracing shaders to execute different shaders based on dynamic conditions. The example ray traces multiple geometries, with each calling a different callable shader from the closest hit shader.
+
+#### [Ray query](examples/rayquery)
+
+Ray queries add acceleration structure intersection functionality to non ray tracing shader stages. This allows for combining ray tracing with rasterization. This example makes uses ray queries to add ray casted shadows to a rasterized sample in the fragment shader.
+
+### Headless
+
+Examples that run one-time tasks and don't make use of visual output (no window system integration). These can be run in environments where no user interface is available ([blog entry](https://www.saschawillems.de/tutorials/vulkan/headless_examples)).
+
+#### [Render](examples/renderheadless)
+
+Renders a basic scene to a (non-visible) frame buffer attachment, reads it back to host memory and stores it to disk without any on-screen presentation, showing proper use of memory barriers required for device to host image synchronization.
+
+#### [Compute](examples/computeheadless)
+
+Only uses compute shader capabilities for running calculations on an input data set (passed via SSBO). A fibonacci row is calculated based on input data via the compute shader, stored back and displayed via command line.
+
+### User Interface
+
+#### [Text rendering](examples/textoverlay/)
+
+Load and render a 2D text overlay created from the bitmap glyph data of a [stb font file](https://nothings.org/stb/font/). This data is uploaded as a texture and used for displaying text on top of a 3D scene in a second pass.
+
+#### [Distance field fonts](examples/distancefieldfonts/)
+
+Uses a texture that stores signed distance field information per character along with a special fragment shader calculating output based on that distance data. This results in crisp high quality font rendering independent of font size and scale.
+
+#### [ImGui overlay](examples/imgui/)
+
+Generates and renders a complex user interface with multiple windows, controls and user interaction on top of a 3D scene. The UI is generated using [Dear ImGUI](https://github.com/ocornut/imgui) and updated each frame.
+
+### Effects
+
+#### [Fullscreen radial blur](examples/radialblur/)
+
+Demonstrates the basics of fullscreen shader effects. The scene is rendered into an offscreen framebuffer at lower resolution and rendered as a fullscreen quad atop the scene using a radial blur fragment shader.
+
+#### [Bloom](examples/bloom/)
+
+Advanced fullscreen effect example adding a bloom effect to a scene. Glowing scene parts are rendered to a low res offscreen framebuffer that is applied atop the scene using a two pass separated gaussian blur.
+
+#### [Parallax mapping](examples/parallaxmapping/)
+
+Implements multiple texture mapping methods to simulate depth based on texture information: Normal mapping, parallax mapping, steep parallax mapping and parallax occlusion mapping (best quality, worst performance).
+
+#### [Spherical environment mapping](examples/sphericalenvmapping/)
+
+Uses a spherical material capture texture array defining environment lighting and reflection information to fake complex lighting.
+
+### Extensions
+
+#### [Conservative rasterization (VK_EXT_conservative_rasterization)](examples/conservativeraster/)
+
+Uses conservative rasterization to change the way fragments are generated by the gpu. The example enables overestimation to generate fragments for every pixel touched instead of only pixels that are fully covered ([blog post](https://www.saschawillems.de/tutorials/vulkan/conservative_rasterization)).
+
+#### [Push descriptors (VK_KHR_push_descriptor)](examples/pushdescriptors/)
+
+Uses push descriptors apply the push constants concept to descriptor sets. Instead of creating per-object descriptor sets for rendering multiple objects, this example passes descriptors at command buffer creation time.
+
+#### [Inline uniform blocks (VK_EXT_inline_uniform_block)](examples/inlineuniformblocks/)
+
+Makes use of inline uniform blocks to pass uniform data directly at descriptor set creation time and also demonstrates how to update data for those descriptors at runtime.
+
+#### [Multiview rendering (VK_KHR_multiview)](examples/multiview/)
+
+Renders a scene to to multiple views (layers) of a single framebuffer to simulate stereoscopic rendering in one pass. Broadcasting to the views is done in the vertex shader using ```gl_ViewIndex```.
+
+#### [Conditional rendering (VK_EXT_conditional_rendering)](examples/conditionalrender)
+
+Demonstrates the use of VK_EXT_conditional_rendering to conditionally dispatch render commands based on values from a dedicated buffer. This allows e.g. visibility toggles without having to rebuild command buffers ([blog post](https://www.saschawillems.de/tutorials/vulkan/conditional_rendering)).
+
+#### [Debug markers (VK_EXT_debug_marker)](examples/debugmarker/)
+
+<span style="color:red">This sample is deprecated</span>
+
+An updated version using ```VK_EXT_debug_utils``` along with an in-depth tutorial is available in the [Official Khronos Vulkan Samples repository](https://github.com/KhronosGroup/Vulkan-Samples/blob/master/samples/extensions/debug_utils).
+
+#### [Negative viewport height (VK_KHR_Maintenance1 or Vulkan 1.1)](examples/negativeviewportheight/)
+
+Shows how to render a scene using a negative viewport height, making the Vulkan render setup more similar to other APIs like OpenGL. Also has several options for changing relevant pipeline state, and displaying meshes with OpenGL or Vulkan style coordinates. Details can be found in [this tutorial](https://www.saschawillems.de/tutorials/vulkan/flipping-viewport).
+
+#### [Variable rate shading (VK_NV_shading_rate_image)](examples/variablerateshading/)
+
+Uses a special image that contains variable shading rates to vary the number of fragment shader invocations across the framebuffer. This makes it possible to lower fragment shader invocations for less important/less noisy parts of the framebuffer.
+
+#### [Descriptor indexing (VK_EXT_descriptor_indexing)](examples/descriptorindexing/)  
+
+Demonstrates the use of VK_EXT_descriptor_indexing for creating descriptor sets with a variable size that can be dynamically indexed in a shader using `GL_EXT_nonuniform_qualifier` and `SPV_EXT_descriptor_indexing`.
+
+#### [Dynamic rendering (VK_KHR_dynamic_rendering)](examples/dynamicrendering/)
+
+Shows usage of the VK_KHR_dynamic_rendering extension, which simplifies the rendering setup by no longer requiring render pass objects or framebuffers.
+
+### Misc
+
+#### [Vulkan Gears](examples/gears/)
+
+Vulkan interpretation of glxgears. Procedurally generates and animates multiple gears.
+
+#### [Vulkan demo scene](examples/vulkanscene/)
+
+Renders a Vulkan demo scene with logos and mascots. Not an actual example but more of a playground and showcase.
+
+## Credits and Attributions
+See [CREDITS.md](CREDITS.md) for additional credits and attributions.
diff --git a/external/Vulkan/android/.gitignore b/external/Vulkan/android/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..8db79c32973cbfbfcdfd9821a86c1f227493b300
--- /dev/null
+++ b/external/Vulkan/android/.gitignore
@@ -0,0 +1,68 @@
+# Built application files
+*.apk
+*.ap_
+
+# Files for the ART/Dalvik VM
+*.dex
+
+# Java class files
+*.class
+
+# Generated files
+bin/
+gen/
+out/
+
+# Gradle files
+.gradle/
+build/
+
+# Local configuration file (sdk path, etc)
+local.properties
+
+# Proguard folder generated by Eclipse
+proguard/
+
+# Log Files
+*.log
+
+# Android Studio Navigation editor temp files
+.navigation/
+
+# Android Studio captures folder
+captures/
+
+# IntelliJ
+*.iml
+.idea/workspace.xml
+.idea/tasks.xml
+.idea/gradle.xml
+.idea/assetWizardSettings.xml
+.idea/dictionaries
+.idea/libraries
+.idea/caches
+
+# Keystore files
+# Uncomment the following line if you do not want to check your keystore files in.
+#*.jks
+
+# External native build folder generated in Android Studio 2.2 and later
+.externalNativeBuild
+
+# Google Services (e.g. APIs or Firebase)
+google-services.json
+
+# Freeline
+freeline.py
+freeline/
+freeline_project_description.json
+
+# fastlane
+fastlane/report.xml
+fastlane/Preview.html
+fastlane/screenshots
+fastlane/test_output
+fastlane/readme.md
+
+**/assets/
+**/src/main/res/drawable/
\ No newline at end of file
diff --git a/external/Vulkan/android/build.gradle b/external/Vulkan/android/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..9d09ac330cd33a4de9b5b699ea5d2f7be736ba6d
--- /dev/null
+++ b/external/Vulkan/android/build.gradle
@@ -0,0 +1,20 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+    repositories {
+        google()
+        jcenter()
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:3.4.2'
+        // NOTE: Do not place your application dependencies here; they belong
+        // in the individual module build.gradle files
+    }
+}
+
+allprojects {
+    repositories {
+        google()
+        jcenter()
+    }
+}
\ No newline at end of file
diff --git a/external/Vulkan/android/common/res/drawable/icon.png b/external/Vulkan/android/common/res/drawable/icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..882f2a59c04ddf1f99a5fc209d57b4472f8dbb36
Binary files /dev/null and b/external/Vulkan/android/common/res/drawable/icon.png differ
diff --git a/external/Vulkan/android/examples/_template/CMakeLists.txt b/external/Vulkan/android/examples/_template/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..d796d7e4fa283d8df5a39c09debd703ca7c0001f
--- /dev/null
+++ b/external/Vulkan/android/examples/_template/CMakeLists.txt
@@ -0,0 +1,34 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME %EXAMPLE_FOLDER%)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/_template/build.gradle b/external/Vulkan/android/examples/_template/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..ffe46fed24c05d2d465c90ff9b569c8b2fcd031c
--- /dev/null
+++ b/external/Vulkan/android/examples/_template/build.gradle
@@ -0,0 +1,54 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.%PACKAGE_NAME%"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+%ASSET_COPY%
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/_template/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/_template/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..911142b2b7182aafffc904d6b556364b6df1e63d
--- /dev/null
+++ b/external/Vulkan/android/examples/_template/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.%PACKAGE_NAME%">
+
+    <application
+        android:label="%APP_LABEL%"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/_template/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/_template/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/_template/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/base/CMakeLists.txt b/external/Vulkan/android/examples/base/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..e2dc6c4285b9e69ea3d66e5187be8baa5ef4530e
--- /dev/null
+++ b/external/Vulkan/android/examples/base/CMakeLists.txt
@@ -0,0 +1,39 @@
+file(GLOB BASE_SRC "../../../base/*.cpp" "../../../external/imgui/*.cpp")
+
+add_library(libbase SHARED ${BASE_SRC})
+
+include_directories(${BASE_DIR})
+include_directories(../../../external)
+include_directories(../../../external/glm)
+include_directories(../../../external/gli)
+include_directories(../../../external/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+set(KTX_DIR ../../../external/ktx)
+set(KTX_SOURCES
+	${KTX_DIR}/lib/texture.c
+	${KTX_DIR}/lib/hashlist.c
+	${KTX_DIR}/lib/checkheader.c
+	${KTX_DIR}/lib/swap.c
+	${KTX_DIR}/lib/memstream.c
+	${KTX_DIR}/lib/filestream.c
+)
+set(KTX_INCLUDE
+	${KTX_DIR}/include
+	${KTX_DIR}/lib
+	${KTX_DIR}/other_include
+)
+
+add_library(libktx ${KTX_SOURCES})
+target_include_directories(libktx PUBLIC ${KTX_INCLUDE})
+set_property(TARGET libktx PROPERTY FOLDER "external")
+
+
+target_link_libraries(
+	libbase
+	android
+	log
+	z
+	libktx
+)
diff --git a/external/Vulkan/android/examples/bloom/CMakeLists.txt b/external/Vulkan/android/examples/bloom/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..91810cef568155d178c7f4ea44c64aa62814dd68
--- /dev/null
+++ b/external/Vulkan/android/examples/bloom/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME bloom)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/bloom/build.gradle b/external/Vulkan/android/examples/bloom/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..98f6545da6a4ee544dd3021e0b4371a76683f0ef
--- /dev/null
+++ b/external/Vulkan/android/examples/bloom/build.gradle
@@ -0,0 +1,84 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanBloom"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/bloom'
+       into 'assets/shaders/glsl/bloom'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'retroufo.gltf'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'retroufo_glow.gltf'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'cube.gltf'
+    }
+
+    copy {
+       from '../../../data/textures'
+       into 'assets/textures'
+       include 'cubemap_space.ktx'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/bloom/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/bloom/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..07962bd41318999f207b1468048e7beb81bcc9fc
--- /dev/null
+++ b/external/Vulkan/android/examples/bloom/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanBloom">
+
+    <application
+        android:label="Vulkan bloom effect"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/bloom/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/bloom/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/bloom/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/computecloth/CMakeLists.txt b/external/Vulkan/android/examples/computecloth/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..6c358fbf1b85d0426d6247b23baed4277595b109
--- /dev/null
+++ b/external/Vulkan/android/examples/computecloth/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME computecloth)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/computecloth/build.gradle b/external/Vulkan/android/examples/computecloth/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..d64d0a7e835eaf7f924c20aa84245a76e551b0ee
--- /dev/null
+++ b/external/Vulkan/android/examples/computecloth/build.gradle
@@ -0,0 +1,72 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanComputecloth"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/computecloth'
+       into 'assets/shaders/glsl/computecloth'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'sphere.gltf'
+    }
+
+    copy {
+       from '../../../data/textures'
+       into 'assets/textures'
+       include 'vulkan_cloth_rgba.ktx'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/computecloth/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/computecloth/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..77117b5243f08d4897b18cc8c6e4d4b85dc9d0ae
--- /dev/null
+++ b/external/Vulkan/android/examples/computecloth/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanComputecloth">
+
+    <application
+        android:label="Vulkan compute cloth simulation"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/computecloth/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/computecloth/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/computecloth/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/computecullandlod/CMakeLists.txt b/external/Vulkan/android/examples/computecullandlod/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..93e8caaaf6877b112f68f7dbedb713e2a24c2daa
--- /dev/null
+++ b/external/Vulkan/android/examples/computecullandlod/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME computecullandlod)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/computecullandlod/build.gradle b/external/Vulkan/android/examples/computecullandlod/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..1e99801a5215879d2f0a181fdbc0809a6c7922a8
--- /dev/null
+++ b/external/Vulkan/android/examples/computecullandlod/build.gradle
@@ -0,0 +1,66 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanComputecullandlod"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/computecullandlod'
+       into 'assets/shaders/glsl/computecullandlod'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'suzanne_lods.gltf'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/computecullandlod/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/computecullandlod/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..3d8f09f993f867913d053465d149c960ebf24d67
--- /dev/null
+++ b/external/Vulkan/android/examples/computecullandlod/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanComputecullandlod">
+
+    <application
+        android:label="Vulkan compute shader cull and LOD"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/computecullandlod/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/computecullandlod/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/computecullandlod/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/computeheadless/CMakeLists.txt b/external/Vulkan/android/examples/computeheadless/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..67c6303d06c6a582adf99618357d90f9ed1df21c
--- /dev/null
+++ b/external/Vulkan/android/examples/computeheadless/CMakeLists.txt
@@ -0,0 +1,34 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME computeheadless)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/computeheadless/build.gradle b/external/Vulkan/android/examples/computeheadless/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..d184269a7d7b871832e8d768f39ca88a293db04a
--- /dev/null
+++ b/external/Vulkan/android/examples/computeheadless/build.gradle
@@ -0,0 +1,60 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanComputeheadless"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/computeheadless'
+       into 'assets/shaders/glsl/computeheadless'
+       include '*.*'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/computeheadless/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/computeheadless/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..ff2891973bc257f137a33a6854523c006e17ba68
--- /dev/null
+++ b/external/Vulkan/android/examples/computeheadless/src/main/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanComputeheadless">
+
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+    <application
+        android:label="Vulkan headless compute"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/computeheadless/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/computeheadless/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/computeheadless/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/computenbody/CMakeLists.txt b/external/Vulkan/android/examples/computenbody/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..4bff99aa261ac367f86f051467635178032d4ecb
--- /dev/null
+++ b/external/Vulkan/android/examples/computenbody/CMakeLists.txt
@@ -0,0 +1,34 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME computenbody)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/computenbody/build.gradle b/external/Vulkan/android/examples/computenbody/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..2cf5b6653cb39e8f9cf499a97276e6eeeeae0da4
--- /dev/null
+++ b/external/Vulkan/android/examples/computenbody/build.gradle
@@ -0,0 +1,72 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanComputenbody"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/computenbody'
+       into 'assets/shaders/glsl/computenbody'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/textures'
+       into 'assets/textures'
+       include 'particle01_rgba.ktx'
+    }
+
+    copy {
+       from '../../../data/textures'
+       into 'assets/textures'
+       include 'particle_gradient_rgba.ktx'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/computenbody/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/computenbody/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..d6a1348ab13dd7f48642a1ef97635be91eee1373
--- /dev/null
+++ b/external/Vulkan/android/examples/computenbody/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanComputenbody">
+
+    <application
+        android:label="Vulkan compute N-Body simulation"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/computenbody/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/computenbody/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/computenbody/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/computeparticles/CMakeLists.txt b/external/Vulkan/android/examples/computeparticles/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..5b694b21a4a0042659a655894304258c639be9a3
--- /dev/null
+++ b/external/Vulkan/android/examples/computeparticles/CMakeLists.txt
@@ -0,0 +1,34 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME computeparticles)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/computeparticles/build.gradle b/external/Vulkan/android/examples/computeparticles/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..dacefcdeb0d0f034b0e7cfbbb0737218d9f59944
--- /dev/null
+++ b/external/Vulkan/android/examples/computeparticles/build.gradle
@@ -0,0 +1,72 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanComputeparticles"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/computeparticles'
+       into 'assets/shaders/glsl/computeparticles'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/textures'
+       into 'assets/textures'
+       include 'particle01_rgba.ktx'
+    }
+
+    copy {
+       from '../../../data/textures'
+       into 'assets/textures'
+       include 'particle_gradient_rgba.ktx'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/computeparticles/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/computeparticles/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..a8c15e55a240e2e408bbc39ac0cafea8dd38ac04
--- /dev/null
+++ b/external/Vulkan/android/examples/computeparticles/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanComputeparticles">
+
+    <application
+        android:label="Vulkan compute particle system"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/computeparticles/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/computeparticles/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/computeparticles/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/computeraytracing/CMakeLists.txt b/external/Vulkan/android/examples/computeraytracing/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..2799a827f245484b494c74c48440c42876daa766
--- /dev/null
+++ b/external/Vulkan/android/examples/computeraytracing/CMakeLists.txt
@@ -0,0 +1,34 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME computeraytracing)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/computeraytracing/build.gradle b/external/Vulkan/android/examples/computeraytracing/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..7646825d13ca88445974a159760f69a7decc84d5
--- /dev/null
+++ b/external/Vulkan/android/examples/computeraytracing/build.gradle
@@ -0,0 +1,60 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanComputeRaytracing"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/computeraytracing'
+       into 'assets/shaders/glsl/computeraytracing'
+       include '*.*'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/computeraytracing/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/computeraytracing/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..c7c94398336907940042b071bc3b9a35ee0eaca0
--- /dev/null
+++ b/external/Vulkan/android/examples/computeraytracing/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanComputeRaytracing">
+
+    <application
+        android:label="Vulkan compute raytracing"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/computeraytracing/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/computeraytracing/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/computeraytracing/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/computeshader/CMakeLists.txt b/external/Vulkan/android/examples/computeshader/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..1650bfa6d27c46329216b0c8a66ab64d1d6db121
--- /dev/null
+++ b/external/Vulkan/android/examples/computeshader/CMakeLists.txt
@@ -0,0 +1,34 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME computeshader)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/computeshader/build.gradle b/external/Vulkan/android/examples/computeshader/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..e89c3dcda306e4dd3db870560b01bdcc831f0220
--- /dev/null
+++ b/external/Vulkan/android/examples/computeshader/build.gradle
@@ -0,0 +1,66 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanComputeshader"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/computeshader'
+       into 'assets/shaders/glsl/computeshader'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/textures'
+       into 'assets/textures'
+       include 'vulkan_11_rgba.ktx'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/computeshader/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/computeshader/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..2f1fe254cbbf929e495d3469bfdbea8a5cb97c66
--- /dev/null
+++ b/external/Vulkan/android/examples/computeshader/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanComputeshader">
+
+    <application
+        android:label="Vulkan compute shaders"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/computeshader/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/computeshader/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/computeshader/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/conservativeraster/CMakeLists.txt b/external/Vulkan/android/examples/conservativeraster/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..c5dbae11c0d93702c17c02111383a2495d159633
--- /dev/null
+++ b/external/Vulkan/android/examples/conservativeraster/CMakeLists.txt
@@ -0,0 +1,34 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME conservativeraster)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/conservativeraster/build.gradle b/external/Vulkan/android/examples/conservativeraster/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..e875180a2cb87c1ea39e7248c0024ffc9386a1df
--- /dev/null
+++ b/external/Vulkan/android/examples/conservativeraster/build.gradle
@@ -0,0 +1,60 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanConservativeraster"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/conservativeraster'
+       into 'assets/shaders/glsl/conservativeraster'
+       include '*.*'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/conservativeraster/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/conservativeraster/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..168547131d375079d7cd0078cbe09143a3add1a2
--- /dev/null
+++ b/external/Vulkan/android/examples/conservativeraster/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanConservativeraster">
+
+    <application
+        android:label="Vulkan conservative rasterization"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/conservativeraster/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/conservativeraster/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/conservativeraster/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/debugmarker/CMakeLists.txt b/external/Vulkan/android/examples/debugmarker/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..45d38cbd0cc2c9719345b3c9f1b37315e9c497dd
--- /dev/null
+++ b/external/Vulkan/android/examples/debugmarker/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME debugmarker)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/debugmarker/build.gradle b/external/Vulkan/android/examples/debugmarker/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..cbec6d6d7ec0f6c5dceaab27579a9e10dfb5e4d5
--- /dev/null
+++ b/external/Vulkan/android/examples/debugmarker/build.gradle
@@ -0,0 +1,72 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanDebugmarker"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/debugmarker'
+       into 'assets/shaders/glsl/debugmarker'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'treasure_smooth.gltf'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'treasure_glow.gltf'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/debugmarker/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/debugmarker/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..8dd2194ad4e0e637ba50c2049c3668d4b435863c
--- /dev/null
+++ b/external/Vulkan/android/examples/debugmarker/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanDebugmarker">
+
+    <application
+        android:label="Vulkan debug markers"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/debugmarker/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/debugmarker/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/debugmarker/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/deferred/CMakeLists.txt b/external/Vulkan/android/examples/deferred/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..09fae9861db50d6b005c9c48e85cf114c92b1cef
--- /dev/null
+++ b/external/Vulkan/android/examples/deferred/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME deferred)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/deferred/build.gradle b/external/Vulkan/android/examples/deferred/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..119e181551b9a403775ebb4c0ed0f35a46fc909b
--- /dev/null
+++ b/external/Vulkan/android/examples/deferred/build.gradle
@@ -0,0 +1,84 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanDeferred"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/deferred'
+       into 'assets/shaders/glsl/deferred'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'deferred_floor.gltf'
+    }
+
+    copy {
+       from '../../../data/textures'
+       into 'assets/textures'
+       include 'stonefloor01_color_*.ktx'
+    }
+
+    copy {
+       from '../../../data/textures'
+       into 'assets/textures'
+       include 'stonefloor01_normal_*.ktx'
+    }
+
+    copy {
+       from '../../../data/models/armor'
+       into 'assets/models/armor'
+       include '*.*'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/deferred/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/deferred/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..b589066310d04f9947264417ffdfcf299f415d86
--- /dev/null
+++ b/external/Vulkan/android/examples/deferred/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanDeferred">
+
+    <application
+        android:label="Vulkan deferred renderer"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/deferred/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/deferred/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/deferred/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/deferredmultisampling/CMakeLists.txt b/external/Vulkan/android/examples/deferredmultisampling/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..65b46893313b8714e92e6772ed695c8aca3df910
--- /dev/null
+++ b/external/Vulkan/android/examples/deferredmultisampling/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME deferredmultisampling)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/deferredmultisampling/build.gradle b/external/Vulkan/android/examples/deferredmultisampling/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..0252e83d0c747eca8b86ae8dc391a1a7a0010670
--- /dev/null
+++ b/external/Vulkan/android/examples/deferredmultisampling/build.gradle
@@ -0,0 +1,84 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanDeferredmultisampling"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/deferredmultisampling'
+       into 'assets/shaders/glsl/deferredmultisampling'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'deferred_box.gltf'
+    }
+
+    copy {
+       from '../../../data/textures'
+       into 'assets/textures'
+       include 'stonefloor02_color_*.ktx'
+    }
+
+    copy {
+       from '../../../data/textures'
+       into 'assets/textures'
+       include 'stonefloor02_normal_*.ktx'
+    }
+
+    copy {
+       from '../../../data/models/armor'
+       into 'assets/models/armor'
+       include '*.*'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/deferredmultisampling/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/deferredmultisampling/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..0011ebe602f477afe26b4e93f9745b3066b2279f
--- /dev/null
+++ b/external/Vulkan/android/examples/deferredmultisampling/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanDeferredmultisampling">
+
+    <application
+        android:label="Vulkan deferred multi sampling"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/deferredmultisampling/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/deferredmultisampling/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/deferredmultisampling/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/deferredshadows/CMakeLists.txt b/external/Vulkan/android/examples/deferredshadows/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..bf1d5ba47a20a1152d16a35dbcd13ed66d717372
--- /dev/null
+++ b/external/Vulkan/android/examples/deferredshadows/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME deferredshadows)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/deferredshadows/build.gradle b/external/Vulkan/android/examples/deferredshadows/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..ac01f58584dc5356fa1c8996d4ba260071279318
--- /dev/null
+++ b/external/Vulkan/android/examples/deferredshadows/build.gradle
@@ -0,0 +1,84 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanDeferredshadows"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/deferredshadows'
+       into 'assets/shaders/glsl/deferredshadows'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'deferred_box.gltf'
+    }
+
+    copy {
+       from '../../../data/textures'
+       into 'assets/textures'
+       include 'stonefloor02_color_*.ktx'
+    }
+
+    copy {
+       from '../../../data/textures'
+       into 'assets/textures'
+       include 'stonefloor02_normal_*.ktx'
+    }
+
+    copy {
+       from '../../../data/models/armor'
+       into 'assets/models/armor'
+       include '*.*'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/deferredshadows/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/deferredshadows/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..504245dbaf6f1f5528390700f1723ff02c200456
--- /dev/null
+++ b/external/Vulkan/android/examples/deferredshadows/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanDeferredshadows">
+
+    <application
+        android:label="Vulkan deferred shadow mapping"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/deferredshadows/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/deferredshadows/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/deferredshadows/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/descriptorindexing/CMakeLists.txt b/external/Vulkan/android/examples/descriptorindexing/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..35801d46b006acc759a76546a1dde994379e36fc
--- /dev/null
+++ b/external/Vulkan/android/examples/descriptorindexing/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME descriptorindexing)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/descriptorindexing/build.gradle b/external/Vulkan/android/examples/descriptorindexing/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..37a2dae532f5f08a57a799ab26a6c8dc8cd25b7c
--- /dev/null
+++ b/external/Vulkan/android/examples/descriptorindexing/build.gradle
@@ -0,0 +1,58 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanDescriptorindexing"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/descriptorindexing'
+       into 'assets/shaders/glsl/descriptorindexing'
+       include '*.*'
+    }
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/descriptorindexing/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/descriptorindexing/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..99a8f33f55fa658f0587ed7c67d345f418ef36b5
--- /dev/null
+++ b/external/Vulkan/android/examples/descriptorindexing/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanDescriptorindexing">
+
+    <application
+        android:label="Vulkan descriptor indexing"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/descriptorindexing/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/descriptorindexing/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/descriptorindexing/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/descriptorsets/CMakeLists.txt b/external/Vulkan/android/examples/descriptorsets/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..17d119956d6cc10c702a75c524d8c83efa223b7e
--- /dev/null
+++ b/external/Vulkan/android/examples/descriptorsets/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME descriptorsets)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/descriptorsets/build.gradle b/external/Vulkan/android/examples/descriptorsets/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..f1992d69ec944734fe7cf911bb8e50a60166685b
--- /dev/null
+++ b/external/Vulkan/android/examples/descriptorsets/build.gradle
@@ -0,0 +1,78 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanDescriptorsets"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/descriptorsets'
+       into 'assets/shaders/glsl/descriptorsets'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'cube.gltf'
+    }
+
+    copy {
+       from '../../../data/textures'
+       into 'assets/textures'
+       include 'crate01_color_height_rgba.ktx'
+    }
+
+    copy {
+       from '../../../data/textures'
+       into 'assets/textures'
+       include 'crate02_color_height_rgba.ktx'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/descriptorsets/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/descriptorsets/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..7f04a3e3aaefd8ad8947a25aa47bede2114fcf8c
--- /dev/null
+++ b/external/Vulkan/android/examples/descriptorsets/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanDescriptorsets">
+
+    <application
+        android:label="Vulkan descriptor sets"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/descriptorsets/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/descriptorsets/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/descriptorsets/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/displacement/CMakeLists.txt b/external/Vulkan/android/examples/displacement/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..e11571662096563289235eeb12cdc41ceef58ba9
--- /dev/null
+++ b/external/Vulkan/android/examples/displacement/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME displacement)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/displacement/build.gradle b/external/Vulkan/android/examples/displacement/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..97bd73bd9b5f99c001c521076e6749afbaba71b7
--- /dev/null
+++ b/external/Vulkan/android/examples/displacement/build.gradle
@@ -0,0 +1,72 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanDisplacement"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/displacement'
+       into 'assets/shaders/glsl/displacement'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'displacement_plane.gltf'
+    }
+
+    copy {
+       from '../../../data/textures'
+       into 'assets/textures'
+       include 'stonefloor03_color_height_rgba.ktx'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/displacement/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/displacement/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..edf236b9707a15c0620d2388510f44574291057c
--- /dev/null
+++ b/external/Vulkan/android/examples/displacement/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanDisplacement">
+
+    <application
+        android:label="Vulkan displacement mapping"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/displacement/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/displacement/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/displacement/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/distancefieldfonts/CMakeLists.txt b/external/Vulkan/android/examples/distancefieldfonts/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..7b8555fc3fcfe69b3795f550d96c6c6bb603cf8d
--- /dev/null
+++ b/external/Vulkan/android/examples/distancefieldfonts/CMakeLists.txt
@@ -0,0 +1,34 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME distancefieldfonts)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/distancefieldfonts/build.gradle b/external/Vulkan/android/examples/distancefieldfonts/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..92bfbd1900ccfd02b2dd3baaeaa5bad57e0f0918
--- /dev/null
+++ b/external/Vulkan/android/examples/distancefieldfonts/build.gradle
@@ -0,0 +1,78 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanDistancefieldfonts"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/distancefieldfonts'
+       into 'assets/shaders/glsl/distancefieldfonts'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/textures'
+       into 'assets/textures'
+       include 'font_sdf_rgba.ktx'
+    }
+
+    copy {
+       from '../../../data/textures'
+       into 'assets/textures'
+       include 'font_bitmap_rgba.ktx'
+    }
+
+    copy {
+       from '../../../data/./'
+       into 'assets/./'
+       include 'font.fnt'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/distancefieldfonts/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/distancefieldfonts/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..070a14d7d797388841b6493c81fd5596328f832b
--- /dev/null
+++ b/external/Vulkan/android/examples/distancefieldfonts/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanDistancefieldfonts">
+
+    <application
+        android:label="Vulkan distance field fonts"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/distancefieldfonts/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/distancefieldfonts/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/distancefieldfonts/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/dynamicuniformbuffer/CMakeLists.txt b/external/Vulkan/android/examples/dynamicuniformbuffer/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..dc984b2ee42ea0dba2e2328d4f8f71f5e9bd12a3
--- /dev/null
+++ b/external/Vulkan/android/examples/dynamicuniformbuffer/CMakeLists.txt
@@ -0,0 +1,34 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME dynamicuniformbuffer)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/dynamicuniformbuffer/build.gradle b/external/Vulkan/android/examples/dynamicuniformbuffer/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..ce47108426990c5dbebf13c8e3ee9ee3b2955120
--- /dev/null
+++ b/external/Vulkan/android/examples/dynamicuniformbuffer/build.gradle
@@ -0,0 +1,60 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanDynamicuniformbuffer"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/dynamicuniformbuffer'
+       into 'assets/shaders/glsl/dynamicuniformbuffer'
+       include '*.*'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/dynamicuniformbuffer/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/dynamicuniformbuffer/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..f6ed456f7096ca20bbfdacf6fcddd7932ac1ebcb
--- /dev/null
+++ b/external/Vulkan/android/examples/dynamicuniformbuffer/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanDynamicuniformbuffer">
+
+    <application
+        android:label="Vulkan dynamic uniform buffers"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/dynamicuniformbuffer/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/dynamicuniformbuffer/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/dynamicuniformbuffer/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/gears/CMakeLists.txt b/external/Vulkan/android/examples/gears/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..45c09bf6cd1da0d01c974ab285c97918d2ba00e8
--- /dev/null
+++ b/external/Vulkan/android/examples/gears/CMakeLists.txt
@@ -0,0 +1,34 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME gears)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/gears/build.gradle b/external/Vulkan/android/examples/gears/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..9d4b347ba02349d2e2db539cc7dcf0c46c48bf2f
--- /dev/null
+++ b/external/Vulkan/android/examples/gears/build.gradle
@@ -0,0 +1,60 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanGears"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/gears'
+       into 'assets/shaders/glsl/gears'
+       include '*.*'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/gears/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/gears/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..c8338ffc388602fe6b10594e1ab811613a3f8ecf
--- /dev/null
+++ b/external/Vulkan/android/examples/gears/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanGears">
+
+    <application
+        android:label="Vulkan gears"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/gears/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/gears/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/gears/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/geometryshader/CMakeLists.txt b/external/Vulkan/android/examples/geometryshader/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..0638e6f582c66bbf68715c0393813568f94931ae
--- /dev/null
+++ b/external/Vulkan/android/examples/geometryshader/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME geometryshader)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/geometryshader/build.gradle b/external/Vulkan/android/examples/geometryshader/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..91f9fc30898eb581247b2b58c4bbc337c68665a1
--- /dev/null
+++ b/external/Vulkan/android/examples/geometryshader/build.gradle
@@ -0,0 +1,66 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanGeometryshader"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/geometryshader'
+       into 'assets/shaders/glsl/geometryshader'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'suzanne.gltf'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/geometryshader/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/geometryshader/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..a975812fc0b02fb4de0d115c115b85236a1eb3b0
--- /dev/null
+++ b/external/Vulkan/android/examples/geometryshader/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanGeometryshader">
+
+    <application
+        android:label="Vulkan geometry shader"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/geometryshader/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/geometryshader/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/geometryshader/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/gltfloading/CMakeLists.txt b/external/Vulkan/android/examples/gltfloading/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..155ea0b3e41f3bded9b3459d4960476f66b857dd
--- /dev/null
+++ b/external/Vulkan/android/examples/gltfloading/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME gltfloading)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/gltfloading/build.gradle b/external/Vulkan/android/examples/gltfloading/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..971b664067cf27f3ebc048c378440bc6503b1c96
--- /dev/null
+++ b/external/Vulkan/android/examples/gltfloading/build.gradle
@@ -0,0 +1,66 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanglTFScene"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/gltfloading'
+       into 'assets/shaders/glsl/gltfloading'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models/FlightHelmet/glTF'
+       into 'assets/models/FlightHelmet/glTF'
+       include '*.*'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/gltfloading/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/gltfloading/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..2c6a34743bca42e8c5c9b7fa4f25bfb6eafb2be8
--- /dev/null
+++ b/external/Vulkan/android/examples/gltfloading/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanglTFScene">
+
+    <application
+        android:label="Vulkan glTF scene rendering"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/gltfloading/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/gltfloading/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/gltfloading/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/gltfscenerendering/CMakeLists.txt b/external/Vulkan/android/examples/gltfscenerendering/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..8457151a821f876d675b68b269b3a9ea9284d7bd
--- /dev/null
+++ b/external/Vulkan/android/examples/gltfscenerendering/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME gltfscenerendering)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/gltfscenerendering/build.gradle b/external/Vulkan/android/examples/gltfscenerendering/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..69ddcf96341ff2e2c8ae0a2de2fe404d956e38e2
--- /dev/null
+++ b/external/Vulkan/android/examples/gltfscenerendering/build.gradle
@@ -0,0 +1,66 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanScenerendering"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/gltfscenerendering'
+       into 'assets/shaders/glsl/gltfscenerendering'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models/sponza'
+       into 'assets/models/sponza'
+       include '*.*'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/gltfscenerendering/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/gltfscenerendering/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..a12e04f2698c36fa17386b3152d5465af3fded32
--- /dev/null
+++ b/external/Vulkan/android/examples/gltfscenerendering/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanScenerendering">
+
+    <application
+        android:label="Vulkan scene rendering"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/gltfscenerendering/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/gltfscenerendering/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/gltfscenerendering/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/gltfskinning/CMakeLists.txt b/external/Vulkan/android/examples/gltfskinning/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..e9919822044760227841d0d77be99ff1d8ed6e81
--- /dev/null
+++ b/external/Vulkan/android/examples/gltfskinning/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME gltfskinning)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/gltfskinning/build.gradle b/external/Vulkan/android/examples/gltfskinning/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..2c5ba8adfe51f498ada290607967c957e510cb86
--- /dev/null
+++ b/external/Vulkan/android/examples/gltfskinning/build.gradle
@@ -0,0 +1,66 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanglTFSkinning"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into "assets/shaders/glsl/base"
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/gltfskinning'
+       into 'assets/shaders/glsl/gltfskinning'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models/CesiumMan/glTF'
+       into 'assets/models/CesiumMan/glTF'
+       include '*.*'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/gltfskinning/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/gltfskinning/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..d2ff525dfc46cb27beb14f6cac70fd5adf7e0391
--- /dev/null
+++ b/external/Vulkan/android/examples/gltfskinning/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanglTFSkinning">
+
+    <application
+        android:label="glTF skinned animation"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/gltfskinning/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/gltfskinning/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/gltfskinning/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/gradle/outputfilename.gradle b/external/Vulkan/android/examples/gradle/outputfilename.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..e71624b3b319433e55b5e12f15c53e3432d7b70b
--- /dev/null
+++ b/external/Vulkan/android/examples/gradle/outputfilename.gradle
@@ -0,0 +1,7 @@
+android {
+    applicationVariants.all { variant ->
+        variant.outputs.all {
+            outputFileName = "../../../../../bin/" + outputFileName
+        }
+    }
+}
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/hdr/CMakeLists.txt b/external/Vulkan/android/examples/hdr/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..7f9857b8aa25f1d10cf433f8e5aca965b0351e1f
--- /dev/null
+++ b/external/Vulkan/android/examples/hdr/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME hdr)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/hdr/build.gradle b/external/Vulkan/android/examples/hdr/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..04f84f1a90aa781f9479aed70b05fbda8bba0354
--- /dev/null
+++ b/external/Vulkan/android/examples/hdr/build.gradle
@@ -0,0 +1,96 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanHDR"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/hdr'
+       into 'assets/shaders/glsl/hdr'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'cube.gltf'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'sphere.gltf'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'teapot.gltf'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'torusknot.gltf'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'venus.gltf'
+    }
+
+    copy {
+       from '../../../data/textures/hdr'
+       into 'assets/textures/hdr'
+       include 'uffizi_cube.ktx'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/hdr/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/hdr/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..2ec30ec53e10832169356b51771b1b57aab77dca
--- /dev/null
+++ b/external/Vulkan/android/examples/hdr/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanHDR">
+
+    <application
+        android:label="Vulkan HDR"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/hdr/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/hdr/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/hdr/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/imgui/CMakeLists.txt b/external/Vulkan/android/examples/imgui/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..6858b1972061c4626e8f77cbe6bf4efbe7ef66a6
--- /dev/null
+++ b/external/Vulkan/android/examples/imgui/CMakeLists.txt
@@ -0,0 +1,36 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME imgui)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+file(GLOB ADD_SOURCE "${EXTERNAL_DIR}/imgui/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC} ${ADD_SOURCE})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/imgui/build.gradle b/external/Vulkan/android/examples/imgui/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..2a293c4e724f69738e7a15fe025f0edb09e67c7d
--- /dev/null
+++ b/external/Vulkan/android/examples/imgui/build.gradle
@@ -0,0 +1,78 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanImGui"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/imgui'
+       into 'assets/shaders/glsl/imgui'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'vulkanscenemodels.gltf'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'vulkanscenebackground.gltf'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'vulkanscenelogos.gltf'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/imgui/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/imgui/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..4941885ff5c807b2c22ae4f2982723b64c27a6a2
--- /dev/null
+++ b/external/Vulkan/android/examples/imgui/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanImGui">
+
+    <application
+        android:label="Vulkan ImGui integration"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/imgui/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/imgui/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/imgui/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/indirectdraw/CMakeLists.txt b/external/Vulkan/android/examples/indirectdraw/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..c9ba7a2879f2f1e77401e1381bfe43584b276d91
--- /dev/null
+++ b/external/Vulkan/android/examples/indirectdraw/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME indirectdraw)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/indirectdraw/build.gradle b/external/Vulkan/android/examples/indirectdraw/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..094e2e58acbeedaf2bf65339281e0aaeebfd5779
--- /dev/null
+++ b/external/Vulkan/android/examples/indirectdraw/build.gradle
@@ -0,0 +1,90 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanIndirectdraw"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/indirectdraw'
+       into 'assets/shaders/glsl/indirectdraw'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'plants.gltf'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'plane_circle.gltf'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'sphere.gltf'
+    }
+
+    copy {
+       from '../../../data/textures'
+       into 'assets/textures'
+       include 'texturearray_plants_rgba.ktx'
+    }
+
+    copy {
+       from '../../../data/textures'
+       into 'assets/textures'
+       include 'ground_dry_rgba.ktx'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/indirectdraw/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/indirectdraw/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..6ae1ef8658f8bd11d2dfb535172eedaf4c57aa14
--- /dev/null
+++ b/external/Vulkan/android/examples/indirectdraw/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanIndirectdraw">
+
+    <application
+        android:label="Vulkan indirect drawing"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/indirectdraw/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/indirectdraw/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/indirectdraw/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/inlineuniformblocks/CMakeLists.txt b/external/Vulkan/android/examples/inlineuniformblocks/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..213b47868688f335055c4d2a7dae1712043efbbb
--- /dev/null
+++ b/external/Vulkan/android/examples/inlineuniformblocks/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME inlineuniformblocks)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/inlineuniformblocks/build.gradle b/external/Vulkan/android/examples/inlineuniformblocks/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..f9cedc8bfa3b5d71c1d366a7cdeee0e8de90fe58
--- /dev/null
+++ b/external/Vulkan/android/examples/inlineuniformblocks/build.gradle
@@ -0,0 +1,65 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanInlineuniformblocks"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/inlineuniformblocks'
+       into 'assets/shaders/glsl/inlineuniformblocks'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'sphere.gltf'
+    }
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/inlineuniformblocks/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/inlineuniformblocks/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..ab24dcc3da98a839984bcab547f3019d1f921c90
--- /dev/null
+++ b/external/Vulkan/android/examples/inlineuniformblocks/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanInlineuniformblocks">
+
+    <application
+        android:label="Vulkan Inline Uniform Blocks"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/inlineuniformblocks/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/inlineuniformblocks/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/inlineuniformblocks/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/inputattachments/CMakeLists.txt b/external/Vulkan/android/examples/inputattachments/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..3fd769eabf52a05a338baa7bfb6ff5382ae35a4e
--- /dev/null
+++ b/external/Vulkan/android/examples/inputattachments/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME inputattachments)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/inputattachments/build.gradle b/external/Vulkan/android/examples/inputattachments/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..3a3e890ccca60b7c9ac7f365c88f9d1f5d132858
--- /dev/null
+++ b/external/Vulkan/android/examples/inputattachments/build.gradle
@@ -0,0 +1,70 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanInputattachments"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+        main {
+            jniLibs {
+                srcDir "${android.ndkDirectory}/sources/third_party/vulkan/src/build-android/jniLibs"
+            }
+        }
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/inputattachments'
+       into 'assets/shaders/glsl/inputattachments'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'treasure_smooth.gltf'
+    }
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/inputattachments/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/inputattachments/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..74054b1436b1f63ff558cef72140fd7fc5893b89
--- /dev/null
+++ b/external/Vulkan/android/examples/inputattachments/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanInputattachments">
+
+    <application
+        android:label="Vulkan input attachments"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/inputattachments/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/inputattachments/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/inputattachments/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/instancing/CMakeLists.txt b/external/Vulkan/android/examples/instancing/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..e458cf326da95ff68c9b007e630a0e095325989e
--- /dev/null
+++ b/external/Vulkan/android/examples/instancing/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME instancing)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/instancing/build.gradle b/external/Vulkan/android/examples/instancing/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..3bf54c05f31dde792b26cc6767575b6adc7633e0
--- /dev/null
+++ b/external/Vulkan/android/examples/instancing/build.gradle
@@ -0,0 +1,84 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanInstancing"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/instancing'
+       into 'assets/shaders/glsl/instancing'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'rock01.gltf'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'lavaplanet.gltf'
+    }
+
+    copy {
+       from '../../../data/textures'
+       into 'assets/textures'
+       include 'texturearray_rocks*.ktx'
+    }
+
+    copy {
+       from '../../../data/textures'
+       into 'assets/textures'
+       include 'lavaplanet*.ktx'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/instancing/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/instancing/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..2d98c6e317d527ef2b65ac66448fba2dd5d68147
--- /dev/null
+++ b/external/Vulkan/android/examples/instancing/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanInstancing">
+
+    <application
+        android:label="Vulkan instancing"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/instancing/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/instancing/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/instancing/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/multisampling/CMakeLists.txt b/external/Vulkan/android/examples/multisampling/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..993c020577cbea533c1b71ddf0e4415795e3ee77
--- /dev/null
+++ b/external/Vulkan/android/examples/multisampling/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME multisampling)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/multisampling/build.gradle b/external/Vulkan/android/examples/multisampling/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..fea1cb730972e366e7bc67545f964adc7a72f31f
--- /dev/null
+++ b/external/Vulkan/android/examples/multisampling/build.gradle
@@ -0,0 +1,66 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanMultisampling"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/multisampling'
+       into 'assets/shaders/glsl/multisampling'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'voyager.gltf'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/multisampling/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/multisampling/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..5ae16c0ca55e8aebf1cb937207ff94b7955c5709
--- /dev/null
+++ b/external/Vulkan/android/examples/multisampling/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanMultisampling">
+
+    <application
+        android:label="Vulkan multi sampling"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/multisampling/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/multisampling/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/multisampling/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/multithreading/CMakeLists.txt b/external/Vulkan/android/examples/multithreading/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..f36d14b66f31466ecf411452956a64edcfccfc42
--- /dev/null
+++ b/external/Vulkan/android/examples/multithreading/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME multithreading)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/multithreading/build.gradle b/external/Vulkan/android/examples/multithreading/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..693978d2ae64bbb28fd2cfb8ea0d0ca2a3fbb267
--- /dev/null
+++ b/external/Vulkan/android/examples/multithreading/build.gradle
@@ -0,0 +1,72 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanMultithreading"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/multithreading'
+       into 'assets/shaders/glsl/multithreading'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'retroufo_red_lowpoly.gltf'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'sphere.gltf'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/multithreading/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/multithreading/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..fac7973484421cc09d21284be04aecba8ccca3dd
--- /dev/null
+++ b/external/Vulkan/android/examples/multithreading/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanMultithreading">
+
+    <application
+        android:label="Vulkan multithreading"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/multithreading/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/multithreading/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/multithreading/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/multiview/CMakeLists.txt b/external/Vulkan/android/examples/multiview/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..7bcf7e8ffe6687c05a16dfde12b0cfcaacf535f7
--- /dev/null
+++ b/external/Vulkan/android/examples/multiview/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME multiview)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/multiview/build.gradle b/external/Vulkan/android/examples/multiview/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..dee3bfe2ee9af1aac7e996ef6e16c1a334371294
--- /dev/null
+++ b/external/Vulkan/android/examples/multiview/build.gradle
@@ -0,0 +1,65 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanMultiview"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/multiview'
+       into 'assets/shaders/glsl/multiview'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'sampleroom.gltf'
+    }
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/multiview/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/multiview/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..97156f1f4aab7afbc173daa9a40aad95892172b6
--- /dev/null
+++ b/external/Vulkan/android/examples/multiview/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanMultiview">
+
+    <application
+        android:label="Vulkan Multiview"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/multiview/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/multiview/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/multiview/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/negativeviewportheight/CMakeLists.txt b/external/Vulkan/android/examples/negativeviewportheight/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..3baa8f40dff9884dfe135fd1eb3a4c1d6b1d74a4
--- /dev/null
+++ b/external/Vulkan/android/examples/negativeviewportheight/CMakeLists.txt
@@ -0,0 +1,34 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME negativeviewportheight)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/negativeviewportheight/build.gradle b/external/Vulkan/android/examples/negativeviewportheight/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..fcee9a9b0d28aa6914bb4ffdd4127c405496a829
--- /dev/null
+++ b/external/Vulkan/android/examples/negativeviewportheight/build.gradle
@@ -0,0 +1,72 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanNegativeviewportheight"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/negativeviewportheight'
+       into 'assets/shaders/glsl/negativeviewportheight'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/textures'
+       into 'assets/textures'
+       include 'texture_orientation_ccw_rgba.ktx'
+    }
+
+    copy {
+       from '../../../data/textures'
+       into 'assets/textures'
+       include 'texture_orientation_cw_rgba.ktx'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/negativeviewportheight/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/negativeviewportheight/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..ee8f262306b470de427a219bb2302b3ccee0c72f
--- /dev/null
+++ b/external/Vulkan/android/examples/negativeviewportheight/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanNegativeviewportheight">
+
+    <application
+        android:label="Vulkan negative viewport height"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/negativeviewportheight/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/negativeviewportheight/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/negativeviewportheight/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/occlusionquery/CMakeLists.txt b/external/Vulkan/android/examples/occlusionquery/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..c1f78072130cb0a85377d7a147041191d167070d
--- /dev/null
+++ b/external/Vulkan/android/examples/occlusionquery/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME occlusionquery)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/occlusionquery/build.gradle b/external/Vulkan/android/examples/occlusionquery/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..d0020f644358e24d501245d295921d4a9fed48ce
--- /dev/null
+++ b/external/Vulkan/android/examples/occlusionquery/build.gradle
@@ -0,0 +1,78 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanOcclusionquery"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/occlusionquery'
+       into 'assets/shaders/glsl/occlusionquery'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'plane_z.gltf'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'teapot.gltf'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'sphere.gltf'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/occlusionquery/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/occlusionquery/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..1e50e18454099b20e38051ca1e61e7946005bd5f
--- /dev/null
+++ b/external/Vulkan/android/examples/occlusionquery/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanOcclusionquery">
+
+    <application
+        android:label="Vulkan occlusion queries"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/occlusionquery/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/occlusionquery/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/occlusionquery/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/offscreen/CMakeLists.txt b/external/Vulkan/android/examples/offscreen/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..9b9819f02904481ba8b50b7280cdc5185db494e2
--- /dev/null
+++ b/external/Vulkan/android/examples/offscreen/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME offscreen)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/offscreen/build.gradle b/external/Vulkan/android/examples/offscreen/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..1e6a66626b22de49d1e53b91c566fd1649f3bc0a
--- /dev/null
+++ b/external/Vulkan/android/examples/offscreen/build.gradle
@@ -0,0 +1,71 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanOffscreen"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/offscreen'
+       into 'assets/shaders/glsl/offscreen'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'plane.gltf'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'chinesedragon.gltf'
+    }
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/offscreen/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/offscreen/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..5572e30a1ff51d1c9cd9e32df5cd73eeaa8ef651
--- /dev/null
+++ b/external/Vulkan/android/examples/offscreen/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanOffscreen">
+
+    <application
+        android:label="Vulkan offscreen rendering"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/offscreen/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/offscreen/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/offscreen/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/oit/CMakeLists.txt b/external/Vulkan/android/examples/oit/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..2a2a178eaedc91e97046db94b438dd78fed2eed2
--- /dev/null
+++ b/external/Vulkan/android/examples/oit/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME oit)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/oit/build.gradle b/external/Vulkan/android/examples/oit/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..348c5be4810dcf3d3f8d8b88fb56f14247f753b6
--- /dev/null
+++ b/external/Vulkan/android/examples/oit/build.gradle
@@ -0,0 +1,71 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanOrderIndependentTransparency"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/oit'
+       into 'assets/shaders/glsl/oit'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'sphere.gltf'
+    }    
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'cube.gltf'
+    }    
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/oit/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/oit/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..2c836182740dece03e9e68a53682a33324457696
--- /dev/null
+++ b/external/Vulkan/android/examples/oit/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanOrderIndependentTransparency">
+
+    <application
+        android:label="Vulkan order independent transparency"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/oit/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/oit/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/oit/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/parallaxmapping/CMakeLists.txt b/external/Vulkan/android/examples/parallaxmapping/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..824485e0aa5739771f7d7b6aa8cd9c94dc6d9330
--- /dev/null
+++ b/external/Vulkan/android/examples/parallaxmapping/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME parallaxmapping)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/parallaxmapping/build.gradle b/external/Vulkan/android/examples/parallaxmapping/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..cf6c4334ba0d0b8d5ffa2afc118e0fbecb990949
--- /dev/null
+++ b/external/Vulkan/android/examples/parallaxmapping/build.gradle
@@ -0,0 +1,78 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanParallaxmapping"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/parallaxmapping'
+       into 'assets/shaders/glsl/parallaxmapping'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'planecd.gltf'
+    }
+
+    copy {
+       from '../../../data/textures'
+       into 'assets/textures'
+       include 'rocks_normal_height_rgba.ktx'
+    }
+
+    copy {
+       from '../../../data/textures'
+       into 'assets/textures'
+       include 'rocks_color*.ktx'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/parallaxmapping/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/parallaxmapping/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..e40d99da376467f8ce817762dd9ed41ada36b6f1
--- /dev/null
+++ b/external/Vulkan/android/examples/parallaxmapping/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanParallaxmapping">
+
+    <application
+        android:label="Vulkan parallax mapping techniques"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/parallaxmapping/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/parallaxmapping/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/parallaxmapping/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/particlefire/CMakeLists.txt b/external/Vulkan/android/examples/particlefire/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..f107918eb548174edfbab8a56e1661b8e2577c9c
--- /dev/null
+++ b/external/Vulkan/android/examples/particlefire/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME particlefire)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/particlefire/build.gradle b/external/Vulkan/android/examples/particlefire/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..a34dca7409cdbb83c6be226f089a4ec267a56711
--- /dev/null
+++ b/external/Vulkan/android/examples/particlefire/build.gradle
@@ -0,0 +1,90 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanParticlefire"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/particlefire'
+       into 'assets/shaders/glsl/particlefire'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'fireplace.gltf'
+    }
+
+    copy {
+       from '../../../data/textures'
+       into 'assets/textures'
+       include 'particle_fire.ktx'
+    }
+
+    copy {
+       from '../../../data/textures'
+       into 'assets/textures'
+       include 'particle_smoke.ktx'
+    }
+
+    copy {
+       from '../../../data/textures'
+       into 'assets/textures'
+       include 'fireplace_normalmap*.ktx'
+    }
+
+    copy {
+       from '../../../data/textures'
+       into 'assets/textures'
+       include 'fireplace_colormap*.ktx'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/particlefire/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/particlefire/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..c8f97268d0fdff31dfe3b6e10e37b461b8c02b61
--- /dev/null
+++ b/external/Vulkan/android/examples/particlefire/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanParticlefire">
+
+    <application
+        android:label="Vulkan CPU particles"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/particlefire/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/particlefire/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/particlefire/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/pbrbasic/CMakeLists.txt b/external/Vulkan/android/examples/pbrbasic/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..9e07f3592394c46f6c21a1b378f677ac569301f3
--- /dev/null
+++ b/external/Vulkan/android/examples/pbrbasic/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME pbrbasic)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/pbrbasic/build.gradle b/external/Vulkan/android/examples/pbrbasic/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..80aa854d35e85442ead019eef6a0323a61009c24
--- /dev/null
+++ b/external/Vulkan/android/examples/pbrbasic/build.gradle
@@ -0,0 +1,84 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanPBRBasic"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/pbrbasic'
+       into 'assets/shaders/glsl/pbrbasic'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'sphere.gltf'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'teapot.gltf'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'torusknot.gltf'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'venus.gltf'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/pbrbasic/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/pbrbasic/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..ed399c39baaf867c39ad3269d27da78765018e39
--- /dev/null
+++ b/external/Vulkan/android/examples/pbrbasic/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanPBRBasic">
+
+    <application
+        android:label="Vulkan PBR basics"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/pbrbasic/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/pbrbasic/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/pbrbasic/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/pbribl/CMakeLists.txt b/external/Vulkan/android/examples/pbribl/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..89573311a9300bf2db4491a4d2767d593d306e98
--- /dev/null
+++ b/external/Vulkan/android/examples/pbribl/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME pbribl)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/pbribl/build.gradle b/external/Vulkan/android/examples/pbribl/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..d02778b7e93d21714645dd88759939ecf25bad5c
--- /dev/null
+++ b/external/Vulkan/android/examples/pbribl/build.gradle
@@ -0,0 +1,96 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanPBRIBL"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/pbribl'
+       into 'assets/shaders/glsl/pbribl'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'cube.gltf'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'sphere.gltf'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'teapot.gltf'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'torusknot.gltf'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'venus.gltf'
+    }
+
+    copy {
+       from '../../../data/textures/hdr'
+       into 'assets/textures/hdr'
+       include 'pisa_cube.ktx'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/pbribl/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/pbribl/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..e3288148fc4b03b93d90d8af515cace175035b78
--- /dev/null
+++ b/external/Vulkan/android/examples/pbribl/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanPBRIBL">
+
+    <application
+        android:label="Vulkan PBR image based lighting"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/pbribl/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/pbribl/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/pbribl/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/pbrtexture/CMakeLists.txt b/external/Vulkan/android/examples/pbrtexture/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..118570e57a18dc9c62e115705d0072b0d1e90b2c
--- /dev/null
+++ b/external/Vulkan/android/examples/pbrtexture/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME pbrtexture)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/pbrtexture/build.gradle b/external/Vulkan/android/examples/pbrtexture/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..c83b421353efec19b585812f7615274e13193417
--- /dev/null
+++ b/external/Vulkan/android/examples/pbrtexture/build.gradle
@@ -0,0 +1,78 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanPBRTexture"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/pbrtexture'
+       into 'assets/shaders/glsl/pbrtexture'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'cube.gltf'
+    }
+
+    copy {
+       from '../../../data/textures/hdr'
+       into 'assets/textures/hdr'
+       include 'gcanyon_cube.ktx'
+    }
+
+    copy {
+       from '../../../data/models/cerberus'
+       into 'assets/models/cerberus'
+       include '*.*'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/pbrtexture/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/pbrtexture/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..8ed371b98880381835cd039e0364628b91c61892
--- /dev/null
+++ b/external/Vulkan/android/examples/pbrtexture/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanPBRTexture">
+
+    <application
+        android:label="Vulkan textured PBR with image based lighting"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/pbrtexture/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/pbrtexture/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/pbrtexture/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/pipelines/CMakeLists.txt b/external/Vulkan/android/examples/pipelines/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..e28bffa6e84a8a6e3882f7a7378623d796aeed38
--- /dev/null
+++ b/external/Vulkan/android/examples/pipelines/CMakeLists.txt
@@ -0,0 +1,36 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME pipelines)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/gli)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/pipelines/build.gradle b/external/Vulkan/android/examples/pipelines/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..0196b50f7994ac5b5be29810af64136d3e88a94e
--- /dev/null
+++ b/external/Vulkan/android/examples/pipelines/build.gradle
@@ -0,0 +1,66 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanPipelines"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/pipelines'
+       into 'assets/shaders/glsl/pipelines'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'treasure_smooth.gltf'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/pipelines/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/pipelines/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..a6e7bc8a53cd977066992fe8c382f51ff8f87223
--- /dev/null
+++ b/external/Vulkan/android/examples/pipelines/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanPipelines">
+
+    <application
+        android:label="Vulkan pipeline state objects"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/pipelines/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/pipelines/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/pipelines/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/pipelinestatistics/CMakeLists.txt b/external/Vulkan/android/examples/pipelinestatistics/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..b1d3c1dc91f5032c1d062610f4c34f40c8521671
--- /dev/null
+++ b/external/Vulkan/android/examples/pipelinestatistics/CMakeLists.txt
@@ -0,0 +1,36 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME pipelinestatistics)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/gli)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/pipelinestatistics/build.gradle b/external/Vulkan/android/examples/pipelinestatistics/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..fab6c3d7c3bc8daa1a177f10eff76cb5ab595b9f
--- /dev/null
+++ b/external/Vulkan/android/examples/pipelinestatistics/build.gradle
@@ -0,0 +1,84 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanPipelinestatistics"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/pipelinestatistics'
+       into 'assets/shaders/glsl/pipelinestatistics'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'sphere.gltf'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'teapot.gltf'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'torusknot.gltf'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'venus.gltf'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/pipelinestatistics/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/pipelinestatistics/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..a26c688c70d990b7beee2034732b9a7ba6202911
--- /dev/null
+++ b/external/Vulkan/android/examples/pipelinestatistics/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanPipelinestatistics">
+
+    <application
+        android:label="Vulkan pipeline statistics"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/pipelinestatistics/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/pipelinestatistics/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/pipelinestatistics/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/pushconstants/CMakeLists.txt b/external/Vulkan/android/examples/pushconstants/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..67ec37559c32e2d54f98f137e7a7d044492e23d2
--- /dev/null
+++ b/external/Vulkan/android/examples/pushconstants/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME pushconstants)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/pushconstants/build.gradle b/external/Vulkan/android/examples/pushconstants/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..b3021ab662e68d5c7810ac9bbb64d04a8d0fe566
--- /dev/null
+++ b/external/Vulkan/android/examples/pushconstants/build.gradle
@@ -0,0 +1,72 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanPushconstants"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/pushconstants'
+       into 'assets/shaders/glsl/pushconstants'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'sphere.gltf'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'samplescene.gltf'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/pushconstants/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/pushconstants/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..da183bf2a54747a0d50ff6c786a26c0a82e15bd3
--- /dev/null
+++ b/external/Vulkan/android/examples/pushconstants/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanPushconstants">
+
+    <application
+        android:label="Vulkan push constants"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/pushconstants/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/pushconstants/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/pushconstants/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/pushdescriptors/CMakeLists.txt b/external/Vulkan/android/examples/pushdescriptors/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..ff150f221470c02e07dc2670b6d3591e046ececa
--- /dev/null
+++ b/external/Vulkan/android/examples/pushdescriptors/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME pushdescriptors)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/pushdescriptors/build.gradle b/external/Vulkan/android/examples/pushdescriptors/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..667c2e2861a96917b53c48784258a3cd9ca44346
--- /dev/null
+++ b/external/Vulkan/android/examples/pushdescriptors/build.gradle
@@ -0,0 +1,78 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanPushdescriptors"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/pushdescriptors'
+       into 'assets/shaders/glsl/pushdescriptors'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'cube.gltf'
+    }
+
+    copy {
+       from '../../../data/textures'
+       into 'assets/textures'
+       include 'crate01_color_height_rgba.ktx'
+    }
+
+    copy {
+       from '../../../data/textures'
+       into 'assets/textures'
+       include 'crate02_color_height_rgba.ktx'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/pushdescriptors/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/pushdescriptors/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..66b5cbbe048a77dc0e26fd707f018c9a0f4d408f
--- /dev/null
+++ b/external/Vulkan/android/examples/pushdescriptors/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanPushdescriptors">
+
+    <application
+        android:label="Vulkan push descriptors"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/pushdescriptors/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/pushdescriptors/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/pushdescriptors/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/radialblur/CMakeLists.txt b/external/Vulkan/android/examples/radialblur/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..1606a2e462d51283a91f0a8a0ccbf53a890e74f1
--- /dev/null
+++ b/external/Vulkan/android/examples/radialblur/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME radialblur)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/radialblur/build.gradle b/external/Vulkan/android/examples/radialblur/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..6392e712e3769cd019ac842b7eb59ffb9626d616
--- /dev/null
+++ b/external/Vulkan/android/examples/radialblur/build.gradle
@@ -0,0 +1,72 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanRadialblur"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/radialblur'
+       into 'assets/shaders/glsl/radialblur'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'glowsphere.gltf'
+    }
+
+    copy {
+       from '../../../data/textures'
+       into 'assets/textures'
+       include 'particle_gradient_rgba.ktx'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/radialblur/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/radialblur/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..f0ee4f17e3d5413861f3a6f2081702674dcfea93
--- /dev/null
+++ b/external/Vulkan/android/examples/radialblur/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanRadialblur">
+
+    <application
+        android:label="Vulkan radial blur effect"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/radialblur/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/radialblur/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/radialblur/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/raytracingbasic/CMakeLists.txt b/external/Vulkan/android/examples/raytracingbasic/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..b54ee8ce4d3f629112d83fd4da142153103756d1
--- /dev/null
+++ b/external/Vulkan/android/examples/raytracingbasic/CMakeLists.txt
@@ -0,0 +1,34 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME raytracingbasic)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/raytracingbasic/build.gradle b/external/Vulkan/android/examples/raytracingbasic/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..f87ab743a056b95fba4d7421a3fca6a7406f01d7
--- /dev/null
+++ b/external/Vulkan/android/examples/raytracingbasic/build.gradle
@@ -0,0 +1,60 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanRaytracingbasic"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/raytracingbasic'
+       into 'assets/shaders/glsl/raytracingbasic'
+       include '*.*'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/raytracingbasic/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/raytracingbasic/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..067fafeda47d5f1c4cca1d9da3be47f46b70f94a
--- /dev/null
+++ b/external/Vulkan/android/examples/raytracingbasic/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanRaytracingbasic">
+
+    <application
+        android:label="Vulkan basic ray tracing"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/raytracingbasic/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/raytracingbasic/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/raytracingbasic/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/raytracingreflections/CMakeLists.txt b/external/Vulkan/android/examples/raytracingreflections/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..4acffbd2d2c4b2337992fa47a8f51e9b38ac538c
--- /dev/null
+++ b/external/Vulkan/android/examples/raytracingreflections/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME raytracingreflections)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/raytracingreflections/build.gradle b/external/Vulkan/android/examples/raytracingreflections/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..f527733c7272e0d7c38225ffeba98677f364227f
--- /dev/null
+++ b/external/Vulkan/android/examples/raytracingreflections/build.gradle
@@ -0,0 +1,65 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanRaytracingreflections"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/raytracingreflections'
+       into 'assets/shaders/glsl/raytracingreflections'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'reflection_scene.gltf'
+    }    
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/raytracingreflections/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/raytracingreflections/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..bb6370683d2e531a0eee9bd446ea5bbfe7c44e84
--- /dev/null
+++ b/external/Vulkan/android/examples/raytracingreflections/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanRaytracingreflections">
+
+    <application
+        android:label="Vulkan ray tracing reflections"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/raytracingreflections/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/raytracingreflections/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/raytracingreflections/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/raytracingshadows/CMakeLists.txt b/external/Vulkan/android/examples/raytracingshadows/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..656a60d721e48deb1d7a18be4503ec0987e91684
--- /dev/null
+++ b/external/Vulkan/android/examples/raytracingshadows/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME raytracingshadows)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/raytracingshadows/build.gradle b/external/Vulkan/android/examples/raytracingshadows/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..c680a4dc4d658ba9372f5a0d6c9e520668bbbb68
--- /dev/null
+++ b/external/Vulkan/android/examples/raytracingshadows/build.gradle
@@ -0,0 +1,65 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanRaytracingshadows"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/raytracingshadows'
+       into 'assets/shaders/glsl/raytracingshadows'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'vulkanscene_shadow.gltf'
+    }    
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/raytracingshadows/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/raytracingshadows/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..ae5447fd6f60cf46310720c6329291e420d1202b
--- /dev/null
+++ b/external/Vulkan/android/examples/raytracingshadows/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanRaytracingshadows">
+
+    <application
+        android:label="Vulkan ray tracing shadows"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/raytracingshadows/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/raytracingshadows/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/raytracingshadows/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/renderheadless/CMakeLists.txt b/external/Vulkan/android/examples/renderheadless/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..04c80615244a2eb4251169a804d364c1aa391773
--- /dev/null
+++ b/external/Vulkan/android/examples/renderheadless/CMakeLists.txt
@@ -0,0 +1,34 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME renderheadless)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/renderheadless/build.gradle b/external/Vulkan/android/examples/renderheadless/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..75bb3f3eaaff35792e1034945e95a0672d6ef69d
--- /dev/null
+++ b/external/Vulkan/android/examples/renderheadless/build.gradle
@@ -0,0 +1,60 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanRenderheadless"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/renderheadless'
+       into 'assets/shaders/glsl/renderheadless'
+       include '*.*'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/renderheadless/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/renderheadless/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..6dd6b2276c447503d4716149f9b3f9c77fa60c9d
--- /dev/null
+++ b/external/Vulkan/android/examples/renderheadless/src/main/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanRenderheadless">
+
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+    <application
+        android:label="Vulkan headless render"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/renderheadless/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/renderheadless/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/renderheadless/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/screenshot/CMakeLists.txt b/external/Vulkan/android/examples/screenshot/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..041e001d2aa92f17fd3be200716384f5a6e36e2b
--- /dev/null
+++ b/external/Vulkan/android/examples/screenshot/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME screenshot)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/screenshot/build.gradle b/external/Vulkan/android/examples/screenshot/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..afc8364dbffab77d7439cfca786b2d35c2785c85
--- /dev/null
+++ b/external/Vulkan/android/examples/screenshot/build.gradle
@@ -0,0 +1,66 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanScreenshot"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/screenshot'
+       into 'assets/shaders/glsl/screenshot'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'chinesedragon.gltf'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/screenshot/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/screenshot/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..e2d9fd337298783dfdd37431becc60cba9bf8f27
--- /dev/null
+++ b/external/Vulkan/android/examples/screenshot/src/main/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanScreenshot">
+
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+    <application
+        android:label="Vulkan screenshot capture"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/screenshot/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/screenshot/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/screenshot/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/shadowmapping/CMakeLists.txt b/external/Vulkan/android/examples/shadowmapping/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..065e30082df3b5cef208482a5fa72f1835406ed5
--- /dev/null
+++ b/external/Vulkan/android/examples/shadowmapping/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME shadowmapping)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/shadowmapping/build.gradle b/external/Vulkan/android/examples/shadowmapping/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..65258b9348257fe3cd960b9b9c22f7266a24f180
--- /dev/null
+++ b/external/Vulkan/android/examples/shadowmapping/build.gradle
@@ -0,0 +1,71 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanShadowmapping"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/shadowmapping'
+       into 'assets/shaders/glsl/shadowmapping'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'vulkanscene_shadow.gltf'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'samplescene.gltf'
+    }
+
+}
+
+preBuild.dependsOn copyTask
diff --git a/external/Vulkan/android/examples/shadowmapping/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/shadowmapping/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..2f98411a41fd7510c66dda72c2d55e76aaad0730
--- /dev/null
+++ b/external/Vulkan/android/examples/shadowmapping/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanShadowmapping">
+
+    <application
+        android:label="Vulkan shadow mapping"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/shadowmapping/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/shadowmapping/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/shadowmapping/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/shadowmappingcascade/CMakeLists.txt b/external/Vulkan/android/examples/shadowmappingcascade/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..090805ece5a8d77f6d2ee750fd3a0573d5789000
--- /dev/null
+++ b/external/Vulkan/android/examples/shadowmappingcascade/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME shadowmappingcascade)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/shadowmappingcascade/build.gradle b/external/Vulkan/android/examples/shadowmappingcascade/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..2c605d4d5bf9ab2cca4beccdb67eea49763d0e4f
--- /dev/null
+++ b/external/Vulkan/android/examples/shadowmappingcascade/build.gradle
@@ -0,0 +1,71 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanShadowmappingcascade"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/shadowmappingcascade'
+       into 'assets/shaders/glsl/shadowmappingcascade'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'terrain_gridlines.gltf'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'oaktree.gltf'
+    }
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/shadowmappingcascade/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/shadowmappingcascade/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..a53062057f3c2e4a38daad9fea191931dd11eb4c
--- /dev/null
+++ b/external/Vulkan/android/examples/shadowmappingcascade/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanShadowmappingcascade">
+
+    <application
+        android:label="Vulkan cascaded shadow mapping"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/shadowmappingcascade/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/shadowmappingcascade/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/shadowmappingcascade/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/shadowmappingomni/CMakeLists.txt b/external/Vulkan/android/examples/shadowmappingomni/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..2ca85dc1720a0d9aafcbb79c3b14ed0f7655b60b
--- /dev/null
+++ b/external/Vulkan/android/examples/shadowmappingomni/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME shadowmappingomni)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/shadowmappingomni/build.gradle b/external/Vulkan/android/examples/shadowmappingomni/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..6563078c0b94f76a914eb3a90f9bfd83f1e7b209
--- /dev/null
+++ b/external/Vulkan/android/examples/shadowmappingomni/build.gradle
@@ -0,0 +1,72 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanShadowmappingomni"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/shadowmappingomni'
+       into 'assets/shaders/glsl/shadowmappingomni'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'shadowscene_fire.gltf'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'cube.gltf'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/shadowmappingomni/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/shadowmappingomni/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..8bf397dc291b281213d67675a44a0ad81c45ac30
--- /dev/null
+++ b/external/Vulkan/android/examples/shadowmappingomni/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanShadowmappingomni">
+
+    <application
+        android:label="Vulkan point light shadow mapping"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/shadowmappingomni/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/shadowmappingomni/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/shadowmappingomni/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/specializationconstants/CMakeLists.txt b/external/Vulkan/android/examples/specializationconstants/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..68d0c0dbc408fe83dd5ee3d27b92a49fc63ed9ac
--- /dev/null
+++ b/external/Vulkan/android/examples/specializationconstants/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME specializationconstants)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/specializationconstants/build.gradle b/external/Vulkan/android/examples/specializationconstants/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..2f9997def6907b6d5b213af0e8d21a5a51fb0618
--- /dev/null
+++ b/external/Vulkan/android/examples/specializationconstants/build.gradle
@@ -0,0 +1,72 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanSpecializationconstants"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/specializationconstants'
+       into 'assets/shaders/glsl/specializationconstants'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'color_teapot_spheres.gltf'
+    }
+
+    copy {
+       from '../../../data/textures'
+       into 'assets/textures'
+       include 'metalplate_nomips_rgba.ktx'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/specializationconstants/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/specializationconstants/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..f51c7827434b627e064f76cb2d6bf34465ac9575
--- /dev/null
+++ b/external/Vulkan/android/examples/specializationconstants/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanSpecializationconstants">
+
+    <application
+        android:label="Vulkan SPIR-V specialization constants"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/specializationconstants/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/specializationconstants/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/specializationconstants/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/sphericalenvmapping/CMakeLists.txt b/external/Vulkan/android/examples/sphericalenvmapping/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..d341deb6e88ed9151de2562e17080eec6479da0e
--- /dev/null
+++ b/external/Vulkan/android/examples/sphericalenvmapping/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME sphericalenvmapping)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/sphericalenvmapping/build.gradle b/external/Vulkan/android/examples/sphericalenvmapping/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..f1271ecb0ddb757899b59535d14c74dab7933438
--- /dev/null
+++ b/external/Vulkan/android/examples/sphericalenvmapping/build.gradle
@@ -0,0 +1,72 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanSphericalenvmapping"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/sphericalenvmapping'
+       into 'assets/shaders/glsl/sphericalenvmapping'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'chinesedragon.gltf'
+    }
+
+    copy {
+       from '../../../data/textures'
+       into 'assets/textures'
+       include 'matcap_array_rgba.ktx'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/sphericalenvmapping/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/sphericalenvmapping/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..3b6be14b9a616d5670313f457b52ea7f5ccd11e7
--- /dev/null
+++ b/external/Vulkan/android/examples/sphericalenvmapping/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanSphericalenvmapping">
+
+    <application
+        android:label="Vulkan spherical environment mapping"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/sphericalenvmapping/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/sphericalenvmapping/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/sphericalenvmapping/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/ssao/CMakeLists.txt b/external/Vulkan/android/examples/ssao/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..cff6a9d323fda98d540fbe82746ab42478bfc36d
--- /dev/null
+++ b/external/Vulkan/android/examples/ssao/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME ssao)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/ssao/build.gradle b/external/Vulkan/android/examples/ssao/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..908c40dd7681b805fd7bf383140d4604d876d0ab
--- /dev/null
+++ b/external/Vulkan/android/examples/ssao/build.gradle
@@ -0,0 +1,65 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanSSAO"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/ssao'
+       into 'assets/shaders/glsl/ssao'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models/sponza'
+       into 'assets/models/sponza'
+       include '*.*'
+    }
+    
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/ssao/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/ssao/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..8f5a76396ae2021ce9b1d54d16e146a07786a73e
--- /dev/null
+++ b/external/Vulkan/android/examples/ssao/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanSSAO">
+
+    <application
+        android:label="Vulkan SSAO"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/ssao/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/ssao/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/ssao/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/stencilbuffer/CMakeLists.txt b/external/Vulkan/android/examples/stencilbuffer/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..ee7169419fc3a93a3e69dcc0c03df9087820daa8
--- /dev/null
+++ b/external/Vulkan/android/examples/stencilbuffer/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME stencilbuffer)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/stencilbuffer/build.gradle b/external/Vulkan/android/examples/stencilbuffer/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..2736850e95cb54deab1724e162c3af208a8caad5
--- /dev/null
+++ b/external/Vulkan/android/examples/stencilbuffer/build.gradle
@@ -0,0 +1,66 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanStencilbuffer"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/stencilbuffer'
+       into 'assets/shaders/glsl/stencilbuffer'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'venus.gltf'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/stencilbuffer/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/stencilbuffer/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..42e98050c0f23224c925a2aab8a0d93acfc9149f
--- /dev/null
+++ b/external/Vulkan/android/examples/stencilbuffer/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanStencilbuffer">
+
+    <application
+        android:label="Vulkan stencil buffer"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/stencilbuffer/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/stencilbuffer/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/stencilbuffer/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/subpasses/CMakeLists.txt b/external/Vulkan/android/examples/subpasses/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..3115b75867bd8ecb785e584fa44ab986bce67429
--- /dev/null
+++ b/external/Vulkan/android/examples/subpasses/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME subpasses)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/subpasses/build.gradle b/external/Vulkan/android/examples/subpasses/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..c92bacdd17bdabcf2a7b0880d57f316e36e5e791
--- /dev/null
+++ b/external/Vulkan/android/examples/subpasses/build.gradle
@@ -0,0 +1,78 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanSubpasses"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/subpasses'
+       into 'assets/shaders/glsl/subpasses'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'samplebuilding.gltf'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'samplebuilding_glass.gltf'
+    }
+
+    copy {
+       from '../../../data/textures'
+       into 'assets/textures'
+       include 'colored_glass_rgba.ktx'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/subpasses/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/subpasses/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..8c6043de4fc0da6a79b4a6c77bcc7af67e657a3a
--- /dev/null
+++ b/external/Vulkan/android/examples/subpasses/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanSubpasses">
+
+    <application
+        android:label="Vulkan sub passes"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/subpasses/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/subpasses/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/subpasses/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/terraintessellation/CMakeLists.txt b/external/Vulkan/android/examples/terraintessellation/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..c19af6abe48ae4bc547685289a66a30cb1d55130
--- /dev/null
+++ b/external/Vulkan/android/examples/terraintessellation/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME terraintessellation)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/terraintessellation/build.gradle b/external/Vulkan/android/examples/terraintessellation/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..5f3c73911e69e54668ee4b6aad02e30661481c7c
--- /dev/null
+++ b/external/Vulkan/android/examples/terraintessellation/build.gradle
@@ -0,0 +1,84 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanTerraintessellation"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/terraintessellation'
+       into 'assets/shaders/glsl/terraintessellation'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'sphere.gltf'
+    }
+
+    copy {
+       from '../../../data/textures'
+       into 'assets/textures'
+       include 'skysphere*.ktx'
+    }
+
+    copy {
+       from '../../../data/textures'
+       into 'assets/textures'
+       include 'terrain_texturearray*.ktx'
+    }
+
+    copy {
+       from '../../../data/textures'
+       into 'assets/textures'
+       include 'terrain_heightmap_r16.ktx'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/terraintessellation/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/terraintessellation/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..4723f70f97729cdb81539091469e944a8467b361
--- /dev/null
+++ b/external/Vulkan/android/examples/terraintessellation/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanTerraintessellation">
+
+    <application
+        android:label="Vulkan terrain tessellation"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/terraintessellation/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/terraintessellation/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/terraintessellation/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/tessellation/CMakeLists.txt b/external/Vulkan/android/examples/tessellation/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..1ef1208c9595e6b7e76aa29622a3ebe5e9f51408
--- /dev/null
+++ b/external/Vulkan/android/examples/tessellation/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME tessellation)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/tessellation/build.gradle b/external/Vulkan/android/examples/tessellation/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..a2b75e45c6d97a9b470aa51429b2a4bf7bca77e1
--- /dev/null
+++ b/external/Vulkan/android/examples/tessellation/build.gradle
@@ -0,0 +1,65 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanTessellation"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/tessellation'
+       into 'assets/shaders/glsl/tessellation'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'deer.gltf'
+    }
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/tessellation/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/tessellation/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..6f7c645c0340e4dea388e876f8b6dab03be51a13
--- /dev/null
+++ b/external/Vulkan/android/examples/tessellation/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanTessellation">
+
+    <application
+        android:label="Vulkan PN-triangles tessellation"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/tessellation/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/tessellation/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/tessellation/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/textoverlay/CMakeLists.txt b/external/Vulkan/android/examples/textoverlay/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..a7a5e766ca53b708e8bb14883d472d743cc69e88
--- /dev/null
+++ b/external/Vulkan/android/examples/textoverlay/CMakeLists.txt
@@ -0,0 +1,36 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME textoverlay)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/gli)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/textoverlay/build.gradle b/external/Vulkan/android/examples/textoverlay/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..dda429c2c29d2e18e8c95c1c77d5d56918022364
--- /dev/null
+++ b/external/Vulkan/android/examples/textoverlay/build.gradle
@@ -0,0 +1,66 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanTextoverlay"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/textoverlay'
+       into 'assets/shaders/glsl/textoverlay'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'cube.gltf'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/textoverlay/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/textoverlay/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..f3a05228fc25aadb2eb0b64459e7661a64ab841f
--- /dev/null
+++ b/external/Vulkan/android/examples/textoverlay/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanTextoverlay">
+
+    <application
+        android:label="Vulkan text overlay"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/textoverlay/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/textoverlay/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/textoverlay/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/texture/CMakeLists.txt b/external/Vulkan/android/examples/texture/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..1e428f833cc3c5a00af688a57e7cf9098407d91f
--- /dev/null
+++ b/external/Vulkan/android/examples/texture/CMakeLists.txt
@@ -0,0 +1,34 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME texture)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/texture/build.gradle b/external/Vulkan/android/examples/texture/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..a0f5da3a619a87cbe2b51430bbe36b4ffb2afeca
--- /dev/null
+++ b/external/Vulkan/android/examples/texture/build.gradle
@@ -0,0 +1,66 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanTexture"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/texture'
+       into 'assets/shaders/glsl/texture'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/textures'
+       into 'assets/textures'
+       include 'metalplate01_rgba.ktx'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/texture/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/texture/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..5482b68115a84ee0d1e98a8fc4c7cb08cc08a459
--- /dev/null
+++ b/external/Vulkan/android/examples/texture/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanTexture">
+
+    <application
+        android:label="Vulkan 2D texture mapping"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/texture/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/texture/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/texture/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/texture3d/CMakeLists.txt b/external/Vulkan/android/examples/texture3d/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..a00291b611aad1fde40c85631a2bde5f1c7cad57
--- /dev/null
+++ b/external/Vulkan/android/examples/texture3d/CMakeLists.txt
@@ -0,0 +1,34 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME texture3d)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/texture3d/build.gradle b/external/Vulkan/android/examples/texture3d/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..ed29b15b7e80653d45c255e7ed04341ee08f9506
--- /dev/null
+++ b/external/Vulkan/android/examples/texture3d/build.gradle
@@ -0,0 +1,60 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanTexture3d"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/texture3d'
+       into 'assets/shaders/glsl/texture3d'
+       include '*.*'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/texture3d/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/texture3d/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..5ba253103906bf23b80c09b3b38fe00651fcb3b7
--- /dev/null
+++ b/external/Vulkan/android/examples/texture3d/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanTexture3d">
+
+    <application
+        android:label="Vulkan 3D texture mapping"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/texture3d/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/texture3d/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/texture3d/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/texturearray/CMakeLists.txt b/external/Vulkan/android/examples/texturearray/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..b1363101216de4460c53f8b9906669cec17daae2
--- /dev/null
+++ b/external/Vulkan/android/examples/texturearray/CMakeLists.txt
@@ -0,0 +1,34 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME texturearray)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/texturearray/build.gradle b/external/Vulkan/android/examples/texturearray/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..b9ae982f84565d6c50cf8220305b5dd434c220e4
--- /dev/null
+++ b/external/Vulkan/android/examples/texturearray/build.gradle
@@ -0,0 +1,66 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanTexturearray"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/texturearray'
+       into 'assets/shaders/glsl/texturearray'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/textures'
+       into 'assets/textures'
+       include 'texturearray_rgba.ktx'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/texturearray/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/texturearray/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..a466469a39cf16a62664fc80e5a00e85b0774231
--- /dev/null
+++ b/external/Vulkan/android/examples/texturearray/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanTexturearray">
+
+    <application
+        android:label="Vulkan 2D texture arrays"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/texturearray/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/texturearray/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/texturearray/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/texturecubemap/CMakeLists.txt b/external/Vulkan/android/examples/texturecubemap/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..a28fd331848beaefce9a42332cafa655b6ec4093
--- /dev/null
+++ b/external/Vulkan/android/examples/texturecubemap/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME texturecubemap)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/texturecubemap/build.gradle b/external/Vulkan/android/examples/texturecubemap/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..52fe317eeff4c71f6555a42d1b8f7169f32cb7c7
--- /dev/null
+++ b/external/Vulkan/android/examples/texturecubemap/build.gradle
@@ -0,0 +1,96 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanTexturecubemap"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/texturecubemap'
+       into 'assets/shaders/glsl/texturecubemap'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'sphere.gltf'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'teapot.gltf'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'torusknot.gltf'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'cube.gltf'
+    }
+
+    copy {
+        from '../../../data/models'
+        into 'assets/models'
+        include 'venus.gltf'
+    }
+
+    copy {
+       from '../../../data/textures'
+       into 'assets/textures'
+       include 'cubemap_yokohama_rgba.ktx'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/texturecubemap/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/texturecubemap/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..a1e81a20105eff07ee88984224e1ae4e0c34b5a0
--- /dev/null
+++ b/external/Vulkan/android/examples/texturecubemap/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanTexturecubemap">
+
+    <application
+        android:label="Vulkan cubemap textures"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/texturecubemap/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/texturecubemap/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/texturecubemap/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/texturecubemaparray/CMakeLists.txt b/external/Vulkan/android/examples/texturecubemaparray/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..ec70106441b6cf905f26956a9c9c0f63e45f3e25
--- /dev/null
+++ b/external/Vulkan/android/examples/texturecubemaparray/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME texturecubemaparray)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/texturecubemaparray/build.gradle b/external/Vulkan/android/examples/texturecubemaparray/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..d087b1d5382c85c6c60a4a167eac80e9c1f23e81
--- /dev/null
+++ b/external/Vulkan/android/examples/texturecubemaparray/build.gradle
@@ -0,0 +1,96 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanTexturecubemapArray"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/texturecubemaparray'
+       into 'assets/shaders/glsl/texturecubemaparray'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'sphere.gltf'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'teapot.gltf'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'torusknot.gltf'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'cube.gltf'
+    }
+
+    copy {
+        from '../../../data/models'
+        into 'assets/models'
+        include 'venus.gltf'
+    }
+
+    copy {
+       from '../../../data/textures'
+       into 'assets/textures'
+       include 'cubemap_array.ktx'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/texturecubemaparray/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/texturecubemaparray/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..f194d0d08269322cd24ed9c21b2ac5b6eaa29342
--- /dev/null
+++ b/external/Vulkan/android/examples/texturecubemaparray/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanTexturecubemapArray">
+
+    <application
+        android:label="Vulkan cubemap arrays"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/texturecubemaparray/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/texturecubemaparray/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/texturecubemaparray/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/texturemipmapgen/CMakeLists.txt b/external/Vulkan/android/examples/texturemipmapgen/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..e2484913b9736b46e8d99063bf7c981018359c89
--- /dev/null
+++ b/external/Vulkan/android/examples/texturemipmapgen/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME texturemipmapgen)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/texturemipmapgen/build.gradle b/external/Vulkan/android/examples/texturemipmapgen/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..9e40740a079711632c7ac2d7abc8460d4316b67a
--- /dev/null
+++ b/external/Vulkan/android/examples/texturemipmapgen/build.gradle
@@ -0,0 +1,72 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanTexturemipmapgen"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/texturemipmapgen'
+       into 'assets/shaders/glsl/texturemipmapgen'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'tunnel_cylinder.gltf'
+    }
+
+    copy {
+       from '../../../data/textures'
+       into 'assets/textures'
+       include 'metalplate_nomips_rgba.ktx'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/texturemipmapgen/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/texturemipmapgen/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..d197eef0c47956e7b69bf19dc7f800086481a7fa
--- /dev/null
+++ b/external/Vulkan/android/examples/texturemipmapgen/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanTexturemipmapgen">
+
+    <application
+        android:label="Vulkan texture mip map generation"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/texturemipmapgen/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/texturemipmapgen/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/texturemipmapgen/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/triangle/CMakeLists.txt b/external/Vulkan/android/examples/triangle/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..6c0328288c5f61c4e4e4d0fd311bae7a6a580b42
--- /dev/null
+++ b/external/Vulkan/android/examples/triangle/CMakeLists.txt
@@ -0,0 +1,34 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME triangle)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/triangle/build.gradle b/external/Vulkan/android/examples/triangle/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..dba659441fb6bd9d816dafcffcf183f8355d28fd
--- /dev/null
+++ b/external/Vulkan/android/examples/triangle/build.gradle
@@ -0,0 +1,60 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanTriangle"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/triangle'
+       into 'assets/shaders/glsl/triangle'
+       include '*.*'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/triangle/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/triangle/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..3dd53b0bf0c004d13864a9648786291059191aa8
--- /dev/null
+++ b/external/Vulkan/android/examples/triangle/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanTriangle">
+
+    <application
+        android:label="Vulkan triangle"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/triangle/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/triangle/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/triangle/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/viewportarray/CMakeLists.txt b/external/Vulkan/android/examples/viewportarray/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..5b0ea747af0e85ac3e0afa6869b54c8492a6c559
--- /dev/null
+++ b/external/Vulkan/android/examples/viewportarray/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME viewportarray)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/viewportarray/build.gradle b/external/Vulkan/android/examples/viewportarray/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..d98e928890f9d9a5da78d3ed654cd2a30512a4e0
--- /dev/null
+++ b/external/Vulkan/android/examples/viewportarray/build.gradle
@@ -0,0 +1,66 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanViewportarray"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/viewportarray'
+       into 'assets/shaders/glsl/viewportarray'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'sampleroom.gltf'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/viewportarray/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/viewportarray/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..edbb29de0457ecaf3df5284144c57f5618ae6e40
--- /dev/null
+++ b/external/Vulkan/android/examples/viewportarray/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanViewportarray">
+
+    <application
+        android:label="Vulkan viewport arrays"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/viewportarray/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/viewportarray/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/viewportarray/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/examples/vulkanscene/CMakeLists.txt b/external/Vulkan/android/examples/vulkanscene/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..364beffb2e7e2ed347a648403fcbfb6535ba98b3
--- /dev/null
+++ b/external/Vulkan/android/examples/vulkanscene/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+set(NAME vulkanscene)
+
+set(SRC_DIR ../../../examples/${NAME})
+set(BASE_DIR ../../../base)
+set(EXTERNAL_DIR ../../../external)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
+
+file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
+
+add_library(native-lib SHARED ${EXAMPLE_SRC})
+
+add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+
+add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+include_directories(${BASE_DIR})
+include_directories(${EXTERNAL_DIR})
+include_directories(${EXTERNAL_DIR}/glm)
+include_directories(${EXTERNAL_DIR}/imgui)
+include_directories(${EXTERNAL_DIR}/tinygltf)
+include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
+
+target_link_libraries(
+    native-lib
+    native-app-glue
+    libbase
+    android
+    log
+    z
+)
diff --git a/external/Vulkan/android/examples/vulkanscene/build.gradle b/external/Vulkan/android/examples/vulkanscene/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..0d69bfd2e287eb9f9e1002479331b9188e176e2e
--- /dev/null
+++ b/external/Vulkan/android/examples/vulkanscene/build.gradle
@@ -0,0 +1,90 @@
+apply plugin: 'com.android.application'
+apply from: '../gradle/outputfilename.gradle'
+
+android {
+    compileSdkVersion 26
+    defaultConfig {
+        applicationId "de.saschawillems.vulkanVulkanscene"
+        minSdkVersion 19
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        ndk {
+            abiFilters "armeabi-v7a"
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++14"
+                arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
+            }
+        }
+    }
+    sourceSets {
+        main.assets.srcDirs = ['assets']
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+task copyTask {
+    copy {
+        from '../../common/res/drawable'
+        into "src/main/res/drawable"
+        include 'icon.png'
+    }
+
+    copy {
+        from '../../../data/shaders/glsl/base'
+        into 'assets/shaders/glsl/base'
+        include '*.spv'
+    }
+
+    copy {
+       from '../../../data/shaders/glsl/vulkanscene'
+       into 'assets/shaders/glsl/vulkanscene'
+       include '*.*'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'vulkanscenelogos.gltf'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'vulkanscenebackground.gltf'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'vulkanscenemodels.gltf'
+    }
+
+    copy {
+       from '../../../data/models'
+       into 'assets/models'
+       include 'cube.gltf'
+    }
+
+    copy {
+       from '../../../data/textures'
+       into 'assets/textures'
+       include 'cubemap_vulkan.ktx'
+    }
+
+
+}
+
+preBuild.dependsOn copyTask
\ No newline at end of file
diff --git a/external/Vulkan/android/examples/vulkanscene/src/main/AndroidManifest.xml b/external/Vulkan/android/examples/vulkanscene/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..6005450d90cd357dff98f6d7e6b08a69c9da432b
--- /dev/null
+++ b/external/Vulkan/android/examples/vulkanscene/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="de.saschawillems.vulkanVulkanscene">
+
+    <application
+        android:label="Vulkan demo scene"
+        android:icon="@drawable/icon"
+        android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
+        <activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
+            android:screenOrientation="landscape"
+            android:configChanges="orientation|keyboardHidden">
+            <meta-data android:name="android.app.lib_name"
+                android:value="native-lib" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+    <uses-feature android:name="android.hardware.gamepad" android:required="false" />
+
+</manifest>
diff --git a/external/Vulkan/android/examples/vulkanscene/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java b/external/Vulkan/android/examples/vulkanscene/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..12e14fc6d60f591c374b826780865301d0bc7f52
--- /dev/null
+++ b/external/Vulkan/android/examples/vulkanscene/src/main/java/de/saschawillems/vulkanSample/VulkanActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+package de.saschawillems.vulkanSample;
+
+import android.app.AlertDialog;
+import android.app.NativeActivity;
+import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+
+import java.util.concurrent.Semaphore;
+
+public class VulkanActivity extends NativeActivity {
+
+    static {
+        // Load native library
+        System.loadLibrary("native-lib");
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    // Use a semaphore to create a modal dialog
+
+    private final Semaphore semaphore = new Semaphore(0, true);
+
+    public void showAlert(final String message)
+    {
+        final VulkanActivity activity = this;
+
+        ApplicationInfo applicationInfo = activity.getApplicationInfo();
+        final String applicationName = applicationInfo.nonLocalizedLabel.toString();
+
+        this.runOnUiThread(new Runnable() {
+           public void run() {
+               AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
+               builder.setTitle(applicationName);
+               builder.setMessage(message);
+               builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
+                   public void onClick(DialogInterface dialog, int id) {
+                       semaphore.release();
+                   }
+               });
+               builder.setCancelable(false);
+               AlertDialog dialog = builder.create();
+               dialog.show();
+           }
+        });
+        try {
+            semaphore.acquire();
+        }
+        catch (InterruptedException e) { }
+    }
+}
diff --git a/external/Vulkan/android/gradle/wrapper/gradle-wrapper.jar b/external/Vulkan/android/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000000000000000000000000000000000000..13372aef5e24af05341d49695ee84e5f9b594659
Binary files /dev/null and b/external/Vulkan/android/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/external/Vulkan/android/gradle/wrapper/gradle-wrapper.properties b/external/Vulkan/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000000000000000000000000000000000000..68616ba906c93b79819ece967f2289701676a2dd
--- /dev/null
+++ b/external/Vulkan/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Sun Aug 04 12:29:22 CEST 2019
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
diff --git a/external/Vulkan/android/gradlew b/external/Vulkan/android/gradlew
new file mode 100755
index 0000000000000000000000000000000000000000..9d82f78915133e1c35a6ea51252590fb38efac2f
--- /dev/null
+++ b/external/Vulkan/android/gradlew
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+    echo "$*"
+}
+
+die ( ) {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+    JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/external/Vulkan/android/gradlew.bat b/external/Vulkan/android/gradlew.bat
new file mode 100644
index 0000000000000000000000000000000000000000..8a0b282aa6885fb573c106b3551f7275c5f17e8e
--- /dev/null
+++ b/external/Vulkan/android/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/external/Vulkan/android/settings.gradle b/external/Vulkan/android/settings.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..74697331a9d02c885044e44e890a947d8146b965
--- /dev/null
+++ b/external/Vulkan/android/settings.gradle
@@ -0,0 +1,9 @@
+file('examples').eachDir { sub ->
+    if (sub.name != '_template') {
+        if (file("examples/$sub.name/build.gradle").exists()) {
+            println("Adding project $sub.name")
+            include ":$sub.name"
+            project(":$sub.name").projectDir = new File("examples/$sub.name")
+        }
+    }
+}
diff --git a/external/Vulkan/base/CMakeLists.txt b/external/Vulkan/base/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..c4ccbc0119dbae28b4785bd37dab04545fdbfff2
--- /dev/null
+++ b/external/Vulkan/base/CMakeLists.txt
@@ -0,0 +1,18 @@
+file(GLOB BASE_SRC "*.cpp" "*.hpp" "*.h" "../external/imgui/*.cpp")
+file(GLOB BASE_HEADERS "*.hpp" "*.h")
+
+set(KTX_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../external/ktx)
+set(KTX_SOURCES
+    ${KTX_DIR}/lib/texture.c
+    ${KTX_DIR}/lib/hashlist.c
+    ${KTX_DIR}/lib/checkheader.c
+    ${KTX_DIR}/lib/swap.c
+    ${KTX_DIR}/lib/memstream.c
+    ${KTX_DIR}/lib/filestream.c)
+
+add_library(base STATIC ${BASE_SRC} ${KTX_SOURCES})
+if(WIN32)
+    target_link_libraries(base ${Vulkan_LIBRARY} ${WINLIBS})
+ else(WIN32)
+    target_link_libraries(base ${Vulkan_LIBRARY} ${XCB_LIBRARIES} ${WAYLAND_CLIENT_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
+endif(WIN32)
\ No newline at end of file
diff --git a/external/Vulkan/base/VulkanAndroid.cpp b/external/Vulkan/base/VulkanAndroid.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..4db82258af88aa3c3c041076fca66c52b1510612
--- /dev/null
+++ b/external/Vulkan/base/VulkanAndroid.cpp
@@ -0,0 +1,324 @@
+/*
+* Android Vulkan function pointer loader
+*
+* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "VulkanAndroid.h"
+
+#if defined(__ANDROID__)
+	#include <android/log.h>
+	#include <dlfcn.h>
+	#include <android/native_window_jni.h>
+
+android_app* androidApp;
+
+PFN_vkCreateInstance vkCreateInstance;
+PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr;
+PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr;
+PFN_vkCreateDevice vkCreateDevice;
+PFN_vkEnumeratePhysicalDevices vkEnumeratePhysicalDevices;
+PFN_vkGetPhysicalDeviceProperties vkGetPhysicalDeviceProperties;
+PFN_vkGetPhysicalDeviceProperties2 vkGetPhysicalDeviceProperties2;
+PFN_vkEnumerateDeviceExtensionProperties vkEnumerateDeviceExtensionProperties;
+PFN_vkEnumerateDeviceLayerProperties vkEnumerateDeviceLayerProperties;
+PFN_vkGetPhysicalDeviceFormatProperties vkGetPhysicalDeviceFormatProperties;
+PFN_vkGetPhysicalDeviceFeatures vkGetPhysicalDeviceFeatures;
+PFN_vkGetPhysicalDeviceFeatures2 vkGetPhysicalDeviceFeatures2;
+PFN_vkGetPhysicalDeviceQueueFamilyProperties vkGetPhysicalDeviceQueueFamilyProperties;
+PFN_vkGetPhysicalDeviceMemoryProperties vkGetPhysicalDeviceMemoryProperties;
+PFN_vkEnumerateInstanceExtensionProperties vkEnumerateInstanceExtensionProperties;
+PFN_vkEnumerateInstanceLayerProperties vkEnumerateInstanceLayerProperties;
+PFN_vkCmdPipelineBarrier vkCmdPipelineBarrier;
+PFN_vkCreateShaderModule vkCreateShaderModule;
+PFN_vkCreateBuffer vkCreateBuffer;
+PFN_vkGetBufferMemoryRequirements vkGetBufferMemoryRequirements;
+PFN_vkMapMemory vkMapMemory;
+PFN_vkUnmapMemory vkUnmapMemory;
+PFN_vkFlushMappedMemoryRanges vkFlushMappedMemoryRanges;
+PFN_vkInvalidateMappedMemoryRanges vkInvalidateMappedMemoryRanges;
+PFN_vkBindBufferMemory vkBindBufferMemory;
+PFN_vkDestroyBuffer vkDestroyBuffer;
+PFN_vkAllocateMemory vkAllocateMemory;
+PFN_vkBindImageMemory vkBindImageMemory;
+PFN_vkGetImageSubresourceLayout vkGetImageSubresourceLayout;
+PFN_vkCmdCopyBuffer vkCmdCopyBuffer;
+PFN_vkCmdCopyBufferToImage vkCmdCopyBufferToImage;
+PFN_vkCmdCopyImage vkCmdCopyImage;
+PFN_vkCmdBlitImage vkCmdBlitImage;
+PFN_vkCmdClearAttachments vkCmdClearAttachments;
+PFN_vkCreateSampler vkCreateSampler;
+PFN_vkDestroySampler vkDestroySampler;
+PFN_vkDestroyImage vkDestroyImage;
+PFN_vkFreeMemory vkFreeMemory;
+PFN_vkCreateRenderPass vkCreateRenderPass;
+PFN_vkCmdBeginRenderPass vkCmdBeginRenderPass;
+PFN_vkCmdEndRenderPass vkCmdEndRenderPass;
+PFN_vkCmdNextSubpass vkCmdNextSubpass;
+PFN_vkCmdExecuteCommands vkCmdExecuteCommands;
+PFN_vkCmdClearColorImage vkCmdClearColorImage;
+PFN_vkCreateImage vkCreateImage;
+PFN_vkGetImageMemoryRequirements vkGetImageMemoryRequirements;
+PFN_vkCreateImageView vkCreateImageView;
+PFN_vkDestroyImageView vkDestroyImageView;
+PFN_vkCreateSemaphore vkCreateSemaphore;
+PFN_vkDestroySemaphore vkDestroySemaphore;
+PFN_vkCreateFence vkCreateFence;
+PFN_vkDestroyFence vkDestroyFence;
+PFN_vkWaitForFences vkWaitForFences;
+PFN_vkResetFences vkResetFences;
+PFN_vkResetDescriptorPool vkResetDescriptorPool;
+PFN_vkCreateCommandPool vkCreateCommandPool;
+PFN_vkDestroyCommandPool vkDestroyCommandPool;
+PFN_vkAllocateCommandBuffers vkAllocateCommandBuffers;
+PFN_vkBeginCommandBuffer vkBeginCommandBuffer;
+PFN_vkEndCommandBuffer vkEndCommandBuffer;
+PFN_vkGetDeviceQueue vkGetDeviceQueue;
+PFN_vkQueueSubmit vkQueueSubmit;
+PFN_vkQueueWaitIdle vkQueueWaitIdle;
+PFN_vkDeviceWaitIdle vkDeviceWaitIdle;
+PFN_vkCreateFramebuffer vkCreateFramebuffer;
+PFN_vkCreatePipelineCache vkCreatePipelineCache;
+PFN_vkCreatePipelineLayout vkCreatePipelineLayout;
+PFN_vkCreateGraphicsPipelines vkCreateGraphicsPipelines;
+PFN_vkCreateComputePipelines vkCreateComputePipelines;
+PFN_vkCreateDescriptorPool vkCreateDescriptorPool;
+PFN_vkCreateDescriptorSetLayout vkCreateDescriptorSetLayout;
+PFN_vkAllocateDescriptorSets vkAllocateDescriptorSets;
+PFN_vkUpdateDescriptorSets vkUpdateDescriptorSets;
+PFN_vkCmdBindDescriptorSets vkCmdBindDescriptorSets;
+PFN_vkCmdBindPipeline vkCmdBindPipeline;
+PFN_vkCmdBindVertexBuffers vkCmdBindVertexBuffers;
+PFN_vkCmdBindIndexBuffer vkCmdBindIndexBuffer;
+PFN_vkCmdSetViewport vkCmdSetViewport;
+PFN_vkCmdSetScissor vkCmdSetScissor;
+PFN_vkCmdSetLineWidth vkCmdSetLineWidth;
+PFN_vkCmdSetDepthBias vkCmdSetDepthBias;
+PFN_vkCmdPushConstants vkCmdPushConstants;
+PFN_vkCmdDrawIndexed vkCmdDrawIndexed;
+PFN_vkCmdDraw vkCmdDraw;
+PFN_vkCmdDrawIndexedIndirect vkCmdDrawIndexedIndirect;
+PFN_vkCmdDrawIndirect vkCmdDrawIndirect;
+PFN_vkCmdDispatch vkCmdDispatch;
+PFN_vkDestroyPipeline vkDestroyPipeline;
+PFN_vkDestroyPipelineLayout vkDestroyPipelineLayout;
+PFN_vkDestroyDescriptorSetLayout vkDestroyDescriptorSetLayout;
+PFN_vkDestroyDevice vkDestroyDevice;
+PFN_vkDestroyInstance vkDestroyInstance;
+PFN_vkDestroyDescriptorPool vkDestroyDescriptorPool;
+PFN_vkFreeCommandBuffers vkFreeCommandBuffers;
+PFN_vkDestroyRenderPass vkDestroyRenderPass;
+PFN_vkDestroyFramebuffer vkDestroyFramebuffer;
+PFN_vkDestroyShaderModule vkDestroyShaderModule;
+PFN_vkDestroyPipelineCache vkDestroyPipelineCache;
+PFN_vkCreateQueryPool vkCreateQueryPool;
+PFN_vkDestroyQueryPool vkDestroyQueryPool;
+PFN_vkGetQueryPoolResults vkGetQueryPoolResults;
+PFN_vkCmdBeginQuery vkCmdBeginQuery;
+PFN_vkCmdEndQuery vkCmdEndQuery;
+PFN_vkCmdResetQueryPool vkCmdResetQueryPool;
+PFN_vkCmdCopyQueryPoolResults vkCmdCopyQueryPoolResults;
+
+PFN_vkCreateAndroidSurfaceKHR vkCreateAndroidSurfaceKHR;
+PFN_vkDestroySurfaceKHR vkDestroySurfaceKHR;
+
+int32_t vks::android::screenDensity;
+
+void *libVulkan;
+
+namespace vks
+{
+	namespace android
+	{
+		// Dynamically load Vulkan library and base function pointers
+		bool loadVulkanLibrary()
+		{
+			__android_log_print(ANDROID_LOG_INFO, "vulkanandroid", "Loading libvulkan.so...\n");
+
+			// Load vulkan library
+			libVulkan = dlopen("libvulkan.so", RTLD_NOW | RTLD_LOCAL);
+			if (!libVulkan)
+			{
+				__android_log_print(ANDROID_LOG_INFO, "vulkanandroid", "Could not load vulkan library : %s!\n", dlerror());
+				return false;
+			}
+
+			// Load base function pointers
+			vkEnumerateInstanceExtensionProperties = reinterpret_cast<PFN_vkEnumerateInstanceExtensionProperties>(dlsym(libVulkan, "vkEnumerateInstanceExtensionProperties"));
+			vkEnumerateInstanceLayerProperties = reinterpret_cast<PFN_vkEnumerateInstanceLayerProperties>(dlsym(libVulkan, "vkEnumerateInstanceLayerProperties"));
+			vkCreateInstance = reinterpret_cast<PFN_vkCreateInstance>(dlsym(libVulkan, "vkCreateInstance"));
+			vkGetInstanceProcAddr = reinterpret_cast<PFN_vkGetInstanceProcAddr>(dlsym(libVulkan, "vkGetInstanceProcAddr"));
+			vkGetDeviceProcAddr = reinterpret_cast<PFN_vkGetDeviceProcAddr>(dlsym(libVulkan, "vkGetDeviceProcAddr"));
+
+			return true;
+		}
+
+		// Load instance based Vulkan function pointers
+		void loadVulkanFunctions(VkInstance instance)
+		{
+			__android_log_print(ANDROID_LOG_INFO, "vulkanandroid", "Loading instance based function pointers...\n");
+
+			vkEnumeratePhysicalDevices = reinterpret_cast<PFN_vkEnumeratePhysicalDevices>(vkGetInstanceProcAddr(instance, "vkEnumeratePhysicalDevices"));
+			vkGetPhysicalDeviceProperties = reinterpret_cast<PFN_vkGetPhysicalDeviceProperties>(vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceProperties"));
+			vkGetPhysicalDeviceProperties2 = reinterpret_cast<PFN_vkGetPhysicalDeviceProperties2>(vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceProperties2"));
+			vkEnumerateDeviceLayerProperties = reinterpret_cast<PFN_vkEnumerateDeviceLayerProperties>(vkGetInstanceProcAddr(instance, "vkEnumerateDeviceLayerProperties"));
+			vkEnumerateDeviceExtensionProperties = reinterpret_cast<PFN_vkEnumerateDeviceExtensionProperties>(vkGetInstanceProcAddr(instance, "vkEnumerateDeviceExtensionProperties"));
+			vkGetPhysicalDeviceQueueFamilyProperties = reinterpret_cast<PFN_vkGetPhysicalDeviceQueueFamilyProperties>(vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceQueueFamilyProperties"));
+			vkGetPhysicalDeviceFeatures = reinterpret_cast<PFN_vkGetPhysicalDeviceFeatures>(vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceFeatures"));
+			vkGetPhysicalDeviceFeatures2 = reinterpret_cast<PFN_vkGetPhysicalDeviceFeatures2>(vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceFeatures2"));
+			vkCreateDevice = reinterpret_cast<PFN_vkCreateDevice>(vkGetInstanceProcAddr(instance, "vkCreateDevice"));
+			vkGetPhysicalDeviceFormatProperties = reinterpret_cast<PFN_vkGetPhysicalDeviceFormatProperties>(vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceFormatProperties"));
+			vkGetPhysicalDeviceMemoryProperties = reinterpret_cast<PFN_vkGetPhysicalDeviceMemoryProperties>(vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceMemoryProperties"));
+
+			vkCmdPipelineBarrier = reinterpret_cast<PFN_vkCmdPipelineBarrier>(vkGetInstanceProcAddr(instance, "vkCmdPipelineBarrier"));
+			vkCreateShaderModule = reinterpret_cast<PFN_vkCreateShaderModule>(vkGetInstanceProcAddr(instance, "vkCreateShaderModule"));
+
+			vkCreateBuffer = reinterpret_cast<PFN_vkCreateBuffer>(vkGetInstanceProcAddr(instance, "vkCreateBuffer"));
+			vkGetBufferMemoryRequirements = reinterpret_cast<PFN_vkGetBufferMemoryRequirements>(vkGetInstanceProcAddr(instance, "vkGetBufferMemoryRequirements"));
+			vkMapMemory = reinterpret_cast<PFN_vkMapMemory>(vkGetInstanceProcAddr(instance, "vkMapMemory"));
+			vkUnmapMemory = reinterpret_cast<PFN_vkUnmapMemory>(vkGetInstanceProcAddr(instance, "vkUnmapMemory"));
+			vkFlushMappedMemoryRanges = reinterpret_cast<PFN_vkFlushMappedMemoryRanges>(vkGetInstanceProcAddr(instance, "vkFlushMappedMemoryRanges"));
+			vkInvalidateMappedMemoryRanges = reinterpret_cast<PFN_vkInvalidateMappedMemoryRanges>(vkGetInstanceProcAddr(instance, "vkInvalidateMappedMemoryRanges"));
+			vkBindBufferMemory = reinterpret_cast<PFN_vkBindBufferMemory>(vkGetInstanceProcAddr(instance, "vkBindBufferMemory"));
+			vkDestroyBuffer = reinterpret_cast<PFN_vkDestroyBuffer>(vkGetInstanceProcAddr(instance, "vkDestroyBuffer"));
+
+			vkAllocateMemory = reinterpret_cast<PFN_vkAllocateMemory>(vkGetInstanceProcAddr(instance, "vkAllocateMemory"));
+			vkFreeMemory = reinterpret_cast<PFN_vkFreeMemory>(vkGetInstanceProcAddr(instance, "vkFreeMemory"));
+			vkCreateRenderPass = reinterpret_cast<PFN_vkCreateRenderPass>(vkGetInstanceProcAddr(instance, "vkCreateRenderPass"));
+			vkCmdBeginRenderPass = reinterpret_cast<PFN_vkCmdBeginRenderPass>(vkGetInstanceProcAddr(instance, "vkCmdBeginRenderPass"));
+			vkCmdEndRenderPass = reinterpret_cast<PFN_vkCmdEndRenderPass>(vkGetInstanceProcAddr(instance, "vkCmdEndRenderPass"));
+			vkCmdNextSubpass = reinterpret_cast<PFN_vkCmdNextSubpass>(vkGetInstanceProcAddr(instance, "vkCmdNextSubpass"));
+			vkCmdExecuteCommands = reinterpret_cast<PFN_vkCmdExecuteCommands>(vkGetInstanceProcAddr(instance, "vkCmdExecuteCommands"));
+	        vkCmdClearColorImage = reinterpret_cast<PFN_vkCmdClearColorImage>(vkGetInstanceProcAddr(instance, "vkCmdClearColorImage"));
+
+			vkCreateImage = reinterpret_cast<PFN_vkCreateImage>(vkGetInstanceProcAddr(instance, "vkCreateImage"));
+			vkGetImageMemoryRequirements = reinterpret_cast<PFN_vkGetImageMemoryRequirements>(vkGetInstanceProcAddr(instance, "vkGetImageMemoryRequirements"));
+			vkCreateImageView = reinterpret_cast<PFN_vkCreateImageView>(vkGetInstanceProcAddr(instance, "vkCreateImageView"));
+			vkDestroyImageView = reinterpret_cast<PFN_vkDestroyImageView>(vkGetInstanceProcAddr(instance, "vkDestroyImageView"));
+			vkBindImageMemory = reinterpret_cast<PFN_vkBindImageMemory>(vkGetInstanceProcAddr(instance, "vkBindImageMemory"));
+			vkGetImageSubresourceLayout = reinterpret_cast<PFN_vkGetImageSubresourceLayout>(vkGetInstanceProcAddr(instance, "vkGetImageSubresourceLayout"));
+			vkCmdCopyImage = reinterpret_cast<PFN_vkCmdCopyImage>(vkGetInstanceProcAddr(instance, "vkCmdCopyImage"));
+			vkCmdBlitImage = reinterpret_cast<PFN_vkCmdBlitImage>(vkGetInstanceProcAddr(instance, "vkCmdBlitImage"));
+			vkDestroyImage = reinterpret_cast<PFN_vkDestroyImage>(vkGetInstanceProcAddr(instance, "vkDestroyImage"));
+
+			vkCmdClearAttachments = reinterpret_cast<PFN_vkCmdClearAttachments>(vkGetInstanceProcAddr(instance, "vkCmdClearAttachments"));
+
+			vkCmdCopyBuffer = reinterpret_cast<PFN_vkCmdCopyBuffer>(vkGetInstanceProcAddr(instance, "vkCmdCopyBuffer"));
+			vkCmdCopyBufferToImage = reinterpret_cast<PFN_vkCmdCopyBufferToImage>(vkGetInstanceProcAddr(instance, "vkCmdCopyBufferToImage"));
+
+			vkCreateSampler = reinterpret_cast<PFN_vkCreateSampler>(vkGetInstanceProcAddr(instance, "vkCreateSampler"));
+			vkDestroySampler = reinterpret_cast<PFN_vkDestroySampler>(vkGetInstanceProcAddr(instance, "vkDestroySampler"));;
+
+			vkCreateSemaphore = reinterpret_cast<PFN_vkCreateSemaphore>(vkGetInstanceProcAddr(instance, "vkCreateSemaphore"));
+			vkDestroySemaphore = reinterpret_cast<PFN_vkDestroySemaphore>(vkGetInstanceProcAddr(instance, "vkDestroySemaphore"));
+
+			vkCreateFence = reinterpret_cast<PFN_vkCreateFence>(vkGetInstanceProcAddr(instance, "vkCreateFence"));
+			vkDestroyFence = reinterpret_cast<PFN_vkDestroyFence>(vkGetInstanceProcAddr(instance, "vkDestroyFence"));
+			vkWaitForFences = reinterpret_cast<PFN_vkWaitForFences>(vkGetInstanceProcAddr(instance, "vkWaitForFences"));
+			vkResetFences = reinterpret_cast<PFN_vkResetFences>(vkGetInstanceProcAddr(instance, "vkResetFences"));;
+	        vkResetDescriptorPool = reinterpret_cast<PFN_vkResetDescriptorPool>(vkGetInstanceProcAddr(instance, "vkResetDescriptorPool"));
+
+			vkCreateCommandPool = reinterpret_cast<PFN_vkCreateCommandPool>(vkGetInstanceProcAddr(instance, "vkCreateCommandPool"));
+			vkDestroyCommandPool = reinterpret_cast<PFN_vkDestroyCommandPool>(vkGetInstanceProcAddr(instance, "vkDestroyCommandPool"));;
+
+			vkAllocateCommandBuffers = reinterpret_cast<PFN_vkAllocateCommandBuffers>(vkGetInstanceProcAddr(instance, "vkAllocateCommandBuffers"));
+			vkBeginCommandBuffer = reinterpret_cast<PFN_vkBeginCommandBuffer>(vkGetInstanceProcAddr(instance, "vkBeginCommandBuffer"));
+			vkEndCommandBuffer = reinterpret_cast<PFN_vkEndCommandBuffer>(vkGetInstanceProcAddr(instance, "vkEndCommandBuffer"));
+
+			vkGetDeviceQueue = reinterpret_cast<PFN_vkGetDeviceQueue>(vkGetInstanceProcAddr(instance, "vkGetDeviceQueue"));
+			vkQueueSubmit = reinterpret_cast<PFN_vkQueueSubmit>(vkGetInstanceProcAddr(instance, "vkQueueSubmit"));
+			vkQueueWaitIdle = reinterpret_cast<PFN_vkQueueWaitIdle>(vkGetInstanceProcAddr(instance, "vkQueueWaitIdle"));
+
+			vkDeviceWaitIdle = reinterpret_cast<PFN_vkDeviceWaitIdle>(vkGetInstanceProcAddr(instance, "vkDeviceWaitIdle"));
+
+			vkCreateFramebuffer = reinterpret_cast<PFN_vkCreateFramebuffer>(vkGetInstanceProcAddr(instance, "vkCreateFramebuffer"));
+
+			vkCreatePipelineCache = reinterpret_cast<PFN_vkCreatePipelineCache>(vkGetInstanceProcAddr(instance, "vkCreatePipelineCache"));
+			vkCreatePipelineLayout = reinterpret_cast<PFN_vkCreatePipelineLayout>(vkGetInstanceProcAddr(instance, "vkCreatePipelineLayout"));
+			vkCreateGraphicsPipelines = reinterpret_cast<PFN_vkCreateGraphicsPipelines>(vkGetInstanceProcAddr(instance, "vkCreateGraphicsPipelines"));
+			vkCreateComputePipelines = reinterpret_cast<PFN_vkCreateComputePipelines>(vkGetInstanceProcAddr(instance, "vkCreateComputePipelines"));
+
+			vkCreateDescriptorPool = reinterpret_cast<PFN_vkCreateDescriptorPool>(vkGetInstanceProcAddr(instance, "vkCreateDescriptorPool"));
+			vkCreateDescriptorSetLayout = reinterpret_cast<PFN_vkCreateDescriptorSetLayout>(vkGetInstanceProcAddr(instance, "vkCreateDescriptorSetLayout"));
+
+			vkAllocateDescriptorSets = reinterpret_cast<PFN_vkAllocateDescriptorSets>(vkGetInstanceProcAddr(instance, "vkAllocateDescriptorSets"));
+			vkUpdateDescriptorSets = reinterpret_cast<PFN_vkUpdateDescriptorSets>(vkGetInstanceProcAddr(instance, "vkUpdateDescriptorSets"));
+
+			vkCmdBindDescriptorSets = reinterpret_cast<PFN_vkCmdBindDescriptorSets>(vkGetInstanceProcAddr(instance, "vkCmdBindDescriptorSets"));
+			vkCmdBindPipeline = reinterpret_cast<PFN_vkCmdBindPipeline>(vkGetInstanceProcAddr(instance, "vkCmdBindPipeline"));
+			vkCmdBindVertexBuffers = reinterpret_cast<PFN_vkCmdBindVertexBuffers>(vkGetInstanceProcAddr(instance, "vkCmdBindVertexBuffers"));
+			vkCmdBindIndexBuffer = reinterpret_cast<PFN_vkCmdBindIndexBuffer>(vkGetInstanceProcAddr(instance, "vkCmdBindIndexBuffer"));
+
+			vkCmdSetViewport = reinterpret_cast<PFN_vkCmdSetViewport>(vkGetInstanceProcAddr(instance, "vkCmdSetViewport"));
+			vkCmdSetScissor = reinterpret_cast<PFN_vkCmdSetScissor>(vkGetInstanceProcAddr(instance, "vkCmdSetScissor"));
+			vkCmdSetLineWidth = reinterpret_cast<PFN_vkCmdSetLineWidth>(vkGetInstanceProcAddr(instance, "vkCmdSetLineWidth"));
+			vkCmdSetDepthBias = reinterpret_cast<PFN_vkCmdSetDepthBias>(vkGetInstanceProcAddr(instance, "vkCmdSetDepthBias"));
+			vkCmdPushConstants = reinterpret_cast<PFN_vkCmdPushConstants>(vkGetInstanceProcAddr(instance, "vkCmdPushConstants"));;
+
+			vkCmdDrawIndexed = reinterpret_cast<PFN_vkCmdDrawIndexed>(vkGetInstanceProcAddr(instance, "vkCmdDrawIndexed"));
+			vkCmdDraw = reinterpret_cast<PFN_vkCmdDraw>(vkGetInstanceProcAddr(instance, "vkCmdDraw"));
+			vkCmdDrawIndexedIndirect = reinterpret_cast<PFN_vkCmdDrawIndexedIndirect>(vkGetInstanceProcAddr(instance, "vkCmdDrawIndexedIndirect"));
+			vkCmdDrawIndirect = reinterpret_cast<PFN_vkCmdDrawIndirect>(vkGetInstanceProcAddr(instance, "vkCmdDrawIndirect"));
+			vkCmdDispatch = reinterpret_cast<PFN_vkCmdDispatch>(vkGetInstanceProcAddr(instance, "vkCmdDispatch"));
+
+			vkDestroyPipeline = reinterpret_cast<PFN_vkDestroyPipeline>(vkGetInstanceProcAddr(instance, "vkDestroyPipeline"));
+			vkDestroyPipelineLayout = reinterpret_cast<PFN_vkDestroyPipelineLayout>(vkGetInstanceProcAddr(instance, "vkDestroyPipelineLayout"));;
+			vkDestroyDescriptorSetLayout = reinterpret_cast<PFN_vkDestroyDescriptorSetLayout>(vkGetInstanceProcAddr(instance, "vkDestroyDescriptorSetLayout"));
+			vkDestroyDevice = reinterpret_cast<PFN_vkDestroyDevice>(vkGetInstanceProcAddr(instance, "vkDestroyDevice"));
+			vkDestroyInstance = reinterpret_cast<PFN_vkDestroyInstance>(vkGetInstanceProcAddr(instance, "vkDestroyInstance"));
+			vkDestroyDescriptorPool = reinterpret_cast<PFN_vkDestroyDescriptorPool>(vkGetInstanceProcAddr(instance, "vkDestroyDescriptorPool"));
+			vkFreeCommandBuffers = reinterpret_cast<PFN_vkFreeCommandBuffers>(vkGetInstanceProcAddr(instance, "vkFreeCommandBuffers"));
+			vkDestroyRenderPass = reinterpret_cast<PFN_vkDestroyRenderPass>(vkGetInstanceProcAddr(instance, "vkDestroyRenderPass"));
+			vkDestroyFramebuffer = reinterpret_cast<PFN_vkDestroyFramebuffer>(vkGetInstanceProcAddr(instance, "vkDestroyFramebuffer"));
+			vkDestroyShaderModule = reinterpret_cast<PFN_vkDestroyShaderModule>(vkGetInstanceProcAddr(instance, "vkDestroyShaderModule"));
+			vkDestroyPipelineCache = reinterpret_cast<PFN_vkDestroyPipelineCache>(vkGetInstanceProcAddr(instance, "vkDestroyPipelineCache"));
+
+			vkCreateQueryPool = reinterpret_cast<PFN_vkCreateQueryPool>(vkGetInstanceProcAddr(instance, "vkCreateQueryPool"));
+			vkDestroyQueryPool = reinterpret_cast<PFN_vkDestroyQueryPool>(vkGetInstanceProcAddr(instance, "vkDestroyQueryPool"));
+			vkGetQueryPoolResults = reinterpret_cast<PFN_vkGetQueryPoolResults>(vkGetInstanceProcAddr(instance, "vkGetQueryPoolResults"));
+
+			vkCmdBeginQuery = reinterpret_cast<PFN_vkCmdBeginQuery>(vkGetInstanceProcAddr(instance, "vkCmdBeginQuery"));
+			vkCmdEndQuery = reinterpret_cast<PFN_vkCmdEndQuery>(vkGetInstanceProcAddr(instance, "vkCmdEndQuery"));
+			vkCmdResetQueryPool = reinterpret_cast<PFN_vkCmdResetQueryPool>(vkGetInstanceProcAddr(instance, "vkCmdResetQueryPool"));
+			vkCmdCopyQueryPoolResults = reinterpret_cast<PFN_vkCmdCopyQueryPoolResults>(vkGetInstanceProcAddr(instance, "vkCmdCopyQueryPoolResults"));
+
+			vkCreateAndroidSurfaceKHR = reinterpret_cast<PFN_vkCreateAndroidSurfaceKHR>(vkGetInstanceProcAddr(instance, "vkCreateAndroidSurfaceKHR"));
+			vkDestroySurfaceKHR = reinterpret_cast<PFN_vkDestroySurfaceKHR>(vkGetInstanceProcAddr(instance, "vkDestroySurfaceKHR"));
+		}
+
+		void freeVulkanLibrary()
+		{
+			dlclose(libVulkan);
+		}
+
+		void getDeviceConfig()
+		{
+			// Screen density
+			AConfiguration* config = AConfiguration_new();
+			AConfiguration_fromAssetManager(config, androidApp->activity->assetManager);
+			vks::android::screenDensity = AConfiguration_getDensity(config);
+			AConfiguration_delete(config);
+		}
+
+		// Displays a native alert dialog using JNI
+		void showAlert(const char* message) {
+			JNIEnv* jni;
+			androidApp->activity->vm->AttachCurrentThread(&jni, NULL);
+
+			jstring jmessage = jni->NewStringUTF(message);
+
+			jclass clazz = jni->GetObjectClass(androidApp->activity->clazz);
+			// Signature has to match java implementation (arguments)
+			jmethodID methodID = jni->GetMethodID(clazz, "showAlert", "(Ljava/lang/String;)V");
+			jni->CallVoidMethod(androidApp->activity->clazz, methodID, jmessage);
+			jni->DeleteLocalRef(jmessage);
+
+			androidApp->activity->vm->DetachCurrentThread();
+			return;
+		}
+	}
+}
+
+#endif
diff --git a/external/Vulkan/base/VulkanAndroid.h b/external/Vulkan/base/VulkanAndroid.h
new file mode 100644
index 0000000000000000000000000000000000000000..99a0c50aaf5bbabd86bc133414b3fdd49062d49a
--- /dev/null
+++ b/external/Vulkan/base/VulkanAndroid.h
@@ -0,0 +1,187 @@
+/*
+* Android Vulkan function pointer prototypes
+*
+* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#pragma once
+
+#ifndef VULKANANDROID_H
+#define VULKANANDROID_H
+
+// Vulkan needs to be loaded dynamically on android
+
+#pragma once
+
+#ifndef VULKANANDROID_HPP
+#define VULKANANDROID_HPP
+
+#include "vulkan/vulkan.h"
+
+#if defined(__ANDROID__)
+
+#include <android/log.h>
+#include <android_native_app_glue.h>
+#include <android/configuration.h>
+#include <memory>
+#include <string>
+
+// Missing from the NDK
+namespace std
+{
+	template<typename T, typename... Args>
+	std::unique_ptr<T> make_unique(Args&&... args)
+	{
+		return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
+	}
+}
+
+// Global reference to android application object
+extern android_app* androidApp;
+
+#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "vulkanExample", __VA_ARGS__))
+#define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "vulkanExample", __VA_ARGS__))
+#define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, "vulkanExample", __VA_ARGS__))
+#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "vulkanExample", __VA_ARGS__))
+
+// Function pointer prototypes
+// Not complete, just the functions used in the caps viewer!
+extern PFN_vkCreateInstance vkCreateInstance;
+extern PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr;
+extern PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr;
+extern PFN_vkCreateDevice vkCreateDevice;
+extern PFN_vkEnumeratePhysicalDevices vkEnumeratePhysicalDevices;
+extern PFN_vkGetPhysicalDeviceProperties vkGetPhysicalDeviceProperties;
+extern PFN_vkGetPhysicalDeviceProperties2 vkGetPhysicalDeviceProperties2;
+extern PFN_vkEnumerateDeviceExtensionProperties vkEnumerateDeviceExtensionProperties;
+extern PFN_vkEnumerateDeviceLayerProperties vkEnumerateDeviceLayerProperties;
+extern PFN_vkGetPhysicalDeviceFormatProperties vkGetPhysicalDeviceFormatProperties;
+extern PFN_vkGetPhysicalDeviceFeatures vkGetPhysicalDeviceFeatures;
+extern PFN_vkGetPhysicalDeviceFeatures2 vkGetPhysicalDeviceFeatures2;
+extern PFN_vkGetPhysicalDeviceQueueFamilyProperties vkGetPhysicalDeviceQueueFamilyProperties;
+extern PFN_vkGetPhysicalDeviceMemoryProperties vkGetPhysicalDeviceMemoryProperties;
+extern PFN_vkEnumerateInstanceExtensionProperties vkEnumerateInstanceExtensionProperties;
+extern PFN_vkEnumerateInstanceLayerProperties vkEnumerateInstanceLayerProperties;
+extern PFN_vkCmdPipelineBarrier vkCmdPipelineBarrier;
+extern PFN_vkCreateShaderModule vkCreateShaderModule;
+extern PFN_vkCreateBuffer vkCreateBuffer;
+extern PFN_vkGetBufferMemoryRequirements vkGetBufferMemoryRequirements;
+extern PFN_vkMapMemory vkMapMemory;
+extern PFN_vkUnmapMemory vkUnmapMemory;
+extern PFN_vkFlushMappedMemoryRanges vkFlushMappedMemoryRanges;
+extern PFN_vkInvalidateMappedMemoryRanges vkInvalidateMappedMemoryRanges;
+extern PFN_vkBindBufferMemory vkBindBufferMemory;
+extern PFN_vkDestroyBuffer vkDestroyBuffer;
+extern PFN_vkAllocateMemory vkAllocateMemory;
+extern PFN_vkBindImageMemory vkBindImageMemory;
+extern PFN_vkGetImageSubresourceLayout vkGetImageSubresourceLayout;
+extern PFN_vkCmdCopyBuffer vkCmdCopyBuffer;
+extern PFN_vkCmdCopyBufferToImage vkCmdCopyBufferToImage;
+extern PFN_vkCmdCopyImage vkCmdCopyImage;
+extern PFN_vkCmdBlitImage vkCmdBlitImage;
+extern PFN_vkCmdClearAttachments vkCmdClearAttachments;
+extern PFN_vkCreateSampler vkCreateSampler;
+extern PFN_vkDestroySampler vkDestroySampler;
+extern PFN_vkDestroyImage vkDestroyImage;
+extern PFN_vkFreeMemory vkFreeMemory;
+extern PFN_vkCreateRenderPass vkCreateRenderPass;
+extern PFN_vkCmdBeginRenderPass vkCmdBeginRenderPass;
+extern PFN_vkCmdEndRenderPass vkCmdEndRenderPass;
+extern PFN_vkCmdNextSubpass vkCmdNextSubpass;
+extern PFN_vkCmdExecuteCommands vkCmdExecuteCommands;
+extern PFN_vkCmdClearColorImage vkCmdClearColorImage;
+extern PFN_vkCreateImage vkCreateImage;
+extern PFN_vkGetImageMemoryRequirements vkGetImageMemoryRequirements;
+extern PFN_vkCreateImageView vkCreateImageView;
+extern PFN_vkDestroyImageView vkDestroyImageView;
+extern PFN_vkCreateSemaphore vkCreateSemaphore;
+extern PFN_vkDestroySemaphore vkDestroySemaphore;
+extern PFN_vkCreateFence vkCreateFence;
+extern PFN_vkDestroyFence vkDestroyFence;
+extern PFN_vkWaitForFences vkWaitForFences;
+extern PFN_vkResetFences vkResetFences;
+extern PFN_vkResetDescriptorPool vkResetDescriptorPool;
+extern PFN_vkCreateCommandPool vkCreateCommandPool;
+extern PFN_vkDestroyCommandPool vkDestroyCommandPool;
+extern PFN_vkAllocateCommandBuffers vkAllocateCommandBuffers;
+extern PFN_vkBeginCommandBuffer vkBeginCommandBuffer;
+extern PFN_vkEndCommandBuffer vkEndCommandBuffer;
+extern PFN_vkGetDeviceQueue vkGetDeviceQueue;
+extern PFN_vkQueueSubmit vkQueueSubmit;
+extern PFN_vkQueueWaitIdle vkQueueWaitIdle;
+extern PFN_vkDeviceWaitIdle vkDeviceWaitIdle;
+extern PFN_vkCreateFramebuffer vkCreateFramebuffer;
+extern PFN_vkCreatePipelineCache vkCreatePipelineCache;
+extern PFN_vkCreatePipelineLayout vkCreatePipelineLayout;
+extern PFN_vkCreateGraphicsPipelines vkCreateGraphicsPipelines;
+extern PFN_vkCreateComputePipelines vkCreateComputePipelines;
+extern PFN_vkCreateDescriptorPool vkCreateDescriptorPool;
+extern PFN_vkCreateDescriptorSetLayout vkCreateDescriptorSetLayout;
+extern PFN_vkAllocateDescriptorSets vkAllocateDescriptorSets;
+extern PFN_vkUpdateDescriptorSets vkUpdateDescriptorSets;
+extern PFN_vkCmdBindDescriptorSets vkCmdBindDescriptorSets;
+extern PFN_vkCmdBindPipeline vkCmdBindPipeline;
+extern PFN_vkCmdBindVertexBuffers vkCmdBindVertexBuffers;
+extern PFN_vkCmdBindIndexBuffer vkCmdBindIndexBuffer;
+extern PFN_vkCmdSetViewport vkCmdSetViewport;
+extern PFN_vkCmdSetScissor vkCmdSetScissor;
+extern PFN_vkCmdSetLineWidth vkCmdSetLineWidth;
+extern PFN_vkCmdSetDepthBias vkCmdSetDepthBias;
+extern PFN_vkCmdPushConstants vkCmdPushConstants;
+extern PFN_vkCmdDrawIndexed vkCmdDrawIndexed;
+extern PFN_vkCmdDraw vkCmdDraw;
+extern PFN_vkCmdDrawIndexedIndirect vkCmdDrawIndexedIndirect;
+extern PFN_vkCmdDrawIndirect vkCmdDrawIndirect;
+extern PFN_vkCmdDispatch vkCmdDispatch;
+extern PFN_vkDestroyPipeline vkDestroyPipeline;
+extern PFN_vkDestroyPipelineLayout vkDestroyPipelineLayout;
+extern PFN_vkDestroyDescriptorSetLayout vkDestroyDescriptorSetLayout;
+extern PFN_vkDestroyDevice vkDestroyDevice;
+extern PFN_vkDestroyInstance vkDestroyInstance;
+extern PFN_vkDestroyDescriptorPool vkDestroyDescriptorPool;
+extern PFN_vkFreeCommandBuffers vkFreeCommandBuffers;
+extern PFN_vkDestroyRenderPass vkDestroyRenderPass;
+extern PFN_vkDestroyFramebuffer vkDestroyFramebuffer;
+extern PFN_vkDestroyShaderModule vkDestroyShaderModule;
+extern PFN_vkDestroyPipelineCache vkDestroyPipelineCache;
+extern PFN_vkCreateQueryPool vkCreateQueryPool;
+extern PFN_vkDestroyQueryPool vkDestroyQueryPool;
+extern PFN_vkGetQueryPoolResults vkGetQueryPoolResults;
+extern PFN_vkCmdBeginQuery vkCmdBeginQuery;
+extern PFN_vkCmdEndQuery vkCmdEndQuery;
+extern PFN_vkCmdResetQueryPool vkCmdResetQueryPool;
+extern PFN_vkCmdCopyQueryPoolResults vkCmdCopyQueryPoolResults;
+
+extern PFN_vkCreateAndroidSurfaceKHR vkCreateAndroidSurfaceKHR;
+extern PFN_vkDestroySurfaceKHR vkDestroySurfaceKHR;
+
+namespace vks
+{
+	namespace android
+	{
+		/* @brief Touch control thresholds from Android NDK samples */
+		const int32_t DOUBLE_TAP_TIMEOUT = 300 * 1000000;
+		const int32_t TAP_TIMEOUT = 180 * 1000000;
+		const int32_t DOUBLE_TAP_SLOP = 100;
+		const int32_t TAP_SLOP = 8;
+
+		/** @brief Density of the device screen (in DPI) */
+		extern int32_t screenDensity;
+
+		bool loadVulkanLibrary();
+		void loadVulkanFunctions(VkInstance instance);
+		void freeVulkanLibrary();
+		void getDeviceConfig();
+		void showAlert(const char* message);
+	}
+}
+
+#endif
+
+#endif // VULKANANDROID_HPP
+
+
+#endif // VULKANANDROID_H
+ 
\ No newline at end of file
diff --git a/external/Vulkan/base/VulkanBuffer.cpp b/external/Vulkan/base/VulkanBuffer.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..7d4bf24c966742e32b7f2c962877f8572a60d03c
--- /dev/null
+++ b/external/Vulkan/base/VulkanBuffer.cpp
@@ -0,0 +1,135 @@
+/*
+* Vulkan buffer class
+*
+* Encapsulates a Vulkan buffer
+*
+* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "VulkanBuffer.h"
+
+namespace vks
+{	
+	/** 
+	* Map a memory range of this buffer. If successful, mapped points to the specified buffer range.
+	* 
+	* @param size (Optional) Size of the memory range to map. Pass VK_WHOLE_SIZE to map the complete buffer range.
+	* @param offset (Optional) Byte offset from beginning
+	* 
+	* @return VkResult of the buffer mapping call
+	*/
+	VkResult Buffer::map(VkDeviceSize size, VkDeviceSize offset)
+	{
+		return vkMapMemory(device, memory, offset, size, 0, &mapped);
+	}
+
+	/**
+	* Unmap a mapped memory range
+	*
+	* @note Does not return a result as vkUnmapMemory can't fail
+	*/
+	void Buffer::unmap()
+	{
+		if (mapped)
+		{
+			vkUnmapMemory(device, memory);
+			mapped = nullptr;
+		}
+	}
+
+	/** 
+	* Attach the allocated memory block to the buffer
+	* 
+	* @param offset (Optional) Byte offset (from the beginning) for the memory region to bind
+	* 
+	* @return VkResult of the bindBufferMemory call
+	*/
+	VkResult Buffer::bind(VkDeviceSize offset)
+	{
+		return vkBindBufferMemory(device, buffer, memory, offset);
+	}
+
+	/**
+	* Setup the default descriptor for this buffer
+	*
+	* @param size (Optional) Size of the memory range of the descriptor
+	* @param offset (Optional) Byte offset from beginning
+	*
+	*/
+	void Buffer::setupDescriptor(VkDeviceSize size, VkDeviceSize offset)
+	{
+		descriptor.offset = offset;
+		descriptor.buffer = buffer;
+		descriptor.range = size;
+	}
+
+	/**
+	* Copies the specified data to the mapped buffer
+	* 
+	* @param data Pointer to the data to copy
+	* @param size Size of the data to copy in machine units
+	*
+	*/
+	void Buffer::copyTo(void* data, VkDeviceSize size)
+	{
+		assert(mapped);
+		memcpy(mapped, data, size);
+	}
+
+	/** 
+	* Flush a memory range of the buffer to make it visible to the device
+	*
+	* @note Only required for non-coherent memory
+	*
+	* @param size (Optional) Size of the memory range to flush. Pass VK_WHOLE_SIZE to flush the complete buffer range.
+	* @param offset (Optional) Byte offset from beginning
+	*
+	* @return VkResult of the flush call
+	*/
+	VkResult Buffer::flush(VkDeviceSize size, VkDeviceSize offset)
+	{
+		VkMappedMemoryRange mappedRange = {};
+		mappedRange.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
+		mappedRange.memory = memory;
+		mappedRange.offset = offset;
+		mappedRange.size = size;
+		return vkFlushMappedMemoryRanges(device, 1, &mappedRange);
+	}
+
+	/**
+	* Invalidate a memory range of the buffer to make it visible to the host
+	*
+	* @note Only required for non-coherent memory
+	*
+	* @param size (Optional) Size of the memory range to invalidate. Pass VK_WHOLE_SIZE to invalidate the complete buffer range.
+	* @param offset (Optional) Byte offset from beginning
+	*
+	* @return VkResult of the invalidate call
+	*/
+	VkResult Buffer::invalidate(VkDeviceSize size, VkDeviceSize offset)
+	{
+		VkMappedMemoryRange mappedRange = {};
+		mappedRange.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
+		mappedRange.memory = memory;
+		mappedRange.offset = offset;
+		mappedRange.size = size;
+		return vkInvalidateMappedMemoryRanges(device, 1, &mappedRange);
+	}
+
+	/** 
+	* Release all Vulkan resources held by this buffer
+	*/
+	void Buffer::destroy()
+	{
+		if (buffer)
+		{
+			vkDestroyBuffer(device, buffer, nullptr);
+		}
+		if (memory)
+		{
+			vkFreeMemory(device, memory, nullptr);
+		}
+	}
+};
diff --git a/external/Vulkan/base/VulkanBuffer.h b/external/Vulkan/base/VulkanBuffer.h
new file mode 100644
index 0000000000000000000000000000000000000000..bd2a421f20bbfaf054fc257afcd69d4b5976cf84
--- /dev/null
+++ b/external/Vulkan/base/VulkanBuffer.h
@@ -0,0 +1,46 @@
+/*
+* Vulkan buffer class
+*
+* Encapsulates a Vulkan buffer
+*
+* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#pragma once
+
+#include <vector>
+
+#include "vulkan/vulkan.h"
+#include "VulkanTools.h"
+
+namespace vks
+{	
+	/**
+	* @brief Encapsulates access to a Vulkan buffer backed up by device memory
+	* @note To be filled by an external source like the VulkanDevice
+	*/
+	struct Buffer
+	{
+		VkDevice device;
+		VkBuffer buffer = VK_NULL_HANDLE;
+		VkDeviceMemory memory = VK_NULL_HANDLE;
+		VkDescriptorBufferInfo descriptor;
+		VkDeviceSize size = 0;
+		VkDeviceSize alignment = 0;
+		void* mapped = nullptr;
+		/** @brief Usage flags to be filled by external source at buffer creation (to query at some later point) */
+		VkBufferUsageFlags usageFlags;
+		/** @brief Memory property flags to be filled by external source at buffer creation (to query at some later point) */
+		VkMemoryPropertyFlags memoryPropertyFlags;
+		VkResult map(VkDeviceSize size = VK_WHOLE_SIZE, VkDeviceSize offset = 0);
+		void unmap();
+		VkResult bind(VkDeviceSize offset = 0);
+		void setupDescriptor(VkDeviceSize size = VK_WHOLE_SIZE, VkDeviceSize offset = 0);
+		void copyTo(void* data, VkDeviceSize size);
+		VkResult flush(VkDeviceSize size = VK_WHOLE_SIZE, VkDeviceSize offset = 0);
+		VkResult invalidate(VkDeviceSize size = VK_WHOLE_SIZE, VkDeviceSize offset = 0);
+		void destroy();
+	};
+}
\ No newline at end of file
diff --git a/external/Vulkan/base/VulkanDebug.cpp b/external/Vulkan/base/VulkanDebug.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..4ba9f9d328eec1a7916b24ad6f366861216016c3
--- /dev/null
+++ b/external/Vulkan/base/VulkanDebug.cpp
@@ -0,0 +1,261 @@
+/*
+* Vulkan examples debug wrapper
+* 
+* Copyright (C) by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "VulkanDebug.h"
+#include <iostream>
+
+namespace vks
+{
+	namespace debug
+	{
+		PFN_vkCreateDebugUtilsMessengerEXT vkCreateDebugUtilsMessengerEXT;
+		PFN_vkDestroyDebugUtilsMessengerEXT vkDestroyDebugUtilsMessengerEXT;
+		VkDebugUtilsMessengerEXT debugUtilsMessenger;
+
+		VKAPI_ATTR VkBool32 VKAPI_CALL debugUtilsMessengerCallback(
+			VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
+			VkDebugUtilsMessageTypeFlagsEXT messageType,
+			const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
+			void* pUserData)
+		{
+			// Select prefix depending on flags passed to the callback
+			std::string prefix("");
+
+			if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT) {
+				prefix = "VERBOSE: ";
+			}
+			else if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) {
+				prefix = "INFO: ";
+			}
+			else if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) {
+				prefix = "WARNING: ";
+			}
+			else if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) {
+				prefix = "ERROR: ";
+			}
+
+
+			// Display message to default output (console/logcat)
+			std::stringstream debugMessage;
+			debugMessage << prefix << "[" << pCallbackData->messageIdNumber << "][" << pCallbackData->pMessageIdName << "] : " << pCallbackData->pMessage;
+
+#if defined(__ANDROID__)
+			if (messageSeverity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) {
+				LOGE("%s", debugMessage.str().c_str());
+			} else {
+				LOGD("%s", debugMessage.str().c_str());
+			}
+#else
+			if (messageSeverity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) {
+				std::cerr << debugMessage.str() << "\n";
+			} else {
+				std::cout << debugMessage.str() << "\n";
+			}
+			fflush(stdout);
+#endif
+
+
+			// The return value of this callback controls whether the Vulkan call that caused the validation message will be aborted or not
+			// We return VK_FALSE as we DON'T want Vulkan calls that cause a validation message to abort
+			// If you instead want to have calls abort, pass in VK_TRUE and the function will return VK_ERROR_VALIDATION_FAILED_EXT 
+			return VK_FALSE;
+		}
+
+		void setupDebugging(VkInstance instance, VkDebugReportFlagsEXT flags, VkDebugReportCallbackEXT callBack)
+		{
+
+			vkCreateDebugUtilsMessengerEXT = reinterpret_cast<PFN_vkCreateDebugUtilsMessengerEXT>(vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"));
+			vkDestroyDebugUtilsMessengerEXT = reinterpret_cast<PFN_vkDestroyDebugUtilsMessengerEXT>(vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"));
+
+			VkDebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCI{};
+			debugUtilsMessengerCI.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
+			debugUtilsMessengerCI.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
+			debugUtilsMessengerCI.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT;
+			debugUtilsMessengerCI.pfnUserCallback = debugUtilsMessengerCallback;
+			VkResult result = vkCreateDebugUtilsMessengerEXT(instance, &debugUtilsMessengerCI, nullptr, &debugUtilsMessenger);
+			assert(result == VK_SUCCESS);
+		}
+
+		void freeDebugCallback(VkInstance instance)
+		{
+			if (debugUtilsMessenger != VK_NULL_HANDLE)
+			{
+				vkDestroyDebugUtilsMessengerEXT(instance, debugUtilsMessenger, nullptr);
+			}
+		}
+	}
+
+	namespace debugmarker
+	{
+		bool active = false;
+
+		PFN_vkDebugMarkerSetObjectTagEXT pfnDebugMarkerSetObjectTag = VK_NULL_HANDLE;
+		PFN_vkDebugMarkerSetObjectNameEXT pfnDebugMarkerSetObjectName = VK_NULL_HANDLE;
+		PFN_vkCmdDebugMarkerBeginEXT pfnCmdDebugMarkerBegin = VK_NULL_HANDLE;
+		PFN_vkCmdDebugMarkerEndEXT pfnCmdDebugMarkerEnd = VK_NULL_HANDLE;
+		PFN_vkCmdDebugMarkerInsertEXT pfnCmdDebugMarkerInsert = VK_NULL_HANDLE;
+
+		void setup(VkDevice device)
+		{
+			pfnDebugMarkerSetObjectTag = reinterpret_cast<PFN_vkDebugMarkerSetObjectTagEXT>(vkGetDeviceProcAddr(device, "vkDebugMarkerSetObjectTagEXT"));
+			pfnDebugMarkerSetObjectName = reinterpret_cast<PFN_vkDebugMarkerSetObjectNameEXT>(vkGetDeviceProcAddr(device, "vkDebugMarkerSetObjectNameEXT"));
+			pfnCmdDebugMarkerBegin = reinterpret_cast<PFN_vkCmdDebugMarkerBeginEXT>(vkGetDeviceProcAddr(device, "vkCmdDebugMarkerBeginEXT"));
+			pfnCmdDebugMarkerEnd = reinterpret_cast<PFN_vkCmdDebugMarkerEndEXT>(vkGetDeviceProcAddr(device, "vkCmdDebugMarkerEndEXT"));
+			pfnCmdDebugMarkerInsert = reinterpret_cast<PFN_vkCmdDebugMarkerInsertEXT>(vkGetDeviceProcAddr(device, "vkCmdDebugMarkerInsertEXT"));
+
+			// Set flag if at least one function pointer is present
+			active = (pfnDebugMarkerSetObjectName != VK_NULL_HANDLE);
+		}
+
+		void setObjectName(VkDevice device, uint64_t object, VkDebugReportObjectTypeEXT objectType, const char *name)
+		{
+			// Check for valid function pointer (may not be present if not running in a debugging application)
+			if (pfnDebugMarkerSetObjectName)
+			{
+				VkDebugMarkerObjectNameInfoEXT nameInfo = {};
+				nameInfo.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_OBJECT_NAME_INFO_EXT;
+				nameInfo.objectType = objectType;
+				nameInfo.object = object;
+				nameInfo.pObjectName = name;
+				pfnDebugMarkerSetObjectName(device, &nameInfo);
+			}
+		}
+
+		void setObjectTag(VkDevice device, uint64_t object, VkDebugReportObjectTypeEXT objectType, uint64_t name, size_t tagSize, const void* tag)
+		{
+			// Check for valid function pointer (may not be present if not running in a debugging application)
+			if (pfnDebugMarkerSetObjectTag)
+			{
+				VkDebugMarkerObjectTagInfoEXT tagInfo = {};
+				tagInfo.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_OBJECT_TAG_INFO_EXT;
+				tagInfo.objectType = objectType;
+				tagInfo.object = object;
+				tagInfo.tagName = name;
+				tagInfo.tagSize = tagSize;
+				tagInfo.pTag = tag;
+				pfnDebugMarkerSetObjectTag(device, &tagInfo);
+			}
+		}
+
+		void beginRegion(VkCommandBuffer cmdbuffer, const char* pMarkerName, glm::vec4 color)
+		{
+			// Check for valid function pointer (may not be present if not running in a debugging application)
+			if (pfnCmdDebugMarkerBegin)
+			{
+				VkDebugMarkerMarkerInfoEXT markerInfo = {};
+				markerInfo.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT;
+				memcpy(markerInfo.color, &color[0], sizeof(float) * 4);
+				markerInfo.pMarkerName = pMarkerName;
+				pfnCmdDebugMarkerBegin(cmdbuffer, &markerInfo);
+			}
+		}
+
+		void insert(VkCommandBuffer cmdbuffer, std::string markerName, glm::vec4 color)
+		{
+			// Check for valid function pointer (may not be present if not running in a debugging application)
+			if (pfnCmdDebugMarkerInsert)
+			{
+				VkDebugMarkerMarkerInfoEXT markerInfo = {};
+				markerInfo.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT;
+				memcpy(markerInfo.color, &color[0], sizeof(float) * 4);
+				markerInfo.pMarkerName = markerName.c_str();
+				pfnCmdDebugMarkerInsert(cmdbuffer, &markerInfo);
+			}
+		}
+
+		void endRegion(VkCommandBuffer cmdBuffer)
+		{
+			// Check for valid function (may not be present if not running in a debugging application)
+			if (pfnCmdDebugMarkerEnd)
+			{
+				pfnCmdDebugMarkerEnd(cmdBuffer);
+			}
+		}
+
+		void setCommandBufferName(VkDevice device, VkCommandBuffer cmdBuffer, const char * name)
+		{
+			setObjectName(device, (uint64_t)cmdBuffer, VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT, name);
+		}
+
+		void setQueueName(VkDevice device, VkQueue queue, const char * name)
+		{
+			setObjectName(device, (uint64_t)queue, VK_DEBUG_REPORT_OBJECT_TYPE_QUEUE_EXT, name);
+		}
+
+		void setImageName(VkDevice device, VkImage image, const char * name)
+		{
+			setObjectName(device, (uint64_t)image, VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_EXT, name);
+		}
+
+		void setSamplerName(VkDevice device, VkSampler sampler, const char * name)
+		{
+			setObjectName(device, (uint64_t)sampler, VK_DEBUG_REPORT_OBJECT_TYPE_SAMPLER_EXT, name);
+		}
+
+		void setBufferName(VkDevice device, VkBuffer buffer, const char * name)
+		{
+			setObjectName(device, (uint64_t)buffer, VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_EXT, name);
+		}
+
+		void setDeviceMemoryName(VkDevice device, VkDeviceMemory memory, const char * name)
+		{
+			setObjectName(device, (uint64_t)memory, VK_DEBUG_REPORT_OBJECT_TYPE_DEVICE_MEMORY_EXT, name);
+		}
+
+		void setShaderModuleName(VkDevice device, VkShaderModule shaderModule, const char * name)
+		{
+			setObjectName(device, (uint64_t)shaderModule, VK_DEBUG_REPORT_OBJECT_TYPE_SHADER_MODULE_EXT, name);
+		}
+
+		void setPipelineName(VkDevice device, VkPipeline pipeline, const char * name)
+		{
+			setObjectName(device, (uint64_t)pipeline, VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_EXT, name);
+		}
+
+		void setPipelineLayoutName(VkDevice device, VkPipelineLayout pipelineLayout, const char * name)
+		{
+			setObjectName(device, (uint64_t)pipelineLayout, VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_LAYOUT_EXT, name);
+		}
+
+		void setRenderPassName(VkDevice device, VkRenderPass renderPass, const char * name)
+		{
+			setObjectName(device, (uint64_t)renderPass, VK_DEBUG_REPORT_OBJECT_TYPE_RENDER_PASS_EXT, name);
+		}
+
+		void setFramebufferName(VkDevice device, VkFramebuffer framebuffer, const char * name)
+		{
+			setObjectName(device, (uint64_t)framebuffer, VK_DEBUG_REPORT_OBJECT_TYPE_FRAMEBUFFER_EXT, name);
+		}
+
+		void setDescriptorSetLayoutName(VkDevice device, VkDescriptorSetLayout descriptorSetLayout, const char * name)
+		{
+			setObjectName(device, (uint64_t)descriptorSetLayout, VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT_EXT, name);
+		}
+
+		void setDescriptorSetName(VkDevice device, VkDescriptorSet descriptorSet, const char * name)
+		{
+			setObjectName(device, (uint64_t)descriptorSet, VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_SET_EXT, name);
+		}
+
+		void setSemaphoreName(VkDevice device, VkSemaphore semaphore, const char * name)
+		{
+			setObjectName(device, (uint64_t)semaphore, VK_DEBUG_REPORT_OBJECT_TYPE_SEMAPHORE_EXT, name);
+		}
+
+		void setFenceName(VkDevice device, VkFence fence, const char * name)
+		{
+			setObjectName(device, (uint64_t)fence, VK_DEBUG_REPORT_OBJECT_TYPE_FENCE_EXT, name);
+		}
+
+		void setEventName(VkDevice device, VkEvent _event, const char * name)
+		{
+			setObjectName(device, (uint64_t)_event, VK_DEBUG_REPORT_OBJECT_TYPE_EVENT_EXT, name);
+		}
+	};
+}
+
diff --git a/external/Vulkan/base/VulkanDebug.h b/external/Vulkan/base/VulkanDebug.h
new file mode 100644
index 0000000000000000000000000000000000000000..3cf29a05d1fc7059938d52796559e0ad8c90ba5a
--- /dev/null
+++ b/external/Vulkan/base/VulkanDebug.h
@@ -0,0 +1,102 @@
+#pragma once
+#include "vulkan/vulkan.h"
+
+#include <math.h>
+#include <stdlib.h>
+#include <string>
+#include <cstring>
+#include <fstream>
+#include <assert.h>
+#include <stdio.h>
+#include <vector>
+#include <sstream>
+#ifdef _WIN32
+#include <windows.h>
+#include <fcntl.h>
+#include <io.h>
+#endif
+#ifdef __ANDROID__
+#include "VulkanAndroid.h"
+#endif
+#define GLM_FORCE_RADIANS
+#define GLM_FORCE_DEPTH_ZERO_TO_ONE
+#include <glm/glm.hpp>
+
+namespace vks
+{
+	namespace debug
+	{
+		// Default validation layers
+		extern int validationLayerCount;
+		extern const char *validationLayerNames[];
+
+		// Default debug callback
+		VKAPI_ATTR VkBool32 VKAPI_CALL messageCallback(
+			VkDebugReportFlagsEXT flags,
+			VkDebugReportObjectTypeEXT objType,
+			uint64_t srcObject,
+			size_t location,
+			int32_t msgCode,
+			const char* pLayerPrefix,
+			const char* pMsg,
+			void* pUserData);
+
+		// Load debug function pointers and set debug callback
+		// if callBack is NULL, default message callback will be used
+		void setupDebugging(
+			VkInstance instance,
+			VkDebugReportFlagsEXT flags,
+			VkDebugReportCallbackEXT callBack);
+		// Clear debug callback
+		void freeDebugCallback(VkInstance instance);
+	}
+
+	// Setup and functions for the VK_EXT_debug_marker_extension
+	// Extension spec can be found at https://github.com/KhronosGroup/Vulkan-Docs/blob/1.0-VK_EXT_debug_marker/doc/specs/vulkan/appendices/VK_EXT_debug_marker.txt
+	// Note that the extension will only be present if run from an offline debugging application
+	// The actual check for extension presence and enabling it on the device is done in the example base class
+	// See VulkanExampleBase::createInstance and VulkanExampleBase::createDevice (base/vulkanexamplebase.cpp)
+	namespace debugmarker
+	{
+		// Set to true if function pointer for the debug marker are available
+		extern bool active;
+
+		// Get function pointers for the debug report extensions from the device
+		void setup(VkDevice device);
+
+		// Sets the debug name of an object
+		// All Objects in Vulkan are represented by their 64-bit handles which are passed into this function
+		// along with the object type
+		void setObjectName(VkDevice device, uint64_t object, VkDebugReportObjectTypeEXT objectType, const char *name);
+
+		// Set the tag for an object
+		void setObjectTag(VkDevice device, uint64_t object, VkDebugReportObjectTypeEXT objectType, uint64_t name, size_t tagSize, const void* tag);
+
+		// Start a new debug marker region
+		void beginRegion(VkCommandBuffer cmdbuffer, const char* pMarkerName, glm::vec4 color);
+
+		// Insert a new debug marker into the command buffer
+		void insert(VkCommandBuffer cmdbuffer, std::string markerName, glm::vec4 color);
+
+		// End the current debug marker region
+		void endRegion(VkCommandBuffer cmdBuffer);
+
+		// Object specific naming functions
+		void setCommandBufferName(VkDevice device, VkCommandBuffer cmdBuffer, const char * name);
+		void setQueueName(VkDevice device, VkQueue queue, const char * name);
+		void setImageName(VkDevice device, VkImage image, const char * name);
+		void setSamplerName(VkDevice device, VkSampler sampler, const char * name);
+		void setBufferName(VkDevice device, VkBuffer buffer, const char * name);
+		void setDeviceMemoryName(VkDevice device, VkDeviceMemory memory, const char * name);
+		void setShaderModuleName(VkDevice device, VkShaderModule shaderModule, const char * name);
+		void setPipelineName(VkDevice device, VkPipeline pipeline, const char * name);
+		void setPipelineLayoutName(VkDevice device, VkPipelineLayout pipelineLayout, const char * name);
+		void setRenderPassName(VkDevice device, VkRenderPass renderPass, const char * name);
+		void setFramebufferName(VkDevice device, VkFramebuffer framebuffer, const char * name);
+		void setDescriptorSetLayoutName(VkDevice device, VkDescriptorSetLayout descriptorSetLayout, const char * name);
+		void setDescriptorSetName(VkDevice device, VkDescriptorSet descriptorSet, const char * name);
+		void setSemaphoreName(VkDevice device, VkSemaphore semaphore, const char * name);
+		void setFenceName(VkDevice device, VkFence fence, const char * name);
+		void setEventName(VkDevice device, VkEvent _event, const char * name);
+	};
+}
diff --git a/external/Vulkan/base/VulkanDevice.cpp b/external/Vulkan/base/VulkanDevice.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..8372afaa51e0c6ee506363cfaa14cb35979a6179
--- /dev/null
+++ b/external/Vulkan/base/VulkanDevice.cpp
@@ -0,0 +1,583 @@
+/*
+* Vulkan device class
+*
+* Encapsulates a physical Vulkan device and its logical representation
+*
+* Copyright (C) by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include <VulkanDevice.h>
+#include <unordered_set>
+
+namespace vks
+{	
+	/**
+	* Default constructor
+	*
+	* @param physicalDevice Physical device that is to be used
+	*/
+	VulkanDevice::VulkanDevice(VkPhysicalDevice physicalDevice)
+	{
+		assert(physicalDevice);
+		this->physicalDevice = physicalDevice;
+
+		// Store Properties features, limits and properties of the physical device for later use
+		// Device properties also contain limits and sparse properties
+		vkGetPhysicalDeviceProperties(physicalDevice, &properties);
+		// Features should be checked by the examples before using them
+		vkGetPhysicalDeviceFeatures(physicalDevice, &features);
+		// Memory properties are used regularly for creating all kinds of buffers
+		vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memoryProperties);
+		// Queue family properties, used for setting up requested queues upon device creation
+		uint32_t queueFamilyCount;
+		vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, nullptr);
+		assert(queueFamilyCount > 0);
+		queueFamilyProperties.resize(queueFamilyCount);
+		vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, queueFamilyProperties.data());
+
+		// Get list of supported extensions
+		uint32_t extCount = 0;
+		vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &extCount, nullptr);
+		if (extCount > 0)
+		{
+			std::vector<VkExtensionProperties> extensions(extCount);
+			if (vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &extCount, &extensions.front()) == VK_SUCCESS)
+			{
+				for (auto ext : extensions)
+				{
+					supportedExtensions.push_back(ext.extensionName);
+				}
+			}
+		}
+	}
+
+	/** 
+	* Default destructor
+	*
+	* @note Frees the logical device
+	*/
+	VulkanDevice::~VulkanDevice()
+	{
+		if (commandPool)
+		{
+			vkDestroyCommandPool(logicalDevice, commandPool, nullptr);
+		}
+		if (logicalDevice)
+		{
+			vkDestroyDevice(logicalDevice, nullptr);
+		}
+	}
+
+	/**
+	* Get the index of a memory type that has all the requested property bits set
+	*
+	* @param typeBits Bit mask with bits set for each memory type supported by the resource to request for (from VkMemoryRequirements)
+	* @param properties Bit mask of properties for the memory type to request
+	* @param (Optional) memTypeFound Pointer to a bool that is set to true if a matching memory type has been found
+	* 
+	* @return Index of the requested memory type
+	*
+	* @throw Throws an exception if memTypeFound is null and no memory type could be found that supports the requested properties
+	*/
+	uint32_t VulkanDevice::getMemoryType(uint32_t typeBits, VkMemoryPropertyFlags properties, VkBool32 *memTypeFound) const
+	{
+		for (uint32_t i = 0; i < memoryProperties.memoryTypeCount; i++)
+		{
+			if ((typeBits & 1) == 1)
+			{
+				if ((memoryProperties.memoryTypes[i].propertyFlags & properties) == properties)
+				{
+					if (memTypeFound)
+					{
+						*memTypeFound = true;
+					}
+					return i;
+				}
+			}
+			typeBits >>= 1;
+		}
+
+		if (memTypeFound)
+		{
+			*memTypeFound = false;
+			return 0;
+		}
+		else
+		{
+			throw std::runtime_error("Could not find a matching memory type");
+		}
+	}
+
+	/**
+	* Get the index of a queue family that supports the requested queue flags
+	*
+	* @param queueFlags Queue flags to find a queue family index for
+	*
+	* @return Index of the queue family index that matches the flags
+	*
+	* @throw Throws an exception if no queue family index could be found that supports the requested flags
+	*/
+	uint32_t VulkanDevice::getQueueFamilyIndex(VkQueueFlagBits queueFlags) const
+	{
+		// Dedicated queue for compute
+		// Try to find a queue family index that supports compute but not graphics
+		if (queueFlags & VK_QUEUE_COMPUTE_BIT)
+		{
+			for (uint32_t i = 0; i < static_cast<uint32_t>(queueFamilyProperties.size()); i++)
+			{
+				if ((queueFamilyProperties[i].queueFlags & queueFlags) && ((queueFamilyProperties[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) == 0))
+				{
+					return i;
+				}
+			}
+		}
+
+		// Dedicated queue for transfer
+		// Try to find a queue family index that supports transfer but not graphics and compute
+		if (queueFlags & VK_QUEUE_TRANSFER_BIT)
+		{
+			for (uint32_t i = 0; i < static_cast<uint32_t>(queueFamilyProperties.size()); i++)
+			{
+				if ((queueFamilyProperties[i].queueFlags & queueFlags) && ((queueFamilyProperties[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) == 0) && ((queueFamilyProperties[i].queueFlags & VK_QUEUE_COMPUTE_BIT) == 0))
+				{
+					return i;
+				}
+			}
+		}
+
+		// For other queue types or if no separate compute queue is present, return the first one to support the requested flags
+		for (uint32_t i = 0; i < static_cast<uint32_t>(queueFamilyProperties.size()); i++)
+		{
+			if (queueFamilyProperties[i].queueFlags & queueFlags)
+			{
+				return i;
+			}
+		}
+
+		throw std::runtime_error("Could not find a matching queue family index");
+	}
+
+	/**
+	* Create the logical device based on the assigned physical device, also gets default queue family indices
+	*
+	* @param enabledFeatures Can be used to enable certain features upon device creation
+	* @param pNextChain Optional chain of pointer to extension structures
+	* @param useSwapChain Set to false for headless rendering to omit the swapchain device extensions
+	* @param requestedQueueTypes Bit flags specifying the queue types to be requested from the device  
+	*
+	* @return VkResult of the device creation call
+	*/
+	VkResult VulkanDevice::createLogicalDevice(VkPhysicalDeviceFeatures enabledFeatures, std::vector<const char*> enabledExtensions, void* pNextChain, bool useSwapChain, VkQueueFlags requestedQueueTypes)
+	{			
+		// Desired queues need to be requested upon logical device creation
+		// Due to differing queue family configurations of Vulkan implementations this can be a bit tricky, especially if the application
+		// requests different queue types
+
+		std::vector<VkDeviceQueueCreateInfo> queueCreateInfos{};
+
+		// Get queue family indices for the requested queue family types
+		// Note that the indices may overlap depending on the implementation
+
+		const float defaultQueuePriority(0.0f);
+
+		// Graphics queue
+		if (requestedQueueTypes & VK_QUEUE_GRAPHICS_BIT)
+		{
+			queueFamilyIndices.graphics = getQueueFamilyIndex(VK_QUEUE_GRAPHICS_BIT);
+			VkDeviceQueueCreateInfo queueInfo{};
+			queueInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
+			queueInfo.queueFamilyIndex = queueFamilyIndices.graphics;
+			queueInfo.queueCount = 1;
+			queueInfo.pQueuePriorities = &defaultQueuePriority;
+			queueCreateInfos.push_back(queueInfo);
+		}
+		else
+		{
+			queueFamilyIndices.graphics = 0;
+		}
+
+		// Dedicated compute queue
+		if (requestedQueueTypes & VK_QUEUE_COMPUTE_BIT)
+		{
+			queueFamilyIndices.compute = getQueueFamilyIndex(VK_QUEUE_COMPUTE_BIT);
+			if (queueFamilyIndices.compute != queueFamilyIndices.graphics)
+			{
+				// If compute family index differs, we need an additional queue create info for the compute queue
+				VkDeviceQueueCreateInfo queueInfo{};
+				queueInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
+				queueInfo.queueFamilyIndex = queueFamilyIndices.compute;
+				queueInfo.queueCount = 1;
+				queueInfo.pQueuePriorities = &defaultQueuePriority;
+				queueCreateInfos.push_back(queueInfo);
+			}
+		}
+		else
+		{
+			// Else we use the same queue
+			queueFamilyIndices.compute = queueFamilyIndices.graphics;
+		}
+
+		// Dedicated transfer queue
+		if (requestedQueueTypes & VK_QUEUE_TRANSFER_BIT)
+		{
+			queueFamilyIndices.transfer = getQueueFamilyIndex(VK_QUEUE_TRANSFER_BIT);
+			if ((queueFamilyIndices.transfer != queueFamilyIndices.graphics) && (queueFamilyIndices.transfer != queueFamilyIndices.compute))
+			{
+				// If compute family index differs, we need an additional queue create info for the compute queue
+				VkDeviceQueueCreateInfo queueInfo{};
+				queueInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
+				queueInfo.queueFamilyIndex = queueFamilyIndices.transfer;
+				queueInfo.queueCount = 1;
+				queueInfo.pQueuePriorities = &defaultQueuePriority;
+				queueCreateInfos.push_back(queueInfo);
+			}
+		}
+		else
+		{
+			// Else we use the same queue
+			queueFamilyIndices.transfer = queueFamilyIndices.graphics;
+		}
+
+		// Create the logical device representation
+		std::vector<const char*> deviceExtensions(enabledExtensions);
+		if (useSwapChain)
+		{
+			// If the device will be used for presenting to a display via a swapchain we need to request the swapchain extension
+			deviceExtensions.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
+		}
+
+		VkDeviceCreateInfo deviceCreateInfo = {};
+		deviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
+		deviceCreateInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfos.size());;
+		deviceCreateInfo.pQueueCreateInfos = queueCreateInfos.data();
+		deviceCreateInfo.pEnabledFeatures = &enabledFeatures;
+		
+		// If a pNext(Chain) has been passed, we need to add it to the device creation info
+		VkPhysicalDeviceFeatures2 physicalDeviceFeatures2{};
+		if (pNextChain) {
+			physicalDeviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
+			physicalDeviceFeatures2.features = enabledFeatures;
+			physicalDeviceFeatures2.pNext = pNextChain;
+			deviceCreateInfo.pEnabledFeatures = nullptr;
+			deviceCreateInfo.pNext = &physicalDeviceFeatures2;
+		}
+
+		// Enable the debug marker extension if it is present (likely meaning a debugging tool is present)
+		if (extensionSupported(VK_EXT_DEBUG_MARKER_EXTENSION_NAME))
+		{
+			deviceExtensions.push_back(VK_EXT_DEBUG_MARKER_EXTENSION_NAME);
+			enableDebugMarkers = true;
+		}
+
+		if (deviceExtensions.size() > 0)
+		{
+			for (const char* enabledExtension : deviceExtensions)
+			{
+				if (!extensionSupported(enabledExtension)) {
+					std::cerr << "Enabled device extension \"" << enabledExtension << "\" is not present at device level\n";
+				}
+			}
+
+			deviceCreateInfo.enabledExtensionCount = (uint32_t)deviceExtensions.size();
+			deviceCreateInfo.ppEnabledExtensionNames = deviceExtensions.data();
+		}
+
+		this->enabledFeatures = enabledFeatures;
+
+		VkResult result = vkCreateDevice(physicalDevice, &deviceCreateInfo, nullptr, &logicalDevice);
+		if (result != VK_SUCCESS) 
+		{
+			return result;
+		}
+
+		// Create a default command pool for graphics command buffers
+		commandPool = createCommandPool(queueFamilyIndices.graphics);
+
+		return result;
+	}
+
+	/**
+	* Create a buffer on the device
+	*
+	* @param usageFlags Usage flag bit mask for the buffer (i.e. index, vertex, uniform buffer)
+	* @param memoryPropertyFlags Memory properties for this buffer (i.e. device local, host visible, coherent)
+	* @param size Size of the buffer in byes
+	* @param buffer Pointer to the buffer handle acquired by the function
+	* @param memory Pointer to the memory handle acquired by the function
+	* @param data Pointer to the data that should be copied to the buffer after creation (optional, if not set, no data is copied over)
+	*
+	* @return VK_SUCCESS if buffer handle and memory have been created and (optionally passed) data has been copied
+	*/
+	VkResult VulkanDevice::createBuffer(VkBufferUsageFlags usageFlags, VkMemoryPropertyFlags memoryPropertyFlags, VkDeviceSize size, VkBuffer *buffer, VkDeviceMemory *memory, void *data)
+	{
+		// Create the buffer handle
+		VkBufferCreateInfo bufferCreateInfo = vks::initializers::bufferCreateInfo(usageFlags, size);
+		bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+		VK_CHECK_RESULT(vkCreateBuffer(logicalDevice, &bufferCreateInfo, nullptr, buffer));
+
+		// Create the memory backing up the buffer handle
+		VkMemoryRequirements memReqs;
+		VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo();
+		vkGetBufferMemoryRequirements(logicalDevice, *buffer, &memReqs);
+		memAlloc.allocationSize = memReqs.size;
+		// Find a memory type index that fits the properties of the buffer
+		memAlloc.memoryTypeIndex = getMemoryType(memReqs.memoryTypeBits, memoryPropertyFlags);
+		// If the buffer has VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT set we also need to enable the appropriate flag during allocation
+		VkMemoryAllocateFlagsInfoKHR allocFlagsInfo{};
+		if (usageFlags & VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT) {
+			allocFlagsInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO_KHR;
+			allocFlagsInfo.flags = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT_KHR;
+			memAlloc.pNext = &allocFlagsInfo;
+		}
+		VK_CHECK_RESULT(vkAllocateMemory(logicalDevice, &memAlloc, nullptr, memory));
+			
+		// If a pointer to the buffer data has been passed, map the buffer and copy over the data
+		if (data != nullptr)
+		{
+			void *mapped;
+			VK_CHECK_RESULT(vkMapMemory(logicalDevice, *memory, 0, size, 0, &mapped));
+			memcpy(mapped, data, size);
+			// If host coherency hasn't been requested, do a manual flush to make writes visible
+			if ((memoryPropertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) == 0)
+			{
+				VkMappedMemoryRange mappedRange = vks::initializers::mappedMemoryRange();
+				mappedRange.memory = *memory;
+				mappedRange.offset = 0;
+				mappedRange.size = size;
+				vkFlushMappedMemoryRanges(logicalDevice, 1, &mappedRange);
+			}
+			vkUnmapMemory(logicalDevice, *memory);
+		}
+
+		// Attach the memory to the buffer object
+		VK_CHECK_RESULT(vkBindBufferMemory(logicalDevice, *buffer, *memory, 0));
+
+		return VK_SUCCESS;
+	}
+
+	/**
+	* Create a buffer on the device
+	*
+	* @param usageFlags Usage flag bit mask for the buffer (i.e. index, vertex, uniform buffer)
+	* @param memoryPropertyFlags Memory properties for this buffer (i.e. device local, host visible, coherent)
+	* @param buffer Pointer to a vk::Vulkan buffer object
+	* @param size Size of the buffer in bytes
+	* @param data Pointer to the data that should be copied to the buffer after creation (optional, if not set, no data is copied over)
+	*
+	* @return VK_SUCCESS if buffer handle and memory have been created and (optionally passed) data has been copied
+	*/
+	VkResult VulkanDevice::createBuffer(VkBufferUsageFlags usageFlags, VkMemoryPropertyFlags memoryPropertyFlags, vks::Buffer *buffer, VkDeviceSize size, void *data)
+	{
+		buffer->device = logicalDevice;
+
+		// Create the buffer handle
+		VkBufferCreateInfo bufferCreateInfo = vks::initializers::bufferCreateInfo(usageFlags, size);
+		VK_CHECK_RESULT(vkCreateBuffer(logicalDevice, &bufferCreateInfo, nullptr, &buffer->buffer));
+
+		// Create the memory backing up the buffer handle
+		VkMemoryRequirements memReqs;
+		VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo();
+		vkGetBufferMemoryRequirements(logicalDevice, buffer->buffer, &memReqs);
+		memAlloc.allocationSize = memReqs.size;
+		// Find a memory type index that fits the properties of the buffer
+		memAlloc.memoryTypeIndex = getMemoryType(memReqs.memoryTypeBits, memoryPropertyFlags);
+		// If the buffer has VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT set we also need to enable the appropriate flag during allocation
+		VkMemoryAllocateFlagsInfoKHR allocFlagsInfo{};
+		if (usageFlags & VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT) {
+			allocFlagsInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO_KHR;
+			allocFlagsInfo.flags = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT_KHR;
+			memAlloc.pNext = &allocFlagsInfo;
+		}
+		VK_CHECK_RESULT(vkAllocateMemory(logicalDevice, &memAlloc, nullptr, &buffer->memory));
+
+		buffer->alignment = memReqs.alignment;
+		buffer->size = size;
+		buffer->usageFlags = usageFlags;
+		buffer->memoryPropertyFlags = memoryPropertyFlags;
+
+		// If a pointer to the buffer data has been passed, map the buffer and copy over the data
+		if (data != nullptr)
+		{
+			VK_CHECK_RESULT(buffer->map());
+			memcpy(buffer->mapped, data, size);
+			if ((memoryPropertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) == 0)
+				buffer->flush();
+
+			buffer->unmap();
+		}
+
+		// Initialize a default descriptor that covers the whole buffer size
+		buffer->setupDescriptor();
+
+		// Attach the memory to the buffer object
+		return buffer->bind();
+	}
+
+	/**
+	* Copy buffer data from src to dst using VkCmdCopyBuffer
+	* 
+	* @param src Pointer to the source buffer to copy from
+	* @param dst Pointer to the destination buffer to copy to
+	* @param queue Pointer
+	* @param copyRegion (Optional) Pointer to a copy region, if NULL, the whole buffer is copied
+	*
+	* @note Source and destination pointers must have the appropriate transfer usage flags set (TRANSFER_SRC / TRANSFER_DST)
+	*/
+	void VulkanDevice::copyBuffer(vks::Buffer *src, vks::Buffer *dst, VkQueue queue, VkBufferCopy *copyRegion)
+	{
+		assert(dst->size <= src->size);
+		assert(src->buffer);
+		VkCommandBuffer copyCmd = createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+		VkBufferCopy bufferCopy{};
+		if (copyRegion == nullptr)
+		{
+			bufferCopy.size = src->size;
+		}
+		else
+		{
+			bufferCopy = *copyRegion;
+		}
+
+		vkCmdCopyBuffer(copyCmd, src->buffer, dst->buffer, 1, &bufferCopy);
+
+		flushCommandBuffer(copyCmd, queue);
+	}
+
+	/** 
+	* Create a command pool for allocation command buffers from
+	* 
+	* @param queueFamilyIndex Family index of the queue to create the command pool for
+	* @param createFlags (Optional) Command pool creation flags (Defaults to VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT)
+	*
+	* @note Command buffers allocated from the created pool can only be submitted to a queue with the same family index
+	*
+	* @return A handle to the created command buffer
+	*/
+	VkCommandPool VulkanDevice::createCommandPool(uint32_t queueFamilyIndex, VkCommandPoolCreateFlags createFlags)
+	{
+		VkCommandPoolCreateInfo cmdPoolInfo = {};
+		cmdPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
+		cmdPoolInfo.queueFamilyIndex = queueFamilyIndex;
+		cmdPoolInfo.flags = createFlags;
+		VkCommandPool cmdPool;
+		VK_CHECK_RESULT(vkCreateCommandPool(logicalDevice, &cmdPoolInfo, nullptr, &cmdPool));
+		return cmdPool;
+	}
+
+	/**
+	* Allocate a command buffer from the command pool
+	*
+	* @param level Level of the new command buffer (primary or secondary)
+	* @param pool Command pool from which the command buffer will be allocated
+	* @param (Optional) begin If true, recording on the new command buffer will be started (vkBeginCommandBuffer) (Defaults to false)
+	*
+	* @return A handle to the allocated command buffer
+	*/
+	VkCommandBuffer VulkanDevice::createCommandBuffer(VkCommandBufferLevel level, VkCommandPool pool, bool begin)
+	{
+		VkCommandBufferAllocateInfo cmdBufAllocateInfo = vks::initializers::commandBufferAllocateInfo(pool, level, 1);
+		VkCommandBuffer cmdBuffer;
+		VK_CHECK_RESULT(vkAllocateCommandBuffers(logicalDevice, &cmdBufAllocateInfo, &cmdBuffer));
+		// If requested, also start recording for the new command buffer
+		if (begin)
+		{
+			VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+			VK_CHECK_RESULT(vkBeginCommandBuffer(cmdBuffer, &cmdBufInfo));
+		}
+		return cmdBuffer;
+	}
+			
+	VkCommandBuffer VulkanDevice::createCommandBuffer(VkCommandBufferLevel level, bool begin)
+	{
+		return createCommandBuffer(level, commandPool, begin);
+	}
+
+	/**
+	* Finish command buffer recording and submit it to a queue
+	*
+	* @param commandBuffer Command buffer to flush
+	* @param queue Queue to submit the command buffer to
+	* @param pool Command pool on which the command buffer has been created
+	* @param free (Optional) Free the command buffer once it has been submitted (Defaults to true)
+	*
+	* @note The queue that the command buffer is submitted to must be from the same family index as the pool it was allocated from
+	* @note Uses a fence to ensure command buffer has finished executing
+	*/
+	void VulkanDevice::flushCommandBuffer(VkCommandBuffer commandBuffer, VkQueue queue, VkCommandPool pool, bool free)
+	{
+		if (commandBuffer == VK_NULL_HANDLE)
+		{
+			return;
+		}
+
+		VK_CHECK_RESULT(vkEndCommandBuffer(commandBuffer));
+
+		VkSubmitInfo submitInfo = vks::initializers::submitInfo();
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &commandBuffer;
+		// Create fence to ensure that the command buffer has finished executing
+		VkFenceCreateInfo fenceInfo = vks::initializers::fenceCreateInfo(VK_FLAGS_NONE);
+		VkFence fence;
+		VK_CHECK_RESULT(vkCreateFence(logicalDevice, &fenceInfo, nullptr, &fence));
+		// Submit to the queue
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, fence));
+		// Wait for the fence to signal that command buffer has finished executing
+		VK_CHECK_RESULT(vkWaitForFences(logicalDevice, 1, &fence, VK_TRUE, DEFAULT_FENCE_TIMEOUT));
+		vkDestroyFence(logicalDevice, fence, nullptr);
+		if (free)
+		{
+			vkFreeCommandBuffers(logicalDevice, pool, 1, &commandBuffer);
+		}
+	}
+
+	void VulkanDevice::flushCommandBuffer(VkCommandBuffer commandBuffer, VkQueue queue, bool free)
+	{
+		return flushCommandBuffer(commandBuffer, queue, commandPool, free);
+	}
+
+	/**
+	* Check if an extension is supported by the (physical device)
+	*
+	* @param extension Name of the extension to check
+	*
+	* @return True if the extension is supported (present in the list read at device creation time)
+	*/
+	bool VulkanDevice::extensionSupported(std::string extension)
+	{
+		return (std::find(supportedExtensions.begin(), supportedExtensions.end(), extension) != supportedExtensions.end());
+	}
+
+	/**
+	* Select the best-fit depth format for this device from a list of possible depth (and stencil) formats
+	*
+	* @param checkSamplingSupport Check if the format can be sampled from (e.g. for shader reads)
+	*
+	* @return The depth format that best fits for the current device
+	*
+	* @throw Throws an exception if no depth format fits the requirements
+	*/
+	VkFormat VulkanDevice::getSupportedDepthFormat(bool checkSamplingSupport)
+	{
+		// All depth formats may be optional, so we need to find a suitable depth format to use
+		std::vector<VkFormat> depthFormats = { VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D32_SFLOAT, VK_FORMAT_D24_UNORM_S8_UINT, VK_FORMAT_D16_UNORM_S8_UINT, VK_FORMAT_D16_UNORM };
+		for (auto& format : depthFormats)
+		{
+			VkFormatProperties formatProperties;
+			vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &formatProperties);
+			// Format must support depth stencil attachment for optimal tiling
+			if (formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT)
+			{
+				if (checkSamplingSupport) {
+					if (!(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT)) {
+						continue;
+					}
+				}
+				return format;
+			}
+		}
+		throw std::runtime_error("Could not find a matching depth format");
+	}
+
+};
diff --git a/external/Vulkan/base/VulkanDevice.h b/external/Vulkan/base/VulkanDevice.h
new file mode 100644
index 0000000000000000000000000000000000000000..0bb49879eca7b7d826133a32eb2f00699e2dd96b
--- /dev/null
+++ b/external/Vulkan/base/VulkanDevice.h
@@ -0,0 +1,71 @@
+/*
+* Vulkan device class
+*
+* Encapsulates a physical Vulkan device and its logical representation
+*
+* Copyright (C) by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#pragma once
+
+#include "VulkanBuffer.h"
+#include "VulkanTools.h"
+#include "vulkan/vulkan.h"
+#include <algorithm>
+#include <assert.h>
+#include <exception>
+
+namespace vks
+{
+struct VulkanDevice
+{
+	/** @brief Physical device representation */
+	VkPhysicalDevice physicalDevice;
+	/** @brief Logical device representation (application's view of the device) */
+	VkDevice logicalDevice;
+	/** @brief Properties of the physical device including limits that the application can check against */
+	VkPhysicalDeviceProperties properties;
+	/** @brief Features of the physical device that an application can use to check if a feature is supported */
+	VkPhysicalDeviceFeatures features;
+	/** @brief Features that have been enabled for use on the physical device */
+	VkPhysicalDeviceFeatures enabledFeatures;
+	/** @brief Memory types and heaps of the physical device */
+	VkPhysicalDeviceMemoryProperties memoryProperties;
+	/** @brief Queue family properties of the physical device */
+	std::vector<VkQueueFamilyProperties> queueFamilyProperties;
+	/** @brief List of extensions supported by the device */
+	std::vector<std::string> supportedExtensions;
+	/** @brief Default command pool for the graphics queue family index */
+	VkCommandPool commandPool = VK_NULL_HANDLE;
+	/** @brief Set to true when the debug marker extension is detected */
+	bool enableDebugMarkers = false;
+	/** @brief Contains queue family indices */
+	struct
+	{
+		uint32_t graphics;
+		uint32_t compute;
+		uint32_t transfer;
+	} queueFamilyIndices;
+	operator VkDevice() const
+	{
+		return logicalDevice;
+	};
+	explicit VulkanDevice(VkPhysicalDevice physicalDevice);
+	~VulkanDevice();
+	uint32_t        getMemoryType(uint32_t typeBits, VkMemoryPropertyFlags properties, VkBool32 *memTypeFound = nullptr) const;
+	uint32_t        getQueueFamilyIndex(VkQueueFlagBits queueFlags) const;
+	VkResult        createLogicalDevice(VkPhysicalDeviceFeatures enabledFeatures, std::vector<const char *> enabledExtensions, void *pNextChain, bool useSwapChain = true, VkQueueFlags requestedQueueTypes = VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT);
+	VkResult        createBuffer(VkBufferUsageFlags usageFlags, VkMemoryPropertyFlags memoryPropertyFlags, VkDeviceSize size, VkBuffer *buffer, VkDeviceMemory *memory, void *data = nullptr);
+	VkResult        createBuffer(VkBufferUsageFlags usageFlags, VkMemoryPropertyFlags memoryPropertyFlags, vks::Buffer *buffer, VkDeviceSize size, void *data = nullptr);
+	void            copyBuffer(vks::Buffer *src, vks::Buffer *dst, VkQueue queue, VkBufferCopy *copyRegion = nullptr);
+	VkCommandPool   createCommandPool(uint32_t queueFamilyIndex, VkCommandPoolCreateFlags createFlags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT);
+	VkCommandBuffer createCommandBuffer(VkCommandBufferLevel level, VkCommandPool pool, bool begin = false);
+	VkCommandBuffer createCommandBuffer(VkCommandBufferLevel level, bool begin = false);
+	void            flushCommandBuffer(VkCommandBuffer commandBuffer, VkQueue queue, VkCommandPool pool, bool free = true);
+	void            flushCommandBuffer(VkCommandBuffer commandBuffer, VkQueue queue, bool free = true);
+	bool            extensionSupported(std::string extension);
+	VkFormat        getSupportedDepthFormat(bool checkSamplingSupport);
+};
+}        // namespace vks
diff --git a/external/Vulkan/base/VulkanFrameBuffer.hpp b/external/Vulkan/base/VulkanFrameBuffer.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..4180a67c22f6d76423692f7e7fc23f5b72d7efbe
--- /dev/null
+++ b/external/Vulkan/base/VulkanFrameBuffer.hpp
@@ -0,0 +1,366 @@
+/*
+* Vulkan framebuffer class
+*
+* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#pragma once
+
+#include <algorithm>
+#include <iterator>
+#include <vector>
+#include "vulkan/vulkan.h"
+#include "VulkanDevice.h"
+#include "VulkanTools.h"
+
+namespace vks
+{
+	/**
+	* @brief Encapsulates a single frame buffer attachment 
+	*/
+	struct FramebufferAttachment
+	{
+		VkImage image;
+		VkDeviceMemory memory;
+		VkImageView view;
+		VkFormat format;
+		VkImageSubresourceRange subresourceRange;
+		VkAttachmentDescription description;
+
+		/**
+		* @brief Returns true if the attachment has a depth component
+		*/
+		bool hasDepth()
+		{
+			std::vector<VkFormat> formats = 
+			{
+				VK_FORMAT_D16_UNORM,
+				VK_FORMAT_X8_D24_UNORM_PACK32,
+				VK_FORMAT_D32_SFLOAT,
+				VK_FORMAT_D16_UNORM_S8_UINT,
+				VK_FORMAT_D24_UNORM_S8_UINT,
+				VK_FORMAT_D32_SFLOAT_S8_UINT,
+			};
+			return std::find(formats.begin(), formats.end(), format) != std::end(formats);
+		}
+
+		/**
+		* @brief Returns true if the attachment has a stencil component
+		*/
+		bool hasStencil()
+		{
+			std::vector<VkFormat> formats = 
+			{
+				VK_FORMAT_S8_UINT,
+				VK_FORMAT_D16_UNORM_S8_UINT,
+				VK_FORMAT_D24_UNORM_S8_UINT,
+				VK_FORMAT_D32_SFLOAT_S8_UINT,
+			};
+			return std::find(formats.begin(), formats.end(), format) != std::end(formats);
+		}
+
+		/**
+		* @brief Returns true if the attachment is a depth and/or stencil attachment
+		*/
+		bool isDepthStencil()
+		{
+			return(hasDepth() || hasStencil());
+		}
+
+	};
+
+	/**
+	* @brief Describes the attributes of an attachment to be created
+	*/
+	struct AttachmentCreateInfo
+	{
+		uint32_t width, height;
+		uint32_t layerCount;
+		VkFormat format;
+		VkImageUsageFlags usage;
+		VkSampleCountFlagBits imageSampleCount = VK_SAMPLE_COUNT_1_BIT;
+	};
+
+	/**
+	* @brief Encapsulates a complete Vulkan framebuffer with an arbitrary number and combination of attachments
+	*/
+	struct Framebuffer
+	{
+	private:
+		vks::VulkanDevice *vulkanDevice;
+	public:
+		uint32_t width, height;
+		VkFramebuffer framebuffer;
+		VkRenderPass renderPass;
+		VkSampler sampler;
+		std::vector<vks::FramebufferAttachment> attachments;
+
+		/**
+		* Default constructor
+		*
+		* @param vulkanDevice Pointer to a valid VulkanDevice
+		*/
+		Framebuffer(vks::VulkanDevice *vulkanDevice)
+		{
+			assert(vulkanDevice);
+			this->vulkanDevice = vulkanDevice;
+		}
+
+		/**
+		* Destroy and free Vulkan resources used for the framebuffer and all of its attachments
+		*/
+		~Framebuffer()
+		{
+			assert(vulkanDevice);
+			for (auto attachment : attachments)
+			{
+				vkDestroyImage(vulkanDevice->logicalDevice, attachment.image, nullptr);
+				vkDestroyImageView(vulkanDevice->logicalDevice, attachment.view, nullptr);
+				vkFreeMemory(vulkanDevice->logicalDevice, attachment.memory, nullptr);
+			}
+			vkDestroySampler(vulkanDevice->logicalDevice, sampler, nullptr);
+			vkDestroyRenderPass(vulkanDevice->logicalDevice, renderPass, nullptr);
+			vkDestroyFramebuffer(vulkanDevice->logicalDevice, framebuffer, nullptr);
+		}
+
+		/**
+		* Add a new attachment described by createinfo to the framebuffer's attachment list
+		*
+		* @param createinfo Structure that specifies the framebuffer to be constructed
+		*
+		* @return Index of the new attachment
+		*/
+		uint32_t addAttachment(vks::AttachmentCreateInfo createinfo)
+		{
+			vks::FramebufferAttachment attachment;
+
+			attachment.format = createinfo.format;
+
+			VkImageAspectFlags aspectMask = VK_FLAGS_NONE;
+
+			// Select aspect mask and layout depending on usage
+
+			// Color attachment
+			if (createinfo.usage & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT)
+			{
+				aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+			}
+
+			// Depth (and/or stencil) attachment
+			if (createinfo.usage & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT)
+			{
+				if (attachment.hasDepth())
+				{
+					aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
+				}
+				if (attachment.hasStencil())
+				{
+					aspectMask = aspectMask | VK_IMAGE_ASPECT_STENCIL_BIT;
+				}
+			}
+
+			assert(aspectMask > 0);
+
+			VkImageCreateInfo image = vks::initializers::imageCreateInfo();
+			image.imageType = VK_IMAGE_TYPE_2D;
+			image.format = createinfo.format;
+			image.extent.width = createinfo.width;
+			image.extent.height = createinfo.height;
+			image.extent.depth = 1;
+			image.mipLevels = 1;
+			image.arrayLayers = createinfo.layerCount;
+			image.samples = createinfo.imageSampleCount;
+			image.tiling = VK_IMAGE_TILING_OPTIMAL;
+			image.usage = createinfo.usage;
+
+			VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo();
+			VkMemoryRequirements memReqs;
+
+			// Create image for this attachment
+			VK_CHECK_RESULT(vkCreateImage(vulkanDevice->logicalDevice, &image, nullptr, &attachment.image));
+			vkGetImageMemoryRequirements(vulkanDevice->logicalDevice, attachment.image, &memReqs);
+			memAlloc.allocationSize = memReqs.size;
+			memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+			VK_CHECK_RESULT(vkAllocateMemory(vulkanDevice->logicalDevice, &memAlloc, nullptr, &attachment.memory));
+			VK_CHECK_RESULT(vkBindImageMemory(vulkanDevice->logicalDevice, attachment.image, attachment.memory, 0));
+
+			attachment.subresourceRange = {};
+			attachment.subresourceRange.aspectMask = aspectMask;
+			attachment.subresourceRange.levelCount = 1;
+			attachment.subresourceRange.layerCount = createinfo.layerCount;
+
+			VkImageViewCreateInfo imageView = vks::initializers::imageViewCreateInfo();
+			imageView.viewType = (createinfo.layerCount == 1) ? VK_IMAGE_VIEW_TYPE_2D : VK_IMAGE_VIEW_TYPE_2D_ARRAY;
+			imageView.format = createinfo.format;
+			imageView.subresourceRange = attachment.subresourceRange;
+			//todo: workaround for depth+stencil attachments
+			imageView.subresourceRange.aspectMask = (attachment.hasDepth()) ? VK_IMAGE_ASPECT_DEPTH_BIT : aspectMask;
+			imageView.image = attachment.image;
+			VK_CHECK_RESULT(vkCreateImageView(vulkanDevice->logicalDevice, &imageView, nullptr, &attachment.view));
+
+			// Fill attachment description
+			attachment.description = {};
+			attachment.description.samples = createinfo.imageSampleCount;
+			attachment.description.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+			attachment.description.storeOp = (createinfo.usage & VK_IMAGE_USAGE_SAMPLED_BIT) ? VK_ATTACHMENT_STORE_OP_STORE : VK_ATTACHMENT_STORE_OP_DONT_CARE;
+			attachment.description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+			attachment.description.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+			attachment.description.format = createinfo.format;
+			attachment.description.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+			// Final layout
+			// If not, final layout depends on attachment type
+			if (attachment.hasDepth() || attachment.hasStencil())
+			{
+				attachment.description.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL;
+			}
+			else
+			{
+				attachment.description.finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+			}
+
+			attachments.push_back(attachment);
+
+			return static_cast<uint32_t>(attachments.size() - 1);
+		}
+
+		/**
+		* Creates a default sampler for sampling from any of the framebuffer attachments
+		* Applications are free to create their own samplers for different use cases 
+		*
+		* @param magFilter Magnification filter for lookups
+		* @param minFilter Minification filter for lookups
+		* @param adressMode Addressing mode for the U,V and W coordinates
+		*
+		* @return VkResult for the sampler creation
+		*/
+		VkResult createSampler(VkFilter magFilter, VkFilter minFilter, VkSamplerAddressMode adressMode)
+		{
+			VkSamplerCreateInfo samplerInfo = vks::initializers::samplerCreateInfo();
+			samplerInfo.magFilter = magFilter;
+			samplerInfo.minFilter = minFilter;
+			samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+			samplerInfo.addressModeU = adressMode;
+			samplerInfo.addressModeV = adressMode;
+			samplerInfo.addressModeW = adressMode;
+			samplerInfo.mipLodBias = 0.0f;
+			samplerInfo.maxAnisotropy = 1.0f;
+			samplerInfo.minLod = 0.0f;
+			samplerInfo.maxLod = 1.0f;
+			samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
+			return vkCreateSampler(vulkanDevice->logicalDevice, &samplerInfo, nullptr, &sampler);
+		}
+
+		/**
+		* Creates a default render pass setup with one sub pass
+		*
+		* @return VK_SUCCESS if all resources have been created successfully
+		*/
+		VkResult createRenderPass()
+		{
+			std::vector<VkAttachmentDescription> attachmentDescriptions;
+			for (auto& attachment : attachments)
+			{
+				attachmentDescriptions.push_back(attachment.description);
+			};
+
+			// Collect attachment references
+			std::vector<VkAttachmentReference> colorReferences;
+			VkAttachmentReference depthReference = {};
+			bool hasDepth = false; 
+			bool hasColor = false;
+
+			uint32_t attachmentIndex = 0;
+
+			for (auto& attachment : attachments)
+			{
+				if (attachment.isDepthStencil())
+				{
+					// Only one depth attachment allowed
+					assert(!hasDepth);
+					depthReference.attachment = attachmentIndex;
+					depthReference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+					hasDepth = true;
+				}
+				else
+				{
+					colorReferences.push_back({ attachmentIndex, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL });
+					hasColor = true;
+				}
+				attachmentIndex++;
+			};
+
+			// Default render pass setup uses only one subpass
+			VkSubpassDescription subpass = {};
+			subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+			if (hasColor)
+			{
+				subpass.pColorAttachments = colorReferences.data();
+				subpass.colorAttachmentCount = static_cast<uint32_t>(colorReferences.size());
+			}
+			if (hasDepth)
+			{
+				subpass.pDepthStencilAttachment = &depthReference;
+			}
+
+			// Use subpass dependencies for attachment layout transitions
+			std::array<VkSubpassDependency, 2> dependencies;
+
+			dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
+			dependencies[0].dstSubpass = 0;
+			dependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+			dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+			dependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
+			dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+			dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+			dependencies[1].srcSubpass = 0;
+			dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
+			dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+			dependencies[1].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+			dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+			dependencies[1].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
+			dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+			// Create render pass
+			VkRenderPassCreateInfo renderPassInfo = {};
+			renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
+			renderPassInfo.pAttachments = attachmentDescriptions.data();
+			renderPassInfo.attachmentCount = static_cast<uint32_t>(attachmentDescriptions.size());
+			renderPassInfo.subpassCount = 1;
+			renderPassInfo.pSubpasses = &subpass;
+			renderPassInfo.dependencyCount = 2;
+			renderPassInfo.pDependencies = dependencies.data();
+			VK_CHECK_RESULT(vkCreateRenderPass(vulkanDevice->logicalDevice, &renderPassInfo, nullptr, &renderPass));
+
+			std::vector<VkImageView> attachmentViews;
+			for (auto attachment : attachments)
+			{
+				attachmentViews.push_back(attachment.view);
+			}
+
+			// Find. max number of layers across attachments
+			uint32_t maxLayers = 0;
+			for (auto attachment : attachments)
+			{
+				if (attachment.subresourceRange.layerCount > maxLayers)
+				{
+					maxLayers = attachment.subresourceRange.layerCount;
+				}
+			}
+
+			VkFramebufferCreateInfo framebufferInfo = {};
+			framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
+			framebufferInfo.renderPass = renderPass;
+			framebufferInfo.pAttachments = attachmentViews.data();
+			framebufferInfo.attachmentCount = static_cast<uint32_t>(attachmentViews.size());
+			framebufferInfo.width = width;
+			framebufferInfo.height = height;
+			framebufferInfo.layers = maxLayers;
+			VK_CHECK_RESULT(vkCreateFramebuffer(vulkanDevice->logicalDevice, &framebufferInfo, nullptr, &framebuffer));
+
+			return VK_SUCCESS;
+		}
+	};
+}
\ No newline at end of file
diff --git a/external/Vulkan/base/VulkanHeightmap.hpp b/external/Vulkan/base/VulkanHeightmap.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..08f66e3b227203ee75277b2a35ff86e57e856968
--- /dev/null
+++ b/external/Vulkan/base/VulkanHeightmap.hpp
@@ -0,0 +1,259 @@
+/*
+* Heightmap terrain generator
+*
+* Copyright (C) by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include <glm/glm.hpp>
+#include <glm/glm.hpp>
+
+#include "vulkan/vulkan.h"
+#include "VulkanDevice.h"
+#include "VulkanBuffer.h"
+#include <ktx.h>
+#include <ktxvulkan.h>
+
+namespace vks 
+{
+	class HeightMap
+	{
+	private:
+		uint16_t *heightdata;
+		uint32_t dim;
+		uint32_t scale;
+
+		vks::VulkanDevice *device = nullptr;
+		VkQueue copyQueue = VK_NULL_HANDLE;
+	public:
+		enum Topology { topologyTriangles, topologyQuads };
+
+		float heightScale = 1.0f;
+		float uvScale = 1.0f;
+
+		vks::Buffer vertexBuffer;
+		vks::Buffer indexBuffer;
+
+		struct Vertex {
+			glm::vec3 pos;
+			glm::vec3 normal;
+			glm::vec2 uv;
+		};
+
+		size_t vertexBufferSize = 0;
+		size_t indexBufferSize = 0;
+		uint32_t indexCount = 0;
+
+		HeightMap(vks::VulkanDevice *device, VkQueue copyQueue)
+		{
+			this->device = device;
+			this->copyQueue = copyQueue;
+		};
+
+		~HeightMap()
+		{
+			vertexBuffer.destroy();
+			indexBuffer.destroy();
+			delete[] heightdata;
+		}
+
+		float getHeight(uint32_t x, uint32_t y)
+		{
+			glm::ivec2 rpos = glm::ivec2(x, y) * glm::ivec2(scale);
+			rpos.x = std::max(0, std::min(rpos.x, (int)dim - 1));
+			rpos.y = std::max(0, std::min(rpos.y, (int)dim - 1));
+			rpos /= glm::ivec2(scale);
+			return *(heightdata + (rpos.x + rpos.y * dim) * scale) / 65535.0f * heightScale;
+		}
+
+#if defined(__ANDROID__)
+		void loadFromFile(const std::string filename, uint32_t patchsize, glm::vec3 scale, Topology topology, AAssetManager* assetManager)
+#else
+		void loadFromFile(const std::string filename, uint32_t patchsize, glm::vec3 scale, Topology topology)
+#endif
+		{
+			assert(device);
+			assert(copyQueue != VK_NULL_HANDLE);
+
+			ktxResult result;
+			ktxTexture* ktxTexture;
+#if defined(__ANDROID__)
+			AAsset* asset = AAssetManager_open(androidApp->activity->assetManager, filename.c_str(), AASSET_MODE_STREAMING);
+			assert(asset);
+			size_t size = AAsset_getLength(asset);
+			assert(size > 0);
+			void *textureData = malloc(size);
+			AAsset_read(asset, textureData, size);
+			AAsset_close(asset);
+			result = ktxTexture_CreateFromMemory(textureData, size, KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, target);
+			free(textureData);
+#else
+			result = ktxTexture_CreateFromNamedFile(filename.c_str(), KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, &ktxTexture);
+#endif
+			assert(result == KTX_SUCCESS);
+			ktx_size_t ktxSize = ktxTexture_GetImageSize(ktxTexture, 0);
+			ktx_uint8_t* ktxImage = ktxTexture_GetData(ktxTexture);
+			dim = ktxTexture->baseWidth;
+			heightdata = new uint16_t[dim * dim];
+			memcpy(heightdata, ktxImage, ktxSize);
+			this->scale = dim / patchsize;
+			ktxTexture_Destroy(ktxTexture);
+
+			// Generate vertices
+			Vertex * vertices = new Vertex[patchsize * patchsize * 4];
+
+			const float wx = 2.0f;
+			const float wy = 2.0f;
+
+			for (uint32_t x = 0; x < patchsize; x++)
+			{
+				for (uint32_t y = 0; y < patchsize; y++)
+				{
+					uint32_t index = (x + y * patchsize);
+					vertices[index].pos[0] = (x * wx + wx / 2.0f - (float)patchsize * wx / 2.0f) * scale.x;
+					vertices[index].pos[1] = -getHeight(x, y);
+					vertices[index].pos[2] = (y * wy + wy / 2.0f - (float)patchsize * wy / 2.0f) * scale.z;
+					vertices[index].uv = glm::vec2((float)x / patchsize, (float)y / patchsize) * uvScale;
+				}
+			}
+
+			for (uint32_t y = 0; y < patchsize; y++)
+			{
+				for (uint32_t x = 0; x < patchsize; x++)
+				{
+					float dx = getHeight(x < patchsize - 1 ? x + 1 : x, y) - getHeight(x > 0 ? x - 1 : x, y);
+					if (x == 0 || x == patchsize - 1)
+						dx *= 2.0f;
+
+					float dy = getHeight(x, y < patchsize - 1 ? y + 1 : y) - getHeight(x, y > 0 ? y - 1 : y);
+					if (y == 0 || y == patchsize - 1)
+						dy *= 2.0f;
+
+					glm::vec3 A = glm::vec3(1.0f, 0.0f, dx);
+					glm::vec3 B = glm::vec3(0.0f, 1.0f, dy);
+
+					glm::vec3 normal = (glm::normalize(glm::cross(A, B)) + 1.0f) * 0.5f;
+
+					vertices[x + y * patchsize].normal = glm::vec3(normal.x, normal.z, normal.y);
+				}
+			}
+
+			// Generate indices
+
+			const uint32_t w = (patchsize - 1);
+			uint32_t *indices;
+
+			switch (topology)
+			{
+			// Indices for triangles
+			case topologyTriangles:
+			{
+				indices = new uint32_t[w * w * 6];
+				for (uint32_t x = 0; x < w; x++)
+				{
+					for (uint32_t y = 0; y < w; y++)
+					{
+						uint32_t index = (x + y * w) * 6;
+						indices[index] = (x + y * patchsize);
+						indices[index + 1] = indices[index] + patchsize;
+						indices[index + 2] = indices[index + 1] + 1;
+
+						indices[index + 3] = indices[index + 1] + 1;
+						indices[index + 4] = indices[index] + 1;
+						indices[index + 5] = indices[index];
+					}
+				}
+				indexCount = (patchsize - 1) * (patchsize - 1) * 6;
+				indexBufferSize = (w * w * 6) * sizeof(uint32_t);
+				break;
+			}
+			// Indices for quad patches (tessellation)
+			case topologyQuads:
+			{
+
+				indices = new uint32_t[w * w * 4];
+				for (uint32_t x = 0; x < w; x++)
+				{
+					for (uint32_t y = 0; y < w; y++)
+					{
+						uint32_t index = (x + y * w) * 4;
+						indices[index] = (x + y * patchsize);
+						indices[index + 1] = indices[index] + patchsize;
+						indices[index + 2] = indices[index + 1] + 1;
+						indices[index + 3] = indices[index] + 1;
+					}
+				}
+				indexCount = (patchsize - 1) * (patchsize - 1) * 4;
+				indexBufferSize = (w * w * 4) * sizeof(uint32_t);
+				break;
+			}
+
+			}
+
+			assert(indexBufferSize > 0);
+
+			vertexBufferSize = (patchsize * patchsize * 4) * sizeof(Vertex);
+
+			// Generate Vulkan buffers
+
+			vks::Buffer vertexStaging, indexStaging;
+
+			// Create staging buffers
+			device->createBuffer(
+				VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
+				VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+				&vertexStaging,
+				vertexBufferSize,
+				vertices);
+
+			device->createBuffer(
+				VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
+				VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+				&indexStaging,
+				indexBufferSize,
+				indices);
+
+			// Device local (target) buffer
+			device->createBuffer(
+				VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
+				VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
+				&vertexBuffer,
+				vertexBufferSize);
+
+			device->createBuffer(
+				VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
+				VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
+				&indexBuffer,
+				indexBufferSize);
+
+			// Copy from staging buffers
+			VkCommandBuffer copyCmd = device->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+
+			VkBufferCopy copyRegion = {};
+
+			copyRegion.size = vertexBufferSize;
+			vkCmdCopyBuffer(
+				copyCmd,
+				vertexStaging.buffer,
+				vertexBuffer.buffer,
+				1,
+				&copyRegion);
+
+			copyRegion.size = indexBufferSize;
+			vkCmdCopyBuffer(
+				copyCmd,
+				indexStaging.buffer,
+				indexBuffer.buffer,
+				1,
+				&copyRegion);
+
+			device->flushCommandBuffer(copyCmd, copyQueue, true);
+
+			vkDestroyBuffer(device->logicalDevice, vertexStaging.buffer, nullptr);
+			vkFreeMemory(device->logicalDevice, vertexStaging.memory, nullptr);
+			vkDestroyBuffer(device->logicalDevice, indexStaging.buffer, nullptr);
+			vkFreeMemory(device->logicalDevice, indexStaging.memory, nullptr);
+		}
+	};
+}
diff --git a/external/Vulkan/base/VulkanInitializers.hpp b/external/Vulkan/base/VulkanInitializers.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..795b5f9ac31adff35f5f4e760c6faf8e672398bc
--- /dev/null
+++ b/external/Vulkan/base/VulkanInitializers.hpp
@@ -0,0 +1,659 @@
+/*
+* Initializers for Vulkan structures and objects used by the examples
+* Saves lot of VK_STRUCTURE_TYPE assignments
+* Some initializers are parameterized for convenience
+*
+* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#pragma once
+
+#include <vector>
+#include "vulkan/vulkan.h"
+
+namespace vks
+{
+	namespace initializers
+	{
+
+		inline VkMemoryAllocateInfo memoryAllocateInfo()
+		{
+			VkMemoryAllocateInfo memAllocInfo {};
+			memAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+			return memAllocInfo;
+		}
+
+		inline VkMappedMemoryRange mappedMemoryRange()
+		{
+			VkMappedMemoryRange mappedMemoryRange {};
+			mappedMemoryRange.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
+			return mappedMemoryRange;
+		}
+
+		inline VkCommandBufferAllocateInfo commandBufferAllocateInfo(
+			VkCommandPool commandPool, 
+			VkCommandBufferLevel level, 
+			uint32_t bufferCount)
+		{
+			VkCommandBufferAllocateInfo commandBufferAllocateInfo {};
+			commandBufferAllocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
+			commandBufferAllocateInfo.commandPool = commandPool;
+			commandBufferAllocateInfo.level = level;
+			commandBufferAllocateInfo.commandBufferCount = bufferCount;
+			return commandBufferAllocateInfo;
+		}
+
+		inline VkCommandPoolCreateInfo commandPoolCreateInfo()
+		{
+			VkCommandPoolCreateInfo cmdPoolCreateInfo {};
+			cmdPoolCreateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
+			return cmdPoolCreateInfo;
+		}
+
+		inline VkCommandBufferBeginInfo commandBufferBeginInfo()
+		{
+			VkCommandBufferBeginInfo cmdBufferBeginInfo {};
+			cmdBufferBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
+			return cmdBufferBeginInfo;
+		}
+
+		inline VkCommandBufferInheritanceInfo commandBufferInheritanceInfo()
+		{
+			VkCommandBufferInheritanceInfo cmdBufferInheritanceInfo {};
+			cmdBufferInheritanceInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO;
+			return cmdBufferInheritanceInfo;
+		}
+
+		inline VkRenderPassBeginInfo renderPassBeginInfo()
+		{
+			VkRenderPassBeginInfo renderPassBeginInfo {};
+			renderPassBeginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
+			return renderPassBeginInfo;
+		}
+
+		inline VkRenderPassCreateInfo renderPassCreateInfo()
+		{
+			VkRenderPassCreateInfo renderPassCreateInfo {};
+			renderPassCreateInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
+			return renderPassCreateInfo;
+		}
+
+		/** @brief Initialize an image memory barrier with no image transfer ownership */
+		inline VkImageMemoryBarrier imageMemoryBarrier()
+		{
+			VkImageMemoryBarrier imageMemoryBarrier {};
+			imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+			imageMemoryBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+			imageMemoryBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+			return imageMemoryBarrier;
+		}
+
+		/** @brief Initialize a buffer memory barrier with no image transfer ownership */
+		inline VkBufferMemoryBarrier bufferMemoryBarrier()
+		{
+			VkBufferMemoryBarrier bufferMemoryBarrier {};
+			bufferMemoryBarrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
+			bufferMemoryBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+			bufferMemoryBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+			return bufferMemoryBarrier;
+		}
+
+		inline VkMemoryBarrier memoryBarrier()
+		{
+			VkMemoryBarrier memoryBarrier {};
+			memoryBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER;
+			return memoryBarrier;
+		}
+
+		inline VkImageCreateInfo imageCreateInfo()
+		{
+			VkImageCreateInfo imageCreateInfo {};
+			imageCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
+			return imageCreateInfo;
+		}
+
+		inline VkSamplerCreateInfo samplerCreateInfo()
+		{
+			VkSamplerCreateInfo samplerCreateInfo {};
+			samplerCreateInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
+			samplerCreateInfo.maxAnisotropy = 1.0f;
+			return samplerCreateInfo;
+		}
+
+		inline VkImageViewCreateInfo imageViewCreateInfo()
+		{
+			VkImageViewCreateInfo imageViewCreateInfo {};
+			imageViewCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+			return imageViewCreateInfo;
+		}
+
+		inline VkFramebufferCreateInfo framebufferCreateInfo()
+		{
+			VkFramebufferCreateInfo framebufferCreateInfo {};
+			framebufferCreateInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
+			return framebufferCreateInfo;
+		}
+
+		inline VkSemaphoreCreateInfo semaphoreCreateInfo()
+		{
+			VkSemaphoreCreateInfo semaphoreCreateInfo {};
+			semaphoreCreateInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
+			return semaphoreCreateInfo;
+		}
+
+		inline VkFenceCreateInfo fenceCreateInfo(VkFenceCreateFlags flags = 0)
+		{
+			VkFenceCreateInfo fenceCreateInfo {};
+			fenceCreateInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
+			fenceCreateInfo.flags = flags;
+			return fenceCreateInfo;
+		}
+
+		inline VkEventCreateInfo eventCreateInfo()
+		{
+			VkEventCreateInfo eventCreateInfo {};
+			eventCreateInfo.sType = VK_STRUCTURE_TYPE_EVENT_CREATE_INFO;
+			return eventCreateInfo;
+		}
+
+		inline VkSubmitInfo submitInfo()
+		{
+			VkSubmitInfo submitInfo {};
+			submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
+			return submitInfo;
+		}
+
+		inline VkViewport viewport(
+			float width,
+			float height,
+			float minDepth,
+			float maxDepth)
+		{
+			VkViewport viewport {};
+			viewport.width = width;
+			viewport.height = height;
+			viewport.minDepth = minDepth;
+			viewport.maxDepth = maxDepth;
+			return viewport;
+		}
+
+		inline VkRect2D rect2D(
+			int32_t width,
+			int32_t height,
+			int32_t offsetX,
+			int32_t offsetY)
+		{
+			VkRect2D rect2D {};
+			rect2D.extent.width = width;
+			rect2D.extent.height = height;
+			rect2D.offset.x = offsetX;
+			rect2D.offset.y = offsetY;
+			return rect2D;
+		}
+
+		inline VkBufferCreateInfo bufferCreateInfo()
+		{
+			VkBufferCreateInfo bufCreateInfo {};
+			bufCreateInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+			return bufCreateInfo;
+		}
+
+		inline VkBufferCreateInfo bufferCreateInfo(
+			VkBufferUsageFlags usage,
+			VkDeviceSize size)
+		{
+			VkBufferCreateInfo bufCreateInfo {};
+			bufCreateInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+			bufCreateInfo.usage = usage;
+			bufCreateInfo.size = size;
+			return bufCreateInfo;
+		}
+
+		inline VkDescriptorPoolCreateInfo descriptorPoolCreateInfo(
+			uint32_t poolSizeCount,
+			VkDescriptorPoolSize* pPoolSizes,
+			uint32_t maxSets)
+		{
+			VkDescriptorPoolCreateInfo descriptorPoolInfo {};
+			descriptorPoolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
+			descriptorPoolInfo.poolSizeCount = poolSizeCount;
+			descriptorPoolInfo.pPoolSizes = pPoolSizes;
+			descriptorPoolInfo.maxSets = maxSets;
+			return descriptorPoolInfo;
+		}
+
+		inline VkDescriptorPoolCreateInfo descriptorPoolCreateInfo(
+			const std::vector<VkDescriptorPoolSize>& poolSizes,
+			uint32_t maxSets)
+		{
+			VkDescriptorPoolCreateInfo descriptorPoolInfo{};
+			descriptorPoolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
+			descriptorPoolInfo.poolSizeCount = static_cast<uint32_t>(poolSizes.size());
+			descriptorPoolInfo.pPoolSizes = poolSizes.data();
+			descriptorPoolInfo.maxSets = maxSets;
+			return descriptorPoolInfo;
+		}
+
+		inline VkDescriptorPoolSize descriptorPoolSize(
+			VkDescriptorType type,
+			uint32_t descriptorCount)
+		{
+			VkDescriptorPoolSize descriptorPoolSize {};
+			descriptorPoolSize.type = type;
+			descriptorPoolSize.descriptorCount = descriptorCount;
+			return descriptorPoolSize;
+		}
+
+		inline VkDescriptorSetLayoutBinding descriptorSetLayoutBinding(
+			VkDescriptorType type,
+			VkShaderStageFlags stageFlags,
+			uint32_t binding,
+			uint32_t descriptorCount = 1)
+		{
+			VkDescriptorSetLayoutBinding setLayoutBinding {};
+			setLayoutBinding.descriptorType = type;
+			setLayoutBinding.stageFlags = stageFlags;
+			setLayoutBinding.binding = binding;
+			setLayoutBinding.descriptorCount = descriptorCount;
+			return setLayoutBinding;
+		}
+
+		inline VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo(
+			const VkDescriptorSetLayoutBinding* pBindings,
+			uint32_t bindingCount)
+		{
+			VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo {};
+			descriptorSetLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
+			descriptorSetLayoutCreateInfo.pBindings = pBindings;
+			descriptorSetLayoutCreateInfo.bindingCount = bindingCount;
+			return descriptorSetLayoutCreateInfo;
+		}
+
+		inline VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo(
+			const std::vector<VkDescriptorSetLayoutBinding>& bindings)
+		{
+			VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo{};
+			descriptorSetLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
+			descriptorSetLayoutCreateInfo.pBindings = bindings.data();
+			descriptorSetLayoutCreateInfo.bindingCount = static_cast<uint32_t>(bindings.size());
+			return descriptorSetLayoutCreateInfo;
+		}
+
+		inline VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo(
+			const VkDescriptorSetLayout* pSetLayouts,
+			uint32_t setLayoutCount = 1)
+		{
+			VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo {};
+			pipelineLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
+			pipelineLayoutCreateInfo.setLayoutCount = setLayoutCount;
+			pipelineLayoutCreateInfo.pSetLayouts = pSetLayouts;
+			return pipelineLayoutCreateInfo;
+		}
+
+		inline VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo(
+			uint32_t setLayoutCount = 1)
+		{
+			VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo{};
+			pipelineLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
+			pipelineLayoutCreateInfo.setLayoutCount = setLayoutCount;
+			return pipelineLayoutCreateInfo;
+		}
+
+		inline VkDescriptorSetAllocateInfo descriptorSetAllocateInfo(
+			VkDescriptorPool descriptorPool,
+			const VkDescriptorSetLayout* pSetLayouts,
+			uint32_t descriptorSetCount)
+		{
+			VkDescriptorSetAllocateInfo descriptorSetAllocateInfo {};
+			descriptorSetAllocateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
+			descriptorSetAllocateInfo.descriptorPool = descriptorPool;
+			descriptorSetAllocateInfo.pSetLayouts = pSetLayouts;
+			descriptorSetAllocateInfo.descriptorSetCount = descriptorSetCount;
+			return descriptorSetAllocateInfo;
+		}
+
+		inline VkDescriptorImageInfo descriptorImageInfo(VkSampler sampler, VkImageView imageView, VkImageLayout imageLayout)
+		{
+			VkDescriptorImageInfo descriptorImageInfo {};
+			descriptorImageInfo.sampler = sampler;
+			descriptorImageInfo.imageView = imageView;
+			descriptorImageInfo.imageLayout = imageLayout;
+			return descriptorImageInfo;
+		}
+
+		inline VkWriteDescriptorSet writeDescriptorSet(
+			VkDescriptorSet dstSet,
+			VkDescriptorType type,
+			uint32_t binding,
+			VkDescriptorBufferInfo* bufferInfo,
+			uint32_t descriptorCount = 1)
+		{
+			VkWriteDescriptorSet writeDescriptorSet {};
+			writeDescriptorSet.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+			writeDescriptorSet.dstSet = dstSet;
+			writeDescriptorSet.descriptorType = type;
+			writeDescriptorSet.dstBinding = binding;
+			writeDescriptorSet.pBufferInfo = bufferInfo;
+			writeDescriptorSet.descriptorCount = descriptorCount;
+			return writeDescriptorSet;
+		}
+
+		inline VkWriteDescriptorSet writeDescriptorSet(
+			VkDescriptorSet dstSet,
+			VkDescriptorType type,
+			uint32_t binding,
+			VkDescriptorImageInfo *imageInfo,
+			uint32_t descriptorCount = 1)
+		{
+			VkWriteDescriptorSet writeDescriptorSet {};
+			writeDescriptorSet.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+			writeDescriptorSet.dstSet = dstSet;
+			writeDescriptorSet.descriptorType = type;
+			writeDescriptorSet.dstBinding = binding;
+			writeDescriptorSet.pImageInfo = imageInfo;
+			writeDescriptorSet.descriptorCount = descriptorCount;
+			return writeDescriptorSet;
+		}
+
+		inline VkVertexInputBindingDescription vertexInputBindingDescription(
+			uint32_t binding,
+			uint32_t stride,
+			VkVertexInputRate inputRate)
+		{
+			VkVertexInputBindingDescription vInputBindDescription {};
+			vInputBindDescription.binding = binding;
+			vInputBindDescription.stride = stride;
+			vInputBindDescription.inputRate = inputRate;
+			return vInputBindDescription;
+		}
+
+		inline VkVertexInputAttributeDescription vertexInputAttributeDescription(
+			uint32_t binding,
+			uint32_t location,
+			VkFormat format,
+			uint32_t offset)
+		{
+			VkVertexInputAttributeDescription vInputAttribDescription {};
+			vInputAttribDescription.location = location;
+			vInputAttribDescription.binding = binding;
+			vInputAttribDescription.format = format;
+			vInputAttribDescription.offset = offset;
+			return vInputAttribDescription;
+		}
+
+		inline VkPipelineVertexInputStateCreateInfo pipelineVertexInputStateCreateInfo()
+		{
+			VkPipelineVertexInputStateCreateInfo pipelineVertexInputStateCreateInfo {};
+			pipelineVertexInputStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
+			return pipelineVertexInputStateCreateInfo;
+		}
+
+		inline VkPipelineVertexInputStateCreateInfo pipelineVertexInputStateCreateInfo(
+			const std::vector<VkVertexInputBindingDescription> &vertexBindingDescriptions,
+			const std::vector<VkVertexInputAttributeDescription> &vertexAttributeDescriptions
+		)
+		{
+			VkPipelineVertexInputStateCreateInfo pipelineVertexInputStateCreateInfo{};
+			pipelineVertexInputStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
+			pipelineVertexInputStateCreateInfo.vertexBindingDescriptionCount = static_cast<uint32_t>(vertexBindingDescriptions.size());
+			pipelineVertexInputStateCreateInfo.pVertexBindingDescriptions = vertexBindingDescriptions.data();
+			pipelineVertexInputStateCreateInfo.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertexAttributeDescriptions.size());
+			pipelineVertexInputStateCreateInfo.pVertexAttributeDescriptions = vertexAttributeDescriptions.data();
+			return pipelineVertexInputStateCreateInfo;
+		}
+
+		inline VkPipelineInputAssemblyStateCreateInfo pipelineInputAssemblyStateCreateInfo(
+			VkPrimitiveTopology topology,
+			VkPipelineInputAssemblyStateCreateFlags flags,
+			VkBool32 primitiveRestartEnable)
+		{
+			VkPipelineInputAssemblyStateCreateInfo pipelineInputAssemblyStateCreateInfo {};
+			pipelineInputAssemblyStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
+			pipelineInputAssemblyStateCreateInfo.topology = topology;
+			pipelineInputAssemblyStateCreateInfo.flags = flags;
+			pipelineInputAssemblyStateCreateInfo.primitiveRestartEnable = primitiveRestartEnable;
+			return pipelineInputAssemblyStateCreateInfo;
+		}
+
+		inline VkPipelineRasterizationStateCreateInfo pipelineRasterizationStateCreateInfo(
+			VkPolygonMode polygonMode,
+			VkCullModeFlags cullMode,
+			VkFrontFace frontFace,
+			VkPipelineRasterizationStateCreateFlags flags = 0)
+		{
+			VkPipelineRasterizationStateCreateInfo pipelineRasterizationStateCreateInfo {};
+			pipelineRasterizationStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
+			pipelineRasterizationStateCreateInfo.polygonMode = polygonMode;
+			pipelineRasterizationStateCreateInfo.cullMode = cullMode;
+			pipelineRasterizationStateCreateInfo.frontFace = frontFace;
+			pipelineRasterizationStateCreateInfo.flags = flags;
+			pipelineRasterizationStateCreateInfo.depthClampEnable = VK_FALSE;
+			pipelineRasterizationStateCreateInfo.lineWidth = 1.0f;
+			return pipelineRasterizationStateCreateInfo;
+		}
+
+		inline VkPipelineColorBlendAttachmentState pipelineColorBlendAttachmentState(
+			VkColorComponentFlags colorWriteMask,
+			VkBool32 blendEnable)
+		{
+			VkPipelineColorBlendAttachmentState pipelineColorBlendAttachmentState {};
+			pipelineColorBlendAttachmentState.colorWriteMask = colorWriteMask;
+			pipelineColorBlendAttachmentState.blendEnable = blendEnable;
+			return pipelineColorBlendAttachmentState;
+		}
+
+		inline VkPipelineColorBlendStateCreateInfo pipelineColorBlendStateCreateInfo(
+			uint32_t attachmentCount,
+			const VkPipelineColorBlendAttachmentState * pAttachments)
+		{
+			VkPipelineColorBlendStateCreateInfo pipelineColorBlendStateCreateInfo {};
+			pipelineColorBlendStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
+			pipelineColorBlendStateCreateInfo.attachmentCount = attachmentCount;
+			pipelineColorBlendStateCreateInfo.pAttachments = pAttachments;
+			return pipelineColorBlendStateCreateInfo;
+		}
+
+		inline VkPipelineDepthStencilStateCreateInfo pipelineDepthStencilStateCreateInfo(
+			VkBool32 depthTestEnable,
+			VkBool32 depthWriteEnable,
+			VkCompareOp depthCompareOp)
+		{
+			VkPipelineDepthStencilStateCreateInfo pipelineDepthStencilStateCreateInfo {};
+			pipelineDepthStencilStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
+			pipelineDepthStencilStateCreateInfo.depthTestEnable = depthTestEnable;
+			pipelineDepthStencilStateCreateInfo.depthWriteEnable = depthWriteEnable;
+			pipelineDepthStencilStateCreateInfo.depthCompareOp = depthCompareOp;
+			pipelineDepthStencilStateCreateInfo.back.compareOp = VK_COMPARE_OP_ALWAYS;
+			return pipelineDepthStencilStateCreateInfo;
+		}
+
+		inline VkPipelineViewportStateCreateInfo pipelineViewportStateCreateInfo(
+			uint32_t viewportCount,
+			uint32_t scissorCount,
+			VkPipelineViewportStateCreateFlags flags = 0)
+		{
+			VkPipelineViewportStateCreateInfo pipelineViewportStateCreateInfo {};
+			pipelineViewportStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
+			pipelineViewportStateCreateInfo.viewportCount = viewportCount;
+			pipelineViewportStateCreateInfo.scissorCount = scissorCount;
+			pipelineViewportStateCreateInfo.flags = flags;
+			return pipelineViewportStateCreateInfo;
+		}
+
+		inline VkPipelineMultisampleStateCreateInfo pipelineMultisampleStateCreateInfo(
+			VkSampleCountFlagBits rasterizationSamples,
+			VkPipelineMultisampleStateCreateFlags flags = 0)
+		{
+			VkPipelineMultisampleStateCreateInfo pipelineMultisampleStateCreateInfo {};
+			pipelineMultisampleStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
+			pipelineMultisampleStateCreateInfo.rasterizationSamples = rasterizationSamples;
+			pipelineMultisampleStateCreateInfo.flags = flags;
+			return pipelineMultisampleStateCreateInfo;
+		}
+
+		inline VkPipelineDynamicStateCreateInfo pipelineDynamicStateCreateInfo(
+			const VkDynamicState * pDynamicStates,
+			uint32_t dynamicStateCount,
+			VkPipelineDynamicStateCreateFlags flags = 0)
+		{
+			VkPipelineDynamicStateCreateInfo pipelineDynamicStateCreateInfo {};
+			pipelineDynamicStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
+			pipelineDynamicStateCreateInfo.pDynamicStates = pDynamicStates;
+			pipelineDynamicStateCreateInfo.dynamicStateCount = dynamicStateCount;
+			pipelineDynamicStateCreateInfo.flags = flags;
+			return pipelineDynamicStateCreateInfo;
+		}
+
+		inline VkPipelineDynamicStateCreateInfo pipelineDynamicStateCreateInfo(
+			const std::vector<VkDynamicState>& pDynamicStates,
+			VkPipelineDynamicStateCreateFlags flags = 0)
+		{
+			VkPipelineDynamicStateCreateInfo pipelineDynamicStateCreateInfo{};
+			pipelineDynamicStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
+			pipelineDynamicStateCreateInfo.pDynamicStates = pDynamicStates.data();
+			pipelineDynamicStateCreateInfo.dynamicStateCount = static_cast<uint32_t>(pDynamicStates.size());
+			pipelineDynamicStateCreateInfo.flags = flags;
+			return pipelineDynamicStateCreateInfo;
+		}
+
+		inline VkPipelineTessellationStateCreateInfo pipelineTessellationStateCreateInfo(uint32_t patchControlPoints)
+		{
+			VkPipelineTessellationStateCreateInfo pipelineTessellationStateCreateInfo {};
+			pipelineTessellationStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO;
+			pipelineTessellationStateCreateInfo.patchControlPoints = patchControlPoints;
+			return pipelineTessellationStateCreateInfo;
+		}
+
+		inline VkGraphicsPipelineCreateInfo pipelineCreateInfo(
+			VkPipelineLayout layout,
+			VkRenderPass renderPass,
+			VkPipelineCreateFlags flags = 0)
+		{
+			VkGraphicsPipelineCreateInfo pipelineCreateInfo {};
+			pipelineCreateInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
+			pipelineCreateInfo.layout = layout;
+			pipelineCreateInfo.renderPass = renderPass;
+			pipelineCreateInfo.flags = flags;
+			pipelineCreateInfo.basePipelineIndex = -1;
+			pipelineCreateInfo.basePipelineHandle = VK_NULL_HANDLE;
+			return pipelineCreateInfo;
+		}
+
+		inline VkGraphicsPipelineCreateInfo pipelineCreateInfo()
+		{
+			VkGraphicsPipelineCreateInfo pipelineCreateInfo{};
+			pipelineCreateInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
+			pipelineCreateInfo.basePipelineIndex = -1;
+			pipelineCreateInfo.basePipelineHandle = VK_NULL_HANDLE;
+			return pipelineCreateInfo;
+		}
+
+		inline VkComputePipelineCreateInfo computePipelineCreateInfo(
+			VkPipelineLayout layout, 
+			VkPipelineCreateFlags flags = 0)
+		{
+			VkComputePipelineCreateInfo computePipelineCreateInfo {};
+			computePipelineCreateInfo.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO;
+			computePipelineCreateInfo.layout = layout;
+			computePipelineCreateInfo.flags = flags;
+			return computePipelineCreateInfo;
+		}
+
+		inline VkPushConstantRange pushConstantRange(
+			VkShaderStageFlags stageFlags,
+			uint32_t size,
+			uint32_t offset)
+		{
+			VkPushConstantRange pushConstantRange {};
+			pushConstantRange.stageFlags = stageFlags;
+			pushConstantRange.offset = offset;
+			pushConstantRange.size = size;
+			return pushConstantRange;
+		}
+
+		inline VkBindSparseInfo bindSparseInfo()
+		{
+			VkBindSparseInfo bindSparseInfo{};
+			bindSparseInfo.sType = VK_STRUCTURE_TYPE_BIND_SPARSE_INFO;
+			return bindSparseInfo;
+		}
+
+		/** @brief Initialize a map entry for a shader specialization constant */
+		inline VkSpecializationMapEntry specializationMapEntry(uint32_t constantID, uint32_t offset, size_t size)
+		{
+			VkSpecializationMapEntry specializationMapEntry{};
+			specializationMapEntry.constantID = constantID;
+			specializationMapEntry.offset = offset;
+			specializationMapEntry.size = size;
+			return specializationMapEntry;
+		}
+
+		/** @brief Initialize a specialization constant info structure to pass to a shader stage */
+		inline VkSpecializationInfo specializationInfo(uint32_t mapEntryCount, const VkSpecializationMapEntry* mapEntries, size_t dataSize, const void* data)
+		{
+			VkSpecializationInfo specializationInfo{};
+			specializationInfo.mapEntryCount = mapEntryCount;
+			specializationInfo.pMapEntries = mapEntries;
+			specializationInfo.dataSize = dataSize;
+			specializationInfo.pData = data;
+			return specializationInfo;
+		}
+
+		/** @brief Initialize a specialization constant info structure to pass to a shader stage */
+		inline VkSpecializationInfo specializationInfo(const std::vector<VkSpecializationMapEntry> &mapEntries, size_t dataSize, const void* data)
+		{
+			VkSpecializationInfo specializationInfo{};
+			specializationInfo.mapEntryCount = static_cast<uint32_t>(mapEntries.size());
+			specializationInfo.pMapEntries = mapEntries.data();
+			specializationInfo.dataSize = dataSize;
+			specializationInfo.pData = data;
+			return specializationInfo;
+		}
+
+		// Ray tracing related
+		inline VkAccelerationStructureGeometryKHR accelerationStructureGeometryKHR()
+		{
+			VkAccelerationStructureGeometryKHR accelerationStructureGeometryKHR{};
+			accelerationStructureGeometryKHR.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR;
+			return accelerationStructureGeometryKHR;
+		}
+
+		inline VkAccelerationStructureBuildGeometryInfoKHR accelerationStructureBuildGeometryInfoKHR()
+		{
+			VkAccelerationStructureBuildGeometryInfoKHR accelerationStructureBuildGeometryInfoKHR{};
+			accelerationStructureBuildGeometryInfoKHR.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR;
+			return accelerationStructureBuildGeometryInfoKHR;
+		}
+
+		inline VkAccelerationStructureBuildSizesInfoKHR accelerationStructureBuildSizesInfoKHR()
+		{
+			VkAccelerationStructureBuildSizesInfoKHR accelerationStructureBuildSizesInfoKHR{};
+			accelerationStructureBuildSizesInfoKHR.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_SIZES_INFO_KHR;
+			return accelerationStructureBuildSizesInfoKHR;
+		}
+
+		inline VkRayTracingShaderGroupCreateInfoKHR rayTracingShaderGroupCreateInfoKHR()
+		{
+			VkRayTracingShaderGroupCreateInfoKHR rayTracingShaderGroupCreateInfoKHR{};
+			rayTracingShaderGroupCreateInfoKHR.sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR;
+			return rayTracingShaderGroupCreateInfoKHR;
+		}
+
+		inline VkRayTracingPipelineCreateInfoKHR rayTracingPipelineCreateInfoKHR()
+		{
+			VkRayTracingPipelineCreateInfoKHR rayTracingPipelineCreateInfoKHR{};
+			rayTracingPipelineCreateInfoKHR.sType = VK_STRUCTURE_TYPE_RAY_TRACING_PIPELINE_CREATE_INFO_KHR;
+			return rayTracingPipelineCreateInfoKHR;
+		}
+
+		inline VkWriteDescriptorSetAccelerationStructureKHR writeDescriptorSetAccelerationStructureKHR()
+		{
+			VkWriteDescriptorSetAccelerationStructureKHR writeDescriptorSetAccelerationStructureKHR{};
+			writeDescriptorSetAccelerationStructureKHR.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR;
+			return writeDescriptorSetAccelerationStructureKHR;
+		}
+
+	}
+}
\ No newline at end of file
diff --git a/external/Vulkan/base/VulkanRaytracingSample.cpp b/external/Vulkan/base/VulkanRaytracingSample.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..dadb0552179b4694f8b2a236a72fab9f0aad1466
--- /dev/null
+++ b/external/Vulkan/base/VulkanRaytracingSample.cpp
@@ -0,0 +1,331 @@
+/*
+* Extended sample base class for ray tracing based samples
+*
+* Copyright (C) 2020-2021 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "VulkanRaytracingSample.h"
+
+void VulkanRaytracingSample::updateRenderPass()
+{
+	// Update the default render pass with different color attachment load ops to keep attachment contents
+	// With this change, we can e.g. draw an UI on top of the ray traced scene
+
+	vkDestroyRenderPass(device, renderPass, nullptr);
+
+	std::array<VkAttachmentDescription, 2> attachments = {};
+	// Color attachment
+	attachments[0].format = swapChain.colorFormat;
+	attachments[0].samples = VK_SAMPLE_COUNT_1_BIT;
+	attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
+	attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+	attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+	attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+	attachments[0].initialLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
+	attachments[0].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
+	// Depth attachment
+	attachments[1].format = depthFormat;
+	attachments[1].samples = VK_SAMPLE_COUNT_1_BIT;
+	attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+	attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+	attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+	attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+	attachments[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+	attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+
+	VkAttachmentReference colorReference = {};
+	colorReference.attachment = 0;
+	colorReference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+
+	VkAttachmentReference depthReference = {};
+	depthReference.attachment = 1;
+	depthReference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+
+	VkSubpassDescription subpassDescription = {};
+	subpassDescription.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+	subpassDescription.colorAttachmentCount = 1;
+	subpassDescription.pColorAttachments = &colorReference;
+	subpassDescription.pDepthStencilAttachment = &depthReference;
+	subpassDescription.inputAttachmentCount = 0;
+	subpassDescription.pInputAttachments = nullptr;
+	subpassDescription.preserveAttachmentCount = 0;
+	subpassDescription.pPreserveAttachments = nullptr;
+	subpassDescription.pResolveAttachments = nullptr;
+
+	// Subpass dependencies for layout transitions
+	std::array<VkSubpassDependency, 2> dependencies;
+
+	dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
+	dependencies[0].dstSubpass = 0;
+	dependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+	dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+	dependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
+	dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+	dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+	dependencies[1].srcSubpass = 0;
+	dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
+	dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+	dependencies[1].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+	dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+	dependencies[1].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
+	dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+	VkRenderPassCreateInfo renderPassInfo = {};
+	renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
+	renderPassInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
+	renderPassInfo.pAttachments = attachments.data();
+	renderPassInfo.subpassCount = 1;
+	renderPassInfo.pSubpasses = &subpassDescription;
+	renderPassInfo.dependencyCount = static_cast<uint32_t>(dependencies.size());
+	renderPassInfo.pDependencies = dependencies.data();
+	VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass));
+}
+
+void VulkanRaytracingSample::enableExtensions()
+{
+	// Require Vulkan 1.1
+	apiVersion = VK_API_VERSION_1_1;
+
+	// Ray tracing related extensions required by this sample
+	enabledDeviceExtensions.push_back(VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME);
+	if (!rayQueryOnly) {
+		enabledDeviceExtensions.push_back(VK_KHR_RAY_TRACING_PIPELINE_EXTENSION_NAME);
+	}
+
+	// Required by VK_KHR_acceleration_structure
+	enabledDeviceExtensions.push_back(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME);
+	if (!rayQueryOnly) {
+		enabledDeviceExtensions.push_back(VK_KHR_DEFERRED_HOST_OPERATIONS_EXTENSION_NAME);
+	}
+	enabledDeviceExtensions.push_back(VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME);
+
+	// Required for VK_KHR_ray_tracing_pipeline
+	enabledDeviceExtensions.push_back(VK_KHR_SPIRV_1_4_EXTENSION_NAME);
+
+	// Required by VK_KHR_spirv_1_4
+	enabledDeviceExtensions.push_back(VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME);
+}
+
+VulkanRaytracingSample::ScratchBuffer VulkanRaytracingSample::createScratchBuffer(VkDeviceSize size)
+{
+	ScratchBuffer scratchBuffer{};
+	// Buffer and memory
+	VkBufferCreateInfo bufferCreateInfo{};
+	bufferCreateInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+	bufferCreateInfo.size = size;
+	bufferCreateInfo.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT;
+	VK_CHECK_RESULT(vkCreateBuffer(vulkanDevice->logicalDevice, &bufferCreateInfo, nullptr, &scratchBuffer.handle));
+	VkMemoryRequirements memoryRequirements{};
+	vkGetBufferMemoryRequirements(vulkanDevice->logicalDevice, scratchBuffer.handle, &memoryRequirements);
+	VkMemoryAllocateFlagsInfo memoryAllocateFlagsInfo{};
+	memoryAllocateFlagsInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO;
+	memoryAllocateFlagsInfo.flags = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT_KHR;
+	VkMemoryAllocateInfo memoryAllocateInfo = {};
+	memoryAllocateInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+	memoryAllocateInfo.pNext = &memoryAllocateFlagsInfo;
+	memoryAllocateInfo.allocationSize = memoryRequirements.size;
+	memoryAllocateInfo.memoryTypeIndex = vulkanDevice->getMemoryType(memoryRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+	VK_CHECK_RESULT(vkAllocateMemory(vulkanDevice->logicalDevice, &memoryAllocateInfo, nullptr, &scratchBuffer.memory));
+	VK_CHECK_RESULT(vkBindBufferMemory(vulkanDevice->logicalDevice, scratchBuffer.handle, scratchBuffer.memory, 0));
+	// Buffer device address
+	VkBufferDeviceAddressInfoKHR bufferDeviceAddresInfo{};
+	bufferDeviceAddresInfo.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO;
+	bufferDeviceAddresInfo.buffer = scratchBuffer.handle;
+	scratchBuffer.deviceAddress = vkGetBufferDeviceAddressKHR(vulkanDevice->logicalDevice, &bufferDeviceAddresInfo);
+	return scratchBuffer;
+}
+
+void VulkanRaytracingSample::deleteScratchBuffer(ScratchBuffer& scratchBuffer)
+{
+	if (scratchBuffer.memory != VK_NULL_HANDLE) {
+		vkFreeMemory(vulkanDevice->logicalDevice, scratchBuffer.memory, nullptr);
+	}
+	if (scratchBuffer.handle != VK_NULL_HANDLE) {
+		vkDestroyBuffer(vulkanDevice->logicalDevice, scratchBuffer.handle, nullptr);
+	}
+}
+
+void VulkanRaytracingSample::createAccelerationStructure(AccelerationStructure& accelerationStructure, VkAccelerationStructureTypeKHR type, VkAccelerationStructureBuildSizesInfoKHR buildSizeInfo)
+{
+	// Buffer and memory
+	VkBufferCreateInfo bufferCreateInfo{};
+	bufferCreateInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+	bufferCreateInfo.size = buildSizeInfo.accelerationStructureSize;
+	bufferCreateInfo.usage = VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT;
+	VK_CHECK_RESULT(vkCreateBuffer(vulkanDevice->logicalDevice, &bufferCreateInfo, nullptr, &accelerationStructure.buffer));
+	VkMemoryRequirements memoryRequirements{};
+	vkGetBufferMemoryRequirements(vulkanDevice->logicalDevice, accelerationStructure.buffer, &memoryRequirements);
+	VkMemoryAllocateFlagsInfo memoryAllocateFlagsInfo{};
+	memoryAllocateFlagsInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO;
+	memoryAllocateFlagsInfo.flags = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT_KHR;
+	VkMemoryAllocateInfo memoryAllocateInfo{};
+	memoryAllocateInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+	memoryAllocateInfo.pNext = &memoryAllocateFlagsInfo;
+	memoryAllocateInfo.allocationSize = memoryRequirements.size;
+	memoryAllocateInfo.memoryTypeIndex = vulkanDevice->getMemoryType(memoryRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+	VK_CHECK_RESULT(vkAllocateMemory(vulkanDevice->logicalDevice, &memoryAllocateInfo, nullptr, &accelerationStructure.memory));
+	VK_CHECK_RESULT(vkBindBufferMemory(vulkanDevice->logicalDevice, accelerationStructure.buffer, accelerationStructure.memory, 0));
+	// Acceleration structure
+	VkAccelerationStructureCreateInfoKHR accelerationStructureCreate_info{};
+	accelerationStructureCreate_info.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_CREATE_INFO_KHR;
+	accelerationStructureCreate_info.buffer = accelerationStructure.buffer;
+	accelerationStructureCreate_info.size = buildSizeInfo.accelerationStructureSize;
+	accelerationStructureCreate_info.type = type;
+	vkCreateAccelerationStructureKHR(vulkanDevice->logicalDevice, &accelerationStructureCreate_info, nullptr, &accelerationStructure.handle);
+	// AS device address
+	VkAccelerationStructureDeviceAddressInfoKHR accelerationDeviceAddressInfo{};
+	accelerationDeviceAddressInfo.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_DEVICE_ADDRESS_INFO_KHR;
+	accelerationDeviceAddressInfo.accelerationStructure = accelerationStructure.handle;
+	accelerationStructure.deviceAddress = vkGetAccelerationStructureDeviceAddressKHR(vulkanDevice->logicalDevice, &accelerationDeviceAddressInfo);
+}
+
+void VulkanRaytracingSample::deleteAccelerationStructure(AccelerationStructure& accelerationStructure)
+{
+	vkFreeMemory(device, accelerationStructure.memory, nullptr);
+	vkDestroyBuffer(device, accelerationStructure.buffer, nullptr);
+	vkDestroyAccelerationStructureKHR(device, accelerationStructure.handle, nullptr);
+}
+
+uint64_t VulkanRaytracingSample::getBufferDeviceAddress(VkBuffer buffer)
+{
+	VkBufferDeviceAddressInfoKHR bufferDeviceAI{};
+	bufferDeviceAI.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO;
+	bufferDeviceAI.buffer = buffer;
+	return vkGetBufferDeviceAddressKHR(vulkanDevice->logicalDevice, &bufferDeviceAI);
+}
+
+void VulkanRaytracingSample::createStorageImage(VkFormat format, VkExtent3D extent)
+{
+	// Release ressources if image is to be recreated
+	if (storageImage.image != VK_NULL_HANDLE) {
+		vkDestroyImageView(device, storageImage.view, nullptr);
+		vkDestroyImage(device, storageImage.image, nullptr);
+		vkFreeMemory(device, storageImage.memory, nullptr);
+		storageImage = {};
+	}
+
+	VkImageCreateInfo image = vks::initializers::imageCreateInfo();
+	image.imageType = VK_IMAGE_TYPE_2D;
+	image.format = format;
+	image.extent = extent;
+	image.mipLevels = 1;
+	image.arrayLayers = 1;
+	image.samples = VK_SAMPLE_COUNT_1_BIT;
+	image.tiling = VK_IMAGE_TILING_OPTIMAL;
+	image.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_STORAGE_BIT;
+	image.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+	VK_CHECK_RESULT(vkCreateImage(vulkanDevice->logicalDevice, &image, nullptr, &storageImage.image));
+
+	VkMemoryRequirements memReqs;
+	vkGetImageMemoryRequirements(vulkanDevice->logicalDevice, storageImage.image, &memReqs);
+	VkMemoryAllocateInfo memoryAllocateInfo = vks::initializers::memoryAllocateInfo();
+	memoryAllocateInfo.allocationSize = memReqs.size;
+	memoryAllocateInfo.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+	VK_CHECK_RESULT(vkAllocateMemory(vulkanDevice->logicalDevice, &memoryAllocateInfo, nullptr, &storageImage.memory));
+	VK_CHECK_RESULT(vkBindImageMemory(vulkanDevice->logicalDevice, storageImage.image, storageImage.memory, 0));
+
+	VkImageViewCreateInfo colorImageView = vks::initializers::imageViewCreateInfo();
+	colorImageView.viewType = VK_IMAGE_VIEW_TYPE_2D;
+	colorImageView.format = format;
+	colorImageView.subresourceRange = {};
+	colorImageView.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+	colorImageView.subresourceRange.baseMipLevel = 0;
+	colorImageView.subresourceRange.levelCount = 1;
+	colorImageView.subresourceRange.baseArrayLayer = 0;
+	colorImageView.subresourceRange.layerCount = 1;
+	colorImageView.image = storageImage.image;
+	VK_CHECK_RESULT(vkCreateImageView(vulkanDevice->logicalDevice, &colorImageView, nullptr, &storageImage.view));
+
+	VkCommandBuffer cmdBuffer = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+	vks::tools::setImageLayout(cmdBuffer, storageImage.image,
+		VK_IMAGE_LAYOUT_UNDEFINED,
+		VK_IMAGE_LAYOUT_GENERAL,
+		{ VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 });
+	vulkanDevice->flushCommandBuffer(cmdBuffer, queue);
+}
+
+void VulkanRaytracingSample::deleteStorageImage()
+{
+	vkDestroyImageView(vulkanDevice->logicalDevice, storageImage.view, nullptr);
+	vkDestroyImage(vulkanDevice->logicalDevice, storageImage.image, nullptr);
+	vkFreeMemory(vulkanDevice->logicalDevice, storageImage.memory, nullptr);
+}
+
+void VulkanRaytracingSample::prepare()
+{
+	VulkanExampleBase::prepare();
+	// Get properties and features
+	rayTracingPipelineProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_PROPERTIES_KHR;
+	VkPhysicalDeviceProperties2 deviceProperties2{};
+	deviceProperties2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
+	deviceProperties2.pNext = &rayTracingPipelineProperties;
+	vkGetPhysicalDeviceProperties2(physicalDevice, &deviceProperties2);
+	accelerationStructureFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_FEATURES_KHR;
+	VkPhysicalDeviceFeatures2 deviceFeatures2{};
+	deviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
+	deviceFeatures2.pNext = &accelerationStructureFeatures;
+	vkGetPhysicalDeviceFeatures2(physicalDevice, &deviceFeatures2);
+	// Get the function pointers required for ray tracing
+	vkGetBufferDeviceAddressKHR = reinterpret_cast<PFN_vkGetBufferDeviceAddressKHR>(vkGetDeviceProcAddr(device, "vkGetBufferDeviceAddressKHR"));
+	vkCmdBuildAccelerationStructuresKHR = reinterpret_cast<PFN_vkCmdBuildAccelerationStructuresKHR>(vkGetDeviceProcAddr(device, "vkCmdBuildAccelerationStructuresKHR"));
+	vkBuildAccelerationStructuresKHR = reinterpret_cast<PFN_vkBuildAccelerationStructuresKHR>(vkGetDeviceProcAddr(device, "vkBuildAccelerationStructuresKHR"));
+	vkCreateAccelerationStructureKHR = reinterpret_cast<PFN_vkCreateAccelerationStructureKHR>(vkGetDeviceProcAddr(device, "vkCreateAccelerationStructureKHR"));
+	vkDestroyAccelerationStructureKHR = reinterpret_cast<PFN_vkDestroyAccelerationStructureKHR>(vkGetDeviceProcAddr(device, "vkDestroyAccelerationStructureKHR"));
+	vkGetAccelerationStructureBuildSizesKHR = reinterpret_cast<PFN_vkGetAccelerationStructureBuildSizesKHR>(vkGetDeviceProcAddr(device, "vkGetAccelerationStructureBuildSizesKHR"));
+	vkGetAccelerationStructureDeviceAddressKHR = reinterpret_cast<PFN_vkGetAccelerationStructureDeviceAddressKHR>(vkGetDeviceProcAddr(device, "vkGetAccelerationStructureDeviceAddressKHR"));
+	vkCmdTraceRaysKHR = reinterpret_cast<PFN_vkCmdTraceRaysKHR>(vkGetDeviceProcAddr(device, "vkCmdTraceRaysKHR"));
+	vkGetRayTracingShaderGroupHandlesKHR = reinterpret_cast<PFN_vkGetRayTracingShaderGroupHandlesKHR>(vkGetDeviceProcAddr(device, "vkGetRayTracingShaderGroupHandlesKHR"));
+	vkCreateRayTracingPipelinesKHR = reinterpret_cast<PFN_vkCreateRayTracingPipelinesKHR>(vkGetDeviceProcAddr(device, "vkCreateRayTracingPipelinesKHR"));
+	// Update the render pass to keep the color attachment contents, so we can draw the UI on top of the ray traced output
+	if (!rayQueryOnly) {
+		updateRenderPass();
+	}
+}
+
+VkStridedDeviceAddressRegionKHR VulkanRaytracingSample::getSbtEntryStridedDeviceAddressRegion(VkBuffer buffer, uint32_t handleCount)
+{
+	const uint32_t handleSizeAligned = vks::tools::alignedSize(rayTracingPipelineProperties.shaderGroupHandleSize, rayTracingPipelineProperties.shaderGroupHandleAlignment);
+	VkStridedDeviceAddressRegionKHR stridedDeviceAddressRegionKHR{};
+	stridedDeviceAddressRegionKHR.deviceAddress = getBufferDeviceAddress(buffer);
+	stridedDeviceAddressRegionKHR.stride = handleSizeAligned;
+	stridedDeviceAddressRegionKHR.size = handleCount * handleSizeAligned;
+	return stridedDeviceAddressRegionKHR;
+}
+
+void VulkanRaytracingSample::createShaderBindingTable(ShaderBindingTable& shaderBindingTable, uint32_t handleCount)
+{
+	// Create buffer to hold all shader handles for the SBT
+	VK_CHECK_RESULT(vulkanDevice->createBuffer(
+		VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, 
+		VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, 
+		&shaderBindingTable, 
+		rayTracingPipelineProperties.shaderGroupHandleSize * handleCount));
+	// Get the strided address to be used when dispatching the rays
+	shaderBindingTable.stridedDeviceAddressRegion = getSbtEntryStridedDeviceAddressRegion(shaderBindingTable.buffer, handleCount);
+	// Map persistent 
+	shaderBindingTable.map();
+}
+
+void VulkanRaytracingSample::drawUI(VkCommandBuffer commandBuffer, VkFramebuffer framebuffer)
+{
+	VkClearValue clearValues[2];
+	clearValues[0].color = defaultClearColor;
+	clearValues[1].depthStencil = { 1.0f, 0 };
+
+	VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+	renderPassBeginInfo.renderPass = renderPass;
+	renderPassBeginInfo.renderArea.offset.x = 0;
+	renderPassBeginInfo.renderArea.offset.y = 0;
+	renderPassBeginInfo.renderArea.extent.width = width;
+	renderPassBeginInfo.renderArea.extent.height = height;
+	renderPassBeginInfo.clearValueCount = 2;
+	renderPassBeginInfo.pClearValues = clearValues;
+	renderPassBeginInfo.framebuffer = framebuffer;
+
+	vkCmdBeginRenderPass(commandBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+	VulkanExampleBase::drawUI(commandBuffer);
+	vkCmdEndRenderPass(commandBuffer);
+}
diff --git a/external/Vulkan/base/VulkanRaytracingSample.h b/external/Vulkan/base/VulkanRaytracingSample.h
new file mode 100644
index 0000000000000000000000000000000000000000..85bdc341a20887e5216f1771a9554d259855da4c
--- /dev/null
+++ b/external/Vulkan/base/VulkanRaytracingSample.h
@@ -0,0 +1,90 @@
+/*
+* Extended sample base class for ray tracing based samples
+*
+* Copyright (C) 2020 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#pragma once
+
+#include "vulkan/vulkan.h"
+#include "vulkanexamplebase.h"
+#include "VulkanTools.h"
+#include "VulkanDevice.h"
+
+class VulkanRaytracingSample : public VulkanExampleBase
+{
+protected:
+	// Update the default render pass with different color attachment load ops
+	virtual void updateRenderPass();
+public:
+	// Function pointers for ray tracing related stuff
+	PFN_vkGetBufferDeviceAddressKHR vkGetBufferDeviceAddressKHR;
+	PFN_vkCreateAccelerationStructureKHR vkCreateAccelerationStructureKHR;
+	PFN_vkDestroyAccelerationStructureKHR vkDestroyAccelerationStructureKHR;
+	PFN_vkGetAccelerationStructureBuildSizesKHR vkGetAccelerationStructureBuildSizesKHR;
+	PFN_vkGetAccelerationStructureDeviceAddressKHR vkGetAccelerationStructureDeviceAddressKHR;
+	PFN_vkBuildAccelerationStructuresKHR vkBuildAccelerationStructuresKHR;
+	PFN_vkCmdBuildAccelerationStructuresKHR vkCmdBuildAccelerationStructuresKHR;
+	PFN_vkCmdTraceRaysKHR vkCmdTraceRaysKHR;
+	PFN_vkGetRayTracingShaderGroupHandlesKHR vkGetRayTracingShaderGroupHandlesKHR;
+	PFN_vkCreateRayTracingPipelinesKHR vkCreateRayTracingPipelinesKHR;
+
+	// Available features and properties
+	VkPhysicalDeviceRayTracingPipelinePropertiesKHR  rayTracingPipelineProperties{};
+	VkPhysicalDeviceAccelerationStructureFeaturesKHR accelerationStructureFeatures{};
+
+	// Enabled features and properties
+	VkPhysicalDeviceBufferDeviceAddressFeatures enabledBufferDeviceAddresFeatures{};
+	VkPhysicalDeviceRayTracingPipelineFeaturesKHR enabledRayTracingPipelineFeatures{};
+	VkPhysicalDeviceAccelerationStructureFeaturesKHR enabledAccelerationStructureFeatures{};
+
+	// Holds information for a ray tracing scratch buffer that is used as a temporary storage
+	struct ScratchBuffer
+	{
+		uint64_t deviceAddress = 0;
+		VkBuffer handle = VK_NULL_HANDLE;
+		VkDeviceMemory memory = VK_NULL_HANDLE;
+	};
+
+	// Holds information for a ray tracing acceleration structure
+	struct AccelerationStructure {
+		VkAccelerationStructureKHR handle;
+		uint64_t deviceAddress = 0;
+		VkDeviceMemory memory;
+		VkBuffer buffer;
+	};
+
+	// Holds information for a storage image that the ray tracing shaders output to
+	struct StorageImage {
+		VkDeviceMemory memory = VK_NULL_HANDLE;
+		VkImage image = VK_NULL_HANDLE;
+		VkImageView view = VK_NULL_HANDLE;
+		VkFormat format;
+	} storageImage;
+
+	// Extends the buffer class and holds information for a shader binding table
+	class ShaderBindingTable : public vks::Buffer {
+	public:
+		VkStridedDeviceAddressRegionKHR stridedDeviceAddressRegion{};
+	};
+
+	// Set to true, to denote that the sample only uses ray queries (changes extension and render pass handling)
+	bool rayQueryOnly = false;
+
+	void enableExtensions();
+	ScratchBuffer createScratchBuffer(VkDeviceSize size);
+	void deleteScratchBuffer(ScratchBuffer& scratchBuffer);
+	void createAccelerationStructure(AccelerationStructure& accelerationStructure, VkAccelerationStructureTypeKHR type, VkAccelerationStructureBuildSizesInfoKHR buildSizeInfo);
+	void deleteAccelerationStructure(AccelerationStructure& accelerationStructure);
+	uint64_t getBufferDeviceAddress(VkBuffer buffer);
+	void createStorageImage(VkFormat format, VkExtent3D extent);
+	void deleteStorageImage();
+	VkStridedDeviceAddressRegionKHR getSbtEntryStridedDeviceAddressRegion(VkBuffer buffer, uint32_t handleCount);
+	void createShaderBindingTable(ShaderBindingTable& shaderBindingTable, uint32_t handleCount);
+	// Draw the ImGUI UI overlay using a render pass
+	void drawUI(VkCommandBuffer commandBuffer, VkFramebuffer framebuffer);
+
+	virtual void prepare();
+};
diff --git a/external/Vulkan/base/VulkanSwapChain.cpp b/external/Vulkan/base/VulkanSwapChain.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..432fee107fddafee937c276359e676dbc9670a21
--- /dev/null
+++ b/external/Vulkan/base/VulkanSwapChain.cpp
@@ -0,0 +1,605 @@
+/*
+* Class wrapping access to the swap chain
+* 
+* A swap chain is a collection of framebuffers used for rendering and presentation to the windowing system
+*
+* Copyright (C) 2016-2021 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "VulkanSwapChain.h"
+
+/** @brief Creates the platform specific surface abstraction of the native platform window used for presentation */	
+#if defined(VK_USE_PLATFORM_WIN32_KHR)
+void VulkanSwapChain::initSurface(void* platformHandle, void* platformWindow)
+#elif defined(VK_USE_PLATFORM_ANDROID_KHR)
+void VulkanSwapChain::initSurface(ANativeWindow* window)
+#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT)
+void VulkanSwapChain::initSurface(IDirectFB* dfb, IDirectFBSurface* window)
+#elif defined(VK_USE_PLATFORM_WAYLAND_KHR)
+void VulkanSwapChain::initSurface(wl_display *display, wl_surface *window)
+#elif defined(VK_USE_PLATFORM_XCB_KHR)
+void VulkanSwapChain::initSurface(xcb_connection_t* connection, xcb_window_t window)
+#elif (defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_MACOS_MVK))
+void VulkanSwapChain::initSurface(void* view)
+#elif (defined(_DIRECT2DISPLAY) || defined(VK_USE_PLATFORM_HEADLESS_EXT))
+void VulkanSwapChain::initSurface(uint32_t width, uint32_t height)
+#endif
+{
+	VkResult err = VK_SUCCESS;
+
+	// Create the os-specific surface
+#if defined(VK_USE_PLATFORM_WIN32_KHR)
+	VkWin32SurfaceCreateInfoKHR surfaceCreateInfo = {};
+	surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;
+	surfaceCreateInfo.hinstance = (HINSTANCE)platformHandle;
+	surfaceCreateInfo.hwnd = (HWND)platformWindow;
+	err = vkCreateWin32SurfaceKHR(instance, &surfaceCreateInfo, nullptr, &surface);
+#elif defined(VK_USE_PLATFORM_ANDROID_KHR)
+	VkAndroidSurfaceCreateInfoKHR surfaceCreateInfo = {};
+	surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR;
+	surfaceCreateInfo.window = window;
+	err = vkCreateAndroidSurfaceKHR(instance, &surfaceCreateInfo, NULL, &surface);
+#elif defined(VK_USE_PLATFORM_IOS_MVK)
+	VkIOSSurfaceCreateInfoMVK surfaceCreateInfo = {};
+	surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_IOS_SURFACE_CREATE_INFO_MVK;
+	surfaceCreateInfo.pNext = NULL;
+	surfaceCreateInfo.flags = 0;
+	surfaceCreateInfo.pView = view;
+	err = vkCreateIOSSurfaceMVK(instance, &surfaceCreateInfo, nullptr, &surface);
+#elif defined(VK_USE_PLATFORM_MACOS_MVK)
+	VkMacOSSurfaceCreateInfoMVK surfaceCreateInfo = {};
+	surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK;
+	surfaceCreateInfo.pNext = NULL;
+	surfaceCreateInfo.flags = 0;
+	surfaceCreateInfo.pView = view;
+	err = vkCreateMacOSSurfaceMVK(instance, &surfaceCreateInfo, NULL, &surface);
+#elif defined(_DIRECT2DISPLAY)
+	createDirect2DisplaySurface(width, height);
+#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT)
+	VkDirectFBSurfaceCreateInfoEXT surfaceCreateInfo = {};
+	surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_DIRECTFB_SURFACE_CREATE_INFO_EXT;
+	surfaceCreateInfo.dfb = dfb;
+	surfaceCreateInfo.surface = window;
+	err = vkCreateDirectFBSurfaceEXT(instance, &surfaceCreateInfo, nullptr, &surface);
+#elif defined(VK_USE_PLATFORM_WAYLAND_KHR)
+	VkWaylandSurfaceCreateInfoKHR surfaceCreateInfo = {};
+	surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR;
+	surfaceCreateInfo.display = display;
+	surfaceCreateInfo.surface = window;
+	err = vkCreateWaylandSurfaceKHR(instance, &surfaceCreateInfo, nullptr, &surface);
+#elif defined(VK_USE_PLATFORM_XCB_KHR)
+	VkXcbSurfaceCreateInfoKHR surfaceCreateInfo = {};
+	surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR;
+	surfaceCreateInfo.connection = connection;
+	surfaceCreateInfo.window = window;
+	err = vkCreateXcbSurfaceKHR(instance, &surfaceCreateInfo, nullptr, &surface);
+#elif defined(VK_USE_PLATFORM_HEADLESS_EXT)
+	VkHeadlessSurfaceCreateInfoEXT surfaceCreateInfo = {};
+	surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_HEADLESS_SURFACE_CREATE_INFO_EXT;
+	PFN_vkCreateHeadlessSurfaceEXT fpCreateHeadlessSurfaceEXT = (PFN_vkCreateHeadlessSurfaceEXT)vkGetInstanceProcAddr(instance, "vkCreateHeadlessSurfaceEXT");
+	if (!fpCreateHeadlessSurfaceEXT){
+		vks::tools::exitFatal("Could not fetch function pointer for the headless extension!", -1);
+	}
+	err = fpCreateHeadlessSurfaceEXT(instance, &surfaceCreateInfo, nullptr, &surface);
+#endif
+
+	if (err != VK_SUCCESS) {
+		vks::tools::exitFatal("Could not create surface!", err);
+	}
+
+	// Get available queue family properties
+	uint32_t queueCount;
+	vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueCount, NULL);
+	assert(queueCount >= 1);
+
+	std::vector<VkQueueFamilyProperties> queueProps(queueCount);
+	vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueCount, queueProps.data());
+
+	// Iterate over each queue to learn whether it supports presenting:
+	// Find a queue with present support
+	// Will be used to present the swap chain images to the windowing system
+	std::vector<VkBool32> supportsPresent(queueCount);
+	for (uint32_t i = 0; i < queueCount; i++) 
+	{
+		fpGetPhysicalDeviceSurfaceSupportKHR(physicalDevice, i, surface, &supportsPresent[i]);
+	}
+
+	// Search for a graphics and a present queue in the array of queue
+	// families, try to find one that supports both
+	uint32_t graphicsQueueNodeIndex = UINT32_MAX;
+	uint32_t presentQueueNodeIndex = UINT32_MAX;
+	for (uint32_t i = 0; i < queueCount; i++) 
+	{
+		if ((queueProps[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0) 
+		{
+			if (graphicsQueueNodeIndex == UINT32_MAX) 
+			{
+				graphicsQueueNodeIndex = i;
+			}
+
+			if (supportsPresent[i] == VK_TRUE) 
+			{
+				graphicsQueueNodeIndex = i;
+				presentQueueNodeIndex = i;
+				break;
+			}
+		}
+	}
+	if (presentQueueNodeIndex == UINT32_MAX) 
+	{	
+		// If there's no queue that supports both present and graphics
+		// try to find a separate present queue
+		for (uint32_t i = 0; i < queueCount; ++i) 
+		{
+			if (supportsPresent[i] == VK_TRUE) 
+			{
+				presentQueueNodeIndex = i;
+				break;
+			}
+		}
+	}
+
+	// Exit if either a graphics or a presenting queue hasn't been found
+	if (graphicsQueueNodeIndex == UINT32_MAX || presentQueueNodeIndex == UINT32_MAX) 
+	{
+		vks::tools::exitFatal("Could not find a graphics and/or presenting queue!", -1);
+	}
+
+	// todo : Add support for separate graphics and presenting queue
+	if (graphicsQueueNodeIndex != presentQueueNodeIndex) 
+	{
+		vks::tools::exitFatal("Separate graphics and presenting queues are not supported yet!", -1);
+	}
+
+	queueNodeIndex = graphicsQueueNodeIndex;
+
+	// Get list of supported surface formats
+	uint32_t formatCount;
+	VK_CHECK_RESULT(fpGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface, &formatCount, NULL));
+	assert(formatCount > 0);
+
+	std::vector<VkSurfaceFormatKHR> surfaceFormats(formatCount);
+	VK_CHECK_RESULT(fpGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface, &formatCount, surfaceFormats.data()));
+
+	// If the surface format list only includes one entry with VK_FORMAT_UNDEFINED,
+	// there is no preferred format, so we assume VK_FORMAT_B8G8R8A8_UNORM
+	if ((formatCount == 1) && (surfaceFormats[0].format == VK_FORMAT_UNDEFINED))
+	{
+		colorFormat = VK_FORMAT_B8G8R8A8_UNORM;
+		colorSpace = surfaceFormats[0].colorSpace;
+	}
+	else
+	{
+		// iterate over the list of available surface format and
+		// check for the presence of VK_FORMAT_B8G8R8A8_UNORM
+		bool found_B8G8R8A8_UNORM = false;
+		for (auto&& surfaceFormat : surfaceFormats)
+		{
+			if (surfaceFormat.format == VK_FORMAT_B8G8R8A8_UNORM)
+			{
+				colorFormat = surfaceFormat.format;
+				colorSpace = surfaceFormat.colorSpace;
+				found_B8G8R8A8_UNORM = true;
+				break;
+			}
+		}
+
+		// in case VK_FORMAT_B8G8R8A8_UNORM is not available
+		// select the first available color format
+		if (!found_B8G8R8A8_UNORM)
+		{
+			colorFormat = surfaceFormats[0].format;
+			colorSpace = surfaceFormats[0].colorSpace;
+		}
+	}
+
+}
+
+/**
+* Set instance, physical and logical device to use for the swapchain and get all required function pointers
+* 
+* @param instance Vulkan instance to use
+* @param physicalDevice Physical device used to query properties and formats relevant to the swapchain
+* @param device Logical representation of the device to create the swapchain for
+*
+*/
+void VulkanSwapChain::connect(VkInstance instance, VkPhysicalDevice physicalDevice, VkDevice device)
+{
+	this->instance = instance;
+	this->physicalDevice = physicalDevice;
+	this->device = device;
+	fpGetPhysicalDeviceSurfaceSupportKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfaceSupportKHR>(vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceSurfaceSupportKHR"));
+	fpGetPhysicalDeviceSurfaceCapabilitiesKHR =  reinterpret_cast<PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR>(vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceSurfaceCapabilitiesKHR"));
+	fpGetPhysicalDeviceSurfaceFormatsKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfaceFormatsKHR>(vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceSurfaceFormatsKHR"));
+	fpGetPhysicalDeviceSurfacePresentModesKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfacePresentModesKHR>(vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceSurfacePresentModesKHR"));
+
+	fpCreateSwapchainKHR = reinterpret_cast<PFN_vkCreateSwapchainKHR>(vkGetDeviceProcAddr(device, "vkCreateSwapchainKHR"));
+	fpDestroySwapchainKHR = reinterpret_cast<PFN_vkDestroySwapchainKHR>(vkGetDeviceProcAddr(device, "vkDestroySwapchainKHR"));
+	fpGetSwapchainImagesKHR = reinterpret_cast<PFN_vkGetSwapchainImagesKHR>(vkGetDeviceProcAddr(device, "vkGetSwapchainImagesKHR"));
+	fpAcquireNextImageKHR = reinterpret_cast<PFN_vkAcquireNextImageKHR>(vkGetDeviceProcAddr(device, "vkAcquireNextImageKHR"));
+	fpQueuePresentKHR = reinterpret_cast<PFN_vkQueuePresentKHR>(vkGetDeviceProcAddr(device, "vkQueuePresentKHR"));
+}
+
+/** 
+* Create the swapchain and get its images with given width and height
+* 
+* @param width Pointer to the width of the swapchain (may be adjusted to fit the requirements of the swapchain)
+* @param height Pointer to the height of the swapchain (may be adjusted to fit the requirements of the swapchain)
+* @param vsync (Optional) Can be used to force vsync-ed rendering (by using VK_PRESENT_MODE_FIFO_KHR as presentation mode)
+*/
+void VulkanSwapChain::create(uint32_t *width, uint32_t *height, bool vsync)
+{
+	// Store the current swap chain handle so we can use it later on to ease up recreation
+	VkSwapchainKHR oldSwapchain = swapChain;
+
+	// Get physical device surface properties and formats
+	VkSurfaceCapabilitiesKHR surfCaps;
+	VK_CHECK_RESULT(fpGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice, surface, &surfCaps));
+
+	// Get available present modes
+	uint32_t presentModeCount;
+	VK_CHECK_RESULT(fpGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface, &presentModeCount, NULL));
+	assert(presentModeCount > 0);
+
+	std::vector<VkPresentModeKHR> presentModes(presentModeCount);
+	VK_CHECK_RESULT(fpGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface, &presentModeCount, presentModes.data()));
+
+	VkExtent2D swapchainExtent = {};
+	// If width (and height) equals the special value 0xFFFFFFFF, the size of the surface will be set by the swapchain
+	if (surfCaps.currentExtent.width == (uint32_t)-1)
+	{
+		// If the surface size is undefined, the size is set to
+		// the size of the images requested.
+		swapchainExtent.width = *width;
+		swapchainExtent.height = *height;
+	}
+	else
+	{
+		// If the surface size is defined, the swap chain size must match
+		swapchainExtent = surfCaps.currentExtent;
+		*width = surfCaps.currentExtent.width;
+		*height = surfCaps.currentExtent.height;
+	}
+
+
+	// Select a present mode for the swapchain
+
+	// The VK_PRESENT_MODE_FIFO_KHR mode must always be present as per spec
+	// This mode waits for the vertical blank ("v-sync")
+	VkPresentModeKHR swapchainPresentMode = VK_PRESENT_MODE_FIFO_KHR;
+
+	// If v-sync is not requested, try to find a mailbox mode
+	// It's the lowest latency non-tearing present mode available
+	if (!vsync)
+	{
+		for (size_t i = 0; i < presentModeCount; i++)
+		{
+			if (presentModes[i] == VK_PRESENT_MODE_MAILBOX_KHR)
+			{
+				swapchainPresentMode = VK_PRESENT_MODE_MAILBOX_KHR;
+				break;
+			}
+			if (presentModes[i] == VK_PRESENT_MODE_IMMEDIATE_KHR)
+			{
+				swapchainPresentMode = VK_PRESENT_MODE_IMMEDIATE_KHR;
+			}
+		}
+	}
+
+	// Determine the number of images
+	uint32_t desiredNumberOfSwapchainImages = surfCaps.minImageCount + 1;
+	if ((surfCaps.maxImageCount > 0) && (desiredNumberOfSwapchainImages > surfCaps.maxImageCount))
+	{
+		desiredNumberOfSwapchainImages = surfCaps.maxImageCount;
+	}
+
+	// Find the transformation of the surface
+	VkSurfaceTransformFlagsKHR preTransform;
+	if (surfCaps.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR)
+	{
+		// We prefer a non-rotated transform
+		preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
+	}
+	else
+	{
+		preTransform = surfCaps.currentTransform;
+	}
+
+	// Find a supported composite alpha format (not all devices support alpha opaque)
+	VkCompositeAlphaFlagBitsKHR compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
+	// Simply select the first composite alpha format available
+	std::vector<VkCompositeAlphaFlagBitsKHR> compositeAlphaFlags = {
+		VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
+		VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR,
+		VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR,
+		VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR,
+	};
+	for (auto& compositeAlphaFlag : compositeAlphaFlags) {
+		if (surfCaps.supportedCompositeAlpha & compositeAlphaFlag) {
+			compositeAlpha = compositeAlphaFlag;
+			break;
+		};
+	}
+
+	VkSwapchainCreateInfoKHR swapchainCI = {};
+	swapchainCI.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
+	swapchainCI.surface = surface;
+	swapchainCI.minImageCount = desiredNumberOfSwapchainImages;
+	swapchainCI.imageFormat = colorFormat;
+	swapchainCI.imageColorSpace = colorSpace;
+	swapchainCI.imageExtent = { swapchainExtent.width, swapchainExtent.height };
+	swapchainCI.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
+	swapchainCI.preTransform = (VkSurfaceTransformFlagBitsKHR)preTransform;
+	swapchainCI.imageArrayLayers = 1;
+	swapchainCI.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
+	swapchainCI.queueFamilyIndexCount = 0;
+	swapchainCI.presentMode = swapchainPresentMode;
+	// Setting oldSwapChain to the saved handle of the previous swapchain aids in resource reuse and makes sure that we can still present already acquired images
+	swapchainCI.oldSwapchain = oldSwapchain;
+	// Setting clipped to VK_TRUE allows the implementation to discard rendering outside of the surface area
+	swapchainCI.clipped = VK_TRUE;
+	swapchainCI.compositeAlpha = compositeAlpha;
+
+	// Enable transfer source on swap chain images if supported
+	if (surfCaps.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_SRC_BIT) {
+		swapchainCI.imageUsage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
+	}
+
+	// Enable transfer destination on swap chain images if supported
+	if (surfCaps.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_DST_BIT) {
+		swapchainCI.imageUsage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
+	}
+
+	VK_CHECK_RESULT(fpCreateSwapchainKHR(device, &swapchainCI, nullptr, &swapChain));
+
+	// If an existing swap chain is re-created, destroy the old swap chain
+	// This also cleans up all the presentable images
+	if (oldSwapchain != VK_NULL_HANDLE) 
+	{ 
+		for (uint32_t i = 0; i < imageCount; i++)
+		{
+			vkDestroyImageView(device, buffers[i].view, nullptr);
+		}
+		fpDestroySwapchainKHR(device, oldSwapchain, nullptr);
+	}
+	VK_CHECK_RESULT(fpGetSwapchainImagesKHR(device, swapChain, &imageCount, NULL));
+
+	// Get the swap chain images
+	images.resize(imageCount);
+	VK_CHECK_RESULT(fpGetSwapchainImagesKHR(device, swapChain, &imageCount, images.data()));
+
+	// Get the swap chain buffers containing the image and imageview
+	buffers.resize(imageCount);
+	for (uint32_t i = 0; i < imageCount; i++)
+	{
+		VkImageViewCreateInfo colorAttachmentView = {};
+		colorAttachmentView.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+		colorAttachmentView.pNext = NULL;
+		colorAttachmentView.format = colorFormat;
+		colorAttachmentView.components = {
+			VK_COMPONENT_SWIZZLE_R,
+			VK_COMPONENT_SWIZZLE_G,
+			VK_COMPONENT_SWIZZLE_B,
+			VK_COMPONENT_SWIZZLE_A
+		};
+		colorAttachmentView.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+		colorAttachmentView.subresourceRange.baseMipLevel = 0;
+		colorAttachmentView.subresourceRange.levelCount = 1;
+		colorAttachmentView.subresourceRange.baseArrayLayer = 0;
+		colorAttachmentView.subresourceRange.layerCount = 1;
+		colorAttachmentView.viewType = VK_IMAGE_VIEW_TYPE_2D;
+		colorAttachmentView.flags = 0;
+
+		buffers[i].image = images[i];
+
+		colorAttachmentView.image = buffers[i].image;
+
+		VK_CHECK_RESULT(vkCreateImageView(device, &colorAttachmentView, nullptr, &buffers[i].view));
+	}
+}
+
+/** 
+* Acquires the next image in the swap chain
+*
+* @param presentCompleteSemaphore (Optional) Semaphore that is signaled when the image is ready for use
+* @param imageIndex Pointer to the image index that will be increased if the next image could be acquired
+*
+* @note The function will always wait until the next image has been acquired by setting timeout to UINT64_MAX
+*
+* @return VkResult of the image acquisition
+*/
+VkResult VulkanSwapChain::acquireNextImage(VkSemaphore presentCompleteSemaphore, uint32_t *imageIndex)
+{
+	// By setting timeout to UINT64_MAX we will always wait until the next image has been acquired or an actual error is thrown
+	// With that we don't have to handle VK_NOT_READY
+	return fpAcquireNextImageKHR(device, swapChain, UINT64_MAX, presentCompleteSemaphore, (VkFence)nullptr, imageIndex);
+}
+
+/**
+* Queue an image for presentation
+*
+* @param queue Presentation queue for presenting the image
+* @param imageIndex Index of the swapchain image to queue for presentation
+* @param waitSemaphore (Optional) Semaphore that is waited on before the image is presented (only used if != VK_NULL_HANDLE)
+*
+* @return VkResult of the queue presentation
+*/
+VkResult VulkanSwapChain::queuePresent(VkQueue queue, uint32_t imageIndex, VkSemaphore waitSemaphore)
+{
+	VkPresentInfoKHR presentInfo = {};
+	presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
+	presentInfo.pNext = NULL;
+	presentInfo.swapchainCount = 1;
+	presentInfo.pSwapchains = &swapChain;
+	presentInfo.pImageIndices = &imageIndex;
+	// Check if a wait semaphore has been specified to wait for before presenting the image
+	if (waitSemaphore != VK_NULL_HANDLE)
+	{
+		presentInfo.pWaitSemaphores = &waitSemaphore;
+		presentInfo.waitSemaphoreCount = 1;
+	}
+	return fpQueuePresentKHR(queue, &presentInfo);
+}
+
+
+/**
+* Destroy and free Vulkan resources used for the swapchain
+*/
+void VulkanSwapChain::cleanup()
+{
+	if (swapChain != VK_NULL_HANDLE)
+	{
+		for (uint32_t i = 0; i < imageCount; i++)
+		{
+			vkDestroyImageView(device, buffers[i].view, nullptr);
+		}
+	}
+	if (surface != VK_NULL_HANDLE)
+	{
+		fpDestroySwapchainKHR(device, swapChain, nullptr);
+		vkDestroySurfaceKHR(instance, surface, nullptr);
+	}
+	surface = VK_NULL_HANDLE;
+	swapChain = VK_NULL_HANDLE;
+}
+
+#if defined(_DIRECT2DISPLAY)
+/**
+* Create direct to display surface
+*/	
+void VulkanSwapChain::createDirect2DisplaySurface(uint32_t width, uint32_t height)
+{
+	uint32_t displayPropertyCount;
+		
+	// Get display property
+	vkGetPhysicalDeviceDisplayPropertiesKHR(physicalDevice, &displayPropertyCount, NULL);
+	VkDisplayPropertiesKHR* pDisplayProperties = new VkDisplayPropertiesKHR[displayPropertyCount];
+	vkGetPhysicalDeviceDisplayPropertiesKHR(physicalDevice, &displayPropertyCount, pDisplayProperties);
+
+	// Get plane property
+	uint32_t planePropertyCount;
+	vkGetPhysicalDeviceDisplayPlanePropertiesKHR(physicalDevice, &planePropertyCount, NULL);
+	VkDisplayPlanePropertiesKHR* pPlaneProperties = new VkDisplayPlanePropertiesKHR[planePropertyCount];
+	vkGetPhysicalDeviceDisplayPlanePropertiesKHR(physicalDevice, &planePropertyCount, pPlaneProperties);
+
+	VkDisplayKHR display = VK_NULL_HANDLE;
+	VkDisplayModeKHR displayMode;
+	VkDisplayModePropertiesKHR* pModeProperties;
+	bool foundMode = false;
+
+	for(uint32_t i = 0; i < displayPropertyCount;++i)
+	{
+		display = pDisplayProperties[i].display;
+		uint32_t modeCount;
+		vkGetDisplayModePropertiesKHR(physicalDevice, display, &modeCount, NULL);
+		pModeProperties = new VkDisplayModePropertiesKHR[modeCount];
+		vkGetDisplayModePropertiesKHR(physicalDevice, display, &modeCount, pModeProperties);
+
+		for (uint32_t j = 0; j < modeCount; ++j)
+		{
+			const VkDisplayModePropertiesKHR* mode = &pModeProperties[j];
+
+			if (mode->parameters.visibleRegion.width == width && mode->parameters.visibleRegion.height == height)
+			{
+				displayMode = mode->displayMode;
+				foundMode = true;
+				break;
+			}
+		}
+		if (foundMode)
+		{
+			break;
+		}
+		delete [] pModeProperties;
+	}
+
+	if(!foundMode)
+	{
+		vks::tools::exitFatal("Can't find a display and a display mode!", -1);
+		return;
+	}
+
+	// Search for a best plane we can use
+	uint32_t bestPlaneIndex = UINT32_MAX;
+	VkDisplayKHR* pDisplays = NULL;
+	for(uint32_t i = 0; i < planePropertyCount; i++)
+	{
+		uint32_t planeIndex=i;
+		uint32_t displayCount;
+		vkGetDisplayPlaneSupportedDisplaysKHR(physicalDevice, planeIndex, &displayCount, NULL);
+		if (pDisplays)
+		{
+			delete [] pDisplays;
+		}
+		pDisplays = new VkDisplayKHR[displayCount];
+		vkGetDisplayPlaneSupportedDisplaysKHR(physicalDevice, planeIndex, &displayCount, pDisplays);
+
+		// Find a display that matches the current plane
+		bestPlaneIndex = UINT32_MAX;
+		for(uint32_t j = 0; j < displayCount; j++)
+		{
+			if(display == pDisplays[j])
+			{
+				bestPlaneIndex = i;
+				break;
+			}
+		}
+		if(bestPlaneIndex != UINT32_MAX)
+		{
+			break;
+		}
+	}
+
+	if(bestPlaneIndex == UINT32_MAX)
+	{
+		vks::tools::exitFatal("Can't find a plane for displaying!", -1);
+		return;
+	}
+
+	VkDisplayPlaneCapabilitiesKHR planeCap;
+	vkGetDisplayPlaneCapabilitiesKHR(physicalDevice, displayMode, bestPlaneIndex, &planeCap);
+	VkDisplayPlaneAlphaFlagBitsKHR alphaMode = (VkDisplayPlaneAlphaFlagBitsKHR)0;
+
+	if (planeCap.supportedAlpha & VK_DISPLAY_PLANE_ALPHA_PER_PIXEL_PREMULTIPLIED_BIT_KHR)
+	{
+		alphaMode = VK_DISPLAY_PLANE_ALPHA_PER_PIXEL_PREMULTIPLIED_BIT_KHR;
+	}
+	else if (planeCap.supportedAlpha & VK_DISPLAY_PLANE_ALPHA_PER_PIXEL_BIT_KHR)
+	{
+		alphaMode = VK_DISPLAY_PLANE_ALPHA_PER_PIXEL_BIT_KHR;
+	}
+	else if (planeCap.supportedAlpha & VK_DISPLAY_PLANE_ALPHA_GLOBAL_BIT_KHR)
+	{
+		alphaMode = VK_DISPLAY_PLANE_ALPHA_GLOBAL_BIT_KHR;
+	}
+	else if (planeCap.supportedAlpha & VK_DISPLAY_PLANE_ALPHA_OPAQUE_BIT_KHR)
+	{
+		alphaMode = VK_DISPLAY_PLANE_ALPHA_OPAQUE_BIT_KHR;
+	}
+
+	VkDisplaySurfaceCreateInfoKHR surfaceInfo{};
+	surfaceInfo.sType = VK_STRUCTURE_TYPE_DISPLAY_SURFACE_CREATE_INFO_KHR;
+	surfaceInfo.pNext = NULL;
+	surfaceInfo.flags = 0;
+	surfaceInfo.displayMode = displayMode;
+	surfaceInfo.planeIndex = bestPlaneIndex;
+	surfaceInfo.planeStackIndex = pPlaneProperties[bestPlaneIndex].currentStackIndex;
+	surfaceInfo.transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
+	surfaceInfo.globalAlpha = 1.0;
+	surfaceInfo.alphaMode = alphaMode;
+	surfaceInfo.imageExtent.width = width;
+	surfaceInfo.imageExtent.height = height;
+
+	VkResult result = vkCreateDisplayPlaneSurfaceKHR(instance, &surfaceInfo, NULL, &surface);
+	if (result !=VK_SUCCESS) {
+		vks::tools::exitFatal("Failed to create surface!", result);
+	}
+
+	delete[] pDisplays;
+	delete[] pModeProperties;
+	delete[] pDisplayProperties;
+	delete[] pPlaneProperties;
+}
+#endif 
diff --git a/external/Vulkan/base/VulkanSwapChain.h b/external/Vulkan/base/VulkanSwapChain.h
new file mode 100644
index 0000000000000000000000000000000000000000..686bf498eb508c293caabcea426509a143939eca
--- /dev/null
+++ b/external/Vulkan/base/VulkanSwapChain.h
@@ -0,0 +1,80 @@
+/*
+* Class wrapping access to the swap chain
+* 
+* A swap chain is a collection of framebuffers used for rendering and presentation to the windowing system
+*
+* Copyright (C) 2016-2017 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#pragma once
+
+#include <stdlib.h>
+#include <string>
+#include <assert.h>
+#include <stdio.h>
+#include <vector>
+
+#include <vulkan/vulkan.h>
+#include "VulkanTools.h"
+
+#ifdef __ANDROID__
+#include "VulkanAndroid.h"
+#endif
+
+typedef struct _SwapChainBuffers {
+	VkImage image;
+	VkImageView view;
+} SwapChainBuffer;
+
+class VulkanSwapChain
+{
+private: 
+	VkInstance instance;
+	VkDevice device;
+	VkPhysicalDevice physicalDevice;
+	VkSurfaceKHR surface;
+	// Function pointers
+	PFN_vkGetPhysicalDeviceSurfaceSupportKHR fpGetPhysicalDeviceSurfaceSupportKHR;
+	PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR fpGetPhysicalDeviceSurfaceCapabilitiesKHR; 
+	PFN_vkGetPhysicalDeviceSurfaceFormatsKHR fpGetPhysicalDeviceSurfaceFormatsKHR;
+	PFN_vkGetPhysicalDeviceSurfacePresentModesKHR fpGetPhysicalDeviceSurfacePresentModesKHR;
+	PFN_vkCreateSwapchainKHR fpCreateSwapchainKHR;
+	PFN_vkDestroySwapchainKHR fpDestroySwapchainKHR;
+	PFN_vkGetSwapchainImagesKHR fpGetSwapchainImagesKHR;
+	PFN_vkAcquireNextImageKHR fpAcquireNextImageKHR;
+	PFN_vkQueuePresentKHR fpQueuePresentKHR;
+public:
+	VkFormat colorFormat;
+	VkColorSpaceKHR colorSpace;
+	VkSwapchainKHR swapChain = VK_NULL_HANDLE;	
+	uint32_t imageCount;
+	std::vector<VkImage> images;
+	std::vector<SwapChainBuffer> buffers;
+	uint32_t queueNodeIndex = UINT32_MAX;
+
+#if defined(VK_USE_PLATFORM_WIN32_KHR)
+	void initSurface(void* platformHandle, void* platformWindow);
+#elif defined(VK_USE_PLATFORM_ANDROID_KHR)
+	void initSurface(ANativeWindow* window);
+#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT)
+	void initSurface(IDirectFB* dfb, IDirectFBSurface* window);
+#elif defined(VK_USE_PLATFORM_WAYLAND_KHR)
+	void initSurface(wl_display* display, wl_surface* window);
+#elif defined(VK_USE_PLATFORM_XCB_KHR)
+	void initSurface(xcb_connection_t* connection, xcb_window_t window);
+#elif (defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_MACOS_MVK))
+	void initSurface(void* view);
+#elif (defined(_DIRECT2DISPLAY) || defined(VK_USE_PLATFORM_HEADLESS_EXT))
+	void initSurface(uint32_t width, uint32_t height);
+#if defined(_DIRECT2DISPLAY)
+	void createDirect2DisplaySurface(uint32_t width, uint32_t height);
+#endif
+#endif
+	void connect(VkInstance instance, VkPhysicalDevice physicalDevice, VkDevice device);
+	void create(uint32_t* width, uint32_t* height, bool vsync = false);
+	VkResult acquireNextImage(VkSemaphore presentCompleteSemaphore, uint32_t* imageIndex);
+	VkResult queuePresent(VkQueue queue, uint32_t imageIndex, VkSemaphore waitSemaphore = VK_NULL_HANDLE);
+	void cleanup();
+};
diff --git a/external/Vulkan/base/VulkanTexture.cpp b/external/Vulkan/base/VulkanTexture.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b136b961e44f792df030a586f366bced870a1c7f
--- /dev/null
+++ b/external/Vulkan/base/VulkanTexture.cpp
@@ -0,0 +1,873 @@
+/*
+* Vulkan texture loader
+*
+* Copyright(C) by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license(MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include <VulkanTexture.h>
+
+namespace vks
+{
+	void Texture::updateDescriptor()
+	{
+		descriptor.sampler = sampler;
+		descriptor.imageView = view;
+		descriptor.imageLayout = imageLayout;
+	}
+
+	void Texture::destroy()
+	{
+		vkDestroyImageView(device->logicalDevice, view, nullptr);
+		vkDestroyImage(device->logicalDevice, image, nullptr);
+		if (sampler)
+		{
+			vkDestroySampler(device->logicalDevice, sampler, nullptr);
+		}
+		vkFreeMemory(device->logicalDevice, deviceMemory, nullptr);
+	}
+
+	ktxResult Texture::loadKTXFile(std::string filename, ktxTexture **target)
+	{
+		ktxResult result = KTX_SUCCESS;
+#if defined(__ANDROID__)
+		AAsset* asset = AAssetManager_open(androidApp->activity->assetManager, filename.c_str(), AASSET_MODE_STREAMING);
+		if (!asset) {
+			vks::tools::exitFatal("Could not load texture from " + filename + "\n\nThe file may be part of the additional asset pack.\n\nRun \"download_assets.py\" in the repository root to download the latest version.", -1);
+		}
+		size_t size = AAsset_getLength(asset);
+		assert(size > 0);
+		ktx_uint8_t *textureData = new ktx_uint8_t[size];
+		AAsset_read(asset, textureData, size);
+		AAsset_close(asset);
+		result = ktxTexture_CreateFromMemory(textureData, size, KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, target);
+		delete[] textureData;
+#else
+		if (!vks::tools::fileExists(filename)) {
+			vks::tools::exitFatal("Could not load texture from " + filename + "\n\nThe file may be part of the additional asset pack.\n\nRun \"download_assets.py\" in the repository root to download the latest version.", -1);
+		}
+		result = ktxTexture_CreateFromNamedFile(filename.c_str(), KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, target);			
+#endif		
+		return result;
+	}
+
+	/**
+	* Load a 2D texture including all mip levels
+	*
+	* @param filename File to load (supports .ktx)
+	* @param format Vulkan format of the image data stored in the file
+	* @param device Vulkan device to create the texture on
+	* @param copyQueue Queue used for the texture staging copy commands (must support transfer)
+	* @param (Optional) imageUsageFlags Usage flags for the texture's image (defaults to VK_IMAGE_USAGE_SAMPLED_BIT)
+	* @param (Optional) imageLayout Usage layout for the texture (defaults VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
+	* @param (Optional) forceLinear Force linear tiling (not advised, defaults to false)
+	*
+	*/
+	void Texture2D::loadFromFile(std::string filename, VkFormat format, vks::VulkanDevice *device, VkQueue copyQueue, VkImageUsageFlags imageUsageFlags, VkImageLayout imageLayout, bool forceLinear)
+	{
+		ktxTexture* ktxTexture;
+		ktxResult result = loadKTXFile(filename, &ktxTexture);
+		assert(result == KTX_SUCCESS);
+
+		this->device = device;
+		width = ktxTexture->baseWidth;
+		height = ktxTexture->baseHeight;
+		mipLevels = ktxTexture->numLevels;
+
+		ktx_uint8_t *ktxTextureData = ktxTexture_GetData(ktxTexture);
+		ktx_size_t ktxTextureSize = ktxTexture_GetSize(ktxTexture);
+
+		// Get device properties for the requested texture format
+		VkFormatProperties formatProperties;
+		vkGetPhysicalDeviceFormatProperties(device->physicalDevice, format, &formatProperties);
+
+		// Only use linear tiling if requested (and supported by the device)
+		// Support for linear tiling is mostly limited, so prefer to use
+		// optimal tiling instead
+		// On most implementations linear tiling will only support a very
+		// limited amount of formats and features (mip maps, cubemaps, arrays, etc.)
+		VkBool32 useStaging = !forceLinear;
+
+		VkMemoryAllocateInfo memAllocInfo = vks::initializers::memoryAllocateInfo();
+		VkMemoryRequirements memReqs;
+
+		// Use a separate command buffer for texture loading
+		VkCommandBuffer copyCmd = device->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+
+		if (useStaging)
+		{
+			// Create a host-visible staging buffer that contains the raw image data
+			VkBuffer stagingBuffer;
+			VkDeviceMemory stagingMemory;
+
+			VkBufferCreateInfo bufferCreateInfo = vks::initializers::bufferCreateInfo();
+			bufferCreateInfo.size = ktxTextureSize;
+			// This buffer is used as a transfer source for the buffer copy
+			bufferCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
+			bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+
+			VK_CHECK_RESULT(vkCreateBuffer(device->logicalDevice, &bufferCreateInfo, nullptr, &stagingBuffer));
+
+			// Get memory requirements for the staging buffer (alignment, memory type bits)
+			vkGetBufferMemoryRequirements(device->logicalDevice, stagingBuffer, &memReqs);
+
+			memAllocInfo.allocationSize = memReqs.size;
+			// Get memory type index for a host visible buffer
+			memAllocInfo.memoryTypeIndex = device->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
+
+			VK_CHECK_RESULT(vkAllocateMemory(device->logicalDevice, &memAllocInfo, nullptr, &stagingMemory));
+			VK_CHECK_RESULT(vkBindBufferMemory(device->logicalDevice, stagingBuffer, stagingMemory, 0));
+
+			// Copy texture data into staging buffer
+			uint8_t *data;
+			VK_CHECK_RESULT(vkMapMemory(device->logicalDevice, stagingMemory, 0, memReqs.size, 0, (void **)&data));
+			memcpy(data, ktxTextureData, ktxTextureSize);
+			vkUnmapMemory(device->logicalDevice, stagingMemory);
+
+			// Setup buffer copy regions for each mip level
+			std::vector<VkBufferImageCopy> bufferCopyRegions;
+
+			for (uint32_t i = 0; i < mipLevels; i++)
+			{
+				ktx_size_t offset;
+				KTX_error_code result = ktxTexture_GetImageOffset(ktxTexture, i, 0, 0, &offset);
+				assert(result == KTX_SUCCESS);
+
+				VkBufferImageCopy bufferCopyRegion = {};
+				bufferCopyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+				bufferCopyRegion.imageSubresource.mipLevel = i;
+				bufferCopyRegion.imageSubresource.baseArrayLayer = 0;
+				bufferCopyRegion.imageSubresource.layerCount = 1;
+				bufferCopyRegion.imageExtent.width = std::max(1u, ktxTexture->baseWidth >> i);
+				bufferCopyRegion.imageExtent.height = std::max(1u, ktxTexture->baseHeight >> i);
+				bufferCopyRegion.imageExtent.depth = 1;
+				bufferCopyRegion.bufferOffset = offset;
+
+				bufferCopyRegions.push_back(bufferCopyRegion);
+			}
+
+			// Create optimal tiled target image
+			VkImageCreateInfo imageCreateInfo = vks::initializers::imageCreateInfo();
+			imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
+			imageCreateInfo.format = format;
+			imageCreateInfo.mipLevels = mipLevels;
+			imageCreateInfo.arrayLayers = 1;
+			imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
+			imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
+			imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+			imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+			imageCreateInfo.extent = { width, height, 1 };
+			imageCreateInfo.usage = imageUsageFlags;
+			// Ensure that the TRANSFER_DST bit is set for staging
+			if (!(imageCreateInfo.usage & VK_IMAGE_USAGE_TRANSFER_DST_BIT))
+			{
+				imageCreateInfo.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
+			}
+			VK_CHECK_RESULT(vkCreateImage(device->logicalDevice, &imageCreateInfo, nullptr, &image));
+
+			vkGetImageMemoryRequirements(device->logicalDevice, image, &memReqs);
+
+			memAllocInfo.allocationSize = memReqs.size;
+
+			memAllocInfo.memoryTypeIndex = device->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+			VK_CHECK_RESULT(vkAllocateMemory(device->logicalDevice, &memAllocInfo, nullptr, &deviceMemory));
+			VK_CHECK_RESULT(vkBindImageMemory(device->logicalDevice, image, deviceMemory, 0));
+
+			VkImageSubresourceRange subresourceRange = {};
+			subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+			subresourceRange.baseMipLevel = 0;
+			subresourceRange.levelCount = mipLevels;
+			subresourceRange.layerCount = 1;
+
+			// Image barrier for optimal image (target)
+			// Optimal image will be used as destination for the copy
+			vks::tools::setImageLayout(
+				copyCmd,
+				image,
+				VK_IMAGE_LAYOUT_UNDEFINED,
+				VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+				subresourceRange);
+
+			// Copy mip levels from staging buffer
+			vkCmdCopyBufferToImage(
+				copyCmd,
+				stagingBuffer,
+				image,
+				VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+				static_cast<uint32_t>(bufferCopyRegions.size()),
+				bufferCopyRegions.data()
+			);
+
+			// Change texture image layout to shader read after all mip levels have been copied
+			this->imageLayout = imageLayout;
+			vks::tools::setImageLayout(
+				copyCmd,
+				image,
+				VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+				imageLayout,
+				subresourceRange);
+
+			device->flushCommandBuffer(copyCmd, copyQueue);
+
+			// Clean up staging resources
+			vkFreeMemory(device->logicalDevice, stagingMemory, nullptr);
+			vkDestroyBuffer(device->logicalDevice, stagingBuffer, nullptr);
+		}
+		else
+		{
+			// Prefer using optimal tiling, as linear tiling 
+			// may support only a small set of features 
+			// depending on implementation (e.g. no mip maps, only one layer, etc.)
+
+			// Check if this support is supported for linear tiling
+			assert(formatProperties.linearTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT);
+
+			VkImage mappableImage;
+			VkDeviceMemory mappableMemory;
+
+			VkImageCreateInfo imageCreateInfo = vks::initializers::imageCreateInfo();
+			imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
+			imageCreateInfo.format = format;
+			imageCreateInfo.extent = { width, height, 1 };
+			imageCreateInfo.mipLevels = 1;
+			imageCreateInfo.arrayLayers = 1;
+			imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
+			imageCreateInfo.tiling = VK_IMAGE_TILING_LINEAR;
+			imageCreateInfo.usage = imageUsageFlags;
+			imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+			imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+
+			// Load mip map level 0 to linear tiling image
+			VK_CHECK_RESULT(vkCreateImage(device->logicalDevice, &imageCreateInfo, nullptr, &mappableImage));
+
+			// Get memory requirements for this image 
+			// like size and alignment
+			vkGetImageMemoryRequirements(device->logicalDevice, mappableImage, &memReqs);
+			// Set memory allocation size to required memory size
+			memAllocInfo.allocationSize = memReqs.size;
+
+			// Get memory type that can be mapped to host memory
+			memAllocInfo.memoryTypeIndex = device->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
+
+			// Allocate host memory
+			VK_CHECK_RESULT(vkAllocateMemory(device->logicalDevice, &memAllocInfo, nullptr, &mappableMemory));
+
+			// Bind allocated image for use
+			VK_CHECK_RESULT(vkBindImageMemory(device->logicalDevice, mappableImage, mappableMemory, 0));
+
+			// Get sub resource layout
+			// Mip map count, array layer, etc.
+			VkImageSubresource subRes = {};
+			subRes.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+			subRes.mipLevel = 0;
+
+			VkSubresourceLayout subResLayout;
+			void *data;
+
+			// Get sub resources layout 
+			// Includes row pitch, size offsets, etc.
+			vkGetImageSubresourceLayout(device->logicalDevice, mappableImage, &subRes, &subResLayout);
+
+			// Map image memory
+			VK_CHECK_RESULT(vkMapMemory(device->logicalDevice, mappableMemory, 0, memReqs.size, 0, &data));
+
+			// Copy image data into memory
+			memcpy(data, ktxTextureData, memReqs.size);
+
+			vkUnmapMemory(device->logicalDevice, mappableMemory);
+
+			// Linear tiled images don't need to be staged
+			// and can be directly used as textures
+			image = mappableImage;
+			deviceMemory = mappableMemory;
+			this->imageLayout = imageLayout;
+
+			// Setup image memory barrier
+			vks::tools::setImageLayout(copyCmd, image, VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_LAYOUT_UNDEFINED, imageLayout);
+
+			device->flushCommandBuffer(copyCmd, copyQueue);
+		}
+
+		ktxTexture_Destroy(ktxTexture);
+
+		// Create a default sampler
+		VkSamplerCreateInfo samplerCreateInfo = {};
+		samplerCreateInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
+		samplerCreateInfo.magFilter = VK_FILTER_LINEAR;
+		samplerCreateInfo.minFilter = VK_FILTER_LINEAR;
+		samplerCreateInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+		samplerCreateInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+		samplerCreateInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+		samplerCreateInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+		samplerCreateInfo.mipLodBias = 0.0f;
+		samplerCreateInfo.compareOp = VK_COMPARE_OP_NEVER;
+		samplerCreateInfo.minLod = 0.0f;
+		// Max level-of-detail should match mip level count
+		samplerCreateInfo.maxLod = (useStaging) ? (float)mipLevels : 0.0f;
+		// Only enable anisotropic filtering if enabled on the device
+		samplerCreateInfo.maxAnisotropy = device->enabledFeatures.samplerAnisotropy ? device->properties.limits.maxSamplerAnisotropy : 1.0f;
+		samplerCreateInfo.anisotropyEnable = device->enabledFeatures.samplerAnisotropy;
+		samplerCreateInfo.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
+		VK_CHECK_RESULT(vkCreateSampler(device->logicalDevice, &samplerCreateInfo, nullptr, &sampler));
+
+		// Create image view
+		// Textures are not directly accessed by the shaders and
+		// are abstracted by image views containing additional
+		// information and sub resource ranges
+		VkImageViewCreateInfo viewCreateInfo = {};
+		viewCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+		viewCreateInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
+		viewCreateInfo.format = format;
+		viewCreateInfo.components = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A };
+		viewCreateInfo.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 };
+		// Linear tiling usually won't support mip maps
+		// Only set mip map count if optimal tiling is used
+		viewCreateInfo.subresourceRange.levelCount = (useStaging) ? mipLevels : 1;
+		viewCreateInfo.image = image;
+		VK_CHECK_RESULT(vkCreateImageView(device->logicalDevice, &viewCreateInfo, nullptr, &view));
+
+		// Update descriptor image info member that can be used for setting up descriptor sets
+		updateDescriptor();
+	}
+
+	/**
+	* Creates a 2D texture from a buffer
+	*
+	* @param buffer Buffer containing texture data to upload
+	* @param bufferSize Size of the buffer in machine units
+	* @param width Width of the texture to create
+	* @param height Height of the texture to create
+	* @param format Vulkan format of the image data stored in the file
+	* @param device Vulkan device to create the texture on
+	* @param copyQueue Queue used for the texture staging copy commands (must support transfer)
+	* @param (Optional) filter Texture filtering for the sampler (defaults to VK_FILTER_LINEAR)
+	* @param (Optional) imageUsageFlags Usage flags for the texture's image (defaults to VK_IMAGE_USAGE_SAMPLED_BIT)
+	* @param (Optional) imageLayout Usage layout for the texture (defaults VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
+	*/
+	void Texture2D::fromBuffer(void* buffer, VkDeviceSize bufferSize, VkFormat format, uint32_t texWidth, uint32_t texHeight, vks::VulkanDevice *device, VkQueue copyQueue, VkFilter filter, VkImageUsageFlags imageUsageFlags, VkImageLayout imageLayout)
+	{
+		assert(buffer);
+
+		this->device = device;
+		width = texWidth;
+		height = texHeight;
+		mipLevels = 1;
+
+		VkMemoryAllocateInfo memAllocInfo = vks::initializers::memoryAllocateInfo();
+		VkMemoryRequirements memReqs;
+
+		// Use a separate command buffer for texture loading
+		VkCommandBuffer copyCmd = device->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+
+		// Create a host-visible staging buffer that contains the raw image data
+		VkBuffer stagingBuffer;
+		VkDeviceMemory stagingMemory;
+
+		VkBufferCreateInfo bufferCreateInfo = vks::initializers::bufferCreateInfo();
+		bufferCreateInfo.size = bufferSize;
+		// This buffer is used as a transfer source for the buffer copy
+		bufferCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
+		bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+
+		VK_CHECK_RESULT(vkCreateBuffer(device->logicalDevice, &bufferCreateInfo, nullptr, &stagingBuffer));
+
+		// Get memory requirements for the staging buffer (alignment, memory type bits)
+		vkGetBufferMemoryRequirements(device->logicalDevice, stagingBuffer, &memReqs);
+
+		memAllocInfo.allocationSize = memReqs.size;
+		// Get memory type index for a host visible buffer
+		memAllocInfo.memoryTypeIndex = device->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
+
+		VK_CHECK_RESULT(vkAllocateMemory(device->logicalDevice, &memAllocInfo, nullptr, &stagingMemory));
+		VK_CHECK_RESULT(vkBindBufferMemory(device->logicalDevice, stagingBuffer, stagingMemory, 0));
+
+		// Copy texture data into staging buffer
+		uint8_t *data;
+		VK_CHECK_RESULT(vkMapMemory(device->logicalDevice, stagingMemory, 0, memReqs.size, 0, (void **)&data));
+		memcpy(data, buffer, bufferSize);
+		vkUnmapMemory(device->logicalDevice, stagingMemory);
+
+		VkBufferImageCopy bufferCopyRegion = {};
+		bufferCopyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+		bufferCopyRegion.imageSubresource.mipLevel = 0;
+		bufferCopyRegion.imageSubresource.baseArrayLayer = 0;
+		bufferCopyRegion.imageSubresource.layerCount = 1;
+		bufferCopyRegion.imageExtent.width = width;
+		bufferCopyRegion.imageExtent.height = height;
+		bufferCopyRegion.imageExtent.depth = 1;
+		bufferCopyRegion.bufferOffset = 0;
+
+		// Create optimal tiled target image
+		VkImageCreateInfo imageCreateInfo = vks::initializers::imageCreateInfo();
+		imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
+		imageCreateInfo.format = format;
+		imageCreateInfo.mipLevels = mipLevels;
+		imageCreateInfo.arrayLayers = 1;
+		imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
+		imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
+		imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+		imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+		imageCreateInfo.extent = { width, height, 1 };
+		imageCreateInfo.usage = imageUsageFlags;
+		// Ensure that the TRANSFER_DST bit is set for staging
+		if (!(imageCreateInfo.usage & VK_IMAGE_USAGE_TRANSFER_DST_BIT))
+		{
+			imageCreateInfo.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
+		}
+		VK_CHECK_RESULT(vkCreateImage(device->logicalDevice, &imageCreateInfo, nullptr, &image));
+
+		vkGetImageMemoryRequirements(device->logicalDevice, image, &memReqs);
+
+		memAllocInfo.allocationSize = memReqs.size;
+
+		memAllocInfo.memoryTypeIndex = device->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+		VK_CHECK_RESULT(vkAllocateMemory(device->logicalDevice, &memAllocInfo, nullptr, &deviceMemory));
+		VK_CHECK_RESULT(vkBindImageMemory(device->logicalDevice, image, deviceMemory, 0));
+
+		VkImageSubresourceRange subresourceRange = {};
+		subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+		subresourceRange.baseMipLevel = 0;
+		subresourceRange.levelCount = mipLevels;
+		subresourceRange.layerCount = 1;
+
+		// Image barrier for optimal image (target)
+		// Optimal image will be used as destination for the copy
+		vks::tools::setImageLayout(
+			copyCmd,
+			image,
+			VK_IMAGE_LAYOUT_UNDEFINED,
+			VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+			subresourceRange);
+
+		// Copy mip levels from staging buffer
+		vkCmdCopyBufferToImage(
+			copyCmd,
+			stagingBuffer,
+			image,
+			VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+			1,
+			&bufferCopyRegion
+		);
+
+		// Change texture image layout to shader read after all mip levels have been copied
+		this->imageLayout = imageLayout;
+		vks::tools::setImageLayout(
+			copyCmd,
+			image,
+			VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+			imageLayout,
+			subresourceRange);
+
+		device->flushCommandBuffer(copyCmd, copyQueue);
+
+		// Clean up staging resources
+		vkFreeMemory(device->logicalDevice, stagingMemory, nullptr);
+		vkDestroyBuffer(device->logicalDevice, stagingBuffer, nullptr);
+
+		// Create sampler
+		VkSamplerCreateInfo samplerCreateInfo = {};
+		samplerCreateInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
+		samplerCreateInfo.magFilter = filter;
+		samplerCreateInfo.minFilter = filter;
+		samplerCreateInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+		samplerCreateInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+		samplerCreateInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+		samplerCreateInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+		samplerCreateInfo.mipLodBias = 0.0f;
+		samplerCreateInfo.compareOp = VK_COMPARE_OP_NEVER;
+		samplerCreateInfo.minLod = 0.0f;
+		samplerCreateInfo.maxLod = 0.0f;
+		samplerCreateInfo.maxAnisotropy = 1.0f;
+		VK_CHECK_RESULT(vkCreateSampler(device->logicalDevice, &samplerCreateInfo, nullptr, &sampler));
+
+		// Create image view
+		VkImageViewCreateInfo viewCreateInfo = {};
+		viewCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+		viewCreateInfo.pNext = NULL;
+		viewCreateInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
+		viewCreateInfo.format = format;
+		viewCreateInfo.components = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A };
+		viewCreateInfo.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 };
+		viewCreateInfo.subresourceRange.levelCount = 1;
+		viewCreateInfo.image = image;
+		VK_CHECK_RESULT(vkCreateImageView(device->logicalDevice, &viewCreateInfo, nullptr, &view));
+
+		// Update descriptor image info member that can be used for setting up descriptor sets
+		updateDescriptor();
+	}
+
+	/**
+	* Load a 2D texture array including all mip levels
+	*
+	* @param filename File to load (supports .ktx)
+	* @param format Vulkan format of the image data stored in the file
+	* @param device Vulkan device to create the texture on
+	* @param copyQueue Queue used for the texture staging copy commands (must support transfer)
+	* @param (Optional) imageUsageFlags Usage flags for the texture's image (defaults to VK_IMAGE_USAGE_SAMPLED_BIT)
+	* @param (Optional) imageLayout Usage layout for the texture (defaults VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
+	*
+	*/
+	void Texture2DArray::loadFromFile(std::string filename, VkFormat format, vks::VulkanDevice *device, VkQueue copyQueue, VkImageUsageFlags imageUsageFlags, VkImageLayout imageLayout)
+	{
+		ktxTexture* ktxTexture;
+		ktxResult result = loadKTXFile(filename, &ktxTexture);
+		assert(result == KTX_SUCCESS);
+
+		this->device = device;
+		width = ktxTexture->baseWidth;
+		height = ktxTexture->baseHeight;
+		layerCount = ktxTexture->numLayers;
+		mipLevels = ktxTexture->numLevels;
+
+		ktx_uint8_t *ktxTextureData = ktxTexture_GetData(ktxTexture);
+		ktx_size_t ktxTextureSize = ktxTexture_GetSize(ktxTexture);
+
+		VkMemoryAllocateInfo memAllocInfo = vks::initializers::memoryAllocateInfo();
+		VkMemoryRequirements memReqs;
+
+		// Create a host-visible staging buffer that contains the raw image data
+		VkBuffer stagingBuffer;
+		VkDeviceMemory stagingMemory;
+
+		VkBufferCreateInfo bufferCreateInfo = vks::initializers::bufferCreateInfo();
+		bufferCreateInfo.size = ktxTextureSize;
+		// This buffer is used as a transfer source for the buffer copy
+		bufferCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
+		bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+
+		VK_CHECK_RESULT(vkCreateBuffer(device->logicalDevice, &bufferCreateInfo, nullptr, &stagingBuffer));
+
+		// Get memory requirements for the staging buffer (alignment, memory type bits)
+		vkGetBufferMemoryRequirements(device->logicalDevice, stagingBuffer, &memReqs);
+
+		memAllocInfo.allocationSize = memReqs.size;
+		// Get memory type index for a host visible buffer
+		memAllocInfo.memoryTypeIndex = device->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
+
+		VK_CHECK_RESULT(vkAllocateMemory(device->logicalDevice, &memAllocInfo, nullptr, &stagingMemory));
+		VK_CHECK_RESULT(vkBindBufferMemory(device->logicalDevice, stagingBuffer, stagingMemory, 0));
+
+		// Copy texture data into staging buffer
+		uint8_t *data;
+		VK_CHECK_RESULT(vkMapMemory(device->logicalDevice, stagingMemory, 0, memReqs.size, 0, (void **)&data));
+		memcpy(data, ktxTextureData, ktxTextureSize);
+		vkUnmapMemory(device->logicalDevice, stagingMemory);
+
+		// Setup buffer copy regions for each layer including all of its miplevels
+		std::vector<VkBufferImageCopy> bufferCopyRegions;
+
+		for (uint32_t layer = 0; layer < layerCount; layer++)
+		{
+			for (uint32_t level = 0; level < mipLevels; level++)
+			{
+				ktx_size_t offset;
+				KTX_error_code result = ktxTexture_GetImageOffset(ktxTexture, level, layer, 0, &offset);
+				assert(result == KTX_SUCCESS);
+
+				VkBufferImageCopy bufferCopyRegion = {};
+				bufferCopyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+				bufferCopyRegion.imageSubresource.mipLevel = level;
+				bufferCopyRegion.imageSubresource.baseArrayLayer = layer;
+				bufferCopyRegion.imageSubresource.layerCount = 1;
+				bufferCopyRegion.imageExtent.width = ktxTexture->baseWidth >> level;
+				bufferCopyRegion.imageExtent.height = ktxTexture->baseHeight >> level;
+				bufferCopyRegion.imageExtent.depth = 1;
+				bufferCopyRegion.bufferOffset = offset;
+
+				bufferCopyRegions.push_back(bufferCopyRegion);
+			}
+		}
+
+		// Create optimal tiled target image
+		VkImageCreateInfo imageCreateInfo = vks::initializers::imageCreateInfo();
+		imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
+		imageCreateInfo.format = format;
+		imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
+		imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
+		imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+		imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+		imageCreateInfo.extent = { width, height, 1 };
+		imageCreateInfo.usage = imageUsageFlags;
+		// Ensure that the TRANSFER_DST bit is set for staging
+		if (!(imageCreateInfo.usage & VK_IMAGE_USAGE_TRANSFER_DST_BIT))
+		{
+			imageCreateInfo.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
+		}
+		imageCreateInfo.arrayLayers = layerCount;
+		imageCreateInfo.mipLevels = mipLevels;
+
+		VK_CHECK_RESULT(vkCreateImage(device->logicalDevice, &imageCreateInfo, nullptr, &image));
+
+		vkGetImageMemoryRequirements(device->logicalDevice, image, &memReqs);
+
+		memAllocInfo.allocationSize = memReqs.size;
+		memAllocInfo.memoryTypeIndex = device->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+
+		VK_CHECK_RESULT(vkAllocateMemory(device->logicalDevice, &memAllocInfo, nullptr, &deviceMemory));
+		VK_CHECK_RESULT(vkBindImageMemory(device->logicalDevice, image, deviceMemory, 0));
+
+		// Use a separate command buffer for texture loading
+		VkCommandBuffer copyCmd = device->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+
+		// Image barrier for optimal image (target)
+		// Set initial layout for all array layers (faces) of the optimal (target) tiled texture
+		VkImageSubresourceRange subresourceRange = {};
+		subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+		subresourceRange.baseMipLevel = 0;
+		subresourceRange.levelCount = mipLevels;
+		subresourceRange.layerCount = layerCount;
+
+		vks::tools::setImageLayout(
+			copyCmd,
+			image,
+			VK_IMAGE_LAYOUT_UNDEFINED,
+			VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+			subresourceRange);
+
+		// Copy the layers and mip levels from the staging buffer to the optimal tiled image
+		vkCmdCopyBufferToImage(
+			copyCmd,
+			stagingBuffer,
+			image,
+			VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+			static_cast<uint32_t>(bufferCopyRegions.size()),
+			bufferCopyRegions.data());
+
+		// Change texture image layout to shader read after all faces have been copied
+		this->imageLayout = imageLayout;
+		vks::tools::setImageLayout(
+			copyCmd,
+			image,
+			VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+			imageLayout,
+			subresourceRange);
+
+		device->flushCommandBuffer(copyCmd, copyQueue);
+
+		// Create sampler
+		VkSamplerCreateInfo samplerCreateInfo = vks::initializers::samplerCreateInfo();
+		samplerCreateInfo.magFilter = VK_FILTER_LINEAR;
+		samplerCreateInfo.minFilter = VK_FILTER_LINEAR;
+		samplerCreateInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+		samplerCreateInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+		samplerCreateInfo.addressModeV = samplerCreateInfo.addressModeU;
+		samplerCreateInfo.addressModeW = samplerCreateInfo.addressModeU;
+		samplerCreateInfo.mipLodBias = 0.0f;
+		samplerCreateInfo.maxAnisotropy = device->enabledFeatures.samplerAnisotropy ? device->properties.limits.maxSamplerAnisotropy : 1.0f;
+		samplerCreateInfo.anisotropyEnable = device->enabledFeatures.samplerAnisotropy;
+		samplerCreateInfo.compareOp = VK_COMPARE_OP_NEVER;
+		samplerCreateInfo.minLod = 0.0f;
+		samplerCreateInfo.maxLod = (float)mipLevels;
+		samplerCreateInfo.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
+		VK_CHECK_RESULT(vkCreateSampler(device->logicalDevice, &samplerCreateInfo, nullptr, &sampler));
+
+		// Create image view
+		VkImageViewCreateInfo viewCreateInfo = vks::initializers::imageViewCreateInfo();
+		viewCreateInfo.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY;
+		viewCreateInfo.format = format;
+		viewCreateInfo.components = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A };
+		viewCreateInfo.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 };
+		viewCreateInfo.subresourceRange.layerCount = layerCount;
+		viewCreateInfo.subresourceRange.levelCount = mipLevels;
+		viewCreateInfo.image = image;
+		VK_CHECK_RESULT(vkCreateImageView(device->logicalDevice, &viewCreateInfo, nullptr, &view));
+
+		// Clean up staging resources
+		ktxTexture_Destroy(ktxTexture);
+		vkFreeMemory(device->logicalDevice, stagingMemory, nullptr);
+		vkDestroyBuffer(device->logicalDevice, stagingBuffer, nullptr);
+
+		// Update descriptor image info member that can be used for setting up descriptor sets
+		updateDescriptor();
+	}
+
+	/**
+	* Load a cubemap texture including all mip levels from a single file
+	*
+	* @param filename File to load (supports .ktx)
+	* @param format Vulkan format of the image data stored in the file
+	* @param device Vulkan device to create the texture on
+	* @param copyQueue Queue used for the texture staging copy commands (must support transfer)
+	* @param (Optional) imageUsageFlags Usage flags for the texture's image (defaults to VK_IMAGE_USAGE_SAMPLED_BIT)
+	* @param (Optional) imageLayout Usage layout for the texture (defaults VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
+	*
+	*/
+	void TextureCubeMap::loadFromFile(std::string filename, VkFormat format, vks::VulkanDevice *device, VkQueue copyQueue, VkImageUsageFlags imageUsageFlags, VkImageLayout imageLayout)
+	{
+		ktxTexture* ktxTexture;
+		ktxResult result = loadKTXFile(filename, &ktxTexture);
+		assert(result == KTX_SUCCESS);
+
+		this->device = device;
+		width = ktxTexture->baseWidth;
+		height = ktxTexture->baseHeight;
+		mipLevels = ktxTexture->numLevels;
+
+		ktx_uint8_t *ktxTextureData = ktxTexture_GetData(ktxTexture);
+		ktx_size_t ktxTextureSize = ktxTexture_GetSize(ktxTexture);
+
+		VkMemoryAllocateInfo memAllocInfo = vks::initializers::memoryAllocateInfo();
+		VkMemoryRequirements memReqs;
+
+		// Create a host-visible staging buffer that contains the raw image data
+		VkBuffer stagingBuffer;
+		VkDeviceMemory stagingMemory;
+
+		VkBufferCreateInfo bufferCreateInfo = vks::initializers::bufferCreateInfo();
+		bufferCreateInfo.size = ktxTextureSize;
+		// This buffer is used as a transfer source for the buffer copy
+		bufferCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
+		bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+
+		VK_CHECK_RESULT(vkCreateBuffer(device->logicalDevice, &bufferCreateInfo, nullptr, &stagingBuffer));
+
+		// Get memory requirements for the staging buffer (alignment, memory type bits)
+		vkGetBufferMemoryRequirements(device->logicalDevice, stagingBuffer, &memReqs);
+
+		memAllocInfo.allocationSize = memReqs.size;
+		// Get memory type index for a host visible buffer
+		memAllocInfo.memoryTypeIndex = device->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
+
+		VK_CHECK_RESULT(vkAllocateMemory(device->logicalDevice, &memAllocInfo, nullptr, &stagingMemory));
+		VK_CHECK_RESULT(vkBindBufferMemory(device->logicalDevice, stagingBuffer, stagingMemory, 0));
+
+		// Copy texture data into staging buffer
+		uint8_t *data;
+		VK_CHECK_RESULT(vkMapMemory(device->logicalDevice, stagingMemory, 0, memReqs.size, 0, (void **)&data));
+		memcpy(data, ktxTextureData, ktxTextureSize);
+		vkUnmapMemory(device->logicalDevice, stagingMemory);
+
+		// Setup buffer copy regions for each face including all of its mip levels
+		std::vector<VkBufferImageCopy> bufferCopyRegions;
+
+		for (uint32_t face = 0; face < 6; face++)
+		{
+			for (uint32_t level = 0; level < mipLevels; level++)
+			{
+				ktx_size_t offset;
+				KTX_error_code result = ktxTexture_GetImageOffset(ktxTexture, level, 0, face, &offset);
+				assert(result == KTX_SUCCESS);
+
+				VkBufferImageCopy bufferCopyRegion = {};
+				bufferCopyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+				bufferCopyRegion.imageSubresource.mipLevel = level;
+				bufferCopyRegion.imageSubresource.baseArrayLayer = face;
+				bufferCopyRegion.imageSubresource.layerCount = 1;
+				bufferCopyRegion.imageExtent.width = ktxTexture->baseWidth >> level;
+				bufferCopyRegion.imageExtent.height = ktxTexture->baseHeight >> level;
+				bufferCopyRegion.imageExtent.depth = 1;
+				bufferCopyRegion.bufferOffset = offset;
+
+				bufferCopyRegions.push_back(bufferCopyRegion);
+			}
+		}
+
+		// Create optimal tiled target image
+		VkImageCreateInfo imageCreateInfo = vks::initializers::imageCreateInfo();
+		imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
+		imageCreateInfo.format = format;
+		imageCreateInfo.mipLevels = mipLevels;
+		imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
+		imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
+		imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+		imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+		imageCreateInfo.extent = { width, height, 1 };
+		imageCreateInfo.usage = imageUsageFlags;
+		// Ensure that the TRANSFER_DST bit is set for staging
+		if (!(imageCreateInfo.usage & VK_IMAGE_USAGE_TRANSFER_DST_BIT))
+		{
+			imageCreateInfo.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
+		}
+		// Cube faces count as array layers in Vulkan
+		imageCreateInfo.arrayLayers = 6;
+		// This flag is required for cube map images
+		imageCreateInfo.flags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;
+
+
+		VK_CHECK_RESULT(vkCreateImage(device->logicalDevice, &imageCreateInfo, nullptr, &image));
+
+		vkGetImageMemoryRequirements(device->logicalDevice, image, &memReqs);
+
+		memAllocInfo.allocationSize = memReqs.size;
+		memAllocInfo.memoryTypeIndex = device->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+
+		VK_CHECK_RESULT(vkAllocateMemory(device->logicalDevice, &memAllocInfo, nullptr, &deviceMemory));
+		VK_CHECK_RESULT(vkBindImageMemory(device->logicalDevice, image, deviceMemory, 0));
+
+		// Use a separate command buffer for texture loading
+		VkCommandBuffer copyCmd = device->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+
+		// Image barrier for optimal image (target)
+		// Set initial layout for all array layers (faces) of the optimal (target) tiled texture
+		VkImageSubresourceRange subresourceRange = {};
+		subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+		subresourceRange.baseMipLevel = 0;
+		subresourceRange.levelCount = mipLevels;
+		subresourceRange.layerCount = 6;
+
+		vks::tools::setImageLayout(
+			copyCmd,
+			image,
+			VK_IMAGE_LAYOUT_UNDEFINED,
+			VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+			subresourceRange);
+
+		// Copy the cube map faces from the staging buffer to the optimal tiled image
+		vkCmdCopyBufferToImage(
+			copyCmd,
+			stagingBuffer,
+			image,
+			VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+			static_cast<uint32_t>(bufferCopyRegions.size()),
+			bufferCopyRegions.data());
+
+		// Change texture image layout to shader read after all faces have been copied
+		this->imageLayout = imageLayout;
+		vks::tools::setImageLayout(
+			copyCmd,
+			image,
+			VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+			imageLayout,
+			subresourceRange);
+
+		device->flushCommandBuffer(copyCmd, copyQueue);
+
+		// Create sampler
+		VkSamplerCreateInfo samplerCreateInfo = vks::initializers::samplerCreateInfo();
+		samplerCreateInfo.magFilter = VK_FILTER_LINEAR;
+		samplerCreateInfo.minFilter = VK_FILTER_LINEAR;
+		samplerCreateInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+		samplerCreateInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+		samplerCreateInfo.addressModeV = samplerCreateInfo.addressModeU;
+		samplerCreateInfo.addressModeW = samplerCreateInfo.addressModeU;
+		samplerCreateInfo.mipLodBias = 0.0f;
+		samplerCreateInfo.maxAnisotropy = device->enabledFeatures.samplerAnisotropy ? device->properties.limits.maxSamplerAnisotropy : 1.0f;
+		samplerCreateInfo.anisotropyEnable = device->enabledFeatures.samplerAnisotropy;
+		samplerCreateInfo.compareOp = VK_COMPARE_OP_NEVER;
+		samplerCreateInfo.minLod = 0.0f;
+		samplerCreateInfo.maxLod = (float)mipLevels;
+		samplerCreateInfo.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
+		VK_CHECK_RESULT(vkCreateSampler(device->logicalDevice, &samplerCreateInfo, nullptr, &sampler));
+
+		// Create image view
+		VkImageViewCreateInfo viewCreateInfo = vks::initializers::imageViewCreateInfo();
+		viewCreateInfo.viewType = VK_IMAGE_VIEW_TYPE_CUBE;
+		viewCreateInfo.format = format;
+		viewCreateInfo.components = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A };
+		viewCreateInfo.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 };
+		viewCreateInfo.subresourceRange.layerCount = 6;
+		viewCreateInfo.subresourceRange.levelCount = mipLevels;
+		viewCreateInfo.image = image;
+		VK_CHECK_RESULT(vkCreateImageView(device->logicalDevice, &viewCreateInfo, nullptr, &view));
+
+		// Clean up staging resources
+		ktxTexture_Destroy(ktxTexture);
+		vkFreeMemory(device->logicalDevice, stagingMemory, nullptr);
+		vkDestroyBuffer(device->logicalDevice, stagingBuffer, nullptr);
+
+		// Update descriptor image info member that can be used for setting up descriptor sets
+		updateDescriptor();
+	}
+
+}
diff --git a/external/Vulkan/base/VulkanTexture.h b/external/Vulkan/base/VulkanTexture.h
new file mode 100644
index 0000000000000000000000000000000000000000..09cf56f2344860cf38f194668b9861c9a557a268
--- /dev/null
+++ b/external/Vulkan/base/VulkanTexture.h
@@ -0,0 +1,97 @@
+/*
+* Vulkan texture loader
+*
+* Copyright(C) by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license(MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#pragma once
+
+#include <fstream>
+#include <stdlib.h>
+#include <string>
+#include <vector>
+
+#include "vulkan/vulkan.h"
+
+#include <ktx.h>
+#include <ktxvulkan.h>
+
+#include "VulkanBuffer.h"
+#include "VulkanDevice.h"
+#include "VulkanTools.h"
+
+#if defined(__ANDROID__)
+#	include <android/asset_manager.h>
+#endif
+
+namespace vks
+{
+class Texture
+{
+  public:
+	vks::VulkanDevice *   device;
+	VkImage               image;
+	VkImageLayout         imageLayout;
+	VkDeviceMemory        deviceMemory;
+	VkImageView           view;
+	uint32_t              width, height;
+	uint32_t              mipLevels;
+	uint32_t              layerCount;
+	VkDescriptorImageInfo descriptor;
+	VkSampler             sampler;
+
+	void      updateDescriptor();
+	void      destroy();
+	ktxResult loadKTXFile(std::string filename, ktxTexture **target);
+};
+
+class Texture2D : public Texture
+{
+  public:
+	void loadFromFile(
+	    std::string        filename,
+	    VkFormat           format,
+	    vks::VulkanDevice *device,
+	    VkQueue            copyQueue,
+	    VkImageUsageFlags  imageUsageFlags = VK_IMAGE_USAGE_SAMPLED_BIT,
+	    VkImageLayout      imageLayout     = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
+	    bool               forceLinear     = false);
+	void fromBuffer(
+	    void *             buffer,
+	    VkDeviceSize       bufferSize,
+	    VkFormat           format,
+	    uint32_t           texWidth,
+	    uint32_t           texHeight,
+	    vks::VulkanDevice *device,
+	    VkQueue            copyQueue,
+	    VkFilter           filter          = VK_FILTER_LINEAR,
+	    VkImageUsageFlags  imageUsageFlags = VK_IMAGE_USAGE_SAMPLED_BIT,
+	    VkImageLayout      imageLayout     = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+};
+
+class Texture2DArray : public Texture
+{
+  public:
+	void loadFromFile(
+	    std::string        filename,
+	    VkFormat           format,
+	    vks::VulkanDevice *device,
+	    VkQueue            copyQueue,
+	    VkImageUsageFlags  imageUsageFlags = VK_IMAGE_USAGE_SAMPLED_BIT,
+	    VkImageLayout      imageLayout     = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+};
+
+class TextureCubeMap : public Texture
+{
+  public:
+	void loadFromFile(
+	    std::string        filename,
+	    VkFormat           format,
+	    vks::VulkanDevice *device,
+	    VkQueue            copyQueue,
+	    VkImageUsageFlags  imageUsageFlags = VK_IMAGE_USAGE_SAMPLED_BIT,
+	    VkImageLayout      imageLayout     = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+};
+}        // namespace vks
diff --git a/external/Vulkan/base/VulkanTools.cpp b/external/Vulkan/base/VulkanTools.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d4b0317e53ae7a3d82390bf7abadb7c9bb60d429
--- /dev/null
+++ b/external/Vulkan/base/VulkanTools.cpp
@@ -0,0 +1,389 @@
+/*
+* Assorted commonly used Vulkan helper functions
+*
+* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "VulkanTools.h"
+
+const std::string getAssetPath()
+{
+#if defined(VK_USE_PLATFORM_ANDROID_KHR)
+	return "";
+#elif defined(VK_EXAMPLE_DATA_DIR)
+	return VK_EXAMPLE_DATA_DIR;
+#else
+	return "./../data/";
+#endif
+}
+
+namespace vks
+{
+	namespace tools
+	{
+		bool errorModeSilent = false;
+
+		std::string errorString(VkResult errorCode)
+		{
+			switch (errorCode)
+			{
+#define STR(r) case VK_ ##r: return #r
+				STR(NOT_READY);
+				STR(TIMEOUT);
+				STR(EVENT_SET);
+				STR(EVENT_RESET);
+				STR(INCOMPLETE);
+				STR(ERROR_OUT_OF_HOST_MEMORY);
+				STR(ERROR_OUT_OF_DEVICE_MEMORY);
+				STR(ERROR_INITIALIZATION_FAILED);
+				STR(ERROR_DEVICE_LOST);
+				STR(ERROR_MEMORY_MAP_FAILED);
+				STR(ERROR_LAYER_NOT_PRESENT);
+				STR(ERROR_EXTENSION_NOT_PRESENT);
+				STR(ERROR_FEATURE_NOT_PRESENT);
+				STR(ERROR_INCOMPATIBLE_DRIVER);
+				STR(ERROR_TOO_MANY_OBJECTS);
+				STR(ERROR_FORMAT_NOT_SUPPORTED);
+				STR(ERROR_SURFACE_LOST_KHR);
+				STR(ERROR_NATIVE_WINDOW_IN_USE_KHR);
+				STR(SUBOPTIMAL_KHR);
+				STR(ERROR_OUT_OF_DATE_KHR);
+				STR(ERROR_INCOMPATIBLE_DISPLAY_KHR);
+				STR(ERROR_VALIDATION_FAILED_EXT);
+				STR(ERROR_INVALID_SHADER_NV);
+#undef STR
+			default:
+				return "UNKNOWN_ERROR";
+			}
+		}
+
+		std::string physicalDeviceTypeString(VkPhysicalDeviceType type)
+		{
+			switch (type)
+			{
+#define STR(r) case VK_PHYSICAL_DEVICE_TYPE_ ##r: return #r
+				STR(OTHER);
+				STR(INTEGRATED_GPU);
+				STR(DISCRETE_GPU);
+				STR(VIRTUAL_GPU);
+				STR(CPU);
+#undef STR
+			default: return "UNKNOWN_DEVICE_TYPE";
+			}
+		}
+
+		VkBool32 getSupportedDepthFormat(VkPhysicalDevice physicalDevice, VkFormat *depthFormat)
+		{
+			// Since all depth formats may be optional, we need to find a suitable depth format to use
+			// Start with the highest precision packed format
+			std::vector<VkFormat> depthFormats = {
+				VK_FORMAT_D32_SFLOAT_S8_UINT,
+				VK_FORMAT_D32_SFLOAT,
+				VK_FORMAT_D24_UNORM_S8_UINT,
+				VK_FORMAT_D16_UNORM_S8_UINT,
+				VK_FORMAT_D16_UNORM
+			};
+
+			for (auto& format : depthFormats)
+			{
+				VkFormatProperties formatProps;
+				vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &formatProps);
+				// Format must support depth stencil attachment for optimal tiling
+				if (formatProps.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT)
+				{
+					*depthFormat = format;
+					return true;
+				}
+			}
+
+			return false;
+		}
+
+		// Returns if a given format support LINEAR filtering
+		VkBool32 formatIsFilterable(VkPhysicalDevice physicalDevice, VkFormat format, VkImageTiling tiling)
+		{
+			VkFormatProperties formatProps;
+			vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &formatProps);
+
+			if (tiling == VK_IMAGE_TILING_OPTIMAL)
+				return formatProps.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT;
+
+			if (tiling == VK_IMAGE_TILING_LINEAR)
+				return formatProps.linearTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT;
+
+			return false;
+		}
+
+		// Create an image memory barrier for changing the layout of
+		// an image and put it into an active command buffer
+		// See chapter 11.4 "Image Layout" for details
+
+		void setImageLayout(
+			VkCommandBuffer cmdbuffer,
+			VkImage image,
+			VkImageLayout oldImageLayout,
+			VkImageLayout newImageLayout,
+			VkImageSubresourceRange subresourceRange,
+			VkPipelineStageFlags srcStageMask,
+			VkPipelineStageFlags dstStageMask)
+		{
+			// Create an image barrier object
+			VkImageMemoryBarrier imageMemoryBarrier = vks::initializers::imageMemoryBarrier();
+			imageMemoryBarrier.oldLayout = oldImageLayout;
+			imageMemoryBarrier.newLayout = newImageLayout;
+			imageMemoryBarrier.image = image;
+			imageMemoryBarrier.subresourceRange = subresourceRange;
+
+			// Source layouts (old)
+			// Source access mask controls actions that have to be finished on the old layout
+			// before it will be transitioned to the new layout
+			switch (oldImageLayout)
+			{
+			case VK_IMAGE_LAYOUT_UNDEFINED:
+				// Image layout is undefined (or does not matter)
+				// Only valid as initial layout
+				// No flags required, listed only for completeness
+				imageMemoryBarrier.srcAccessMask = 0;
+				break;
+
+			case VK_IMAGE_LAYOUT_PREINITIALIZED:
+				// Image is preinitialized
+				// Only valid as initial layout for linear images, preserves memory contents
+				// Make sure host writes have been finished
+				imageMemoryBarrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT;
+				break;
+
+			case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
+				// Image is a color attachment
+				// Make sure any writes to the color buffer have been finished
+				imageMemoryBarrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+				break;
+
+			case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL:
+				// Image is a depth/stencil attachment
+				// Make sure any writes to the depth/stencil buffer have been finished
+				imageMemoryBarrier.srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
+				break;
+
+			case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL:
+				// Image is a transfer source
+				// Make sure any reads from the image have been finished
+				imageMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
+				break;
+
+			case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
+				// Image is a transfer destination
+				// Make sure any writes to the image have been finished
+				imageMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+				break;
+
+			case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
+				// Image is read by a shader
+				// Make sure any shader reads from the image have been finished
+				imageMemoryBarrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
+				break;
+			default:
+				// Other source layouts aren't handled (yet)
+				break;
+			}
+
+			// Target layouts (new)
+			// Destination access mask controls the dependency for the new image layout
+			switch (newImageLayout)
+			{
+			case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
+				// Image will be used as a transfer destination
+				// Make sure any writes to the image have been finished
+				imageMemoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+				break;
+
+			case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL:
+				// Image will be used as a transfer source
+				// Make sure any reads from the image have been finished
+				imageMemoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
+				break;
+
+			case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
+				// Image will be used as a color attachment
+				// Make sure any writes to the color buffer have been finished
+				imageMemoryBarrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+				break;
+
+			case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL:
+				// Image layout will be used as a depth/stencil attachment
+				// Make sure any writes to depth/stencil buffer have been finished
+				imageMemoryBarrier.dstAccessMask = imageMemoryBarrier.dstAccessMask | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
+				break;
+
+			case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
+				// Image will be read in a shader (sampler, input attachment)
+				// Make sure any writes to the image have been finished
+				if (imageMemoryBarrier.srcAccessMask == 0)
+				{
+					imageMemoryBarrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT;
+				}
+				imageMemoryBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
+				break;
+			default:
+				// Other source layouts aren't handled (yet)
+				break;
+			}
+
+			// Put barrier inside setup command buffer
+			vkCmdPipelineBarrier(
+				cmdbuffer,
+				srcStageMask,
+				dstStageMask,
+				0,
+				0, nullptr,
+				0, nullptr,
+				1, &imageMemoryBarrier);
+		}
+
+		// Fixed sub resource on first mip level and layer
+		void setImageLayout(
+			VkCommandBuffer cmdbuffer,
+			VkImage image,
+			VkImageAspectFlags aspectMask,
+			VkImageLayout oldImageLayout,
+			VkImageLayout newImageLayout,
+			VkPipelineStageFlags srcStageMask,
+			VkPipelineStageFlags dstStageMask)
+		{
+			VkImageSubresourceRange subresourceRange = {};
+			subresourceRange.aspectMask = aspectMask;
+			subresourceRange.baseMipLevel = 0;
+			subresourceRange.levelCount = 1;
+			subresourceRange.layerCount = 1;
+			setImageLayout(cmdbuffer, image, oldImageLayout, newImageLayout, subresourceRange, srcStageMask, dstStageMask);
+		}
+
+		void insertImageMemoryBarrier(
+			VkCommandBuffer cmdbuffer,
+			VkImage image,
+			VkAccessFlags srcAccessMask,
+			VkAccessFlags dstAccessMask,
+			VkImageLayout oldImageLayout,
+			VkImageLayout newImageLayout,
+			VkPipelineStageFlags srcStageMask,
+			VkPipelineStageFlags dstStageMask,
+			VkImageSubresourceRange subresourceRange)
+		{
+			VkImageMemoryBarrier imageMemoryBarrier = vks::initializers::imageMemoryBarrier();
+			imageMemoryBarrier.srcAccessMask = srcAccessMask;
+			imageMemoryBarrier.dstAccessMask = dstAccessMask;
+			imageMemoryBarrier.oldLayout = oldImageLayout;
+			imageMemoryBarrier.newLayout = newImageLayout;
+			imageMemoryBarrier.image = image;
+			imageMemoryBarrier.subresourceRange = subresourceRange;
+
+			vkCmdPipelineBarrier(
+				cmdbuffer,
+				srcStageMask,
+				dstStageMask,
+				0,
+				0, nullptr,
+				0, nullptr,
+				1, &imageMemoryBarrier);
+		}
+
+		void exitFatal(const std::string& message, int32_t exitCode)
+		{
+#if defined(_WIN32)
+			if (!errorModeSilent) {
+				MessageBox(NULL, message.c_str(), NULL, MB_OK | MB_ICONERROR);
+			}
+#elif defined(__ANDROID__)
+            LOGE("Fatal error: %s", message.c_str());
+			vks::android::showAlert(message.c_str());
+#endif
+			std::cerr << message << "\n";
+#if !defined(__ANDROID__)
+			exit(exitCode);
+#endif
+		}
+
+		void exitFatal(const std::string& message, VkResult resultCode)
+		{
+			exitFatal(message, (int32_t)resultCode);
+		}
+
+#if defined(__ANDROID__)
+		// Android shaders are stored as assets in the apk
+		// So they need to be loaded via the asset manager
+		VkShaderModule loadShader(AAssetManager* assetManager, const char *fileName, VkDevice device)
+		{
+			// Load shader from compressed asset
+			AAsset* asset = AAssetManager_open(assetManager, fileName, AASSET_MODE_STREAMING);
+			assert(asset);
+			size_t size = AAsset_getLength(asset);
+			assert(size > 0);
+
+			char *shaderCode = new char[size];
+			AAsset_read(asset, shaderCode, size);
+			AAsset_close(asset);
+
+			VkShaderModule shaderModule;
+			VkShaderModuleCreateInfo moduleCreateInfo;
+			moduleCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
+			moduleCreateInfo.pNext = NULL;
+			moduleCreateInfo.codeSize = size;
+			moduleCreateInfo.pCode = (uint32_t*)shaderCode;
+			moduleCreateInfo.flags = 0;
+
+			VK_CHECK_RESULT(vkCreateShaderModule(device, &moduleCreateInfo, NULL, &shaderModule));
+
+			delete[] shaderCode;
+
+			return shaderModule;
+		}
+#else
+		VkShaderModule loadShader(const char *fileName, VkDevice device)
+		{
+			std::ifstream is(fileName, std::ios::binary | std::ios::in | std::ios::ate);
+
+			if (is.is_open())
+			{
+				size_t size = is.tellg();
+				is.seekg(0, std::ios::beg);
+				char* shaderCode = new char[size];
+				is.read(shaderCode, size);
+				is.close();
+
+				assert(size > 0);
+
+				VkShaderModule shaderModule;
+				VkShaderModuleCreateInfo moduleCreateInfo{};
+				moduleCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
+				moduleCreateInfo.codeSize = size;
+				moduleCreateInfo.pCode = (uint32_t*)shaderCode;
+
+				VK_CHECK_RESULT(vkCreateShaderModule(device, &moduleCreateInfo, NULL, &shaderModule));
+
+				delete[] shaderCode;
+
+				return shaderModule;
+			}
+			else
+			{
+				std::cerr << "Error: Could not open shader file \"" << fileName << "\"" << "\n";
+				return VK_NULL_HANDLE;
+			}
+		}
+#endif
+
+		bool fileExists(const std::string &filename)
+		{
+			std::ifstream f(filename.c_str());
+			return !f.fail();
+		}
+
+		uint32_t alignedSize(uint32_t value, uint32_t alignment)
+        {
+	        return (value + alignment - 1) & ~(alignment - 1);
+        }
+
+	}
+}
diff --git a/external/Vulkan/base/VulkanTools.h b/external/Vulkan/base/VulkanTools.h
new file mode 100644
index 0000000000000000000000000000000000000000..d392e401ce94bfa0a867f8b83d46dc3aa36a045f
--- /dev/null
+++ b/external/Vulkan/base/VulkanTools.h
@@ -0,0 +1,131 @@
+/*
+* Assorted Vulkan helper functions
+*
+* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#pragma once
+
+#include "vulkan/vulkan.h"
+#include "VulkanInitializers.hpp"
+
+#include <math.h>
+#include <stdlib.h>
+#include <string>
+#include <cstring>
+#include <fstream>
+#include <assert.h>
+#include <stdio.h>
+#include <vector>
+#include <iostream>
+#include <stdexcept>
+#include <fstream>
+#if defined(_WIN32)
+#include <windows.h>
+#include <fcntl.h>
+#include <io.h>
+#elif defined(__ANDROID__)
+#include "VulkanAndroid.h"
+#include <android/asset_manager.h>
+#endif
+
+// Custom define for better code readability
+#define VK_FLAGS_NONE 0
+// Default fence timeout in nanoseconds
+#define DEFAULT_FENCE_TIMEOUT 100000000000
+
+// Macro to check and display Vulkan return results
+#if defined(__ANDROID__)
+#define VK_CHECK_RESULT(f)																				\
+{																										\
+	VkResult res = (f);																					\
+	if (res != VK_SUCCESS)																				\
+	{																									\
+		LOGE("Fatal : VkResult is \" %s \" in %s at line %d", vks::tools::errorString(res).c_str(), __FILE__, __LINE__); \
+		assert(res == VK_SUCCESS);																		\
+	}																									\
+}
+#else
+#define VK_CHECK_RESULT(f)																				\
+{																										\
+	VkResult res = (f);																					\
+	if (res != VK_SUCCESS)																				\
+	{																									\
+		std::cout << "Fatal : VkResult is \"" << vks::tools::errorString(res) << "\" in " << __FILE__ << " at line " << __LINE__ << "\n"; \
+		assert(res == VK_SUCCESS);																		\
+	}																									\
+}
+#endif
+
+const std::string getAssetPath();
+
+namespace vks
+{
+	namespace tools
+	{
+		/** @brief Disable message boxes on fatal errors */
+		extern bool errorModeSilent;
+
+		/** @brief Returns an error code as a string */
+		std::string errorString(VkResult errorCode);
+
+		/** @brief Returns the device type as a string */
+		std::string physicalDeviceTypeString(VkPhysicalDeviceType type);
+
+		// Selected a suitable supported depth format starting with 32 bit down to 16 bit
+		// Returns false if none of the depth formats in the list is supported by the device
+		VkBool32 getSupportedDepthFormat(VkPhysicalDevice physicalDevice, VkFormat *depthFormat);
+
+		// Returns if a given format support LINEAR filtering
+		VkBool32 formatIsFilterable(VkPhysicalDevice physicalDevice, VkFormat format, VkImageTiling tiling);
+
+		// Put an image memory barrier for setting an image layout on the sub resource into the given command buffer
+		void setImageLayout(
+			VkCommandBuffer cmdbuffer,
+			VkImage image,
+			VkImageLayout oldImageLayout,
+			VkImageLayout newImageLayout,
+			VkImageSubresourceRange subresourceRange,
+			VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
+			VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT);
+		// Uses a fixed sub resource layout with first mip level and layer
+		void setImageLayout(
+			VkCommandBuffer cmdbuffer,
+			VkImage image,
+			VkImageAspectFlags aspectMask,
+			VkImageLayout oldImageLayout,
+			VkImageLayout newImageLayout,
+			VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
+			VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT);
+
+		/** @brief Insert an image memory barrier into the command buffer */
+		void insertImageMemoryBarrier(
+			VkCommandBuffer cmdbuffer,
+			VkImage image,
+			VkAccessFlags srcAccessMask,
+			VkAccessFlags dstAccessMask,
+			VkImageLayout oldImageLayout,
+			VkImageLayout newImageLayout,
+			VkPipelineStageFlags srcStageMask,
+			VkPipelineStageFlags dstStageMask,
+			VkImageSubresourceRange subresourceRange);
+
+		// Display error message and exit on fatal error
+		void exitFatal(const std::string& message, int32_t exitCode);
+		void exitFatal(const std::string& message, VkResult resultCode);
+
+		// Load a SPIR-V shader (binary)
+#if defined(__ANDROID__)
+		VkShaderModule loadShader(AAssetManager* assetManager, const char *fileName, VkDevice device);
+#else
+		VkShaderModule loadShader(const char *fileName, VkDevice device);
+#endif
+
+		/** @brief Checks if a file exists */
+		bool fileExists(const std::string &filename);
+
+		uint32_t alignedSize(uint32_t value, uint32_t alignment);
+	}
+}
diff --git a/external/Vulkan/base/VulkanUIOverlay.cpp b/external/Vulkan/base/VulkanUIOverlay.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..60c9226c56d309c7e47d9c75297940fde1d5b693
--- /dev/null
+++ b/external/Vulkan/base/VulkanUIOverlay.cpp
@@ -0,0 +1,488 @@
+
+/*
+* UI overlay class using ImGui
+*
+* Copyright (C) 2017 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "VulkanUIOverlay.h"
+
+namespace vks 
+{
+	UIOverlay::UIOverlay()
+	{
+#if defined(__ANDROID__)		
+		if (vks::android::screenDensity >= ACONFIGURATION_DENSITY_XXHIGH) {
+			scale = 3.5f;
+		}
+		else if (vks::android::screenDensity >= ACONFIGURATION_DENSITY_XHIGH) {
+			scale = 2.5f;
+		}
+		else if (vks::android::screenDensity >= ACONFIGURATION_DENSITY_HIGH) {
+			scale = 2.0f;
+		};
+#endif
+
+		// Init ImGui
+		ImGui::CreateContext();
+		// Color scheme
+		ImGuiStyle& style = ImGui::GetStyle();
+		style.Colors[ImGuiCol_TitleBg] = ImVec4(1.0f, 0.0f, 0.0f, 1.0f);
+		style.Colors[ImGuiCol_TitleBgActive] = ImVec4(1.0f, 0.0f, 0.0f, 1.0f);
+		style.Colors[ImGuiCol_TitleBgCollapsed] = ImVec4(1.0f, 0.0f, 0.0f, 0.1f);
+		style.Colors[ImGuiCol_MenuBarBg] = ImVec4(1.0f, 0.0f, 0.0f, 0.4f);
+		style.Colors[ImGuiCol_Header] = ImVec4(0.8f, 0.0f, 0.0f, 0.4f);
+		style.Colors[ImGuiCol_HeaderActive] = ImVec4(1.0f, 0.0f, 0.0f, 0.4f);
+		style.Colors[ImGuiCol_HeaderHovered] = ImVec4(1.0f, 0.0f, 0.0f, 0.4f);
+		style.Colors[ImGuiCol_FrameBg] = ImVec4(0.0f, 0.0f, 0.0f, 0.8f);
+		style.Colors[ImGuiCol_CheckMark] = ImVec4(1.0f, 0.0f, 0.0f, 0.8f);
+		style.Colors[ImGuiCol_SliderGrab] = ImVec4(1.0f, 0.0f, 0.0f, 0.4f);
+		style.Colors[ImGuiCol_SliderGrabActive] = ImVec4(1.0f, 0.0f, 0.0f, 0.8f);
+		style.Colors[ImGuiCol_FrameBgHovered] = ImVec4(1.0f, 1.0f, 1.0f, 0.1f);
+		style.Colors[ImGuiCol_FrameBgActive] = ImVec4(1.0f, 1.0f, 1.0f, 0.2f);
+		style.Colors[ImGuiCol_Button] = ImVec4(1.0f, 0.0f, 0.0f, 0.4f);
+		style.Colors[ImGuiCol_ButtonHovered] = ImVec4(1.0f, 0.0f, 0.0f, 0.6f);
+		style.Colors[ImGuiCol_ButtonActive] = ImVec4(1.0f, 0.0f, 0.0f, 0.8f);
+		// Dimensions
+		ImGuiIO& io = ImGui::GetIO();
+		io.FontGlobalScale = scale;
+	}
+
+	UIOverlay::~UIOverlay()	{
+		if (ImGui::GetCurrentContext()) {
+			ImGui::DestroyContext();
+		}
+	}
+
+	/** Prepare all vulkan resources required to render the UI overlay */
+	void UIOverlay::prepareResources()
+	{
+		ImGuiIO& io = ImGui::GetIO();
+
+		// Create font texture
+		unsigned char* fontData;
+		int texWidth, texHeight;
+#if defined(__ANDROID__)
+		float scale = (float)vks::android::screenDensity / (float)ACONFIGURATION_DENSITY_MEDIUM;
+		AAsset* asset = AAssetManager_open(androidApp->activity->assetManager, "Roboto-Medium.ttf", AASSET_MODE_STREAMING);
+		if (asset) {
+			size_t size = AAsset_getLength(asset);
+			assert(size > 0);
+			char *fontAsset = new char[size];
+			AAsset_read(asset, fontAsset, size);
+			AAsset_close(asset);
+			io.Fonts->AddFontFromMemoryTTF(fontAsset, size, 14.0f * scale);
+			delete[] fontAsset;
+		}
+#else
+		const std::string filename = getAssetPath() + "Roboto-Medium.ttf";
+		io.Fonts->AddFontFromFileTTF(filename.c_str(), 16.0f);
+#endif		
+		io.Fonts->GetTexDataAsRGBA32(&fontData, &texWidth, &texHeight);
+		VkDeviceSize uploadSize = texWidth*texHeight * 4 * sizeof(char);
+
+		// Create target image for copy
+		VkImageCreateInfo imageInfo = vks::initializers::imageCreateInfo();
+		imageInfo.imageType = VK_IMAGE_TYPE_2D;
+		imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
+		imageInfo.extent.width = texWidth;
+		imageInfo.extent.height = texHeight;
+		imageInfo.extent.depth = 1;
+		imageInfo.mipLevels = 1;
+		imageInfo.arrayLayers = 1;
+		imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
+		imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
+		imageInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
+		imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+		imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+		VK_CHECK_RESULT(vkCreateImage(device->logicalDevice, &imageInfo, nullptr, &fontImage));
+		VkMemoryRequirements memReqs;
+		vkGetImageMemoryRequirements(device->logicalDevice, fontImage, &memReqs);
+		VkMemoryAllocateInfo memAllocInfo = vks::initializers::memoryAllocateInfo();
+		memAllocInfo.allocationSize = memReqs.size;
+		memAllocInfo.memoryTypeIndex = device->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+		VK_CHECK_RESULT(vkAllocateMemory(device->logicalDevice, &memAllocInfo, nullptr, &fontMemory));
+		VK_CHECK_RESULT(vkBindImageMemory(device->logicalDevice, fontImage, fontMemory, 0));
+
+		// Image view
+		VkImageViewCreateInfo viewInfo = vks::initializers::imageViewCreateInfo();
+		viewInfo.image = fontImage;
+		viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
+		viewInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
+		viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+		viewInfo.subresourceRange.levelCount = 1;
+		viewInfo.subresourceRange.layerCount = 1;
+		VK_CHECK_RESULT(vkCreateImageView(device->logicalDevice, &viewInfo, nullptr, &fontView));
+
+		// Staging buffers for font data upload
+		vks::Buffer stagingBuffer;
+
+		VK_CHECK_RESULT(device->createBuffer(
+			VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&stagingBuffer,
+			uploadSize));
+
+		stagingBuffer.map();
+		memcpy(stagingBuffer.mapped, fontData, uploadSize);
+		stagingBuffer.unmap();
+
+		// Copy buffer data to font image
+		VkCommandBuffer copyCmd = device->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+
+		// Prepare for transfer
+		vks::tools::setImageLayout(
+			copyCmd,
+			fontImage,
+			VK_IMAGE_ASPECT_COLOR_BIT,
+			VK_IMAGE_LAYOUT_UNDEFINED,
+			VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+			VK_PIPELINE_STAGE_HOST_BIT,
+			VK_PIPELINE_STAGE_TRANSFER_BIT);
+
+		// Copy
+		VkBufferImageCopy bufferCopyRegion = {};
+		bufferCopyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+		bufferCopyRegion.imageSubresource.layerCount = 1;
+		bufferCopyRegion.imageExtent.width = texWidth;
+		bufferCopyRegion.imageExtent.height = texHeight;
+		bufferCopyRegion.imageExtent.depth = 1;
+
+		vkCmdCopyBufferToImage(
+			copyCmd,
+			stagingBuffer.buffer,
+			fontImage,
+			VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+			1,
+			&bufferCopyRegion
+		);
+
+		// Prepare for shader read
+		vks::tools::setImageLayout(
+			copyCmd,
+			fontImage,
+			VK_IMAGE_ASPECT_COLOR_BIT,
+			VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+			VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
+			VK_PIPELINE_STAGE_TRANSFER_BIT,
+			VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);
+
+		device->flushCommandBuffer(copyCmd, queue, true);
+
+		stagingBuffer.destroy();
+
+		// Font texture Sampler
+		VkSamplerCreateInfo samplerInfo = vks::initializers::samplerCreateInfo();
+		samplerInfo.magFilter = VK_FILTER_LINEAR;
+		samplerInfo.minFilter = VK_FILTER_LINEAR;
+		samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+		samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+		samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+		samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+		samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
+		VK_CHECK_RESULT(vkCreateSampler(device->logicalDevice, &samplerInfo, nullptr, &sampler));
+
+		// Descriptor pool
+		std::vector<VkDescriptorPoolSize> poolSizes = {
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1)
+		};
+		VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 2);
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device->logicalDevice, &descriptorPoolInfo, nullptr, &descriptorPool));
+
+		// Descriptor set layout
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0),
+		};
+		VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device->logicalDevice, &descriptorLayout, nullptr, &descriptorSetLayout));
+
+		// Descriptor set
+		VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1);
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device->logicalDevice, &allocInfo, &descriptorSet));
+		VkDescriptorImageInfo fontDescriptor = vks::initializers::descriptorImageInfo(
+			sampler,
+			fontView,
+			VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
+		);
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets = {
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &fontDescriptor)
+		};
+		vkUpdateDescriptorSets(device->logicalDevice, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr);
+	}
+
+	/** Prepare a separate pipeline for the UI overlay rendering decoupled from the main application */
+	void UIOverlay::preparePipeline(const VkPipelineCache pipelineCache, const VkRenderPass renderPass)
+	{
+		// Pipeline layout
+		// Push constants for UI rendering parameters
+		VkPushConstantRange pushConstantRange = vks::initializers::pushConstantRange(VK_SHADER_STAGE_VERTEX_BIT, sizeof(PushConstBlock), 0);
+		VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1);
+		pipelineLayoutCreateInfo.pushConstantRangeCount = 1;
+		pipelineLayoutCreateInfo.pPushConstantRanges = &pushConstantRange;
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device->logicalDevice, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout));
+
+		// Setup graphics pipeline for UI rendering
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState =
+			vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+
+		VkPipelineRasterizationStateCreateInfo rasterizationState =
+			vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE, VK_FRONT_FACE_COUNTER_CLOCKWISE);
+
+		// Enable blending
+		VkPipelineColorBlendAttachmentState blendAttachmentState{};
+		blendAttachmentState.blendEnable = VK_TRUE;
+		blendAttachmentState.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
+		blendAttachmentState.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
+		blendAttachmentState.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
+		blendAttachmentState.colorBlendOp = VK_BLEND_OP_ADD;
+		blendAttachmentState.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
+		blendAttachmentState.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
+		blendAttachmentState.alphaBlendOp = VK_BLEND_OP_ADD;
+
+		VkPipelineColorBlendStateCreateInfo colorBlendState =
+			vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+
+		VkPipelineDepthStencilStateCreateInfo depthStencilState =
+			vks::initializers::pipelineDepthStencilStateCreateInfo(VK_FALSE, VK_FALSE, VK_COMPARE_OP_ALWAYS);
+
+		VkPipelineViewportStateCreateInfo viewportState =
+			vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+
+		VkPipelineMultisampleStateCreateInfo multisampleState =
+			vks::initializers::pipelineMultisampleStateCreateInfo(rasterizationSamples);
+
+		std::vector<VkDynamicState> dynamicStateEnables = {
+			VK_DYNAMIC_STATE_VIEWPORT,
+			VK_DYNAMIC_STATE_SCISSOR
+		};
+		VkPipelineDynamicStateCreateInfo dynamicState =
+			vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
+
+		VkGraphicsPipelineCreateInfo pipelineCreateInfo = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass);
+
+		pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState;
+		pipelineCreateInfo.pRasterizationState = &rasterizationState;
+		pipelineCreateInfo.pColorBlendState = &colorBlendState;
+		pipelineCreateInfo.pMultisampleState = &multisampleState;
+		pipelineCreateInfo.pViewportState = &viewportState;
+		pipelineCreateInfo.pDepthStencilState = &depthStencilState;
+		pipelineCreateInfo.pDynamicState = &dynamicState;
+		pipelineCreateInfo.stageCount = static_cast<uint32_t>(shaders.size());
+		pipelineCreateInfo.pStages = shaders.data();
+		pipelineCreateInfo.subpass = subpass;
+
+		// Vertex bindings an attributes based on ImGui vertex definition
+		std::vector<VkVertexInputBindingDescription> vertexInputBindings = {
+			vks::initializers::vertexInputBindingDescription(0, sizeof(ImDrawVert), VK_VERTEX_INPUT_RATE_VERTEX),
+		};
+		std::vector<VkVertexInputAttributeDescription> vertexInputAttributes = {
+			vks::initializers::vertexInputAttributeDescription(0, 0, VK_FORMAT_R32G32_SFLOAT, offsetof(ImDrawVert, pos)),	// Location 0: Position
+			vks::initializers::vertexInputAttributeDescription(0, 1, VK_FORMAT_R32G32_SFLOAT, offsetof(ImDrawVert, uv)),	// Location 1: UV
+			vks::initializers::vertexInputAttributeDescription(0, 2, VK_FORMAT_R8G8B8A8_UNORM, offsetof(ImDrawVert, col)),	// Location 0: Color
+		};
+		VkPipelineVertexInputStateCreateInfo vertexInputState = vks::initializers::pipelineVertexInputStateCreateInfo();
+		vertexInputState.vertexBindingDescriptionCount = static_cast<uint32_t>(vertexInputBindings.size());
+		vertexInputState.pVertexBindingDescriptions = vertexInputBindings.data();
+		vertexInputState.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertexInputAttributes.size());
+		vertexInputState.pVertexAttributeDescriptions = vertexInputAttributes.data();
+
+		pipelineCreateInfo.pVertexInputState = &vertexInputState;
+
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device->logicalDevice, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipeline));
+	}
+
+	/** Update vertex and index buffer containing the imGui elements when required */
+	bool UIOverlay::update()
+	{
+		ImDrawData* imDrawData = ImGui::GetDrawData();
+		bool updateCmdBuffers = false;
+
+		if (!imDrawData) { return false; };
+
+		// Note: Alignment is done inside buffer creation
+		VkDeviceSize vertexBufferSize = imDrawData->TotalVtxCount * sizeof(ImDrawVert);
+		VkDeviceSize indexBufferSize = imDrawData->TotalIdxCount * sizeof(ImDrawIdx);
+
+		// Update buffers only if vertex or index count has been changed compared to current buffer size
+		if ((vertexBufferSize == 0) || (indexBufferSize == 0)) {
+			return false;
+		}
+
+		// Vertex buffer
+		if ((vertexBuffer.buffer == VK_NULL_HANDLE) || (vertexCount != imDrawData->TotalVtxCount)) {
+			vertexBuffer.unmap();
+			vertexBuffer.destroy();
+			VK_CHECK_RESULT(device->createBuffer(VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, &vertexBuffer, vertexBufferSize));
+			vertexCount = imDrawData->TotalVtxCount;
+			vertexBuffer.unmap();
+			vertexBuffer.map();
+			updateCmdBuffers = true;
+		}
+
+		// Index buffer
+		VkDeviceSize indexSize = imDrawData->TotalIdxCount * sizeof(ImDrawIdx);
+		if ((indexBuffer.buffer == VK_NULL_HANDLE) || (indexCount < imDrawData->TotalIdxCount)) {
+			indexBuffer.unmap();
+			indexBuffer.destroy();
+			VK_CHECK_RESULT(device->createBuffer(VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, &indexBuffer, indexBufferSize));
+			indexCount = imDrawData->TotalIdxCount;
+			indexBuffer.map();
+			updateCmdBuffers = true;
+		}
+
+		// Upload data
+		ImDrawVert* vtxDst = (ImDrawVert*)vertexBuffer.mapped;
+		ImDrawIdx* idxDst = (ImDrawIdx*)indexBuffer.mapped;
+
+		for (int n = 0; n < imDrawData->CmdListsCount; n++) {
+			const ImDrawList* cmd_list = imDrawData->CmdLists[n];
+			memcpy(vtxDst, cmd_list->VtxBuffer.Data, cmd_list->VtxBuffer.Size * sizeof(ImDrawVert));
+			memcpy(idxDst, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx));
+			vtxDst += cmd_list->VtxBuffer.Size;
+			idxDst += cmd_list->IdxBuffer.Size;
+		}
+
+		// Flush to make writes visible to GPU
+		vertexBuffer.flush();
+		indexBuffer.flush();
+
+		return updateCmdBuffers;
+	}
+
+	void UIOverlay::draw(const VkCommandBuffer commandBuffer)
+	{
+		ImDrawData* imDrawData = ImGui::GetDrawData();
+		int32_t vertexOffset = 0;
+		int32_t indexOffset = 0;
+
+		if ((!imDrawData) || (imDrawData->CmdListsCount == 0)) {
+			return;
+		}
+
+		ImGuiIO& io = ImGui::GetIO();
+
+		vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
+		vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL);
+
+		pushConstBlock.scale = glm::vec2(2.0f / io.DisplaySize.x, 2.0f / io.DisplaySize.y);
+		pushConstBlock.translate = glm::vec2(-1.0f);
+		vkCmdPushConstants(commandBuffer, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(PushConstBlock), &pushConstBlock);
+
+		VkDeviceSize offsets[1] = { 0 };
+		vkCmdBindVertexBuffers(commandBuffer, 0, 1, &vertexBuffer.buffer, offsets);
+		vkCmdBindIndexBuffer(commandBuffer, indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT16);
+
+		for (int32_t i = 0; i < imDrawData->CmdListsCount; i++)
+		{
+			const ImDrawList* cmd_list = imDrawData->CmdLists[i];
+			for (int32_t j = 0; j < cmd_list->CmdBuffer.Size; j++)
+			{
+				const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[j];
+				VkRect2D scissorRect;
+				scissorRect.offset.x = std::max((int32_t)(pcmd->ClipRect.x), 0);
+				scissorRect.offset.y = std::max((int32_t)(pcmd->ClipRect.y), 0);
+				scissorRect.extent.width = (uint32_t)(pcmd->ClipRect.z - pcmd->ClipRect.x);
+				scissorRect.extent.height = (uint32_t)(pcmd->ClipRect.w - pcmd->ClipRect.y);
+				vkCmdSetScissor(commandBuffer, 0, 1, &scissorRect);
+				vkCmdDrawIndexed(commandBuffer, pcmd->ElemCount, 1, indexOffset, vertexOffset, 0);
+				indexOffset += pcmd->ElemCount;
+			}
+			vertexOffset += cmd_list->VtxBuffer.Size;
+		}
+	}
+
+	void UIOverlay::resize(uint32_t width, uint32_t height)
+	{
+		ImGuiIO& io = ImGui::GetIO();
+		io.DisplaySize = ImVec2((float)(width), (float)(height));
+	}
+
+	void UIOverlay::freeResources()
+	{
+		vertexBuffer.destroy();
+		indexBuffer.destroy();
+		vkDestroyImageView(device->logicalDevice, fontView, nullptr);
+		vkDestroyImage(device->logicalDevice, fontImage, nullptr);
+		vkFreeMemory(device->logicalDevice, fontMemory, nullptr);
+		vkDestroySampler(device->logicalDevice, sampler, nullptr);
+		vkDestroyDescriptorSetLayout(device->logicalDevice, descriptorSetLayout, nullptr);
+		vkDestroyDescriptorPool(device->logicalDevice, descriptorPool, nullptr);
+		vkDestroyPipelineLayout(device->logicalDevice, pipelineLayout, nullptr);
+		vkDestroyPipeline(device->logicalDevice, pipeline, nullptr);
+	}
+
+	bool UIOverlay::header(const char *caption)
+	{
+		return ImGui::CollapsingHeader(caption, ImGuiTreeNodeFlags_DefaultOpen);
+	}
+
+	bool UIOverlay::checkBox(const char *caption, bool *value)
+	{
+		bool res = ImGui::Checkbox(caption, value);
+		if (res) { updated = true; };
+		return res;
+	}
+
+	bool UIOverlay::checkBox(const char *caption, int32_t *value)
+	{
+		bool val = (*value == 1);
+		bool res = ImGui::Checkbox(caption, &val);
+		*value = val;
+		if (res) { updated = true; };
+		return res;
+	}
+
+	bool UIOverlay::inputFloat(const char *caption, float *value, float step, uint32_t precision)
+	{
+		bool res = ImGui::InputFloat(caption, value, step, step * 10.0f, precision);
+		if (res) { updated = true; };
+		return res;
+	}
+
+	bool UIOverlay::sliderFloat(const char* caption, float* value, float min, float max)
+	{
+		bool res = ImGui::SliderFloat(caption, value, min, max);
+		if (res) { updated = true; };
+		return res;
+	}
+
+	bool UIOverlay::sliderInt(const char* caption, int32_t* value, int32_t min, int32_t max)
+	{
+		bool res = ImGui::SliderInt(caption, value, min, max);
+		if (res) { updated = true; };
+		return res;
+	}
+
+	bool UIOverlay::comboBox(const char *caption, int32_t *itemindex, std::vector<std::string> items)
+	{
+		if (items.empty()) {
+			return false;
+		}
+		std::vector<const char*> charitems;
+		charitems.reserve(items.size());
+		for (size_t i = 0; i < items.size(); i++) {
+			charitems.push_back(items[i].c_str());
+		}
+		uint32_t itemCount = static_cast<uint32_t>(charitems.size());
+		bool res = ImGui::Combo(caption, itemindex, &charitems[0], itemCount, itemCount);
+		if (res) { updated = true; };
+		return res;
+	}
+
+	bool UIOverlay::button(const char *caption)
+	{
+		bool res = ImGui::Button(caption);
+		if (res) { updated = true; };
+		return res;
+	}
+
+	void UIOverlay::text(const char *formatstr, ...)
+	{
+		va_list args;
+		va_start(args, formatstr);
+		ImGui::TextV(formatstr, args);
+		va_end(args);
+	}
+}
\ No newline at end of file
diff --git a/external/Vulkan/base/VulkanUIOverlay.h b/external/Vulkan/base/VulkanUIOverlay.h
new file mode 100644
index 0000000000000000000000000000000000000000..afb46ed62318ec7d7a390492313512726f22fc84
--- /dev/null
+++ b/external/Vulkan/base/VulkanUIOverlay.h
@@ -0,0 +1,91 @@
+/*
+* UI overlay class using ImGui
+*
+* Copyright (C) 2017 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#pragma once
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <vector>
+#include <sstream>
+#include <iomanip>
+
+#include <vulkan/vulkan.h>
+#include "VulkanTools.h"
+#include "VulkanDebug.h"
+#include "VulkanBuffer.h"
+#include "VulkanDevice.h"
+
+#include "../external/imgui/imgui.h"
+
+#if defined(__ANDROID__)
+#include "VulkanAndroid.h"
+#endif
+
+namespace vks 
+{
+	class UIOverlay 
+	{
+	public:
+		vks::VulkanDevice *device;
+		VkQueue queue;
+
+		VkSampleCountFlagBits rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
+		uint32_t subpass = 0;
+
+		vks::Buffer vertexBuffer;
+		vks::Buffer indexBuffer;
+		int32_t vertexCount = 0;
+		int32_t indexCount = 0;
+
+		std::vector<VkPipelineShaderStageCreateInfo> shaders;
+
+		VkDescriptorPool descriptorPool;
+		VkDescriptorSetLayout descriptorSetLayout;
+		VkDescriptorSet descriptorSet;
+		VkPipelineLayout pipelineLayout;
+		VkPipeline pipeline;
+
+		VkDeviceMemory fontMemory = VK_NULL_HANDLE;
+		VkImage fontImage = VK_NULL_HANDLE;
+		VkImageView fontView = VK_NULL_HANDLE;
+		VkSampler sampler;
+
+		struct PushConstBlock {
+			glm::vec2 scale;
+			glm::vec2 translate;
+		} pushConstBlock;
+
+		bool visible = true;
+		bool updated = false;
+		float scale = 1.0f;
+
+		UIOverlay();
+		~UIOverlay();
+
+		void preparePipeline(const VkPipelineCache pipelineCache, const VkRenderPass renderPass);
+		void prepareResources();
+
+		bool update();
+		void draw(const VkCommandBuffer commandBuffer);
+		void resize(uint32_t width, uint32_t height);
+
+		void freeResources();
+
+		bool header(const char* caption);
+		bool checkBox(const char* caption, bool* value);
+		bool checkBox(const char* caption, int32_t* value);
+		bool inputFloat(const char* caption, float* value, float step, uint32_t precision);
+		bool sliderFloat(const char* caption, float* value, float min, float max);
+		bool sliderInt(const char* caption, int32_t* value, int32_t min, int32_t max);
+		bool comboBox(const char* caption, int32_t* itemindex, std::vector<std::string> items);
+		bool button(const char* caption);
+		void text(const char* formatstr, ...);
+	};
+}
\ No newline at end of file
diff --git a/external/Vulkan/base/VulkanglTFModel.cpp b/external/Vulkan/base/VulkanglTFModel.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..324f1f55ac28233293147995f251423677f220bb
--- /dev/null
+++ b/external/Vulkan/base/VulkanglTFModel.cpp
@@ -0,0 +1,1580 @@
+
+/*
+* Vulkan glTF model and texture loading class based on tinyglTF (https://github.com/syoyo/tinygltf)
+*
+* Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#define TINYGLTF_IMPLEMENTATION
+#define STB_IMAGE_IMPLEMENTATION
+#define TINYGLTF_NO_STB_IMAGE_WRITE
+
+#include "VulkanglTFModel.h"
+
+VkDescriptorSetLayout vkglTF::descriptorSetLayoutImage = VK_NULL_HANDLE;
+VkDescriptorSetLayout vkglTF::descriptorSetLayoutUbo = VK_NULL_HANDLE;
+VkMemoryPropertyFlags vkglTF::memoryPropertyFlags = 0;
+uint32_t vkglTF::descriptorBindingFlags = vkglTF::DescriptorBindingFlags::ImageBaseColor;
+
+/*
+	We use a custom image loading function with tinyglTF, so we can do custom stuff loading ktx textures
+*/
+bool loadImageDataFunc(tinygltf::Image* image, const int imageIndex, std::string* error, std::string* warning, int req_width, int req_height, const unsigned char* bytes, int size, void* userData)
+{
+	// KTX files will be handled by our own code
+	if (image->uri.find_last_of(".") != std::string::npos) {
+		if (image->uri.substr(image->uri.find_last_of(".") + 1) == "ktx") {
+			return true;
+		}
+	}
+
+	return tinygltf::LoadImageData(image, imageIndex, error, warning, req_width, req_height, bytes, size, userData);
+}
+
+bool loadImageDataFuncEmpty(tinygltf::Image* image, const int imageIndex, std::string* error, std::string* warning, int req_width, int req_height, const unsigned char* bytes, int size, void* userData) 
+{
+	// This function will be used for samples that don't require images to be loaded
+	return true;
+}
+
+
+/*
+	glTF texture loading class
+*/
+
+void vkglTF::Texture::updateDescriptor()
+{
+	descriptor.sampler = sampler;
+	descriptor.imageView = view;
+	descriptor.imageLayout = imageLayout;
+}
+
+void vkglTF::Texture::destroy()
+{
+	if (device)
+	{
+		vkDestroyImageView(device->logicalDevice, view, nullptr);
+		vkDestroyImage(device->logicalDevice, image, nullptr);
+		vkFreeMemory(device->logicalDevice, deviceMemory, nullptr);
+		vkDestroySampler(device->logicalDevice, sampler, nullptr);
+	}
+}
+
+void vkglTF::Texture::fromglTfImage(tinygltf::Image &gltfimage, std::string path, vks::VulkanDevice *device, VkQueue copyQueue)
+{
+	this->device = device;
+
+	bool isKtx = false;
+	// Image points to an external ktx file
+	if (gltfimage.uri.find_last_of(".") != std::string::npos) {
+		if (gltfimage.uri.substr(gltfimage.uri.find_last_of(".") + 1) == "ktx") {
+			isKtx = true;
+		}
+	}
+
+	VkFormat format;
+
+	if (!isKtx) {
+		// Texture was loaded using STB_Image
+
+		unsigned char* buffer = nullptr;
+		VkDeviceSize bufferSize = 0;
+		bool deleteBuffer = false;
+		if (gltfimage.component == 3) {
+			// Most devices don't support RGB only on Vulkan so convert if necessary
+			// TODO: Check actual format support and transform only if required
+			bufferSize = gltfimage.width * gltfimage.height * 4;
+			buffer = new unsigned char[bufferSize];
+			unsigned char* rgba = buffer;
+			unsigned char* rgb = &gltfimage.image[0];
+			for (size_t i = 0; i < gltfimage.width * gltfimage.height; ++i) {
+				for (int32_t j = 0; j < 3; ++j) {
+					rgba[j] = rgb[j];
+				}
+				rgba += 4;
+				rgb += 3;
+			}
+			deleteBuffer = true;
+		}
+		else {
+			buffer = &gltfimage.image[0];
+			bufferSize = gltfimage.image.size();
+		}
+
+		format = VK_FORMAT_R8G8B8A8_UNORM;
+
+		VkFormatProperties formatProperties;
+
+		width = gltfimage.width;
+		height = gltfimage.height;
+		mipLevels = static_cast<uint32_t>(floor(log2(std::max(width, height))) + 1.0);
+
+		vkGetPhysicalDeviceFormatProperties(device->physicalDevice, format, &formatProperties);
+		assert(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT);
+		assert(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT);
+
+		VkMemoryAllocateInfo memAllocInfo{};
+		memAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+		VkMemoryRequirements memReqs{};
+
+		VkBuffer stagingBuffer;
+		VkDeviceMemory stagingMemory;
+
+		VkBufferCreateInfo bufferCreateInfo{};
+		bufferCreateInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+		bufferCreateInfo.size = bufferSize;
+		bufferCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
+		bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+		VK_CHECK_RESULT(vkCreateBuffer(device->logicalDevice, &bufferCreateInfo, nullptr, &stagingBuffer));
+		vkGetBufferMemoryRequirements(device->logicalDevice, stagingBuffer, &memReqs);
+		memAllocInfo.allocationSize = memReqs.size;
+		memAllocInfo.memoryTypeIndex = device->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
+		VK_CHECK_RESULT(vkAllocateMemory(device->logicalDevice, &memAllocInfo, nullptr, &stagingMemory));
+		VK_CHECK_RESULT(vkBindBufferMemory(device->logicalDevice, stagingBuffer, stagingMemory, 0));
+
+		uint8_t* data;
+		VK_CHECK_RESULT(vkMapMemory(device->logicalDevice, stagingMemory, 0, memReqs.size, 0, (void**)&data));
+		memcpy(data, buffer, bufferSize);
+		vkUnmapMemory(device->logicalDevice, stagingMemory);
+
+		VkImageCreateInfo imageCreateInfo{};
+		imageCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
+		imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
+		imageCreateInfo.format = format;
+		imageCreateInfo.mipLevels = mipLevels;
+		imageCreateInfo.arrayLayers = 1;
+		imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
+		imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
+		imageCreateInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT;
+		imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+		imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+		imageCreateInfo.extent = { width, height, 1 };
+		imageCreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
+		VK_CHECK_RESULT(vkCreateImage(device->logicalDevice, &imageCreateInfo, nullptr, &image));
+		vkGetImageMemoryRequirements(device->logicalDevice, image, &memReqs);
+		memAllocInfo.allocationSize = memReqs.size;
+		memAllocInfo.memoryTypeIndex = device->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+		VK_CHECK_RESULT(vkAllocateMemory(device->logicalDevice, &memAllocInfo, nullptr, &deviceMemory));
+		VK_CHECK_RESULT(vkBindImageMemory(device->logicalDevice, image, deviceMemory, 0));
+
+		VkCommandBuffer copyCmd = device->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+
+		VkImageSubresourceRange subresourceRange = {};
+		subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+		subresourceRange.levelCount = 1;
+		subresourceRange.layerCount = 1;
+
+		{
+			VkImageMemoryBarrier imageMemoryBarrier{};
+			imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+			imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+			imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+			imageMemoryBarrier.srcAccessMask = 0;
+			imageMemoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+			imageMemoryBarrier.image = image;
+			imageMemoryBarrier.subresourceRange = subresourceRange;
+			vkCmdPipelineBarrier(copyCmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr, 1, &imageMemoryBarrier);
+		}
+
+		VkBufferImageCopy bufferCopyRegion = {};
+		bufferCopyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+		bufferCopyRegion.imageSubresource.mipLevel = 0;
+		bufferCopyRegion.imageSubresource.baseArrayLayer = 0;
+		bufferCopyRegion.imageSubresource.layerCount = 1;
+		bufferCopyRegion.imageExtent.width = width;
+		bufferCopyRegion.imageExtent.height = height;
+		bufferCopyRegion.imageExtent.depth = 1;
+
+		vkCmdCopyBufferToImage(copyCmd, stagingBuffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &bufferCopyRegion);
+
+		{
+			VkImageMemoryBarrier imageMemoryBarrier{};
+			imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+			imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+			imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
+			imageMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+			imageMemoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
+			imageMemoryBarrier.image = image;
+			imageMemoryBarrier.subresourceRange = subresourceRange;
+			vkCmdPipelineBarrier(copyCmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr, 1, &imageMemoryBarrier);
+		}
+
+		device->flushCommandBuffer(copyCmd, copyQueue, true);
+
+		vkFreeMemory(device->logicalDevice, stagingMemory, nullptr);
+		vkDestroyBuffer(device->logicalDevice, stagingBuffer, nullptr);
+
+		// Generate the mip chain (glTF uses jpg and png, so we need to create this manually)
+		VkCommandBuffer blitCmd = device->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+		for (uint32_t i = 1; i < mipLevels; i++) {
+			VkImageBlit imageBlit{};
+
+			imageBlit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+			imageBlit.srcSubresource.layerCount = 1;
+			imageBlit.srcSubresource.mipLevel = i - 1;
+			imageBlit.srcOffsets[1].x = int32_t(width >> (i - 1));
+			imageBlit.srcOffsets[1].y = int32_t(height >> (i - 1));
+			imageBlit.srcOffsets[1].z = 1;
+
+			imageBlit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+			imageBlit.dstSubresource.layerCount = 1;
+			imageBlit.dstSubresource.mipLevel = i;
+			imageBlit.dstOffsets[1].x = int32_t(width >> i);
+			imageBlit.dstOffsets[1].y = int32_t(height >> i);
+			imageBlit.dstOffsets[1].z = 1;
+
+			VkImageSubresourceRange mipSubRange = {};
+			mipSubRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+			mipSubRange.baseMipLevel = i;
+			mipSubRange.levelCount = 1;
+			mipSubRange.layerCount = 1;
+
+			{
+				VkImageMemoryBarrier imageMemoryBarrier{};
+				imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+				imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+				imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+				imageMemoryBarrier.srcAccessMask = 0;
+				imageMemoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+				imageMemoryBarrier.image = image;
+				imageMemoryBarrier.subresourceRange = mipSubRange;
+				vkCmdPipelineBarrier(blitCmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &imageMemoryBarrier);
+			}
+
+			vkCmdBlitImage(blitCmd, image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &imageBlit, VK_FILTER_LINEAR);
+
+			{
+				VkImageMemoryBarrier imageMemoryBarrier{};
+				imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+				imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+				imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
+				imageMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+				imageMemoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
+				imageMemoryBarrier.image = image;
+				imageMemoryBarrier.subresourceRange = mipSubRange;
+				vkCmdPipelineBarrier(blitCmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &imageMemoryBarrier);
+			}
+		}
+
+		subresourceRange.levelCount = mipLevels;
+		imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+
+		{
+			VkImageMemoryBarrier imageMemoryBarrier{};
+			imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+			imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
+			imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+			imageMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+			imageMemoryBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
+			imageMemoryBarrier.image = image;
+			imageMemoryBarrier.subresourceRange = subresourceRange;
+			vkCmdPipelineBarrier(blitCmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr, 1, &imageMemoryBarrier);
+		}
+
+		device->flushCommandBuffer(blitCmd, copyQueue, true);
+	}
+	else {
+		// Texture is stored in an external ktx file
+		std::string filename = path + "/" + gltfimage.uri;
+
+		ktxTexture* ktxTexture;
+
+		ktxResult result = KTX_SUCCESS;
+#if defined(__ANDROID__)
+		AAsset* asset = AAssetManager_open(androidApp->activity->assetManager, filename.c_str(), AASSET_MODE_STREAMING);
+		if (!asset) {
+			vks::tools::exitFatal("Could not load texture from " + filename + "\n\nThe file may be part of the additional asset pack.\n\nRun \"download_assets.py\" in the repository root to download the latest version.", -1);
+		}
+		size_t size = AAsset_getLength(asset);
+		assert(size > 0);
+		ktx_uint8_t* textureData = new ktx_uint8_t[size];
+		AAsset_read(asset, textureData, size);
+		AAsset_close(asset);
+		result = ktxTexture_CreateFromMemory(textureData, size, KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, &ktxTexture);
+		delete[] textureData;
+#else
+		if (!vks::tools::fileExists(filename)) {
+			vks::tools::exitFatal("Could not load texture from " + filename + "\n\nThe file may be part of the additional asset pack.\n\nRun \"download_assets.py\" in the repository root to download the latest version.", -1);
+		}
+		result = ktxTexture_CreateFromNamedFile(filename.c_str(), KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, &ktxTexture);
+#endif		
+		assert(result == KTX_SUCCESS);
+
+		this->device = device;
+		width = ktxTexture->baseWidth;
+		height = ktxTexture->baseHeight;
+		mipLevels = ktxTexture->numLevels;
+
+		ktx_uint8_t* ktxTextureData = ktxTexture_GetData(ktxTexture);
+		ktx_size_t ktxTextureSize = ktxTexture_GetSize(ktxTexture);
+		// @todo: Use ktxTexture_GetVkFormat(ktxTexture)
+		format = VK_FORMAT_R8G8B8A8_UNORM;
+
+		// Get device properties for the requested texture format
+		VkFormatProperties formatProperties;
+		vkGetPhysicalDeviceFormatProperties(device->physicalDevice, format, &formatProperties);
+
+		VkCommandBuffer copyCmd = device->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+		VkBuffer stagingBuffer;
+		VkDeviceMemory stagingMemory;
+
+		VkBufferCreateInfo bufferCreateInfo = vks::initializers::bufferCreateInfo();
+		bufferCreateInfo.size = ktxTextureSize;
+		// This buffer is used as a transfer source for the buffer copy
+		bufferCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
+		bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+		VK_CHECK_RESULT(vkCreateBuffer(device->logicalDevice, &bufferCreateInfo, nullptr, &stagingBuffer));
+
+		VkMemoryAllocateInfo memAllocInfo = vks::initializers::memoryAllocateInfo();
+		VkMemoryRequirements memReqs;
+		vkGetBufferMemoryRequirements(device->logicalDevice, stagingBuffer, &memReqs);
+		memAllocInfo.allocationSize = memReqs.size;
+		memAllocInfo.memoryTypeIndex = device->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
+		VK_CHECK_RESULT(vkAllocateMemory(device->logicalDevice, &memAllocInfo, nullptr, &stagingMemory));
+		VK_CHECK_RESULT(vkBindBufferMemory(device->logicalDevice, stagingBuffer, stagingMemory, 0));
+
+		uint8_t* data;
+		VK_CHECK_RESULT(vkMapMemory(device->logicalDevice, stagingMemory, 0, memReqs.size, 0, (void**)&data));
+		memcpy(data, ktxTextureData, ktxTextureSize);
+		vkUnmapMemory(device->logicalDevice, stagingMemory);
+
+		std::vector<VkBufferImageCopy> bufferCopyRegions;
+		for (uint32_t i = 0; i < mipLevels; i++)
+		{
+			ktx_size_t offset;
+			KTX_error_code result = ktxTexture_GetImageOffset(ktxTexture, i, 0, 0, &offset);
+			assert(result == KTX_SUCCESS);
+			VkBufferImageCopy bufferCopyRegion = {};
+			bufferCopyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+			bufferCopyRegion.imageSubresource.mipLevel = i;
+			bufferCopyRegion.imageSubresource.baseArrayLayer = 0;
+			bufferCopyRegion.imageSubresource.layerCount = 1;
+			bufferCopyRegion.imageExtent.width = std::max(1u, ktxTexture->baseWidth >> i);
+			bufferCopyRegion.imageExtent.height = std::max(1u, ktxTexture->baseHeight >> i);
+			bufferCopyRegion.imageExtent.depth = 1;
+			bufferCopyRegion.bufferOffset = offset;
+			bufferCopyRegions.push_back(bufferCopyRegion);
+		}
+
+		// Create optimal tiled target image
+		VkImageCreateInfo imageCreateInfo = vks::initializers::imageCreateInfo();
+		imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
+		imageCreateInfo.format = format;
+		imageCreateInfo.mipLevels = mipLevels;
+		imageCreateInfo.arrayLayers = 1;
+		imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
+		imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
+		imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+		imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+		imageCreateInfo.extent = { width, height, 1 };
+		imageCreateInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
+		VK_CHECK_RESULT(vkCreateImage(device->logicalDevice, &imageCreateInfo, nullptr, &image));
+
+		vkGetImageMemoryRequirements(device->logicalDevice, image, &memReqs);
+		memAllocInfo.allocationSize = memReqs.size;
+		memAllocInfo.memoryTypeIndex = device->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+		VK_CHECK_RESULT(vkAllocateMemory(device->logicalDevice, &memAllocInfo, nullptr, &deviceMemory));
+		VK_CHECK_RESULT(vkBindImageMemory(device->logicalDevice, image, deviceMemory, 0));
+
+		VkImageSubresourceRange subresourceRange = {};
+		subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+		subresourceRange.baseMipLevel = 0;
+		subresourceRange.levelCount = mipLevels;
+		subresourceRange.layerCount = 1;
+
+		vks::tools::setImageLayout(copyCmd, image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, subresourceRange);
+		vkCmdCopyBufferToImage(copyCmd, stagingBuffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, static_cast<uint32_t>(bufferCopyRegions.size()), bufferCopyRegions.data());
+		vks::tools::setImageLayout(copyCmd, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, subresourceRange);
+		device->flushCommandBuffer(copyCmd, copyQueue);
+		this->imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+
+		vkFreeMemory(device->logicalDevice, stagingMemory, nullptr);
+		vkDestroyBuffer(device->logicalDevice, stagingBuffer, nullptr);
+
+		ktxTexture_Destroy(ktxTexture);
+	}
+
+	VkSamplerCreateInfo samplerInfo{};
+	samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
+	samplerInfo.magFilter = VK_FILTER_LINEAR;
+	samplerInfo.minFilter = VK_FILTER_LINEAR;
+	samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+	samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
+	samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
+	samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
+	samplerInfo.compareOp = VK_COMPARE_OP_NEVER;
+	samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
+	samplerInfo.maxAnisotropy = 1.0;
+	samplerInfo.anisotropyEnable = VK_FALSE;
+	samplerInfo.maxLod = (float)mipLevels;
+	samplerInfo.maxAnisotropy = 8.0f;
+	samplerInfo.anisotropyEnable = VK_TRUE;
+	VK_CHECK_RESULT(vkCreateSampler(device->logicalDevice, &samplerInfo, nullptr, &sampler));
+
+	VkImageViewCreateInfo viewInfo{};
+	viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+	viewInfo.image = image;
+	viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
+	viewInfo.format = format;
+	viewInfo.components = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A };
+	viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+	viewInfo.subresourceRange.layerCount = 1;
+	viewInfo.subresourceRange.levelCount = mipLevels;
+	VK_CHECK_RESULT(vkCreateImageView(device->logicalDevice, &viewInfo, nullptr, &view));
+
+	descriptor.sampler = sampler;
+	descriptor.imageView = view;
+	descriptor.imageLayout = imageLayout;
+}
+
+/*
+	glTF material
+*/
+void vkglTF::Material::createDescriptorSet(VkDescriptorPool descriptorPool, VkDescriptorSetLayout descriptorSetLayout, uint32_t descriptorBindingFlags)
+{
+	VkDescriptorSetAllocateInfo descriptorSetAllocInfo{};
+	descriptorSetAllocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
+	descriptorSetAllocInfo.descriptorPool = descriptorPool;
+	descriptorSetAllocInfo.pSetLayouts = &descriptorSetLayout;
+	descriptorSetAllocInfo.descriptorSetCount = 1;
+	VK_CHECK_RESULT(vkAllocateDescriptorSets(device->logicalDevice, &descriptorSetAllocInfo, &descriptorSet));
+	std::vector<VkDescriptorImageInfo> imageDescriptors{};
+	std::vector<VkWriteDescriptorSet> writeDescriptorSets{};
+	if (descriptorBindingFlags & DescriptorBindingFlags::ImageBaseColor) {
+		imageDescriptors.push_back(baseColorTexture->descriptor);
+		VkWriteDescriptorSet writeDescriptorSet{};
+		writeDescriptorSet.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+		writeDescriptorSet.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+		writeDescriptorSet.descriptorCount = 1;
+		writeDescriptorSet.dstSet = descriptorSet;
+		writeDescriptorSet.dstBinding = static_cast<uint32_t>(writeDescriptorSets.size());
+		writeDescriptorSet.pImageInfo = &baseColorTexture->descriptor;
+		writeDescriptorSets.push_back(writeDescriptorSet);
+	}
+	if (normalTexture && descriptorBindingFlags & DescriptorBindingFlags::ImageNormalMap) {
+		imageDescriptors.push_back(normalTexture->descriptor);
+		VkWriteDescriptorSet writeDescriptorSet{};
+		writeDescriptorSet.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+		writeDescriptorSet.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+		writeDescriptorSet.descriptorCount = 1;
+		writeDescriptorSet.dstSet = descriptorSet;
+		writeDescriptorSet.dstBinding = static_cast<uint32_t>(writeDescriptorSets.size());
+		writeDescriptorSet.pImageInfo = &normalTexture->descriptor;
+		writeDescriptorSets.push_back(writeDescriptorSet);
+	}
+	vkUpdateDescriptorSets(device->logicalDevice, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr);
+}
+
+
+/*
+	glTF primitive
+*/
+void vkglTF::Primitive::setDimensions(glm::vec3 min, glm::vec3 max) {
+	dimensions.min = min;
+	dimensions.max = max;
+	dimensions.size = max - min;
+	dimensions.center = (min + max) / 2.0f;
+	dimensions.radius = glm::distance(min, max) / 2.0f;
+}
+
+/*
+	glTF mesh
+*/
+vkglTF::Mesh::Mesh(vks::VulkanDevice *device, glm::mat4 matrix) {
+	this->device = device;
+	this->uniformBlock.matrix = matrix;
+	VK_CHECK_RESULT(device->createBuffer(
+		VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+		VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+		sizeof(uniformBlock),
+		&uniformBuffer.buffer,
+		&uniformBuffer.memory,
+		&uniformBlock));
+	VK_CHECK_RESULT(vkMapMemory(device->logicalDevice, uniformBuffer.memory, 0, sizeof(uniformBlock), 0, &uniformBuffer.mapped));
+	uniformBuffer.descriptor = { uniformBuffer.buffer, 0, sizeof(uniformBlock) };
+};
+
+vkglTF::Mesh::~Mesh() {
+	vkDestroyBuffer(device->logicalDevice, uniformBuffer.buffer, nullptr);
+	vkFreeMemory(device->logicalDevice, uniformBuffer.memory, nullptr);
+}
+
+/*
+	glTF node
+*/
+glm::mat4 vkglTF::Node::localMatrix() {
+	return glm::translate(glm::mat4(1.0f), translation) * glm::mat4(rotation) * glm::scale(glm::mat4(1.0f), scale) * matrix;
+}
+
+glm::mat4 vkglTF::Node::getMatrix() {
+	glm::mat4 m = localMatrix();
+	vkglTF::Node *p = parent;
+	while (p) {
+		m = p->localMatrix() * m;
+		p = p->parent;
+	}
+	return m;
+}
+
+void vkglTF::Node::update() {
+	if (mesh) {
+		glm::mat4 m = getMatrix();
+		if (skin) {
+			mesh->uniformBlock.matrix = m;
+			// Update join matrices
+			glm::mat4 inverseTransform = glm::inverse(m);
+			for (size_t i = 0; i < skin->joints.size(); i++) {
+				vkglTF::Node *jointNode = skin->joints[i];
+				glm::mat4 jointMat = jointNode->getMatrix() * skin->inverseBindMatrices[i];
+				jointMat = inverseTransform * jointMat;
+				mesh->uniformBlock.jointMatrix[i] = jointMat;
+			}
+			mesh->uniformBlock.jointcount = (float)skin->joints.size();
+			memcpy(mesh->uniformBuffer.mapped, &mesh->uniformBlock, sizeof(mesh->uniformBlock));
+		} else {
+			memcpy(mesh->uniformBuffer.mapped, &m, sizeof(glm::mat4));
+		}
+	}
+
+	for (auto& child : children) {
+		child->update();
+	}
+}
+
+vkglTF::Node::~Node() {
+	if (mesh) {
+		delete mesh;
+	}
+	for (auto& child : children) {
+		delete child;
+	}
+}
+
+/*
+	glTF default vertex layout with easy Vulkan mapping functions
+*/
+
+VkVertexInputBindingDescription vkglTF::Vertex::vertexInputBindingDescription;
+std::vector<VkVertexInputAttributeDescription> vkglTF::Vertex::vertexInputAttributeDescriptions;
+VkPipelineVertexInputStateCreateInfo vkglTF::Vertex::pipelineVertexInputStateCreateInfo;
+
+VkVertexInputBindingDescription vkglTF::Vertex::inputBindingDescription(uint32_t binding) {
+	return VkVertexInputBindingDescription({ binding, sizeof(Vertex), VK_VERTEX_INPUT_RATE_VERTEX });
+}
+
+VkVertexInputAttributeDescription vkglTF::Vertex::inputAttributeDescription(uint32_t binding, uint32_t location, VertexComponent component) {
+	switch (component) {
+		case VertexComponent::Position: 
+			return VkVertexInputAttributeDescription({ location, binding, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, pos) });
+		case VertexComponent::Normal:
+			return VkVertexInputAttributeDescription({ location, binding, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, normal) });
+		case VertexComponent::UV:
+			return VkVertexInputAttributeDescription({ location, binding, VK_FORMAT_R32G32_SFLOAT, offsetof(Vertex, uv) });
+		case VertexComponent::Color:
+			return VkVertexInputAttributeDescription({ location, binding, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(Vertex, color) });
+		case VertexComponent::Tangent:
+			return VkVertexInputAttributeDescription({ location, binding, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(Vertex, tangent)} );
+		case VertexComponent::Joint0:
+			return VkVertexInputAttributeDescription({ location, binding, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(Vertex, joint0) });
+		case VertexComponent::Weight0:
+			return VkVertexInputAttributeDescription({ location, binding, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(Vertex, weight0) });
+		default:
+			return VkVertexInputAttributeDescription({});
+	}
+}
+
+std::vector<VkVertexInputAttributeDescription> vkglTF::Vertex::inputAttributeDescriptions(uint32_t binding, const std::vector<VertexComponent> components) {
+	std::vector<VkVertexInputAttributeDescription> result;
+	uint32_t location = 0;
+	for (VertexComponent component : components) {
+		result.push_back(Vertex::inputAttributeDescription(binding, location, component));
+		location++;
+	}
+	return result;
+}
+
+/** @brief Returns the default pipeline vertex input state create info structure for the requested vertex components */
+VkPipelineVertexInputStateCreateInfo* vkglTF::Vertex::getPipelineVertexInputState(const std::vector<VertexComponent> components) {
+	vertexInputBindingDescription = Vertex::inputBindingDescription(0);
+	Vertex::vertexInputAttributeDescriptions = Vertex::inputAttributeDescriptions(0, components);
+	pipelineVertexInputStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
+	pipelineVertexInputStateCreateInfo.vertexBindingDescriptionCount = 1;
+	pipelineVertexInputStateCreateInfo.pVertexBindingDescriptions = &Vertex::vertexInputBindingDescription;
+	pipelineVertexInputStateCreateInfo.vertexAttributeDescriptionCount = static_cast<uint32_t>(Vertex::vertexInputAttributeDescriptions.size());
+	pipelineVertexInputStateCreateInfo.pVertexAttributeDescriptions = Vertex::vertexInputAttributeDescriptions.data();
+	return &pipelineVertexInputStateCreateInfo;
+}
+
+vkglTF::Texture* vkglTF::Model::getTexture(uint32_t index)
+{
+
+	if (index < textures.size()) {
+		return &textures[index];
+	}
+	return nullptr;
+}
+
+void vkglTF::Model::createEmptyTexture(VkQueue transferQueue)
+{
+	emptyTexture.device = device;
+	emptyTexture.width = 1;
+	emptyTexture.height = 1;
+	emptyTexture.layerCount = 1;
+	emptyTexture.mipLevels = 1;
+
+	size_t bufferSize = emptyTexture.width * emptyTexture.height * 4;
+	unsigned char* buffer = new unsigned char[bufferSize];
+	memset(buffer, 0, bufferSize);
+
+	VkBuffer stagingBuffer;
+	VkDeviceMemory stagingMemory;
+	VkBufferCreateInfo bufferCreateInfo = vks::initializers::bufferCreateInfo();
+	bufferCreateInfo.size = bufferSize;
+	// This buffer is used as a transfer source for the buffer copy
+	bufferCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
+	bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+	VK_CHECK_RESULT(vkCreateBuffer(device->logicalDevice, &bufferCreateInfo, nullptr, &stagingBuffer));
+
+	VkMemoryAllocateInfo memAllocInfo = vks::initializers::memoryAllocateInfo();
+	VkMemoryRequirements memReqs;
+	vkGetBufferMemoryRequirements(device->logicalDevice, stagingBuffer, &memReqs);
+	memAllocInfo.allocationSize = memReqs.size;
+	memAllocInfo.memoryTypeIndex = device->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
+	VK_CHECK_RESULT(vkAllocateMemory(device->logicalDevice, &memAllocInfo, nullptr, &stagingMemory));
+	VK_CHECK_RESULT(vkBindBufferMemory(device->logicalDevice, stagingBuffer, stagingMemory, 0));
+
+	// Copy texture data into staging buffer
+	uint8_t* data;
+	VK_CHECK_RESULT(vkMapMemory(device->logicalDevice, stagingMemory, 0, memReqs.size, 0, (void**)&data));
+	memcpy(data, buffer, bufferSize);
+	vkUnmapMemory(device->logicalDevice, stagingMemory);
+
+	VkBufferImageCopy bufferCopyRegion = {};
+	bufferCopyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+	bufferCopyRegion.imageSubresource.layerCount = 1;
+	bufferCopyRegion.imageExtent.width = emptyTexture.width;
+	bufferCopyRegion.imageExtent.height = emptyTexture.height;
+	bufferCopyRegion.imageExtent.depth = 1;
+
+	// Create optimal tiled target image
+	VkImageCreateInfo imageCreateInfo = vks::initializers::imageCreateInfo();
+	imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
+	imageCreateInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
+	imageCreateInfo.mipLevels = 1;
+	imageCreateInfo.arrayLayers = 1;
+	imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
+	imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
+	imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+	imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+	imageCreateInfo.extent = { emptyTexture.width, emptyTexture.height, 1 };
+	imageCreateInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
+	VK_CHECK_RESULT(vkCreateImage(device->logicalDevice, &imageCreateInfo, nullptr, &emptyTexture.image));
+
+	vkGetImageMemoryRequirements(device->logicalDevice, emptyTexture.image, &memReqs);
+	memAllocInfo.allocationSize = memReqs.size;
+	memAllocInfo.memoryTypeIndex = device->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+	VK_CHECK_RESULT(vkAllocateMemory(device->logicalDevice, &memAllocInfo, nullptr, &emptyTexture.deviceMemory));
+	VK_CHECK_RESULT(vkBindImageMemory(device->logicalDevice, emptyTexture.image, emptyTexture.deviceMemory, 0));
+
+	VkImageSubresourceRange subresourceRange{};
+	subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+	subresourceRange.baseMipLevel = 0;
+	subresourceRange.levelCount = 1;
+	subresourceRange.layerCount = 1;
+
+	VkCommandBuffer copyCmd = device->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+	vks::tools::setImageLayout(copyCmd, emptyTexture.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, subresourceRange);
+	vkCmdCopyBufferToImage(copyCmd, stagingBuffer, emptyTexture.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &bufferCopyRegion);
+	vks::tools::setImageLayout(copyCmd, emptyTexture.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, subresourceRange);
+	device->flushCommandBuffer(copyCmd, transferQueue);
+	emptyTexture.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+
+	// Clean up staging resources
+	vkFreeMemory(device->logicalDevice, stagingMemory, nullptr);
+	vkDestroyBuffer(device->logicalDevice, stagingBuffer, nullptr);
+
+	VkSamplerCreateInfo samplerCreateInfo = vks::initializers::samplerCreateInfo();
+	samplerCreateInfo.magFilter = VK_FILTER_LINEAR;
+	samplerCreateInfo.minFilter = VK_FILTER_LINEAR;
+	samplerCreateInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+	samplerCreateInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+	samplerCreateInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+	samplerCreateInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+	samplerCreateInfo.compareOp = VK_COMPARE_OP_NEVER;
+	samplerCreateInfo.maxAnisotropy = 1.0f;
+	VK_CHECK_RESULT(vkCreateSampler(device->logicalDevice, &samplerCreateInfo, nullptr, &emptyTexture.sampler));
+
+	VkImageViewCreateInfo viewCreateInfo = vks::initializers::imageViewCreateInfo();
+	viewCreateInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
+	viewCreateInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
+	viewCreateInfo.components = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A };
+	viewCreateInfo.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 };
+	viewCreateInfo.subresourceRange.levelCount = 1;
+	viewCreateInfo.image = emptyTexture.image;
+	VK_CHECK_RESULT(vkCreateImageView(device->logicalDevice, &viewCreateInfo, nullptr, &emptyTexture.view));
+
+	emptyTexture.descriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+	emptyTexture.descriptor.imageView = emptyTexture.view;
+	emptyTexture.descriptor.sampler = emptyTexture.sampler;
+}
+
+/*
+	glTF model loading and rendering class
+*/
+vkglTF::Model::~Model()
+{
+	vkDestroyBuffer(device->logicalDevice, vertices.buffer, nullptr);
+	vkFreeMemory(device->logicalDevice, vertices.memory, nullptr);
+	vkDestroyBuffer(device->logicalDevice, indices.buffer, nullptr);
+	vkFreeMemory(device->logicalDevice, indices.memory, nullptr);
+	for (auto texture : textures) {
+		texture.destroy();
+	}
+	for (auto node : nodes) {
+		delete node;
+	}
+	if (descriptorSetLayoutUbo != VK_NULL_HANDLE) {
+		vkDestroyDescriptorSetLayout(device->logicalDevice, descriptorSetLayoutUbo, nullptr);
+		descriptorSetLayoutUbo = VK_NULL_HANDLE;
+	}
+	if (descriptorSetLayoutImage != VK_NULL_HANDLE) {
+		vkDestroyDescriptorSetLayout(device->logicalDevice, descriptorSetLayoutImage, nullptr);
+		descriptorSetLayoutImage = VK_NULL_HANDLE;
+	}
+	vkDestroyDescriptorPool(device->logicalDevice, descriptorPool, nullptr);
+	emptyTexture.destroy();
+}
+
+void vkglTF::Model::loadNode(vkglTF::Node *parent, const tinygltf::Node &node, uint32_t nodeIndex, const tinygltf::Model &model, std::vector<uint32_t>& indexBuffer, std::vector<Vertex>& vertexBuffer, float globalscale)
+{
+	vkglTF::Node *newNode = new Node{};
+	newNode->index = nodeIndex;
+	newNode->parent = parent;
+	newNode->name = node.name;
+	newNode->skinIndex = node.skin;
+	newNode->matrix = glm::mat4(1.0f);
+
+	// Generate local node matrix
+	glm::vec3 translation = glm::vec3(0.0f);
+	if (node.translation.size() == 3) {
+		translation = glm::make_vec3(node.translation.data());
+		newNode->translation = translation;
+	}
+	glm::mat4 rotation = glm::mat4(1.0f);
+	if (node.rotation.size() == 4) {
+		glm::quat q = glm::make_quat(node.rotation.data());
+		newNode->rotation = glm::mat4(q);
+	}
+	glm::vec3 scale = glm::vec3(1.0f);
+	if (node.scale.size() == 3) {
+		scale = glm::make_vec3(node.scale.data());
+		newNode->scale = scale;
+	}
+	if (node.matrix.size() == 16) {
+		newNode->matrix = glm::make_mat4x4(node.matrix.data());
+		if (globalscale != 1.0f) {
+			//newNode->matrix = glm::scale(newNode->matrix, glm::vec3(globalscale));
+		}
+	};
+
+	// Node with children
+	if (node.children.size() > 0) {
+		for (auto i = 0; i < node.children.size(); i++) {
+			loadNode(newNode, model.nodes[node.children[i]], node.children[i], model, indexBuffer, vertexBuffer, globalscale);
+		}
+	}
+
+	// Node contains mesh data
+	if (node.mesh > -1) {
+		const tinygltf::Mesh mesh = model.meshes[node.mesh];
+		Mesh *newMesh = new Mesh(device, newNode->matrix);
+		newMesh->name = mesh.name;
+		for (size_t j = 0; j < mesh.primitives.size(); j++) {
+			const tinygltf::Primitive &primitive = mesh.primitives[j];
+			if (primitive.indices < 0) {
+				continue;
+			}
+			uint32_t indexStart = static_cast<uint32_t>(indexBuffer.size());
+			uint32_t vertexStart = static_cast<uint32_t>(vertexBuffer.size());
+			uint32_t indexCount = 0;
+			uint32_t vertexCount = 0;
+			glm::vec3 posMin{};
+			glm::vec3 posMax{};
+			bool hasSkin = false;
+			// Vertices
+			{
+				const float *bufferPos = nullptr;
+				const float *bufferNormals = nullptr;
+				const float *bufferTexCoords = nullptr;
+				const float* bufferColors = nullptr;
+				const float *bufferTangents = nullptr;
+				uint32_t numColorComponents;
+				const uint16_t *bufferJoints = nullptr;
+				const float *bufferWeights = nullptr;
+
+				// Position attribute is required
+				assert(primitive.attributes.find("POSITION") != primitive.attributes.end());
+
+				const tinygltf::Accessor &posAccessor = model.accessors[primitive.attributes.find("POSITION")->second];
+				const tinygltf::BufferView &posView = model.bufferViews[posAccessor.bufferView];
+				bufferPos = reinterpret_cast<const float *>(&(model.buffers[posView.buffer].data[posAccessor.byteOffset + posView.byteOffset]));
+				posMin = glm::vec3(posAccessor.minValues[0], posAccessor.minValues[1], posAccessor.minValues[2]);
+				posMax = glm::vec3(posAccessor.maxValues[0], posAccessor.maxValues[1], posAccessor.maxValues[2]);
+
+				if (primitive.attributes.find("NORMAL") != primitive.attributes.end()) {
+					const tinygltf::Accessor &normAccessor = model.accessors[primitive.attributes.find("NORMAL")->second];
+					const tinygltf::BufferView &normView = model.bufferViews[normAccessor.bufferView];
+					bufferNormals = reinterpret_cast<const float *>(&(model.buffers[normView.buffer].data[normAccessor.byteOffset + normView.byteOffset]));
+				}
+
+				if (primitive.attributes.find("TEXCOORD_0") != primitive.attributes.end()) {
+					const tinygltf::Accessor &uvAccessor = model.accessors[primitive.attributes.find("TEXCOORD_0")->second];
+					const tinygltf::BufferView &uvView = model.bufferViews[uvAccessor.bufferView];
+					bufferTexCoords = reinterpret_cast<const float *>(&(model.buffers[uvView.buffer].data[uvAccessor.byteOffset + uvView.byteOffset]));
+				}
+
+				if (primitive.attributes.find("COLOR_0") != primitive.attributes.end())
+				{
+					const tinygltf::Accessor& colorAccessor = model.accessors[primitive.attributes.find("COLOR_0")->second];
+					const tinygltf::BufferView& colorView = model.bufferViews[colorAccessor.bufferView];
+					// Color buffer are either of type vec3 or vec4
+					numColorComponents = colorAccessor.type == TINYGLTF_PARAMETER_TYPE_FLOAT_VEC3 ? 3 : 4;
+					bufferColors = reinterpret_cast<const float*>(&(model.buffers[colorView.buffer].data[colorAccessor.byteOffset + colorView.byteOffset]));
+				}
+
+				if (primitive.attributes.find("TANGENT") != primitive.attributes.end())
+				{
+					const tinygltf::Accessor &tangentAccessor = model.accessors[primitive.attributes.find("TANGENT")->second];
+					const tinygltf::BufferView &tangentView = model.bufferViews[tangentAccessor.bufferView];
+					bufferTangents = reinterpret_cast<const float *>(&(model.buffers[tangentView.buffer].data[tangentAccessor.byteOffset + tangentView.byteOffset]));
+				}
+
+				// Skinning
+				// Joints
+				if (primitive.attributes.find("JOINTS_0") != primitive.attributes.end()) {
+					const tinygltf::Accessor &jointAccessor = model.accessors[primitive.attributes.find("JOINTS_0")->second];
+					const tinygltf::BufferView &jointView = model.bufferViews[jointAccessor.bufferView];
+					bufferJoints = reinterpret_cast<const uint16_t *>(&(model.buffers[jointView.buffer].data[jointAccessor.byteOffset + jointView.byteOffset]));
+				}
+
+				if (primitive.attributes.find("WEIGHTS_0") != primitive.attributes.end()) {
+					const tinygltf::Accessor &uvAccessor = model.accessors[primitive.attributes.find("WEIGHTS_0")->second];
+					const tinygltf::BufferView &uvView = model.bufferViews[uvAccessor.bufferView];
+					bufferWeights = reinterpret_cast<const float *>(&(model.buffers[uvView.buffer].data[uvAccessor.byteOffset + uvView.byteOffset]));
+				}
+
+				hasSkin = (bufferJoints && bufferWeights);
+
+				vertexCount = static_cast<uint32_t>(posAccessor.count);
+
+				for (size_t v = 0; v < posAccessor.count; v++) {
+					Vertex vert{};
+					vert.pos = glm::vec4(glm::make_vec3(&bufferPos[v * 3]), 1.0f);
+					vert.normal = glm::normalize(glm::vec3(bufferNormals ? glm::make_vec3(&bufferNormals[v * 3]) : glm::vec3(0.0f)));
+					vert.uv = bufferTexCoords ? glm::make_vec2(&bufferTexCoords[v * 2]) : glm::vec3(0.0f);
+					if (bufferColors) {
+						switch (numColorComponents) {
+							case 3: 
+								vert.color = glm::vec4(glm::make_vec3(&bufferColors[v * 3]), 1.0f);
+							case 4:
+								vert.color = glm::make_vec4(&bufferColors[v * 4]);
+						}
+					}
+					else {
+						vert.color = glm::vec4(1.0f);
+					}
+					vert.tangent = bufferTangents ? glm::vec4(glm::make_vec4(&bufferTangents[v * 4])) : glm::vec4(0.0f);
+					vert.joint0 = hasSkin ? glm::vec4(glm::make_vec4(&bufferJoints[v * 4])) : glm::vec4(0.0f);
+					vert.weight0 = hasSkin ? glm::make_vec4(&bufferWeights[v * 4]) : glm::vec4(0.0f);
+					vertexBuffer.push_back(vert);
+				}
+			}
+			// Indices
+			{
+				const tinygltf::Accessor &accessor = model.accessors[primitive.indices];
+				const tinygltf::BufferView &bufferView = model.bufferViews[accessor.bufferView];
+				const tinygltf::Buffer &buffer = model.buffers[bufferView.buffer];
+
+				indexCount = static_cast<uint32_t>(accessor.count);
+
+				switch (accessor.componentType) {
+				case TINYGLTF_PARAMETER_TYPE_UNSIGNED_INT: {
+					uint32_t *buf = new uint32_t[accessor.count];
+					memcpy(buf, &buffer.data[accessor.byteOffset + bufferView.byteOffset], accessor.count * sizeof(uint32_t));
+					for (size_t index = 0; index < accessor.count; index++) {
+						indexBuffer.push_back(buf[index] + vertexStart);
+					}
+					break;
+				}
+				case TINYGLTF_PARAMETER_TYPE_UNSIGNED_SHORT: {
+					uint16_t *buf = new uint16_t[accessor.count];
+					memcpy(buf, &buffer.data[accessor.byteOffset + bufferView.byteOffset], accessor.count * sizeof(uint16_t));
+					for (size_t index = 0; index < accessor.count; index++) {
+						indexBuffer.push_back(buf[index] + vertexStart);
+					}
+					break;
+				}
+				case TINYGLTF_PARAMETER_TYPE_UNSIGNED_BYTE: {
+					uint8_t *buf = new uint8_t[accessor.count];
+					memcpy(buf, &buffer.data[accessor.byteOffset + bufferView.byteOffset], accessor.count * sizeof(uint8_t));
+					for (size_t index = 0; index < accessor.count; index++) {
+						indexBuffer.push_back(buf[index] + vertexStart);
+					}
+					break;
+				}
+				default:
+					std::cerr << "Index component type " << accessor.componentType << " not supported!" << std::endl;
+					return;
+				}
+			}
+			Primitive *newPrimitive = new Primitive(indexStart, indexCount, primitive.material > -1 ? materials[primitive.material] : materials.back());
+			newPrimitive->firstVertex = vertexStart;
+			newPrimitive->vertexCount = vertexCount;
+			newPrimitive->setDimensions(posMin, posMax);
+			newMesh->primitives.push_back(newPrimitive);
+		}
+		newNode->mesh = newMesh;
+	}
+	if (parent) {
+		parent->children.push_back(newNode);
+	} else {
+		nodes.push_back(newNode);
+	}
+	linearNodes.push_back(newNode);
+}
+
+void vkglTF::Model::loadSkins(tinygltf::Model &gltfModel)
+{
+	for (tinygltf::Skin &source : gltfModel.skins) {
+		Skin *newSkin = new Skin{};
+		newSkin->name = source.name;
+				
+		// Find skeleton root node
+		if (source.skeleton > -1) {
+			newSkin->skeletonRoot = nodeFromIndex(source.skeleton);
+		}
+
+		// Find joint nodes
+		for (int jointIndex : source.joints) {
+			Node* node = nodeFromIndex(jointIndex);
+			if (node) {
+				newSkin->joints.push_back(nodeFromIndex(jointIndex));
+			}
+		}
+
+		// Get inverse bind matrices from buffer
+		if (source.inverseBindMatrices > -1) {
+			const tinygltf::Accessor &accessor = gltfModel.accessors[source.inverseBindMatrices];
+			const tinygltf::BufferView &bufferView = gltfModel.bufferViews[accessor.bufferView];
+			const tinygltf::Buffer &buffer = gltfModel.buffers[bufferView.buffer];
+			newSkin->inverseBindMatrices.resize(accessor.count);
+			memcpy(newSkin->inverseBindMatrices.data(), &buffer.data[accessor.byteOffset + bufferView.byteOffset], accessor.count * sizeof(glm::mat4));
+		}
+
+		skins.push_back(newSkin);
+	}
+}
+
+void vkglTF::Model::loadImages(tinygltf::Model &gltfModel, vks::VulkanDevice *device, VkQueue transferQueue)
+{
+	for (tinygltf::Image &image : gltfModel.images) {
+		vkglTF::Texture texture;
+		texture.fromglTfImage(image, path, device, transferQueue);
+		textures.push_back(texture);
+	}
+	// Create an empty texture to be used for empty material images
+	createEmptyTexture(transferQueue);
+}
+
+void vkglTF::Model::loadMaterials(tinygltf::Model &gltfModel)
+{
+	for (tinygltf::Material &mat : gltfModel.materials) {
+		vkglTF::Material material(device);
+		if (mat.values.find("baseColorTexture") != mat.values.end()) {
+			material.baseColorTexture = getTexture(gltfModel.textures[mat.values["baseColorTexture"].TextureIndex()].source);
+		}
+		// Metallic roughness workflow
+		if (mat.values.find("metallicRoughnessTexture") != mat.values.end()) {
+			material.metallicRoughnessTexture = getTexture(gltfModel.textures[mat.values["metallicRoughnessTexture"].TextureIndex()].source);
+		}
+		if (mat.values.find("roughnessFactor") != mat.values.end()) {
+			material.roughnessFactor = static_cast<float>(mat.values["roughnessFactor"].Factor());
+		}
+		if (mat.values.find("metallicFactor") != mat.values.end()) {
+			material.metallicFactor = static_cast<float>(mat.values["metallicFactor"].Factor());
+		}
+		if (mat.values.find("baseColorFactor") != mat.values.end()) {
+			material.baseColorFactor = glm::make_vec4(mat.values["baseColorFactor"].ColorFactor().data());
+		}				
+		if (mat.additionalValues.find("normalTexture") != mat.additionalValues.end()) {
+			material.normalTexture = getTexture(gltfModel.textures[mat.additionalValues["normalTexture"].TextureIndex()].source);
+		} else {
+			material.normalTexture = &emptyTexture;
+		}
+		if (mat.additionalValues.find("emissiveTexture") != mat.additionalValues.end()) {
+			material.emissiveTexture = getTexture(gltfModel.textures[mat.additionalValues["emissiveTexture"].TextureIndex()].source);
+		}
+		if (mat.additionalValues.find("occlusionTexture") != mat.additionalValues.end()) {
+			material.occlusionTexture = getTexture(gltfModel.textures[mat.additionalValues["occlusionTexture"].TextureIndex()].source);
+		}
+		if (mat.additionalValues.find("alphaMode") != mat.additionalValues.end()) {
+			tinygltf::Parameter param = mat.additionalValues["alphaMode"];
+			if (param.string_value == "BLEND") {
+				material.alphaMode = Material::ALPHAMODE_BLEND;
+			}
+			if (param.string_value == "MASK") {
+				material.alphaMode = Material::ALPHAMODE_MASK;
+			}
+		}
+		if (mat.additionalValues.find("alphaCutoff") != mat.additionalValues.end()) {
+			material.alphaCutoff = static_cast<float>(mat.additionalValues["alphaCutoff"].Factor());
+		}
+
+		materials.push_back(material);
+	}
+	// Push a default material at the end of the list for meshes with no material assigned
+	materials.push_back(Material(device));
+}
+
+void vkglTF::Model::loadAnimations(tinygltf::Model &gltfModel)
+{
+	for (tinygltf::Animation &anim : gltfModel.animations) {
+		vkglTF::Animation animation{};
+		animation.name = anim.name;
+		if (anim.name.empty()) {
+			animation.name = std::to_string(animations.size());
+		}
+
+		// Samplers
+		for (auto &samp : anim.samplers) {
+			vkglTF::AnimationSampler sampler{};
+
+			if (samp.interpolation == "LINEAR") {
+				sampler.interpolation = AnimationSampler::InterpolationType::LINEAR;
+			}
+			if (samp.interpolation == "STEP") {
+				sampler.interpolation = AnimationSampler::InterpolationType::STEP;
+			}
+			if (samp.interpolation == "CUBICSPLINE") {
+				sampler.interpolation = AnimationSampler::InterpolationType::CUBICSPLINE;
+			}
+
+			// Read sampler input time values
+			{
+				const tinygltf::Accessor &accessor = gltfModel.accessors[samp.input];
+				const tinygltf::BufferView &bufferView = gltfModel.bufferViews[accessor.bufferView];
+				const tinygltf::Buffer &buffer = gltfModel.buffers[bufferView.buffer];
+
+				assert(accessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT);
+
+				float *buf = new float[accessor.count];
+				memcpy(buf, &buffer.data[accessor.byteOffset + bufferView.byteOffset], accessor.count * sizeof(float));
+				for (size_t index = 0; index < accessor.count; index++) {
+					sampler.inputs.push_back(buf[index]);
+				}
+
+				for (auto input : sampler.inputs) {
+					if (input < animation.start) {
+						animation.start = input;
+					};
+					if (input > animation.end) {
+						animation.end = input;
+					}
+				}
+			}
+
+			// Read sampler output T/R/S values 
+			{
+				const tinygltf::Accessor &accessor = gltfModel.accessors[samp.output];
+				const tinygltf::BufferView &bufferView = gltfModel.bufferViews[accessor.bufferView];
+				const tinygltf::Buffer &buffer = gltfModel.buffers[bufferView.buffer];
+
+				assert(accessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT);
+
+				switch (accessor.type) {
+				case TINYGLTF_TYPE_VEC3: {
+					glm::vec3 *buf = new glm::vec3[accessor.count];
+					memcpy(buf, &buffer.data[accessor.byteOffset + bufferView.byteOffset], accessor.count * sizeof(glm::vec3));
+					for (size_t index = 0; index < accessor.count; index++) {
+						sampler.outputsVec4.push_back(glm::vec4(buf[index], 0.0f));
+					}
+					break;
+				}
+				case TINYGLTF_TYPE_VEC4: {
+					glm::vec4 *buf = new glm::vec4[accessor.count];
+					memcpy(buf, &buffer.data[accessor.byteOffset + bufferView.byteOffset], accessor.count * sizeof(glm::vec4));
+					for (size_t index = 0; index < accessor.count; index++) {
+						sampler.outputsVec4.push_back(buf[index]);
+					}
+					break;
+				}
+				default: {
+					std::cout << "unknown type" << std::endl;
+					break;
+				}
+				}
+			}
+
+			animation.samplers.push_back(sampler);
+		}
+
+		// Channels
+		for (auto &source: anim.channels) {
+			vkglTF::AnimationChannel channel{};
+
+			if (source.target_path == "rotation") {
+				channel.path = AnimationChannel::PathType::ROTATION;
+			}
+			if (source.target_path == "translation") {
+				channel.path = AnimationChannel::PathType::TRANSLATION;
+			}
+			if (source.target_path == "scale") {
+				channel.path = AnimationChannel::PathType::SCALE;
+			}
+			if (source.target_path == "weights") {
+				std::cout << "weights not yet supported, skipping channel" << std::endl;
+				continue;
+			}
+			channel.samplerIndex = source.sampler;
+			channel.node = nodeFromIndex(source.target_node);
+			if (!channel.node) {
+				continue;
+			}
+
+			animation.channels.push_back(channel);
+		}
+
+		animations.push_back(animation);
+	}
+}
+
+void vkglTF::Model::loadFromFile(std::string filename, vks::VulkanDevice *device, VkQueue transferQueue, uint32_t fileLoadingFlags, float scale)
+{
+	tinygltf::Model gltfModel;
+	tinygltf::TinyGLTF gltfContext;
+	if (fileLoadingFlags & FileLoadingFlags::DontLoadImages) {
+		gltfContext.SetImageLoader(loadImageDataFuncEmpty, nullptr);
+	} else {
+		gltfContext.SetImageLoader(loadImageDataFunc, nullptr);
+	}
+#if defined(__ANDROID__)
+	// On Android all assets are packed with the apk in a compressed form, so we need to open them using the asset manager
+	// We let tinygltf handle this, by passing the asset manager of our app
+	tinygltf::asset_manager = androidApp->activity->assetManager;
+#endif
+	size_t pos = filename.find_last_of('/');
+	path = filename.substr(0, pos);
+
+	std::string error, warning;
+
+	this->device = device;
+
+#if defined(__ANDROID__)
+	// On Android all assets are packed with the apk in a compressed form, so we need to open them using the asset manager
+	// We let tinygltf handle this, by passing the asset manager of our app
+	tinygltf::asset_manager = androidApp->activity->assetManager;
+#endif
+	bool fileLoaded = gltfContext.LoadASCIIFromFile(&gltfModel, &error, &warning, filename);
+
+	std::vector<uint32_t> indexBuffer;
+	std::vector<Vertex> vertexBuffer;
+
+	if (fileLoaded) {
+		if (!(fileLoadingFlags & FileLoadingFlags::DontLoadImages)) {
+			loadImages(gltfModel, device, transferQueue);
+		}
+		loadMaterials(gltfModel);
+		const tinygltf::Scene &scene = gltfModel.scenes[gltfModel.defaultScene > -1 ? gltfModel.defaultScene : 0];
+		for (size_t i = 0; i < scene.nodes.size(); i++) {
+			const tinygltf::Node node = gltfModel.nodes[scene.nodes[i]];
+			loadNode(nullptr, node, scene.nodes[i], gltfModel, indexBuffer, vertexBuffer, scale);
+		}
+		if (gltfModel.animations.size() > 0) {
+			loadAnimations(gltfModel);
+		}
+		loadSkins(gltfModel);
+
+		for (auto node : linearNodes) {
+			// Assign skins
+			if (node->skinIndex > -1) {
+				node->skin = skins[node->skinIndex];
+			}
+			// Initial pose
+			if (node->mesh) {
+				node->update();
+			}
+		}
+	}
+	else {
+		// TODO: throw
+		vks::tools::exitFatal("Could not load glTF file \"" + filename + "\": " + error, -1);
+		return;
+	}
+
+	// Pre-Calculations for requested features
+	if ((fileLoadingFlags & FileLoadingFlags::PreTransformVertices) || (fileLoadingFlags & FileLoadingFlags::PreMultiplyVertexColors) || (fileLoadingFlags & FileLoadingFlags::FlipY)) {
+		const bool preTransform = fileLoadingFlags & FileLoadingFlags::PreTransformVertices;
+		const bool preMultiplyColor = fileLoadingFlags & FileLoadingFlags::PreMultiplyVertexColors;
+		const bool flipY = fileLoadingFlags & FileLoadingFlags::FlipY;
+		for (Node* node : linearNodes) {
+			if (node->mesh) {
+				const glm::mat4 localMatrix = node->getMatrix();
+				for (Primitive* primitive : node->mesh->primitives) {
+					for (uint32_t i = 0; i < primitive->vertexCount; i++) {
+						Vertex& vertex = vertexBuffer[primitive->firstVertex + i];
+						// Pre-transform vertex positions by node-hierarchy
+						if (preTransform) {
+							vertex.pos = glm::vec3(localMatrix * glm::vec4(vertex.pos, 1.0f));
+							vertex.normal = glm::normalize(glm::mat3(localMatrix) * vertex.normal);
+						}
+						// Flip Y-Axis of vertex positions
+						if (flipY) {
+							vertex.pos.y *= -1.0f;
+							vertex.normal.y *= -1.0f;
+						}
+						// Pre-Multiply vertex colors with material base color
+						if (preMultiplyColor) {
+							vertex.color = primitive->material.baseColorFactor * vertex.color;
+						}
+					}
+				}
+			}
+		}
+	}
+
+	for (auto extension : gltfModel.extensionsUsed) {
+		if (extension == "KHR_materials_pbrSpecularGlossiness") {
+			std::cout << "Required extension: " << extension;
+			metallicRoughnessWorkflow = false;
+		}
+	}
+
+	size_t vertexBufferSize = vertexBuffer.size() * sizeof(Vertex);
+	size_t indexBufferSize = indexBuffer.size() * sizeof(uint32_t);
+	indices.count = static_cast<uint32_t>(indexBuffer.size());
+	vertices.count = static_cast<uint32_t>(vertexBuffer.size());
+
+	assert((vertexBufferSize > 0) && (indexBufferSize > 0));
+
+	struct StagingBuffer {
+		VkBuffer buffer;
+		VkDeviceMemory memory;
+	} vertexStaging, indexStaging;
+
+	// Create staging buffers
+	// Vertex data
+	VK_CHECK_RESULT(device->createBuffer(
+		VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
+		VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+		vertexBufferSize,
+		&vertexStaging.buffer,
+		&vertexStaging.memory,
+		vertexBuffer.data()));
+	// Index data
+	VK_CHECK_RESULT(device->createBuffer(
+		VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
+		VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+		indexBufferSize,
+		&indexStaging.buffer,
+		&indexStaging.memory,
+		indexBuffer.data()));
+
+	// Create device local buffers
+	// Vertex buffer
+	VK_CHECK_RESULT(device->createBuffer(
+	    VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | memoryPropertyFlags,
+		VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
+		vertexBufferSize,
+		&vertices.buffer,
+		&vertices.memory));
+	// Index buffer
+	VK_CHECK_RESULT(device->createBuffer(
+	    VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | memoryPropertyFlags,
+		VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
+		indexBufferSize,
+		&indices.buffer,
+		&indices.memory));
+
+	// Copy from staging buffers
+	VkCommandBuffer copyCmd = device->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+
+	VkBufferCopy copyRegion = {};
+
+	copyRegion.size = vertexBufferSize;
+	vkCmdCopyBuffer(copyCmd, vertexStaging.buffer, vertices.buffer, 1, &copyRegion);
+
+	copyRegion.size = indexBufferSize;
+	vkCmdCopyBuffer(copyCmd, indexStaging.buffer, indices.buffer, 1, &copyRegion);
+
+	device->flushCommandBuffer(copyCmd, transferQueue, true);
+
+	vkDestroyBuffer(device->logicalDevice, vertexStaging.buffer, nullptr);
+	vkFreeMemory(device->logicalDevice, vertexStaging.memory, nullptr);
+	vkDestroyBuffer(device->logicalDevice, indexStaging.buffer, nullptr);
+	vkFreeMemory(device->logicalDevice, indexStaging.memory, nullptr);
+
+	getSceneDimensions();
+
+	// Setup descriptors
+	uint32_t uboCount{ 0 };
+	uint32_t imageCount{ 0 };
+	for (auto node : linearNodes) {
+		if (node->mesh) {
+			uboCount++;
+		}
+	}
+	for (auto material : materials) {
+		if (material.baseColorTexture != nullptr) {
+			imageCount++;
+		}
+	}
+	std::vector<VkDescriptorPoolSize> poolSizes = {
+		{ VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, uboCount },
+	};
+	if (imageCount > 0) {
+		if (descriptorBindingFlags & DescriptorBindingFlags::ImageBaseColor) {
+			poolSizes.push_back({ VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, imageCount });
+		}
+		if (descriptorBindingFlags & DescriptorBindingFlags::ImageNormalMap) {
+			poolSizes.push_back({ VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, imageCount });
+		}
+	}
+	VkDescriptorPoolCreateInfo descriptorPoolCI{};
+	descriptorPoolCI.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
+	descriptorPoolCI.poolSizeCount = static_cast<uint32_t>(poolSizes.size());
+	descriptorPoolCI.pPoolSizes = poolSizes.data();
+	descriptorPoolCI.maxSets = uboCount + imageCount;
+	VK_CHECK_RESULT(vkCreateDescriptorPool(device->logicalDevice, &descriptorPoolCI, nullptr, &descriptorPool));
+
+	// Descriptors for per-node uniform buffers
+	{
+		// Layout is global, so only create if it hasn't already been created before
+		if (descriptorSetLayoutUbo == VK_NULL_HANDLE) {
+			std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+				vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0),
+			};
+			VkDescriptorSetLayoutCreateInfo descriptorLayoutCI{};
+			descriptorLayoutCI.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
+			descriptorLayoutCI.bindingCount = static_cast<uint32_t>(setLayoutBindings.size());
+			descriptorLayoutCI.pBindings = setLayoutBindings.data();
+			VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device->logicalDevice, &descriptorLayoutCI, nullptr, &descriptorSetLayoutUbo));
+		}
+		for (auto node : nodes) {
+			prepareNodeDescriptor(node, descriptorSetLayoutUbo);
+		}
+	}
+
+	// Descriptors for per-material images
+	{
+		// Layout is global, so only create if it hasn't already been created before
+		if (descriptorSetLayoutImage == VK_NULL_HANDLE) {
+			std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings{};
+			if (descriptorBindingFlags & DescriptorBindingFlags::ImageBaseColor) {
+				setLayoutBindings.push_back(vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, static_cast<uint32_t>(setLayoutBindings.size())));
+			}
+			if (descriptorBindingFlags & DescriptorBindingFlags::ImageNormalMap) {
+				setLayoutBindings.push_back(vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, static_cast<uint32_t>(setLayoutBindings.size())));
+			}
+			VkDescriptorSetLayoutCreateInfo descriptorLayoutCI{};
+			descriptorLayoutCI.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
+			descriptorLayoutCI.bindingCount = static_cast<uint32_t>(setLayoutBindings.size());
+			descriptorLayoutCI.pBindings = setLayoutBindings.data();
+			VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device->logicalDevice, &descriptorLayoutCI, nullptr, &descriptorSetLayoutImage));
+		}
+		for (auto& material : materials) {
+			if (material.baseColorTexture != nullptr) {
+				material.createDescriptorSet(descriptorPool, vkglTF::descriptorSetLayoutImage, descriptorBindingFlags);
+			}
+		}
+	}
+}
+
+void vkglTF::Model::bindBuffers(VkCommandBuffer commandBuffer)
+{
+	const VkDeviceSize offsets[1] = {0};
+	vkCmdBindVertexBuffers(commandBuffer, 0, 1, &vertices.buffer, offsets);
+	vkCmdBindIndexBuffer(commandBuffer, indices.buffer, 0, VK_INDEX_TYPE_UINT32);
+	buffersBound = true;
+}
+
+void vkglTF::Model::drawNode(Node *node, VkCommandBuffer commandBuffer, uint32_t renderFlags, VkPipelineLayout pipelineLayout, uint32_t bindImageSet)
+{
+	if (node->mesh) {
+		for (Primitive* primitive : node->mesh->primitives) {
+			bool skip = false;
+			const vkglTF::Material& material = primitive->material;
+			if (renderFlags & RenderFlags::RenderOpaqueNodes) {
+				skip = (material.alphaMode != Material::ALPHAMODE_OPAQUE);
+			}
+			if (renderFlags & RenderFlags::RenderAlphaMaskedNodes) {
+				skip = (material.alphaMode != Material::ALPHAMODE_MASK);
+			}
+			if (renderFlags & RenderFlags::RenderAlphaBlendedNodes) {
+				skip = (material.alphaMode != Material::ALPHAMODE_BLEND);
+			}
+			if (!skip) {
+				if (renderFlags & RenderFlags::BindImages) {
+					vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, bindImageSet, 1, &material.descriptorSet, 0, nullptr);
+				}
+				vkCmdDrawIndexed(commandBuffer, primitive->indexCount, 1, primitive->firstIndex, 0, 0);
+			}
+		}
+	}
+	for (auto& child : node->children) {
+		drawNode(child, commandBuffer, renderFlags);
+	}
+}
+
+void vkglTF::Model::draw(VkCommandBuffer commandBuffer, uint32_t renderFlags, VkPipelineLayout pipelineLayout, uint32_t bindImageSet)
+{
+	if (!buffersBound) {
+		const VkDeviceSize offsets[1] = {0};
+		vkCmdBindVertexBuffers(commandBuffer, 0, 1, &vertices.buffer, offsets);
+		vkCmdBindIndexBuffer(commandBuffer, indices.buffer, 0, VK_INDEX_TYPE_UINT32);
+	}
+	for (auto& node : nodes) {
+		drawNode(node, commandBuffer, renderFlags, pipelineLayout, bindImageSet);
+	}
+}
+
+void vkglTF::Model::getNodeDimensions(Node *node, glm::vec3 &min, glm::vec3 &max)
+{
+	if (node->mesh) {
+		for (Primitive *primitive : node->mesh->primitives) {
+			glm::vec4 locMin = glm::vec4(primitive->dimensions.min, 1.0f) * node->getMatrix();
+			glm::vec4 locMax = glm::vec4(primitive->dimensions.max, 1.0f) * node->getMatrix();
+			if (locMin.x < min.x) { min.x = locMin.x; }
+			if (locMin.y < min.y) { min.y = locMin.y; }
+			if (locMin.z < min.z) { min.z = locMin.z; }
+			if (locMax.x > max.x) { max.x = locMax.x; }
+			if (locMax.y > max.y) { max.y = locMax.y; }
+			if (locMax.z > max.z) { max.z = locMax.z; }
+		}
+	}
+	for (auto child : node->children) {
+		getNodeDimensions(child, min, max);
+	}
+}
+
+void vkglTF::Model::getSceneDimensions()
+{
+	dimensions.min = glm::vec3(FLT_MAX);
+	dimensions.max = glm::vec3(-FLT_MAX);
+	for (auto node : nodes) {
+		getNodeDimensions(node, dimensions.min, dimensions.max);
+	}
+	dimensions.size = dimensions.max - dimensions.min;
+	dimensions.center = (dimensions.min + dimensions.max) / 2.0f;
+	dimensions.radius = glm::distance(dimensions.min, dimensions.max) / 2.0f;
+}
+
+void vkglTF::Model::updateAnimation(uint32_t index, float time)
+{
+	if (index > static_cast<uint32_t>(animations.size()) - 1) {
+		std::cout << "No animation with index " << index << std::endl;
+		return;
+	}
+	Animation &animation = animations[index];
+
+	bool updated = false;
+	for (auto& channel : animation.channels) {
+		vkglTF::AnimationSampler &sampler = animation.samplers[channel.samplerIndex];
+		if (sampler.inputs.size() > sampler.outputsVec4.size()) {
+			continue;
+		}
+
+		for (auto i = 0; i < sampler.inputs.size() - 1; i++) {
+			if ((time >= sampler.inputs[i]) && (time <= sampler.inputs[i + 1])) {
+				float u = std::max(0.0f, time - sampler.inputs[i]) / (sampler.inputs[i + 1] - sampler.inputs[i]);
+				if (u <= 1.0f) {
+					switch (channel.path) {
+					case vkglTF::AnimationChannel::PathType::TRANSLATION: {
+						glm::vec4 trans = glm::mix(sampler.outputsVec4[i], sampler.outputsVec4[i + 1], u);
+						channel.node->translation = glm::vec3(trans);
+						break;
+					}
+					case vkglTF::AnimationChannel::PathType::SCALE: {
+						glm::vec4 trans = glm::mix(sampler.outputsVec4[i], sampler.outputsVec4[i + 1], u);
+						channel.node->scale = glm::vec3(trans);
+						break;
+					}
+					case vkglTF::AnimationChannel::PathType::ROTATION: {
+						glm::quat q1;
+						q1.x = sampler.outputsVec4[i].x;
+						q1.y = sampler.outputsVec4[i].y;
+						q1.z = sampler.outputsVec4[i].z;
+						q1.w = sampler.outputsVec4[i].w;
+						glm::quat q2;
+						q2.x = sampler.outputsVec4[i + 1].x;
+						q2.y = sampler.outputsVec4[i + 1].y;
+						q2.z = sampler.outputsVec4[i + 1].z;
+						q2.w = sampler.outputsVec4[i + 1].w;
+						channel.node->rotation = glm::normalize(glm::slerp(q1, q2, u));
+						break;
+					}
+					}
+					updated = true;
+				}
+			}
+		}
+	}
+	if (updated) {
+		for (auto &node : nodes) {
+			node->update();
+		}
+	}
+}
+
+/*
+	Helper functions
+*/
+vkglTF::Node* vkglTF::Model::findNode(Node *parent, uint32_t index) {
+	Node* nodeFound = nullptr;
+	if (parent->index == index) {
+		return parent;
+	}
+	for (auto& child : parent->children) {
+		nodeFound = findNode(child, index);
+		if (nodeFound) {
+			break;
+		}
+	}
+	return nodeFound;
+}
+
+vkglTF::Node* vkglTF::Model::nodeFromIndex(uint32_t index) {
+	Node* nodeFound = nullptr;
+	for (auto &node : nodes) {
+		nodeFound = findNode(node, index);
+		if (nodeFound) {
+			break;
+		}
+	}
+	return nodeFound;
+}
+
+void vkglTF::Model::prepareNodeDescriptor(vkglTF::Node* node, VkDescriptorSetLayout descriptorSetLayout) {
+	if (node->mesh) {
+		VkDescriptorSetAllocateInfo descriptorSetAllocInfo{};
+		descriptorSetAllocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
+		descriptorSetAllocInfo.descriptorPool = descriptorPool;
+		descriptorSetAllocInfo.pSetLayouts = &descriptorSetLayout;
+		descriptorSetAllocInfo.descriptorSetCount = 1;
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device->logicalDevice, &descriptorSetAllocInfo, &node->mesh->uniformBuffer.descriptorSet));
+
+		VkWriteDescriptorSet writeDescriptorSet{};
+		writeDescriptorSet.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+		writeDescriptorSet.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+		writeDescriptorSet.descriptorCount = 1;
+		writeDescriptorSet.dstSet = node->mesh->uniformBuffer.descriptorSet;
+		writeDescriptorSet.dstBinding = 0;
+		writeDescriptorSet.pBufferInfo = &node->mesh->uniformBuffer.descriptor;
+
+		vkUpdateDescriptorSets(device->logicalDevice, 1, &writeDescriptorSet, 0, nullptr);
+	}
+	for (auto& child : node->children) {
+		prepareNodeDescriptor(child, descriptorSetLayout);
+	}
+}
diff --git a/external/Vulkan/base/VulkanglTFModel.h b/external/Vulkan/base/VulkanglTFModel.h
new file mode 100644
index 0000000000000000000000000000000000000000..26a0a1232d20d11f93d2b76383c38cefa395523d
--- /dev/null
+++ b/external/Vulkan/base/VulkanglTFModel.h
@@ -0,0 +1,308 @@
+/*
+* Vulkan glTF model and texture loading class based on tinyglTF (https://github.com/syoyo/tinygltf)
+*
+* Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#pragma once
+
+#include <stdlib.h>
+#include <string>
+#include <fstream>
+#include <vector>
+
+#include "vulkan/vulkan.h"
+#include "VulkanDevice.h"
+
+#include <ktx.h>
+#include <ktxvulkan.h>
+
+#define GLM_FORCE_RADIANS
+#define GLM_FORCE_DEPTH_ZERO_TO_ONE
+#include <glm/glm.hpp>
+#include <glm/gtc/matrix_transform.hpp>
+#include <glm/gtc/type_ptr.hpp>
+
+#define TINYGLTF_NO_STB_IMAGE_WRITE
+#ifdef VK_USE_PLATFORM_ANDROID_KHR
+#define TINYGLTF_ANDROID_LOAD_FROM_ASSETS
+#endif
+#include "tiny_gltf.h"
+
+#if defined(__ANDROID__)
+#include <android/asset_manager.h>
+#endif
+
+namespace vkglTF
+{
+	enum DescriptorBindingFlags {
+		ImageBaseColor = 0x00000001,
+		ImageNormalMap = 0x00000002
+	};
+
+	extern VkDescriptorSetLayout descriptorSetLayoutImage;
+	extern VkDescriptorSetLayout descriptorSetLayoutUbo;
+	extern VkMemoryPropertyFlags memoryPropertyFlags;
+	extern uint32_t descriptorBindingFlags;
+
+	struct Node;
+
+	/*
+		glTF texture loading class
+	*/
+	struct Texture {
+		vks::VulkanDevice* device = nullptr;
+		VkImage image;
+		VkImageLayout imageLayout;
+		VkDeviceMemory deviceMemory;
+		VkImageView view;
+		uint32_t width, height;
+		uint32_t mipLevels;
+		uint32_t layerCount;
+		VkDescriptorImageInfo descriptor;
+		VkSampler sampler;
+		void updateDescriptor();
+		void destroy();
+		void fromglTfImage(tinygltf::Image& gltfimage, std::string path, vks::VulkanDevice* device, VkQueue copyQueue);
+	};
+
+	/*
+		glTF material class
+	*/
+	struct Material {
+		vks::VulkanDevice* device = nullptr;
+		enum AlphaMode { ALPHAMODE_OPAQUE, ALPHAMODE_MASK, ALPHAMODE_BLEND };
+		AlphaMode alphaMode = ALPHAMODE_OPAQUE;
+		float alphaCutoff = 1.0f;
+		float metallicFactor = 1.0f;
+		float roughnessFactor = 1.0f;
+		glm::vec4 baseColorFactor = glm::vec4(1.0f);
+		vkglTF::Texture* baseColorTexture = nullptr;
+		vkglTF::Texture* metallicRoughnessTexture = nullptr;
+		vkglTF::Texture* normalTexture = nullptr;
+		vkglTF::Texture* occlusionTexture = nullptr;
+		vkglTF::Texture* emissiveTexture = nullptr;
+
+		vkglTF::Texture* specularGlossinessTexture;
+		vkglTF::Texture* diffuseTexture;
+
+		VkDescriptorSet descriptorSet = VK_NULL_HANDLE;
+
+		Material(vks::VulkanDevice* device) : device(device) {};
+		void createDescriptorSet(VkDescriptorPool descriptorPool, VkDescriptorSetLayout descriptorSetLayout, uint32_t descriptorBindingFlags);
+	};
+
+	/*
+		glTF primitive
+	*/
+	struct Primitive {
+		uint32_t firstIndex;
+		uint32_t indexCount;
+		uint32_t firstVertex;
+		uint32_t vertexCount;
+		Material& material;
+
+		struct Dimensions {
+			glm::vec3 min = glm::vec3(FLT_MAX);
+			glm::vec3 max = glm::vec3(-FLT_MAX);
+			glm::vec3 size;
+			glm::vec3 center;
+			float radius;
+		} dimensions;
+
+		void setDimensions(glm::vec3 min, glm::vec3 max);
+		Primitive(uint32_t firstIndex, uint32_t indexCount, Material& material) : firstIndex(firstIndex), indexCount(indexCount), material(material) {};
+	};
+
+	/*
+		glTF mesh
+	*/
+	struct Mesh {
+		vks::VulkanDevice* device;
+
+		std::vector<Primitive*> primitives;
+		std::string name;
+
+		struct UniformBuffer {
+			VkBuffer buffer;
+			VkDeviceMemory memory;
+			VkDescriptorBufferInfo descriptor;
+			VkDescriptorSet descriptorSet = VK_NULL_HANDLE;
+			void* mapped;
+		} uniformBuffer;
+
+		struct UniformBlock {
+			glm::mat4 matrix;
+			glm::mat4 jointMatrix[64]{};
+			float jointcount{ 0 };
+		} uniformBlock;
+
+		Mesh(vks::VulkanDevice* device, glm::mat4 matrix);
+		~Mesh();
+	};
+
+	/*
+		glTF skin
+	*/
+	struct Skin {
+		std::string name;
+		Node* skeletonRoot = nullptr;
+		std::vector<glm::mat4> inverseBindMatrices;
+		std::vector<Node*> joints;
+	};
+
+	/*
+		glTF node
+	*/
+	struct Node {
+		Node* parent;
+		uint32_t index;
+		std::vector<Node*> children;
+		glm::mat4 matrix;
+		std::string name;
+		Mesh* mesh;
+		Skin* skin;
+		int32_t skinIndex = -1;
+		glm::vec3 translation{};
+		glm::vec3 scale{ 1.0f };
+		glm::quat rotation{};
+		glm::mat4 localMatrix();
+		glm::mat4 getMatrix();
+		void update();
+		~Node();
+	};
+
+	/*
+		glTF animation channel
+	*/
+	struct AnimationChannel {
+		enum PathType { TRANSLATION, ROTATION, SCALE };
+		PathType path;
+		Node* node;
+		uint32_t samplerIndex;
+	};
+
+	/*
+		glTF animation sampler
+	*/
+	struct AnimationSampler {
+		enum InterpolationType { LINEAR, STEP, CUBICSPLINE };
+		InterpolationType interpolation;
+		std::vector<float> inputs;
+		std::vector<glm::vec4> outputsVec4;
+	};
+
+	/*
+		glTF animation
+	*/
+	struct Animation {
+		std::string name;
+		std::vector<AnimationSampler> samplers;
+		std::vector<AnimationChannel> channels;
+		float start = std::numeric_limits<float>::max();
+		float end = std::numeric_limits<float>::min();
+	};
+
+	/*
+		glTF default vertex layout with easy Vulkan mapping functions
+	*/
+	enum class VertexComponent { Position, Normal, UV, Color, Tangent, Joint0, Weight0 };
+
+	struct Vertex {
+		glm::vec3 pos;
+		glm::vec3 normal;
+		glm::vec2 uv;
+		glm::vec4 color;
+		glm::vec4 joint0;
+		glm::vec4 weight0;
+		glm::vec4 tangent;
+		static VkVertexInputBindingDescription vertexInputBindingDescription;
+		static std::vector<VkVertexInputAttributeDescription> vertexInputAttributeDescriptions;
+		static VkPipelineVertexInputStateCreateInfo pipelineVertexInputStateCreateInfo;
+		static VkVertexInputBindingDescription inputBindingDescription(uint32_t binding);
+		static VkVertexInputAttributeDescription inputAttributeDescription(uint32_t binding, uint32_t location, VertexComponent component);
+		static std::vector<VkVertexInputAttributeDescription> inputAttributeDescriptions(uint32_t binding, const std::vector<VertexComponent> components);
+		/** @brief Returns the default pipeline vertex input state create info structure for the requested vertex components */
+		static VkPipelineVertexInputStateCreateInfo* getPipelineVertexInputState(const std::vector<VertexComponent> components);
+	};
+
+	enum FileLoadingFlags {
+		None = 0x00000000,
+		PreTransformVertices = 0x00000001,
+		PreMultiplyVertexColors = 0x00000002,
+		FlipY = 0x00000004,
+		DontLoadImages = 0x00000008
+	};
+
+	enum RenderFlags {
+		BindImages = 0x00000001,
+		RenderOpaqueNodes = 0x00000002,
+		RenderAlphaMaskedNodes = 0x00000004,
+		RenderAlphaBlendedNodes = 0x00000008
+	};
+
+	/*
+		glTF model loading and rendering class
+	*/
+	class Model {
+	private:
+		vkglTF::Texture* getTexture(uint32_t index);
+		vkglTF::Texture emptyTexture;
+		void createEmptyTexture(VkQueue transferQueue);
+	public:
+		vks::VulkanDevice* device;
+		VkDescriptorPool descriptorPool;
+
+		struct Vertices {
+			int count;
+			VkBuffer buffer;
+			VkDeviceMemory memory;
+		} vertices;
+		struct Indices {
+			int count;
+			VkBuffer buffer;
+			VkDeviceMemory memory;
+		} indices;
+
+		std::vector<Node*> nodes;
+		std::vector<Node*> linearNodes;
+
+		std::vector<Skin*> skins;
+
+		std::vector<Texture> textures;
+		std::vector<Material> materials;
+		std::vector<Animation> animations;
+
+		struct Dimensions {
+			glm::vec3 min = glm::vec3(FLT_MAX);
+			glm::vec3 max = glm::vec3(-FLT_MAX);
+			glm::vec3 size;
+			glm::vec3 center;
+			float radius;
+		} dimensions;
+
+		bool metallicRoughnessWorkflow = true;
+		bool buffersBound = false;
+		std::string path;
+
+		Model() {};
+		~Model();
+		void loadNode(vkglTF::Node* parent, const tinygltf::Node& node, uint32_t nodeIndex, const tinygltf::Model& model, std::vector<uint32_t>& indexBuffer, std::vector<Vertex>& vertexBuffer, float globalscale);
+		void loadSkins(tinygltf::Model& gltfModel);
+		void loadImages(tinygltf::Model& gltfModel, vks::VulkanDevice* device, VkQueue transferQueue);
+		void loadMaterials(tinygltf::Model& gltfModel);
+		void loadAnimations(tinygltf::Model& gltfModel);
+		void loadFromFile(std::string filename, vks::VulkanDevice* device, VkQueue transferQueue, uint32_t fileLoadingFlags = vkglTF::FileLoadingFlags::None, float scale = 1.0f);
+		void bindBuffers(VkCommandBuffer commandBuffer);
+		void drawNode(Node* node, VkCommandBuffer commandBuffer, uint32_t renderFlags = 0, VkPipelineLayout pipelineLayout = VK_NULL_HANDLE, uint32_t bindImageSet = 1);
+		void draw(VkCommandBuffer commandBuffer, uint32_t renderFlags = 0, VkPipelineLayout pipelineLayout = VK_NULL_HANDLE, uint32_t bindImageSet = 1);
+		void getNodeDimensions(Node* node, glm::vec3& min, glm::vec3& max);
+		void getSceneDimensions();
+		void updateAnimation(uint32_t index, float time);
+		Node* findNode(Node* parent, uint32_t index);
+		Node* nodeFromIndex(uint32_t index);
+		void prepareNodeDescriptor(vkglTF::Node* node, VkDescriptorSetLayout descriptorSetLayout);
+	};
+}
\ No newline at end of file
diff --git a/external/Vulkan/base/benchmark.hpp b/external/Vulkan/base/benchmark.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..bae91a6ac0f4cefee213c3533609ee4481c4ddc7
--- /dev/null
+++ b/external/Vulkan/base/benchmark.hpp
@@ -0,0 +1,104 @@
+/*
+* Benchmark class
+*
+* Copyright (C) 2016-2017 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include <vector>
+#include <string>
+#include <algorithm>
+#include <limits>
+#include <functional>
+#include <chrono>
+#include <iomanip>
+
+namespace vks
+{
+	class Benchmark {
+	private:
+		FILE *stream;
+		VkPhysicalDeviceProperties deviceProps;
+	public:
+		bool active = false;
+		bool outputFrameTimes = false;
+		int outputFrames = -1; // -1 means no frames limit
+		uint32_t warmup = 1;
+		uint32_t duration = 10;
+		std::vector<double> frameTimes;
+		std::string filename = "";
+
+		double runtime = 0.0;
+		uint32_t frameCount = 0;
+
+		void run(std::function<void()> renderFunc, VkPhysicalDeviceProperties deviceProps) {
+			active = true;
+			this->deviceProps = deviceProps;
+#if defined(_WIN32)
+			AttachConsole(ATTACH_PARENT_PROCESS);
+			freopen_s(&stream, "CONOUT$", "w+", stdout);
+			freopen_s(&stream, "CONOUT$", "w+", stderr);
+#endif
+			std::cout << std::fixed << std::setprecision(3);
+
+			// Warm up phase to get more stable frame rates
+			{
+				double tMeasured = 0.0;
+				while (tMeasured < (warmup * 1000)) {
+					auto tStart = std::chrono::high_resolution_clock::now();
+					renderFunc();
+					auto tDiff = std::chrono::duration<double, std::milli>(std::chrono::high_resolution_clock::now() - tStart).count();
+					tMeasured += tDiff;
+				};
+			}
+
+			// Benchmark phase
+			{
+				while (runtime < (duration * 1000.0)) {
+					auto tStart = std::chrono::high_resolution_clock::now();
+					renderFunc();
+					auto tDiff = std::chrono::duration<double, std::milli>(std::chrono::high_resolution_clock::now() - tStart).count();
+					runtime += tDiff;
+					frameTimes.push_back(tDiff);
+					frameCount++;
+					if (outputFrames != -1 && outputFrames == frameCount) break;
+				};
+				std::cout << "Benchmark finished" << "\n";
+				std::cout << "device : " << deviceProps.deviceName << " (driver version: " << deviceProps.driverVersion << ")" << "\n";
+				std::cout << "runtime: " << (runtime / 1000.0) << "\n";
+				std::cout << "frames : " << frameCount << "\n";
+				std::cout << "fps    : " << frameCount / (runtime / 1000.0) << "\n";
+			}
+		}
+
+		void saveResults() {
+			std::ofstream result(filename, std::ios::out);
+			if (result.is_open()) {
+				result << std::fixed << std::setprecision(4);
+
+				result << "device,driverversion,duration (ms),frames,fps" << "\n";
+				result << deviceProps.deviceName << "," << deviceProps.driverVersion << "," << runtime << "," << frameCount << "," << frameCount / (runtime / 1000.0) << "\n";
+
+				if (outputFrameTimes) {
+					result << "\n" << "frame,ms" << "\n";
+					for (size_t i = 0; i < frameTimes.size(); i++) {
+						result << i << "," << frameTimes[i] << "\n";
+					}
+					double tMin = *std::min_element(frameTimes.begin(), frameTimes.end());
+					double tMax = *std::max_element(frameTimes.begin(), frameTimes.end());
+					double tAvg = std::accumulate(frameTimes.begin(), frameTimes.end(), 0.0) / (double)frameTimes.size();
+					std::cout << "best   : " << (1000.0 / tMin) << " fps (" << tMin << " ms)" << "\n";
+					std::cout << "worst  : " << (1000.0 / tMax) << " fps (" << tMax << " ms)" << "\n";
+					std::cout << "avg    : " << (1000.0 / tAvg) << " fps (" << tAvg << " ms)" << "\n";
+					std::cout << "\n";
+				}
+
+				result.flush();
+#if defined(_WIN32)
+				FreeConsole();
+#endif
+			}
+		}
+	};
+}
\ No newline at end of file
diff --git a/external/Vulkan/base/camera.hpp b/external/Vulkan/base/camera.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..6358f88bae3e6b4ce986693c0a6621698a7db44c
--- /dev/null
+++ b/external/Vulkan/base/camera.hpp
@@ -0,0 +1,242 @@
+/*
+* Basic camera class
+*
+* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#define GLM_FORCE_RADIANS
+#define GLM_FORCE_DEPTH_ZERO_TO_ONE
+#include <glm/glm.hpp>
+#include <glm/gtc/quaternion.hpp>
+#include <glm/gtc/matrix_transform.hpp>
+
+class Camera
+{
+private:
+	float fov;
+	float znear, zfar;
+
+	void updateViewMatrix()
+	{
+		glm::mat4 rotM = glm::mat4(1.0f);
+		glm::mat4 transM;
+
+		rotM = glm::rotate(rotM, glm::radians(rotation.x * (flipY ? -1.0f : 1.0f)), glm::vec3(1.0f, 0.0f, 0.0f));
+		rotM = glm::rotate(rotM, glm::radians(rotation.y), glm::vec3(0.0f, 1.0f, 0.0f));
+		rotM = glm::rotate(rotM, glm::radians(rotation.z), glm::vec3(0.0f, 0.0f, 1.0f));
+
+		glm::vec3 translation = position;
+		if (flipY) {
+			translation.y *= -1.0f;
+		}
+		transM = glm::translate(glm::mat4(1.0f), translation);
+
+		if (type == CameraType::firstperson)
+		{
+			matrices.view = rotM * transM;
+		}
+		else
+		{
+			matrices.view = transM * rotM;
+		}
+
+		viewPos = glm::vec4(position, 0.0f) * glm::vec4(-1.0f, 1.0f, -1.0f, 1.0f);
+
+		updated = true;
+	};
+public:
+	enum CameraType { lookat, firstperson };
+	CameraType type = CameraType::lookat;
+
+	glm::vec3 rotation = glm::vec3();
+	glm::vec3 position = glm::vec3();
+	glm::vec4 viewPos = glm::vec4();
+
+	float rotationSpeed = 1.0f;
+	float movementSpeed = 1.0f;
+
+	bool updated = false;
+	bool flipY = false;
+
+	struct
+	{
+		glm::mat4 perspective;
+		glm::mat4 view;
+	} matrices;
+
+	struct
+	{
+		bool left = false;
+		bool right = false;
+		bool up = false;
+		bool down = false;
+	} keys;
+
+	bool moving()
+	{
+		return keys.left || keys.right || keys.up || keys.down;
+	}
+
+	float getNearClip() { 
+		return znear;
+	}
+
+	float getFarClip() {
+		return zfar;
+	}
+
+	void setPerspective(float fov, float aspect, float znear, float zfar)
+	{
+		this->fov = fov;
+		this->znear = znear;
+		this->zfar = zfar;
+		matrices.perspective = glm::perspective(glm::radians(fov), aspect, znear, zfar);
+		if (flipY) {
+			matrices.perspective[1][1] *= -1.0f;
+		}
+	};
+
+	void updateAspectRatio(float aspect)
+	{
+		matrices.perspective = glm::perspective(glm::radians(fov), aspect, znear, zfar);
+		if (flipY) {
+			matrices.perspective[1][1] *= -1.0f;
+		}
+	}
+
+	void setPosition(glm::vec3 position)
+	{
+		this->position = position;
+		updateViewMatrix();
+	}
+
+	void setRotation(glm::vec3 rotation)
+	{
+		this->rotation = rotation;
+		updateViewMatrix();
+	}
+
+	void rotate(glm::vec3 delta)
+	{
+		this->rotation += delta;
+		updateViewMatrix();
+	}
+
+	void setTranslation(glm::vec3 translation)
+	{
+		this->position = translation;
+		updateViewMatrix();
+	};
+
+	void translate(glm::vec3 delta)
+	{
+		this->position += delta;
+		updateViewMatrix();
+	}
+
+	void setRotationSpeed(float rotationSpeed)
+	{
+		this->rotationSpeed = rotationSpeed;
+	}
+
+	void setMovementSpeed(float movementSpeed)
+	{
+		this->movementSpeed = movementSpeed;
+	}
+
+	void update(float deltaTime)
+	{
+		updated = false;
+		if (type == CameraType::firstperson)
+		{
+			if (moving())
+			{
+				glm::vec3 camFront;
+				camFront.x = -cos(glm::radians(rotation.x)) * sin(glm::radians(rotation.y));
+				camFront.y = sin(glm::radians(rotation.x));
+				camFront.z = cos(glm::radians(rotation.x)) * cos(glm::radians(rotation.y));
+				camFront = glm::normalize(camFront);
+
+				float moveSpeed = deltaTime * movementSpeed;
+
+				if (keys.up)
+					position += camFront * moveSpeed;
+				if (keys.down)
+					position -= camFront * moveSpeed;
+				if (keys.left)
+					position -= glm::normalize(glm::cross(camFront, glm::vec3(0.0f, 1.0f, 0.0f))) * moveSpeed;
+				if (keys.right)
+					position += glm::normalize(glm::cross(camFront, glm::vec3(0.0f, 1.0f, 0.0f))) * moveSpeed;
+
+				updateViewMatrix();
+			}
+		}
+	};
+
+	// Update camera passing separate axis data (gamepad)
+	// Returns true if view or position has been changed
+	bool updatePad(glm::vec2 axisLeft, glm::vec2 axisRight, float deltaTime)
+	{
+		bool retVal = false;
+
+		if (type == CameraType::firstperson)
+		{
+			// Use the common console thumbstick layout		
+			// Left = view, right = move
+
+			const float deadZone = 0.0015f;
+			const float range = 1.0f - deadZone;
+
+			glm::vec3 camFront;
+			camFront.x = -cos(glm::radians(rotation.x)) * sin(glm::radians(rotation.y));
+			camFront.y = sin(glm::radians(rotation.x));
+			camFront.z = cos(glm::radians(rotation.x)) * cos(glm::radians(rotation.y));
+			camFront = glm::normalize(camFront);
+
+			float moveSpeed = deltaTime * movementSpeed * 2.0f;
+			float rotSpeed = deltaTime * rotationSpeed * 50.0f;
+			 
+			// Move
+			if (fabsf(axisLeft.y) > deadZone)
+			{
+				float pos = (fabsf(axisLeft.y) - deadZone) / range;
+				position -= camFront * pos * ((axisLeft.y < 0.0f) ? -1.0f : 1.0f) * moveSpeed;
+				retVal = true;
+			}
+			if (fabsf(axisLeft.x) > deadZone)
+			{
+				float pos = (fabsf(axisLeft.x) - deadZone) / range;
+				position += glm::normalize(glm::cross(camFront, glm::vec3(0.0f, 1.0f, 0.0f))) * pos * ((axisLeft.x < 0.0f) ? -1.0f : 1.0f) * moveSpeed;
+				retVal = true;
+			}
+
+			// Rotate
+			if (fabsf(axisRight.x) > deadZone)
+			{
+				float pos = (fabsf(axisRight.x) - deadZone) / range;
+				rotation.y += pos * ((axisRight.x < 0.0f) ? -1.0f : 1.0f) * rotSpeed;
+				retVal = true;
+			}
+			if (fabsf(axisRight.y) > deadZone)
+			{
+				float pos = (fabsf(axisRight.y) - deadZone) / range;
+				rotation.x -= pos * ((axisRight.y < 0.0f) ? -1.0f : 1.0f) * rotSpeed;
+				retVal = true;
+			}
+		}
+		else
+		{
+			// todo: move code from example base class for look-at
+		}
+
+		if (retVal)
+		{
+			updateViewMatrix();
+		}
+
+		return retVal;
+	}
+
+};
\ No newline at end of file
diff --git a/external/Vulkan/base/frustum.hpp b/external/Vulkan/base/frustum.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..47545d7bba56af18f8241eb5f6e225556921b083
--- /dev/null
+++ b/external/Vulkan/base/frustum.hpp
@@ -0,0 +1,72 @@
+/*
+* View frustum culling class
+*
+* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include <array>
+#include <math.h>
+#include <glm/glm.hpp>
+
+namespace vks
+{
+	class Frustum
+	{
+	public:
+		enum side { LEFT = 0, RIGHT = 1, TOP = 2, BOTTOM = 3, BACK = 4, FRONT = 5 };
+		std::array<glm::vec4, 6> planes;
+
+		void update(glm::mat4 matrix)
+		{
+			planes[LEFT].x = matrix[0].w + matrix[0].x;
+			planes[LEFT].y = matrix[1].w + matrix[1].x;
+			planes[LEFT].z = matrix[2].w + matrix[2].x;
+			planes[LEFT].w = matrix[3].w + matrix[3].x;
+
+			planes[RIGHT].x = matrix[0].w - matrix[0].x;
+			planes[RIGHT].y = matrix[1].w - matrix[1].x;
+			planes[RIGHT].z = matrix[2].w - matrix[2].x;
+			planes[RIGHT].w = matrix[3].w - matrix[3].x;
+
+			planes[TOP].x = matrix[0].w - matrix[0].y;
+			planes[TOP].y = matrix[1].w - matrix[1].y;
+			planes[TOP].z = matrix[2].w - matrix[2].y;
+			planes[TOP].w = matrix[3].w - matrix[3].y;
+
+			planes[BOTTOM].x = matrix[0].w + matrix[0].y;
+			planes[BOTTOM].y = matrix[1].w + matrix[1].y;
+			planes[BOTTOM].z = matrix[2].w + matrix[2].y;
+			planes[BOTTOM].w = matrix[3].w + matrix[3].y;
+
+			planes[BACK].x = matrix[0].w + matrix[0].z;
+			planes[BACK].y = matrix[1].w + matrix[1].z;
+			planes[BACK].z = matrix[2].w + matrix[2].z;
+			planes[BACK].w = matrix[3].w + matrix[3].z;
+
+			planes[FRONT].x = matrix[0].w - matrix[0].z;
+			planes[FRONT].y = matrix[1].w - matrix[1].z;
+			planes[FRONT].z = matrix[2].w - matrix[2].z;
+			planes[FRONT].w = matrix[3].w - matrix[3].z;
+
+			for (auto i = 0; i < planes.size(); i++)
+			{
+				float length = sqrtf(planes[i].x * planes[i].x + planes[i].y * planes[i].y + planes[i].z * planes[i].z);
+				planes[i] /= length;
+			}
+		}
+		
+		bool checkSphere(glm::vec3 pos, float radius)
+		{
+			for (auto i = 0; i < planes.size(); i++)
+			{
+				if ((planes[i].x * pos.x) + (planes[i].y * pos.y) + (planes[i].z * pos.z) + planes[i].w <= -radius)
+				{
+					return false;
+				}
+			}
+			return true;
+		}
+	};
+}
diff --git a/external/Vulkan/base/keycodes.hpp b/external/Vulkan/base/keycodes.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..d9d7f654416ae0168d1c652f33cdad81262de27d
--- /dev/null
+++ b/external/Vulkan/base/keycodes.hpp
@@ -0,0 +1,140 @@
+/*
+* Key codes for multiple platforms
+*
+* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#pragma once
+
+#if defined(_WIN32)
+#define KEY_ESCAPE VK_ESCAPE 
+#define KEY_F1 VK_F1
+#define KEY_F2 VK_F2
+#define KEY_F3 VK_F3
+#define KEY_F4 VK_F4
+#define KEY_F5 VK_F5
+#define KEY_W 0x57
+#define KEY_A 0x41
+#define KEY_S 0x53
+#define KEY_D 0x44
+#define KEY_P 0x50
+#define KEY_SPACE 0x20
+#define KEY_KPADD 0x6B
+#define KEY_KPSUB 0x6D
+#define KEY_B 0x42
+#define KEY_F 0x46
+#define KEY_L 0x4C
+#define KEY_N 0x4E
+#define KEY_O 0x4F
+#define KEY_T 0x54
+
+#elif defined(VK_USE_PLATFORM_ANDROID_KHR)
+#define GAMEPAD_BUTTON_A 0x1000
+#define GAMEPAD_BUTTON_B 0x1001
+#define GAMEPAD_BUTTON_X 0x1002
+#define GAMEPAD_BUTTON_Y 0x1003
+#define GAMEPAD_BUTTON_L1 0x1004
+#define GAMEPAD_BUTTON_R1 0x1005
+#define GAMEPAD_BUTTON_START 0x1006
+#define TOUCH_DOUBLE_TAP 0x1100
+
+#elif defined(VK_USE_PLATFORM_IOS_MVK)
+// Use numeric keys instead of function keys.
+// Use main keyboard plus/minus instead of keypad plus/minus
+// Use Delete key instead of Escape key.
+#define KEY_ESCAPE 0x33
+#define KEY_F1 '1'
+#define KEY_F2 '2'
+#define KEY_F3 '3'
+#define KEY_F4 '4'
+#define KEY_W 'w'
+#define KEY_A 'a'
+#define KEY_S 's'
+#define KEY_D 'd'
+#define KEY_P 'p'
+#define KEY_SPACE ' '
+#define KEY_KPADD '+'
+#define KEY_KPSUB '-'
+#define KEY_B 'b'
+#define KEY_F 'f'
+#define KEY_L 'l'
+#define KEY_N 'n'
+#define KEY_O 'o'
+#define KEY_T 't'
+
+#elif defined(VK_USE_PLATFORM_MACOS_MVK)
+// For compatibility with iOS UX and absent keypad on MacBook:
+// - Use numeric keys instead of function keys
+// - Use main keyboard plus/minus instead of keypad plus/minus
+// - Use Delete key instead of Escape key
+#define KEY_ESCAPE 0x33
+#define KEY_F1 0x12
+#define KEY_F2 0x13
+#define KEY_F3 0x14
+#define KEY_F4 0x15
+#define KEY_W 0x0D
+#define KEY_A 0x00
+#define KEY_S 0x01
+#define KEY_D 0x02
+#define KEY_P 0x23
+#define KEY_SPACE 0x31
+#define KEY_KPADD 0x18
+#define KEY_KPSUB 0x1B
+#define KEY_B 0x0B
+#define KEY_F 0x03
+#define KEY_L 0x25
+#define KEY_N 0x2D
+#define KEY_O 0x1F
+#define KEY_T 0x11
+
+#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT)
+#define KEY_ESCAPE DIKS_ESCAPE
+#define KEY_F1 DIKS_F1
+#define KEY_F2 DIKS_F2
+#define KEY_F3 DIKS_F3
+#define KEY_F4 DIKS_F4
+#define KEY_W DIKS_SMALL_W
+#define KEY_A DIKS_SMALL_A
+#define KEY_S DIKS_SMALL_S
+#define KEY_D DIKS_SMALL_D
+#define KEY_P DIKS_SMALL_P
+#define KEY_SPACE DIKS_SPACE
+#define KEY_KPADD DIKS_PLUS_SIGN
+#define KEY_KPSUB DIKS_MINUS_SIGN
+#define KEY_B DIKS_SMALL_B
+#define KEY_F DIKS_SMALL_F
+#define KEY_L DIKS_SMALL_L
+#define KEY_N DIKS_SMALL_N
+#define KEY_O DIKS_SMALL_O
+#define KEY_T DIKS_SMALL_T
+
+#elif defined(VK_USE_PLATFORM_WAYLAND_KHR)
+#include <linux/input.h>
+
+// todo: hack for bloom example
+#define KEY_KPADD KEY_KPPLUS
+#define KEY_KPSUB KEY_KPMINUS
+
+#elif defined(__linux__) || defined(__FreeBSD__)
+#define KEY_ESCAPE 0x9
+#define KEY_F1 0x43
+#define KEY_F2 0x44
+#define KEY_F3 0x45
+#define KEY_F4 0x46
+#define KEY_W 0x19
+#define KEY_A 0x26
+#define KEY_S 0x27
+#define KEY_D 0x28
+#define KEY_P 0x21
+#define KEY_SPACE 0x41
+#define KEY_KPADD 0x56
+#define KEY_KPSUB 0x52
+#define KEY_B 0x38
+#define KEY_F 0x29
+#define KEY_L 0x2E
+#define KEY_N 0x39
+#define KEY_O 0x20
+#define KEY_T 0x1C
+#endif
\ No newline at end of file
diff --git a/external/Vulkan/base/threadpool.hpp b/external/Vulkan/base/threadpool.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..d14ecf7115d3700e4eb8b9c0073bad2181469a31
--- /dev/null
+++ b/external/Vulkan/base/threadpool.hpp
@@ -0,0 +1,121 @@
+/*
+* Basic C++11 based thread pool with per-thread job queues
+*
+* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include <vector>
+#include <thread>
+#include <queue>
+#include <mutex>
+#include <condition_variable>
+#include <functional>
+
+// make_unique is not available in C++11
+// Taken from Herb Sutter's blog (https://herbsutter.com/gotw/_102/)
+template<typename T, typename ...Args>
+std::unique_ptr<T> make_unique(Args&& ...args)
+{
+	return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
+}
+
+namespace vks
+{
+	class Thread
+	{
+	private:
+		bool destroying = false;
+		std::thread worker;
+		std::queue<std::function<void()>> jobQueue;
+		std::mutex queueMutex;
+		std::condition_variable condition;
+
+		// Loop through all remaining jobs
+		void queueLoop()
+		{
+			while (true)
+			{
+				std::function<void()> job;
+				{
+					std::unique_lock<std::mutex> lock(queueMutex);
+					condition.wait(lock, [this] { return !jobQueue.empty() || destroying; });
+					if (destroying)
+					{
+						break;
+					}
+					job = jobQueue.front();
+				}
+
+				job();
+
+				{
+					std::lock_guard<std::mutex> lock(queueMutex);
+					jobQueue.pop();
+					condition.notify_one();
+				}
+			}
+		}
+
+	public:
+		Thread()
+		{
+			worker = std::thread(&Thread::queueLoop, this);
+		}
+
+		~Thread()
+		{
+			if (worker.joinable())
+			{
+				wait();
+				queueMutex.lock();
+				destroying = true;
+				condition.notify_one();
+				queueMutex.unlock();
+				worker.join();
+			}
+		}
+
+		// Add a new job to the thread's queue
+		void addJob(std::function<void()> function)
+		{
+			std::lock_guard<std::mutex> lock(queueMutex);
+			jobQueue.push(std::move(function));
+			condition.notify_one();
+		}
+
+		// Wait until all work items have been finished
+		void wait()
+		{
+			std::unique_lock<std::mutex> lock(queueMutex);
+			condition.wait(lock, [this]() { return jobQueue.empty(); });
+		}
+	};
+	
+	class ThreadPool
+	{
+	public:
+		std::vector<std::unique_ptr<Thread>> threads;
+
+		// Sets the number of threads to be allocated in this pool
+		void setThreadCount(uint32_t count)
+		{
+			threads.clear();
+			for (auto i = 0; i < count; i++)
+			{
+				threads.push_back(make_unique<Thread>());
+			}
+		}
+
+		// Wait until all threads have finished their work items
+		void wait()
+		{
+			for (auto &thread : threads)
+			{
+				thread->wait();
+			}
+		}
+	};
+
+}
diff --git a/external/Vulkan/base/vulkanexamplebase.cpp b/external/Vulkan/base/vulkanexamplebase.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..106145a5f53744ee04adb32a2d22de3501376647
--- /dev/null
+++ b/external/Vulkan/base/vulkanexamplebase.cpp
@@ -0,0 +1,2862 @@
+/*
+* Vulkan Example base class
+*
+* Copyright (C) by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkanexamplebase.h"
+
+#if (defined(VK_USE_PLATFORM_MACOS_MVK) && defined(VK_EXAMPLE_XCODE_GENERATED))
+#include <Cocoa/Cocoa.h>
+#include <Carbon/Carbon.h>
+#include <QuartzCore/CAMetalLayer.h>
+#include <CoreVideo/CVDisplayLink.h>
+#endif
+
+std::vector<const char*> VulkanExampleBase::args;
+
+VkResult VulkanExampleBase::createInstance(bool enableValidation)
+{
+	this->settings.validation = enableValidation;
+
+	// Validation can also be forced via a define
+#if defined(_VALIDATION)
+	this->settings.validation = true;
+#endif
+
+	VkApplicationInfo appInfo = {};
+	appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
+	appInfo.pApplicationName = name.c_str();
+	appInfo.pEngineName = name.c_str();
+	appInfo.apiVersion = apiVersion;
+
+	std::vector<const char*> instanceExtensions = { VK_KHR_SURFACE_EXTENSION_NAME };
+
+	// Enable surface extensions depending on os
+#if defined(_WIN32)
+	instanceExtensions.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME);
+#elif defined(VK_USE_PLATFORM_ANDROID_KHR)
+	instanceExtensions.push_back(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME);
+#elif defined(_DIRECT2DISPLAY)
+	instanceExtensions.push_back(VK_KHR_DISPLAY_EXTENSION_NAME);
+#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT)
+	instanceExtensions.push_back(VK_EXT_DIRECTFB_SURFACE_EXTENSION_NAME);
+#elif defined(VK_USE_PLATFORM_WAYLAND_KHR)
+	instanceExtensions.push_back(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME);
+#elif defined(VK_USE_PLATFORM_XCB_KHR)
+	instanceExtensions.push_back(VK_KHR_XCB_SURFACE_EXTENSION_NAME);
+#elif defined(VK_USE_PLATFORM_IOS_MVK)
+	instanceExtensions.push_back(VK_MVK_IOS_SURFACE_EXTENSION_NAME);
+#elif defined(VK_USE_PLATFORM_MACOS_MVK)
+	instanceExtensions.push_back(VK_MVK_MACOS_SURFACE_EXTENSION_NAME);
+#elif defined(VK_USE_PLATFORM_HEADLESS_EXT)
+	instanceExtensions.push_back(VK_EXT_HEADLESS_SURFACE_EXTENSION_NAME);
+#endif
+
+	// Get extensions supported by the instance and store for later use
+	uint32_t extCount = 0;
+	vkEnumerateInstanceExtensionProperties(nullptr, &extCount, nullptr);
+	if (extCount > 0)
+	{
+		std::vector<VkExtensionProperties> extensions(extCount);
+		if (vkEnumerateInstanceExtensionProperties(nullptr, &extCount, &extensions.front()) == VK_SUCCESS)
+		{
+			for (VkExtensionProperties extension : extensions)
+			{
+				supportedInstanceExtensions.push_back(extension.extensionName);
+			}
+		}
+	}
+
+	// Enabled requested instance extensions
+	if (enabledInstanceExtensions.size() > 0) 
+	{
+		for (const char * enabledExtension : enabledInstanceExtensions) 
+		{
+			// Output message if requested extension is not available
+			if (std::find(supportedInstanceExtensions.begin(), supportedInstanceExtensions.end(), enabledExtension) == supportedInstanceExtensions.end())
+			{
+				std::cerr << "Enabled instance extension \"" << enabledExtension << "\" is not present at instance level\n";
+			}
+			instanceExtensions.push_back(enabledExtension);
+		}
+	}
+
+	VkInstanceCreateInfo instanceCreateInfo = {};
+	instanceCreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
+	instanceCreateInfo.pNext = NULL;
+	instanceCreateInfo.pApplicationInfo = &appInfo;
+	if (instanceExtensions.size() > 0)
+	{
+		if (settings.validation)
+		{
+			instanceExtensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
+		}
+		instanceCreateInfo.enabledExtensionCount = (uint32_t)instanceExtensions.size();
+		instanceCreateInfo.ppEnabledExtensionNames = instanceExtensions.data();
+	}
+
+	// The VK_LAYER_KHRONOS_validation contains all current validation functionality.
+	// Note that on Android this layer requires at least NDK r20
+	const char* validationLayerName = "VK_LAYER_KHRONOS_validation";
+	if (settings.validation)
+	{
+		// Check if this layer is available at instance level
+		uint32_t instanceLayerCount;
+		vkEnumerateInstanceLayerProperties(&instanceLayerCount, nullptr);
+		std::vector<VkLayerProperties> instanceLayerProperties(instanceLayerCount);
+		vkEnumerateInstanceLayerProperties(&instanceLayerCount, instanceLayerProperties.data());
+		bool validationLayerPresent = false;
+		for (VkLayerProperties layer : instanceLayerProperties) {
+			if (strcmp(layer.layerName, validationLayerName) == 0) {
+				validationLayerPresent = true;
+				break;
+			}
+		}
+		if (validationLayerPresent) {
+			instanceCreateInfo.ppEnabledLayerNames = &validationLayerName;
+			instanceCreateInfo.enabledLayerCount = 1;
+		} else {
+			std::cerr << "Validation layer VK_LAYER_KHRONOS_validation not present, validation is disabled";
+		}
+	}
+	return vkCreateInstance(&instanceCreateInfo, nullptr, &instance);
+}
+
+void VulkanExampleBase::renderFrame()
+{
+	VulkanExampleBase::prepareFrame();
+	submitInfo.commandBufferCount = 1;
+	submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+	VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+	VulkanExampleBase::submitFrame();
+}
+
+std::string VulkanExampleBase::getWindowTitle()
+{
+	std::string device(deviceProperties.deviceName);
+	std::string windowTitle;
+	windowTitle = title + " - " + device;
+	if (!settings.overlay) {
+		windowTitle += " - " + std::to_string(frameCounter) + " fps";
+	}
+	return windowTitle;
+}
+
+void VulkanExampleBase::createCommandBuffers()
+{
+	// Create one command buffer for each swap chain image and reuse for rendering
+	drawCmdBuffers.resize(swapChain.imageCount);
+
+	VkCommandBufferAllocateInfo cmdBufAllocateInfo =
+		vks::initializers::commandBufferAllocateInfo(
+			cmdPool,
+			VK_COMMAND_BUFFER_LEVEL_PRIMARY,
+			static_cast<uint32_t>(drawCmdBuffers.size()));
+
+	VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, drawCmdBuffers.data()));
+}
+
+void VulkanExampleBase::destroyCommandBuffers()
+{
+	vkFreeCommandBuffers(device, cmdPool, static_cast<uint32_t>(drawCmdBuffers.size()), drawCmdBuffers.data());
+}
+
+std::string VulkanExampleBase::getShadersPath() const
+{
+	return getAssetPath() + "shaders/" + shaderDir + "/";
+}
+
+void VulkanExampleBase::createPipelineCache()
+{
+	VkPipelineCacheCreateInfo pipelineCacheCreateInfo = {};
+	pipelineCacheCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO;
+	VK_CHECK_RESULT(vkCreatePipelineCache(device, &pipelineCacheCreateInfo, nullptr, &pipelineCache));
+}
+
+void VulkanExampleBase::prepare()
+{
+	if (vulkanDevice->enableDebugMarkers) {
+		vks::debugmarker::setup(device);
+	}
+	initSwapchain();
+	createCommandPool();
+	setupSwapChain();
+	createCommandBuffers();
+	createSynchronizationPrimitives();
+	setupDepthStencil();
+	setupRenderPass();
+	createPipelineCache();
+	setupFrameBuffer();
+	settings.overlay = settings.overlay && (!benchmark.active);
+	if (settings.overlay) {
+		UIOverlay.device = vulkanDevice;
+		UIOverlay.queue = queue;
+		UIOverlay.shaders = {
+			loadShader(getShadersPath() + "base/uioverlay.vert.spv", VK_SHADER_STAGE_VERTEX_BIT),
+			loadShader(getShadersPath() + "base/uioverlay.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT),
+		};
+		UIOverlay.prepareResources();
+		UIOverlay.preparePipeline(pipelineCache, renderPass);
+	}
+}
+
+VkPipelineShaderStageCreateInfo VulkanExampleBase::loadShader(std::string fileName, VkShaderStageFlagBits stage)
+{
+	VkPipelineShaderStageCreateInfo shaderStage = {};
+	shaderStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+	shaderStage.stage = stage;
+#if defined(VK_USE_PLATFORM_ANDROID_KHR)
+	shaderStage.module = vks::tools::loadShader(androidApp->activity->assetManager, fileName.c_str(), device);
+#else
+	shaderStage.module = vks::tools::loadShader(fileName.c_str(), device);
+#endif
+	shaderStage.pName = "main";
+	assert(shaderStage.module != VK_NULL_HANDLE);
+	shaderModules.push_back(shaderStage.module);
+	return shaderStage;
+}
+
+void VulkanExampleBase::nextFrame()
+{
+	auto tStart = std::chrono::high_resolution_clock::now();
+	if (viewUpdated)
+	{
+		viewUpdated = false;
+		viewChanged();
+	}
+
+	render();
+	frameCounter++;
+	auto tEnd = std::chrono::high_resolution_clock::now();
+	auto tDiff = std::chrono::duration<double, std::milli>(tEnd - tStart).count();
+	frameTimer = (float)tDiff / 1000.0f;
+	camera.update(frameTimer);
+	if (camera.moving())
+	{
+		viewUpdated = true;
+	}
+	// Convert to clamped timer value
+	if (!paused)
+	{
+		timer += timerSpeed * frameTimer;
+		if (timer > 1.0)
+		{
+			timer -= 1.0f;
+		}
+	}
+	float fpsTimer = (float)(std::chrono::duration<double, std::milli>(tEnd - lastTimestamp).count());
+	if (fpsTimer > 1000.0f)
+	{
+		lastFPS = static_cast<uint32_t>((float)frameCounter * (1000.0f / fpsTimer));
+#if defined(_WIN32)
+		if (!settings.overlay)	{
+			std::string windowTitle = getWindowTitle();
+			SetWindowText(window, windowTitle.c_str());
+		}
+#endif
+		frameCounter = 0;
+		lastTimestamp = tEnd;
+	}
+	// TODO: Cap UI overlay update rates
+	updateOverlay();
+}
+
+void VulkanExampleBase::renderLoop()
+{
+	if (benchmark.active) {
+		benchmark.run([=] { render(); }, vulkanDevice->properties);
+		vkDeviceWaitIdle(device);
+		if (benchmark.filename != "") {
+			benchmark.saveResults();
+		}
+		return;
+	}
+
+	destWidth = width;
+	destHeight = height;
+	lastTimestamp = std::chrono::high_resolution_clock::now();
+#if defined(_WIN32)
+	MSG msg;
+	bool quitMessageReceived = false;
+	while (!quitMessageReceived) {
+		while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
+			TranslateMessage(&msg);
+			DispatchMessage(&msg);
+			if (msg.message == WM_QUIT) {
+				quitMessageReceived = true;
+				break;
+			}
+		}
+		if (prepared && !IsIconic(window)) {
+			nextFrame();
+		}
+	}
+#elif defined(VK_USE_PLATFORM_ANDROID_KHR)
+	while (1)
+	{
+		int ident;
+		int events;
+		struct android_poll_source* source;
+		bool destroy = false;
+
+		focused = true;
+
+		while ((ident = ALooper_pollAll(focused ? 0 : -1, NULL, &events, (void**)&source)) >= 0)
+		{
+			if (source != NULL)
+			{
+				source->process(androidApp, source);
+			}
+			if (androidApp->destroyRequested != 0)
+			{
+				LOGD("Android app destroy requested");
+				destroy = true;
+				break;
+			}
+		}
+
+		// App destruction requested
+		// Exit loop, example will be destroyed in application main
+		if (destroy)
+		{
+			ANativeActivity_finish(androidApp->activity);
+			break;
+		}
+
+		// Render frame
+		if (prepared)
+		{
+			auto tStart = std::chrono::high_resolution_clock::now();
+			render();
+			frameCounter++;
+			auto tEnd = std::chrono::high_resolution_clock::now();
+			auto tDiff = std::chrono::duration<double, std::milli>(tEnd - tStart).count();
+			frameTimer = tDiff / 1000.0f;
+			camera.update(frameTimer);
+			// Convert to clamped timer value
+			if (!paused)
+			{
+				timer += timerSpeed * frameTimer;
+				if (timer > 1.0)
+				{
+					timer -= 1.0f;
+				}
+			}
+			float fpsTimer = std::chrono::duration<double, std::milli>(tEnd - lastTimestamp).count();
+			if (fpsTimer > 1000.0f)
+			{
+				lastFPS = (float)frameCounter * (1000.0f / fpsTimer);
+				frameCounter = 0;
+				lastTimestamp = tEnd;
+			}
+
+			// TODO: Cap UI overlay update rates/only issue when update requested
+			updateOverlay();
+
+			bool updateView = false;
+
+			// Check touch state (for movement)
+			if (touchDown) {
+				touchTimer += frameTimer;
+			}
+			if (touchTimer >= 1.0) {
+				camera.keys.up = true;
+				viewChanged();
+			}
+
+			// Check gamepad state
+			const float deadZone = 0.0015f;
+			// todo : check if gamepad is present
+			// todo : time based and relative axis positions
+			if (camera.type != Camera::CameraType::firstperson)
+			{
+				// Rotate
+				if (std::abs(gamePadState.axisLeft.x) > deadZone)
+				{
+					camera.rotate(glm::vec3(0.0f, gamePadState.axisLeft.x * 0.5f, 0.0f));
+					updateView = true;
+				}
+				if (std::abs(gamePadState.axisLeft.y) > deadZone)
+				{
+					camera.rotate(glm::vec3(gamePadState.axisLeft.y * 0.5f, 0.0f, 0.0f));
+					updateView = true;
+				}
+				// Zoom
+				if (std::abs(gamePadState.axisRight.y) > deadZone)
+				{
+					camera.translate(glm::vec3(0.0f, 0.0f, gamePadState.axisRight.y * 0.01f));
+					updateView = true;
+				}
+				if (updateView)
+				{
+					viewChanged();
+				}
+			}
+			else
+			{
+				updateView = camera.updatePad(gamePadState.axisLeft, gamePadState.axisRight, frameTimer);
+				if (updateView)
+				{
+					viewChanged();
+				}
+			}
+		}
+	}
+#elif defined(_DIRECT2DISPLAY)
+	while (!quit)
+	{
+		auto tStart = std::chrono::high_resolution_clock::now();
+		if (viewUpdated)
+		{
+			viewUpdated = false;
+			viewChanged();
+		}
+		render();
+		frameCounter++;
+		auto tEnd = std::chrono::high_resolution_clock::now();
+		auto tDiff = std::chrono::duration<double, std::milli>(tEnd - tStart).count();
+		frameTimer = tDiff / 1000.0f;
+		camera.update(frameTimer);
+		if (camera.moving())
+		{
+			viewUpdated = true;
+		}
+		// Convert to clamped timer value
+		if (!paused)
+		{
+			timer += timerSpeed * frameTimer;
+			if (timer > 1.0)
+			{
+				timer -= 1.0f;
+			}
+		}
+		float fpsTimer = std::chrono::duration<double, std::milli>(tEnd - lastTimestamp).count();
+		if (fpsTimer > 1000.0f)
+		{
+			lastFPS = (float)frameCounter * (1000.0f / fpsTimer);
+			frameCounter = 0;
+			lastTimestamp = tEnd;
+		}
+		updateOverlay();
+	}
+#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT)
+	while (!quit)
+	{
+		auto tStart = std::chrono::high_resolution_clock::now();
+		if (viewUpdated)
+		{
+			viewUpdated = false;
+			viewChanged();
+		}
+		DFBWindowEvent event;
+		while (!event_buffer->GetEvent(event_buffer, DFB_EVENT(&event)))
+		{
+			handleEvent(&event);
+		}
+		render();
+		frameCounter++;
+		auto tEnd = std::chrono::high_resolution_clock::now();
+		auto tDiff = std::chrono::duration<double, std::milli>(tEnd - tStart).count();
+		frameTimer = tDiff / 1000.0f;
+		camera.update(frameTimer);
+		if (camera.moving())
+		{
+			viewUpdated = true;
+		}
+		// Convert to clamped timer value
+		if (!paused)
+		{
+			timer += timerSpeed * frameTimer;
+			if (timer > 1.0)
+			{
+				timer -= 1.0f;
+			}
+		}
+		float fpsTimer = std::chrono::duration<double, std::milli>(tEnd - lastTimestamp).count();
+		if (fpsTimer > 1000.0f)
+		{
+			lastFPS = (float)frameCounter * (1000.0f / fpsTimer);
+			frameCounter = 0;
+			lastTimestamp = tEnd;
+		}
+		updateOverlay();
+	}
+#elif defined(VK_USE_PLATFORM_WAYLAND_KHR)
+	while (!quit)
+	{
+		auto tStart = std::chrono::high_resolution_clock::now();
+		if (viewUpdated)
+		{
+			viewUpdated = false;
+			viewChanged();
+		}
+
+		while (!configured)
+			wl_display_dispatch(display);
+		while (wl_display_prepare_read(display) != 0)
+			wl_display_dispatch_pending(display);
+		wl_display_flush(display);
+		wl_display_read_events(display);
+		wl_display_dispatch_pending(display);
+
+		render();
+		frameCounter++;
+		auto tEnd = std::chrono::high_resolution_clock::now();
+		auto tDiff = std::chrono::duration<double, std::milli>(tEnd - tStart).count();
+		frameTimer = tDiff / 1000.0f;
+		camera.update(frameTimer);
+		if (camera.moving())
+		{
+			viewUpdated = true;
+		}
+		// Convert to clamped timer value
+		if (!paused)
+		{
+			timer += timerSpeed * frameTimer;
+			if (timer > 1.0)
+			{
+				timer -= 1.0f;
+			}
+		}
+		float fpsTimer = std::chrono::duration<double, std::milli>(tEnd - lastTimestamp).count();
+		if (fpsTimer > 1000.0f)
+		{
+			if (!settings.overlay)
+			{
+				std::string windowTitle = getWindowTitle();
+				xdg_toplevel_set_title(xdg_toplevel, windowTitle.c_str());
+			}
+			lastFPS = (float)frameCounter * (1000.0f / fpsTimer);
+			frameCounter = 0;
+			lastTimestamp = tEnd;
+		}
+		updateOverlay();
+	}
+#elif defined(VK_USE_PLATFORM_XCB_KHR)
+	xcb_flush(connection);
+	while (!quit)
+	{
+		auto tStart = std::chrono::high_resolution_clock::now();
+		if (viewUpdated)
+		{
+			viewUpdated = false;
+			viewChanged();
+		}
+		xcb_generic_event_t *event;
+		while ((event = xcb_poll_for_event(connection)))
+		{
+			handleEvent(event);
+			free(event);
+		}
+		render();
+		frameCounter++;
+		auto tEnd = std::chrono::high_resolution_clock::now();
+		auto tDiff = std::chrono::duration<double, std::milli>(tEnd - tStart).count();
+		frameTimer = tDiff / 1000.0f;
+		camera.update(frameTimer);
+		if (camera.moving())
+		{
+			viewUpdated = true;
+		}
+		// Convert to clamped timer value
+		if (!paused)
+		{
+			timer += timerSpeed * frameTimer;
+			if (timer > 1.0)
+			{
+				timer -= 1.0f;
+			}
+		}
+		float fpsTimer = std::chrono::duration<double, std::milli>(tEnd - lastTimestamp).count();
+		if (fpsTimer > 1000.0f)
+		{
+			if (!settings.overlay)
+			{
+				std::string windowTitle = getWindowTitle();
+				xcb_change_property(connection, XCB_PROP_MODE_REPLACE,
+					window, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8,
+					windowTitle.size(), windowTitle.c_str());
+			}
+			lastFPS = (float)frameCounter * (1000.0f / fpsTimer);
+			frameCounter = 0;
+			lastTimestamp = tEnd;
+		}
+		updateOverlay();
+	}
+#elif defined(VK_USE_PLATFORM_HEADLESS_EXT)
+	while (!quit)
+	{
+		auto tStart = std::chrono::high_resolution_clock::now();
+		if (viewUpdated)
+		{
+			viewUpdated = false;
+			viewChanged();
+		}
+		render();
+		frameCounter++;
+		auto tEnd = std::chrono::high_resolution_clock::now();
+		auto tDiff = std::chrono::duration<double, std::milli>(tEnd - tStart).count();
+		frameTimer = tDiff / 1000.0f;
+		camera.update(frameTimer);
+		if (camera.moving())
+		{
+			viewUpdated = true;
+		}
+		// Convert to clamped timer value
+		timer += timerSpeed * frameTimer;
+		if (timer > 1.0)
+		{
+			timer -= 1.0f;
+		}
+		float fpsTimer = std::chrono::duration<double, std::milli>(tEnd - lastTimestamp).count();
+		if (fpsTimer > 1000.0f)
+		{
+			lastFPS = (float)frameCounter * (1000.0f / fpsTimer);
+			frameCounter = 0;
+			lastTimestamp = tEnd;
+		}
+		updateOverlay();
+	}
+#elif (defined(VK_USE_PLATFORM_MACOS_MVK) && defined(VK_EXAMPLE_XCODE_GENERATED))
+	[NSApp run];
+#endif
+	// Flush device to make sure all resources can be freed
+	if (device != VK_NULL_HANDLE) {
+		vkDeviceWaitIdle(device);
+	}
+}
+
+void VulkanExampleBase::updateOverlay()
+{
+	if (!settings.overlay)
+		return;
+
+	ImGuiIO& io = ImGui::GetIO();
+
+	io.DisplaySize = ImVec2((float)width, (float)height);
+	io.DeltaTime = frameTimer;
+
+	io.MousePos = ImVec2(mousePos.x, mousePos.y);
+	io.MouseDown[0] = mouseButtons.left;
+	io.MouseDown[1] = mouseButtons.right;
+
+	ImGui::NewFrame();
+
+	ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0);
+	ImGui::SetNextWindowPos(ImVec2(10, 10));
+	ImGui::SetNextWindowSize(ImVec2(0, 0), ImGuiSetCond_FirstUseEver);
+	ImGui::Begin("Vulkan Example", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove);
+	ImGui::TextUnformatted(title.c_str());
+	ImGui::TextUnformatted(deviceProperties.deviceName);
+	ImGui::Text("%.2f ms/frame (%.1d fps)", (1000.0f / lastFPS), lastFPS);
+
+#if defined(VK_USE_PLATFORM_ANDROID_KHR)
+	ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 5.0f * UIOverlay.scale));
+#endif
+	ImGui::PushItemWidth(110.0f * UIOverlay.scale);
+	OnUpdateUIOverlay(&UIOverlay);
+	ImGui::PopItemWidth();
+#if defined(VK_USE_PLATFORM_ANDROID_KHR)
+	ImGui::PopStyleVar();
+#endif
+
+	ImGui::End();
+	ImGui::PopStyleVar();
+	ImGui::Render();
+
+	if (UIOverlay.update() || UIOverlay.updated) {
+		buildCommandBuffers();
+		UIOverlay.updated = false;
+	}
+
+#if defined(VK_USE_PLATFORM_ANDROID_KHR)
+	if (mouseButtons.left) {
+		mouseButtons.left = false;
+	}
+#endif
+}
+
+void VulkanExampleBase::drawUI(const VkCommandBuffer commandBuffer)
+{
+	if (settings.overlay) {
+		const VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+		const VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
+		vkCmdSetViewport(commandBuffer, 0, 1, &viewport);
+		vkCmdSetScissor(commandBuffer, 0, 1, &scissor);
+
+		UIOverlay.draw(commandBuffer);
+	}
+}
+
+void VulkanExampleBase::prepareFrame()
+{
+	// Acquire the next image from the swap chain
+	VkResult result = swapChain.acquireNextImage(semaphores.presentComplete, &currentBuffer);
+	// Recreate the swapchain if it's no longer compatible with the surface (OUT_OF_DATE) or no longer optimal for presentation (SUBOPTIMAL)
+	if ((result == VK_ERROR_OUT_OF_DATE_KHR) || (result == VK_SUBOPTIMAL_KHR)) {
+		windowResize();
+	}
+	else {
+		VK_CHECK_RESULT(result);
+	}
+}
+
+void VulkanExampleBase::submitFrame()
+{
+	VkResult result = swapChain.queuePresent(queue, currentBuffer, semaphores.renderComplete);
+	if (!((result == VK_SUCCESS) || (result == VK_SUBOPTIMAL_KHR))) {
+		if (result == VK_ERROR_OUT_OF_DATE_KHR) {
+			// Swap chain is no longer compatible with the surface and needs to be recreated
+			windowResize();
+			return;
+		} else {
+			VK_CHECK_RESULT(result);
+		}
+	}
+	VK_CHECK_RESULT(vkQueueWaitIdle(queue));
+}
+
+VulkanExampleBase::VulkanExampleBase(bool enableValidation)
+{
+#if !defined(VK_USE_PLATFORM_ANDROID_KHR)
+	// Check for a valid asset path
+	struct stat info;
+	if (stat(getAssetPath().c_str(), &info) != 0)
+	{
+#if defined(_WIN32)
+		std::string msg = "Could not locate asset path in \"" + getAssetPath() + "\" !";
+		MessageBox(NULL, msg.c_str(), "Fatal error", MB_OK | MB_ICONERROR);
+#else
+		std::cerr << "Error: Could not find asset path in " << getAssetPath() << "\n";
+#endif
+		exit(-1);
+	}
+#endif
+
+	settings.validation = enableValidation;
+	
+	// Command line arguments
+	commandLineParser.parse(args);
+	if (commandLineParser.isSet("help")) {
+#if defined(_WIN32)
+		setupConsole("Vulkan example");
+#endif
+		commandLineParser.printHelp();
+		std::cin.get();
+		exit(0);
+	}
+	if (commandLineParser.isSet("validation")) {
+		settings.validation = true;
+	}
+	if (commandLineParser.isSet("vsync")) {
+		settings.vsync = true;
+	}
+	if (commandLineParser.isSet("height")) {
+		height = commandLineParser.getValueAsInt("height", width);
+	}
+	if (commandLineParser.isSet("width")) {
+		width = commandLineParser.getValueAsInt("width", width);
+	}
+	if (commandLineParser.isSet("fullscreen")) {
+		settings.fullscreen = true;
+	}
+	if (commandLineParser.isSet("shaders")) {
+		std::string value = commandLineParser.getValueAsString("shaders", "glsl");
+		if ((value != "glsl") && (value != "hlsl")) {
+			std::cerr << "Shader type must be one of 'glsl' or 'hlsl'\n";
+		}
+		else {
+			shaderDir = value;
+		}
+	}
+	if (commandLineParser.isSet("benchmark")) {
+		benchmark.active = true;
+		vks::tools::errorModeSilent = true;
+	}
+	if (commandLineParser.isSet("benchmarkwarmup")) {
+		benchmark.warmup = commandLineParser.getValueAsInt("benchmarkwarmup", benchmark.warmup);
+	}
+	if (commandLineParser.isSet("benchmarkruntime")) {
+		benchmark.duration = commandLineParser.getValueAsInt("benchmarkruntime", benchmark.duration);
+	}
+	if (commandLineParser.isSet("benchmarkresultfile")) {
+		benchmark.filename = commandLineParser.getValueAsString("benchmarkresultfile", benchmark.filename);
+	}	
+	if (commandLineParser.isSet("benchmarkresultframes")) {
+		benchmark.outputFrameTimes = true;
+	}
+	if (commandLineParser.isSet("benchmarkframes")) {
+		benchmark.outputFrames = commandLineParser.getValueAsInt("benchmarkframes", benchmark.outputFrames);
+	}
+
+#if defined(VK_USE_PLATFORM_ANDROID_KHR)
+	// Vulkan library is loaded dynamically on Android
+	bool libLoaded = vks::android::loadVulkanLibrary();
+	assert(libLoaded);
+#elif defined(_DIRECT2DISPLAY)
+
+#elif defined(VK_USE_PLATFORM_WAYLAND_KHR)
+	initWaylandConnection();
+#elif defined(VK_USE_PLATFORM_XCB_KHR)
+	initxcbConnection();
+#endif
+
+#if defined(_WIN32)
+	// Enable console if validation is active, debug message callback will output to it
+	if (this->settings.validation)
+	{
+		setupConsole("Vulkan example");
+	}
+	setupDPIAwareness();
+#endif
+}
+
+VulkanExampleBase::~VulkanExampleBase()
+{
+	// Clean up Vulkan resources
+	swapChain.cleanup();
+	if (descriptorPool != VK_NULL_HANDLE)
+	{
+		vkDestroyDescriptorPool(device, descriptorPool, nullptr);
+	}
+	destroyCommandBuffers();
+	if (renderPass != VK_NULL_HANDLE)
+	{
+		vkDestroyRenderPass(device, renderPass, nullptr);
+	}
+	for (uint32_t i = 0; i < frameBuffers.size(); i++)
+	{
+		vkDestroyFramebuffer(device, frameBuffers[i], nullptr);
+	}
+
+	for (auto& shaderModule : shaderModules)
+	{
+		vkDestroyShaderModule(device, shaderModule, nullptr);
+	}
+	vkDestroyImageView(device, depthStencil.view, nullptr);
+	vkDestroyImage(device, depthStencil.image, nullptr);
+	vkFreeMemory(device, depthStencil.mem, nullptr);
+
+	vkDestroyPipelineCache(device, pipelineCache, nullptr);
+
+	vkDestroyCommandPool(device, cmdPool, nullptr);
+
+	vkDestroySemaphore(device, semaphores.presentComplete, nullptr);
+	vkDestroySemaphore(device, semaphores.renderComplete, nullptr);
+	for (auto& fence : waitFences) {
+		vkDestroyFence(device, fence, nullptr);
+	}
+
+	if (settings.overlay) {
+		UIOverlay.freeResources();
+	}
+
+	delete vulkanDevice;
+
+	if (settings.validation)
+	{
+		vks::debug::freeDebugCallback(instance);
+	}
+
+	vkDestroyInstance(instance, nullptr);
+
+#if defined(_DIRECT2DISPLAY)
+
+#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT)
+	if (event_buffer)
+		event_buffer->Release(event_buffer);
+	if (surface)
+		surface->Release(surface);
+	if (window)
+		window->Release(window);
+	if (layer)
+		layer->Release(layer);
+	if (dfb)
+		dfb->Release(dfb);
+#elif defined(VK_USE_PLATFORM_WAYLAND_KHR)
+	xdg_toplevel_destroy(xdg_toplevel);
+	xdg_surface_destroy(xdg_surface);
+	wl_surface_destroy(surface);
+	if (keyboard)
+		wl_keyboard_destroy(keyboard);
+	if (pointer)
+		wl_pointer_destroy(pointer);
+	if (seat)
+		wl_seat_destroy(seat);
+	xdg_wm_base_destroy(shell);
+	wl_compositor_destroy(compositor);
+	wl_registry_destroy(registry);
+	wl_display_disconnect(display);
+#elif defined(VK_USE_PLATFORM_ANDROID_KHR)
+	// todo : android cleanup (if required)
+#elif defined(VK_USE_PLATFORM_XCB_KHR)
+	xcb_destroy_window(connection, window);
+	xcb_disconnect(connection);
+#endif
+}
+
+bool VulkanExampleBase::initVulkan()
+{
+	VkResult err;
+
+	// Vulkan instance
+	err = createInstance(settings.validation);
+	if (err) {
+		vks::tools::exitFatal("Could not create Vulkan instance : \n" + vks::tools::errorString(err), err);
+		return false;
+	}
+
+#if defined(VK_USE_PLATFORM_ANDROID_KHR)
+	vks::android::loadVulkanFunctions(instance);
+#endif
+
+	// If requested, we enable the default validation layers for debugging
+	if (settings.validation)
+	{
+		// The report flags determine what type of messages for the layers will be displayed
+		// For validating (debugging) an application the error and warning bits should suffice
+		VkDebugReportFlagsEXT debugReportFlags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT;
+		// Additional flags include performance info, loader and layer debug messages, etc.
+		vks::debug::setupDebugging(instance, debugReportFlags, VK_NULL_HANDLE);
+	}
+
+	// Physical device
+	uint32_t gpuCount = 0;
+	// Get number of available physical devices
+	VK_CHECK_RESULT(vkEnumeratePhysicalDevices(instance, &gpuCount, nullptr));
+	if (gpuCount == 0) {
+		vks::tools::exitFatal("No device with Vulkan support found", -1);
+		return false;
+	}
+	// Enumerate devices
+	std::vector<VkPhysicalDevice> physicalDevices(gpuCount);
+	err = vkEnumeratePhysicalDevices(instance, &gpuCount, physicalDevices.data());
+	if (err) {
+		vks::tools::exitFatal("Could not enumerate physical devices : \n" + vks::tools::errorString(err), err);
+		return false;
+	}
+
+	// GPU selection
+
+	// Select physical device to be used for the Vulkan example
+	// Defaults to the first device unless specified by command line
+	uint32_t selectedDevice = 0;
+
+#if !defined(VK_USE_PLATFORM_ANDROID_KHR)
+	// GPU selection via command line argument
+	if (commandLineParser.isSet("gpuselection")) {
+		uint32_t index = commandLineParser.getValueAsInt("gpuselection", 0);
+		if (index > gpuCount - 1) {
+			std::cerr << "Selected device index " << index << " is out of range, reverting to device 0 (use -listgpus to show available Vulkan devices)" << "\n";
+		} else {
+			selectedDevice = index;
+		}
+	}
+	if (commandLineParser.isSet("gpulist")) {
+		std::cout << "Available Vulkan devices" << "\n";
+		for (uint32_t i = 0; i < gpuCount; i++) {
+			VkPhysicalDeviceProperties deviceProperties;
+			vkGetPhysicalDeviceProperties(physicalDevices[i], &deviceProperties);
+			std::cout << "Device [" << i << "] : " << deviceProperties.deviceName << std::endl;
+			std::cout << " Type: " << vks::tools::physicalDeviceTypeString(deviceProperties.deviceType) << "\n";
+			std::cout << " API: " << (deviceProperties.apiVersion >> 22) << "." << ((deviceProperties.apiVersion >> 12) & 0x3ff) << "." << (deviceProperties.apiVersion & 0xfff) << "\n";
+		}
+	}
+#endif
+
+	physicalDevice = physicalDevices[selectedDevice];
+
+	// Store properties (including limits), features and memory properties of the physical device (so that examples can check against them)
+	vkGetPhysicalDeviceProperties(physicalDevice, &deviceProperties);
+	vkGetPhysicalDeviceFeatures(physicalDevice, &deviceFeatures);
+	vkGetPhysicalDeviceMemoryProperties(physicalDevice, &deviceMemoryProperties);
+
+	// Derived examples can override this to set actual features (based on above readings) to enable for logical device creation
+	getEnabledFeatures();
+
+	// Vulkan device creation
+	// This is handled by a separate class that gets a logical device representation
+	// and encapsulates functions related to a device
+	vulkanDevice = new vks::VulkanDevice(physicalDevice);
+	VkResult res = vulkanDevice->createLogicalDevice(enabledFeatures, enabledDeviceExtensions, deviceCreatepNextChain);
+	if (res != VK_SUCCESS) {
+		vks::tools::exitFatal("Could not create Vulkan device: \n" + vks::tools::errorString(res), res);
+		return false;
+	}
+	device = vulkanDevice->logicalDevice;
+
+	// Get a graphics queue from the device
+	vkGetDeviceQueue(device, vulkanDevice->queueFamilyIndices.graphics, 0, &queue);
+
+	// Find a suitable depth format
+	VkBool32 validDepthFormat = vks::tools::getSupportedDepthFormat(physicalDevice, &depthFormat);
+	assert(validDepthFormat);
+
+	swapChain.connect(instance, physicalDevice, device);
+
+	// Create synchronization objects
+	VkSemaphoreCreateInfo semaphoreCreateInfo = vks::initializers::semaphoreCreateInfo();
+	// Create a semaphore used to synchronize image presentation
+	// Ensures that the image is displayed before we start submitting new commands to the queue
+	VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &semaphores.presentComplete));
+	// Create a semaphore used to synchronize command submission
+	// Ensures that the image is not presented until all commands have been submitted and executed
+	VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &semaphores.renderComplete));
+
+	// Set up submit info structure
+	// Semaphores will stay the same during application lifetime
+	// Command buffer submission info is set by each example
+	submitInfo = vks::initializers::submitInfo();
+	submitInfo.pWaitDstStageMask = &submitPipelineStages;
+	submitInfo.waitSemaphoreCount = 1;
+	submitInfo.pWaitSemaphores = &semaphores.presentComplete;
+	submitInfo.signalSemaphoreCount = 1;
+	submitInfo.pSignalSemaphores = &semaphores.renderComplete;
+
+	return true;
+}
+
+#if defined(_WIN32)
+// Win32 : Sets up a console window and redirects standard output to it
+void VulkanExampleBase::setupConsole(std::string title)
+{
+	AllocConsole();
+	AttachConsole(GetCurrentProcessId());
+	FILE *stream;
+	freopen_s(&stream, "CONIN$", "r", stdin);
+	freopen_s(&stream, "CONOUT$", "w+", stdout);
+	freopen_s(&stream, "CONOUT$", "w+", stderr);
+	SetConsoleTitle(TEXT(title.c_str()));
+}
+
+void VulkanExampleBase::setupDPIAwareness()
+{
+	typedef HRESULT *(__stdcall *SetProcessDpiAwarenessFunc)(PROCESS_DPI_AWARENESS);
+
+	HMODULE shCore = LoadLibraryA("Shcore.dll");
+	if (shCore)
+	{
+		SetProcessDpiAwarenessFunc setProcessDpiAwareness =
+			(SetProcessDpiAwarenessFunc)GetProcAddress(shCore, "SetProcessDpiAwareness");
+
+		if (setProcessDpiAwareness != nullptr)
+		{
+			setProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE);
+		}
+
+		FreeLibrary(shCore);
+	}
+}
+
+HWND VulkanExampleBase::setupWindow(HINSTANCE hinstance, WNDPROC wndproc)
+{
+	this->windowInstance = hinstance;
+
+	WNDCLASSEX wndClass;
+
+	wndClass.cbSize = sizeof(WNDCLASSEX);
+	wndClass.style = CS_HREDRAW | CS_VREDRAW;
+	wndClass.lpfnWndProc = wndproc;
+	wndClass.cbClsExtra = 0;
+	wndClass.cbWndExtra = 0;
+	wndClass.hInstance = hinstance;
+	wndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
+	wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
+	wndClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
+	wndClass.lpszMenuName = NULL;
+	wndClass.lpszClassName = name.c_str();
+	wndClass.hIconSm = LoadIcon(NULL, IDI_WINLOGO);
+
+	if (!RegisterClassEx(&wndClass))
+	{
+		std::cout << "Could not register window class!\n";
+		fflush(stdout);
+		exit(1);
+	}
+
+	int screenWidth = GetSystemMetrics(SM_CXSCREEN);
+	int screenHeight = GetSystemMetrics(SM_CYSCREEN);
+
+	if (settings.fullscreen)
+	{
+		if ((width != (uint32_t)screenWidth) && (height != (uint32_t)screenHeight))
+		{
+			DEVMODE dmScreenSettings;
+			memset(&dmScreenSettings, 0, sizeof(dmScreenSettings));
+			dmScreenSettings.dmSize       = sizeof(dmScreenSettings);
+			dmScreenSettings.dmPelsWidth  = width;
+			dmScreenSettings.dmPelsHeight = height;
+			dmScreenSettings.dmBitsPerPel = 32;
+			dmScreenSettings.dmFields     = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;
+			if (ChangeDisplaySettings(&dmScreenSettings, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL)
+			{
+				if (MessageBox(NULL, "Fullscreen Mode not supported!\n Switch to window mode?", "Error", MB_YESNO | MB_ICONEXCLAMATION) == IDYES)
+				{
+					settings.fullscreen = false;
+				}
+				else
+				{
+					return nullptr;
+				}
+			}
+			screenWidth = width;
+			screenHeight = height;
+		}
+
+	}
+
+	DWORD dwExStyle;
+	DWORD dwStyle;
+
+	if (settings.fullscreen)
+	{
+		dwExStyle = WS_EX_APPWINDOW;
+		dwStyle = WS_POPUP | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;
+	}
+	else
+	{
+		dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
+		dwStyle = WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;
+	}
+
+	RECT windowRect;
+	windowRect.left = 0L;
+	windowRect.top = 0L;
+	windowRect.right = settings.fullscreen ? (long)screenWidth : (long)width;
+	windowRect.bottom = settings.fullscreen ? (long)screenHeight : (long)height;
+
+	AdjustWindowRectEx(&windowRect, dwStyle, FALSE, dwExStyle);
+
+	std::string windowTitle = getWindowTitle();
+	window = CreateWindowEx(0,
+		name.c_str(),
+		windowTitle.c_str(),
+		dwStyle | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
+		0,
+		0,
+		windowRect.right - windowRect.left,
+		windowRect.bottom - windowRect.top,
+		NULL,
+		NULL,
+		hinstance,
+		NULL);
+
+	if (!settings.fullscreen)
+	{
+		// Center on screen
+		uint32_t x = (GetSystemMetrics(SM_CXSCREEN) - windowRect.right) / 2;
+		uint32_t y = (GetSystemMetrics(SM_CYSCREEN) - windowRect.bottom) / 2;
+		SetWindowPos(window, 0, x, y, 0, 0, SWP_NOZORDER | SWP_NOSIZE);
+	}
+
+	if (!window)
+	{
+		printf("Could not create window!\n");
+		fflush(stdout);
+		return nullptr;
+	}
+
+	ShowWindow(window, SW_SHOW);
+	SetForegroundWindow(window);
+	SetFocus(window);
+
+	return window;
+}
+
+void VulkanExampleBase::handleMessages(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+	switch (uMsg)
+	{
+	case WM_CLOSE:
+		prepared = false;
+		DestroyWindow(hWnd);
+		PostQuitMessage(0);
+		break;
+	case WM_PAINT:
+		ValidateRect(window, NULL);
+		break;
+	case WM_KEYDOWN:
+		switch (wParam)
+		{
+		case KEY_P:
+			paused = !paused;
+			break;
+		case KEY_F1:
+			if (settings.overlay) {
+				UIOverlay.visible = !UIOverlay.visible;
+			}
+			break;
+		case KEY_ESCAPE:
+			PostQuitMessage(0);
+			break;
+		}
+
+		if (camera.type == Camera::firstperson)
+		{
+			switch (wParam)
+			{
+			case KEY_W:
+				camera.keys.up = true;
+				break;
+			case KEY_S:
+				camera.keys.down = true;
+				break;
+			case KEY_A:
+				camera.keys.left = true;
+				break;
+			case KEY_D:
+				camera.keys.right = true;
+				break;
+			}
+		}
+
+		keyPressed((uint32_t)wParam);
+		break;
+	case WM_KEYUP:
+		if (camera.type == Camera::firstperson)
+		{
+			switch (wParam)
+			{
+			case KEY_W:
+				camera.keys.up = false;
+				break;
+			case KEY_S:
+				camera.keys.down = false;
+				break;
+			case KEY_A:
+				camera.keys.left = false;
+				break;
+			case KEY_D:
+				camera.keys.right = false;
+				break;
+			}
+		}
+		break;
+	case WM_LBUTTONDOWN:
+		mousePos = glm::vec2((float)LOWORD(lParam), (float)HIWORD(lParam));
+		mouseButtons.left = true;
+		break;
+	case WM_RBUTTONDOWN:
+		mousePos = glm::vec2((float)LOWORD(lParam), (float)HIWORD(lParam));
+		mouseButtons.right = true;
+		break;
+	case WM_MBUTTONDOWN:
+		mousePos = glm::vec2((float)LOWORD(lParam), (float)HIWORD(lParam));
+		mouseButtons.middle = true;
+		break;
+	case WM_LBUTTONUP:
+		mouseButtons.left = false;
+		break;
+	case WM_RBUTTONUP:
+		mouseButtons.right = false;
+		break;
+	case WM_MBUTTONUP:
+		mouseButtons.middle = false;
+		break;
+	case WM_MOUSEWHEEL:
+	{
+		short wheelDelta = GET_WHEEL_DELTA_WPARAM(wParam);
+		camera.translate(glm::vec3(0.0f, 0.0f, (float)wheelDelta * 0.005f));
+		viewUpdated = true;
+		break;
+	}
+	case WM_MOUSEMOVE:
+	{
+		handleMouseMove(LOWORD(lParam), HIWORD(lParam));
+		break;
+	}
+	case WM_SIZE:
+		if ((prepared) && (wParam != SIZE_MINIMIZED))
+		{
+			if ((resizing) || ((wParam == SIZE_MAXIMIZED) || (wParam == SIZE_RESTORED)))
+			{
+				destWidth = LOWORD(lParam);
+				destHeight = HIWORD(lParam);
+				windowResize();
+			}
+		}
+		break;
+	case WM_GETMINMAXINFO:
+	{
+		LPMINMAXINFO minMaxInfo = (LPMINMAXINFO)lParam;
+		minMaxInfo->ptMinTrackSize.x = 64;
+		minMaxInfo->ptMinTrackSize.y = 64;
+		break;
+	}
+	case WM_ENTERSIZEMOVE:
+		resizing = true;
+		break;
+	case WM_EXITSIZEMOVE:
+		resizing = false;
+		break;
+	}
+}
+#elif defined(VK_USE_PLATFORM_ANDROID_KHR)
+int32_t VulkanExampleBase::handleAppInput(struct android_app* app, AInputEvent* event)
+{
+	VulkanExampleBase* vulkanExample = reinterpret_cast<VulkanExampleBase*>(app->userData);
+	if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION)
+	{
+		int32_t eventSource = AInputEvent_getSource(event);
+		switch (eventSource) {
+			case AINPUT_SOURCE_JOYSTICK: {
+				// Left thumbstick
+				vulkanExample->gamePadState.axisLeft.x = AMotionEvent_getAxisValue(event, AMOTION_EVENT_AXIS_X, 0);
+				vulkanExample->gamePadState.axisLeft.y = AMotionEvent_getAxisValue(event, AMOTION_EVENT_AXIS_Y, 0);
+				// Right thumbstick
+				vulkanExample->gamePadState.axisRight.x = AMotionEvent_getAxisValue(event, AMOTION_EVENT_AXIS_Z, 0);
+				vulkanExample->gamePadState.axisRight.y = AMotionEvent_getAxisValue(event, AMOTION_EVENT_AXIS_RZ, 0);
+				break;
+			}
+
+			case AINPUT_SOURCE_TOUCHSCREEN: {
+				int32_t action = AMotionEvent_getAction(event);
+
+				switch (action) {
+					case AMOTION_EVENT_ACTION_UP: {
+						vulkanExample->lastTapTime = AMotionEvent_getEventTime(event);
+						vulkanExample->touchPos.x = AMotionEvent_getX(event, 0);
+						vulkanExample->touchPos.y = AMotionEvent_getY(event, 0);
+						vulkanExample->touchTimer = 0.0;
+						vulkanExample->touchDown = false;
+						vulkanExample->camera.keys.up = false;
+
+						// Detect single tap
+						int64_t eventTime = AMotionEvent_getEventTime(event);
+						int64_t downTime = AMotionEvent_getDownTime(event);
+						if (eventTime - downTime <= vks::android::TAP_TIMEOUT) {
+							float deadZone = (160.f / vks::android::screenDensity) * vks::android::TAP_SLOP * vks::android::TAP_SLOP;
+							float x = AMotionEvent_getX(event, 0) - vulkanExample->touchPos.x;
+							float y = AMotionEvent_getY(event, 0) - vulkanExample->touchPos.y;
+							if ((x * x + y * y) < deadZone) {
+								vulkanExample->mouseButtons.left = true;
+							}
+						};
+
+						return 1;
+						break;
+					}
+					case AMOTION_EVENT_ACTION_DOWN: {
+						// Detect double tap
+						int64_t eventTime = AMotionEvent_getEventTime(event);
+						if (eventTime - vulkanExample->lastTapTime <= vks::android::DOUBLE_TAP_TIMEOUT) {
+							float deadZone = (160.f / vks::android::screenDensity) * vks::android::DOUBLE_TAP_SLOP * vks::android::DOUBLE_TAP_SLOP;
+							float x = AMotionEvent_getX(event, 0) - vulkanExample->touchPos.x;
+							float y = AMotionEvent_getY(event, 0) - vulkanExample->touchPos.y;
+							if ((x * x + y * y) < deadZone) {
+								vulkanExample->keyPressed(TOUCH_DOUBLE_TAP);
+								vulkanExample->touchDown = false;
+							}
+						}
+						else {
+							vulkanExample->touchDown = true;
+						}
+						vulkanExample->touchPos.x = AMotionEvent_getX(event, 0);
+						vulkanExample->touchPos.y = AMotionEvent_getY(event, 0);
+						vulkanExample->mousePos.x = AMotionEvent_getX(event, 0);
+						vulkanExample->mousePos.y = AMotionEvent_getY(event, 0);
+						break;
+					}
+					case AMOTION_EVENT_ACTION_MOVE: {
+						bool handled = false;
+						if (vulkanExample->settings.overlay) {
+							ImGuiIO& io = ImGui::GetIO();
+							handled = io.WantCaptureMouse;
+						}
+						if (!handled) {
+							int32_t eventX = AMotionEvent_getX(event, 0);
+							int32_t eventY = AMotionEvent_getY(event, 0);
+
+							float deltaX = (float)(vulkanExample->touchPos.y - eventY) * vulkanExample->camera.rotationSpeed * 0.5f;
+							float deltaY = (float)(vulkanExample->touchPos.x - eventX) * vulkanExample->camera.rotationSpeed * 0.5f;
+
+							vulkanExample->camera.rotate(glm::vec3(deltaX, 0.0f, 0.0f));
+							vulkanExample->camera.rotate(glm::vec3(0.0f, -deltaY, 0.0f));
+
+							vulkanExample->viewChanged();
+
+							vulkanExample->touchPos.x = eventX;
+							vulkanExample->touchPos.y = eventY;
+						}
+						break;
+					}
+					default:
+						return 1;
+						break;
+				}
+			}
+
+			return 1;
+		}
+	}
+
+	if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_KEY)
+	{
+		int32_t keyCode = AKeyEvent_getKeyCode((const AInputEvent*)event);
+		int32_t action = AKeyEvent_getAction((const AInputEvent*)event);
+		int32_t button = 0;
+
+		if (action == AKEY_EVENT_ACTION_UP)
+			return 0;
+
+		switch (keyCode)
+		{
+		case AKEYCODE_BUTTON_A:
+			vulkanExample->keyPressed(GAMEPAD_BUTTON_A);
+			break;
+		case AKEYCODE_BUTTON_B:
+			vulkanExample->keyPressed(GAMEPAD_BUTTON_B);
+			break;
+		case AKEYCODE_BUTTON_X:
+			vulkanExample->keyPressed(GAMEPAD_BUTTON_X);
+			break;
+		case AKEYCODE_BUTTON_Y:
+			vulkanExample->keyPressed(GAMEPAD_BUTTON_Y);
+			break;
+		case AKEYCODE_BUTTON_L1:
+			vulkanExample->keyPressed(GAMEPAD_BUTTON_L1);
+			break;
+		case AKEYCODE_BUTTON_R1:
+			vulkanExample->keyPressed(GAMEPAD_BUTTON_R1);
+			break;
+		case AKEYCODE_BUTTON_START:
+			vulkanExample->paused = !vulkanExample->paused;
+			break;
+		};
+
+		LOGD("Button %d pressed", keyCode);
+	}
+
+	return 0;
+}
+
+void VulkanExampleBase::handleAppCommand(android_app * app, int32_t cmd)
+{
+	assert(app->userData != NULL);
+	VulkanExampleBase* vulkanExample = reinterpret_cast<VulkanExampleBase*>(app->userData);
+	switch (cmd)
+	{
+	case APP_CMD_SAVE_STATE:
+		LOGD("APP_CMD_SAVE_STATE");
+		/*
+		vulkanExample->app->savedState = malloc(sizeof(struct saved_state));
+		*((struct saved_state*)vulkanExample->app->savedState) = vulkanExample->state;
+		vulkanExample->app->savedStateSize = sizeof(struct saved_state);
+		*/
+		break;
+	case APP_CMD_INIT_WINDOW:
+		LOGD("APP_CMD_INIT_WINDOW");
+		if (androidApp->window != NULL)
+		{
+			if (vulkanExample->initVulkan()) {
+				vulkanExample->prepare();
+				assert(vulkanExample->prepared);
+			}
+			else {
+				LOGE("Could not initialize Vulkan, exiting!");
+				androidApp->destroyRequested = 1;
+			}
+		}
+		else
+		{
+			LOGE("No window assigned!");
+		}
+		break;
+	case APP_CMD_LOST_FOCUS:
+		LOGD("APP_CMD_LOST_FOCUS");
+		vulkanExample->focused = false;
+		break;
+	case APP_CMD_GAINED_FOCUS:
+		LOGD("APP_CMD_GAINED_FOCUS");
+		vulkanExample->focused = true;
+		break;
+	case APP_CMD_TERM_WINDOW:
+		// Window is hidden or closed, clean up resources
+		LOGD("APP_CMD_TERM_WINDOW");
+		if (vulkanExample->prepared) {
+			vulkanExample->swapChain.cleanup();
+		}
+		break;
+	}
+}
+#elif (defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_MACOS_MVK))
+#if defined(VK_EXAMPLE_XCODE_GENERATED)
+@interface AppDelegate : NSObject<NSApplicationDelegate>
+{
+}
+
+@end
+
+@implementation AppDelegate
+{
+}
+
+- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender
+{
+	return YES;
+}
+
+@end
+
+static CVReturn displayLinkOutputCallback(CVDisplayLinkRef displayLink, const CVTimeStamp *inNow,
+	const CVTimeStamp *inOutputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut,
+	void *displayLinkContext)
+{
+	@autoreleasepool
+	{
+		auto vulkanExample = static_cast<VulkanExampleBase*>(displayLinkContext);
+			vulkanExample->displayLinkOutputCb();
+	}
+	return kCVReturnSuccess;
+}
+
+@interface View : NSView<NSWindowDelegate>
+{
+@public
+	VulkanExampleBase *vulkanExample;
+}
+
+@end
+
+@implementation View
+{
+	CVDisplayLinkRef displayLink;
+}
+
+- (instancetype)initWithFrame:(NSRect)frameRect
+{
+	self = [super initWithFrame:(frameRect)];
+	if (self)
+	{
+		self.wantsLayer = YES;
+		self.layer = [CAMetalLayer layer];
+	}
+	return self;
+}
+
+- (void)viewDidMoveToWindow
+{
+	CVDisplayLinkCreateWithActiveCGDisplays(&displayLink);
+	CVDisplayLinkSetOutputCallback(displayLink, &displayLinkOutputCallback, vulkanExample);
+	CVDisplayLinkStart(displayLink);
+}
+
+- (BOOL)acceptsFirstResponder
+{
+	return YES;
+}
+
+- (void)keyDown:(NSEvent*)event
+{
+	switch (event.keyCode)
+	{
+		case kVK_ANSI_P:
+			vulkanExample->paused = !vulkanExample->paused;
+			break;
+		case kVK_Escape:
+			[NSApp terminate:nil];
+			break;
+		case kVK_ANSI_W:
+			vulkanExample->camera.keys.up = true;
+			break;
+		case kVK_ANSI_S:
+			vulkanExample->camera.keys.down = true;
+			break;
+		case kVK_ANSI_A:
+			vulkanExample->camera.keys.left = true;
+			break;
+		case kVK_ANSI_D:
+			vulkanExample->camera.keys.right = true;
+			break;
+		default:
+			break;
+	}
+}
+
+- (void)keyUp:(NSEvent*)event
+{
+	switch (event.keyCode)
+	{
+		case kVK_ANSI_W:
+			vulkanExample->camera.keys.up = false;
+			break;
+		case kVK_ANSI_S:
+			vulkanExample->camera.keys.down = false;
+			break;
+		case kVK_ANSI_A:
+			vulkanExample->camera.keys.left = false;
+		break;
+			case kVK_ANSI_D:
+		vulkanExample->camera.keys.right = false;
+			break;
+		default:
+			break;
+	}
+}
+
+- (NSPoint)getMouseLocalPoint:(NSEvent*)event
+{
+	NSPoint location = [event locationInWindow];
+	NSPoint point = [self convertPoint:location fromView:nil];
+	point.y = self.frame.size.height - point.y;
+	return point;
+}
+
+- (void)mouseDown:(NSEvent *)event
+{
+	auto point = [self getMouseLocalPoint:event];
+	vulkanExample->mousePos = glm::vec2(point.x, point.y);
+	vulkanExample->mouseButtons.left = true;
+}
+
+- (void)mouseUp:(NSEvent *)event
+{
+	auto point = [self getMouseLocalPoint:event];
+	vulkanExample->mousePos = glm::vec2(point.x, point.y);
+	vulkanExample->mouseButtons.left = false;
+}
+
+- (void)otherMouseDown:(NSEvent *)event
+{
+	vulkanExample->mouseButtons.right = true;
+}
+
+- (void)otherMouseUp:(NSEvent *)event
+{
+	vulkanExample->mouseButtons.right = false;
+}
+
+- (void)mouseDragged:(NSEvent *)event
+{
+	auto point = [self getMouseLocalPoint:event];
+	vulkanExample->mouseDragged(point.x, point.y);
+}
+
+- (void)mouseMoved:(NSEvent *)event
+{
+	auto point = [self getMouseLocalPoint:event];
+	vulkanExample->mouseDragged(point.x, point.y);
+}
+
+- (void)scrollWheel:(NSEvent *)event
+{
+	short wheelDelta = [event deltaY];
+	vulkanExample->camera.translate(glm::vec3(0.0f, 0.0f,
+		-(float)wheelDelta * 0.05f * vulkanExample->camera.movementSpeed));
+}
+
+- (NSSize)windowWillResize:(NSWindow *)sender toSize:(NSSize)frameSize
+{
+	CVDisplayLinkStop(displayLink);
+	vulkanExample->windowWillResize(frameSize.width, frameSize.height);
+	return frameSize;
+}
+
+- (void)windowDidResize:(NSNotification *)notification
+{
+	vulkanExample->windowDidResize();
+	CVDisplayLinkStart(displayLink);
+}
+
+- (BOOL)windowShouldClose:(NSWindow *)sender
+{
+	return TRUE;
+}
+
+- (void)windowWillClose:(NSNotification *)notification
+{
+	CVDisplayLinkStop(displayLink);
+}
+
+@end
+#endif
+
+void* VulkanExampleBase::setupWindow(void* view)
+{
+#if defined(VK_EXAMPLE_XCODE_GENERATED)
+	NSApp = [NSApplication sharedApplication];
+	[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
+	[NSApp setDelegate:[AppDelegate new]];
+
+	const auto kContentRect = NSMakeRect(0.0f, 0.0f, width, height);
+	const auto kWindowStyle = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskResizable;
+
+	auto window = [[NSWindow alloc] initWithContentRect:kContentRect
+											  styleMask:kWindowStyle
+												backing:NSBackingStoreBuffered
+												  defer:NO];
+	[window setTitle:@(title.c_str())];
+	[window setAcceptsMouseMovedEvents:YES];
+	[window center];
+	[window makeKeyAndOrderFront:nil];
+
+	auto nsView = [[View alloc] initWithFrame:kContentRect];
+	nsView->vulkanExample = this;
+	[window setDelegate:nsView];
+	[window setContentView:nsView];
+	this->view = (__bridge void*)nsView;
+#else
+	this->view = view;
+#endif
+	return view;
+}
+
+void VulkanExampleBase::displayLinkOutputCb()
+{
+	if (prepared)
+		nextFrame();
+}
+
+void VulkanExampleBase::mouseDragged(float x, float y)
+{
+	handleMouseMove(static_cast<uint32_t>(x), static_cast<uint32_t>(y));
+}
+
+void VulkanExampleBase::windowWillResize(float x, float y)
+{
+	resizing = true;
+	if (prepared)
+	{
+		destWidth = x;
+		destHeight = y;
+		windowResize();
+	}
+}
+
+void VulkanExampleBase::windowDidResize()
+{
+	resizing = false;
+}
+#elif defined(_DIRECT2DISPLAY)
+#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT)
+IDirectFBSurface *VulkanExampleBase::setupWindow()
+{
+	DFBResult ret;
+	int posx = 0, posy = 0;
+
+	ret = DirectFBInit(NULL, NULL);
+	if (ret)
+	{
+		std::cout << "Could not initialize DirectFB!\n";
+		fflush(stdout);
+		exit(1);
+	}
+
+	ret = DirectFBCreate(&dfb);
+	if (ret)
+	{
+		std::cout << "Could not create main interface of DirectFB!\n";
+		fflush(stdout);
+		exit(1);
+	}
+
+	ret = dfb->GetDisplayLayer(dfb, DLID_PRIMARY, &layer);
+	if (ret)
+	{
+		std::cout << "Could not get DirectFB display layer interface!\n";
+		fflush(stdout);
+		exit(1);
+	}
+
+	DFBDisplayLayerConfig layer_config;
+	ret = layer->GetConfiguration(layer, &layer_config);
+	if (ret)
+	{
+		std::cout << "Could not get DirectFB display layer configuration!\n";
+		fflush(stdout);
+		exit(1);
+	}
+
+	if (settings.fullscreen)
+	{
+		width = layer_config.width;
+		height = layer_config.height;
+	}
+	else
+	{
+		if (layer_config.width > width)
+			posx = (layer_config.width - width) / 2;
+		if (layer_config.height > height)
+			posy = (layer_config.height - height) / 2;
+	}
+
+	DFBWindowDescription desc;
+	desc.flags = (DFBWindowDescriptionFlags)(DWDESC_WIDTH | DWDESC_HEIGHT | DWDESC_POSX | DWDESC_POSY);
+	desc.width = width;
+	desc.height = height;
+	desc.posx = posx;
+	desc.posy = posy;
+	ret = layer->CreateWindow(layer, &desc, &window);
+	if (ret)
+	{
+		std::cout << "Could not create DirectFB window interface!\n";
+		fflush(stdout);
+		exit(1);
+	}
+
+	ret = window->GetSurface(window, &surface);
+	if (ret)
+	{
+		std::cout << "Could not get DirectFB surface interface!\n";
+		fflush(stdout);
+		exit(1);
+	}
+
+	ret = window->CreateEventBuffer(window, &event_buffer);
+	if (ret)
+	{
+		std::cout << "Could not create DirectFB event buffer interface!\n";
+		fflush(stdout);
+		exit(1);
+	}
+
+	ret = window->SetOpacity(window, 0xFF);
+	if (ret)
+	{
+		std::cout << "Could not set DirectFB window opacity!\n";
+		fflush(stdout);
+		exit(1);
+	}
+
+	return surface;
+}
+
+void VulkanExampleBase::handleEvent(const DFBWindowEvent *event)
+{
+	switch (event->type)
+	{
+	case DWET_CLOSE:
+		quit = true;
+		break;
+	case DWET_MOTION:
+		handleMouseMove(event->x, event->y);
+		break;
+	case DWET_BUTTONDOWN:
+		switch (event->button)
+		{
+		case DIBI_LEFT:
+			mouseButtons.left = true;
+			break;
+		case DIBI_MIDDLE:
+			mouseButtons.middle = true;
+			break;
+		case DIBI_RIGHT:
+			mouseButtons.right = true;
+			break;
+		default:
+			break;
+		}
+		break;
+	case DWET_BUTTONUP:
+		switch (event->button)
+		{
+		case DIBI_LEFT:
+			mouseButtons.left = false;
+			break;
+		case DIBI_MIDDLE:
+			mouseButtons.middle = false;
+			break;
+		case DIBI_RIGHT:
+			mouseButtons.right = false;
+			break;
+		default:
+			break;
+		}
+		break;
+	case DWET_KEYDOWN:
+		switch (event->key_symbol)
+		{
+			case KEY_W:
+				camera.keys.up = true;
+				break;
+			case KEY_S:
+				camera.keys.down = true;
+				break;
+			case KEY_A:
+				camera.keys.left = true;
+				break;
+			case KEY_D:
+				camera.keys.right = true;
+				break;
+			case KEY_P:
+				paused = !paused;
+				break;
+			case KEY_F1:
+				if (settings.overlay) {
+					settings.overlay = !settings.overlay;
+				}
+				break;
+			default:
+				break;
+		}
+		break;
+	case DWET_KEYUP:
+		switch (event->key_symbol)
+		{
+			case KEY_W:
+				camera.keys.up = false;
+				break;
+			case KEY_S:
+				camera.keys.down = false;
+				break;
+			case KEY_A:
+				camera.keys.left = false;
+				break;
+			case KEY_D:
+				camera.keys.right = false;
+				break;
+			case KEY_ESCAPE:
+				quit = true;
+				break;
+			default:
+				break;
+		}
+		keyPressed(event->key_symbol);
+		break;
+	case DWET_SIZE:
+		destWidth = event->w;
+		destHeight = event->h;
+		windowResize();
+		break;
+	default:
+		break;
+	}
+}
+#elif defined(VK_USE_PLATFORM_WAYLAND_KHR)
+/*static*/void VulkanExampleBase::registryGlobalCb(void *data,
+		wl_registry *registry, uint32_t name, const char *interface,
+		uint32_t version)
+{
+	VulkanExampleBase *self = reinterpret_cast<VulkanExampleBase *>(data);
+	self->registryGlobal(registry, name, interface, version);
+}
+
+/*static*/void VulkanExampleBase::seatCapabilitiesCb(void *data, wl_seat *seat,
+		uint32_t caps)
+{
+	VulkanExampleBase *self = reinterpret_cast<VulkanExampleBase *>(data);
+	self->seatCapabilities(seat, caps);
+}
+
+/*static*/void VulkanExampleBase::pointerEnterCb(void *data,
+		wl_pointer *pointer, uint32_t serial, wl_surface *surface,
+		wl_fixed_t sx, wl_fixed_t sy)
+{
+}
+
+/*static*/void VulkanExampleBase::pointerLeaveCb(void *data,
+		wl_pointer *pointer, uint32_t serial, wl_surface *surface)
+{
+}
+
+/*static*/void VulkanExampleBase::pointerMotionCb(void *data,
+		wl_pointer *pointer, uint32_t time, wl_fixed_t sx, wl_fixed_t sy)
+{
+	VulkanExampleBase *self = reinterpret_cast<VulkanExampleBase *>(data);
+	self->pointerMotion(pointer, time, sx, sy);
+}
+void VulkanExampleBase::pointerMotion(wl_pointer *pointer, uint32_t time, wl_fixed_t sx, wl_fixed_t sy)
+{
+	handleMouseMove(wl_fixed_to_int(sx), wl_fixed_to_int(sy));
+}
+
+/*static*/void VulkanExampleBase::pointerButtonCb(void *data,
+		wl_pointer *pointer, uint32_t serial, uint32_t time, uint32_t button,
+		uint32_t state)
+{
+	VulkanExampleBase *self = reinterpret_cast<VulkanExampleBase *>(data);
+	self->pointerButton(pointer, serial, time, button, state);
+}
+
+void VulkanExampleBase::pointerButton(struct wl_pointer *pointer,
+		uint32_t serial, uint32_t time, uint32_t button, uint32_t state)
+{
+	switch (button)
+	{
+	case BTN_LEFT:
+		mouseButtons.left = !!state;
+		break;
+	case BTN_MIDDLE:
+		mouseButtons.middle = !!state;
+		break;
+	case BTN_RIGHT:
+		mouseButtons.right = !!state;
+		break;
+	default:
+		break;
+	}
+}
+
+/*static*/void VulkanExampleBase::pointerAxisCb(void *data,
+		wl_pointer *pointer, uint32_t time, uint32_t axis,
+		wl_fixed_t value)
+{
+	VulkanExampleBase *self = reinterpret_cast<VulkanExampleBase *>(data);
+	self->pointerAxis(pointer, time, axis, value);
+}
+
+void VulkanExampleBase::pointerAxis(wl_pointer *pointer, uint32_t time,
+		uint32_t axis, wl_fixed_t value)
+{
+	double d = wl_fixed_to_double(value);
+	switch (axis)
+	{
+	case REL_X:
+		camera.translate(glm::vec3(0.0f, 0.0f, d * 0.005f));
+		viewUpdated = true;
+		break;
+	default:
+		break;
+	}
+}
+
+/*static*/void VulkanExampleBase::keyboardKeymapCb(void *data,
+		struct wl_keyboard *keyboard, uint32_t format, int fd, uint32_t size)
+{
+}
+
+/*static*/void VulkanExampleBase::keyboardEnterCb(void *data,
+		struct wl_keyboard *keyboard, uint32_t serial,
+		struct wl_surface *surface, struct wl_array *keys)
+{
+}
+
+/*static*/void VulkanExampleBase::keyboardLeaveCb(void *data,
+		struct wl_keyboard *keyboard, uint32_t serial,
+		struct wl_surface *surface)
+{
+}
+
+/*static*/void VulkanExampleBase::keyboardKeyCb(void *data,
+		struct wl_keyboard *keyboard, uint32_t serial, uint32_t time,
+		uint32_t key, uint32_t state)
+{
+	VulkanExampleBase *self = reinterpret_cast<VulkanExampleBase *>(data);
+	self->keyboardKey(keyboard, serial, time, key, state);
+}
+
+void VulkanExampleBase::keyboardKey(struct wl_keyboard *keyboard,
+		uint32_t serial, uint32_t time, uint32_t key, uint32_t state)
+{
+	switch (key)
+	{
+	case KEY_W:
+		camera.keys.up = !!state;
+		break;
+	case KEY_S:
+		camera.keys.down = !!state;
+		break;
+	case KEY_A:
+		camera.keys.left = !!state;
+		break;
+	case KEY_D:
+		camera.keys.right = !!state;
+		break;
+	case KEY_P:
+		if (state)
+			paused = !paused;
+		break;
+	case KEY_F1:
+		if (state && settings.overlay)
+			settings.overlay = !settings.overlay;
+		break;
+	case KEY_ESC:
+		quit = true;
+		break;
+	}
+
+	if (state)
+		keyPressed(key);
+}
+
+/*static*/void VulkanExampleBase::keyboardModifiersCb(void *data,
+		struct wl_keyboard *keyboard, uint32_t serial, uint32_t mods_depressed,
+		uint32_t mods_latched, uint32_t mods_locked, uint32_t group)
+{
+}
+
+void VulkanExampleBase::seatCapabilities(wl_seat *seat, uint32_t caps)
+{
+	if ((caps & WL_SEAT_CAPABILITY_POINTER) && !pointer)
+	{
+		pointer = wl_seat_get_pointer(seat);
+		static const struct wl_pointer_listener pointer_listener =
+		{ pointerEnterCb, pointerLeaveCb, pointerMotionCb, pointerButtonCb,
+				pointerAxisCb, };
+		wl_pointer_add_listener(pointer, &pointer_listener, this);
+	}
+	else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && pointer)
+	{
+		wl_pointer_destroy(pointer);
+		pointer = nullptr;
+	}
+
+	if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !keyboard)
+	{
+		keyboard = wl_seat_get_keyboard(seat);
+		static const struct wl_keyboard_listener keyboard_listener =
+		{ keyboardKeymapCb, keyboardEnterCb, keyboardLeaveCb, keyboardKeyCb,
+				keyboardModifiersCb, };
+		wl_keyboard_add_listener(keyboard, &keyboard_listener, this);
+	}
+	else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && keyboard)
+	{
+		wl_keyboard_destroy(keyboard);
+		keyboard = nullptr;
+	}
+}
+
+static void xdg_wm_base_ping(void *data, struct xdg_wm_base *shell, uint32_t serial)
+{
+	xdg_wm_base_pong(shell, serial);
+}
+
+static const struct xdg_wm_base_listener xdg_wm_base_listener = {
+	xdg_wm_base_ping,
+};
+
+void VulkanExampleBase::registryGlobal(wl_registry *registry, uint32_t name,
+		const char *interface, uint32_t version)
+{
+	if (strcmp(interface, "wl_compositor") == 0)
+	{
+		compositor = (wl_compositor *) wl_registry_bind(registry, name,
+				&wl_compositor_interface, 3);
+	}
+	else if (strcmp(interface, "xdg_wm_base") == 0)
+	{
+		shell = (xdg_wm_base *) wl_registry_bind(registry, name,
+				&xdg_wm_base_interface, 1);
+		xdg_wm_base_add_listener(shell, &xdg_wm_base_listener, nullptr);
+	}
+	else if (strcmp(interface, "wl_seat") == 0)
+	{
+		seat = (wl_seat *) wl_registry_bind(registry, name, &wl_seat_interface,
+				1);
+
+		static const struct wl_seat_listener seat_listener =
+		{ seatCapabilitiesCb, };
+		wl_seat_add_listener(seat, &seat_listener, this);
+	}
+}
+
+/*static*/void VulkanExampleBase::registryGlobalRemoveCb(void *data,
+		struct wl_registry *registry, uint32_t name)
+{
+}
+
+void VulkanExampleBase::initWaylandConnection()
+{
+	display = wl_display_connect(NULL);
+	if (!display)
+	{
+		std::cout << "Could not connect to Wayland display!\n";
+		fflush(stdout);
+		exit(1);
+	}
+
+	registry = wl_display_get_registry(display);
+	if (!registry)
+	{
+		std::cout << "Could not get Wayland registry!\n";
+		fflush(stdout);
+		exit(1);
+	}
+
+	static const struct wl_registry_listener registry_listener =
+	{ registryGlobalCb, registryGlobalRemoveCb };
+	wl_registry_add_listener(registry, &registry_listener, this);
+	wl_display_dispatch(display);
+	wl_display_roundtrip(display);
+	if (!compositor || !shell)
+	{
+		std::cout << "Could not bind Wayland protocols!\n";
+		fflush(stdout);
+		exit(1);
+	}
+	if (!seat)
+	{
+		std::cout << "WARNING: Input handling not available!\n";
+		fflush(stdout);
+	}
+}
+
+void VulkanExampleBase::setSize(int width, int height)
+{
+	if (width <= 0 || height <= 0)
+		return;
+
+	destWidth = width;
+	destHeight = height;
+
+	windowResize();
+}
+
+static void
+xdg_surface_handle_configure(void *data, struct xdg_surface *surface,
+			     uint32_t serial)
+{
+	VulkanExampleBase *base = (VulkanExampleBase *) data;
+
+	xdg_surface_ack_configure(surface, serial);
+	base->configured = true;
+}
+
+static const struct xdg_surface_listener xdg_surface_listener = {
+	xdg_surface_handle_configure,
+};
+
+
+static void
+xdg_toplevel_handle_configure(void *data, struct xdg_toplevel *toplevel,
+			      int32_t width, int32_t height,
+			      struct wl_array *states)
+{
+	VulkanExampleBase *base = (VulkanExampleBase *) data;
+
+	base->setSize(width, height);
+}
+
+static void
+xdg_toplevel_handle_close(void *data, struct xdg_toplevel *xdg_toplevel)
+{
+	VulkanExampleBase *base = (VulkanExampleBase *) data;
+
+	base->quit = true;
+}
+
+
+static const struct xdg_toplevel_listener xdg_toplevel_listener = {
+	xdg_toplevel_handle_configure,
+	xdg_toplevel_handle_close,
+};
+
+
+struct xdg_surface *VulkanExampleBase::setupWindow()
+{
+	surface = wl_compositor_create_surface(compositor);
+	xdg_surface = xdg_wm_base_get_xdg_surface(shell, surface);
+
+	xdg_surface_add_listener(xdg_surface, &xdg_surface_listener, this);
+	xdg_toplevel = xdg_surface_get_toplevel(xdg_surface);
+	xdg_toplevel_add_listener(xdg_toplevel, &xdg_toplevel_listener, this);
+
+	std::string windowTitle = getWindowTitle();
+	xdg_toplevel_set_title(xdg_toplevel, windowTitle.c_str());
+	wl_surface_commit(surface);
+	return xdg_surface;
+}
+
+#elif defined(VK_USE_PLATFORM_XCB_KHR)
+
+static inline xcb_intern_atom_reply_t* intern_atom_helper(xcb_connection_t *conn, bool only_if_exists, const char *str)
+{
+	xcb_intern_atom_cookie_t cookie = xcb_intern_atom(conn, only_if_exists, strlen(str), str);
+	return xcb_intern_atom_reply(conn, cookie, NULL);
+}
+
+// Set up a window using XCB and request event types
+xcb_window_t VulkanExampleBase::setupWindow()
+{
+	uint32_t value_mask, value_list[32];
+
+	window = xcb_generate_id(connection);
+
+	value_mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
+	value_list[0] = screen->black_pixel;
+	value_list[1] =
+		XCB_EVENT_MASK_KEY_RELEASE |
+		XCB_EVENT_MASK_KEY_PRESS |
+		XCB_EVENT_MASK_EXPOSURE |
+		XCB_EVENT_MASK_STRUCTURE_NOTIFY |
+		XCB_EVENT_MASK_POINTER_MOTION |
+		XCB_EVENT_MASK_BUTTON_PRESS |
+		XCB_EVENT_MASK_BUTTON_RELEASE;
+
+	if (settings.fullscreen)
+	{
+		width = destWidth = screen->width_in_pixels;
+		height = destHeight = screen->height_in_pixels;
+	}
+
+	xcb_create_window(connection,
+		XCB_COPY_FROM_PARENT,
+		window, screen->root,
+		0, 0, width, height, 0,
+		XCB_WINDOW_CLASS_INPUT_OUTPUT,
+		screen->root_visual,
+		value_mask, value_list);
+
+	/* Magic code that will send notification when window is destroyed */
+	xcb_intern_atom_reply_t* reply = intern_atom_helper(connection, true, "WM_PROTOCOLS");
+	atom_wm_delete_window = intern_atom_helper(connection, false, "WM_DELETE_WINDOW");
+
+	xcb_change_property(connection, XCB_PROP_MODE_REPLACE,
+		window, (*reply).atom, 4, 32, 1,
+		&(*atom_wm_delete_window).atom);
+
+	std::string windowTitle = getWindowTitle();
+	xcb_change_property(connection, XCB_PROP_MODE_REPLACE,
+		window, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8,
+		title.size(), windowTitle.c_str());
+
+	free(reply);
+
+	/**
+	 * Set the WM_CLASS property to display
+	 * title in dash tooltip and application menu
+	 * on GNOME and other desktop environments
+	 */
+	std::string wm_class;
+	wm_class = wm_class.insert(0, name);
+	wm_class = wm_class.insert(name.size(), 1, '\0');
+	wm_class = wm_class.insert(name.size() + 1, title);
+	wm_class = wm_class.insert(wm_class.size(), 1, '\0');
+	xcb_change_property(connection, XCB_PROP_MODE_REPLACE, window, XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, 8, wm_class.size() + 2, wm_class.c_str());
+
+	if (settings.fullscreen)
+	{
+		xcb_intern_atom_reply_t *atom_wm_state = intern_atom_helper(connection, false, "_NET_WM_STATE");
+		xcb_intern_atom_reply_t *atom_wm_fullscreen = intern_atom_helper(connection, false, "_NET_WM_STATE_FULLSCREEN");
+		xcb_change_property(connection,
+				XCB_PROP_MODE_REPLACE,
+				window, atom_wm_state->atom,
+				XCB_ATOM_ATOM, 32, 1,
+				&(atom_wm_fullscreen->atom));
+		free(atom_wm_fullscreen);
+		free(atom_wm_state);
+	}
+
+	xcb_map_window(connection, window);
+
+	return(window);
+}
+
+// Initialize XCB connection
+void VulkanExampleBase::initxcbConnection()
+{
+	const xcb_setup_t *setup;
+	xcb_screen_iterator_t iter;
+	int scr;
+
+	// xcb_connect always returns a non-NULL pointer to a xcb_connection_t,
+	// even on failure. Callers need to use xcb_connection_has_error() to
+	// check for failure. When finished, use xcb_disconnect() to close the
+	// connection and free the structure.
+	connection = xcb_connect(NULL, &scr);
+	assert( connection );
+	if( xcb_connection_has_error(connection) ) {
+		printf("Could not find a compatible Vulkan ICD!\n");
+		fflush(stdout);
+		exit(1);
+	}
+
+	setup = xcb_get_setup(connection);
+	iter = xcb_setup_roots_iterator(setup);
+	while (scr-- > 0)
+		xcb_screen_next(&iter);
+	screen = iter.data;
+}
+
+void VulkanExampleBase::handleEvent(const xcb_generic_event_t *event)
+{
+	switch (event->response_type & 0x7f)
+	{
+	case XCB_CLIENT_MESSAGE:
+		if ((*(xcb_client_message_event_t*)event).data.data32[0] ==
+			(*atom_wm_delete_window).atom) {
+			quit = true;
+		}
+		break;
+	case XCB_MOTION_NOTIFY:
+	{
+		xcb_motion_notify_event_t *motion = (xcb_motion_notify_event_t *)event;
+		handleMouseMove((int32_t)motion->event_x, (int32_t)motion->event_y);
+		break;
+	}
+	break;
+	case XCB_BUTTON_PRESS:
+	{
+		xcb_button_press_event_t *press = (xcb_button_press_event_t *)event;
+		if (press->detail == XCB_BUTTON_INDEX_1)
+			mouseButtons.left = true;
+		if (press->detail == XCB_BUTTON_INDEX_2)
+			mouseButtons.middle = true;
+		if (press->detail == XCB_BUTTON_INDEX_3)
+			mouseButtons.right = true;
+	}
+	break;
+	case XCB_BUTTON_RELEASE:
+	{
+		xcb_button_press_event_t *press = (xcb_button_press_event_t *)event;
+		if (press->detail == XCB_BUTTON_INDEX_1)
+			mouseButtons.left = false;
+		if (press->detail == XCB_BUTTON_INDEX_2)
+			mouseButtons.middle = false;
+		if (press->detail == XCB_BUTTON_INDEX_3)
+			mouseButtons.right = false;
+	}
+	break;
+	case XCB_KEY_PRESS:
+	{
+		const xcb_key_release_event_t *keyEvent = (const xcb_key_release_event_t *)event;
+		switch (keyEvent->detail)
+		{
+			case KEY_W:
+				camera.keys.up = true;
+				break;
+			case KEY_S:
+				camera.keys.down = true;
+				break;
+			case KEY_A:
+				camera.keys.left = true;
+				break;
+			case KEY_D:
+				camera.keys.right = true;
+				break;
+			case KEY_P:
+				paused = !paused;
+				break;
+			case KEY_F1:
+				if (settings.overlay) {
+					settings.overlay = !settings.overlay;
+				}
+				break;
+		}
+	}
+	break;
+	case XCB_KEY_RELEASE:
+	{
+		const xcb_key_release_event_t *keyEvent = (const xcb_key_release_event_t *)event;
+		switch (keyEvent->detail)
+		{
+			case KEY_W:
+				camera.keys.up = false;
+				break;
+			case KEY_S:
+				camera.keys.down = false;
+				break;
+			case KEY_A:
+				camera.keys.left = false;
+				break;
+			case KEY_D:
+				camera.keys.right = false;
+				break;
+			case KEY_ESCAPE:
+				quit = true;
+				break;
+		}
+		keyPressed(keyEvent->detail);
+	}
+	break;
+	case XCB_DESTROY_NOTIFY:
+		quit = true;
+		break;
+	case XCB_CONFIGURE_NOTIFY:
+	{
+		const xcb_configure_notify_event_t *cfgEvent = (const xcb_configure_notify_event_t *)event;
+		if ((prepared) && ((cfgEvent->width != width) || (cfgEvent->height != height)))
+		{
+				destWidth = cfgEvent->width;
+				destHeight = cfgEvent->height;
+				if ((destWidth > 0) && (destHeight > 0))
+				{
+					windowResize();
+				}
+		}
+	}
+	break;
+	default:
+		break;
+	}
+}
+#else
+void VulkanExampleBase::setupWindow()
+{
+}
+#endif
+
+void VulkanExampleBase::viewChanged() {}
+
+void VulkanExampleBase::keyPressed(uint32_t) {}
+
+void VulkanExampleBase::mouseMoved(double x, double y, bool & handled) {}
+
+void VulkanExampleBase::buildCommandBuffers() {}
+
+void VulkanExampleBase::createSynchronizationPrimitives()
+{
+	// Wait fences to sync command buffer access
+	VkFenceCreateInfo fenceCreateInfo = vks::initializers::fenceCreateInfo(VK_FENCE_CREATE_SIGNALED_BIT);
+	waitFences.resize(drawCmdBuffers.size());
+	for (auto& fence : waitFences) {
+		VK_CHECK_RESULT(vkCreateFence(device, &fenceCreateInfo, nullptr, &fence));
+	}
+}
+
+void VulkanExampleBase::createCommandPool()
+{
+	VkCommandPoolCreateInfo cmdPoolInfo = {};
+	cmdPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
+	cmdPoolInfo.queueFamilyIndex = swapChain.queueNodeIndex;
+	cmdPoolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
+	VK_CHECK_RESULT(vkCreateCommandPool(device, &cmdPoolInfo, nullptr, &cmdPool));
+}
+
+void VulkanExampleBase::setupDepthStencil()
+{
+	VkImageCreateInfo imageCI{};
+	imageCI.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
+	imageCI.imageType = VK_IMAGE_TYPE_2D;
+	imageCI.format = depthFormat;
+	imageCI.extent = { width, height, 1 };
+	imageCI.mipLevels = 1;
+	imageCI.arrayLayers = 1;
+	imageCI.samples = VK_SAMPLE_COUNT_1_BIT;
+	imageCI.tiling = VK_IMAGE_TILING_OPTIMAL;
+	imageCI.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
+
+	VK_CHECK_RESULT(vkCreateImage(device, &imageCI, nullptr, &depthStencil.image));
+	VkMemoryRequirements memReqs{};
+	vkGetImageMemoryRequirements(device, depthStencil.image, &memReqs);
+
+	VkMemoryAllocateInfo memAllloc{};
+	memAllloc.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+	memAllloc.allocationSize = memReqs.size;
+	memAllloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+	VK_CHECK_RESULT(vkAllocateMemory(device, &memAllloc, nullptr, &depthStencil.mem));
+	VK_CHECK_RESULT(vkBindImageMemory(device, depthStencil.image, depthStencil.mem, 0));
+
+	VkImageViewCreateInfo imageViewCI{};
+	imageViewCI.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+	imageViewCI.viewType = VK_IMAGE_VIEW_TYPE_2D;
+	imageViewCI.image = depthStencil.image;
+	imageViewCI.format = depthFormat;
+	imageViewCI.subresourceRange.baseMipLevel = 0;
+	imageViewCI.subresourceRange.levelCount = 1;
+	imageViewCI.subresourceRange.baseArrayLayer = 0;
+	imageViewCI.subresourceRange.layerCount = 1;
+	imageViewCI.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
+	// Stencil aspect should only be set on depth + stencil formats (VK_FORMAT_D16_UNORM_S8_UINT..VK_FORMAT_D32_SFLOAT_S8_UINT
+	if (depthFormat >= VK_FORMAT_D16_UNORM_S8_UINT) {
+		imageViewCI.subresourceRange.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
+	}
+	VK_CHECK_RESULT(vkCreateImageView(device, &imageViewCI, nullptr, &depthStencil.view));
+}
+
+void VulkanExampleBase::setupFrameBuffer()
+{
+	VkImageView attachments[2];
+
+	// Depth/Stencil attachment is the same for all frame buffers
+	attachments[1] = depthStencil.view;
+
+	VkFramebufferCreateInfo frameBufferCreateInfo = {};
+	frameBufferCreateInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
+	frameBufferCreateInfo.pNext = NULL;
+	frameBufferCreateInfo.renderPass = renderPass;
+	frameBufferCreateInfo.attachmentCount = 2;
+	frameBufferCreateInfo.pAttachments = attachments;
+	frameBufferCreateInfo.width = width;
+	frameBufferCreateInfo.height = height;
+	frameBufferCreateInfo.layers = 1;
+
+	// Create frame buffers for every swap chain image
+	frameBuffers.resize(swapChain.imageCount);
+	for (uint32_t i = 0; i < frameBuffers.size(); i++)
+	{
+		attachments[0] = swapChain.buffers[i].view;
+		VK_CHECK_RESULT(vkCreateFramebuffer(device, &frameBufferCreateInfo, nullptr, &frameBuffers[i]));
+	}
+}
+
+void VulkanExampleBase::setupRenderPass()
+{
+	std::array<VkAttachmentDescription, 2> attachments = {};
+	// Color attachment
+	attachments[0].format = swapChain.colorFormat;
+	attachments[0].samples = VK_SAMPLE_COUNT_1_BIT;
+	attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+	attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+	attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+	attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+	attachments[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+	attachments[0].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
+	// Depth attachment
+	attachments[1].format = depthFormat;
+	attachments[1].samples = VK_SAMPLE_COUNT_1_BIT;
+	attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+	attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+	attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+	attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+	attachments[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+	attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+
+	VkAttachmentReference colorReference = {};
+	colorReference.attachment = 0;
+	colorReference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+
+	VkAttachmentReference depthReference = {};
+	depthReference.attachment = 1;
+	depthReference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+
+	VkSubpassDescription subpassDescription = {};
+	subpassDescription.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+	subpassDescription.colorAttachmentCount = 1;
+	subpassDescription.pColorAttachments = &colorReference;
+	subpassDescription.pDepthStencilAttachment = &depthReference;
+	subpassDescription.inputAttachmentCount = 0;
+	subpassDescription.pInputAttachments = nullptr;
+	subpassDescription.preserveAttachmentCount = 0;
+	subpassDescription.pPreserveAttachments = nullptr;
+	subpassDescription.pResolveAttachments = nullptr;
+
+	// Subpass dependencies for layout transitions
+	std::array<VkSubpassDependency, 2> dependencies;
+
+	dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
+	dependencies[0].dstSubpass = 0;
+	dependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+	dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+	dependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
+	dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+	dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+	dependencies[1].srcSubpass = 0;
+	dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
+	dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+	dependencies[1].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+	dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+	dependencies[1].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
+	dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+	VkRenderPassCreateInfo renderPassInfo = {};
+	renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
+	renderPassInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
+	renderPassInfo.pAttachments = attachments.data();
+	renderPassInfo.subpassCount = 1;
+	renderPassInfo.pSubpasses = &subpassDescription;
+	renderPassInfo.dependencyCount = static_cast<uint32_t>(dependencies.size());
+	renderPassInfo.pDependencies = dependencies.data();
+
+	VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass));
+}
+
+void VulkanExampleBase::getEnabledFeatures() {}
+
+void VulkanExampleBase::windowResize()
+{
+	if (!prepared)
+	{
+		return;
+	}
+	prepared = false;
+	resized = true;
+
+	// Ensure all operations on the device have been finished before destroying resources
+	vkDeviceWaitIdle(device);
+
+	// Recreate swap chain
+	width = destWidth;
+	height = destHeight;
+	setupSwapChain();
+
+	// Recreate the frame buffers
+	vkDestroyImageView(device, depthStencil.view, nullptr);
+	vkDestroyImage(device, depthStencil.image, nullptr);
+	vkFreeMemory(device, depthStencil.mem, nullptr);
+	setupDepthStencil();
+	for (uint32_t i = 0; i < frameBuffers.size(); i++) {
+		vkDestroyFramebuffer(device, frameBuffers[i], nullptr);
+	}
+	setupFrameBuffer();
+
+	if ((width > 0.0f) && (height > 0.0f)) {
+		if (settings.overlay) {
+			UIOverlay.resize(width, height);
+		}
+	}
+
+	// Command buffers need to be recreated as they may store
+	// references to the recreated frame buffer
+	destroyCommandBuffers();
+	createCommandBuffers();
+	buildCommandBuffers();
+
+	vkDeviceWaitIdle(device);
+
+	if ((width > 0.0f) && (height > 0.0f)) {
+		camera.updateAspectRatio((float)width / (float)height);
+	}
+
+	// Notify derived class
+	windowResized();
+	viewChanged();
+
+	prepared = true;
+}
+
+void VulkanExampleBase::handleMouseMove(int32_t x, int32_t y)
+{
+	int32_t dx = (int32_t)mousePos.x - x;
+	int32_t dy = (int32_t)mousePos.y - y;
+
+	bool handled = false;
+
+	if (settings.overlay) {
+		ImGuiIO& io = ImGui::GetIO();
+		handled = io.WantCaptureMouse;
+	}
+	mouseMoved((float)x, (float)y, handled);
+
+	if (handled) {
+		mousePos = glm::vec2((float)x, (float)y);
+		return;
+	}
+
+	if (mouseButtons.left) {
+		camera.rotate(glm::vec3(dy * camera.rotationSpeed, -dx * camera.rotationSpeed, 0.0f));
+		viewUpdated = true;
+	}
+	if (mouseButtons.right) {
+		camera.translate(glm::vec3(-0.0f, 0.0f, dy * .005f));
+		viewUpdated = true;
+	}
+	if (mouseButtons.middle) {
+		camera.translate(glm::vec3(-dx * 0.01f, -dy * 0.01f, 0.0f));
+		viewUpdated = true;
+	}
+	mousePos = glm::vec2((float)x, (float)y);
+}
+
+void VulkanExampleBase::windowResized() {}
+
+void VulkanExampleBase::initSwapchain()
+{
+#if defined(_WIN32)
+	swapChain.initSurface(windowInstance, window);
+#elif defined(VK_USE_PLATFORM_ANDROID_KHR)
+	swapChain.initSurface(androidApp->window);
+#elif (defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_MACOS_MVK))
+	swapChain.initSurface(view);
+#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT)
+	swapChain.initSurface(dfb, surface);
+#elif defined(VK_USE_PLATFORM_WAYLAND_KHR)
+	swapChain.initSurface(display, surface);
+#elif defined(VK_USE_PLATFORM_XCB_KHR)
+	swapChain.initSurface(connection, window);
+#elif (defined(_DIRECT2DISPLAY) || defined(VK_USE_PLATFORM_HEADLESS_EXT))
+	swapChain.initSurface(width, height);
+#endif
+}
+
+void VulkanExampleBase::setupSwapChain()
+{
+	swapChain.create(&width, &height, settings.vsync);
+}
+
+void VulkanExampleBase::OnUpdateUIOverlay(vks::UIOverlay *overlay) {}
+
+// Command line argument parser class
+
+CommandLineParser::CommandLineParser()
+{
+	add("help", { "--help" }, 0, "Show help");
+	add("validation", {"-v", "--validation"}, 0, "Enable validation layers");
+	add("vsync", {"-vs", "--vsync"}, 0, "Enable V-Sync");
+	add("fullscreen", { "-f", "--fullscreen" }, 0, "Start in fullscreen mode");
+	add("width", { "-w", "--width" }, 1, "Set window width");
+	add("height", { "-h", "--height" }, 1, "Set window height");
+	add("shaders", { "-s", "--shaders" }, 1, "Select shader type to use (glsl or hlsl)");
+	add("gpuselection", { "-g", "--gpu" }, 1, "Select GPU to run on");
+	add("gpulist", { "-gl", "--listgpus" }, 0, "Display a list of available Vulkan devices");
+	add("benchmark", { "-b", "--benchmark" }, 0, "Run example in benchmark mode");
+	add("benchmarkwarmup", { "-bw", "--benchwarmup" }, 1, "Set warmup time for benchmark mode in seconds");
+	add("benchmarkruntime", { "-br", "--benchruntime" }, 1, "Set duration time for benchmark mode in seconds");
+	add("benchmarkresultfile", { "-bf", "--benchfilename" }, 1, "Set file name for benchmark results");
+	add("benchmarkresultframes", { "-bt", "--benchframetimes" }, 0, "Save frame times to benchmark results file");
+	add("benchmarkframes", { "-bfs", "--benchmarkframes" }, 1, "Only render the given number of frames");
+}
+
+void CommandLineParser::add(std::string name, std::vector<std::string> commands, bool hasValue, std::string help)
+{
+	options[name].commands = commands;
+	options[name].help = help;
+	options[name].set = false;
+	options[name].hasValue = hasValue;
+	options[name].value = "";
+}
+
+void CommandLineParser::printHelp()
+{
+	std::cout << "Available command line options:\n";
+	for (auto option : options) {
+		std::cout << " ";
+		for (size_t i = 0; i < option.second.commands.size(); i++) {
+			std::cout << option.second.commands[i];
+			if (i < option.second.commands.size() - 1) {
+				std::cout << ", ";
+			}
+		}
+		std::cout << ": " << option.second.help << "\n";
+	}
+	std::cout << "Press any key to close...";
+}
+
+void CommandLineParser::parse(std::vector<const char*> arguments)
+{
+	bool printHelp = false;
+	// Known arguments
+	for (auto& option : options) {
+		for (auto& command : option.second.commands) {
+			for (size_t i = 0; i < arguments.size(); i++) {
+				if (strcmp(arguments[i], command.c_str()) == 0) {
+					option.second.set = true;
+					// Get value
+					if (option.second.hasValue) {
+						if (arguments.size() > i + 1) {
+							option.second.value = arguments[i + 1];
+						}
+						if (option.second.value == "") {
+							printHelp = true;
+							break;
+						}
+					}
+				}
+			}
+		}
+	}
+	// Print help for unknown arguments or missing argument values
+	if (printHelp) {
+		options["help"].set = true;
+	}
+}
+
+bool CommandLineParser::isSet(std::string name)
+{
+	return ((options.find(name) != options.end()) && options[name].set);
+}
+
+std::string CommandLineParser::getValueAsString(std::string name, std::string defaultValue)
+{
+	assert(options.find(name) != options.end());
+	std::string value = options[name].value;
+	return (value != "") ? value : defaultValue;
+}
+
+int32_t CommandLineParser::getValueAsInt(std::string name, int32_t defaultValue)
+{
+	assert(options.find(name) != options.end());
+	std::string value = options[name].value;
+	if (value != "") {
+		char* numConvPtr;
+		int32_t intVal = strtol(value.c_str(), &numConvPtr, 10);
+		return (intVal > 0) ? intVal : defaultValue;
+	} else {
+		return defaultValue;
+	}
+	return int32_t();
+}
diff --git a/external/Vulkan/base/vulkanexamplebase.h b/external/Vulkan/base/vulkanexamplebase.h
new file mode 100644
index 0000000000000000000000000000000000000000..14644f6b1c6f67511bd308b46f621a7b8a6d34b0
--- /dev/null
+++ b/external/Vulkan/base/vulkanexamplebase.h
@@ -0,0 +1,543 @@
+/*
+* Vulkan Example base class
+*
+* Copyright (C) by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#pragma once
+
+#ifdef _WIN32
+#pragma comment(linker, "/subsystem:windows")
+#include <windows.h>
+#include <fcntl.h>
+#include <io.h>
+#include <ShellScalingAPI.h>
+#elif defined(VK_USE_PLATFORM_ANDROID_KHR)
+#include <android/native_activity.h>
+#include <android/asset_manager.h>
+#include <android_native_app_glue.h>
+#include <sys/system_properties.h>
+#include "VulkanAndroid.h"
+#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT)
+#include <directfb.h>
+#elif defined(VK_USE_PLATFORM_WAYLAND_KHR)
+#include <wayland-client.h>
+#include "xdg-shell-client-protocol.h"
+#elif defined(_DIRECT2DISPLAY)
+//
+#elif defined(VK_USE_PLATFORM_XCB_KHR)
+#include <xcb/xcb.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <vector>
+#include <array>
+#include <unordered_map>
+#include <numeric>
+#include <ctime>
+#include <iostream>
+#include <chrono>
+#include <random>
+#include <algorithm>
+#include <sys/stat.h>
+
+#define GLM_FORCE_RADIANS
+#define GLM_FORCE_DEPTH_ZERO_TO_ONE
+#define GLM_ENABLE_EXPERIMENTAL
+#include <glm/glm.hpp>
+#include <glm/gtc/matrix_transform.hpp>
+#include <glm/gtc/matrix_inverse.hpp>
+#include <glm/gtc/type_ptr.hpp>
+#include <string>
+#include <numeric>
+#include <array>
+
+#include "vulkan/vulkan.h"
+
+#include "keycodes.hpp"
+#include "VulkanTools.h"
+#include "VulkanDebug.h"
+#include "VulkanUIOverlay.h"
+#include "VulkanSwapChain.h"
+#include "VulkanBuffer.h"
+#include "VulkanDevice.h"
+#include "VulkanTexture.h"
+
+#include "VulkanInitializers.hpp"
+#include "camera.hpp"
+#include "benchmark.hpp"
+
+class CommandLineParser
+{
+public:
+	struct CommandLineOption {
+		std::vector<std::string> commands;
+		std::string value;
+		bool hasValue = false;
+		std::string help;
+		bool set = false;
+	};
+	std::unordered_map<std::string, CommandLineOption> options;
+	CommandLineParser();
+	void add(std::string name, std::vector<std::string> commands, bool hasValue, std::string help);
+	void printHelp();
+	void parse(std::vector<const char*> arguments);
+	bool isSet(std::string name);
+	std::string getValueAsString(std::string name, std::string defaultValue);
+	int32_t getValueAsInt(std::string name, int32_t defaultValue);
+};
+
+class VulkanExampleBase
+{
+private:
+	std::string getWindowTitle();
+	bool viewUpdated = false;
+	uint32_t destWidth;
+	uint32_t destHeight;
+	bool resizing = false;
+	void windowResize();
+	void handleMouseMove(int32_t x, int32_t y);
+	void nextFrame();
+	void updateOverlay();
+	void createPipelineCache();
+	void createCommandPool();
+	void createSynchronizationPrimitives();
+	void initSwapchain();
+	void setupSwapChain();
+	void createCommandBuffers();
+	void destroyCommandBuffers();
+	std::string shaderDir = "glsl";
+protected:
+	// Returns the path to the root of the glsl or hlsl shader directory.
+	std::string getShadersPath() const;
+
+	// Frame counter to display fps
+	uint32_t frameCounter = 0;
+	uint32_t lastFPS = 0;
+	std::chrono::time_point<std::chrono::high_resolution_clock> lastTimestamp;
+	// Vulkan instance, stores all per-application states
+	VkInstance instance;
+	std::vector<std::string> supportedInstanceExtensions;
+	// Physical device (GPU) that Vulkan will use
+	VkPhysicalDevice physicalDevice;
+	// Stores physical device properties (for e.g. checking device limits)
+	VkPhysicalDeviceProperties deviceProperties;
+	// Stores the features available on the selected physical device (for e.g. checking if a feature is available)
+	VkPhysicalDeviceFeatures deviceFeatures;
+	// Stores all available memory (type) properties for the physical device
+	VkPhysicalDeviceMemoryProperties deviceMemoryProperties;
+	/** @brief Set of physical device features to be enabled for this example (must be set in the derived constructor) */
+	VkPhysicalDeviceFeatures enabledFeatures{};
+	/** @brief Set of device extensions to be enabled for this example (must be set in the derived constructor) */
+	std::vector<const char*> enabledDeviceExtensions;
+	std::vector<const char*> enabledInstanceExtensions;
+	/** @brief Optional pNext structure for passing extension structures to device creation */
+	void* deviceCreatepNextChain = nullptr;
+	/** @brief Logical device, application's view of the physical device (GPU) */
+	VkDevice device;
+	// Handle to the device graphics queue that command buffers are submitted to
+	VkQueue queue;
+	// Depth buffer format (selected during Vulkan initialization)
+	VkFormat depthFormat;
+	// Command buffer pool
+	VkCommandPool cmdPool;
+	/** @brief Pipeline stages used to wait at for graphics queue submissions */
+	VkPipelineStageFlags submitPipelineStages = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+	// Contains command buffers and semaphores to be presented to the queue
+	VkSubmitInfo submitInfo;
+	// Command buffers used for rendering
+	std::vector<VkCommandBuffer> drawCmdBuffers;
+	// Global render pass for frame buffer writes
+	VkRenderPass renderPass = VK_NULL_HANDLE;
+	// List of available frame buffers (same as number of swap chain images)
+	std::vector<VkFramebuffer>frameBuffers;
+	// Active frame buffer index
+	uint32_t currentBuffer = 0;
+	// Descriptor set pool
+	VkDescriptorPool descriptorPool = VK_NULL_HANDLE;
+	// List of shader modules created (stored for cleanup)
+	std::vector<VkShaderModule> shaderModules;
+	// Pipeline cache object
+	VkPipelineCache pipelineCache;
+	// Wraps the swap chain to present images (framebuffers) to the windowing system
+	VulkanSwapChain swapChain;
+	// Synchronization semaphores
+	struct {
+		// Swap chain image presentation
+		VkSemaphore presentComplete;
+		// Command buffer submission and execution
+		VkSemaphore renderComplete;
+	} semaphores;
+	std::vector<VkFence> waitFences;
+public:
+	bool prepared = false;
+	bool resized = false;
+	uint32_t width = 1280;
+	uint32_t height = 720;
+
+	vks::UIOverlay UIOverlay;
+	CommandLineParser commandLineParser;
+
+	/** @brief Last frame time measured using a high performance timer (if available) */
+	float frameTimer = 1.0f;
+
+	vks::Benchmark benchmark;
+
+	/** @brief Encapsulated physical and logical vulkan device */
+	vks::VulkanDevice *vulkanDevice;
+
+	/** @brief Example settings that can be changed e.g. by command line arguments */
+	struct Settings {
+		/** @brief Activates validation layers (and message output) when set to true */
+		bool validation = false;
+		/** @brief Set to true if fullscreen mode has been requested via command line */
+		bool fullscreen = false;
+		/** @brief Set to true if v-sync will be forced for the swapchain */
+		bool vsync = false;
+		/** @brief Enable UI overlay */
+		bool overlay = true;
+	} settings;
+
+	VkClearColorValue defaultClearColor = { { 0.025f, 0.025f, 0.025f, 1.0f } };
+
+	static std::vector<const char*> args;
+
+	// Defines a frame rate independent timer value clamped from -1.0...1.0
+	// For use in animations, rotations, etc.
+	float timer = 0.0f;
+	// Multiplier for speeding up (or slowing down) the global timer
+	float timerSpeed = 0.25f;
+	bool paused = false;
+
+	Camera camera;
+	glm::vec2 mousePos;
+
+	std::string title = "Vulkan Example";
+	std::string name = "vulkanExample";
+	uint32_t apiVersion = VK_API_VERSION_1_0;
+
+	struct {
+		VkImage image;
+		VkDeviceMemory mem;
+		VkImageView view;
+	} depthStencil;
+
+	struct {
+		glm::vec2 axisLeft = glm::vec2(0.0f);
+		glm::vec2 axisRight = glm::vec2(0.0f);
+	} gamePadState;
+
+	struct {
+		bool left = false;
+		bool right = false;
+		bool middle = false;
+	} mouseButtons;
+
+	// OS specific
+#if defined(_WIN32)
+	HWND window;
+	HINSTANCE windowInstance;
+#elif defined(VK_USE_PLATFORM_ANDROID_KHR)
+	// true if application has focused, false if moved to background
+	bool focused = false;
+	struct TouchPos {
+		int32_t x;
+		int32_t y;
+	} touchPos;
+	bool touchDown = false;
+	double touchTimer = 0.0;
+	int64_t lastTapTime = 0;
+#elif (defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_MACOS_MVK))
+	void* view;
+#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT)
+	bool quit = false;
+	IDirectFB *dfb = nullptr;
+	IDirectFBDisplayLayer *layer = nullptr;
+	IDirectFBWindow *window = nullptr;
+	IDirectFBSurface *surface = nullptr;
+	IDirectFBEventBuffer *event_buffer = nullptr;
+#elif defined(VK_USE_PLATFORM_WAYLAND_KHR)
+	wl_display *display = nullptr;
+	wl_registry *registry = nullptr;
+	wl_compositor *compositor = nullptr;
+	struct xdg_wm_base *shell = nullptr;
+	wl_seat *seat = nullptr;
+	wl_pointer *pointer = nullptr;
+	wl_keyboard *keyboard = nullptr;
+	wl_surface *surface = nullptr;
+	struct xdg_surface *xdg_surface;
+	struct xdg_toplevel *xdg_toplevel;
+	bool quit = false;
+	bool configured = false;
+
+#elif defined(_DIRECT2DISPLAY)
+	bool quit = false;
+#elif defined(VK_USE_PLATFORM_XCB_KHR)
+	bool quit = false;
+	xcb_connection_t *connection;
+	xcb_screen_t *screen;
+	xcb_window_t window;
+	xcb_intern_atom_reply_t *atom_wm_delete_window;
+#elif defined(VK_USE_PLATFORM_HEADLESS_EXT)
+	bool quit = false;
+#endif
+
+	VulkanExampleBase(bool enableValidation = false);
+	virtual ~VulkanExampleBase();
+	/** @brief Setup the vulkan instance, enable required extensions and connect to the physical device (GPU) */
+	bool initVulkan();
+
+#if defined(_WIN32)
+	void setupConsole(std::string title);
+	void setupDPIAwareness();
+	HWND setupWindow(HINSTANCE hinstance, WNDPROC wndproc);
+	void handleMessages(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+#elif defined(VK_USE_PLATFORM_ANDROID_KHR)
+	static int32_t handleAppInput(struct android_app* app, AInputEvent* event);
+	static void handleAppCommand(android_app* app, int32_t cmd);
+#elif (defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_MACOS_MVK))
+	void* setupWindow(void* view);
+	void displayLinkOutputCb();
+	void mouseDragged(float x, float y);
+	void windowWillResize(float x, float y);
+	void windowDidResize();
+#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT)
+	IDirectFBSurface *setupWindow();
+	void handleEvent(const DFBWindowEvent *event);
+#elif defined(VK_USE_PLATFORM_WAYLAND_KHR)
+	struct xdg_surface *setupWindow();
+	void initWaylandConnection();
+	void setSize(int width, int height);
+	static void registryGlobalCb(void *data, struct wl_registry *registry,
+			uint32_t name, const char *interface, uint32_t version);
+	void registryGlobal(struct wl_registry *registry, uint32_t name,
+			const char *interface, uint32_t version);
+	static void registryGlobalRemoveCb(void *data, struct wl_registry *registry,
+			uint32_t name);
+	static void seatCapabilitiesCb(void *data, wl_seat *seat, uint32_t caps);
+	void seatCapabilities(wl_seat *seat, uint32_t caps);
+	static void pointerEnterCb(void *data, struct wl_pointer *pointer,
+			uint32_t serial, struct wl_surface *surface, wl_fixed_t sx,
+			wl_fixed_t sy);
+	static void pointerLeaveCb(void *data, struct wl_pointer *pointer,
+			uint32_t serial, struct wl_surface *surface);
+	static void pointerMotionCb(void *data, struct wl_pointer *pointer,
+			uint32_t time, wl_fixed_t sx, wl_fixed_t sy);
+	void pointerMotion(struct wl_pointer *pointer,
+			uint32_t time, wl_fixed_t sx, wl_fixed_t sy);
+	static void pointerButtonCb(void *data, struct wl_pointer *wl_pointer,
+			uint32_t serial, uint32_t time, uint32_t button, uint32_t state);
+	void pointerButton(struct wl_pointer *wl_pointer,
+			uint32_t serial, uint32_t time, uint32_t button, uint32_t state);
+	static void pointerAxisCb(void *data, struct wl_pointer *wl_pointer,
+			uint32_t time, uint32_t axis, wl_fixed_t value);
+	void pointerAxis(struct wl_pointer *wl_pointer,
+			uint32_t time, uint32_t axis, wl_fixed_t value);
+	static void keyboardKeymapCb(void *data, struct wl_keyboard *keyboard,
+			uint32_t format, int fd, uint32_t size);
+	static void keyboardEnterCb(void *data, struct wl_keyboard *keyboard,
+			uint32_t serial, struct wl_surface *surface, struct wl_array *keys);
+	static void keyboardLeaveCb(void *data, struct wl_keyboard *keyboard,
+			uint32_t serial, struct wl_surface *surface);
+	static void keyboardKeyCb(void *data, struct wl_keyboard *keyboard,
+			uint32_t serial, uint32_t time, uint32_t key, uint32_t state);
+	void keyboardKey(struct wl_keyboard *keyboard,
+			uint32_t serial, uint32_t time, uint32_t key, uint32_t state);
+	static void keyboardModifiersCb(void *data, struct wl_keyboard *keyboard,
+			uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched,
+			uint32_t mods_locked, uint32_t group);
+
+#elif defined(_DIRECT2DISPLAY)
+//
+#elif defined(VK_USE_PLATFORM_XCB_KHR)
+	xcb_window_t setupWindow();
+	void initxcbConnection();
+	void handleEvent(const xcb_generic_event_t *event);
+#else
+	void setupWindow();
+#endif
+	/** @brief (Virtual) Creates the application wide Vulkan instance */
+	virtual VkResult createInstance(bool enableValidation);
+	/** @brief (Pure virtual) Render function to be implemented by the sample application */
+	virtual void render() = 0;
+	/** @brief (Virtual) Called when the camera view has changed */
+	virtual void viewChanged();
+	/** @brief (Virtual) Called after a key was pressed, can be used to do custom key handling */
+	virtual void keyPressed(uint32_t);
+	/** @brief (Virtual) Called after the mouse cursor moved and before internal events (like camera rotation) is handled */
+	virtual void mouseMoved(double x, double y, bool &handled);
+	/** @brief (Virtual) Called when the window has been resized, can be used by the sample application to recreate resources */
+	virtual void windowResized();
+	/** @brief (Virtual) Called when resources have been recreated that require a rebuild of the command buffers (e.g. frame buffer), to be implemented by the sample application */
+	virtual void buildCommandBuffers();
+	/** @brief (Virtual) Setup default depth and stencil views */
+	virtual void setupDepthStencil();
+	/** @brief (Virtual) Setup default framebuffers for all requested swapchain images */
+	virtual void setupFrameBuffer();
+	/** @brief (Virtual) Setup a default renderpass */
+	virtual void setupRenderPass();
+	/** @brief (Virtual) Called after the physical device features have been read, can be used to set features to enable on the device */
+	virtual void getEnabledFeatures();
+
+	/** @brief Prepares all Vulkan resources and functions required to run the sample */
+	virtual void prepare();
+
+	/** @brief Loads a SPIR-V shader file for the given shader stage */
+	VkPipelineShaderStageCreateInfo loadShader(std::string fileName, VkShaderStageFlagBits stage);
+
+	/** @brief Entry point for the main render loop */
+	void renderLoop();
+
+	/** @brief Adds the drawing commands for the ImGui overlay to the given command buffer */
+	void drawUI(const VkCommandBuffer commandBuffer);
+
+	/** Prepare the next frame for workload submission by acquiring the next swap chain image */
+	void prepareFrame();
+	/** @brief Presents the current image to the swap chain */
+	void submitFrame();
+	/** @brief (Virtual) Default image acquire + submission and command buffer submission function */
+	virtual void renderFrame();
+
+	/** @brief (Virtual) Called when the UI overlay is updating, can be used to add custom elements to the overlay */
+	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay);
+};
+
+// OS specific macros for the example main entry points
+#if defined(_WIN32)
+// Windows entry point
+#define VULKAN_EXAMPLE_MAIN()																		\
+VulkanExample *vulkanExample;																		\
+LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)						\
+{																									\
+	if (vulkanExample != NULL)																		\
+	{																								\
+		vulkanExample->handleMessages(hWnd, uMsg, wParam, lParam);									\
+	}																								\
+	return (DefWindowProc(hWnd, uMsg, wParam, lParam));												\
+}																									\
+int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int)									\
+{																									\
+	for (int32_t i = 0; i < __argc; i++) { VulkanExample::args.push_back(__argv[i]); };  			\
+	vulkanExample = new VulkanExample();															\
+	vulkanExample->initVulkan();																	\
+	vulkanExample->setupWindow(hInstance, WndProc);													\
+	vulkanExample->prepare();																		\
+	vulkanExample->renderLoop();																	\
+	delete(vulkanExample);																			\
+	return 0;																						\
+}
+#elif defined(VK_USE_PLATFORM_ANDROID_KHR)
+// Android entry point
+#define VULKAN_EXAMPLE_MAIN()																		\
+VulkanExample *vulkanExample;																		\
+void android_main(android_app* state)																\
+{																									\
+	vulkanExample = new VulkanExample();															\
+	state->userData = vulkanExample;																\
+	state->onAppCmd = VulkanExample::handleAppCommand;												\
+	state->onInputEvent = VulkanExample::handleAppInput;											\
+	androidApp = state;																				\
+	vks::android::getDeviceConfig();																\
+	vulkanExample->renderLoop();																	\
+	delete(vulkanExample);																			\
+}
+#elif defined(_DIRECT2DISPLAY)
+// Linux entry point with direct to display wsi
+#define VULKAN_EXAMPLE_MAIN()																		\
+VulkanExample *vulkanExample;																		\
+static void handleEvent()                                											\
+{																									\
+}																									\
+int main(const int argc, const char *argv[])													    \
+{																									\
+	for (size_t i = 0; i < argc; i++) { VulkanExample::args.push_back(argv[i]); };  				\
+	vulkanExample = new VulkanExample();															\
+	vulkanExample->initVulkan();																	\
+	vulkanExample->prepare();																		\
+	vulkanExample->renderLoop();																	\
+	delete(vulkanExample);																			\
+	return 0;																						\
+}
+#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT)
+#define VULKAN_EXAMPLE_MAIN()																		\
+VulkanExample *vulkanExample;																		\
+static void handleEvent(const DFBWindowEvent *event)												\
+{																									\
+	if (vulkanExample != NULL)																		\
+	{																								\
+		vulkanExample->handleEvent(event);															\
+	}																								\
+}																									\
+int main(const int argc, const char *argv[])													    \
+{																									\
+	for (size_t i = 0; i < argc; i++) { VulkanExample::args.push_back(argv[i]); };  				\
+	vulkanExample = new VulkanExample();															\
+	vulkanExample->initVulkan();																	\
+	vulkanExample->setupWindow();					 												\
+	vulkanExample->prepare();																		\
+	vulkanExample->renderLoop();																	\
+	delete(vulkanExample);																			\
+	return 0;																						\
+}
+#elif (defined(VK_USE_PLATFORM_WAYLAND_KHR) || defined(VK_USE_PLATFORM_HEADLESS_EXT))
+#define VULKAN_EXAMPLE_MAIN()																		\
+VulkanExample *vulkanExample;																		\
+int main(const int argc, const char *argv[])													    \
+{																									\
+	for (size_t i = 0; i < argc; i++) { VulkanExample::args.push_back(argv[i]); };  				\
+	vulkanExample = new VulkanExample();															\
+	vulkanExample->initVulkan();																	\
+	vulkanExample->setupWindow();					 												\
+	vulkanExample->prepare();																		\
+	vulkanExample->renderLoop();																	\
+	delete(vulkanExample);																			\
+	return 0;																						\
+}
+#elif defined(VK_USE_PLATFORM_XCB_KHR)
+#define VULKAN_EXAMPLE_MAIN()																		\
+VulkanExample *vulkanExample;																		\
+static void handleEvent(const xcb_generic_event_t *event)											\
+{																									\
+	if (vulkanExample != NULL)																		\
+	{																								\
+		vulkanExample->handleEvent(event);															\
+	}																								\
+}																									\
+int main(const int argc, const char *argv[])													    \
+{																									\
+	for (size_t i = 0; i < argc; i++) { VulkanExample::args.push_back(argv[i]); };  				\
+	vulkanExample = new VulkanExample();															\
+	vulkanExample->initVulkan();																	\
+	vulkanExample->setupWindow();					 												\
+	vulkanExample->prepare();																		\
+	vulkanExample->renderLoop();																	\
+	delete(vulkanExample);																			\
+	return 0;																						\
+}
+#elif (defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_MACOS_MVK))
+#if defined(VK_EXAMPLE_XCODE_GENERATED)
+#define VULKAN_EXAMPLE_MAIN()																		\
+VulkanExample *vulkanExample;																		\
+int main(const int argc, const char *argv[])														\
+{																									\
+	@autoreleasepool																				\
+	{																								\
+		for (size_t i = 0; i < argc; i++) { VulkanExample::args.push_back(argv[i]); };				\
+		vulkanExample = new VulkanExample();														\
+		vulkanExample->initVulkan();																\
+		vulkanExample->setupWindow(nullptr);														\
+		vulkanExample->prepare();																	\
+		vulkanExample->renderLoop();																\
+		delete(vulkanExample);																		\
+	}																								\
+	return 0;																						\
+}
+#else
+#define VULKAN_EXAMPLE_MAIN()
+#endif
+#endif
diff --git a/external/Vulkan/bin/benchmark-all.py b/external/Vulkan/bin/benchmark-all.py
new file mode 100644
index 0000000000000000000000000000000000000000..78773df2602ce95378e9c4c0bcecaf7c3d9a25f7
--- /dev/null
+++ b/external/Vulkan/bin/benchmark-all.py
@@ -0,0 +1,84 @@
+# Benchmark all examples
+import subprocess
+import sys
+import os
+import platform
+
+EXAMPLES = [
+	"bloom",
+	"computecloth",
+	"computecullandlod",
+	"computenbody",
+	"computeparticles",
+	"computeshader",
+	"debugmarker",
+	"deferred",
+	"deferredmultisampling",
+	"deferredshadows",
+	"displacement",
+	"distancefieldfonts",
+	"dynamicuniformbuffer",
+	"gears",
+	"geometryshader",
+	"hdr",
+	"imgui",
+	"indirectdraw",
+	"instancing",
+	"mesh",
+	"multisampling",
+	"multithreading",
+	"occlusionquery",
+	"offscreen",
+	"parallaxmapping",
+	"particlefire",
+	"pbrbasic",
+	"pbribl",
+	"pbrtexture",
+	"pipelines",
+	"pushconstants",
+	"radialblur",
+	"raytracing",
+	"scenerendering",
+	"shadowmapping",
+	"shadowmappingomni",
+	"skeletalanimation",
+	"specializationconstants",
+	"sphericalenvmapping",
+	"ssao",
+	"stencilbuffer",
+	"subpasses",
+	"terraintessellation",
+	"tessellation",
+	"textoverlay",
+	"texture",
+	"texture3d",
+	"texturearray",
+	"texturecubemap",
+	"texturemipmapgen",
+	"texturesparseresidency",
+	"triangle",
+	"viewportarray",
+	"vulkanscene"
+]
+
+CURR_INDEX = 0
+
+ARGS = "-fullscreen -b"
+
+print("Benchmarking all examples...")
+
+os.makedirs("./benchmark", exist_ok=True)
+
+for example in EXAMPLES:
+	print("---- (%d/%d) Running %s in benchmark mode ----" % (CURR_INDEX+1, len(EXAMPLES), example))
+	if platform.system() == 'Linux':
+		RESULT_CODE = subprocess.call("./%s %s -bf ./benchmark/%s.csv 5" % (example, ARGS, example), shell=True)
+	else:
+		RESULT_CODE = subprocess.call("%s %s -bf ./benchmark/%s.csv 5" % (example, ARGS, example))
+	if RESULT_CODE == 0:
+		print("Results written to ./benchmark/%s.csv" % example)
+	else:
+		print("Error, result code = %d" % RESULT_CODE)
+	CURR_INDEX += 1
+
+print("Benchmark run finished")
diff --git a/external/Vulkan/cmake/FindDirectFB.cmake b/external/Vulkan/cmake/FindDirectFB.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..2c98b2acdc63aaca287114c728d141a6e5bc8ec5
--- /dev/null
+++ b/external/Vulkan/cmake/FindDirectFB.cmake
@@ -0,0 +1,28 @@
+# Try to find DirectFB
+#
+# This will define:
+#
+#   DIRECTFB_FOUND       - True if DirectFB is found
+#   DIRECTFB_LIBRARIES   - Link these to use DirectFB
+#   DIRECTFB_INCLUDE_DIR - Include directory for DirectFB
+#   DIRECTFB_DEFINITIONS - Compiler flags for using DirectFB
+#
+# Redistribution and use is allowed according to the terms of the BSD license.
+# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
+
+IF (NOT WIN32)
+  FIND_PACKAGE(PkgConfig)
+  PKG_CHECK_MODULES(PKG_DIRECTFB QUIET directfb)
+
+  SET(DIRECTFB_DEFINITIONS ${PKG_DIRECTFB_CFLAGS})
+
+  FIND_PATH(DIRECTFB_INCLUDE_DIR  NAMES directfb.h HINTS ${PKG_DIRECTFB_INCLUDE_DIRS})
+
+  FIND_LIBRARY(DIRECTFB_LIBRARIES NAMES directfb   HINTS ${PKG_DIRECTFB_LIBRARY_DIRS})
+
+  include(FindPackageHandleStandardArgs)
+
+  FIND_PACKAGE_HANDLE_STANDARD_ARGS(DIRECTFB DEFAULT_MSG DIRECTFB_LIBRARIES DIRECTFB_INCLUDE_DIR)
+
+  MARK_AS_ADVANCED(DIRECTFB_INCLUDE_DIR DIRECTFB_LIBRARIES)
+ENDIF ()
diff --git a/external/Vulkan/cmake/FindWayland.cmake b/external/Vulkan/cmake/FindWayland.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..f93218b8739e66a50678a4b6965231438b87692f
--- /dev/null
+++ b/external/Vulkan/cmake/FindWayland.cmake
@@ -0,0 +1,66 @@
+# Try to find Wayland on a Unix system
+#
+# This will define:
+#
+#   WAYLAND_FOUND       - True if Wayland is found
+#   WAYLAND_LIBRARIES   - Link these to use Wayland
+#   WAYLAND_INCLUDE_DIR - Include directory for Wayland
+#   WAYLAND_DEFINITIONS - Compiler flags for using Wayland
+#
+# In addition the following more fine grained variables will be defined:
+#
+#   WAYLAND_CLIENT_FOUND  WAYLAND_CLIENT_INCLUDE_DIR  WAYLAND_CLIENT_LIBRARIES
+#   WAYLAND_SERVER_FOUND  WAYLAND_SERVER_INCLUDE_DIR  WAYLAND_SERVER_LIBRARIES
+#   WAYLAND_EGL_FOUND     WAYLAND_EGL_INCLUDE_DIR     WAYLAND_EGL_LIBRARIES
+#
+# Copyright (c) 2013 Martin Gräßlin <mgraesslin@kde.org>
+#
+# Redistribution and use is allowed according to the terms of the BSD license.
+# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
+
+IF (NOT WIN32)
+  IF (WAYLAND_INCLUDE_DIR AND WAYLAND_LIBRARIES)
+    # In the cache already
+    SET(WAYLAND_FIND_QUIETLY TRUE)
+  ENDIF ()
+
+  # Use pkg-config to get the directories and then use these values
+  # in the FIND_PATH() and FIND_LIBRARY() calls
+  FIND_PACKAGE(PkgConfig)
+  PKG_CHECK_MODULES(PKG_WAYLAND QUIET wayland-client wayland-server wayland-egl wayland-cursor)
+
+  SET(WAYLAND_DEFINITIONS ${PKG_WAYLAND_CFLAGS})
+
+  FIND_PATH(WAYLAND_CLIENT_INCLUDE_DIR  NAMES wayland-client.h HINTS ${PKG_WAYLAND_INCLUDE_DIRS})
+  FIND_PATH(WAYLAND_SERVER_INCLUDE_DIR  NAMES wayland-server.h HINTS ${PKG_WAYLAND_INCLUDE_DIRS})
+  FIND_PATH(WAYLAND_EGL_INCLUDE_DIR     NAMES wayland-egl.h    HINTS ${PKG_WAYLAND_INCLUDE_DIRS})
+  FIND_PATH(WAYLAND_CURSOR_INCLUDE_DIR  NAMES wayland-cursor.h HINTS ${PKG_WAYLAND_INCLUDE_DIRS})
+
+  FIND_LIBRARY(WAYLAND_CLIENT_LIBRARIES NAMES wayland-client   HINTS ${PKG_WAYLAND_LIBRARY_DIRS})
+  FIND_LIBRARY(WAYLAND_SERVER_LIBRARIES NAMES wayland-server   HINTS ${PKG_WAYLAND_LIBRARY_DIRS})
+  FIND_LIBRARY(WAYLAND_EGL_LIBRARIES    NAMES wayland-egl      HINTS ${PKG_WAYLAND_LIBRARY_DIRS})
+  FIND_LIBRARY(WAYLAND_CURSOR_LIBRARIES NAMES wayland-cursor   HINTS ${PKG_WAYLAND_LIBRARY_DIRS})
+
+  set(WAYLAND_INCLUDE_DIR ${WAYLAND_CLIENT_INCLUDE_DIR} ${WAYLAND_SERVER_INCLUDE_DIR} ${WAYLAND_EGL_INCLUDE_DIR} ${WAYLAND_CURSOR_INCLUDE_DIR})
+
+  set(WAYLAND_LIBRARIES ${WAYLAND_CLIENT_LIBRARIES} ${WAYLAND_SERVER_LIBRARIES} ${WAYLAND_EGL_LIBRARIES} ${WAYLAND_CURSOR_LIBRARIES})
+
+  list(REMOVE_DUPLICATES WAYLAND_INCLUDE_DIR)
+
+  include(FindPackageHandleStandardArgs)
+
+  FIND_PACKAGE_HANDLE_STANDARD_ARGS(WAYLAND_CLIENT  DEFAULT_MSG  WAYLAND_CLIENT_LIBRARIES  WAYLAND_CLIENT_INCLUDE_DIR)
+  FIND_PACKAGE_HANDLE_STANDARD_ARGS(WAYLAND_SERVER  DEFAULT_MSG  WAYLAND_SERVER_LIBRARIES  WAYLAND_SERVER_INCLUDE_DIR)
+  FIND_PACKAGE_HANDLE_STANDARD_ARGS(WAYLAND_EGL     DEFAULT_MSG  WAYLAND_EGL_LIBRARIES     WAYLAND_EGL_INCLUDE_DIR)
+  FIND_PACKAGE_HANDLE_STANDARD_ARGS(WAYLAND_CURSOR  DEFAULT_MSG  WAYLAND_CURSOR_LIBRARIES  WAYLAND_CURSOR_INCLUDE_DIR)
+  FIND_PACKAGE_HANDLE_STANDARD_ARGS(WAYLAND         DEFAULT_MSG  WAYLAND_LIBRARIES         WAYLAND_INCLUDE_DIR)
+
+  MARK_AS_ADVANCED(
+        WAYLAND_INCLUDE_DIR         WAYLAND_LIBRARIES
+        WAYLAND_CLIENT_INCLUDE_DIR  WAYLAND_CLIENT_LIBRARIES
+        WAYLAND_SERVER_INCLUDE_DIR  WAYLAND_SERVER_LIBRARIES
+        WAYLAND_EGL_INCLUDE_DIR     WAYLAND_EGL_LIBRARIES
+        WAYLAND_CURSOR_INCLUDE_DIR  WAYLAND_CURSOR_LIBRARIES
+  )
+
+ENDIF ()
diff --git a/external/Vulkan/cmake/FindXCB.cmake b/external/Vulkan/cmake/FindXCB.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..89ed904bf2e5aaa2dd32679fcc64993d61aa044c
--- /dev/null
+++ b/external/Vulkan/cmake/FindXCB.cmake
@@ -0,0 +1,51 @@
+# - FindXCB
+#
+# Copyright 2015 Valve Coporation
+
+find_package(PkgConfig)
+
+if(NOT XCB_FIND_COMPONENTS)
+    set(XCB_FIND_COMPONENTS xcb)
+endif()
+
+include(FindPackageHandleStandardArgs)
+set(XCB_FOUND true)
+set(XCB_INCLUDE_DIRS "")
+set(XCB_LIBRARIES "")
+foreach(comp ${XCB_FIND_COMPONENTS})
+    # component name
+    string(TOUPPER ${comp} compname)
+    string(REPLACE "-" "_" compname ${compname})
+    # header name
+    string(REPLACE "xcb-" "" headername xcb/${comp}.h)
+    # library name
+    set(libname ${comp})
+
+    pkg_check_modules(PC_${comp} QUIET ${comp})
+
+    find_path(${compname}_INCLUDE_DIR NAMES ${headername}
+        HINTS
+        ${PC_${comp}_INCLUDEDIR}
+        ${PC_${comp}_INCLUDE_DIRS}
+        )
+
+    find_library(${compname}_LIBRARY NAMES ${libname}
+        HINTS
+        ${PC_${comp}_LIBDIR}
+        ${PC_${comp}_LIBRARY_DIRS}
+        )
+
+    find_package_handle_standard_args(${comp}
+        FOUND_VAR ${comp}_FOUND
+        REQUIRED_VARS ${compname}_INCLUDE_DIR ${compname}_LIBRARY)
+    mark_as_advanced(${compname}_INCLUDE_DIR ${compname}_LIBRARY)
+
+    list(APPEND XCB_INCLUDE_DIRS ${${compname}_INCLUDE_DIR})
+    list(APPEND XCB_LIBRARIES ${${compname}_LIBRARY})
+
+    if(NOT ${comp}_FOUND)
+        set(XCB_FOUND false)
+    endif()
+endforeach()
+
+list(REMOVE_DUPLICATES XCB_INCLUDE_DIRS)
\ No newline at end of file
diff --git a/external/Vulkan/data/README.md b/external/Vulkan/data/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..1566ad0a0758cf39e648e49eb9df28f343e691a6
--- /dev/null
+++ b/external/Vulkan/data/README.md
@@ -0,0 +1,13 @@
+# Getting the assets
+
+Binary assets (models, textures, etc.) are not stored in this repository and need to be downloaded manually.
+
+## Downloading the assets
+
+### Option 1: Run the python script
+
+Run the [download_assets.py](../download_assets.py) python script which will download the asset pack and unpacks it into the appropriate folder.
+
+### Option 2: Manual download
+
+Download the asset pack from [https://vulkan.gpuinfo.org/downloads/vulkan_asset_pack_gltf.zip](https://vulkan.gpuinfo.org/downloads/vulkan_asset_pack_gltf.zip) and extract it in the ```data``` directory.
diff --git a/external/Vulkan/data/shaders/glsl/base/textoverlay.frag b/external/Vulkan/data/shaders/glsl/base/textoverlay.frag
new file mode 100644
index 0000000000000000000000000000000000000000..83ceb1ead06ce0f68c2711b96fd2c0f822bf1328
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/base/textoverlay.frag
@@ -0,0 +1,13 @@
+#version 450 core
+
+layout (location = 0) in vec2 inUV;
+
+layout (binding = 0) uniform sampler2D samplerFont;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main(void)
+{
+	float color = texture(samplerFont, inUV).r;
+	outFragColor = vec4(vec3(color), 1.0);
+}
diff --git a/external/Vulkan/data/shaders/glsl/base/textoverlay.frag.spv b/external/Vulkan/data/shaders/glsl/base/textoverlay.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..8f8aae1171b488662c12239f734ac16e86381227
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/base/textoverlay.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/base/textoverlay.vert b/external/Vulkan/data/shaders/glsl/base/textoverlay.vert
new file mode 100644
index 0000000000000000000000000000000000000000..548bfdb22e90f6b0ec61fbcf4dbf255e16f7cffd
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/base/textoverlay.vert
@@ -0,0 +1,17 @@
+#version 450 core
+
+layout (location = 0) in vec2 inPos;
+layout (location = 1) in vec2 inUV;
+
+layout (location = 0) out vec2 outUV;
+
+out gl_PerVertex 
+{
+    vec4 gl_Position;   
+};
+
+void main(void)
+{
+	gl_Position = vec4(inPos, 0.0, 1.0);
+	outUV = inUV;
+}
diff --git a/external/Vulkan/data/shaders/glsl/base/textoverlay.vert.spv b/external/Vulkan/data/shaders/glsl/base/textoverlay.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..07b9000aa0d947dc1f908d13706152457059f77a
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/base/textoverlay.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/base/uioverlay.frag b/external/Vulkan/data/shaders/glsl/base/uioverlay.frag
new file mode 100644
index 0000000000000000000000000000000000000000..e0d79957f6d7c504bab818a32f22d4d95309e152
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/base/uioverlay.frag
@@ -0,0 +1,13 @@
+#version 450
+
+layout (binding = 0) uniform sampler2D fontSampler;
+
+layout (location = 0) in vec2 inUV;
+layout (location = 1) in vec4 inColor;
+
+layout (location = 0) out vec4 outColor;
+
+void main() 
+{
+	outColor = inColor * texture(fontSampler, inUV);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/base/uioverlay.frag.spv b/external/Vulkan/data/shaders/glsl/base/uioverlay.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..147c5d2fbcb029ff1d3bcbe365ad07fec025f66a
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/base/uioverlay.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/base/uioverlay.vert b/external/Vulkan/data/shaders/glsl/base/uioverlay.vert
new file mode 100644
index 0000000000000000000000000000000000000000..ddb63a45d4980a719b844ab256882d3f4a3121d7
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/base/uioverlay.vert
@@ -0,0 +1,25 @@
+#version 450
+
+layout (location = 0) in vec2 inPos;
+layout (location = 1) in vec2 inUV;
+layout (location = 2) in vec4 inColor;
+
+layout (push_constant) uniform PushConstants {
+	vec2 scale;
+	vec2 translate;
+} pushConstants;
+
+layout (location = 0) out vec2 outUV;
+layout (location = 1) out vec4 outColor;
+
+out gl_PerVertex 
+{
+	vec4 gl_Position;   
+};
+
+void main() 
+{
+	outUV = inUV;
+	outColor = inColor;
+	gl_Position = vec4(inPos * pushConstants.scale + pushConstants.translate, 0.0, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/base/uioverlay.vert.spv b/external/Vulkan/data/shaders/glsl/base/uioverlay.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..f359961ffb7266e16dac9c14878ecea2f1ddfcd9
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/base/uioverlay.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/bloom/colorpass.frag b/external/Vulkan/data/shaders/glsl/bloom/colorpass.frag
new file mode 100644
index 0000000000000000000000000000000000000000..d9f74f2ddece2dde3e6779a27be3b51ddb89f347
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/bloom/colorpass.frag
@@ -0,0 +1,14 @@
+#version 450
+
+layout (binding = 1) uniform sampler2D colorMap;
+
+layout (location = 0) in vec3 inColor;
+layout (location = 1) in vec2 inUV;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+	outFragColor.rgb = inColor;
+//	outFragColor = texture(colorMap, inUV);// * vec4(inColor, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/bloom/colorpass.frag.spv b/external/Vulkan/data/shaders/glsl/bloom/colorpass.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..71873365785d23788bd7f24802a75f86a3232904
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/bloom/colorpass.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/bloom/colorpass.vert b/external/Vulkan/data/shaders/glsl/bloom/colorpass.vert
new file mode 100644
index 0000000000000000000000000000000000000000..565f2ddcdb8b493daf36bee009fc0175b3980cef
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/bloom/colorpass.vert
@@ -0,0 +1,27 @@
+#version 450
+
+layout (location = 0) in vec4 inPos;
+layout (location = 1) in vec2 inUV;
+layout (location = 2) in vec3 inColor;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 view;
+	mat4 model;
+} ubo;
+
+layout (location = 0) out vec3 outColor;
+layout (location = 1) out vec2 outUV;
+
+out gl_PerVertex
+{
+	vec4 gl_Position;
+};
+
+void main() 
+{
+	outUV = inUV;
+	outColor = inColor;
+	gl_Position = ubo.projection * ubo.view * ubo.model * inPos;
+}
diff --git a/external/Vulkan/data/shaders/glsl/bloom/colorpass.vert.spv b/external/Vulkan/data/shaders/glsl/bloom/colorpass.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..86c3ca642a624ad551a680e91c3b87c386bbf325
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/bloom/colorpass.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/bloom/gaussblur.frag b/external/Vulkan/data/shaders/glsl/bloom/gaussblur.frag
new file mode 100644
index 0000000000000000000000000000000000000000..3758f6c731270ee1998d3d5fe505af7fff0a2cc9
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/bloom/gaussblur.frag
@@ -0,0 +1,44 @@
+#version 450
+
+layout (binding = 1) uniform sampler2D samplerColor;
+
+layout (binding = 0) uniform UBO 
+{
+	float blurScale;
+	float blurStrength;
+} ubo;
+
+layout (constant_id = 0) const int blurdirection = 0;
+
+layout (location = 0) in vec2 inUV;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+	float weight[5];
+	weight[0] = 0.227027;
+	weight[1] = 0.1945946;
+	weight[2] = 0.1216216;
+	weight[3] = 0.054054;
+	weight[4] = 0.016216;
+
+	vec2 tex_offset = 1.0 / textureSize(samplerColor, 0) * ubo.blurScale; // gets size of single texel
+	vec3 result = texture(samplerColor, inUV).rgb * weight[0]; // current fragment's contribution
+	for(int i = 1; i < 5; ++i)
+	{
+		if (blurdirection == 1)
+		{
+			// H
+			result += texture(samplerColor, inUV + vec2(tex_offset.x * i, 0.0)).rgb * weight[i] * ubo.blurStrength;
+			result += texture(samplerColor, inUV - vec2(tex_offset.x * i, 0.0)).rgb * weight[i] * ubo.blurStrength;
+		}
+		else
+		{
+			// V
+			result += texture(samplerColor, inUV + vec2(0.0, tex_offset.y * i)).rgb * weight[i] * ubo.blurStrength;
+			result += texture(samplerColor, inUV - vec2(0.0, tex_offset.y * i)).rgb * weight[i] * ubo.blurStrength;
+		}
+	}
+	outFragColor = vec4(result, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/bloom/gaussblur.frag.spv b/external/Vulkan/data/shaders/glsl/bloom/gaussblur.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..328633b4d5094283f61922026a06d7fee3539b38
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/bloom/gaussblur.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/bloom/gaussblur.vert b/external/Vulkan/data/shaders/glsl/bloom/gaussblur.vert
new file mode 100644
index 0000000000000000000000000000000000000000..a5d60d0e750f260d3c034db033e15f18fb6a89d8
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/bloom/gaussblur.vert
@@ -0,0 +1,14 @@
+#version 450
+
+layout (location = 0) out vec2 outUV;
+
+out gl_PerVertex
+{
+	vec4 gl_Position;
+};
+
+void main() 
+{
+	outUV = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
+	gl_Position = vec4(outUV * 2.0f - 1.0f, 0.0f, 1.0f);
+}
diff --git a/external/Vulkan/data/shaders/glsl/bloom/gaussblur.vert.spv b/external/Vulkan/data/shaders/glsl/bloom/gaussblur.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..4040f608bd837727d1051a6539a15338d11242ae
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/bloom/gaussblur.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/bloom/phongpass.frag b/external/Vulkan/data/shaders/glsl/bloom/phongpass.frag
new file mode 100644
index 0000000000000000000000000000000000000000..e748d4607234d9375bc1b55f49ad6f4a1dddd51e
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/bloom/phongpass.frag
@@ -0,0 +1,30 @@
+#version 450
+
+layout (binding = 1) uniform sampler2D colorMap;
+
+layout (location = 0) in vec3 inNormal;
+layout (location = 1) in vec2 inUV;
+layout (location = 2) in vec3 inColor;
+layout (location = 3) in vec3 inViewVec;
+layout (location = 4) in vec3 inLightVec;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+	vec3 ambient = vec3(0.0f);
+	
+	// Adjust light calculations for glow color 
+	if ((inColor.r >= 0.9) || (inColor.g >= 0.9) || (inColor.b >= 0.9)) 
+	{
+		ambient = inColor * 0.25;
+	}
+
+	vec3 N = normalize(inNormal);
+	vec3 L = normalize(inLightVec);
+	vec3 V = normalize(inViewVec);
+	vec3 R = reflect(-L, N);
+	vec3 diffuse = max(dot(N, L), 0.0) * inColor;
+	vec3 specular = pow(max(dot(R, V), 0.0), 8.0) * vec3(0.75);
+	outFragColor = vec4(ambient + diffuse + specular, 1.0);	
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/bloom/phongpass.frag.spv b/external/Vulkan/data/shaders/glsl/bloom/phongpass.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..27e5b53a94dd7e82afe695009757aba7b83f9479
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/bloom/phongpass.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/bloom/phongpass.vert b/external/Vulkan/data/shaders/glsl/bloom/phongpass.vert
new file mode 100644
index 0000000000000000000000000000000000000000..1c5618b269d8e765c7fa2cec257f2f29b0b650c3
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/bloom/phongpass.vert
@@ -0,0 +1,38 @@
+#version 450
+
+layout (location = 0) in vec4 inPos;
+layout (location = 1) in vec2 inUV;
+layout (location = 2) in vec3 inColor;
+layout (location = 3) in vec3 inNormal;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 view;
+	mat4 model;
+} ubo;
+
+layout (location = 0) out vec3 outNormal;
+layout (location = 1) out vec2 outUV;
+layout (location = 2) out vec3 outColor;
+layout (location = 3) out vec3 outViewVec;
+layout (location = 4) out vec3 outLightVec;
+
+out gl_PerVertex
+{
+	vec4 gl_Position;
+};
+
+void main() 
+{
+	outNormal = inNormal;
+	outColor = inColor;
+	outUV = inUV;
+	gl_Position = ubo.projection * ubo.view * ubo.model * inPos;
+
+	vec3 lightPos = vec3(-5.0, -5.0, 0.0);
+	vec4 pos = ubo.view * ubo.model * inPos;
+	outNormal = mat3(ubo.view * ubo.model) * inNormal;
+	outLightVec = lightPos - pos.xyz;
+	outViewVec = -pos.xyz;	
+}
diff --git a/external/Vulkan/data/shaders/glsl/bloom/phongpass.vert.spv b/external/Vulkan/data/shaders/glsl/bloom/phongpass.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..7fa727f8080f258788bbb295b4c44f4c8b938e0c
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/bloom/phongpass.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/bloom/skybox.frag b/external/Vulkan/data/shaders/glsl/bloom/skybox.frag
new file mode 100644
index 0000000000000000000000000000000000000000..56b8f42cbb23fec3cc0ce8bb731ef604bb59b61d
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/bloom/skybox.frag
@@ -0,0 +1,12 @@
+#version 450
+
+layout (binding = 1) uniform samplerCube samplerCubeMap;
+
+layout (location = 0) in vec3 inUVW;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+	outFragColor = texture(samplerCubeMap, inUVW);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/bloom/skybox.frag.spv b/external/Vulkan/data/shaders/glsl/bloom/skybox.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..54a894ccad057de259cab4c5e03a47bf8e3384de
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/bloom/skybox.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/bloom/skybox.vert b/external/Vulkan/data/shaders/glsl/bloom/skybox.vert
new file mode 100644
index 0000000000000000000000000000000000000000..3659e4070c95f65a18febc61c323f0322565a515
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/bloom/skybox.vert
@@ -0,0 +1,24 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 view;
+	mat4 model;
+} ubo;
+
+layout (location = 0) out vec3 outUVW;
+
+out gl_PerVertex
+{
+	vec4 gl_Position;
+};
+
+
+void main() 
+{
+	outUVW = inPos;
+	gl_Position = ubo.projection * ubo.view * ubo.model * vec4(inPos.xyz, 1.0);
+}
diff --git a/external/Vulkan/data/shaders/glsl/bloom/skybox.vert.spv b/external/Vulkan/data/shaders/glsl/bloom/skybox.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..c62b7c4796c5b79494f18956311073b2bc65e0f1
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/bloom/skybox.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/compileshaders.py b/external/Vulkan/data/shaders/glsl/compileshaders.py
new file mode 100644
index 0000000000000000000000000000000000000000..e0a4e1f5f9b23451c0856f8168ed9610a8e7edf0
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/compileshaders.py
@@ -0,0 +1,49 @@
+import argparse
+import fileinput
+import os
+import subprocess
+import sys
+
+parser = argparse.ArgumentParser(description='Compile all GLSL shaders')
+parser.add_argument('--glslang', type=str, help='path to glslangvalidator executable')
+parser.add_argument('--g', action='store_true', help='compile with debug symbols')
+args = parser.parse_args()
+
+def findGlslang():
+    def isExe(path):
+        return os.path.isfile(path) and os.access(path, os.X_OK)
+
+    if args.glslang != None and isExe(args.glslang):
+        return args.glslang
+
+    exe_name = "glslangvalidator"
+    if os.name == "nt":
+        exe_name += ".exe"
+
+    for exe_dir in os.environ["PATH"].split(os.pathsep):
+        full_path = os.path.join(exe_dir, exe_name)
+        if isExe(full_path):
+            return full_path
+
+    sys.exit("Could not find DXC executable on PATH, and was not specified with --dxc")
+
+glslang_path = findGlslang()
+dir_path = os.path.dirname(os.path.realpath(__file__))
+dir_path = dir_path.replace('\\', '/')
+for root, dirs, files in os.walk(dir_path):
+    for file in files:
+        if file.endswith(".vert") or file.endswith(".frag") or file.endswith(".comp") or file.endswith(".geom") or file.endswith(".tesc") or file.endswith(".tese") or file.endswith(".rgen") or file.endswith(".rchit") or file.endswith(".rmiss"):
+            input_file = os.path.join(root, file)
+            output_file = input_file + ".spv"
+
+            add_params = ""
+            if args.g:
+                add_params = "-g"
+
+            if file.endswith(".rgen") or file.endswith(".rchit") or file.endswith(".rmiss"):
+               add_params = add_params + " --target-env vulkan1.2"
+
+            res = subprocess.call("%s -V %s -o %s %s" % (glslang_path, input_file, output_file, add_params), shell=True)
+            # res = subprocess.call([glslang_path, '-V', input_file, '-o', output_file, add_params], shell=True)
+            if res != 0:
+                sys.exit()
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/computecloth/cloth.comp b/external/Vulkan/data/shaders/glsl/computecloth/cloth.comp
new file mode 100644
index 0000000000000000000000000000000000000000..8c0ad6820ea4a3b5a888ad5208a5d1e7bf6ec51f
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/computecloth/cloth.comp
@@ -0,0 +1,153 @@
+#version 450
+
+struct Particle {
+	vec4 pos;
+	vec4 vel;
+	vec4 uv;
+	vec4 normal;
+	float pinned;
+};
+
+layout(std430, binding = 0) buffer ParticleIn {
+	Particle particleIn[ ];
+};
+
+layout(std430, binding = 1) buffer ParticleOut {
+	Particle particleOut[ ];
+};
+
+// todo: use shared memory to speed up calculation
+
+layout (local_size_x = 10, local_size_y = 10) in;
+
+layout (binding = 2) uniform UBO 
+{
+	float deltaT;
+	float particleMass;
+	float springStiffness;
+	float damping;
+	float restDistH;
+	float restDistV;
+	float restDistD;
+	float sphereRadius;
+	vec4 spherePos;
+	vec4 gravity;
+	ivec2 particleCount;
+} params;
+
+layout (push_constant) uniform PushConsts {
+	uint calculateNormals;
+} pushConsts;
+
+vec3 springForce(vec3 p0, vec3 p1, float restDist) 
+{
+	vec3 dist = p0 - p1;
+	return normalize(dist) * params.springStiffness * (length(dist) - restDist);
+}
+
+void main() 
+{
+	uvec3 id = gl_GlobalInvocationID; 
+
+	uint index = id.y * params.particleCount.x + id.x;
+	if (index > params.particleCount.x * params.particleCount.y) 
+		return;
+
+	// Pinned?
+	if (particleIn[index].pinned == 1.0) {
+		particleOut[index].pos = particleOut[index].pos;
+		particleOut[index].vel = vec4(0.0);
+		return;
+	}
+
+	// Initial force from gravity
+	vec3 force = params.gravity.xyz * params.particleMass;
+
+	vec3 pos = particleIn[index].pos.xyz;
+	vec3 vel = particleIn[index].vel.xyz;
+
+	// Spring forces from neighboring particles
+	// left
+	if (id.x > 0) {
+		force += springForce(particleIn[index-1].pos.xyz, pos, params.restDistH);
+	} 
+	// right
+	if (id.x < params.particleCount.x - 1) {
+		force += springForce(particleIn[index + 1].pos.xyz, pos, params.restDistH);
+	}
+	// upper
+	if (id.y < params.particleCount.y - 1) {
+		force += springForce(particleIn[index + params.particleCount.x].pos.xyz, pos, params.restDistV);
+	} 
+	// lower
+	if (id.y > 0) {
+		force += springForce(particleIn[index - params.particleCount.x].pos.xyz, pos, params.restDistV);
+	} 
+	// upper-left
+	if ((id.x > 0) && (id.y < params.particleCount.y - 1)) {
+		force += springForce(particleIn[index + params.particleCount.x - 1].pos.xyz, pos, params.restDistD);
+	}
+	// lower-left
+	if ((id.x > 0) && (id.y > 0)) {
+		force += springForce(particleIn[index - params.particleCount.x - 1].pos.xyz, pos, params.restDistD);
+	}
+	// upper-right
+	if ((id.x < params.particleCount.x - 1) && (id.y < params.particleCount.y - 1)) {
+		force += springForce(particleIn[index + params.particleCount.x + 1].pos.xyz, pos, params.restDistD);
+	}
+	// lower-right
+	if ((id.x < params.particleCount.x - 1) && (id.y > 0)) {
+		force += springForce(particleIn[index - params.particleCount.x + 1].pos.xyz, pos, params.restDistD);
+	}
+
+	force += (-params.damping * vel);
+
+	// Integrate
+	vec3 f = force * (1.0 / params.particleMass);
+	particleOut[index].pos = vec4(pos + vel * params.deltaT + 0.5 * f * params.deltaT * params.deltaT, 1.0);
+	particleOut[index].vel = vec4(vel + f * params.deltaT, 0.0);
+
+	// Sphere collision
+	vec3 sphereDist = particleOut[index].pos.xyz - params.spherePos.xyz;
+	if (length(sphereDist) < params.sphereRadius + 0.01) {
+		// If the particle is inside the sphere, push it to the outer radius
+		particleOut[index].pos.xyz = params.spherePos.xyz + normalize(sphereDist) * (params.sphereRadius + 0.01);		
+		// Cancel out velocity
+		particleOut[index].vel = vec4(0.0);
+	}
+
+	// Normals
+	if (pushConsts.calculateNormals == 1) {
+		vec3 normal = vec3(0.0);
+		vec3 a, b, c;
+		if (id.y > 0) {
+			if (id.x > 0) {
+				a = particleIn[index - 1].pos.xyz - pos;
+				b = particleIn[index - params.particleCount.x - 1].pos.xyz - pos;
+				c = particleIn[index - params.particleCount.x].pos.xyz - pos;
+				normal += cross(a,b) + cross(b,c);
+			}
+			if (id.x < params.particleCount.x - 1) {
+				a = particleIn[index - params.particleCount.x].pos.xyz - pos;
+				b = particleIn[index - params.particleCount.x + 1].pos.xyz - pos;
+				c = particleIn[index + 1].pos.xyz - pos;
+				normal += cross(a,b) + cross(b,c);
+			}
+		}
+		if (id.y < params.particleCount.y - 1) {
+			if (id.x > 0) {
+				a = particleIn[index + params.particleCount.x].pos.xyz - pos;
+				b = particleIn[index + params.particleCount.x - 1].pos.xyz - pos;
+				c = particleIn[index - 1].pos.xyz - pos;
+				normal += cross(a,b) + cross(b,c);
+			}
+			if (id.x < params.particleCount.x - 1) {
+				a = particleIn[index + 1].pos.xyz - pos;
+				b = particleIn[index + params.particleCount.x + 1].pos.xyz - pos;
+				c = particleIn[index + params.particleCount.x].pos.xyz - pos;
+				normal += cross(a,b) + cross(b,c);
+			}
+		}
+		particleOut[index].normal = vec4(normalize(normal), 0.0f);
+	}
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/computecloth/cloth.comp.spv b/external/Vulkan/data/shaders/glsl/computecloth/cloth.comp.spv
new file mode 100644
index 0000000000000000000000000000000000000000..959d720626a0bab3df57391de6a422af7d1dae22
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/computecloth/cloth.comp.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/computecloth/cloth.frag b/external/Vulkan/data/shaders/glsl/computecloth/cloth.frag
new file mode 100644
index 0000000000000000000000000000000000000000..e5f30db5d8fb37b0eeb44d01c3a52ac463be0901
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/computecloth/cloth.frag
@@ -0,0 +1,22 @@
+#version 450
+
+layout (binding = 1) uniform sampler2D samplerColor;
+
+layout (location = 0) in vec2 inUV;
+layout (location = 1) in vec3 inNormal;
+layout (location = 2) in vec3 inViewVec;
+layout (location = 3) in vec3 inLightVec;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main () 
+{
+	vec3 color = texture(samplerColor, inUV).rgb;
+	vec3 N = normalize(inNormal);
+	vec3 L = normalize(inLightVec);
+	vec3 V = normalize(inViewVec);
+	vec3 R = reflect(-L, N);
+	vec3 diffuse = max(dot(N, L), 0.15) * vec3(1.0);
+	vec3 specular = pow(max(dot(R, V), 0.0), 8.0) * vec3(0.2);
+	outFragColor = vec4(diffuse * color.rgb + specular, 1.0);	
+}
diff --git a/external/Vulkan/data/shaders/glsl/computecloth/cloth.frag.spv b/external/Vulkan/data/shaders/glsl/computecloth/cloth.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..f7102ce03b34522ae091f554658aa9cc43a0945c
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/computecloth/cloth.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/computecloth/cloth.vert b/external/Vulkan/data/shaders/glsl/computecloth/cloth.vert
new file mode 100644
index 0000000000000000000000000000000000000000..c2cf9b57b710914afdb4caeeda7051af2170efdc
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/computecloth/cloth.vert
@@ -0,0 +1,35 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec2 inUV;
+layout (location = 2) in vec3 inNormal;
+
+layout (location = 0) out vec2 outUV;
+layout (location = 1) out vec3 outNormal;
+layout (location = 2) out vec3 outViewVec;
+layout (location = 3) out vec3 outLightVec;
+
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 modelview;
+	vec4 lightPos;
+} ubo;
+
+out gl_PerVertex
+{
+	vec4 gl_Position;
+};
+
+void main () 
+{
+	outUV = inUV;
+	outNormal = inNormal.xyz;
+	vec4 eyePos = ubo.modelview * vec4(inPos.x, inPos.y, inPos.z, 1.0); 
+	gl_Position = ubo.projection * eyePos;
+	vec4 pos = vec4(inPos, 1.0);
+	vec3 lPos = ubo.lightPos.xyz;
+	outLightVec = lPos - pos.xyz;
+	outViewVec = -pos.xyz;		
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/computecloth/cloth.vert.spv b/external/Vulkan/data/shaders/glsl/computecloth/cloth.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..480fafa154292c44f32f66798b3f148fbc68838f
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/computecloth/cloth.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/computecloth/sphere.frag b/external/Vulkan/data/shaders/glsl/computecloth/sphere.frag
new file mode 100644
index 0000000000000000000000000000000000000000..45be53196c7ed02458a12c138aedcae2aea63646
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/computecloth/sphere.frag
@@ -0,0 +1,19 @@
+#version 450
+
+layout (location = 0) in vec3 inNormal;
+layout (location = 1) in vec3 inViewVec;
+layout (location = 2) in vec3 inLightVec;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main () 
+{
+	vec3 color = vec3(0.5);
+	vec3 N = normalize(inNormal);
+	vec3 L = normalize(inLightVec);
+	vec3 V = normalize(inViewVec);
+	vec3 R = reflect(-L, N);
+	vec3 diffuse = max(dot(N, L), 0.15) * vec3(1.0);
+	vec3 specular = pow(max(dot(R, V), 0.0), 32.0) * vec3(1.0);
+	outFragColor = vec4(diffuse * color.rgb + specular, 1.0);	
+}
diff --git a/external/Vulkan/data/shaders/glsl/computecloth/sphere.frag.spv b/external/Vulkan/data/shaders/glsl/computecloth/sphere.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..7ed0c55ada14348adaf6add369b3fa4b5385f00b
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/computecloth/sphere.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/computecloth/sphere.vert b/external/Vulkan/data/shaders/glsl/computecloth/sphere.vert
new file mode 100644
index 0000000000000000000000000000000000000000..de6b5a6d647c3db1a51c816b8b270a74dd814686
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/computecloth/sphere.vert
@@ -0,0 +1,31 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 2) in vec3 inNormal;
+
+layout (location = 0) out vec3 outNormal;
+layout (location = 1) out vec3 outViewVec;
+layout (location = 2) out vec3 outLightVec;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 modelview;
+	vec4 lightPos;
+} ubo;
+
+out gl_PerVertex
+{
+	vec4 gl_Position;
+};
+
+void main () 
+{
+	vec4 eyePos = ubo.modelview * vec4(inPos.x, inPos.y, inPos.z, 1.0); 
+	gl_Position = ubo.projection * eyePos;
+	vec4 pos = vec4(inPos, 1.0);
+	vec3 lPos = ubo.lightPos.xyz;
+	outLightVec = lPos - pos.xyz;
+	outViewVec = -pos.xyz;
+	outNormal = inNormal;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/computecloth/sphere.vert.spv b/external/Vulkan/data/shaders/glsl/computecloth/sphere.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..923ecf581613c6f77a02049eb1df8b5c7187b18e
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/computecloth/sphere.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/computecullandlod/cull.comp b/external/Vulkan/data/shaders/glsl/computecullandlod/cull.comp
new file mode 100644
index 0000000000000000000000000000000000000000..9566839184bbef8204206194ab17c46d01b08618
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/computecullandlod/cull.comp
@@ -0,0 +1,120 @@
+#version 450
+
+layout (constant_id = 0) const int MAX_LOD_LEVEL = 5;
+
+struct InstanceData 
+{
+	vec3 pos;
+	float scale;
+};
+
+// Binding 0: Instance input data for culling
+layout (binding = 0, std140) buffer Instances 
+{
+   InstanceData instances[ ];
+};
+
+// Same layout as VkDrawIndexedIndirectCommand
+struct IndexedIndirectCommand 
+{
+	uint indexCount;
+	uint instanceCount;
+	uint firstIndex;
+	uint vertexOffset;
+	uint firstInstance;
+};
+
+// Binding 1: Multi draw output
+layout (binding = 1, std430) writeonly buffer IndirectDraws
+{
+	IndexedIndirectCommand indirectDraws[ ];
+};
+
+// Binding 2: Uniform block object with matrices
+layout (binding = 2) uniform UBO 
+{
+	mat4 projection;
+	mat4 modelview;
+	vec4 cameraPos;
+	vec4 frustumPlanes[6];
+} ubo;
+
+// Binding 3: Indirect draw stats
+layout (binding = 3) buffer UBOOut
+{
+	uint drawCount;
+	uint lodCount[MAX_LOD_LEVEL + 1];
+} uboOut;
+
+// Binding 4: level-of-detail information
+struct LOD
+{
+	uint firstIndex;
+	uint indexCount;
+	float distance;
+	float _pad0;
+};
+layout (binding = 4) readonly buffer LODs
+{
+	LOD lods[ ];
+};
+
+layout (local_size_x = 16) in;
+
+bool frustumCheck(vec4 pos, float radius)
+{
+	// Check sphere against frustum planes
+	for (int i = 0; i < 6; i++) 
+	{
+		if (dot(pos, ubo.frustumPlanes[i]) + radius < 0.0)
+		{
+			return false;
+		}
+	}
+	return true;
+}
+
+void main()
+{
+	uint idx = gl_GlobalInvocationID.x + gl_GlobalInvocationID.y * gl_NumWorkGroups.x * gl_WorkGroupSize.x;
+
+	// Clear stats on first invocation
+	if (idx == 0)
+	{
+		atomicExchange(uboOut.drawCount, 0);
+		for (uint i = 0; i < MAX_LOD_LEVEL + 1; i++)
+		{
+			atomicExchange(uboOut.lodCount[i], 0);
+		}
+	}
+
+	vec4 pos = vec4(instances[idx].pos.xyz, 1.0);
+
+	// Check if object is within current viewing frustum
+	if (frustumCheck(pos, 1.0))
+	{
+		indirectDraws[idx].instanceCount = 1;
+		
+		// Increase number of indirect draw counts
+		atomicAdd(uboOut.drawCount, 1);
+
+		// Select appropriate LOD level based on distance to camera
+		uint lodLevel = MAX_LOD_LEVEL;
+		for (uint i = 0; i < MAX_LOD_LEVEL; i++)
+		{
+			if (distance(instances[idx].pos.xyz, ubo.cameraPos.xyz) < lods[i].distance) 
+			{
+				lodLevel = i;
+				break;
+			}
+		}
+		indirectDraws[idx].firstIndex = lods[lodLevel].firstIndex;
+		indirectDraws[idx].indexCount = lods[lodLevel].indexCount;
+		// Update stats
+		atomicAdd(uboOut.lodCount[lodLevel], 1);
+	}
+	else
+	{
+		indirectDraws[idx].instanceCount = 0;
+	}
+}
diff --git a/external/Vulkan/data/shaders/glsl/computecullandlod/cull.comp.spv b/external/Vulkan/data/shaders/glsl/computecullandlod/cull.comp.spv
new file mode 100644
index 0000000000000000000000000000000000000000..2191acc7e0ae28a521807fd0812a741311647f60
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/computecullandlod/cull.comp.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/computecullandlod/indirectdraw.frag b/external/Vulkan/data/shaders/glsl/computecullandlod/indirectdraw.frag
new file mode 100644
index 0000000000000000000000000000000000000000..0b7b29dceceab6bbb5ee1e9e37c8d6b8447c965f
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/computecullandlod/indirectdraw.frag
@@ -0,0 +1,17 @@
+#version 450
+
+layout (location = 0) in vec3 inNormal;
+layout (location = 1) in vec3 inColor;
+layout (location = 2) in vec3 inViewVec;
+layout (location = 3) in vec3 inLightVec;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main()
+{
+	vec3 N = normalize(inNormal);
+	vec3 L = normalize(inLightVec);
+	vec3 ambient = vec3(0.25);
+	vec3 diffuse = vec3(max(dot(N, L), 0.0));
+	outFragColor = vec4((ambient + diffuse) * inColor, 1.0);
+}
diff --git a/external/Vulkan/data/shaders/glsl/computecullandlod/indirectdraw.frag.spv b/external/Vulkan/data/shaders/glsl/computecullandlod/indirectdraw.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..12c1d44eba9cc28cf3f43181f5e95bd74fd1ea05
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/computecullandlod/indirectdraw.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/computecullandlod/indirectdraw.vert b/external/Vulkan/data/shaders/glsl/computecullandlod/indirectdraw.vert
new file mode 100644
index 0000000000000000000000000000000000000000..4087e1bb2ed22d0c78d15a4d6eb109cb0f85f352
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/computecullandlod/indirectdraw.vert
@@ -0,0 +1,42 @@
+#version 450
+
+// Vertex attributes
+layout (location = 0) in vec4 inPos;
+layout (location = 1) in vec3 inNormal;
+layout (location = 2) in vec3 inColor;
+
+// Instanced attributes
+layout (location = 4) in vec3 instancePos;
+layout (location = 5) in float instanceScale;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 modelview;
+} ubo;
+
+layout (location = 0) out vec3 outNormal;
+layout (location = 1) out vec3 outColor;
+layout (location = 2) out vec3 outViewVec;
+layout (location = 3) out vec3 outLightVec;
+
+out gl_PerVertex
+{
+	vec4 gl_Position;
+};
+
+void main() 
+{
+	outColor = inColor;
+		
+	outNormal = inNormal;
+	
+	vec4 pos = vec4((inPos.xyz * instanceScale) + instancePos, 1.0);
+
+	gl_Position = ubo.projection * ubo.modelview * pos;
+	
+	vec4 wPos = ubo.modelview * vec4(pos.xyz, 1.0); 
+	vec4 lPos = vec4(0.0, 10.0, 50.0, 1.0);
+	outLightVec = lPos.xyz - pos.xyz;
+	outViewVec = -pos.xyz;	
+}
diff --git a/external/Vulkan/data/shaders/glsl/computecullandlod/indirectdraw.vert.spv b/external/Vulkan/data/shaders/glsl/computecullandlod/indirectdraw.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..bcbf69270ca22321dd3f26fad3fb25950ee2e4be
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/computecullandlod/indirectdraw.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/computeheadless/headless.comp b/external/Vulkan/data/shaders/glsl/computeheadless/headless.comp
new file mode 100644
index 0000000000000000000000000000000000000000..28be9a6cbf71002c40dbeea9e777966b8bd8e7c8
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/computeheadless/headless.comp
@@ -0,0 +1,32 @@
+#version 450
+
+layout(binding = 0) buffer Pos {
+   uint values[ ];
+};
+
+layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+
+layout (constant_id = 0) const uint BUFFER_ELEMENTS = 32;
+
+uint fibonacci(uint n) {
+	if(n <= 1){
+		return n;
+	}
+	uint curr = 1;
+	uint prev = 1;
+	for(uint i = 2; i < n; ++i) {
+		uint temp = curr;
+		curr += prev;
+		prev = temp;
+	}
+	return curr;
+}
+
+void main() 
+{
+	uint index = gl_GlobalInvocationID.x;
+	if (index >= BUFFER_ELEMENTS) 
+		return;	
+	values[index] = fibonacci(values[index]);
+}
+
diff --git a/external/Vulkan/data/shaders/glsl/computeheadless/headless.comp.spv b/external/Vulkan/data/shaders/glsl/computeheadless/headless.comp.spv
new file mode 100644
index 0000000000000000000000000000000000000000..54da283a06e9f945f333dccf5670ac43dec304fd
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/computeheadless/headless.comp.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/computenbody/particle.frag b/external/Vulkan/data/shaders/glsl/computenbody/particle.frag
new file mode 100644
index 0000000000000000000000000000000000000000..638c840256da2b81cf186f01f39fe6bf6b9efe76
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/computenbody/particle.frag
@@ -0,0 +1,14 @@
+#version 450
+
+layout (binding = 0) uniform sampler2D samplerColorMap;
+layout (binding = 1) uniform sampler2D samplerGradientRamp;
+
+layout (location = 0) in float inGradientPos;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main () 
+{
+	vec3 color = texture(samplerGradientRamp, vec2(inGradientPos, 0.0)).rgb;
+	outFragColor.rgb = texture(samplerColorMap, gl_PointCoord).rgb * color;
+}
diff --git a/external/Vulkan/data/shaders/glsl/computenbody/particle.frag.spv b/external/Vulkan/data/shaders/glsl/computenbody/particle.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..b9f752f573e03554814a4ea1b6094e8bdc6f37d5
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/computenbody/particle.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/computenbody/particle.vert b/external/Vulkan/data/shaders/glsl/computenbody/particle.vert
new file mode 100644
index 0000000000000000000000000000000000000000..a42963bfba54db83dc5ae69df2c26c210f0a6feb
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/computenbody/particle.vert
@@ -0,0 +1,32 @@
+#version 450
+
+layout (location = 0) in vec4 inPos;
+layout (location = 1) in vec4 inVel;
+
+layout (location = 0) out float outGradientPos;
+
+layout (binding = 2) uniform UBO 
+{
+	mat4 projection;
+	mat4 modelview;
+	vec2 screendim;
+} ubo;
+
+out gl_PerVertex
+{
+	vec4 gl_Position;
+	float gl_PointSize;
+};
+
+void main () 
+{
+	const float spriteSize = 0.005 * inPos.w; // Point size influenced by mass (stored in inPos.w);
+
+	vec4 eyePos = ubo.modelview * vec4(inPos.x, inPos.y, inPos.z, 1.0); 
+	vec4 projectedCorner = ubo.projection * vec4(0.5 * spriteSize, 0.5 * spriteSize, eyePos.z, eyePos.w);
+	gl_PointSize = clamp(ubo.screendim.x * projectedCorner.x / projectedCorner.w, 1.0, 128.0);
+	
+	gl_Position = ubo.projection * eyePos;
+
+	outGradientPos = inVel.w;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/computenbody/particle.vert.spv b/external/Vulkan/data/shaders/glsl/computenbody/particle.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..a89bd16f1731c2e6645693f30846b919ecb6dc8b
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/computenbody/particle.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/computenbody/particle_calculate.comp b/external/Vulkan/data/shaders/glsl/computenbody/particle_calculate.comp
new file mode 100644
index 0000000000000000000000000000000000000000..7d46a3395d86da8a46a20c43a037d303a1db2416
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/computenbody/particle_calculate.comp
@@ -0,0 +1,74 @@
+
+#version 450
+
+struct Particle
+{
+	vec4 pos;
+	vec4 vel;
+};
+
+// Binding 0 : Position storage buffer
+layout(std140, binding = 0) buffer Pos 
+{
+   Particle particles[ ];
+};
+
+layout (local_size_x = 256) in;
+
+layout (binding = 1) uniform UBO 
+{
+	float deltaT;
+	int particleCount;
+} ubo;
+
+layout (constant_id = 0) const int SHARED_DATA_SIZE = 512;
+layout (constant_id = 1) const float GRAVITY = 0.002;
+layout (constant_id = 2) const float POWER = 0.75;
+layout (constant_id = 3) const float SOFTEN = 0.0075;
+
+// Share data between computer shader invocations to speed up caluclations
+shared vec4 sharedData[SHARED_DATA_SIZE];
+
+void main() 
+{
+	// Current SSBO index
+	uint index = gl_GlobalInvocationID.x;
+	if (index >= ubo.particleCount) 
+		return;	
+
+	vec4 position = particles[index].pos;
+	vec4 velocity = particles[index].vel;
+	vec4 acceleration = vec4(0.0);
+
+	for (int i = 0; i < ubo.particleCount; i += SHARED_DATA_SIZE)
+	{
+		if (i + gl_LocalInvocationID.x < ubo.particleCount)
+		{
+			sharedData[gl_LocalInvocationID.x] = particles[i + gl_LocalInvocationID.x].pos;
+		}
+		else
+		{
+			sharedData[gl_LocalInvocationID.x] = vec4(0.0);
+		}
+
+		memoryBarrierShared();
+		barrier();
+
+		for (int j = 0; j < gl_WorkGroupSize.x; j++)
+		{
+			vec4 other = sharedData[j];
+			vec3 len = other.xyz - position.xyz;
+			acceleration.xyz += GRAVITY * len * other.w / pow(dot(len, len) + SOFTEN, POWER);
+		}
+
+		memoryBarrierShared();
+		barrier();
+	}
+
+	particles[index].vel.xyz += ubo.deltaT * acceleration.xyz;
+
+	// Gradient texture position
+	particles[index].vel.w += 0.1 * ubo.deltaT;
+	if (particles[index].vel.w > 1.0)
+		particles[index].vel.w -= 1.0;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/computenbody/particle_calculate.comp.spv b/external/Vulkan/data/shaders/glsl/computenbody/particle_calculate.comp.spv
new file mode 100644
index 0000000000000000000000000000000000000000..63d6804506e0b683182b5339f8f7da3983d9ea2f
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/computenbody/particle_calculate.comp.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/computenbody/particle_integrate.comp b/external/Vulkan/data/shaders/glsl/computenbody/particle_integrate.comp
new file mode 100644
index 0000000000000000000000000000000000000000..4ac2e07a91b6ff9dc7f741355cb452945f54af12
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/computenbody/particle_integrate.comp
@@ -0,0 +1,30 @@
+#version 450
+
+struct Particle
+{
+	vec4 pos;
+	vec4 vel;
+};
+
+// Binding 0 : Position storage buffer
+layout(std140, binding = 0) buffer Pos 
+{
+   Particle particles[ ];
+};
+
+layout (local_size_x = 256) in;
+
+layout (binding = 1) uniform UBO 
+{
+	float deltaT;
+	int particleCount;
+} ubo;
+
+void main() 
+{
+	int index = int(gl_GlobalInvocationID);
+	vec4 position = particles[index].pos;
+	vec4 velocity = particles[index].vel;
+	position += ubo.deltaT * velocity;
+	particles[index].pos = position;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/computenbody/particle_integrate.comp.spv b/external/Vulkan/data/shaders/glsl/computenbody/particle_integrate.comp.spv
new file mode 100644
index 0000000000000000000000000000000000000000..477a12a924a7e06ec529d14109858220e967eefd
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/computenbody/particle_integrate.comp.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/computeparticles/particle.comp b/external/Vulkan/data/shaders/glsl/computeparticles/particle.comp
new file mode 100644
index 0000000000000000000000000000000000000000..36a7be18997f658d63a08fdf5ed3de13260a33dd
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/computeparticles/particle.comp
@@ -0,0 +1,76 @@
+#version 450
+
+struct Particle
+{
+	vec2 pos;
+	vec2 vel;
+	vec4 gradientPos;
+};
+
+// Binding 0 : Position storage buffer
+layout(std140, binding = 0) buffer Pos 
+{
+   Particle particles[ ];
+};
+
+layout (local_size_x = 256) in;
+
+layout (binding = 1) uniform UBO 
+{
+	float deltaT;
+	float destX;
+	float destY;
+	int particleCount;
+} ubo;
+
+vec2 attraction(vec2 pos, vec2 attractPos) 
+{
+    vec2 delta = attractPos - pos;
+	const float damp = 0.5;
+    float dDampedDot = dot(delta, delta) + damp;
+    float invDist = 1.0f / sqrt(dDampedDot);
+    float invDistCubed = invDist*invDist*invDist;
+    return delta * invDistCubed * 0.0035;
+}
+
+vec2 repulsion(vec2 pos, vec2 attractPos)
+{
+	vec2 delta = attractPos - pos;
+	float targetDistance = sqrt(dot(delta, delta));
+	return delta * (1.0 / (targetDistance * targetDistance * targetDistance)) * -0.000035;
+}
+
+void main() 
+{
+    // Current SSBO index
+    uint index = gl_GlobalInvocationID.x;
+	// Don't try to write beyond particle count
+    if (index >= ubo.particleCount) 
+		return;	
+
+    // Read position and velocity
+    vec2 vVel = particles[index].vel.xy;
+    vec2 vPos = particles[index].pos.xy;
+
+    vec2 destPos = vec2(ubo.destX, ubo.destY);
+
+    vec2 delta = destPos - vPos;
+    float targetDistance = sqrt(dot(delta, delta));
+    vVel += repulsion(vPos, destPos.xy) * 0.05;
+
+    // Move by velocity
+    vPos += vVel * ubo.deltaT;
+
+    // collide with boundary
+    if ((vPos.x < -1.0) || (vPos.x > 1.0) || (vPos.y < -1.0) || (vPos.y > 1.0))
+    	vVel = (-vVel * 0.1) + attraction(vPos, destPos) * 12;
+    else
+    	particles[index].pos.xy = vPos;
+
+    // Write back
+    particles[index].vel.xy = vVel;
+	particles[index].gradientPos.x += 0.02 * ubo.deltaT;
+	if (particles[index].gradientPos.x > 1.0)
+		particles[index].gradientPos.x -= 1.0;
+}
+
diff --git a/external/Vulkan/data/shaders/glsl/computeparticles/particle.comp.spv b/external/Vulkan/data/shaders/glsl/computeparticles/particle.comp.spv
new file mode 100644
index 0000000000000000000000000000000000000000..977b98b116e085c5337811c53801342909bea373
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/computeparticles/particle.comp.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/computeparticles/particle.frag b/external/Vulkan/data/shaders/glsl/computeparticles/particle.frag
new file mode 100644
index 0000000000000000000000000000000000000000..c7dc1c8521f131ac2cb521e987661effe610087d
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/computeparticles/particle.frag
@@ -0,0 +1,15 @@
+#version 450
+
+layout (binding = 0) uniform sampler2D samplerColorMap;
+layout (binding = 1) uniform sampler2D samplerGradientRamp;
+
+layout (location = 0) in vec4 inColor;
+layout (location = 1) in float inGradientPos;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main () 
+{
+	vec3 color = texture(samplerGradientRamp, vec2(inGradientPos, 0.0)).rgb;
+	outFragColor.rgb = texture(samplerColorMap, gl_PointCoord).rgb * color;
+}
diff --git a/external/Vulkan/data/shaders/glsl/computeparticles/particle.frag.spv b/external/Vulkan/data/shaders/glsl/computeparticles/particle.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..d3d9c746ada4a671ec08514184e561ec9f0a9f30
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/computeparticles/particle.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/computeparticles/particle.vert b/external/Vulkan/data/shaders/glsl/computeparticles/particle.vert
new file mode 100644
index 0000000000000000000000000000000000000000..5ff7a2c3e83b5b1c765e18ecb94deab529052166
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/computeparticles/particle.vert
@@ -0,0 +1,21 @@
+#version 450
+
+layout (location = 0) in vec2 inPos;
+layout (location = 1) in vec4 inGradientPos;
+
+layout (location = 0) out vec4 outColor;
+layout (location = 1) out float outGradientPos;
+
+out gl_PerVertex
+{
+	vec4 gl_Position;
+	float gl_PointSize;
+};
+
+void main () 
+{
+  gl_PointSize = 8.0;
+  outColor = vec4(0.035);
+  outGradientPos = inGradientPos.x;
+  gl_Position = vec4(inPos.xy, 1.0, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/computeparticles/particle.vert.spv b/external/Vulkan/data/shaders/glsl/computeparticles/particle.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..6a36778755cc896f2a0c806cb60b1d3efae38a54
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/computeparticles/particle.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/computeraytracing/raytracing.comp b/external/Vulkan/data/shaders/glsl/computeraytracing/raytracing.comp
new file mode 100644
index 0000000000000000000000000000000000000000..051b80bd552ab097c3b32f8de572e5ddc065cbf3
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/computeraytracing/raytracing.comp
@@ -0,0 +1,254 @@
+// Shader is looseley based on the ray tracing coding session by Inigo Quilez (www.iquilezles.org)
+
+#version 450
+
+layout (local_size_x = 16, local_size_y = 16) in;
+layout (binding = 0, rgba8) uniform writeonly image2D resultImage;
+
+#define EPSILON 0.0001
+#define MAXLEN 1000.0
+#define SHADOW 0.5
+#define RAYBOUNCES 2
+#define REFLECTIONS true
+#define REFLECTIONSTRENGTH 0.4
+#define REFLECTIONFALLOFF 0.5
+
+struct Camera 
+{
+	vec3 pos;   
+	vec3 lookat;
+	float fov; 
+};
+
+layout (binding = 1) uniform UBO 
+{
+	vec3 lightPos;
+	float aspectRatio;
+	vec4 fogColor;
+	Camera camera;
+	mat4 rotMat;
+} ubo;
+
+struct Sphere 
+{
+	vec3 pos;
+	float radius;
+	vec3 diffuse;
+	float specular;
+	int id;
+};
+
+struct Plane
+{
+	vec3 normal;
+	float distance;
+	vec3 diffuse;
+	float specular;
+	int id;
+};
+
+layout (std140, binding = 2) buffer Spheres
+{
+	Sphere spheres[ ];
+};
+
+layout (std140, binding = 3) buffer Planes
+{
+	Plane planes[ ];
+};
+
+void reflectRay(inout vec3 rayD, in vec3 mormal)
+{
+	rayD = rayD + 2.0 * -dot(mormal, rayD) * mormal;
+}
+
+// Lighting =========================================================
+
+float lightDiffuse(vec3 normal, vec3 lightDir) 
+{
+	return clamp(dot(normal, lightDir), 0.1, 1.0);
+}
+
+float lightSpecular(vec3 normal, vec3 lightDir, float specularFactor)
+{
+	vec3 viewVec = normalize(ubo.camera.pos);
+	vec3 halfVec = normalize(lightDir + viewVec);
+	return pow(clamp(dot(normal, halfVec), 0.0, 1.0), specularFactor);
+}
+
+// Sphere ===========================================================
+
+float sphereIntersect(in vec3 rayO, in vec3 rayD, in Sphere sphere)
+{
+	vec3 oc = rayO - sphere.pos;
+	float b = 2.0 * dot(oc, rayD);
+	float c = dot(oc, oc) - sphere.radius*sphere.radius;
+	float h = b*b - 4.0*c;
+	if (h < 0.0) 
+	{
+		return -1.0;
+	}
+	float t = (-b - sqrt(h)) / 2.0;
+
+	return t;
+}
+
+vec3 sphereNormal(in vec3 pos, in Sphere sphere)
+{
+	return (pos - sphere.pos) / sphere.radius;
+}
+
+// Plane ===========================================================
+
+float planeIntersect(vec3 rayO, vec3 rayD, Plane plane)
+{
+	float d = dot(rayD, plane.normal);
+
+	if (d == 0.0)
+		return 0.0;
+
+	float t = -(plane.distance + dot(rayO, plane.normal)) / d;
+
+	if (t < 0.0)
+		return 0.0;
+
+	return t;
+}
+
+	
+int intersect(in vec3 rayO, in vec3 rayD, inout float resT)
+{
+	int id = -1;
+
+	for (int i = 0; i < spheres.length(); i++)
+	{
+		float tSphere = sphereIntersect(rayO, rayD, spheres[i]);
+		if ((tSphere > EPSILON) && (tSphere < resT))
+		{
+			id = spheres[i].id;
+			resT = tSphere;
+		}
+	}	
+
+	for (int i = 0; i < planes.length(); i++)
+	{
+		float tplane = planeIntersect(rayO, rayD, planes[i]);
+		if ((tplane > EPSILON) && (tplane < resT))
+		{
+			id = planes[i].id;
+			resT = tplane;
+		}	
+	}
+	
+	return id;
+}
+
+float calcShadow(in vec3 rayO, in vec3 rayD, in int objectId, inout float t)
+{
+	for (int i = 0; i < spheres.length(); i++)
+	{
+		if (spheres[i].id == objectId)
+			continue;
+		float tSphere = sphereIntersect(rayO, rayD, spheres[i]);
+		if ((tSphere > EPSILON) && (tSphere < t))
+		{
+			t = tSphere;
+			return SHADOW;
+		}
+	}		
+	return 1.0;
+}
+
+vec3 fog(in float t, in vec3 color)
+{
+	return mix(color, ubo.fogColor.rgb, clamp(sqrt(t*t)/20.0, 0.0, 1.0));
+}
+
+vec3 renderScene(inout vec3 rayO, inout vec3 rayD, inout int id)
+{
+	vec3 color = vec3(0.0);
+	float t = MAXLEN;
+
+	// Get intersected object ID
+	int objectID = intersect(rayO, rayD, t);
+	
+	if (objectID == -1)
+	{
+		return color;
+	}
+	
+	vec3 pos = rayO + t * rayD;
+	vec3 lightVec = normalize(ubo.lightPos - pos);				
+	vec3 normal;
+
+	// Planes
+
+	// Spheres
+
+	for (int i = 0; i < planes.length(); i++)
+	{
+		if (objectID == planes[i].id)
+		{
+			normal = planes[i].normal;
+			float diffuse = lightDiffuse(normal, lightVec);
+			float specular = lightSpecular(normal, lightVec, planes[i].specular);
+			color = diffuse * planes[i].diffuse + specular;	
+		}
+	}
+
+	for (int i = 0; i < spheres.length(); i++)
+	{
+		if (objectID == spheres[i].id)
+		{
+			normal = sphereNormal(pos, spheres[i]);	
+			float diffuse = lightDiffuse(normal, lightVec);
+			float specular = lightSpecular(normal, lightVec, spheres[i].specular);
+			color = diffuse * spheres[i].diffuse + specular;	
+		}
+	}
+
+	if (id == -1)
+		return color;
+
+	id = objectID;
+
+	// Shadows
+	t = length(ubo.lightPos - pos);
+	color *= calcShadow(pos, lightVec, id, t);
+	
+	// Fog
+	color = fog(t, color);	
+	
+	// Reflect ray for next render pass
+	reflectRay(rayD, normal);
+	rayO = pos;	
+	
+	return color;
+}
+
+void main()
+{
+	ivec2 dim = imageSize(resultImage);
+	vec2 uv = vec2(gl_GlobalInvocationID.xy) / dim;
+
+	vec3 rayO = ubo.camera.pos;
+	vec3 rayD = normalize(vec3((-1.0 + 2.0 * uv) * vec2(ubo.aspectRatio, 1.0), -1.0));
+		
+	// Basic color path
+	int id = 0;
+	vec3 finalColor = renderScene(rayO, rayD, id);
+	
+	// Reflection
+	if (REFLECTIONS)
+	{
+		float reflectionStrength = REFLECTIONSTRENGTH;
+		for (int i = 0; i < RAYBOUNCES; i++)
+		{
+			vec3 reflectionColor = renderScene(rayO, rayD, id);
+			finalColor = (1.0 - reflectionStrength) * finalColor + reflectionStrength * mix(reflectionColor, finalColor, 1.0 - reflectionStrength);			
+			reflectionStrength *= REFLECTIONFALLOFF;
+		}
+	}
+			
+	imageStore(resultImage, ivec2(gl_GlobalInvocationID.xy), vec4(finalColor, 0.0));
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/computeraytracing/raytracing.comp.spv b/external/Vulkan/data/shaders/glsl/computeraytracing/raytracing.comp.spv
new file mode 100644
index 0000000000000000000000000000000000000000..ee022372759e95510875c0ef79a19012925023bd
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/computeraytracing/raytracing.comp.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/computeraytracing/texture.frag b/external/Vulkan/data/shaders/glsl/computeraytracing/texture.frag
new file mode 100644
index 0000000000000000000000000000000000000000..ecd21f25bc3b877f5cff314910ea9880bfe338c0
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/computeraytracing/texture.frag
@@ -0,0 +1,12 @@
+#version 450
+
+layout (binding = 0) uniform sampler2D samplerColor;
+
+layout (location = 0) in vec2 inUV;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+  outFragColor = texture(samplerColor, vec2(inUV.s, 1.0 - inUV.t));
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/computeraytracing/texture.frag.spv b/external/Vulkan/data/shaders/glsl/computeraytracing/texture.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..81f30b2ff5e4d7fc9ad9a87064f817c1da118399
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/computeraytracing/texture.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/computeraytracing/texture.vert b/external/Vulkan/data/shaders/glsl/computeraytracing/texture.vert
new file mode 100644
index 0000000000000000000000000000000000000000..969b8f7fcce3df252b33a8e5c94491c845db196e
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/computeraytracing/texture.vert
@@ -0,0 +1,14 @@
+#version 450
+
+layout (location = 0) out vec2 outUV;
+
+out gl_PerVertex 
+{
+	vec4 gl_Position;
+};
+
+void main() 
+{
+	outUV = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
+	gl_Position = vec4(outUV * 2.0f + -1.0f, 0.0f, 1.0f);
+}
diff --git a/external/Vulkan/data/shaders/glsl/computeraytracing/texture.vert.spv b/external/Vulkan/data/shaders/glsl/computeraytracing/texture.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..3a8d9076ad5f79a23fa655751e703dde9c349ecd
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/computeraytracing/texture.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/computeshader/edgedetect.comp b/external/Vulkan/data/shaders/glsl/computeshader/edgedetect.comp
new file mode 100644
index 0000000000000000000000000000000000000000..5c70178d2ec16377062bb65eb504538d3ec08241
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/computeshader/edgedetect.comp
@@ -0,0 +1,44 @@
+#version 450
+
+layout (local_size_x = 16, local_size_y = 16) in;
+layout (binding = 0, rgba8) uniform readonly image2D inputImage;
+layout (binding = 1, rgba8) uniform image2D resultImage;
+
+float conv(in float[9] kernel, in float[9] data, in float denom, in float offset) 
+{
+   float res = 0.0;
+   for (int i=0; i<9; ++i) 
+   {
+      res += kernel[i] * data[i];
+   }
+   return clamp(res/denom + offset, 0.0, 1.0);
+}
+
+struct ImageData 
+{
+	float avg[9];
+} imageData;	
+
+void main()
+{	
+	// Fetch neighbouring texels
+	int n = -1;
+	for (int i=-1; i<2; ++i) 
+	{   
+		for(int j=-1; j<2; ++j) 
+		{    
+			n++;    
+			vec3 rgb = imageLoad(inputImage, ivec2(gl_GlobalInvocationID.x + i, gl_GlobalInvocationID.y + j)).rgb;
+			imageData.avg[n] = (rgb.r + rgb.g + rgb.b) / 3.0;
+		}
+	}
+
+	float[9] kernel;
+	kernel[0] = -1.0/8.0; kernel[1] = -1.0/8.0; kernel[2] = -1.0/8.0;
+	kernel[3] = -1.0/8.0; kernel[4] =  1.0;     kernel[5] = -1.0/8.0;
+	kernel[6] = -1.0/8.0; kernel[7] = -1.0/8.0; kernel[8] = -1.0/8.0;
+									
+	vec4 res = vec4(vec3(conv(kernel, imageData.avg, 0.1, 0.0)), 1.0);
+
+	imageStore(resultImage, ivec2(gl_GlobalInvocationID.xy), res);
+}
diff --git a/external/Vulkan/data/shaders/glsl/computeshader/edgedetect.comp.spv b/external/Vulkan/data/shaders/glsl/computeshader/edgedetect.comp.spv
new file mode 100644
index 0000000000000000000000000000000000000000..66a32b3603f3486313dde730307bee691cd79cb9
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/computeshader/edgedetect.comp.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/computeshader/emboss.comp b/external/Vulkan/data/shaders/glsl/computeshader/emboss.comp
new file mode 100644
index 0000000000000000000000000000000000000000..4402d7cd3b12704839cd6f4774f63fa17c840d80
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/computeshader/emboss.comp
@@ -0,0 +1,44 @@
+#version 450
+
+layout (local_size_x = 16, local_size_y = 16) in;
+layout (binding = 0, rgba8) uniform readonly image2D inputImage;
+layout (binding = 1, rgba8) uniform image2D resultImage;
+
+float conv(in float[9] kernel, in float[9] data, in float denom, in float offset) 
+{
+   float res = 0.0;
+   for (int i=0; i<9; ++i) 
+   {
+      res += kernel[i] * data[i];
+   }
+   return clamp(res/denom + offset, 0.0, 1.0);
+}
+
+struct ImageData 
+{
+	float avg[9];
+} imageData;	
+
+void main()
+{	
+	// Fetch neighbouring texels
+	int n = -1;
+	for (int i=-1; i<2; ++i) 
+	{   
+		for(int j=-1; j<2; ++j) 
+		{    
+			n++;    
+			vec3 rgb = imageLoad(inputImage, ivec2(gl_GlobalInvocationID.x + i, gl_GlobalInvocationID.y + j)).rgb;
+			imageData.avg[n] = (rgb.r + rgb.g + rgb.b) / 3.0;
+		}
+	}
+
+	float[9] kernel;
+	kernel[0] = -1.0; kernel[1] =  0.0; kernel[2] =  0.0;
+	kernel[3] = 0.0; kernel[4] = -1.0; kernel[5] =  0.0;
+	kernel[6] = 0.0; kernel[7] =  0.0; kernel[8] = 2.0;
+									
+	vec4 res = vec4(vec3(conv(kernel, imageData.avg, 1.0, 0.50)), 1.0);
+
+	imageStore(resultImage, ivec2(gl_GlobalInvocationID.xy), res);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/computeshader/emboss.comp.spv b/external/Vulkan/data/shaders/glsl/computeshader/emboss.comp.spv
new file mode 100644
index 0000000000000000000000000000000000000000..88cc8d796f0dc2c49b339febec6c3fd441fd0b8f
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/computeshader/emboss.comp.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/computeshader/sharpen.comp b/external/Vulkan/data/shaders/glsl/computeshader/sharpen.comp
new file mode 100644
index 0000000000000000000000000000000000000000..f94c8e9f5a119ac57023d49106e9819277f00dd2
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/computeshader/sharpen.comp
@@ -0,0 +1,53 @@
+#version 450
+
+layout (local_size_x = 16, local_size_y = 16) in;
+layout (binding = 0, rgba8) uniform readonly image2D inputImage;
+layout (binding = 1, rgba8) uniform image2D resultImage;
+
+float conv(in float[9] kernel, in float[9] data, in float denom, in float offset) 
+{
+   float res = 0.0;
+   for (int i=0; i<9; ++i) 
+   {
+      res += kernel[i] * data[i];
+   }
+   return clamp(res/denom + offset, 0.0, 1.0);
+}
+
+struct ImageData 
+{
+	float r[9];
+	float g[9];
+	float b[9];
+} imageData;	
+
+void main()
+{
+	
+	// Fetch neighbouring texels
+	int n = -1;
+	for (int i=-1; i<2; ++i) 
+	{   
+		for(int j=-1; j<2; ++j) 
+		{    
+			n++;    
+			vec3 rgb = imageLoad(inputImage, ivec2(gl_GlobalInvocationID.x + i, gl_GlobalInvocationID.y + j)).rgb;
+			imageData.r[n] = rgb.r;
+			imageData.g[n] = rgb.g;
+			imageData.b[n] = rgb.b;
+		}
+	}
+
+	float[9] kernel;
+	kernel[0] = -1.0; kernel[1] = -1.0; kernel[2] = -1.0;
+	kernel[3] = -1.0; kernel[4] =  9.0; kernel[5] = -1.0;
+	kernel[6] = -1.0; kernel[7] = -1.0; kernel[8] = -1.0;
+								
+	vec4 res = vec4(
+		conv(kernel, imageData.r, 1.0, 0.0), 
+		conv(kernel, imageData.g, 1.0, 0.0), 
+		conv(kernel, imageData.b, 1.0, 0.0),
+		1.0);
+
+	imageStore(resultImage, ivec2(gl_GlobalInvocationID.xy), res);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/computeshader/sharpen.comp.spv b/external/Vulkan/data/shaders/glsl/computeshader/sharpen.comp.spv
new file mode 100644
index 0000000000000000000000000000000000000000..f023b76875c0d361657d164bac4477d3c204f94c
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/computeshader/sharpen.comp.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/computeshader/texture.frag b/external/Vulkan/data/shaders/glsl/computeshader/texture.frag
new file mode 100644
index 0000000000000000000000000000000000000000..108e89b0a97e56b90915b5f48bd1075ed4027f82
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/computeshader/texture.frag
@@ -0,0 +1,12 @@
+#version 450
+
+layout (binding = 1) uniform sampler2D samplerColor;
+
+layout (location = 0) in vec2 inUV;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+  outFragColor = texture(samplerColor, inUV);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/computeshader/texture.frag.spv b/external/Vulkan/data/shaders/glsl/computeshader/texture.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..401a74346f9008f15c38c323804b7f38b18a3a12
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/computeshader/texture.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/computeshader/texture.vert b/external/Vulkan/data/shaders/glsl/computeshader/texture.vert
new file mode 100644
index 0000000000000000000000000000000000000000..7548607299f6e8287dde1c5e153ea70abfb9eb82
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/computeshader/texture.vert
@@ -0,0 +1,23 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec2 inUV;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 model;
+} ubo;
+
+layout (location = 0) out vec2 outUV;
+
+out gl_PerVertex
+{
+	vec4 gl_Position;
+};
+
+void main() 
+{
+	outUV = inUV;
+	gl_Position = ubo.projection * ubo.model * vec4(inPos.xyz, 1.0);
+}
diff --git a/external/Vulkan/data/shaders/glsl/computeshader/texture.vert.spv b/external/Vulkan/data/shaders/glsl/computeshader/texture.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..f72d60147ec9b2bfba962b7945be4cf9e84988d8
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/computeshader/texture.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/conditionalrender/model.frag b/external/Vulkan/data/shaders/glsl/conditionalrender/model.frag
new file mode 100644
index 0000000000000000000000000000000000000000..aab33e1d5f35578c5aeae246fd75d4df5810ff0a
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/conditionalrender/model.frag
@@ -0,0 +1,20 @@
+#version 450
+
+layout (location = 0) in vec3 inNormal;
+layout (location = 1) in vec3 inColor;
+layout (location = 2) in vec3 inViewVec;
+layout (location = 3) in vec3 inLightVec;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+	vec3 N = normalize(inNormal);
+	vec3 L = normalize(inLightVec);
+	vec3 V = normalize(inViewVec);
+	vec3 R = reflect(-L, N);
+	vec3 ambient = vec3(0.1);
+	vec3 diffuse = max(dot(N, L), 0.0) * vec3(1.0);
+	vec3 specular = pow(max(dot(R, V), 0.0), 16.0) * vec3(0.75);
+	outFragColor = vec4((ambient + diffuse) * inColor.rgb + specular, 1.0);		
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/conditionalrender/model.frag.spv b/external/Vulkan/data/shaders/glsl/conditionalrender/model.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..33607eb75d5f3c2f287f4f7af3842d156a3aba54
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/conditionalrender/model.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/conditionalrender/model.vert b/external/Vulkan/data/shaders/glsl/conditionalrender/model.vert
new file mode 100644
index 0000000000000000000000000000000000000000..9be6be0c1d25bb13131d0ef69486721220cab11a
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/conditionalrender/model.vert
@@ -0,0 +1,44 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec3 inNormal;
+layout (location = 2) in vec3 inColor;
+
+layout (set = 0, binding = 0) uniform UBO {
+	mat4 projection;
+	mat4 view;
+	mat4 model;
+} ubo;
+
+layout (set = 1, binding = 0) uniform Node {
+	mat4 matrix;
+} node;
+
+layout(push_constant) uniform PushBlock {
+	vec4 baseColorFactor;
+} material;
+
+layout (location = 0) out vec3 outNormal;
+layout (location = 1) out vec3 outColor;
+layout (location = 2) out vec3 outViewVec;
+layout (location = 3) out vec3 outLightVec;
+
+out gl_PerVertex
+{
+	vec4 gl_Position;
+};
+
+void main() 
+{
+	outNormal = inNormal;
+	outColor = material.baseColorFactor.rgb;
+	vec4 pos = vec4(inPos, 1.0);
+	gl_Position = ubo.projection * ubo.view * ubo.model * node.matrix * pos;
+
+	outNormal = mat3(ubo.view * ubo.model * node.matrix) * inNormal;
+
+	vec4 localpos = ubo.view * ubo.model * node.matrix * pos;
+	vec3 lightPos = vec3(10.0f, -10.0f, 10.0f);
+	outLightVec = lightPos.xyz - localpos.xyz;
+	outViewVec = -localpos.xyz;		
+}
diff --git a/external/Vulkan/data/shaders/glsl/conditionalrender/model.vert.spv b/external/Vulkan/data/shaders/glsl/conditionalrender/model.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..4ab97f4c3d4da3e4e524d01f79a9c70d21777ab3
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/conditionalrender/model.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/conservativeraster/fullscreen.frag b/external/Vulkan/data/shaders/glsl/conservativeraster/fullscreen.frag
new file mode 100644
index 0000000000000000000000000000000000000000..e5c1e4971d3e93a852e52a286dc9e7667c478f9e
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/conservativeraster/fullscreen.frag
@@ -0,0 +1,11 @@
+#version 450
+
+layout (binding = 1) uniform sampler2D samplerColor;
+
+layout (location = 0) in vec2 inUV;
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+	outFragColor =  texture(samplerColor, inUV);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/conservativeraster/fullscreen.frag.spv b/external/Vulkan/data/shaders/glsl/conservativeraster/fullscreen.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..401a74346f9008f15c38c323804b7f38b18a3a12
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/conservativeraster/fullscreen.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/conservativeraster/fullscreen.vert b/external/Vulkan/data/shaders/glsl/conservativeraster/fullscreen.vert
new file mode 100644
index 0000000000000000000000000000000000000000..a5d60d0e750f260d3c034db033e15f18fb6a89d8
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/conservativeraster/fullscreen.vert
@@ -0,0 +1,14 @@
+#version 450
+
+layout (location = 0) out vec2 outUV;
+
+out gl_PerVertex
+{
+	vec4 gl_Position;
+};
+
+void main() 
+{
+	outUV = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
+	gl_Position = vec4(outUV * 2.0f - 1.0f, 0.0f, 1.0f);
+}
diff --git a/external/Vulkan/data/shaders/glsl/conservativeraster/fullscreen.vert.spv b/external/Vulkan/data/shaders/glsl/conservativeraster/fullscreen.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..4040f608bd837727d1051a6539a15338d11242ae
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/conservativeraster/fullscreen.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/conservativeraster/triangle.frag b/external/Vulkan/data/shaders/glsl/conservativeraster/triangle.frag
new file mode 100644
index 0000000000000000000000000000000000000000..8af8ccc0a4d4842ed844b64d064855444a37dab7
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/conservativeraster/triangle.frag
@@ -0,0 +1,10 @@
+#version 450
+
+layout (location = 0) in vec3 inColor;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+	outFragColor.rgb = inColor;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/conservativeraster/triangle.frag.spv b/external/Vulkan/data/shaders/glsl/conservativeraster/triangle.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..798349bacc26825d4dba4a6daa642f3aa384b48d
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/conservativeraster/triangle.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/conservativeraster/triangle.vert b/external/Vulkan/data/shaders/glsl/conservativeraster/triangle.vert
new file mode 100644
index 0000000000000000000000000000000000000000..a8dc00641b33bf46d82d4ad43aa44cd8788543b0
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/conservativeraster/triangle.vert
@@ -0,0 +1,23 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec3 inColor;
+
+layout (binding = 0) uniform UBO
+{
+	mat4 projection;
+	mat4 model;
+} ubo;
+
+layout (location = 0) out vec3 outColor;
+
+out gl_PerVertex
+{
+	vec4 gl_Position;
+};
+
+void main() 
+{
+	outColor = inColor;
+	gl_Position = ubo.projection * ubo.model * vec4(inPos, 1.0);
+}
diff --git a/external/Vulkan/data/shaders/glsl/conservativeraster/triangle.vert.spv b/external/Vulkan/data/shaders/glsl/conservativeraster/triangle.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..d664f43c102b52e086b58702c32fcc9a079e3568
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/conservativeraster/triangle.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/conservativeraster/triangleoverlay.frag b/external/Vulkan/data/shaders/glsl/conservativeraster/triangleoverlay.frag
new file mode 100644
index 0000000000000000000000000000000000000000..1c4fa467f21d36b7b7b3f3c7a87a1048b8a1fa68
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/conservativeraster/triangleoverlay.frag
@@ -0,0 +1,8 @@
+#version 450
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+	outFragColor.rgb = vec3(1.0, 1.0, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/conservativeraster/triangleoverlay.frag.spv b/external/Vulkan/data/shaders/glsl/conservativeraster/triangleoverlay.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..70e4b0655afc84dd2eb022c04218131ad1f82156
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/conservativeraster/triangleoverlay.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/debugmarker/colorpass.frag b/external/Vulkan/data/shaders/glsl/debugmarker/colorpass.frag
new file mode 100644
index 0000000000000000000000000000000000000000..8af8ccc0a4d4842ed844b64d064855444a37dab7
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/debugmarker/colorpass.frag
@@ -0,0 +1,10 @@
+#version 450
+
+layout (location = 0) in vec3 inColor;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+	outFragColor.rgb = inColor;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/debugmarker/colorpass.frag.spv b/external/Vulkan/data/shaders/glsl/debugmarker/colorpass.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..798349bacc26825d4dba4a6daa642f3aa384b48d
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/debugmarker/colorpass.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/debugmarker/colorpass.vert b/external/Vulkan/data/shaders/glsl/debugmarker/colorpass.vert
new file mode 100644
index 0000000000000000000000000000000000000000..02aca66ae45a247a8b247dcc04c31ce8086832b4
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/debugmarker/colorpass.vert
@@ -0,0 +1,25 @@
+#version 450
+
+layout (location = 0) in vec4 inPos;
+layout (location = 3) in vec3 inColor;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 model;
+} ubo;
+
+layout (location = 0) out vec3 outColor;
+
+out gl_PerVertex
+{
+	vec4 gl_Position;
+};
+
+void main() 
+{
+	{
+		outColor = inColor;
+	}
+	gl_Position = ubo.projection * ubo.model * inPos;
+}
diff --git a/external/Vulkan/data/shaders/glsl/debugmarker/colorpass.vert.spv b/external/Vulkan/data/shaders/glsl/debugmarker/colorpass.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..8d61379508c55f5800c9230ea927d1967bab1292
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/debugmarker/colorpass.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/debugmarker/postprocess.frag b/external/Vulkan/data/shaders/glsl/debugmarker/postprocess.frag
new file mode 100644
index 0000000000000000000000000000000000000000..e2a11fd33def3912ed4e0f789061d6d1c7490064
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/debugmarker/postprocess.frag
@@ -0,0 +1,39 @@
+#version 450
+
+layout (binding = 1) uniform sampler2D samplerColor;
+
+layout (location = 0) in vec2 inUV;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+	// Single pass gauss blur
+
+	const vec2 texOffset = vec2(0.01, 0.01);
+
+	vec2 tc0 = inUV + vec2(-texOffset.s, -texOffset.t);
+	vec2 tc1 = inUV + vec2(         0.0, -texOffset.t);
+	vec2 tc2 = inUV + vec2(+texOffset.s, -texOffset.t);
+	vec2 tc3 = inUV + vec2(-texOffset.s,          0.0);
+	vec2 tc4 = inUV + vec2(         0.0,          0.0);
+	vec2 tc5 = inUV + vec2(+texOffset.s,          0.0);
+	vec2 tc6 = inUV + vec2(-texOffset.s, +texOffset.t);
+	vec2 tc7 = inUV + vec2(         0.0, +texOffset.t);
+	vec2 tc8 = inUV + vec2(+texOffset.s, +texOffset.t);
+
+	vec4 col0 = texture(samplerColor, tc0);
+	vec4 col1 = texture(samplerColor, tc1);
+	vec4 col2 = texture(samplerColor, tc2);
+	vec4 col3 = texture(samplerColor, tc3);
+	vec4 col4 = texture(samplerColor, tc4);
+	vec4 col5 = texture(samplerColor, tc5);
+	vec4 col6 = texture(samplerColor, tc6);
+	vec4 col7 = texture(samplerColor, tc7);
+	vec4 col8 = texture(samplerColor, tc8);
+
+	vec4 sum = (1.0 * col0 + 2.0 * col1 + 1.0 * col2 + 
+			  2.0 * col3 + 4.0 * col4 + 2.0 * col5 +
+			  1.0 * col6 + 2.0 * col7 + 1.0 * col8) / 16.0; 
+	outFragColor = vec4(sum.rgb, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/debugmarker/postprocess.frag.spv b/external/Vulkan/data/shaders/glsl/debugmarker/postprocess.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..eb0a125e32ef4e79ae2631644454dc16ebb588a4
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/debugmarker/postprocess.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/debugmarker/postprocess.vert b/external/Vulkan/data/shaders/glsl/debugmarker/postprocess.vert
new file mode 100644
index 0000000000000000000000000000000000000000..fa4ac1f9c3e9588df676dc3e2b47522b8f4db0bc
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/debugmarker/postprocess.vert
@@ -0,0 +1,14 @@
+#version 450
+
+layout (location = 0) out vec2 outUV;
+
+out gl_PerVertex 
+{
+    vec4 gl_Position;   
+};
+
+void main() 
+{
+	outUV = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
+	gl_Position = vec4(outUV * vec2(2.0f, 2.0f) + vec2(-1.0f, -1.0f), 0.0f, 1.0f);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/debugmarker/postprocess.vert.spv b/external/Vulkan/data/shaders/glsl/debugmarker/postprocess.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..57be5820e675e566746bfea7f6a160e71adf2de6
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/debugmarker/postprocess.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/debugmarker/toon.frag b/external/Vulkan/data/shaders/glsl/debugmarker/toon.frag
new file mode 100644
index 0000000000000000000000000000000000000000..622fc86e71feae7f2f9de0ef6dfefe9b523c61ed
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/debugmarker/toon.frag
@@ -0,0 +1,36 @@
+#version 450
+
+layout (binding = 1) uniform sampler2D samplerColorMap;
+
+layout (location = 0) in vec3 inNormal;
+layout (location = 1) in vec3 inColor;
+layout (location = 2) in vec2 inUV;
+layout (location = 3) in vec3 inViewVec;
+layout (location = 4) in vec3 inLightVec;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+	// Desaturate color
+    vec3 color = vec3(mix(inColor, vec3(dot(vec3(0.2126,0.7152,0.0722), inColor)), 0.65));	
+
+	// High ambient colors because mesh materials are pretty dark
+	vec3 ambient = color * vec3(1.0);
+	vec3 N = normalize(inNormal);
+	vec3 L = normalize(inLightVec);
+	vec3 V = normalize(inViewVec);
+	vec3 R = reflect(-L, N);
+	vec3 diffuse = max(dot(N, L), 0.0) * color;
+	vec3 specular = pow(max(dot(R, V), 0.0), 16.0) * vec3(0.75);
+	outFragColor = vec4(ambient + diffuse * 1.75 + specular, 1.0);		
+	
+	float intensity = dot(N,L);
+	float shade = 1.0;
+	shade = intensity < 0.5 ? 0.75 : shade;
+	shade = intensity < 0.35 ? 0.6 : shade;
+	shade = intensity < 0.25 ? 0.5 : shade;
+	shade = intensity < 0.1 ? 0.25 : shade;
+
+	outFragColor.rgb = inColor * 3.0 * shade;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/debugmarker/toon.frag.spv b/external/Vulkan/data/shaders/glsl/debugmarker/toon.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..62a22effa48db0957bf69007bdfffd422f39feb9
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/debugmarker/toon.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/debugmarker/toon.vert b/external/Vulkan/data/shaders/glsl/debugmarker/toon.vert
new file mode 100644
index 0000000000000000000000000000000000000000..d91395076115f9a327ff1d2dc748bdf95362697e
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/debugmarker/toon.vert
@@ -0,0 +1,38 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec3 inNormal;
+layout (location = 2) in vec2 inUV;
+layout (location = 3) in vec3 inColor;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 model;
+	vec4 lightPos;
+} ubo;
+
+layout (location = 0) out vec3 outNormal;
+layout (location = 1) out vec3 outColor;
+layout (location = 2) out vec2 outUV;
+layout (location = 3) out vec3 outViewVec;
+layout (location = 4) out vec3 outLightVec;
+
+out gl_PerVertex
+{
+	vec4 gl_Position;
+};
+
+void main() 
+{
+	outNormal = inNormal;
+	outColor = inColor;
+	outUV = inUV;
+	gl_Position = ubo.projection * ubo.model * vec4(inPos.xyz, 1.0);
+	
+	vec4 pos = ubo.model * vec4(inPos, 1.0);
+	outNormal = mat3(ubo.model) * inNormal;
+	vec3 lPos = mat3(ubo.model) * ubo.lightPos.xyz;
+	outLightVec = lPos - pos.xyz;
+	outViewVec = -pos.xyz;		
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/debugmarker/toon.vert.spv b/external/Vulkan/data/shaders/glsl/debugmarker/toon.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..82758e686cfd5a35ead56c59b5827cba399c672f
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/debugmarker/toon.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/deferred/deferred.frag b/external/Vulkan/data/shaders/glsl/deferred/deferred.frag
new file mode 100644
index 0000000000000000000000000000000000000000..a628aa51a78745767181d7ddbd158d106cf5dc5a
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/deferred/deferred.frag
@@ -0,0 +1,94 @@
+#version 450
+
+layout (binding = 1) uniform sampler2D samplerposition;
+layout (binding = 2) uniform sampler2D samplerNormal;
+layout (binding = 3) uniform sampler2D samplerAlbedo;
+
+layout (location = 0) in vec2 inUV;
+
+layout (location = 0) out vec4 outFragcolor;
+
+struct Light {
+	vec4 position;
+	vec3 color;
+	float radius;
+};
+
+layout (binding = 4) uniform UBO 
+{
+	Light lights[6];
+	vec4 viewPos;
+	int displayDebugTarget;
+} ubo;
+
+void main() 
+{
+	// Get G-Buffer values
+	vec3 fragPos = texture(samplerposition, inUV).rgb;
+	vec3 normal = texture(samplerNormal, inUV).rgb;
+	vec4 albedo = texture(samplerAlbedo, inUV);
+	
+	// Debug display
+	if (ubo.displayDebugTarget > 0) {
+		switch (ubo.displayDebugTarget) {
+			case 1: 
+				outFragcolor.rgb = fragPos;
+				break;
+			case 2: 
+				outFragcolor.rgb = normal;
+				break;
+			case 3: 
+				outFragcolor.rgb = albedo.rgb;
+				break;
+			case 4: 
+				outFragcolor.rgb = albedo.aaa;
+				break;
+		}		
+		outFragcolor.a = 1.0;
+		return;
+	}
+
+	// Render-target composition
+
+	#define lightCount 6
+	#define ambient 0.0
+	
+	// Ambient part
+	vec3 fragcolor  = albedo.rgb * ambient;
+	
+	for(int i = 0; i < lightCount; ++i)
+	{
+		// Vector to light
+		vec3 L = ubo.lights[i].position.xyz - fragPos;
+		// Distance from light to fragment position
+		float dist = length(L);
+
+		// Viewer to fragment
+		vec3 V = ubo.viewPos.xyz - fragPos;
+		V = normalize(V);
+		
+		//if(dist < ubo.lights[i].radius)
+		{
+			// Light to fragment
+			L = normalize(L);
+
+			// Attenuation
+			float atten = ubo.lights[i].radius / (pow(dist, 2.0) + 1.0);
+
+			// Diffuse part
+			vec3 N = normalize(normal);
+			float NdotL = max(0.0, dot(N, L));
+			vec3 diff = ubo.lights[i].color * albedo.rgb * NdotL * atten;
+
+			// Specular part
+			// Specular map values are stored in alpha of albedo mrt
+			vec3 R = reflect(-L, N);
+			float NdotR = max(0.0, dot(R, V));
+			vec3 spec = ubo.lights[i].color * albedo.a * pow(NdotR, 16.0) * atten;
+
+			fragcolor += diff + spec;	
+		}	
+	}    	
+   
+  outFragcolor = vec4(fragcolor, 1.0);	
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/deferred/deferred.frag.spv b/external/Vulkan/data/shaders/glsl/deferred/deferred.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..c80c3af1a1d77b379441fc3906bd60653eafdac8
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/deferred/deferred.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/deferred/deferred.vert b/external/Vulkan/data/shaders/glsl/deferred/deferred.vert
new file mode 100644
index 0000000000000000000000000000000000000000..3a42f3c54083149098d3dcb9fcc22eb529727b15
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/deferred/deferred.vert
@@ -0,0 +1,9 @@
+#version 450
+
+layout (location = 0) out vec2 outUV;
+
+void main() 
+{
+	outUV = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
+	gl_Position = vec4(outUV * 2.0f - 1.0f, 0.0f, 1.0f);
+}
diff --git a/external/Vulkan/data/shaders/glsl/deferred/deferred.vert.spv b/external/Vulkan/data/shaders/glsl/deferred/deferred.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..5683fcb316e0ff06e54a6f73a0c33ca38f7e937c
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/deferred/deferred.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/deferred/mrt.frag b/external/Vulkan/data/shaders/glsl/deferred/mrt.frag
new file mode 100644
index 0000000000000000000000000000000000000000..1aa99da8b5f7b626e0cfb53c93643825206cd981
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/deferred/mrt.frag
@@ -0,0 +1,29 @@
+#version 450
+
+layout (binding = 1) uniform sampler2D samplerColor;
+layout (binding = 2) uniform sampler2D samplerNormalMap;
+
+layout (location = 0) in vec3 inNormal;
+layout (location = 1) in vec2 inUV;
+layout (location = 2) in vec3 inColor;
+layout (location = 3) in vec3 inWorldPos;
+layout (location = 4) in vec3 inTangent;
+
+layout (location = 0) out vec4 outPosition;
+layout (location = 1) out vec4 outNormal;
+layout (location = 2) out vec4 outAlbedo;
+
+void main() 
+{
+	outPosition = vec4(inWorldPos, 1.0);
+
+	// Calculate normal in tangent space
+	vec3 N = normalize(inNormal);
+	vec3 T = normalize(inTangent);
+	vec3 B = cross(N, T);
+	mat3 TBN = mat3(T, B, N);
+	vec3 tnorm = TBN * normalize(texture(samplerNormalMap, inUV).xyz * 2.0 - vec3(1.0));
+	outNormal = vec4(tnorm, 1.0);
+
+	outAlbedo = texture(samplerColor, inUV);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/deferred/mrt.frag.spv b/external/Vulkan/data/shaders/glsl/deferred/mrt.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..98c20d13b3f1c0443537e8190b8add9bdf29dfc3
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/deferred/mrt.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/deferred/mrt.vert b/external/Vulkan/data/shaders/glsl/deferred/mrt.vert
new file mode 100644
index 0000000000000000000000000000000000000000..5c564fb4dc316fc4825dd39a5d29d418b401693e
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/deferred/mrt.vert
@@ -0,0 +1,41 @@
+#version 450
+
+layout (location = 0) in vec4 inPos;
+layout (location = 1) in vec2 inUV;
+layout (location = 2) in vec3 inColor;
+layout (location = 3) in vec3 inNormal;
+layout (location = 4) in vec3 inTangent;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 model;
+	mat4 view;
+	vec4 instancePos[3];
+} ubo;
+
+layout (location = 0) out vec3 outNormal;
+layout (location = 1) out vec2 outUV;
+layout (location = 2) out vec3 outColor;
+layout (location = 3) out vec3 outWorldPos;
+layout (location = 4) out vec3 outTangent;
+
+void main() 
+{
+	vec4 tmpPos = inPos + ubo.instancePos[gl_InstanceIndex];
+
+	gl_Position = ubo.projection * ubo.view * ubo.model * tmpPos;
+	
+	outUV = inUV;
+
+	// Vertex position in world space
+	outWorldPos = vec3(ubo.model * tmpPos);
+	
+	// Normal in world space
+	mat3 mNormal = transpose(inverse(mat3(ubo.model)));
+	outNormal = mNormal * normalize(inNormal);	
+	outTangent = mNormal * normalize(inTangent);
+	
+	// Currently just vertex color
+	outColor = inColor;
+}
diff --git a/external/Vulkan/data/shaders/glsl/deferred/mrt.vert.spv b/external/Vulkan/data/shaders/glsl/deferred/mrt.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..d3946c0a53e5da6d3b45e245fdf8c3b7c5d3f8dd
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/deferred/mrt.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/deferredmultisampling/deferred.frag b/external/Vulkan/data/shaders/glsl/deferredmultisampling/deferred.frag
new file mode 100644
index 0000000000000000000000000000000000000000..3c882889acaec6f1cb24341e24cb6a6c14388c30
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/deferredmultisampling/deferred.frag
@@ -0,0 +1,120 @@
+#version 450
+
+layout (binding = 1) uniform sampler2DMS samplerPosition;
+layout (binding = 2) uniform sampler2DMS samplerNormal;
+layout (binding = 3) uniform sampler2DMS samplerAlbedo;
+
+layout (location = 0) in vec2 inUV;
+
+layout (location = 0) out vec4 outFragcolor;
+
+struct Light {
+	vec4 position;
+	vec3 color;
+	float radius;
+};
+
+layout (binding = 4) uniform UBO 
+{
+	Light lights[6];
+	vec4 viewPos;
+	int debugDisplayTarget;
+} ubo;
+
+layout (constant_id = 0) const int NUM_SAMPLES = 8;
+
+#define NUM_LIGHTS 6
+
+// Manual resolve for MSAA samples 
+vec4 resolve(sampler2DMS tex, ivec2 uv)
+{
+	vec4 result = vec4(0.0);	   
+	for (int i = 0; i < NUM_SAMPLES; i++)
+	{
+		vec4 val = texelFetch(tex, uv, i); 
+		result += val;
+	}    
+	// Average resolved samples
+	return result / float(NUM_SAMPLES);
+}
+
+vec3 calculateLighting(vec3 pos, vec3 normal, vec4 albedo)
+{
+	vec3 result = vec3(0.0);
+
+	for(int i = 0; i < NUM_LIGHTS; ++i)
+	{
+		// Vector to light
+		vec3 L = ubo.lights[i].position.xyz - pos;
+		// Distance from light to fragment position
+		float dist = length(L);
+
+		// Viewer to fragment
+		vec3 V = ubo.viewPos.xyz - pos;
+		V = normalize(V);
+		
+		// Light to fragment
+		L = normalize(L);
+
+		// Attenuation
+		float atten = ubo.lights[i].radius / (pow(dist, 2.0) + 1.0);
+
+		// Diffuse part
+		vec3 N = normalize(normal);
+		float NdotL = max(0.0, dot(N, L));
+		vec3 diff = ubo.lights[i].color * albedo.rgb * NdotL * atten;
+
+		// Specular part
+		vec3 R = reflect(-L, N);
+		float NdotR = max(0.0, dot(R, V));
+		vec3 spec = ubo.lights[i].color * albedo.a * pow(NdotR, 8.0) * atten;
+
+		result += diff + spec;	
+	}
+	return result;
+}
+
+void main() 
+{
+	ivec2 attDim = textureSize(samplerPosition);
+	ivec2 UV = ivec2(inUV * attDim);
+	
+	// Debug display
+	if (ubo.debugDisplayTarget > 0) {
+		switch (ubo.debugDisplayTarget) {
+			case 1: 
+				outFragcolor.rgb = texelFetch(samplerPosition, UV, 0).rgb;
+				break;
+			case 2: 
+				outFragcolor.rgb = texelFetch(samplerNormal, UV, 0).rgb;
+				break;
+			case 3: 
+				outFragcolor.rgb = texelFetch(samplerAlbedo, UV, 0).rgb;
+				break;
+			case 4: 
+				outFragcolor.rgb = texelFetch(samplerAlbedo, UV, 0).aaa;
+				break;
+		}		
+		outFragcolor.a = 1.0;
+		return;
+	}
+
+	#define ambient 0.15
+
+	// Ambient part
+	vec4 alb = resolve(samplerAlbedo, UV);
+	vec3 fragColor = vec3(0.0);
+	
+	// Calualte lighting for every MSAA sample
+	for (int i = 0; i < NUM_SAMPLES; i++)
+	{ 
+		vec3 pos = texelFetch(samplerPosition, UV, i).rgb;
+		vec3 normal = texelFetch(samplerNormal, UV, i).rgb;
+		vec4 albedo = texelFetch(samplerAlbedo, UV, i);
+		fragColor += calculateLighting(pos, normal, albedo);
+	}
+
+	fragColor = (alb.rgb * ambient) + fragColor / float(NUM_SAMPLES);
+   
+	outFragcolor = vec4(fragColor, 1.0);	
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/deferredmultisampling/deferred.frag.spv b/external/Vulkan/data/shaders/glsl/deferredmultisampling/deferred.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..2388689ce43f9df5cbabf3d85d523e583476be0d
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/deferredmultisampling/deferred.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/deferredmultisampling/deferred.vert b/external/Vulkan/data/shaders/glsl/deferredmultisampling/deferred.vert
new file mode 100644
index 0000000000000000000000000000000000000000..3a42f3c54083149098d3dcb9fcc22eb529727b15
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/deferredmultisampling/deferred.vert
@@ -0,0 +1,9 @@
+#version 450
+
+layout (location = 0) out vec2 outUV;
+
+void main() 
+{
+	outUV = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
+	gl_Position = vec4(outUV * 2.0f - 1.0f, 0.0f, 1.0f);
+}
diff --git a/external/Vulkan/data/shaders/glsl/deferredmultisampling/deferred.vert.spv b/external/Vulkan/data/shaders/glsl/deferredmultisampling/deferred.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..5683fcb316e0ff06e54a6f73a0c33ca38f7e937c
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/deferredmultisampling/deferred.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/deferredmultisampling/mrt.frag b/external/Vulkan/data/shaders/glsl/deferredmultisampling/mrt.frag
new file mode 100644
index 0000000000000000000000000000000000000000..1aa99da8b5f7b626e0cfb53c93643825206cd981
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/deferredmultisampling/mrt.frag
@@ -0,0 +1,29 @@
+#version 450
+
+layout (binding = 1) uniform sampler2D samplerColor;
+layout (binding = 2) uniform sampler2D samplerNormalMap;
+
+layout (location = 0) in vec3 inNormal;
+layout (location = 1) in vec2 inUV;
+layout (location = 2) in vec3 inColor;
+layout (location = 3) in vec3 inWorldPos;
+layout (location = 4) in vec3 inTangent;
+
+layout (location = 0) out vec4 outPosition;
+layout (location = 1) out vec4 outNormal;
+layout (location = 2) out vec4 outAlbedo;
+
+void main() 
+{
+	outPosition = vec4(inWorldPos, 1.0);
+
+	// Calculate normal in tangent space
+	vec3 N = normalize(inNormal);
+	vec3 T = normalize(inTangent);
+	vec3 B = cross(N, T);
+	mat3 TBN = mat3(T, B, N);
+	vec3 tnorm = TBN * normalize(texture(samplerNormalMap, inUV).xyz * 2.0 - vec3(1.0));
+	outNormal = vec4(tnorm, 1.0);
+
+	outAlbedo = texture(samplerColor, inUV);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/deferredmultisampling/mrt.frag.spv b/external/Vulkan/data/shaders/glsl/deferredmultisampling/mrt.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..98c20d13b3f1c0443537e8190b8add9bdf29dfc3
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/deferredmultisampling/mrt.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/deferredmultisampling/mrt.vert b/external/Vulkan/data/shaders/glsl/deferredmultisampling/mrt.vert
new file mode 100644
index 0000000000000000000000000000000000000000..4e8bda2b3093d080d33dfaf99112f0423e7be354
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/deferredmultisampling/mrt.vert
@@ -0,0 +1,46 @@
+#version 450
+
+layout (location = 0) in vec4 inPos;
+layout (location = 1) in vec2 inUV;
+layout (location = 2) in vec3 inColor;
+layout (location = 3) in vec3 inNormal;
+layout (location = 4) in vec3 inTangent;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 model;
+	mat4 view;
+	vec4 instancePos[3];
+} ubo;
+
+layout (location = 0) out vec3 outNormal;
+layout (location = 1) out vec2 outUV;
+layout (location = 2) out vec3 outColor;
+layout (location = 3) out vec3 outWorldPos;
+layout (location = 4) out vec3 outTangent;
+
+out gl_PerVertex
+{
+	vec4 gl_Position;
+};
+
+void main() 
+{
+	vec4 tmpPos = vec4(inPos.xyz, 1.0) + ubo.instancePos[gl_InstanceIndex];
+
+	gl_Position = ubo.projection * ubo.view * ubo.model * tmpPos;
+	
+	outUV = inUV;
+
+	// Vertex position in world space
+	outWorldPos = vec3(ubo.model * tmpPos);
+	
+	// Normal in world space
+	mat3 mNormal = transpose(inverse(mat3(ubo.model)));
+	outNormal = mNormal * normalize(inNormal);	
+	outTangent = mNormal * normalize(inTangent);
+	
+	// Currently just vertex color
+	outColor = inColor;
+}
diff --git a/external/Vulkan/data/shaders/glsl/deferredmultisampling/mrt.vert.spv b/external/Vulkan/data/shaders/glsl/deferredmultisampling/mrt.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..8edad7b720689aad57b67bbecf638540b9cb2a71
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/deferredmultisampling/mrt.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/deferredshadows/deferred.frag b/external/Vulkan/data/shaders/glsl/deferredshadows/deferred.frag
new file mode 100644
index 0000000000000000000000000000000000000000..c5848f96a8acb6e6d52c81ddd58f4f171daa889a
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/deferredshadows/deferred.frag
@@ -0,0 +1,168 @@
+#version 450
+
+layout (binding = 1) uniform sampler2D samplerposition;
+layout (binding = 2) uniform sampler2D samplerNormal;
+layout (binding = 3) uniform sampler2D samplerAlbedo;
+layout (binding = 5) uniform sampler2DArray samplerShadowMap;
+
+layout (location = 0) in vec2 inUV;
+
+layout (location = 0) out vec4 outFragColor;
+
+#define LIGHT_COUNT 3
+#define SHADOW_FACTOR 0.25
+#define AMBIENT_LIGHT 0.1
+#define USE_PCF
+
+struct Light 
+{
+	vec4 position;
+	vec4 target;
+	vec4 color;
+	mat4 viewMatrix;
+};
+
+layout (binding = 4) uniform UBO 
+{
+	vec4 viewPos;
+	Light lights[LIGHT_COUNT];
+	int useShadows;
+	int debugDisplayTarget;
+} ubo;
+
+float textureProj(vec4 P, float layer, vec2 offset)
+{
+	float shadow = 1.0;
+	vec4 shadowCoord = P / P.w;
+	shadowCoord.st = shadowCoord.st * 0.5 + 0.5;
+	
+	if (shadowCoord.z > -1.0 && shadowCoord.z < 1.0) 
+	{
+		float dist = texture(samplerShadowMap, vec3(shadowCoord.st + offset, layer)).r;
+		if (shadowCoord.w > 0.0 && dist < shadowCoord.z) 
+		{
+			shadow = SHADOW_FACTOR;
+		}
+	}
+	return shadow;
+}
+
+float filterPCF(vec4 sc, float layer)
+{
+	ivec2 texDim = textureSize(samplerShadowMap, 0).xy;
+	float scale = 1.5;
+	float dx = scale * 1.0 / float(texDim.x);
+	float dy = scale * 1.0 / float(texDim.y);
+
+	float shadowFactor = 0.0;
+	int count = 0;
+	int range = 1;
+	
+	for (int x = -range; x <= range; x++)
+	{
+		for (int y = -range; y <= range; y++)
+		{
+			shadowFactor += textureProj(sc, layer, vec2(dx*x, dy*y));
+			count++;
+		}
+	
+	}
+	return shadowFactor / count;
+}
+
+vec3 shadow(vec3 fragcolor, vec3 fragpos) {
+	for(int i = 0; i < LIGHT_COUNT; ++i)
+	{
+		vec4 shadowClip	= ubo.lights[i].viewMatrix * vec4(fragpos, 1.0);
+
+		float shadowFactor;
+		#ifdef USE_PCF
+			shadowFactor= filterPCF(shadowClip, i);
+		#else
+			shadowFactor = textureProj(shadowClip, i, vec2(0.0));
+		#endif
+
+		fragcolor *= shadowFactor;
+	}
+	return fragcolor;
+}
+
+void main() 
+{
+	// Get G-Buffer values
+	vec3 fragPos = texture(samplerposition, inUV).rgb;
+	vec3 normal = texture(samplerNormal, inUV).rgb;
+	vec4 albedo = texture(samplerAlbedo, inUV);
+
+	// Debug display
+	if (ubo.debugDisplayTarget > 0) {
+		switch (ubo.debugDisplayTarget) {
+			case 1: 
+				outFragColor.rgb = shadow(vec3(1.0), fragPos).rgb;
+				break;
+			case 2: 
+				outFragColor.rgb = fragPos;
+				break;
+			case 3: 
+				outFragColor.rgb = normal;
+				break;
+			case 4: 
+				outFragColor.rgb = albedo.rgb;
+				break;
+			case 5: 
+				outFragColor.rgb = albedo.aaa;
+				break;
+		}		
+		outFragColor.a = 1.0;
+		return;
+	}
+
+	// Ambient part
+	vec3 fragcolor  = albedo.rgb * AMBIENT_LIGHT;
+
+	vec3 N = normalize(normal);
+		
+	for(int i = 0; i < LIGHT_COUNT; ++i)
+	{
+		// Vector to light
+		vec3 L = ubo.lights[i].position.xyz - fragPos;
+		// Distance from light to fragment position
+		float dist = length(L);
+		L = normalize(L);
+
+		// Viewer to fragment
+		vec3 V = ubo.viewPos.xyz - fragPos;
+		V = normalize(V);
+
+		float lightCosInnerAngle = cos(radians(15.0));
+		float lightCosOuterAngle = cos(radians(25.0));
+		float lightRange = 100.0;
+
+		// Direction vector from source to target
+		vec3 dir = normalize(ubo.lights[i].position.xyz - ubo.lights[i].target.xyz);
+
+		// Dual cone spot light with smooth transition between inner and outer angle
+		float cosDir = dot(L, dir);
+		float spotEffect = smoothstep(lightCosOuterAngle, lightCosInnerAngle, cosDir);
+		float heightAttenuation = smoothstep(lightRange, 0.0f, dist);
+
+		// Diffuse lighting
+		float NdotL = max(0.0, dot(N, L));
+		vec3 diff = vec3(NdotL);
+
+		// Specular lighting
+		vec3 R = reflect(-L, N);
+		float NdotR = max(0.0, dot(R, V));
+		vec3 spec = vec3(pow(NdotR, 16.0) * albedo.a * 2.5);
+
+		fragcolor += vec3((diff + spec) * spotEffect * heightAttenuation) * ubo.lights[i].color.rgb * albedo.rgb;
+	}    	
+
+	// Shadow calculations in a separate pass
+	if (ubo.useShadows > 0)
+	{
+		fragcolor = shadow(fragcolor, fragPos);
+	}
+
+	outFragColor = vec4(fragcolor, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/deferredshadows/deferred.frag.spv b/external/Vulkan/data/shaders/glsl/deferredshadows/deferred.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..5c431ab8dea54c474ed86c2cfe23f9a5ae34ea12
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/deferredshadows/deferred.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/deferredshadows/deferred.vert b/external/Vulkan/data/shaders/glsl/deferredshadows/deferred.vert
new file mode 100644
index 0000000000000000000000000000000000000000..047d67a4d3c41338f4e594e372a0e52fd41910b5
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/deferredshadows/deferred.vert
@@ -0,0 +1,9 @@
+#version 450
+
+layout (location = 0) out vec2 outUV;
+
+void main() 
+{
+	outUV = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
+	gl_Position = vec4(outUV * 2.0f - 1.0f, 0.0f, 1.0f);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/deferredshadows/deferred.vert.spv b/external/Vulkan/data/shaders/glsl/deferredshadows/deferred.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..5683fcb316e0ff06e54a6f73a0c33ca38f7e937c
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/deferredshadows/deferred.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/deferredshadows/geom.spv b/external/Vulkan/data/shaders/glsl/deferredshadows/geom.spv
new file mode 100644
index 0000000000000000000000000000000000000000..0f7a014907b210fe7ad5afaf8db9028e610bdb6c
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/deferredshadows/geom.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/deferredshadows/mrt.frag b/external/Vulkan/data/shaders/glsl/deferredshadows/mrt.frag
new file mode 100644
index 0000000000000000000000000000000000000000..1aa99da8b5f7b626e0cfb53c93643825206cd981
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/deferredshadows/mrt.frag
@@ -0,0 +1,29 @@
+#version 450
+
+layout (binding = 1) uniform sampler2D samplerColor;
+layout (binding = 2) uniform sampler2D samplerNormalMap;
+
+layout (location = 0) in vec3 inNormal;
+layout (location = 1) in vec2 inUV;
+layout (location = 2) in vec3 inColor;
+layout (location = 3) in vec3 inWorldPos;
+layout (location = 4) in vec3 inTangent;
+
+layout (location = 0) out vec4 outPosition;
+layout (location = 1) out vec4 outNormal;
+layout (location = 2) out vec4 outAlbedo;
+
+void main() 
+{
+	outPosition = vec4(inWorldPos, 1.0);
+
+	// Calculate normal in tangent space
+	vec3 N = normalize(inNormal);
+	vec3 T = normalize(inTangent);
+	vec3 B = cross(N, T);
+	mat3 TBN = mat3(T, B, N);
+	vec3 tnorm = TBN * normalize(texture(samplerNormalMap, inUV).xyz * 2.0 - vec3(1.0));
+	outNormal = vec4(tnorm, 1.0);
+
+	outAlbedo = texture(samplerColor, inUV);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/deferredshadows/mrt.frag.spv b/external/Vulkan/data/shaders/glsl/deferredshadows/mrt.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..98c20d13b3f1c0443537e8190b8add9bdf29dfc3
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/deferredshadows/mrt.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/deferredshadows/mrt.vert b/external/Vulkan/data/shaders/glsl/deferredshadows/mrt.vert
new file mode 100644
index 0000000000000000000000000000000000000000..04ef643d65c56847aed955389984ca5da546279d
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/deferredshadows/mrt.vert
@@ -0,0 +1,41 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec2 inUV;
+layout (location = 2) in vec3 inColor;
+layout (location = 3) in vec3 inNormal;
+layout (location = 4) in vec4 inTangent;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 model;
+	mat4 view;
+	vec4 instancePos[3];
+} ubo;
+
+layout (location = 0) out vec3 outNormal;
+layout (location = 1) out vec2 outUV;
+layout (location = 2) out vec3 outColor;
+layout (location = 3) out vec3 outWorldPos;
+layout (location = 4) out vec3 outTangent;
+
+void main() 
+{
+	vec4 tmpPos = vec4(inPos.xyz, 1.0) + ubo.instancePos[gl_InstanceIndex];
+
+	gl_Position = ubo.projection * ubo.view * ubo.model * tmpPos;
+	
+	outUV = inUV;
+
+	// Vertex position in world space
+	outWorldPos = vec3(ubo.model * tmpPos);
+
+	// Normal in world space
+	mat3 mNormal = transpose(inverse(mat3(ubo.model)));
+	outNormal = mNormal * normalize(inNormal);	
+	outTangent = mNormal * normalize(inTangent.xyz);
+	
+	// Currently just vertex color
+	outColor = inColor;
+}
diff --git a/external/Vulkan/data/shaders/glsl/deferredshadows/mrt.vert.spv b/external/Vulkan/data/shaders/glsl/deferredshadows/mrt.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..11b7f8f84dab007ebf8bbb0b2f2fd742f417e27e
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/deferredshadows/mrt.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/deferredshadows/shadow.geom b/external/Vulkan/data/shaders/glsl/deferredshadows/shadow.geom
new file mode 100644
index 0000000000000000000000000000000000000000..2ca45c97cf1f9d34a5d1e75294d3be9507e5b290
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/deferredshadows/shadow.geom
@@ -0,0 +1,27 @@
+#version 450
+
+#define LIGHT_COUNT 3
+
+layout (triangles, invocations = LIGHT_COUNT) in;
+layout (triangle_strip, max_vertices = 3) out;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 mvp[LIGHT_COUNT];
+	vec4 instancePos[3];
+} ubo;
+
+layout (location = 0) in int inInstanceIndex[];
+
+void main() 
+{
+	vec4 instancedPos = ubo.instancePos[inInstanceIndex[0]]; 
+	for (int i = 0; i < gl_in.length(); i++)
+	{
+		gl_Layer = gl_InvocationID;
+		vec4 tmpPos = gl_in[i].gl_Position + instancedPos;
+		gl_Position = ubo.mvp[gl_InvocationID] * tmpPos;
+		EmitVertex();
+	}
+	EndPrimitive();
+}
diff --git a/external/Vulkan/data/shaders/glsl/deferredshadows/shadow.geom.spv b/external/Vulkan/data/shaders/glsl/deferredshadows/shadow.geom.spv
new file mode 100644
index 0000000000000000000000000000000000000000..2af584d2ec2023b6d0df6e79a8679547588ccb25
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/deferredshadows/shadow.geom.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/deferredshadows/shadow.vert b/external/Vulkan/data/shaders/glsl/deferredshadows/shadow.vert
new file mode 100644
index 0000000000000000000000000000000000000000..7b432d5b25ba0d25405ea8d686bec49c7b62172f
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/deferredshadows/shadow.vert
@@ -0,0 +1,11 @@
+#version 450
+
+layout (location = 0) in vec4 inPos;
+
+layout (location = 0) out int outInstanceIndex;
+
+void main()
+{
+	outInstanceIndex = gl_InstanceIndex;
+	gl_Position = inPos;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/deferredshadows/shadow.vert.spv b/external/Vulkan/data/shaders/glsl/deferredshadows/shadow.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..903d6cd53535348a7cfe3f05f83fe8387f188254
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/deferredshadows/shadow.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/descriptorindexing/descriptorindexing.frag b/external/Vulkan/data/shaders/glsl/descriptorindexing/descriptorindexing.frag
new file mode 100644
index 0000000000000000000000000000000000000000..ce97215aec1a7d9f279d84424cb262e354c4f47c
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/descriptorindexing/descriptorindexing.frag
@@ -0,0 +1,17 @@
+// Copyright 2021 Sascha Willems
+
+#version 450
+
+#extension GL_EXT_nonuniform_qualifier : require
+
+layout (set = 0, binding = 1) uniform sampler2D textures[];
+
+layout (location = 0) in vec2 inUV;
+layout (location = 1) flat in int inTexIndex;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+	outFragColor = texture(textures[nonuniformEXT(inTexIndex)], inUV);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/descriptorindexing/descriptorindexing.frag.spv b/external/Vulkan/data/shaders/glsl/descriptorindexing/descriptorindexing.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..1e2499ad6e667c4740f11b47f30eaa71845fb2bd
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/descriptorindexing/descriptorindexing.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/descriptorindexing/descriptorindexing.vert b/external/Vulkan/data/shaders/glsl/descriptorindexing/descriptorindexing.vert
new file mode 100644
index 0000000000000000000000000000000000000000..a0428445b4ddc469660bc6eb38f1f55db2398349
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/descriptorindexing/descriptorindexing.vert
@@ -0,0 +1,25 @@
+// Copyright 2021 Sascha Willems
+
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec2 inUV;
+layout (location = 2) in int inTextureIndex;
+
+layout (binding = 0) uniform Matrices
+{
+	mat4 projection;
+	mat4 view;
+	mat4 model;
+} matrices;
+
+layout (location = 0) out vec2 outUV;
+layout (location = 1) flat out int outTexIndex;
+
+void main() 
+{
+	outUV = inUV;
+	outTexIndex = inTextureIndex;
+	vec4 pos = vec4(inPos, 1.0f);
+	gl_Position = matrices.projection * matrices.view * matrices.model * pos;
+}
diff --git a/external/Vulkan/data/shaders/glsl/descriptorindexing/descriptorindexing.vert.spv b/external/Vulkan/data/shaders/glsl/descriptorindexing/descriptorindexing.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..bec53942f859bd0cf35b82c1677cc4f554820e0f
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/descriptorindexing/descriptorindexing.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/descriptorsets/cube.frag b/external/Vulkan/data/shaders/glsl/descriptorsets/cube.frag
new file mode 100644
index 0000000000000000000000000000000000000000..56fe351846a35edb5c92c4c5d83bba376fee62df
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/descriptorsets/cube.frag
@@ -0,0 +1,14 @@
+#version 450
+
+layout (set = 0, binding = 1) uniform sampler2D samplerColorMap;
+
+layout (location = 0) in vec3 inNormal;
+layout (location = 1) in vec3 inColor;
+layout (location = 2) in vec2 inUV;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+	outFragColor = texture(samplerColorMap, inUV) * vec4(inColor, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/descriptorsets/cube.frag.spv b/external/Vulkan/data/shaders/glsl/descriptorsets/cube.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..edf35092507050cde8498e4aa0fa017c414e7dc9
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/descriptorsets/cube.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/descriptorsets/cube.vert b/external/Vulkan/data/shaders/glsl/descriptorsets/cube.vert
new file mode 100644
index 0000000000000000000000000000000000000000..1d91505e7f276963bf7ad69727059a3aefbc0588
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/descriptorsets/cube.vert
@@ -0,0 +1,28 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec3 inNormal;
+layout (location = 2) in vec2 inUV;
+layout (location = 3) in vec3 inColor;
+
+layout (set = 0, binding = 0) uniform UBOMatrices {
+	mat4 projection;
+	mat4 view;
+	mat4 model;
+} uboMatrices;
+
+layout (location = 0) out vec3 outNormal;
+layout (location = 1) out vec3 outColor;
+layout (location = 2) out vec2 outUV;
+
+out gl_PerVertex {
+	vec4 gl_Position;
+};
+
+void main() 
+{
+	outNormal = inNormal;
+	outColor = inColor;
+	outUV = inUV;
+	gl_Position = uboMatrices.projection * uboMatrices.view * uboMatrices.model * vec4(inPos.xyz, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/descriptorsets/cube.vert.spv b/external/Vulkan/data/shaders/glsl/descriptorsets/cube.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..86306db8d3d03ec4c695f7ba78c64f6c1528a996
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/descriptorsets/cube.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/displacement/base.frag b/external/Vulkan/data/shaders/glsl/displacement/base.frag
new file mode 100644
index 0000000000000000000000000000000000000000..4f18178e74cc918e8134776c266925942ea9e512
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/displacement/base.frag
@@ -0,0 +1,26 @@
+#version 450
+
+layout (binding = 2) uniform sampler2D colorMap;
+
+layout (location = 0) in vec3 inNormal;
+layout (location = 1) in vec2 inUV;
+layout (location = 2) in vec3 inEyePos;
+layout (location = 3) in vec3 inLightVec;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main()
+{
+	vec3 N = normalize(inNormal);
+	vec3 L = normalize(vec3(1.0));	
+	
+	outFragColor.rgb = texture(colorMap, inUV).rgb;
+	
+	vec3 Eye = normalize(-inEyePos);
+	vec3 Reflected = normalize(reflect(-inLightVec, inNormal)); 
+
+	vec4 IAmbient = vec4(0.0, 0.0, 0.0, 1.0);
+	vec4 IDiffuse = vec4(1.0) * max(dot(inNormal, inLightVec), 0.0);
+
+	outFragColor = vec4((IAmbient + IDiffuse) * vec4(texture(colorMap, inUV).rgb, 1.0));	
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/displacement/base.frag.spv b/external/Vulkan/data/shaders/glsl/displacement/base.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..3d35236f9c9e6408e517879ab907967ae58dea8c
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/displacement/base.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/displacement/base.vert b/external/Vulkan/data/shaders/glsl/displacement/base.vert
new file mode 100644
index 0000000000000000000000000000000000000000..61ef82018feb245a4831d2e1f9e005246e84ab38
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/displacement/base.vert
@@ -0,0 +1,15 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec3 inNormal;
+layout (location = 2) in vec2 inUV;
+
+layout (location = 0) out vec3 outNormal;
+layout (location = 1) out vec2 outUV;
+
+void main(void)
+{
+	gl_Position = vec4(inPos.xyz, 1.0);
+	outUV = inUV;
+	outNormal = inNormal;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/displacement/base.vert.spv b/external/Vulkan/data/shaders/glsl/displacement/base.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..d1b4a7e9f69a18eed763a35c2a012b576eb23252
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/displacement/base.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/displacement/displacement.tesc b/external/Vulkan/data/shaders/glsl/displacement/displacement.tesc
new file mode 100644
index 0000000000000000000000000000000000000000..31bbe507e60d73752d039a22cd1931b44f54d345
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/displacement/displacement.tesc
@@ -0,0 +1,29 @@
+#version 450
+
+layout (binding = 0) uniform UBO 
+{
+	float tessLevel;
+} ubo; 
+ 
+layout (vertices = 3) out;
+ 
+layout (location = 0) in vec3 inNormal[];
+layout (location = 1) in vec2 inUV[];
+ 
+layout (location = 0) out vec3 outNormal[3];
+layout (location = 1) out vec2 outUV[3];
+ 
+void main()
+{
+	if (gl_InvocationID == 0)
+	{
+		gl_TessLevelInner[0] = ubo.tessLevel;
+		gl_TessLevelOuter[0] = ubo.tessLevel;
+		gl_TessLevelOuter[1] = ubo.tessLevel;
+		gl_TessLevelOuter[2] = ubo.tessLevel;		
+	}
+
+	gl_out[gl_InvocationID].gl_Position =  gl_in[gl_InvocationID].gl_Position;
+	outNormal[gl_InvocationID] = inNormal[gl_InvocationID];
+	outUV[gl_InvocationID] = inUV[gl_InvocationID];
+} 
diff --git a/external/Vulkan/data/shaders/glsl/displacement/displacement.tesc.spv b/external/Vulkan/data/shaders/glsl/displacement/displacement.tesc.spv
new file mode 100644
index 0000000000000000000000000000000000000000..a85c805d17f3b7406ee85da4c2a23166b7e0bfbe
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/displacement/displacement.tesc.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/displacement/displacement.tese b/external/Vulkan/data/shaders/glsl/displacement/displacement.tese
new file mode 100644
index 0000000000000000000000000000000000000000..afe12d1afd0cb648b933d7b78961703394989420
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/displacement/displacement.tese
@@ -0,0 +1,36 @@
+#version 450
+
+layout (binding = 1) uniform UBO 
+{
+	mat4 projection;
+	mat4 modelview;
+	vec4 lightPos;
+	float tessAlpha;
+	float tessStrength;
+} ubo; 
+
+layout (binding = 2) uniform sampler2D displacementMap; 
+
+layout(triangles, equal_spacing, cw) in;
+
+layout (location = 0) in vec3 inNormal[];
+layout (location = 1) in vec2 inUV[];
+ 
+layout (location = 0) out vec3 outNormal;
+layout (location = 1) out vec2 outUV;
+layout (location = 2) out vec3 outEyesPos;
+layout (location = 3) out vec3 outLightVec;
+
+void main()
+{
+	gl_Position = (gl_TessCoord.x * gl_in[0].gl_Position) + (gl_TessCoord.y * gl_in[1].gl_Position) + (gl_TessCoord.z * gl_in[2].gl_Position); 
+	outUV = gl_TessCoord.x * inUV[0] + gl_TessCoord.y * inUV[1] + gl_TessCoord.z * inUV[2];
+	outNormal = gl_TessCoord.x * inNormal[0] + gl_TessCoord.y * inNormal[1] + gl_TessCoord.z * inNormal[2]; 
+				
+	gl_Position.xyz += normalize(outNormal) * (max(textureLod(displacementMap, outUV.st, 0.0).a, 0.0) * ubo.tessStrength);
+				
+	outEyesPos = (gl_Position).xyz;
+	outLightVec = normalize(ubo.lightPos.xyz - outEyesPos);	
+		
+	gl_Position = ubo.projection * ubo.modelview * gl_Position;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/displacement/displacement.tese.spv b/external/Vulkan/data/shaders/glsl/displacement/displacement.tese.spv
new file mode 100644
index 0000000000000000000000000000000000000000..4a697f154108604b17b3bcbedb1a6c6fe57254e6
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/displacement/displacement.tese.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/distancefieldfonts/bitmap.frag b/external/Vulkan/data/shaders/glsl/distancefieldfonts/bitmap.frag
new file mode 100644
index 0000000000000000000000000000000000000000..084daf2aedef3e98d6064d3649cabe918f306892
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/distancefieldfonts/bitmap.frag
@@ -0,0 +1,12 @@
+#version 450
+
+layout (binding = 1) uniform sampler2D samplerColor;
+
+layout (location = 0) in vec2 inUV;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+	outFragColor = vec4(texture(samplerColor, inUV).a);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/distancefieldfonts/bitmap.frag.spv b/external/Vulkan/data/shaders/glsl/distancefieldfonts/bitmap.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..53a6df8533c79b9d7297761e90371ad76fad0956
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/distancefieldfonts/bitmap.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/distancefieldfonts/bitmap.vert b/external/Vulkan/data/shaders/glsl/distancefieldfonts/bitmap.vert
new file mode 100644
index 0000000000000000000000000000000000000000..1418d5c5a8f5e82d717b6c5624c7a26c8992b173
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/distancefieldfonts/bitmap.vert
@@ -0,0 +1,18 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec2 inUV;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 model;
+} ubo;
+
+layout (location = 0) out vec2 outUV;
+
+void main() 
+{
+	outUV = inUV;
+	gl_Position = ubo.projection * ubo.model * vec4(inPos.xyz, 1.0);
+}
diff --git a/external/Vulkan/data/shaders/glsl/distancefieldfonts/bitmap.vert.spv b/external/Vulkan/data/shaders/glsl/distancefieldfonts/bitmap.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..0872996a8fae4889cc9212be02f9dc1f4b261bec
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/distancefieldfonts/bitmap.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/distancefieldfonts/sdf.frag b/external/Vulkan/data/shaders/glsl/distancefieldfonts/sdf.frag
new file mode 100644
index 0000000000000000000000000000000000000000..c8d4287037fb83469fe20b6d426f670f190da75a
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/distancefieldfonts/sdf.frag
@@ -0,0 +1,32 @@
+#version 450
+
+layout (binding = 1) uniform sampler2D samplerColor;
+
+layout (binding = 2) uniform UBO 
+{
+	vec4 outlineColor;
+	float outlineWidth;
+	float outline;
+} ubo;
+
+layout (location = 0) in vec2 inUV;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+    float distance = texture(samplerColor, inUV).a;
+    float smoothWidth = fwidth(distance);	
+    float alpha = smoothstep(0.5 - smoothWidth, 0.5 + smoothWidth, distance);
+	vec3 rgb = vec3(alpha);
+									 
+	if (ubo.outline > 0.0) 
+	{
+		float w = 1.0 - ubo.outlineWidth;
+		alpha = smoothstep(w - smoothWidth, w + smoothWidth, distance);
+        rgb += mix(vec3(alpha), ubo.outlineColor.rgb, alpha);
+    }									 
+									 
+    outFragColor = vec4(rgb, alpha);	
+	
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/distancefieldfonts/sdf.frag.spv b/external/Vulkan/data/shaders/glsl/distancefieldfonts/sdf.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..f16588d15fd627143216e58c05d7fe24930c74bf
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/distancefieldfonts/sdf.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/distancefieldfonts/sdf.vert b/external/Vulkan/data/shaders/glsl/distancefieldfonts/sdf.vert
new file mode 100644
index 0000000000000000000000000000000000000000..1418d5c5a8f5e82d717b6c5624c7a26c8992b173
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/distancefieldfonts/sdf.vert
@@ -0,0 +1,18 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec2 inUV;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 model;
+} ubo;
+
+layout (location = 0) out vec2 outUV;
+
+void main() 
+{
+	outUV = inUV;
+	gl_Position = ubo.projection * ubo.model * vec4(inPos.xyz, 1.0);
+}
diff --git a/external/Vulkan/data/shaders/glsl/distancefieldfonts/sdf.vert.spv b/external/Vulkan/data/shaders/glsl/distancefieldfonts/sdf.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..0872996a8fae4889cc9212be02f9dc1f4b261bec
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/distancefieldfonts/sdf.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/dynamicrendering/texture.frag b/external/Vulkan/data/shaders/glsl/dynamicrendering/texture.frag
new file mode 100644
index 0000000000000000000000000000000000000000..225535329d9af07f0f5d045eb26e434d31bd6aa1
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/dynamicrendering/texture.frag
@@ -0,0 +1,24 @@
+#version 450
+
+layout (set = 1, binding = 0) uniform sampler2D samplerColor;
+
+layout (location = 0) in vec2 inUV;
+layout (location = 1) in vec3 inNormal;
+layout (location = 2) in vec3 inViewVec;
+layout (location = 3) in vec3 inLightVec;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+	vec4 color = texture(samplerColor, inUV);
+
+	vec3 N = normalize(inNormal);
+	vec3 L = normalize(inLightVec);
+	vec3 V = normalize(inViewVec);
+	vec3 R = reflect(-L, N);
+	vec3 diffuse = max(dot(N, L), 0.0) * vec3(1.0);
+	float specular = pow(max(dot(R, V), 0.0), 16.0) * color.a;
+
+	outFragColor = vec4(diffuse * color.rgb + specular, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/dynamicrendering/texture.frag.spv b/external/Vulkan/data/shaders/glsl/dynamicrendering/texture.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..1ab597cc220df68405fa413398fecf91c6a48325
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/dynamicrendering/texture.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/dynamicrendering/texture.vert b/external/Vulkan/data/shaders/glsl/dynamicrendering/texture.vert
new file mode 100644
index 0000000000000000000000000000000000000000..3c7c17eb8e6a692e40b8842751fa9a3d2d8d3f8f
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/dynamicrendering/texture.vert
@@ -0,0 +1,33 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec3 inNormal;
+layout (location = 2) in vec2 inUV;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 model;
+	vec4 viewPos;
+} ubo;
+
+layout (location = 0) out vec2 outUV;
+layout (location = 1) out vec3 outNormal;
+layout (location = 2) out vec3 outViewVec;
+layout (location = 3) out vec3 outLightVec;
+
+void main() 
+{
+	outUV = inUV;
+
+	vec3 worldPos = vec3(ubo.model * vec4(inPos, 1.0));
+
+	gl_Position = ubo.projection * ubo.model * vec4(inPos.xyz, 1.0);
+
+    vec4 pos = ubo.model * vec4(inPos, 1.0);
+	outNormal = mat3(inverse(transpose(ubo.model))) * inNormal;
+	vec3 lightPos = vec3(0.0);
+	vec3 lPos = mat3(ubo.model) * lightPos.xyz;
+    outLightVec = lPos - pos.xyz;
+    outViewVec = ubo.viewPos.xyz - pos.xyz;		
+}
diff --git a/external/Vulkan/data/shaders/glsl/dynamicrendering/texture.vert.spv b/external/Vulkan/data/shaders/glsl/dynamicrendering/texture.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..1d7d8865ae153715719443f3e464e7251c70eac0
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/dynamicrendering/texture.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/dynamicuniformbuffer/base.frag b/external/Vulkan/data/shaders/glsl/dynamicuniformbuffer/base.frag
new file mode 100644
index 0000000000000000000000000000000000000000..cbf870133ae7e48854bf177822d9e8c4a7e573a6
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/dynamicuniformbuffer/base.frag
@@ -0,0 +1,10 @@
+#version 450
+
+layout (location = 0) in vec3 inColor;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+	outFragColor = vec4(inColor, 1.0);	
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/dynamicuniformbuffer/base.frag.spv b/external/Vulkan/data/shaders/glsl/dynamicuniformbuffer/base.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..f67f201135c138f22f9bfb7bd9b0a8e1c9227e92
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/dynamicuniformbuffer/base.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/dynamicuniformbuffer/base.vert b/external/Vulkan/data/shaders/glsl/dynamicuniformbuffer/base.vert
new file mode 100644
index 0000000000000000000000000000000000000000..acf87fec1377422d2628a44f153d8cf8d0a857d5
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/dynamicuniformbuffer/base.vert
@@ -0,0 +1,30 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec3 inColor;
+
+layout (binding = 0) uniform UboView 
+{
+	mat4 projection;
+	mat4 view;
+} uboView;
+
+layout (binding = 1) uniform UboInstance 
+{
+	mat4 model; 
+} uboInstance;
+
+layout (location = 0) out vec3 outColor;
+
+out gl_PerVertex 
+{
+	vec4 gl_Position;   
+};
+
+void main() 
+{
+	outColor = inColor;
+	mat4 modelView = uboView.view * uboInstance.model;
+	vec3 worldPos = vec3(modelView * vec4(inPos, 1.0));
+	gl_Position = uboView.projection * modelView * vec4(inPos.xyz, 1.0);
+}
diff --git a/external/Vulkan/data/shaders/glsl/dynamicuniformbuffer/base.vert.spv b/external/Vulkan/data/shaders/glsl/dynamicuniformbuffer/base.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..840e527713e7558368527787af35cb328f941716
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/dynamicuniformbuffer/base.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/gears/gears.frag b/external/Vulkan/data/shaders/glsl/gears/gears.frag
new file mode 100644
index 0000000000000000000000000000000000000000..1795af76522d196069f42eeb00a65f68a6f403eb
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/gears/gears.frag
@@ -0,0 +1,21 @@
+#version 450
+
+layout (location = 0) in vec3 inNormal;
+layout (location = 1) in vec3 inColor;
+layout (location = 2) in vec3 inEyePos;
+layout (location = 3) in vec3 inLightVec;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+  vec3 Eye = normalize(-inEyePos);
+  vec3 Reflected = normalize(reflect(-inLightVec, inNormal)); 
+ 
+  vec4 IAmbient = vec4(0.2, 0.2, 0.2, 1.0);
+  vec4 IDiffuse = vec4(0.5, 0.5, 0.5, 0.5) * max(dot(inNormal, inLightVec), 0.0);
+  float specular = 0.25;
+  vec4 ISpecular = vec4(0.5, 0.5, 0.5, 1.0) * pow(max(dot(Reflected, Eye), 0.0), 0.8) * specular; 
+ 
+  outFragColor = vec4((IAmbient + IDiffuse) * vec4(inColor, 1.0) + ISpecular);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/gears/gears.frag.spv b/external/Vulkan/data/shaders/glsl/gears/gears.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..8d979893813ab6c1f8d34bd8d09393e9398723ce
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/gears/gears.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/gears/gears.vert b/external/Vulkan/data/shaders/glsl/gears/gears.vert
new file mode 100644
index 0000000000000000000000000000000000000000..51f011952cbf7cfe7191dd2ca19216715ba5230f
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/gears/gears.vert
@@ -0,0 +1,31 @@
+#version 450
+
+layout (location = 0) in vec4 inPos;
+layout (location = 1) in vec3 inNormal;
+layout (location = 2) in vec3 inColor;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 model;
+	mat4 normal;
+	mat4 view;
+	vec3 lightpos;
+} ubo;
+
+layout (location = 0) out vec3 outNormal;
+layout (location = 1) out vec3 outColor;
+layout (location = 2) out vec3 outEyePos;
+layout (location = 3) out vec3 outLightVec;
+
+void main() 
+{
+	outNormal = normalize(mat3(ubo.normal) * inNormal);
+	outColor = inColor;
+	mat4 modelView = ubo.view * ubo.model;
+	vec4 pos = modelView * inPos;	
+	outEyePos = vec3(modelView * pos);
+	vec4 lightPos = vec4(ubo.lightpos, 1.0) * modelView;
+	outLightVec = normalize(lightPos.xyz - outEyePos);
+	gl_Position = ubo.projection * pos;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/gears/gears.vert.spv b/external/Vulkan/data/shaders/glsl/gears/gears.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..fc950d2f56d0f363e256d1b2b8b2bede4be478bd
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/gears/gears.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/geometryshader/base.frag b/external/Vulkan/data/shaders/glsl/geometryshader/base.frag
new file mode 100644
index 0000000000000000000000000000000000000000..d1c4172152c748dcdd28ec753895c4aef57d4b32
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/geometryshader/base.frag
@@ -0,0 +1,10 @@
+#version 450
+
+layout (location = 0) in vec3 inColor;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main(void)
+{
+	outFragColor = vec4(inColor, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/geometryshader/base.frag.spv b/external/Vulkan/data/shaders/glsl/geometryshader/base.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..3ede302d86fb0124b63ffee35962310d0c2b8ea9
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/geometryshader/base.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/geometryshader/base.vert b/external/Vulkan/data/shaders/glsl/geometryshader/base.vert
new file mode 100644
index 0000000000000000000000000000000000000000..99b5ebeea026a643bddf8db041e52c982dcddb03
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/geometryshader/base.vert
@@ -0,0 +1,12 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec3 inNormal;
+
+layout (location = 0) out vec3 outNormal;
+
+void main(void)
+{
+	outNormal = inNormal;
+	gl_Position = vec4(inPos.xyz, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/geometryshader/base.vert.spv b/external/Vulkan/data/shaders/glsl/geometryshader/base.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..ce0c1682fe66111e92daa4efa2c0cd6cc951b0b5
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/geometryshader/base.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/geometryshader/mesh.frag b/external/Vulkan/data/shaders/glsl/geometryshader/mesh.frag
new file mode 100644
index 0000000000000000000000000000000000000000..aab33e1d5f35578c5aeae246fd75d4df5810ff0a
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/geometryshader/mesh.frag
@@ -0,0 +1,20 @@
+#version 450
+
+layout (location = 0) in vec3 inNormal;
+layout (location = 1) in vec3 inColor;
+layout (location = 2) in vec3 inViewVec;
+layout (location = 3) in vec3 inLightVec;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+	vec3 N = normalize(inNormal);
+	vec3 L = normalize(inLightVec);
+	vec3 V = normalize(inViewVec);
+	vec3 R = reflect(-L, N);
+	vec3 ambient = vec3(0.1);
+	vec3 diffuse = max(dot(N, L), 0.0) * vec3(1.0);
+	vec3 specular = pow(max(dot(R, V), 0.0), 16.0) * vec3(0.75);
+	outFragColor = vec4((ambient + diffuse) * inColor.rgb + specular, 1.0);		
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/geometryshader/mesh.frag.spv b/external/Vulkan/data/shaders/glsl/geometryshader/mesh.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..896f10e66831ad0465ad9397f887e8882266b5de
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/geometryshader/mesh.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/geometryshader/mesh.vert b/external/Vulkan/data/shaders/glsl/geometryshader/mesh.vert
new file mode 100644
index 0000000000000000000000000000000000000000..69047000c6dfdd2953dc08e6ec8cd92888219975
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/geometryshader/mesh.vert
@@ -0,0 +1,30 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec3 inNormal;
+layout (location = 2) in vec3 inColor;
+
+layout (set = 0, binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 model;
+} ubo;
+
+layout (location = 0) out vec3 outNormal;
+layout (location = 1) out vec3 outColor;
+layout (location = 2) out vec3 outViewVec;
+layout (location = 3) out vec3 outLightVec;
+
+void main() 
+{
+	outNormal = inNormal;
+	outColor = inColor;
+	gl_Position = ubo.projection * ubo.model * vec4(inPos, 1.0);
+
+	vec4 pos = ubo.model * vec4(inPos, 1.0);
+	outNormal = mat3(ubo.model) * inNormal;
+
+	vec3 lightPos = vec3(1.0f, 1.0f, 1.0f);
+	outLightVec = lightPos.xyz - pos.xyz;
+	outViewVec = -pos.xyz;		
+}
diff --git a/external/Vulkan/data/shaders/glsl/geometryshader/mesh.vert.spv b/external/Vulkan/data/shaders/glsl/geometryshader/mesh.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..136c49eea9e810e430f71ab61311c0c8a17a4647
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/geometryshader/mesh.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/geometryshader/normaldebug.geom b/external/Vulkan/data/shaders/glsl/geometryshader/normaldebug.geom
new file mode 100644
index 0000000000000000000000000000000000000000..3ed021582eb12249642db219c12aeefc463671f2
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/geometryshader/normaldebug.geom
@@ -0,0 +1,34 @@
+#version 450
+
+layout (triangles) in;
+layout (line_strip, max_vertices = 6) out;
+
+layout (binding = 1) uniform UBO 
+{
+	mat4 projection;
+	mat4 model;
+} ubo;
+
+layout (location = 0) in vec3 inNormal[];
+
+layout (location = 0) out vec3 outColor;
+
+void main(void)
+{	
+	float normalLength = 0.02;
+	for(int i=0; i<gl_in.length(); i++)
+	{
+		vec3 pos = gl_in[i].gl_Position.xyz;
+		vec3 normal = inNormal[i].xyz;
+
+		gl_Position = ubo.projection * (ubo.model * vec4(pos, 1.0));
+		outColor = vec3(1.0, 0.0, 0.0);
+		EmitVertex();
+
+		gl_Position = ubo.projection * (ubo.model * vec4(pos + normal * normalLength, 1.0));
+		outColor = vec3(0.0, 0.0, 1.0);
+		EmitVertex();
+
+		EndPrimitive();
+	}
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/geometryshader/normaldebug.geom.spv b/external/Vulkan/data/shaders/glsl/geometryshader/normaldebug.geom.spv
new file mode 100644
index 0000000000000000000000000000000000000000..d5eb49e268893e78c7cc0a4713d235d1a65c3692
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/geometryshader/normaldebug.geom.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/gltfloading/mesh.frag b/external/Vulkan/data/shaders/glsl/gltfloading/mesh.frag
new file mode 100644
index 0000000000000000000000000000000000000000..fb415cefddfbb59ceef7c3263fb74c7f9321f09c
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/gltfloading/mesh.frag
@@ -0,0 +1,24 @@
+#version 450
+
+layout (set = 1, binding = 0) uniform sampler2D samplerColorMap;
+
+layout (location = 0) in vec3 inNormal;
+layout (location = 1) in vec3 inColor;
+layout (location = 2) in vec2 inUV;
+layout (location = 3) in vec3 inViewVec;
+layout (location = 4) in vec3 inLightVec;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+	vec4 color = texture(samplerColorMap, inUV) * vec4(inColor, 1.0);
+
+	vec3 N = normalize(inNormal);
+	vec3 L = normalize(inLightVec);
+	vec3 V = normalize(inViewVec);
+	vec3 R = reflect(-L, N);
+	vec3 diffuse = max(dot(N, L), 0.15) * inColor;
+	vec3 specular = pow(max(dot(R, V), 0.0), 16.0) * vec3(0.75);
+	outFragColor = vec4(diffuse * color.rgb + specular, 1.0);		
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/gltfloading/mesh.frag.spv b/external/Vulkan/data/shaders/glsl/gltfloading/mesh.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..5cd562653a43a2f4e3575be29980fd2deef432dd
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/gltfloading/mesh.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/gltfloading/mesh.vert b/external/Vulkan/data/shaders/glsl/gltfloading/mesh.vert
new file mode 100644
index 0000000000000000000000000000000000000000..2760684e1a19115cda69d3d6a50cb90004d77144
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/gltfloading/mesh.vert
@@ -0,0 +1,37 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec3 inNormal;
+layout (location = 2) in vec2 inUV;
+layout (location = 3) in vec3 inColor;
+
+layout (set = 0, binding = 0) uniform UBOScene
+{
+	mat4 projection;
+	mat4 view;
+	vec4 lightPos;
+} uboScene;
+
+layout(push_constant) uniform PushConsts {
+	mat4 model;
+} primitive;
+
+layout (location = 0) out vec3 outNormal;
+layout (location = 1) out vec3 outColor;
+layout (location = 2) out vec2 outUV;
+layout (location = 3) out vec3 outViewVec;
+layout (location = 4) out vec3 outLightVec;
+
+void main() 
+{
+	outNormal = inNormal;
+	outColor = inColor;
+	outUV = inUV;
+	gl_Position = uboScene.projection * uboScene.view * primitive.model * vec4(inPos.xyz, 1.0);
+	
+	vec4 pos = uboScene.view * vec4(inPos, 1.0);
+	outNormal = mat3(uboScene.view) * inNormal;
+	vec3 lPos = mat3(uboScene.view) * uboScene.lightPos.xyz;
+	outLightVec = lPos - pos.xyz;
+	outViewVec = -pos.xyz;		
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/gltfloading/mesh.vert.spv b/external/Vulkan/data/shaders/glsl/gltfloading/mesh.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..cf6fc4ac34758c1159e7f64a2d1acf8dcb20d627
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/gltfloading/mesh.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/gltfscenerendering/scene.frag b/external/Vulkan/data/shaders/glsl/gltfscenerendering/scene.frag
new file mode 100644
index 0000000000000000000000000000000000000000..54e0ef8d4524a16109a6ad6233bf9456bbd2c14b
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/gltfscenerendering/scene.frag
@@ -0,0 +1,41 @@
+#version 450
+
+layout (set = 1, binding = 0) uniform sampler2D samplerColorMap;
+layout (set = 1, binding = 1) uniform sampler2D samplerNormalMap;
+
+layout (location = 0) in vec3 inNormal;
+layout (location = 1) in vec3 inColor;
+layout (location = 2) in vec2 inUV;
+layout (location = 3) in vec3 inViewVec;
+layout (location = 4) in vec3 inLightVec;
+layout (location = 5) in vec4 inTangent;
+
+layout (location = 0) out vec4 outFragColor;
+
+layout (constant_id = 0) const bool ALPHA_MASK = false;
+layout (constant_id = 1) const float ALPHA_MASK_CUTOFF = 0.0f;
+
+void main() 
+{
+	vec4 color = texture(samplerColorMap, inUV) * vec4(inColor, 1.0);
+
+	if (ALPHA_MASK) {
+		if (color.a < ALPHA_MASK_CUTOFF) {
+			discard;
+		}
+	}
+
+	vec3 N = normalize(inNormal);
+	vec3 T = normalize(inTangent.xyz);
+	vec3 B = cross(inNormal, inTangent.xyz) * inTangent.w;
+	mat3 TBN = mat3(T, B, N);
+	N = TBN * normalize(texture(samplerNormalMap, inUV).xyz * 2.0 - vec3(1.0));
+
+	const float ambient = 0.1;
+	vec3 L = normalize(inLightVec);
+	vec3 V = normalize(inViewVec);
+	vec3 R = reflect(-L, N);
+	vec3 diffuse = max(dot(N, L), ambient).rrr;
+	float specular = pow(max(dot(R, V), 0.0), 32.0);
+	outFragColor = vec4(diffuse * color.rgb + specular, color.a);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/gltfscenerendering/scene.frag.spv b/external/Vulkan/data/shaders/glsl/gltfscenerendering/scene.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..4f0436674659e867a8943b85a8a5db8b33e38e65
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/gltfscenerendering/scene.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/gltfscenerendering/scene.vert b/external/Vulkan/data/shaders/glsl/gltfscenerendering/scene.vert
new file mode 100644
index 0000000000000000000000000000000000000000..2c4655e17fca77d0ec1d3270dafcb06992388b63
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/gltfscenerendering/scene.vert
@@ -0,0 +1,40 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec3 inNormal;
+layout (location = 2) in vec2 inUV;
+layout (location = 3) in vec3 inColor;
+layout (location = 4) in vec4 inTangent;
+
+layout (set = 0, binding = 0) uniform UBOScene 
+{
+	mat4 projection;
+	mat4 view;
+	vec4 lightPos;
+	vec4 viewPos;
+} uboScene;
+
+layout(push_constant) uniform PushConsts {
+	mat4 model;
+} primitive;
+
+layout (location = 0) out vec3 outNormal;
+layout (location = 1) out vec3 outColor;
+layout (location = 2) out vec2 outUV;
+layout (location = 3) out vec3 outViewVec;
+layout (location = 4) out vec3 outLightVec;
+layout (location = 5) out vec4 outTangent;
+
+void main() 
+{
+	outNormal = inNormal;
+	outColor = inColor;
+	outUV = inUV;
+	outTangent = inTangent;
+	gl_Position = uboScene.projection * uboScene.view * primitive.model * vec4(inPos.xyz, 1.0);
+	
+	outNormal = mat3(primitive.model) * inNormal;
+	vec4 pos = primitive.model * vec4(inPos, 1.0);
+	outLightVec = uboScene.lightPos.xyz - pos.xyz;
+	outViewVec = uboScene.viewPos.xyz - pos.xyz;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/gltfscenerendering/scene.vert.spv b/external/Vulkan/data/shaders/glsl/gltfscenerendering/scene.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..c3ff307c9b9a22ce534b7a4df2af39c2ddeda9bd
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/gltfscenerendering/scene.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/gltfskinning/skinnedmodel.frag b/external/Vulkan/data/shaders/glsl/gltfskinning/skinnedmodel.frag
new file mode 100644
index 0000000000000000000000000000000000000000..50b8226fca257e0a131a07de2239ece41e2f237f
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/gltfskinning/skinnedmodel.frag
@@ -0,0 +1,24 @@
+#version 450
+
+layout (set = 2, binding = 0) uniform sampler2D samplerColorMap;
+
+layout (location = 0) in vec3 inNormal;
+layout (location = 1) in vec3 inColor;
+layout (location = 2) in vec2 inUV;
+layout (location = 3) in vec3 inViewVec;
+layout (location = 4) in vec3 inLightVec;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+	vec4 color = texture(samplerColorMap, inUV) * vec4(inColor, 1.0);
+
+	vec3 N = normalize(inNormal);
+	vec3 L = normalize(inLightVec);
+	vec3 V = normalize(inViewVec);
+	vec3 R = reflect(-L, N);
+	vec3 diffuse = max(dot(N, L), 0.5) * inColor;
+	vec3 specular = pow(max(dot(R, V), 0.0), 16.0) * vec3(0.75);
+	outFragColor = vec4(diffuse * color.rgb + specular, 1.0);		
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/gltfskinning/skinnedmodel.frag.spv b/external/Vulkan/data/shaders/glsl/gltfskinning/skinnedmodel.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..3a3e2dfe7c3e8ed468f2ef1dc59fd89f154b7a97
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/gltfskinning/skinnedmodel.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/gltfskinning/skinnedmodel.vert b/external/Vulkan/data/shaders/glsl/gltfskinning/skinnedmodel.vert
new file mode 100644
index 0000000000000000000000000000000000000000..ecb24348d0fff17af09a515bd9e40fec96f8a373
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/gltfskinning/skinnedmodel.vert
@@ -0,0 +1,52 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec3 inNormal;
+layout (location = 2) in vec2 inUV;
+layout (location = 3) in vec3 inColor;
+layout (location = 4) in vec4 inJointIndices;
+layout (location = 5) in vec4 inJointWeights;
+
+layout (set = 0, binding = 0) uniform UBOScene
+{
+	mat4 projection;
+	mat4 view;
+	vec4 lightPos;
+} uboScene;
+
+layout(push_constant) uniform PushConsts {
+	mat4 model;
+} primitive;
+
+layout(std430, set = 1, binding = 0) readonly buffer JointMatrices {
+	mat4 jointMatrices[];
+};
+
+layout (location = 0) out vec3 outNormal;
+layout (location = 1) out vec3 outColor;
+layout (location = 2) out vec2 outUV;
+layout (location = 3) out vec3 outViewVec;
+layout (location = 4) out vec3 outLightVec;
+
+void main() 
+{
+	outNormal = inNormal;
+	outColor = inColor;
+	outUV = inUV;
+
+	// Calculate skinned matrix from weights and joint indices of the current vertex
+	mat4 skinMat = 
+		inJointWeights.x * jointMatrices[int(inJointIndices.x)] +
+		inJointWeights.y * jointMatrices[int(inJointIndices.y)] +
+		inJointWeights.z * jointMatrices[int(inJointIndices.z)] +
+		inJointWeights.w * jointMatrices[int(inJointIndices.w)];
+
+	gl_Position = uboScene.projection * uboScene.view * primitive.model * skinMat * vec4(inPos.xyz, 1.0);
+	
+	outNormal = normalize(transpose(inverse(mat3(uboScene.view * primitive.model * skinMat))) * inNormal);
+
+	vec4 pos = uboScene.view * vec4(inPos, 1.0);
+	vec3 lPos = mat3(uboScene.view) * uboScene.lightPos.xyz;
+	outLightVec = lPos - pos.xyz;
+	outViewVec = -pos.xyz;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/gltfskinning/skinnedmodel.vert.spv b/external/Vulkan/data/shaders/glsl/gltfskinning/skinnedmodel.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..de0e0016db9253e8cb68ee6317d638ad14d1f49f
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/gltfskinning/skinnedmodel.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/hdr/bloom.frag b/external/Vulkan/data/shaders/glsl/hdr/bloom.frag
new file mode 100644
index 0000000000000000000000000000000000000000..c3bf402bda1fbd05597302fc1285aa635e167598
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/hdr/bloom.frag
@@ -0,0 +1,63 @@
+#version 450
+
+layout (binding = 0) uniform sampler2D samplerColor0;
+layout (binding = 1) uniform sampler2D samplerColor1;
+
+layout (location = 0) in vec2 inUV;
+
+layout (location = 0) out vec4 outColor;
+
+layout (constant_id = 0) const int dir = 0;
+
+void main(void)
+{
+	// From the OpenGL Super bible
+	const float weights[] = float[](0.0024499299678342,
+									0.0043538453346397,
+									0.0073599963704157,
+									0.0118349786570722,
+									0.0181026699707781,
+									0.0263392293891488,
+									0.0364543006660986,
+									0.0479932050577658,
+									0.0601029809166942,
+									0.0715974486241365,
+									0.0811305381519717,
+									0.0874493212267511,
+									0.0896631113333857,
+									0.0874493212267511,
+									0.0811305381519717,
+									0.0715974486241365,
+									0.0601029809166942,
+									0.0479932050577658,
+									0.0364543006660986,
+									0.0263392293891488,
+									0.0181026699707781,
+									0.0118349786570722,
+									0.0073599963704157,
+									0.0043538453346397,
+									0.0024499299678342);
+
+
+	const float blurScale = 0.003;
+	const float blurStrength = 1.0;
+
+	float ar = 1.0;
+	// Aspect ratio for vertical blur pass
+	if (dir == 1)
+	{
+		vec2 ts = textureSize(samplerColor1, 0);
+		ar = ts.y / ts.x;
+	}
+
+	vec2 P = inUV.yx - vec2(0, (weights.length() >> 1) * ar * blurScale);
+
+	vec4 color = vec4(0.0);
+	for (int i = 0; i < weights.length(); i++)
+	{
+		vec2 dv = vec2(0.0, i * blurScale) * ar;
+		color += texture(samplerColor1, P + dv) * weights[i] * blurStrength;
+	}
+
+	outColor = color;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/hdr/bloom.frag.spv b/external/Vulkan/data/shaders/glsl/hdr/bloom.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..65e7d3829b6622b1bc8db526eaa3b9434d136b4a
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/hdr/bloom.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/hdr/bloom.vert b/external/Vulkan/data/shaders/glsl/hdr/bloom.vert
new file mode 100644
index 0000000000000000000000000000000000000000..a5d60d0e750f260d3c034db033e15f18fb6a89d8
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/hdr/bloom.vert
@@ -0,0 +1,14 @@
+#version 450
+
+layout (location = 0) out vec2 outUV;
+
+out gl_PerVertex
+{
+	vec4 gl_Position;
+};
+
+void main() 
+{
+	outUV = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
+	gl_Position = vec4(outUV * 2.0f - 1.0f, 0.0f, 1.0f);
+}
diff --git a/external/Vulkan/data/shaders/glsl/hdr/bloom.vert.spv b/external/Vulkan/data/shaders/glsl/hdr/bloom.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..ffcd1a8449e738b2d9e7085c5a9f84d7a55e501a
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/hdr/bloom.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/hdr/composition.frag b/external/Vulkan/data/shaders/glsl/hdr/composition.frag
new file mode 100644
index 0000000000000000000000000000000000000000..27f2a5f26bf19d868a1f462855db98b0bd4b355c
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/hdr/composition.frag
@@ -0,0 +1,13 @@
+#version 450
+
+layout (binding = 0) uniform sampler2D samplerColor0;
+layout (binding = 1) uniform sampler2D samplerColor1;
+
+layout (location = 0) in vec2 inUV;
+
+layout (location = 0) out vec4 outColor;
+
+void main() 
+{
+	outColor = texture(samplerColor0, inUV);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/hdr/composition.frag.spv b/external/Vulkan/data/shaders/glsl/hdr/composition.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..1a334e0ecf8a3af7b8381ea2b4fc1a1299b467cc
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/hdr/composition.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/hdr/composition.vert b/external/Vulkan/data/shaders/glsl/hdr/composition.vert
new file mode 100644
index 0000000000000000000000000000000000000000..a5d60d0e750f260d3c034db033e15f18fb6a89d8
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/hdr/composition.vert
@@ -0,0 +1,14 @@
+#version 450
+
+layout (location = 0) out vec2 outUV;
+
+out gl_PerVertex
+{
+	vec4 gl_Position;
+};
+
+void main() 
+{
+	outUV = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
+	gl_Position = vec4(outUV * 2.0f - 1.0f, 0.0f, 1.0f);
+}
diff --git a/external/Vulkan/data/shaders/glsl/hdr/composition.vert.spv b/external/Vulkan/data/shaders/glsl/hdr/composition.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..ffcd1a8449e738b2d9e7085c5a9f84d7a55e501a
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/hdr/composition.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/hdr/gbuffer.frag b/external/Vulkan/data/shaders/glsl/hdr/gbuffer.frag
new file mode 100644
index 0000000000000000000000000000000000000000..b920dad34d3809f4ad831a3ff8a4c4ce737024e6
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/hdr/gbuffer.frag
@@ -0,0 +1,96 @@
+#version 450
+
+layout (binding = 1) uniform samplerCube samplerEnvMap;
+
+layout (binding = 0) uniform UBO {
+	mat4 projection;
+	mat4 modelview;
+	mat4 inverseModelview;
+} ubo;
+
+layout (location = 0) in vec3 inUVW;
+layout (location = 1) in vec3 inPos;
+layout (location = 2) in vec3 inNormal;
+layout (location = 3) in vec3 inViewVec;
+layout (location = 4) in vec3 inLightVec;
+
+layout (location = 0) out vec4 outColor0;
+layout (location = 1) out vec4 outColor1;
+
+layout (constant_id = 0) const int type = 0;
+
+#define PI 3.1415926
+#define TwoPI (2.0 * PI)
+
+layout (binding = 2) uniform Exposure {
+	float exposure;
+} exposure;
+
+void main()
+{
+	vec4 color;
+	vec3 wcNormal;
+
+	switch (type) {
+		case 0: // Skybox
+			{
+				vec3 normal = normalize(inUVW);
+				color = texture(samplerEnvMap, normal);
+			}
+			break;
+
+		case 1: // Reflect
+			{
+				vec3 wViewVec = mat3(ubo.inverseModelview) * normalize(inViewVec);
+				vec3 normal = normalize(inNormal);
+				vec3 wNormal = mat3(ubo.inverseModelview) * normal;
+
+				float NdotL = max(dot(normal, inLightVec), 0.0);
+
+				vec3 eyeDir = normalize(inViewVec);
+				vec3 halfVec = normalize(inLightVec + eyeDir);
+				float NdotH = max(dot(normal, halfVec), 0.0);
+				float NdotV = max(dot(normal, eyeDir), 0.0);
+				float VdotH = max(dot(eyeDir, halfVec), 0.0);
+
+				// Geometric attenuation
+				float NH2 = 2.0 * NdotH;
+				float g1 = (NH2 * NdotV) / VdotH;
+				float g2 = (NH2 * NdotL) / VdotH;
+				float geoAtt = min(1.0, min(g1, g2));
+
+				const float F0 = 0.6;
+				const float k = 0.2;
+
+				// Fresnel (schlick approximation)
+				float fresnel = pow(1.0 - VdotH, 5.0);
+				fresnel *= (1.0 - F0);
+				fresnel += F0;
+
+				float spec = (fresnel * geoAtt) / (NdotV * NdotL * 3.14);
+
+				color = texture(samplerEnvMap, reflect(-wViewVec, wNormal));
+
+				color = vec4(color.rgb * NdotL * (k + spec * (1.0 - k)), 1.0);
+			}
+			break;
+
+		case 2: // Refract
+			{
+				vec3 wViewVec = mat3(ubo.inverseModelview) * normalize(inViewVec);
+				vec3 wNormal = mat3(ubo.inverseModelview) * inNormal;
+				color = texture(samplerEnvMap, refract(-wViewVec, wNormal, 1.0/1.6));
+			}
+			break;
+	}
+
+
+	// Color with manual exposure into attachment 0
+	outColor0.rgb = vec3(1.0) - exp(-color.rgb * exposure.exposure);
+
+	// Bright parts for bloom into attachment 1
+	float l = dot(outColor0.rgb, vec3(0.2126, 0.7152, 0.0722));
+	float threshold = 0.75;
+	outColor1.rgb = (l > threshold) ? outColor0.rgb : vec3(0.0);
+	outColor1.a = 1.0;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/hdr/gbuffer.frag.spv b/external/Vulkan/data/shaders/glsl/hdr/gbuffer.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..57f315b32d34afe90f1405b81ae28e87653163f4
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/hdr/gbuffer.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/hdr/gbuffer.vert b/external/Vulkan/data/shaders/glsl/hdr/gbuffer.vert
new file mode 100644
index 0000000000000000000000000000000000000000..ef9f0ad76cfe1350d272ff2121cb2df3db7f1aa0
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/hdr/gbuffer.vert
@@ -0,0 +1,45 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec3 inNormal;
+
+layout (constant_id = 0) const int type = 0;
+
+layout (binding = 0) uniform UBO {
+	mat4 projection;
+	mat4 modelview;
+	mat4 inverseModelview;
+} ubo;
+
+layout (location = 0) out vec3 outUVW;
+layout (location = 1) out vec3 outPos;
+layout (location = 2) out vec3 outNormal;
+layout (location = 3) out vec3 outViewVec;
+layout (location = 4) out vec3 outLightVec;
+
+out gl_PerVertex
+{
+	vec4 gl_Position;
+};
+
+void main()
+{
+	outUVW = inPos;
+
+	switch(type) {
+		case 0: // Skybox
+			outPos = vec3(mat3(ubo.modelview) * inPos);
+			gl_Position = vec4(ubo.projection * vec4(outPos, 1.0));
+			break;
+		case 1: // Object
+			outPos = vec3(ubo.modelview * vec4(inPos, 1.0));
+			gl_Position = ubo.projection * ubo.modelview * vec4(inPos.xyz, 1.0);
+			break;
+	}
+	outPos = vec3(ubo.modelview * vec4(inPos, 1.0));
+	outNormal = mat3(ubo.modelview) * inNormal;
+
+	vec3 lightPos = vec3(0.0f, -5.0f, 5.0f);
+	outLightVec = lightPos.xyz - outPos.xyz;
+	outViewVec = -outPos.xyz;
+}
diff --git a/external/Vulkan/data/shaders/glsl/hdr/gbuffer.vert.spv b/external/Vulkan/data/shaders/glsl/hdr/gbuffer.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..6563548ce0d1b30126b81d389de28d432f7f9dd3
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/hdr/gbuffer.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/imgui/scene.frag b/external/Vulkan/data/shaders/glsl/imgui/scene.frag
new file mode 100644
index 0000000000000000000000000000000000000000..8e2935aad9048cbc862578f5db6be4f166c95e6e
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/imgui/scene.frag
@@ -0,0 +1,19 @@
+#version 450
+
+layout (location = 0) in vec3 inNormal;
+layout (location = 1) in vec3 inColor;
+layout (location = 2) in vec3 inViewVec;
+layout (location = 3) in vec3 inLightVec;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+	vec3 N = normalize(inNormal);
+	vec3 L = normalize(inLightVec);
+	vec3 V = normalize(inViewVec);
+	vec3 R = reflect(-L, N);
+	float diffuse = max(dot(N, L), 0.0);
+	vec3 specular = pow(max(dot(R, V), 0.0), 16.0) * vec3(0.75);
+	outFragColor = vec4(diffuse * inColor + specular, 1.0);		
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/imgui/scene.frag.spv b/external/Vulkan/data/shaders/glsl/imgui/scene.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..4870a1892fac5817958d3e1c86ecc86b1b6a96f3
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/imgui/scene.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/imgui/scene.vert b/external/Vulkan/data/shaders/glsl/imgui/scene.vert
new file mode 100644
index 0000000000000000000000000000000000000000..9c70822d2400d582236fc1d2194c60dbbfc8326c
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/imgui/scene.vert
@@ -0,0 +1,35 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec3 inNormal;
+layout (location = 2) in vec3 inColor;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 model;
+	vec4 lightPos;
+} ubo;
+
+layout (location = 0) out vec3 outNormal;
+layout (location = 1) out vec3 outColor;
+layout (location = 2) out vec3 outViewVec;
+layout (location = 3) out vec3 outLightVec;
+
+out gl_PerVertex
+{
+	vec4 gl_Position;
+};
+
+void main() 
+{
+	outNormal = inNormal;
+	outColor = inColor;
+	gl_Position = ubo.projection * ubo.model * vec4(inPos.xyz, 1.0);
+	
+	vec4 pos = ubo.model * vec4(inPos, 1.0);
+	outNormal = mat3(ubo.model) * inNormal;
+	vec3 lPos = mat3(ubo.model) * ubo.lightPos.xyz;
+	outLightVec = lPos - pos.xyz;
+	outViewVec = -pos.xyz;		
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/imgui/scene.vert.spv b/external/Vulkan/data/shaders/glsl/imgui/scene.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..69fc0b0e3cc1541b95f8f27b96868431d7f84dbf
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/imgui/scene.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/imgui/ui.frag b/external/Vulkan/data/shaders/glsl/imgui/ui.frag
new file mode 100644
index 0000000000000000000000000000000000000000..e0d79957f6d7c504bab818a32f22d4d95309e152
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/imgui/ui.frag
@@ -0,0 +1,13 @@
+#version 450
+
+layout (binding = 0) uniform sampler2D fontSampler;
+
+layout (location = 0) in vec2 inUV;
+layout (location = 1) in vec4 inColor;
+
+layout (location = 0) out vec4 outColor;
+
+void main() 
+{
+	outColor = inColor * texture(fontSampler, inUV);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/imgui/ui.frag.spv b/external/Vulkan/data/shaders/glsl/imgui/ui.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..147c5d2fbcb029ff1d3bcbe365ad07fec025f66a
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/imgui/ui.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/imgui/ui.vert b/external/Vulkan/data/shaders/glsl/imgui/ui.vert
new file mode 100644
index 0000000000000000000000000000000000000000..ddb63a45d4980a719b844ab256882d3f4a3121d7
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/imgui/ui.vert
@@ -0,0 +1,25 @@
+#version 450
+
+layout (location = 0) in vec2 inPos;
+layout (location = 1) in vec2 inUV;
+layout (location = 2) in vec4 inColor;
+
+layout (push_constant) uniform PushConstants {
+	vec2 scale;
+	vec2 translate;
+} pushConstants;
+
+layout (location = 0) out vec2 outUV;
+layout (location = 1) out vec4 outColor;
+
+out gl_PerVertex 
+{
+	vec4 gl_Position;   
+};
+
+void main() 
+{
+	outUV = inUV;
+	outColor = inColor;
+	gl_Position = vec4(inPos * pushConstants.scale + pushConstants.translate, 0.0, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/imgui/ui.vert.spv b/external/Vulkan/data/shaders/glsl/imgui/ui.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..f359961ffb7266e16dac9c14878ecea2f1ddfcd9
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/imgui/ui.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/indirectdraw/ground.frag b/external/Vulkan/data/shaders/glsl/indirectdraw/ground.frag
new file mode 100644
index 0000000000000000000000000000000000000000..92f2048bf4cd9de387e6334329617033973fffc4
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/indirectdraw/ground.frag
@@ -0,0 +1,12 @@
+#version 450
+
+layout (binding = 2) uniform sampler2D samplerColor;
+
+layout (location = 0) in vec2 inUV;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+	outFragColor = texture(samplerColor, inUV);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/indirectdraw/ground.frag.spv b/external/Vulkan/data/shaders/glsl/indirectdraw/ground.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..905e465367514bb38d8332561fa2f5a01b9823fb
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/indirectdraw/ground.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/indirectdraw/ground.vert b/external/Vulkan/data/shaders/glsl/indirectdraw/ground.vert
new file mode 100644
index 0000000000000000000000000000000000000000..b1aa82e6d6dfbbd43d466242ebae69a1fc79c561
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/indirectdraw/ground.vert
@@ -0,0 +1,21 @@
+#version 450
+
+// Vertex attributes
+layout (location = 0) in vec4 inPos;
+layout (location = 1) in vec3 inNormal;
+layout (location = 2) in vec2 inUV;
+layout (location = 3) in vec3 inColor;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 modelview;
+} ubo;
+
+layout (location = 0) out vec2 outUV;
+
+void main() 
+{
+	outUV = inUV * 32.0;
+	gl_Position = ubo.projection * ubo.modelview * vec4(inPos.xyz, 1.0);
+}
diff --git a/external/Vulkan/data/shaders/glsl/indirectdraw/ground.vert.spv b/external/Vulkan/data/shaders/glsl/indirectdraw/ground.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..da1441edccc71acfdfaf95cde55a1677b6cf8738
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/indirectdraw/ground.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/indirectdraw/indirectdraw.frag b/external/Vulkan/data/shaders/glsl/indirectdraw/indirectdraw.frag
new file mode 100644
index 0000000000000000000000000000000000000000..acdea70a4dd7d753c1639befe0567ec442d1d8ad
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/indirectdraw/indirectdraw.frag
@@ -0,0 +1,27 @@
+#version 450
+
+layout (binding = 1) uniform sampler2DArray samplerArray;
+
+layout (location = 0) in vec3 inNormal;
+layout (location = 1) in vec3 inColor;
+layout (location = 2) in vec3 inUV;
+layout (location = 3) in vec3 inViewVec;
+layout (location = 4) in vec3 inLightVec;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main()
+{
+	vec4 color = texture(samplerArray, inUV);
+
+	if (color.a < 0.5)
+	{
+		discard;
+	}
+
+	vec3 N = normalize(inNormal);
+	vec3 L = normalize(inLightVec);
+	vec3 ambient = vec3(0.65);
+	vec3 diffuse = max(dot(N, L), 0.0) * inColor;
+	outFragColor = vec4((ambient + diffuse) * color.rgb, 1.0);
+}
diff --git a/external/Vulkan/data/shaders/glsl/indirectdraw/indirectdraw.frag.spv b/external/Vulkan/data/shaders/glsl/indirectdraw/indirectdraw.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..cc4c82a4480fea532e8fd1c39cc0d12967b8c8c7
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/indirectdraw/indirectdraw.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/indirectdraw/indirectdraw.vert b/external/Vulkan/data/shaders/glsl/indirectdraw/indirectdraw.vert
new file mode 100644
index 0000000000000000000000000000000000000000..ce081731a3fd8661a73c8ea5ddbbce58e76ef54b
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/indirectdraw/indirectdraw.vert
@@ -0,0 +1,73 @@
+#version 450
+
+// Vertex attributes
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec3 inNormal;
+layout (location = 2) in vec2 inUV;
+layout (location = 3) in vec3 inColor;
+
+// Instanced attributes
+layout (location = 4) in vec3 instancePos;
+layout (location = 5) in vec3 instanceRot;
+layout (location = 6) in float instanceScale;
+layout (location = 7) in int instanceTexIndex;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 modelview;
+} ubo;
+
+layout (location = 0) out vec3 outNormal;
+layout (location = 1) out vec3 outColor;
+layout (location = 2) out vec3 outUV;
+layout (location = 3) out vec3 outViewVec;
+layout (location = 4) out vec3 outLightVec;
+
+void main() 
+{
+	outColor = inColor;
+	outUV = vec3(inUV, instanceTexIndex);
+
+	mat4 mx, my, mz;
+	
+	// rotate around x
+	float s = sin(instanceRot.x);
+	float c = cos(instanceRot.x);
+
+	mx[0] = vec4(c, s, 0.0, 0.0);
+	mx[1] = vec4(-s, c, 0.0, 0.0);
+	mx[2] = vec4(0.0, 0.0, 1.0, 0.0);
+	mx[3] = vec4(0.0, 0.0, 0.0, 1.0);	
+	
+	// rotate around y
+	s = sin(instanceRot.y);
+	c = cos(instanceRot.y);
+
+	my[0] = vec4(c, 0.0, s, 0.0);
+	my[1] = vec4(0.0, 1.0, 0.0, 0.0);
+	my[2] = vec4(-s, 0.0, c, 0.0);
+	my[3] = vec4(0.0, 0.0, 0.0, 1.0);	
+	
+	// rot around z
+	s = sin(instanceRot.z);
+	c = cos(instanceRot.z);	
+	
+	mz[0] = vec4(1.0, 0.0, 0.0, 0.0);
+	mz[1] = vec4(0.0, c, s, 0.0);
+	mz[2] = vec4(0.0, -s, c, 0.0);
+	mz[3] = vec4(0.0, 0.0, 0.0, 1.0);	
+	
+	mat4 rotMat = mz * my * mx;
+		
+	outNormal = inNormal * mat3(rotMat);
+	
+	vec4 pos = vec4((inPos.xyz * instanceScale) + instancePos, 1.0) * rotMat;
+
+	gl_Position = ubo.projection * ubo.modelview * pos;
+	
+	vec4 wPos = ubo.modelview * vec4(pos.xyz, 1.0); 
+	vec4 lPos = vec4(0.0, -5.0, 0.0, 1.0);
+	outLightVec = lPos.xyz - pos.xyz;
+	outViewVec = -pos.xyz;	
+}
diff --git a/external/Vulkan/data/shaders/glsl/indirectdraw/indirectdraw.vert.spv b/external/Vulkan/data/shaders/glsl/indirectdraw/indirectdraw.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..f2e2596fac1a5d84fa5db2f87a0e2afb510c677b
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/indirectdraw/indirectdraw.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/indirectdraw/skysphere.frag b/external/Vulkan/data/shaders/glsl/indirectdraw/skysphere.frag
new file mode 100644
index 0000000000000000000000000000000000000000..349e02e198c59222369d25256c678d4e6e2692a9
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/indirectdraw/skysphere.frag
@@ -0,0 +1,14 @@
+#version 450
+
+layout (binding = 2) uniform sampler2D samplerColor;
+
+layout (location = 0) in vec2 inUV;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+	const vec4 gradientStart = vec4(0.93, 0.9, 0.81, 1.0);
+	const vec4 gradientEnd = vec4(0.35, 0.5, 1.0, 1.0);
+	outFragColor = mix(gradientStart, gradientEnd, min(0.5 - (inUV.t + 0.05), 0.5)/0.15 + 0.5);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/indirectdraw/skysphere.frag.spv b/external/Vulkan/data/shaders/glsl/indirectdraw/skysphere.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..bdb5e3db83c2eb7922e96b50a6a1d986f1d38449
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/indirectdraw/skysphere.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/indirectdraw/skysphere.vert b/external/Vulkan/data/shaders/glsl/indirectdraw/skysphere.vert
new file mode 100644
index 0000000000000000000000000000000000000000..c45acc9912e9f3ddc1cf25ac93c10c9f3358f111
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/indirectdraw/skysphere.vert
@@ -0,0 +1,20 @@
+#version 450
+
+// Vertex attributes
+layout (location = 0) in vec4 inPos;
+layout (location = 2) in vec2 inUV;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 modelview;
+} ubo;
+
+layout (location = 0) out vec2 outUV;
+
+void main() 
+{
+	outUV = inUV;
+	// Skysphere always at center, only use rotation part of modelview matrix
+	gl_Position = ubo.projection * mat4(mat3(ubo.modelview)) * vec4(inPos.xyz, 1.0);
+}
diff --git a/external/Vulkan/data/shaders/glsl/indirectdraw/skysphere.vert.spv b/external/Vulkan/data/shaders/glsl/indirectdraw/skysphere.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..d889ea21bfa6c4a75b78949105ba063e41f9f6da
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/indirectdraw/skysphere.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/inlineuniformblocks/pbr.frag b/external/Vulkan/data/shaders/glsl/inlineuniformblocks/pbr.frag
new file mode 100644
index 0000000000000000000000000000000000000000..6c237e7651875f8c2867c3a726d4b79732e42e29
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/inlineuniformblocks/pbr.frag
@@ -0,0 +1,116 @@
+#version 450
+
+layout (location = 0) in vec3 inWorldPos;
+layout (location = 1) in vec3 inNormal;
+
+layout (set = 0, binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 model;
+	mat4 view;
+	vec3 camPos;
+} ubo;
+
+// Inline uniform block
+layout (set = 1, binding = 0) uniform UniformInline {
+	float roughness;
+	float metallic;
+	float r;
+	float g;
+	float b;
+	float ambient;
+} material;
+
+layout (location = 0) out vec4 outColor;
+
+const float PI = 3.14159265359;
+
+vec3 materialcolor()
+{
+	return vec3(material.r, material.g, material.b);
+}
+
+// Normal Distribution function --------------------------------------
+float D_GGX(float dotNH, float roughness)
+{
+	float alpha = roughness * roughness;
+	float alpha2 = alpha * alpha;
+	float denom = dotNH * dotNH * (alpha2 - 1.0) + 1.0;
+	return (alpha2)/(PI * denom*denom); 
+}
+
+// Geometric Shadowing function --------------------------------------
+float G_SchlicksmithGGX(float dotNL, float dotNV, float roughness)
+{
+	float r = (roughness + 1.0);
+	float k = (r*r) / 8.0;
+	float GL = dotNL / (dotNL * (1.0 - k) + k);
+	float GV = dotNV / (dotNV * (1.0 - k) + k);
+	return GL * GV;
+}
+
+// Fresnel function ----------------------------------------------------
+vec3 F_Schlick(float cosTheta, float metallic)
+{
+	vec3 F0 = mix(vec3(0.04), materialcolor(), metallic); // * material.specular
+	vec3 F = F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0); 
+	return F;    
+}
+
+// Specular BRDF composition --------------------------------------------
+
+vec3 BRDF(vec3 L, vec3 V, vec3 N, float metallic, float roughness)
+{
+	// Precalculate vectors and dot products	
+	vec3 H = normalize (V + L);
+	float dotNV = clamp(dot(N, V), 0.0, 1.0);
+	float dotNL = clamp(dot(N, L), 0.0, 1.0);
+	float dotLH = clamp(dot(L, H), 0.0, 1.0);
+	float dotNH = clamp(dot(N, H), 0.0, 1.0);
+
+	// Light color fixed
+	vec3 lightColor = vec3(1.0);
+
+	vec3 color = vec3(0.0);
+
+	if (dotNL > 0.0)
+	{
+		float rroughness = max(0.05, roughness);
+		// D = Normal distribution (Distribution of the microfacets)
+		float D = D_GGX(dotNH, rroughness); 
+		// G = Geometric shadowing term (Microfacets shadowing)
+		float G = G_SchlicksmithGGX(dotNL, dotNV, rroughness);
+		// F = Fresnel factor (Reflectance depending on angle of incidence)
+		vec3 F = F_Schlick(dotNV, metallic);
+
+		vec3 spec = D * F * G / (4.0 * dotNL * dotNV);
+
+		color += spec * dotNL * lightColor;
+	}
+
+	return color;
+}
+
+// ----------------------------------------------------------------------------
+void main()
+{		  
+	vec3 N = normalize(inNormal);
+	vec3 V = normalize(ubo.camPos - inWorldPos);
+
+	float roughness = material.roughness;
+
+	// Specular contribution
+	vec3 lightPos = vec3(0.0f, 0.0f, 10.0f);
+	vec3 Lo = vec3(0.0);
+	vec3 L = normalize(lightPos.xyz - inWorldPos);
+	Lo += BRDF(L, V, N, material.metallic, roughness);
+
+	// Combine with ambient
+	vec3 color = materialcolor() * material.ambient;
+	color += Lo;
+
+	// Gamma correct
+	color = pow(color, vec3(0.4545));
+
+	outColor = vec4(color, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/inlineuniformblocks/pbr.frag.spv b/external/Vulkan/data/shaders/glsl/inlineuniformblocks/pbr.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..ce5ec66d55305c2a6c5a632cf76b193aac51d6b7
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/inlineuniformblocks/pbr.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/inlineuniformblocks/pbr.vert b/external/Vulkan/data/shaders/glsl/inlineuniformblocks/pbr.vert
new file mode 100644
index 0000000000000000000000000000000000000000..8edb8b249ba874ef8439456527d5e6fd4cc7349b
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/inlineuniformblocks/pbr.vert
@@ -0,0 +1,27 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec3 inNormal;
+
+layout (set = 0, binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 model;
+	mat4 view;
+	vec3 camPos;
+} ubo;
+
+layout (location = 0) out vec3 outWorldPos;
+layout (location = 1) out vec3 outNormal;
+
+layout(push_constant) uniform PushConsts {
+	vec3 objPos;
+} pushConsts;
+
+void main() 
+{
+	vec3 locPos = vec3(ubo.model * vec4(inPos, 1.0));
+	outWorldPos = locPos + pushConsts.objPos;
+	outNormal = mat3(ubo.model) * inNormal;
+	gl_Position =  ubo.projection * ubo.view * vec4(outWorldPos, 1.0);
+}
diff --git a/external/Vulkan/data/shaders/glsl/inlineuniformblocks/pbr.vert.spv b/external/Vulkan/data/shaders/glsl/inlineuniformblocks/pbr.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..ee5168183e3e0dd3d382844892494fa592b73c7b
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/inlineuniformblocks/pbr.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/inputattachments/attachmentread.frag b/external/Vulkan/data/shaders/glsl/inputattachments/attachmentread.frag
new file mode 100644
index 0000000000000000000000000000000000000000..685a619a97712949eeffccdc4f5838407c33a387
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/inputattachments/attachmentread.frag
@@ -0,0 +1,33 @@
+#version 450
+
+layout (input_attachment_index = 0, binding = 0) uniform subpassInput inputColor;
+layout (input_attachment_index = 1, binding = 1) uniform subpassInput inputDepth;
+
+layout (binding = 2) uniform UBO {
+	vec2 brightnessContrast;
+	vec2 range;
+	int attachmentIndex;
+} ubo;
+
+layout (location = 0) out vec4 outColor;
+
+vec3 brightnessContrast(vec3 color, float brightness, float contrast) {
+	return (color - 0.5) * contrast + 0.5 + brightness;
+}
+
+void main() 
+{
+	// Apply brightness and contrast filer to color input
+	if (ubo.attachmentIndex == 0) {
+		// Read color from previous color input attachment
+		vec3 color = subpassLoad(inputColor).rgb;
+		outColor.rgb = brightnessContrast(color, ubo.brightnessContrast[0], ubo.brightnessContrast[1]);
+	}
+
+	// Visualize depth input range
+	if (ubo.attachmentIndex == 1) {
+		// Read depth from previous depth input attachment
+		float depth = subpassLoad(inputDepth).r;
+		outColor.rgb = vec3((depth - ubo.range[0]) * 1.0 / (ubo.range[1] - ubo.range[0]));
+	}
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/inputattachments/attachmentread.frag.spv b/external/Vulkan/data/shaders/glsl/inputattachments/attachmentread.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..5c4575f417d68106955807a72170e429d169c0fc
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/inputattachments/attachmentread.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/inputattachments/attachmentread.vert b/external/Vulkan/data/shaders/glsl/inputattachments/attachmentread.vert
new file mode 100644
index 0000000000000000000000000000000000000000..d65ceaa501608b9103abdf5a69e126ef0f48d088
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/inputattachments/attachmentread.vert
@@ -0,0 +1,10 @@
+#version 450
+
+out gl_PerVertex {
+	vec4 gl_Position;
+};
+
+void main() 
+{
+	gl_Position = vec4(vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2) * 2.0f - 1.0f, 0.0f, 1.0f);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/inputattachments/attachmentread.vert.spv b/external/Vulkan/data/shaders/glsl/inputattachments/attachmentread.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..3859ed6e8f12da477a77f59bd468eb179bac22fe
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/inputattachments/attachmentread.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/inputattachments/attachmentwrite.frag b/external/Vulkan/data/shaders/glsl/inputattachments/attachmentwrite.frag
new file mode 100644
index 0000000000000000000000000000000000000000..69e1b6b7238099125e1e1bd8d762986093f6e324
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/inputattachments/attachmentwrite.frag
@@ -0,0 +1,23 @@
+#version 450
+
+layout (location = 0) in vec3 inColor;
+layout (location = 1) in vec3 inNormal;
+layout (location = 2) in vec3 inViewVec;
+layout (location = 3) in vec3 inLightVec;
+
+layout (location = 0) out vec4 outColor;
+
+void main() 
+{
+	// Toon shading color attachment output
+	float intensity = dot(normalize(inNormal), normalize(inLightVec));
+	float shade = 1.0;
+	shade = intensity < 0.5 ? 0.75 : shade;
+	shade = intensity < 0.35 ? 0.6 : shade;
+	shade = intensity < 0.25 ? 0.5 : shade;
+	shade = intensity < 0.1 ? 0.25 : shade;
+
+	outColor.rgb = inColor * 3.0 * shade;
+
+	// Depth attachment does not need to be explicitly written
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/inputattachments/attachmentwrite.frag.spv b/external/Vulkan/data/shaders/glsl/inputattachments/attachmentwrite.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..cfb4a18e9f98e7efb14ef8643a09d266e8cad412
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/inputattachments/attachmentwrite.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/inputattachments/attachmentwrite.vert b/external/Vulkan/data/shaders/glsl/inputattachments/attachmentwrite.vert
new file mode 100644
index 0000000000000000000000000000000000000000..c5b82566f947c2d70bb7ed89381916ddebd4a2d4
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/inputattachments/attachmentwrite.vert
@@ -0,0 +1,29 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec3 inColor;
+layout (location = 2) in vec3 inNormal;
+
+layout (binding = 0) uniform UBO {
+	mat4 projection;
+	mat4 model;
+	mat4 view;
+} ubo;
+
+layout (location = 0) out vec3 outColor;
+layout (location = 1) out vec3 outNormal;
+layout (location = 2) out vec3 outViewVec;
+layout (location = 3) out vec3 outLightVec;
+
+out gl_PerVertex {
+	vec4 gl_Position;
+};
+
+void main() 
+{
+	gl_Position = ubo.projection * ubo.view * ubo.model * vec4(inPos, 1.0);
+	outColor = inColor;
+	outNormal = inNormal;
+	outLightVec = vec3(0.0f, 5.0f, 15.0f) - inPos;
+	outViewVec = -inPos.xyz;		
+}
diff --git a/external/Vulkan/data/shaders/glsl/inputattachments/attachmentwrite.vert.spv b/external/Vulkan/data/shaders/glsl/inputattachments/attachmentwrite.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..6a52830575268bc5fed78590cc6e6713504d935a
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/inputattachments/attachmentwrite.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/instancing/instancing.frag b/external/Vulkan/data/shaders/glsl/instancing/instancing.frag
new file mode 100644
index 0000000000000000000000000000000000000000..73beb4c2b6da15b91713e1af0547cfb53a7eca1f
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/instancing/instancing.frag
@@ -0,0 +1,23 @@
+#version 450
+
+layout (binding = 1) uniform sampler2DArray samplerArray;
+
+layout (location = 0) in vec3 inNormal;
+layout (location = 1) in vec3 inColor;
+layout (location = 2) in vec3 inUV;
+layout (location = 3) in vec3 inViewVec;
+layout (location = 4) in vec3 inLightVec;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+	vec4 color = texture(samplerArray, inUV) * vec4(inColor, 1.0);	
+	vec3 N = normalize(inNormal);
+	vec3 L = normalize(inLightVec);
+	vec3 V = normalize(inViewVec);
+	vec3 R = reflect(-L, N);
+	vec3 diffuse = max(dot(N, L), 0.1) * inColor;
+	vec3 specular = (dot(N,L) > 0.0) ? pow(max(dot(R, V), 0.0), 16.0) * vec3(0.75) * color.r : vec3(0.0);
+	outFragColor = vec4(diffuse * color.rgb + specular, 1.0);		
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/instancing/instancing.frag.spv b/external/Vulkan/data/shaders/glsl/instancing/instancing.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..243925488c1802057bce68c5ffbaac28e22fb1b4
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/instancing/instancing.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/instancing/instancing.vert b/external/Vulkan/data/shaders/glsl/instancing/instancing.vert
new file mode 100644
index 0000000000000000000000000000000000000000..78d750a6a732b4495bac5d3f61de27679caa41c8
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/instancing/instancing.vert
@@ -0,0 +1,81 @@
+#version 450
+
+// Vertex attributes
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec3 inNormal;
+layout (location = 2) in vec2 inUV;
+layout (location = 3) in vec3 inColor;
+
+// Instanced attributes
+layout (location = 4) in vec3 instancePos;
+layout (location = 5) in vec3 instanceRot;
+layout (location = 6) in float instanceScale;
+layout (location = 7) in int instanceTexIndex;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 modelview;
+	vec4 lightPos;
+	float locSpeed;
+	float globSpeed;
+} ubo;
+
+layout (location = 0) out vec3 outNormal;
+layout (location = 1) out vec3 outColor;
+layout (location = 2) out vec3 outUV;
+layout (location = 3) out vec3 outViewVec;
+layout (location = 4) out vec3 outLightVec;
+
+void main() 
+{
+	outColor = inColor;
+	outUV = vec3(inUV, instanceTexIndex);
+
+	mat3 mx, my, mz;
+	
+	// rotate around x
+	float s = sin(instanceRot.x + ubo.locSpeed);
+	float c = cos(instanceRot.x + ubo.locSpeed);
+
+	mx[0] = vec3(c, s, 0.0);
+	mx[1] = vec3(-s, c, 0.0);
+	mx[2] = vec3(0.0, 0.0, 1.0);
+	
+	// rotate around y
+	s = sin(instanceRot.y + ubo.locSpeed);
+	c = cos(instanceRot.y + ubo.locSpeed);
+
+	my[0] = vec3(c, 0.0, s);
+	my[1] = vec3(0.0, 1.0, 0.0);
+	my[2] = vec3(-s, 0.0, c);
+	
+	// rot around z
+	s = sin(instanceRot.z + ubo.locSpeed);
+	c = cos(instanceRot.z + ubo.locSpeed);	
+	
+	mz[0] = vec3(1.0, 0.0, 0.0);
+	mz[1] = vec3(0.0, c, s);
+	mz[2] = vec3(0.0, -s, c);
+	
+	mat3 rotMat = mz * my * mx;
+
+	mat4 gRotMat;
+	s = sin(instanceRot.y + ubo.globSpeed);
+	c = cos(instanceRot.y + ubo.globSpeed);
+	gRotMat[0] = vec4(c, 0.0, s, 0.0);
+	gRotMat[1] = vec4(0.0, 1.0, 0.0, 0.0);
+	gRotMat[2] = vec4(-s, 0.0, c, 0.0);
+	gRotMat[3] = vec4(0.0, 0.0, 0.0, 1.0);	
+	
+	vec4 locPos = vec4(inPos.xyz * rotMat, 1.0);
+	vec4 pos = vec4((locPos.xyz * instanceScale) + instancePos, 1.0);
+
+	gl_Position = ubo.projection * ubo.modelview * gRotMat * pos;
+	outNormal = mat3(ubo.modelview * gRotMat) * inverse(rotMat) * inNormal;
+
+	pos = ubo.modelview * vec4(inPos.xyz + instancePos, 1.0);
+	vec3 lPos = mat3(ubo.modelview) * ubo.lightPos.xyz;
+	outLightVec = lPos - pos.xyz;
+	outViewVec = -pos.xyz;		
+}
diff --git a/external/Vulkan/data/shaders/glsl/instancing/instancing.vert.spv b/external/Vulkan/data/shaders/glsl/instancing/instancing.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..4fe79ffc7b010e900e712dcce1c0bf082adad151
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/instancing/instancing.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/instancing/planet.frag b/external/Vulkan/data/shaders/glsl/instancing/planet.frag
new file mode 100644
index 0000000000000000000000000000000000000000..81926b90aa2844d5cec87fbe4a2e382444185927
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/instancing/planet.frag
@@ -0,0 +1,23 @@
+#version 450
+
+layout (binding = 1) uniform sampler2D samplerColorMap;
+
+layout (location = 0) in vec3 inNormal;
+layout (location = 1) in vec3 inColor;
+layout (location = 2) in vec2 inUV;
+layout (location = 3) in vec3 inViewVec;
+layout (location = 4) in vec3 inLightVec;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+	vec4 color = texture(samplerColorMap, inUV) * vec4(inColor, 1.0) * 1.5;
+	vec3 N = normalize(inNormal);
+	vec3 L = normalize(inLightVec);
+	vec3 V = normalize(inViewVec);
+	vec3 R = reflect(-L, N);
+	vec3 diffuse = max(dot(N, L), 0.0) * inColor;
+	vec3 specular = pow(max(dot(R, V), 0.0), 4.0) * vec3(0.5) * color.r;
+	outFragColor = vec4(diffuse * color.rgb + specular, 1.0);		
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/instancing/planet.frag.spv b/external/Vulkan/data/shaders/glsl/instancing/planet.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..f6db777e758f1b4b5d153fcdfa7fbab1b65683d7
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/instancing/planet.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/instancing/planet.vert b/external/Vulkan/data/shaders/glsl/instancing/planet.vert
new file mode 100644
index 0000000000000000000000000000000000000000..8638d23c9005f86b4a4c2333c48271ac40257704
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/instancing/planet.vert
@@ -0,0 +1,32 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec3 inNormal;
+layout (location = 2) in vec2 inUV;
+layout (location = 3) in vec3 inColor;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 modelview;
+	vec4 lightPos;
+} ubo;
+
+layout (location = 0) out vec3 outNormal;
+layout (location = 1) out vec3 outColor;
+layout (location = 2) out vec2 outUV;
+layout (location = 3) out vec3 outViewVec;
+layout (location = 4) out vec3 outLightVec;
+
+void main() 
+{
+	outColor = inColor;
+	outUV = inUV;
+	gl_Position = ubo.projection * ubo.modelview * vec4(inPos.xyz, 1.0);
+	
+	vec4 pos = ubo.modelview * vec4(inPos, 1.0);
+	outNormal = mat3(ubo.modelview) * inNormal;
+	vec3 lPos = mat3(ubo.modelview) * ubo.lightPos.xyz;
+	outLightVec = lPos - pos.xyz;
+	outViewVec = -pos.xyz;		
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/instancing/planet.vert.spv b/external/Vulkan/data/shaders/glsl/instancing/planet.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..80a8401fc9aadb92774af303c66b33db44ba66cf
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/instancing/planet.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/instancing/starfield.frag b/external/Vulkan/data/shaders/glsl/instancing/starfield.frag
new file mode 100644
index 0000000000000000000000000000000000000000..87c53d82e8d5c6ec9a4461987050deb8f82c43d2
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/instancing/starfield.frag
@@ -0,0 +1,34 @@
+#version 450
+
+layout (location = 0) in vec3 inUVW;
+
+layout (location = 0) out vec4 outFragColor;
+
+#define HASHSCALE3 vec3(443.897, 441.423, 437.195)
+#define STARFREQUENCY 0.01
+
+// Hash function by Dave Hoskins (https://www.shadertoy.com/view/4djSRW)
+float hash33(vec3 p3)
+{
+	p3 = fract(p3 * HASHSCALE3);
+	p3 += dot(p3, p3.yxz+vec3(19.19));
+	return fract((p3.x + p3.y)*p3.z + (p3.x+p3.z)*p3.y + (p3.y+p3.z)*p3.x);
+}
+
+vec3 starField(vec3 pos)
+{
+	vec3 color = vec3(0.0);
+	float threshhold = (1.0 - STARFREQUENCY);
+	float rnd = hash33(pos);
+	if (rnd >= threshhold)
+	{
+		float starCol = pow((rnd - threshhold) / (1.0 - threshhold), 16.0);
+		color += vec3(starCol);
+	}	
+	return color;
+}
+
+void main() 
+{
+	outFragColor = vec4(starField(inUVW), 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/instancing/starfield.frag.spv b/external/Vulkan/data/shaders/glsl/instancing/starfield.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..8b9dab3e2ac5256f18f9e1426beaffaac2d9f2c1
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/instancing/starfield.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/instancing/starfield.vert b/external/Vulkan/data/shaders/glsl/instancing/starfield.vert
new file mode 100644
index 0000000000000000000000000000000000000000..381e249f016c4946c506cf21139bebd64c1b2e42
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/instancing/starfield.vert
@@ -0,0 +1,9 @@
+#version 450
+
+layout (location = 0) out vec3 outUVW;
+
+void main() 
+{
+	outUVW = vec3((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2, gl_VertexIndex & 2);
+	gl_Position = vec4(outUVW.st * 2.0f - 1.0f, 0.0f, 1.0f);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/instancing/starfield.vert.spv b/external/Vulkan/data/shaders/glsl/instancing/starfield.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..27b5ccbe4cf2d7e21b4e9263f690f91a43f819bf
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/instancing/starfield.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/multisampling/mesh.frag b/external/Vulkan/data/shaders/glsl/multisampling/mesh.frag
new file mode 100644
index 0000000000000000000000000000000000000000..fb415cefddfbb59ceef7c3263fb74c7f9321f09c
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/multisampling/mesh.frag
@@ -0,0 +1,24 @@
+#version 450
+
+layout (set = 1, binding = 0) uniform sampler2D samplerColorMap;
+
+layout (location = 0) in vec3 inNormal;
+layout (location = 1) in vec3 inColor;
+layout (location = 2) in vec2 inUV;
+layout (location = 3) in vec3 inViewVec;
+layout (location = 4) in vec3 inLightVec;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+	vec4 color = texture(samplerColorMap, inUV) * vec4(inColor, 1.0);
+
+	vec3 N = normalize(inNormal);
+	vec3 L = normalize(inLightVec);
+	vec3 V = normalize(inViewVec);
+	vec3 R = reflect(-L, N);
+	vec3 diffuse = max(dot(N, L), 0.15) * inColor;
+	vec3 specular = pow(max(dot(R, V), 0.0), 16.0) * vec3(0.75);
+	outFragColor = vec4(diffuse * color.rgb + specular, 1.0);		
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/multisampling/mesh.frag.spv b/external/Vulkan/data/shaders/glsl/multisampling/mesh.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..5cd562653a43a2f4e3575be29980fd2deef432dd
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/multisampling/mesh.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/multisampling/mesh.vert b/external/Vulkan/data/shaders/glsl/multisampling/mesh.vert
new file mode 100644
index 0000000000000000000000000000000000000000..2e742bf17677ecfd1bc4ffe322e5c425885b579d
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/multisampling/mesh.vert
@@ -0,0 +1,33 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec3 inNormal;
+layout (location = 2) in vec2 inUV;
+layout (location = 3) in vec3 inColor;
+
+layout (set = 0, binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 model;
+	vec4 lightPos;
+} ubo;
+
+layout (location = 0) out vec3 outNormal;
+layout (location = 1) out vec3 outColor;
+layout (location = 2) out vec2 outUV;
+layout (location = 3) out vec3 outViewVec;
+layout (location = 4) out vec3 outLightVec;
+
+void main() 
+{
+	outNormal = inNormal;
+	outColor = inColor;
+	outUV = inUV;
+	gl_Position = ubo.projection * ubo.model * vec4(inPos.xyz, 1.0);
+	
+	vec4 pos = ubo.model * vec4(inPos, 1.0);
+	outNormal = mat3(ubo.model) * inNormal;
+	vec3 lPos = mat3(ubo.model) * ubo.lightPos.xyz;
+	outLightVec = lPos - pos.xyz;
+	outViewVec = -pos.xyz;		
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/multisampling/mesh.vert.spv b/external/Vulkan/data/shaders/glsl/multisampling/mesh.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..d96f39264fc5b3947f13c85b859305fd5795324e
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/multisampling/mesh.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/multithreading/phong.frag b/external/Vulkan/data/shaders/glsl/multithreading/phong.frag
new file mode 100644
index 0000000000000000000000000000000000000000..274cd0635deef09242e017f80e934cd0f46fff2f
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/multithreading/phong.frag
@@ -0,0 +1,20 @@
+#version 450
+
+layout (location = 0) in vec3 inNormal;
+layout (location = 1) in vec3 inColor;
+layout (location = 3) in vec3 inViewVec;
+layout (location = 4) in vec3 inLightVec;
+
+layout (location = 0) out vec4 outFragColor;
+
+
+void main() 
+{
+	vec3 N = normalize(inNormal);
+	vec3 L = normalize(inLightVec);
+	vec3 V = normalize(inViewVec);
+	vec3 R = reflect(-L, N);
+	vec3 diffuse = max(dot(N, L), 0.0) * inColor;
+	vec3 specular = pow(max(dot(R, V), 0.0), 8.0) * vec3(0.75);
+	outFragColor = vec4(diffuse + specular, 1.0);	
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/multithreading/phong.frag.spv b/external/Vulkan/data/shaders/glsl/multithreading/phong.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..31922bcbb6d85d13d726cc6fdc64d37189f607ba
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/multithreading/phong.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/multithreading/phong.vert b/external/Vulkan/data/shaders/glsl/multithreading/phong.vert
new file mode 100644
index 0000000000000000000000000000000000000000..a65a6b2bad6db9efd7a12c32406ada8ab93c8dc7
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/multithreading/phong.vert
@@ -0,0 +1,39 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec3 inNormal;
+layout (location = 2) in vec3 inColor;
+
+layout (std140, push_constant) uniform PushConsts 
+{
+	mat4 mvp;
+	vec3 color;
+} pushConsts;
+
+layout (location = 0) out vec3 outNormal;
+layout (location = 1) out vec3 outColor;
+layout (location = 3) out vec3 outViewVec;
+layout (location = 4) out vec3 outLightVec;
+
+void main() 
+{
+	outNormal = inNormal;
+
+	if ( (inColor.r == 1.0) && (inColor.g == 0.0) && (inColor.b == 0.0))
+	{	
+		outColor = pushConsts.color;
+	}
+	else
+	{
+		outColor = inColor;
+	}
+	
+	gl_Position = pushConsts.mvp * vec4(inPos.xyz, 1.0);
+	
+    vec4 pos = pushConsts.mvp * vec4(inPos, 1.0);
+    outNormal = mat3(pushConsts.mvp) * inNormal;
+//	vec3 lPos = ubo.lightPos.xyz;
+vec3 lPos = vec3(0.0);
+    outLightVec = lPos - pos.xyz;
+    outViewVec = -pos.xyz;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/multithreading/phong.vert.spv b/external/Vulkan/data/shaders/glsl/multithreading/phong.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..84ca10a3ca3eefc951b83448b3a193a542a414a9
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/multithreading/phong.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/multithreading/starsphere.frag b/external/Vulkan/data/shaders/glsl/multithreading/starsphere.frag
new file mode 100644
index 0000000000000000000000000000000000000000..c61301f3bdb78545cbb899bc48ec1412bb9df44c
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/multithreading/starsphere.frag
@@ -0,0 +1,39 @@
+#version 450
+
+layout (location = 0) in vec3 inUVW;
+
+layout (location = 0) out vec4 outFragColor;
+
+#define HASHSCALE3 vec3(443.897, 441.423, 437.195)
+#define STARFREQUENCY 0.01
+
+// Hash function by Dave Hoskins (https://www.shadertoy.com/view/4djSRW)
+float hash33(vec3 p3)
+{
+	p3 = fract(p3 * HASHSCALE3);
+    p3 += dot(p3, p3.yxz+vec3(19.19));
+    return fract((p3.x + p3.y)*p3.z + (p3.x+p3.z)*p3.y + (p3.y+p3.z)*p3.x);
+}
+
+vec3 starField(vec3 pos)
+{
+	vec3 color = vec3(0.0);
+    float threshhold = (1.0 - STARFREQUENCY);
+    float rnd = hash33(pos);
+    if (rnd >= threshhold)
+    {
+        float starCol = pow((rnd - threshhold) / (1.0 - threshhold), 16.0);
+		color += vec3(starCol);
+    }	
+	return color;
+}
+
+void main() 
+{
+	// Fake atmosphere at the bottom
+	vec3 atmosphere = clamp(vec3(0.1, 0.15, 0.4) * (inUVW.t + 0.25), 0.0, 1.0);
+
+	vec3 color = starField(inUVW) + atmosphere;
+	
+	outFragColor = vec4(color, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/multithreading/starsphere.frag.spv b/external/Vulkan/data/shaders/glsl/multithreading/starsphere.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..6505ae2fe19b62583256bc52c22637231ac380b8
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/multithreading/starsphere.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/multithreading/starsphere.vert b/external/Vulkan/data/shaders/glsl/multithreading/starsphere.vert
new file mode 100644
index 0000000000000000000000000000000000000000..3ff9b4b17de7849681a032b18afafd240e3d3eeb
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/multithreading/starsphere.vert
@@ -0,0 +1,16 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+
+layout (std140, push_constant) uniform PushConsts 
+{
+	mat4 mvp;
+} pushConsts;
+
+layout (location = 0) out vec3 outUVW;
+
+void main() 
+{
+	outUVW = inPos;
+	gl_Position = pushConsts.mvp * vec4(inPos.xyz, 1.0);
+}
diff --git a/external/Vulkan/data/shaders/glsl/multithreading/starsphere.vert.spv b/external/Vulkan/data/shaders/glsl/multithreading/starsphere.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..1a98a97a6bfad1289837d42a08c7c488fd095870
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/multithreading/starsphere.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/multiview/multiview.frag b/external/Vulkan/data/shaders/glsl/multiview/multiview.frag
new file mode 100644
index 0000000000000000000000000000000000000000..94d7f9a3aa3b03a564107b4d6b21f50bd546e2a7
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/multiview/multiview.frag
@@ -0,0 +1,20 @@
+#version 450
+
+layout (location = 0) in vec3 inNormal;
+layout (location = 1) in vec3 inColor;
+layout (location = 2) in vec3 inViewVec;
+layout (location = 3) in vec3 inLightVec;
+
+layout (location = 0) out vec4 outColor;
+
+void main() 
+{
+	vec3 N = normalize(inNormal);
+	vec3 L = normalize(inLightVec);
+	vec3 V = normalize(inViewVec);
+	vec3 R = reflect(-L, N);
+	vec3 ambient = vec3(0.1);
+	vec3 diffuse = max(dot(N, L), 0.0) * vec3(1.0);
+	vec3 specular = pow(max(dot(R, V), 0.0), 16.0) * vec3(0.75);
+	outColor = vec4((ambient + diffuse) * inColor.rgb + specular, 1.0);		
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/multiview/multiview.frag.spv b/external/Vulkan/data/shaders/glsl/multiview/multiview.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..1eae642c5f92b14ea02a9d31a97ada9064663ffc
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/multiview/multiview.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/multiview/multiview.vert b/external/Vulkan/data/shaders/glsl/multiview/multiview.vert
new file mode 100644
index 0000000000000000000000000000000000000000..14a6dcc5f52a91d8155fdecb61b2bf394fac4416
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/multiview/multiview.vert
@@ -0,0 +1,35 @@
+#version 450
+
+#extension GL_EXT_multiview : enable
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec3 inNormal;
+layout (location = 2) in vec3 inColor;
+
+layout (location = 0) out vec3 outNormal;
+layout (location = 1) out vec3 outColor;
+layout (location = 2) out vec3 outViewVec;
+layout (location = 3) out vec3 outLightVec;
+
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection[2];
+	mat4 modelview[2];
+	vec4 lightPos;
+} ubo;
+
+void main() 
+{
+	outColor = inColor;
+	outNormal = mat3(ubo.modelview[gl_ViewIndex]) * inNormal;
+
+	vec4 pos = vec4(inPos.xyz, 1.0);
+	vec4 worldPos = ubo.modelview[gl_ViewIndex] * pos;
+		
+	vec3 lPos = vec3(ubo.modelview[gl_ViewIndex] * ubo.lightPos);
+	outLightVec = lPos - worldPos.xyz;
+	outViewVec = -worldPos.xyz;	
+
+	gl_Position = ubo.projection[gl_ViewIndex] * worldPos;
+}
diff --git a/external/Vulkan/data/shaders/glsl/multiview/multiview.vert.spv b/external/Vulkan/data/shaders/glsl/multiview/multiview.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..d217329b48cf41fe417b0c5d9cc27e06ec76cf97
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/multiview/multiview.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/multiview/viewdisplay.frag b/external/Vulkan/data/shaders/glsl/multiview/viewdisplay.frag
new file mode 100644
index 0000000000000000000000000000000000000000..0549b8518fea72836ac6e9e062e46b43e64a9570
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/multiview/viewdisplay.frag
@@ -0,0 +1,25 @@
+#version 450
+
+layout (binding = 1) uniform sampler2DArray samplerView;
+
+layout (binding = 0) uniform UBO 
+{
+	layout(offset = 272) float distortionAlpha;
+} ubo;
+
+layout (location = 0) in vec2 inUV;
+layout (location = 0) out vec4 outColor;
+
+layout (constant_id = 0) const float VIEW_LAYER = 0.0f;
+
+void main() 
+{
+	const float alpha = ubo.distortionAlpha;
+
+	vec2 p1 = vec2(2.0 * inUV - 1.0);
+	vec2 p2 = p1 / (1.0 - alpha * length(p1));
+	p2 = (p2 + 1.0) * 0.5;
+
+	bool inside = ((p2.x >= 0.0) && (p2.x <= 1.0) && (p2.y >= 0.0 ) && (p2.y <= 1.0));
+	outColor = inside ? texture(samplerView, vec3(p2, VIEW_LAYER)) : vec4(0.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/multiview/viewdisplay.frag.spv b/external/Vulkan/data/shaders/glsl/multiview/viewdisplay.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..474dfc48acad239cdfd1e6674ad956fe1075afbc
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/multiview/viewdisplay.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/multiview/viewdisplay.vert b/external/Vulkan/data/shaders/glsl/multiview/viewdisplay.vert
new file mode 100644
index 0000000000000000000000000000000000000000..3a42f3c54083149098d3dcb9fcc22eb529727b15
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/multiview/viewdisplay.vert
@@ -0,0 +1,9 @@
+#version 450
+
+layout (location = 0) out vec2 outUV;
+
+void main() 
+{
+	outUV = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
+	gl_Position = vec4(outUV * 2.0f - 1.0f, 0.0f, 1.0f);
+}
diff --git a/external/Vulkan/data/shaders/glsl/multiview/viewdisplay.vert.spv b/external/Vulkan/data/shaders/glsl/multiview/viewdisplay.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..822048d724c47414b1e36df04557a1320854ff83
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/multiview/viewdisplay.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/negativeviewportheight/quad.frag b/external/Vulkan/data/shaders/glsl/negativeviewportheight/quad.frag
new file mode 100644
index 0000000000000000000000000000000000000000..fb6b63226d70b21d574dc8cdcc121dfb88ef1c68
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/negativeviewportheight/quad.frag
@@ -0,0 +1,11 @@
+#version 450
+
+layout (binding = 0) uniform sampler2D samplerColor;
+
+layout (location = 0) in vec2 inUV;
+layout (location = 0) out vec4 outColor;
+
+void main() 
+{
+	outColor = texture(samplerColor, inUV);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/negativeviewportheight/quad.frag.spv b/external/Vulkan/data/shaders/glsl/negativeviewportheight/quad.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..30d62d651b5661657887a1b4f170d9caddf04f9a
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/negativeviewportheight/quad.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/negativeviewportheight/quad.vert b/external/Vulkan/data/shaders/glsl/negativeviewportheight/quad.vert
new file mode 100644
index 0000000000000000000000000000000000000000..a7febf5a60e13c683b425078b250f3acbdd86218
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/negativeviewportheight/quad.vert
@@ -0,0 +1,12 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec2 inUV;
+
+layout (location = 0) out vec2 outUV;
+
+void main() 
+{
+	outUV = inUV;
+	gl_Position = vec4(inPos, 1.0f);
+}
diff --git a/external/Vulkan/data/shaders/glsl/negativeviewportheight/quad.vert.spv b/external/Vulkan/data/shaders/glsl/negativeviewportheight/quad.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..c93f1261d1aceb56b5850efcb04082117a916c15
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/negativeviewportheight/quad.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/occlusionquery/mesh.frag b/external/Vulkan/data/shaders/glsl/occlusionquery/mesh.frag
new file mode 100644
index 0000000000000000000000000000000000000000..5755f10fca1c249c01b522b4425ac97f24835baa
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/occlusionquery/mesh.frag
@@ -0,0 +1,28 @@
+#version 450
+
+layout (location = 0) in vec3 inNormal;
+layout (location = 1) in vec3 inColor;
+layout (location = 2) in float inVisible;
+layout (location = 3) in vec3 inViewVec;
+layout (location = 4) in vec3 inLightVec;
+
+layout (location = 0) out vec4 outFragColor;
+
+
+void main() 
+{
+	if (inVisible > 0.0) 
+	{	
+		vec3 N = normalize(inNormal);
+		vec3 L = normalize(inLightVec);
+		vec3 V = normalize(inViewVec);
+		vec3 R = reflect(-L, N);
+		vec3 diffuse = max(dot(N, L), 0.25) * inColor;
+		vec3 specular = pow(max(dot(R, V), 0.0), 8.0) * vec3(0.75);
+		outFragColor = vec4(diffuse + specular, 1.0);	
+	}
+	else
+	{
+		outFragColor = vec4(vec3(0.1), 1.0);	
+	}
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/occlusionquery/mesh.frag.spv b/external/Vulkan/data/shaders/glsl/occlusionquery/mesh.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..a87b9dc563917dc43457dc2da6e38a898dd83093
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/occlusionquery/mesh.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/occlusionquery/mesh.vert b/external/Vulkan/data/shaders/glsl/occlusionquery/mesh.vert
new file mode 100644
index 0000000000000000000000000000000000000000..031c1a4185e74caca98216c8e6c74164bc8b57ea
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/occlusionquery/mesh.vert
@@ -0,0 +1,35 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec3 inNormal;
+layout (location = 2) in vec3 inColor;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 view;
+	mat4 model;
+	vec4 color;
+	vec4 lightPos;
+	float visible;
+} ubo;
+
+layout (location = 0) out vec3 outNormal;
+layout (location = 1) out vec3 outColor;
+layout (location = 2) out float outVisible;
+layout (location = 3) out vec3 outViewVec;
+layout (location = 4) out vec3 outLightVec;
+
+void main() 
+{
+	outNormal = inNormal;
+	outColor = inColor * ubo.color.rgb;
+	outVisible = ubo.visible;
+	
+	gl_Position = ubo.projection * ubo.view * ubo.model * vec4(inPos.xyz, 1.0);
+	
+    vec4 pos = ubo.model * vec4(inPos, 1.0);
+    outNormal = mat3(ubo.model) * inNormal;
+    outLightVec = ubo.lightPos.xyz - pos.xyz;
+    outViewVec = -pos.xyz;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/occlusionquery/mesh.vert.spv b/external/Vulkan/data/shaders/glsl/occlusionquery/mesh.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..ce4baf11c37b981923d9b39e0db36156eda3e932
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/occlusionquery/mesh.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/occlusionquery/occluder.frag b/external/Vulkan/data/shaders/glsl/occlusionquery/occluder.frag
new file mode 100644
index 0000000000000000000000000000000000000000..e57001d86b92d021135ecb4e8267ceb59e0a3359
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/occlusionquery/occluder.frag
@@ -0,0 +1,10 @@
+#version 450
+
+layout (location = 0) in vec3 inColor;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+    outFragColor = vec4(inColor, 0.5);	
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/occlusionquery/occluder.frag.spv b/external/Vulkan/data/shaders/glsl/occlusionquery/occluder.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..baa4f8415300f8e3fbd1821793a045dae9bc7e58
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/occlusionquery/occluder.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/occlusionquery/occluder.vert b/external/Vulkan/data/shaders/glsl/occlusionquery/occluder.vert
new file mode 100644
index 0000000000000000000000000000000000000000..558950df59a61c3bcc83e4ec3a51ecfc3418ab28
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/occlusionquery/occluder.vert
@@ -0,0 +1,22 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec3 inNormal;
+layout (location = 2) in vec3 inColor;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 view;
+	mat4 model;
+	vec4 color;
+	vec4 lightPos;
+} ubo;
+
+layout (location = 0) out vec3 outColor;
+
+void main() 
+{
+	outColor = inColor * ubo.color.rgb;
+	gl_Position = ubo.projection * ubo.view * ubo.model * vec4(inPos.xyz, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/occlusionquery/occluder.vert.spv b/external/Vulkan/data/shaders/glsl/occlusionquery/occluder.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..dbc3b9feca782ba7e9ce74a38143d0d8b289905e
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/occlusionquery/occluder.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/occlusionquery/simple.frag b/external/Vulkan/data/shaders/glsl/occlusionquery/simple.frag
new file mode 100644
index 0000000000000000000000000000000000000000..e88b26affd5eac7efe63a365ac15c682065222b5
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/occlusionquery/simple.frag
@@ -0,0 +1,10 @@
+#version 450
+
+layout (location = 0) in vec3 inColor;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+    outFragColor = vec4(1.0);	
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/occlusionquery/simple.frag.spv b/external/Vulkan/data/shaders/glsl/occlusionquery/simple.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..6dbf76f11735d59d11abbc5aaa09123e48c8615c
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/occlusionquery/simple.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/occlusionquery/simple.vert b/external/Vulkan/data/shaders/glsl/occlusionquery/simple.vert
new file mode 100644
index 0000000000000000000000000000000000000000..0e193854c270a8c6e41ef1d9bd63224f56181d90
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/occlusionquery/simple.vert
@@ -0,0 +1,19 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 view;
+	mat4 model;
+	vec4 color;
+	vec4 lightPos;
+} ubo;
+
+layout (location = 0) out vec3 outColor;
+
+void main() 
+{
+	gl_Position = ubo.projection * ubo.view * ubo.model * vec4(inPos.xyz, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/occlusionquery/simple.vert.spv b/external/Vulkan/data/shaders/glsl/occlusionquery/simple.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..b6011ed3f611b25b61c64aca5a6747d410fcdb3d
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/occlusionquery/simple.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/offscreen/mirror.frag b/external/Vulkan/data/shaders/glsl/offscreen/mirror.frag
new file mode 100644
index 0000000000000000000000000000000000000000..9c784dc2e822130f291a031c5c9c791abbf85ece
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/offscreen/mirror.frag
@@ -0,0 +1,38 @@
+#version 450
+
+layout (binding = 1) uniform sampler2D samplerColor;
+
+layout (location = 0) in vec2 inUV;
+layout (location = 1) in vec4 inPos;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+	vec4 tmp = vec4(1.0 / inPos.w);
+	vec4 projCoord = inPos * tmp;
+
+	// Scale and bias
+	projCoord += vec4(1.0);
+	projCoord *= vec4(0.5);
+		
+	// Slow single pass blur
+	// For demonstration purposes only
+	const float blurSize = 1.0 / 512.0;	
+
+	outFragColor = vec4(vec3(0.0), 1.);
+
+	if (gl_FrontFacing) 
+	{
+		// Only render mirrored scene on front facing (upper) side of mirror surface
+		vec4 reflection = vec4(0.0);
+		for (int x = -3; x <= 3; x++)
+		{
+			for (int y = -3; y <= 3; y++)
+			{
+				reflection += texture(samplerColor, vec2(projCoord.s + x * blurSize, projCoord.t + y * blurSize)) / 49.0;
+			}
+		}
+		outFragColor += reflection;
+	};
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/offscreen/mirror.frag.spv b/external/Vulkan/data/shaders/glsl/offscreen/mirror.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..7f382743284bce16aff225531d284ee0af59ec03
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/offscreen/mirror.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/offscreen/mirror.vert b/external/Vulkan/data/shaders/glsl/offscreen/mirror.vert
new file mode 100644
index 0000000000000000000000000000000000000000..738333fe3237788893711dc0b8cf9f9b19ac9d42
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/offscreen/mirror.vert
@@ -0,0 +1,21 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec2 inUV;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 view;
+	mat4 model;
+} ubo;
+
+layout (location = 0) out vec2 outUV;
+layout (location = 1) out vec4 outPos;
+
+void main() 
+{
+	outUV = inUV;
+	outPos = ubo.projection * ubo.view * ubo.model * vec4(inPos.xyz, 1.0);
+	gl_Position = outPos;		
+}
diff --git a/external/Vulkan/data/shaders/glsl/offscreen/mirror.vert.spv b/external/Vulkan/data/shaders/glsl/offscreen/mirror.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..16b29306c2f2d7638c626f2556fa80b36e116bf9
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/offscreen/mirror.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/offscreen/phong.frag b/external/Vulkan/data/shaders/glsl/offscreen/phong.frag
new file mode 100644
index 0000000000000000000000000000000000000000..7c26398d4d97ef1d2da1397db774fe1e1718671b
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/offscreen/phong.frag
@@ -0,0 +1,25 @@
+#version 450
+
+layout (location = 0) in vec3 inNormal;
+layout (location = 1) in vec3 inColor;
+layout (location = 2) in vec3 inEyePos;
+layout (location = 3) in vec3 inLightVec;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+	vec3 Eye = normalize(-inEyePos);
+	vec3 Reflected = normalize(reflect(-inLightVec, inNormal)); 
+
+	vec4 IAmbient = vec4(0.1, 0.1, 0.1, 1.0);
+	vec4 IDiffuse = vec4(max(dot(inNormal, inLightVec), 0.0));
+	float specular = 0.75;
+	vec4 ISpecular = vec4(0.0);
+	if (dot(inEyePos, inNormal) < 0.0)
+	{
+		ISpecular = vec4(0.5, 0.5, 0.5, 1.0) * pow(max(dot(Reflected, Eye), 0.0), 16.0) * specular; 
+	}
+
+	outFragColor = vec4((IAmbient + IDiffuse) * vec4(inColor, 1.0) + ISpecular);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/offscreen/phong.frag.spv b/external/Vulkan/data/shaders/glsl/offscreen/phong.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..8c26766fb7236bd7fafc0fcd863c6650e075a0ad
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/offscreen/phong.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/offscreen/phong.vert b/external/Vulkan/data/shaders/glsl/offscreen/phong.vert
new file mode 100644
index 0000000000000000000000000000000000000000..9491b09d8fbbffa299fd42cbd4f5d3ed1b338f91
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/offscreen/phong.vert
@@ -0,0 +1,31 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec3 inColor;
+layout (location = 2) in vec3 inNormal;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 view;
+	mat4 model;
+	vec4 lightPos;
+} ubo;
+
+layout (location = 0) out vec3 outNormal;
+layout (location = 1) out vec3 outColor;
+layout (location = 2) out vec3 outEyePos;
+layout (location = 3) out vec3 outLightVec;
+
+void main() 
+{
+	outNormal = inNormal;
+	outColor = inColor;
+	gl_Position = ubo.projection * ubo.view * ubo.model * vec4(inPos, 1.0);
+	outEyePos = vec3(ubo.view * ubo.model * vec4(inPos, 1.0));
+	outLightVec = normalize(ubo.lightPos.xyz - outEyePos);
+
+	// Clip against reflection plane
+	vec4 clipPlane = vec4(0.0, -1.0, 0.0, 1.5);	
+	gl_ClipDistance[0] = dot(vec4(inPos, 1.0), clipPlane);	
+}
diff --git a/external/Vulkan/data/shaders/glsl/offscreen/phong.vert.spv b/external/Vulkan/data/shaders/glsl/offscreen/phong.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..15e5a71f7e04dcafc83c9669efb2617d57680673
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/offscreen/phong.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/offscreen/quad.frag b/external/Vulkan/data/shaders/glsl/offscreen/quad.frag
new file mode 100644
index 0000000000000000000000000000000000000000..108e89b0a97e56b90915b5f48bd1075ed4027f82
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/offscreen/quad.frag
@@ -0,0 +1,12 @@
+#version 450
+
+layout (binding = 1) uniform sampler2D samplerColor;
+
+layout (location = 0) in vec2 inUV;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+  outFragColor = texture(samplerColor, inUV);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/offscreen/quad.frag.spv b/external/Vulkan/data/shaders/glsl/offscreen/quad.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..03440f7c5c0ea9f2475b7bbea8fa8983aaaf0d3f
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/offscreen/quad.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/offscreen/quad.vert b/external/Vulkan/data/shaders/glsl/offscreen/quad.vert
new file mode 100644
index 0000000000000000000000000000000000000000..3a42f3c54083149098d3dcb9fcc22eb529727b15
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/offscreen/quad.vert
@@ -0,0 +1,9 @@
+#version 450
+
+layout (location = 0) out vec2 outUV;
+
+void main() 
+{
+	outUV = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
+	gl_Position = vec4(outUV * 2.0f - 1.0f, 0.0f, 1.0f);
+}
diff --git a/external/Vulkan/data/shaders/glsl/offscreen/quad.vert.spv b/external/Vulkan/data/shaders/glsl/offscreen/quad.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..5683fcb316e0ff06e54a6f73a0c33ca38f7e937c
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/offscreen/quad.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/oit/color.frag b/external/Vulkan/data/shaders/glsl/oit/color.frag
new file mode 100644
index 0000000000000000000000000000000000000000..06a53080512945c69329b478e4c33256f67bc090
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/oit/color.frag
@@ -0,0 +1,56 @@
+#version 450
+
+#define MAX_FRAGMENT_COUNT 128
+
+struct Node
+{
+    vec4 color;
+    float depth;
+    uint next;
+};
+
+layout (location = 0) out vec4 outFragColor;
+
+layout (set = 0, binding = 0, r32ui) uniform uimage2D headIndexImage;
+
+layout (set = 0, binding = 1) buffer LinkedListSBO
+{
+    Node nodes[];
+};
+
+void main()
+{
+    Node fragments[MAX_FRAGMENT_COUNT];
+    int count = 0;
+
+    uint nodeIdx = imageLoad(headIndexImage, ivec2(gl_FragCoord.xy)).r;
+
+    while (nodeIdx != 0xffffffff && count < MAX_FRAGMENT_COUNT)
+    {
+        fragments[count] = nodes[nodeIdx];
+        nodeIdx = fragments[count].next;
+        ++count;
+    }
+    
+    // Do the insertion sort
+    for (uint i = 1; i < count; ++i)
+    {
+        Node insert = fragments[i];
+        uint j = i;
+        while (j > 0 && insert.depth > fragments[j - 1].depth)
+        {
+            fragments[j] = fragments[j-1];
+            --j;
+        }
+        fragments[j] = insert;
+    }
+
+    // Do blending
+    vec4 color = vec4(0.025, 0.025, 0.025, 1.0f);
+    for (int i = 0; i < count; ++i)
+    {
+        color = mix(color, fragments[i].color, fragments[i].color.a);
+    }
+
+    outFragColor = color;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/oit/color.frag.spv b/external/Vulkan/data/shaders/glsl/oit/color.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..8b5d3670266302c14dad9571c676694a218ada26
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/oit/color.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/oit/color.vert b/external/Vulkan/data/shaders/glsl/oit/color.vert
new file mode 100644
index 0000000000000000000000000000000000000000..ccfa57567a0539a5af64898a1eb1522c4ca5c994
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/oit/color.vert
@@ -0,0 +1,7 @@
+#version 450
+
+void main() 
+{
+    vec2 uv = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
+    gl_Position = vec4(uv * 2.0f + -1.0f, 0.0f, 1.0f);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/oit/color.vert.spv b/external/Vulkan/data/shaders/glsl/oit/color.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..b24b3211ff656ebe94bab5daf08cd80e4a1700a6
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/oit/color.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/oit/geometry.frag b/external/Vulkan/data/shaders/glsl/oit/geometry.frag
new file mode 100644
index 0000000000000000000000000000000000000000..17c143fc22b255762535c6bde136c81f146e76c9
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/oit/geometry.frag
@@ -0,0 +1,46 @@
+#version 450
+
+layout (early_fragment_tests) in;
+
+struct Node
+{
+    vec4 color;
+    float depth;
+    uint next;
+};
+
+layout (set = 0, binding = 1) buffer GeometrySBO
+{
+    uint count;
+    uint maxNodeCount;
+};
+
+layout (set = 0, binding = 2, r32ui) uniform coherent uimage2D headIndexImage;
+
+layout (set = 0, binding = 3) buffer LinkedListSBO
+{
+    Node nodes[];
+};
+
+layout(push_constant) uniform PushConsts {
+	mat4 model;
+    vec4 color;
+} pushConsts;
+
+void main()
+{
+    // Increase the node count
+    uint nodeIdx = atomicAdd(count, 1);
+
+    // Check LinkedListSBO is full
+    if (nodeIdx < maxNodeCount)
+    {
+        // Exchange new head index and previous head index
+        uint prevHeadIdx = imageAtomicExchange(headIndexImage, ivec2(gl_FragCoord.xy), nodeIdx);
+
+        // Store node data
+        nodes[nodeIdx].color = pushConsts.color;
+        nodes[nodeIdx].depth = gl_FragCoord.z;
+        nodes[nodeIdx].next = prevHeadIdx;
+    }
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/oit/geometry.frag.spv b/external/Vulkan/data/shaders/glsl/oit/geometry.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..7e861318bc539afe8c57e4ee090dd8f314ed9baa
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/oit/geometry.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/oit/geometry.vert b/external/Vulkan/data/shaders/glsl/oit/geometry.vert
new file mode 100644
index 0000000000000000000000000000000000000000..e53b0c75d1b1398aa7246c63f7227476663b06ce
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/oit/geometry.vert
@@ -0,0 +1,20 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+
+layout (set = 0, binding = 0) uniform RenderPassUBO
+{
+    mat4 projection;
+    mat4 view;
+} renderPassUBO;
+
+layout(push_constant) uniform PushConsts {
+	mat4 model;
+    vec4 color;
+} pushConsts;
+
+void main()
+{
+    mat4 PVM = renderPassUBO.projection * renderPassUBO.view * pushConsts.model;
+    gl_Position = PVM * vec4(inPos, 1.0);
+}
diff --git a/external/Vulkan/data/shaders/glsl/oit/geometry.vert.spv b/external/Vulkan/data/shaders/glsl/oit/geometry.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..1784d170012999c986556b2f8348fe27067a70f7
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/oit/geometry.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/parallaxmapping/parallax.frag b/external/Vulkan/data/shaders/glsl/parallaxmapping/parallax.frag
new file mode 100644
index 0000000000000000000000000000000000000000..f7cbbdb17df5a2c940fca97f631fbca7449dc5a7
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/parallaxmapping/parallax.frag
@@ -0,0 +1,109 @@
+#version 450
+
+layout (binding = 1) uniform sampler2D sColorMap;
+layout (binding = 2) uniform sampler2D sNormalHeightMap;
+
+layout (binding = 3) uniform UBO 
+{
+	float heightScale;
+	float parallaxBias;
+	float numLayers;
+	int mappingMode;
+} ubo;
+
+layout (location = 0) in vec2 inUV;
+layout (location = 1) in vec3 inTangentLightPos;
+layout (location = 2) in vec3 inTangentViewPos;
+layout (location = 3) in vec3 inTangentFragPos;
+
+layout (location = 0) out vec4 outColor;
+
+vec2 parallaxMapping(vec2 uv, vec3 viewDir) 
+{
+	float height = 1.0 - textureLod(sNormalHeightMap, uv, 0.0).a;
+	vec2 p = viewDir.xy * (height * (ubo.heightScale * 0.5) + ubo.parallaxBias) / viewDir.z;
+	return uv - p;  
+}
+
+vec2 steepParallaxMapping(vec2 uv, vec3 viewDir) 
+{
+	float layerDepth = 1.0 / ubo.numLayers;
+	float currLayerDepth = 0.0;
+	vec2 deltaUV = viewDir.xy * ubo.heightScale / (viewDir.z * ubo.numLayers);
+	vec2 currUV = uv;
+	float height = 1.0 - textureLod(sNormalHeightMap, currUV, 0.0).a;
+	for (int i = 0; i < ubo.numLayers; i++) {
+		currLayerDepth += layerDepth;
+		currUV -= deltaUV;
+		height = 1.0 - textureLod(sNormalHeightMap, currUV, 0.0).a;
+		if (height < currLayerDepth) {
+			break;
+		}
+	}
+	return currUV;
+}
+
+vec2 parallaxOcclusionMapping(vec2 uv, vec3 viewDir) 
+{
+	float layerDepth = 1.0 / ubo.numLayers;
+	float currLayerDepth = 0.0;
+	vec2 deltaUV = viewDir.xy * ubo.heightScale / (viewDir.z * ubo.numLayers);
+	vec2 currUV = uv;
+	float height = 1.0 - textureLod(sNormalHeightMap, currUV, 0.0).a;
+	for (int i = 0; i < ubo.numLayers; i++) {
+		currLayerDepth += layerDepth;
+		currUV -= deltaUV;
+		height = 1.0 - textureLod(sNormalHeightMap, currUV, 0.0).a;
+		if (height < currLayerDepth) {
+			break;
+		}
+	}
+	vec2 prevUV = currUV + deltaUV;
+	float nextDepth = height - currLayerDepth;
+	float prevDepth = 1.0 - textureLod(sNormalHeightMap, prevUV, 0.0).a - currLayerDepth + layerDepth;
+	return mix(currUV, prevUV, nextDepth / (nextDepth - prevDepth));
+}
+
+void main(void) 
+{
+	vec3 V = normalize(inTangentViewPos - inTangentFragPos);
+	vec2 uv = inUV;
+
+	if (ubo.mappingMode == 0) {
+		// Color only
+		outColor = texture(sColorMap, inUV);
+	} else {
+		switch(ubo.mappingMode) {
+			case 2:
+				uv = parallaxMapping(inUV, V);
+				break;
+			case 3:
+				uv = steepParallaxMapping(inUV, V);
+				break;
+			case 4:
+				uv = parallaxOcclusionMapping(inUV, V);
+				break;
+		}
+
+		// Perform sampling before (potentially) discarding.
+		// This is to avoid implicit derivatives in non-uniform control flow.
+		vec3 normalHeightMapLod = textureLod(sNormalHeightMap, uv, 0.0).rgb;
+		vec3 color = texture(sColorMap, uv).rgb;
+
+		// Discard fragments at texture border
+		if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) {
+			discard;
+		}
+
+		vec3 N = normalize(normalHeightMapLod * 2.0 - 1.0);
+		vec3 L = normalize(inTangentLightPos - inTangentFragPos);
+		vec3 R = reflect(-L, N);
+		vec3 H = normalize(L + V);  
+   
+		vec3 ambient = 0.2 * color;
+		vec3 diffuse = max(dot(L, N), 0.0) * color;
+		vec3 specular = vec3(0.15) * pow(max(dot(N, H), 0.0), 32.0);
+
+		outColor = vec4(ambient + diffuse + specular, 1.0f);
+	}	
+}
diff --git a/external/Vulkan/data/shaders/glsl/parallaxmapping/parallax.frag.spv b/external/Vulkan/data/shaders/glsl/parallaxmapping/parallax.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..f591ae65cabf39fcf8635b2d619b4490a92e1a7d
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/parallaxmapping/parallax.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/parallaxmapping/parallax.vert b/external/Vulkan/data/shaders/glsl/parallaxmapping/parallax.vert
new file mode 100644
index 0000000000000000000000000000000000000000..5ceb6b221b2cb0d3be8ba9dbc8c0b31b499ba2c6
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/parallaxmapping/parallax.vert
@@ -0,0 +1,36 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec2 inUV;
+layout (location = 2) in vec3 inNormal;
+layout (location = 3) in vec4 inTangent;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 view;
+	mat4 model;
+	vec4 lightPos;
+	vec4 cameraPos;
+} ubo;
+
+layout (location = 0) out vec2 outUV;
+layout (location = 1) out vec3 outTangentLightPos;
+layout (location = 2) out vec3 outTangentViewPos;
+layout (location = 3) out vec3 outTangentFragPos;
+
+void main(void) 
+{
+	gl_Position = ubo.projection * ubo.view * ubo.model * vec4(inPos, 1.0f);
+	outTangentFragPos = vec3(ubo.model * vec4(inPos, 1.0));   
+	outUV = inUV;
+		
+	vec3 N = normalize(mat3(ubo.model) * inNormal);
+	vec3 T = normalize(mat3(ubo.model) * inTangent.xyz);
+	vec3 B = normalize(cross(N, T));
+	mat3 TBN = transpose(mat3(T, B, N));
+
+	outTangentLightPos = TBN * ubo.lightPos.xyz;
+	outTangentViewPos  = TBN * ubo.cameraPos.xyz;
+	outTangentFragPos  = TBN * outTangentFragPos;
+}
diff --git a/external/Vulkan/data/shaders/glsl/parallaxmapping/parallax.vert.spv b/external/Vulkan/data/shaders/glsl/parallaxmapping/parallax.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..2ffdfc7f3ccc2266be75af1a1f295aa83243cf86
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/parallaxmapping/parallax.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/particlefire/normalmap.frag b/external/Vulkan/data/shaders/glsl/particlefire/normalmap.frag
new file mode 100644
index 0000000000000000000000000000000000000000..34acfee7976a575c511cbb5dc7dd688d434c98cd
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/particlefire/normalmap.frag
@@ -0,0 +1,41 @@
+#version 450
+
+layout (binding = 1) uniform sampler2D sColorMap;
+layout (binding = 2) uniform sampler2D sNormalHeightMap;
+
+#define lightRadius 45.0
+
+layout (location = 0) in vec2 inUV;
+layout (location = 1) in vec3 inLightVec;
+layout (location = 2) in vec3 inLightVecB;
+layout (location = 3) in vec3 inLightDir;
+layout (location = 4) in vec3 inViewVec;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main(void) 
+{
+	vec3 specularColor = vec3(0.85, 0.5, 0.0);
+
+	float invRadius = 1.0/lightRadius;
+	float ambient = 0.25;
+
+	vec3 rgb, normal;
+
+	rgb = texture(sColorMap, inUV).rgb;
+	normal = normalize((texture(sNormalHeightMap, inUV).rgb - 0.5) * 2.0);
+
+	float distSqr = dot(inLightVecB, inLightVecB);
+	vec3 lVec = inLightVecB * inversesqrt(distSqr);
+
+	float atten = max(clamp(1.0 - invRadius * sqrt(distSqr), 0.0, 1.0), ambient);
+	float diffuse = clamp(dot(lVec, normal), 0.0, 1.0);
+
+	vec3 light = normalize(-inLightVec);
+	vec3 view = normalize(inViewVec);
+	vec3 reflectDir = reflect(-light, normal);
+		
+	float specular = pow(max(dot(view, reflectDir), 0.0), 4.0);
+	
+	outFragColor = vec4((rgb * atten + (diffuse * rgb + 0.5 * specular * specularColor.rgb)) * atten, 1.0);   
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/particlefire/normalmap.frag.spv b/external/Vulkan/data/shaders/glsl/particlefire/normalmap.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..9bd9a4a2a56e2fa4ff71c73ac565d7cd4a1ca70f
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/particlefire/normalmap.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/particlefire/normalmap.vert b/external/Vulkan/data/shaders/glsl/particlefire/normalmap.vert
new file mode 100644
index 0000000000000000000000000000000000000000..d57a26fae7d18c66b7f7d835127c1e17cc68fb98
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/particlefire/normalmap.vert
@@ -0,0 +1,50 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec2 inUV;
+layout (location = 2) in vec3 inNormal;
+layout (location = 3) in vec4 inTangent;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 model;
+	mat4 normal;
+	vec4 lightPos;
+} ubo;
+
+layout (location = 0) out vec2 outUV;
+layout (location = 1) out vec3 outLightVec;
+layout (location = 2) out vec3 outLightVecB;
+layout (location = 3) out vec3 outLightDir;
+layout (location = 4) out vec3 outViewVec;
+
+void main(void) 
+{
+	vec3 vertexPosition = vec3(ubo.model *  vec4(inPos, 1.0));
+	outLightDir = normalize(ubo.lightPos.xyz - vertexPosition);
+
+	vec3 biTangent = cross(inNormal, inTangent.xyz);
+
+	// Setup (t)angent-(b)inormal-(n)ormal matrix for converting
+	// object coordinates into tangent space
+	mat3 tbnMatrix;
+	tbnMatrix[0] =  mat3(ubo.normal) * inTangent.xyz;
+	tbnMatrix[1] =  mat3(ubo.normal) * biTangent;
+	tbnMatrix[2] =  mat3(ubo.normal) * inNormal;
+
+	outLightVec.xyz = vec3(ubo.lightPos.xyz - vertexPosition) * tbnMatrix;
+
+	vec3 lightDist = ubo.lightPos.xyz - inPos;
+	outLightVecB.x = dot(inTangent.xyz, lightDist);
+	outLightVecB.y = dot(biTangent, lightDist);
+	outLightVecB.z = dot(inNormal, lightDist);
+
+	outViewVec.x = dot(inTangent.xyz, inPos);
+	outViewVec.y = dot(biTangent, inPos);
+	outViewVec.z = dot(inNormal, inPos);
+
+	outUV = inUV;
+	
+	gl_Position = ubo.projection * ubo.model * vec4(inPos, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/particlefire/normalmap.vert.spv b/external/Vulkan/data/shaders/glsl/particlefire/normalmap.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..a2904765c0f1c436021a97823ebbe8814ebf06b4
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/particlefire/normalmap.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/particlefire/particle.frag b/external/Vulkan/data/shaders/glsl/particlefire/particle.frag
new file mode 100644
index 0000000000000000000000000000000000000000..1aa29c5e8d11788651cee4dfc704b8089bd45058
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/particlefire/particle.frag
@@ -0,0 +1,42 @@
+#version 450
+
+layout (binding = 1) uniform sampler2D samplerSmoke;
+layout (binding = 2) uniform sampler2D samplerFire;
+
+layout (location = 0) in vec4 inColor;
+layout (location = 1) in float inAlpha;
+layout (location = 2) in flat int inType;
+layout (location = 3) in float inRotation;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main () 
+{
+	vec4 color;
+	float alpha = (inAlpha <= 1.0) ? inAlpha : 2.0 - inAlpha;
+	
+	// Rotate texture coordinates
+	// Rotate UV	
+	float rotCenter = 0.5;
+	float rotCos = cos(inRotation);
+	float rotSin = sin(inRotation);
+	vec2 rotUV = vec2(
+		rotCos * (gl_PointCoord.x - rotCenter) + rotSin * (gl_PointCoord.y - rotCenter) + rotCenter,
+		rotCos * (gl_PointCoord.y - rotCenter) - rotSin * (gl_PointCoord.x - rotCenter) + rotCenter);
+
+	
+	if (inType == 0) 
+	{
+		// Flame
+		color = texture(samplerFire, rotUV);
+		outFragColor.a = 0.0;
+	}
+	else
+	{
+		// Smoke
+		color = texture(samplerSmoke, rotUV);
+		outFragColor.a = color.a * alpha;
+	}
+	
+	outFragColor.rgb = color.rgb * inColor.rgb * alpha;	
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/particlefire/particle.frag.spv b/external/Vulkan/data/shaders/glsl/particlefire/particle.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..21fe3f05e6d4fd2b3ea25a0df1ea6b6f0b68f024
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/particlefire/particle.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/particlefire/particle.vert b/external/Vulkan/data/shaders/glsl/particlefire/particle.vert
new file mode 100644
index 0000000000000000000000000000000000000000..9123248b603cc74acb658a47b3ffa5c0b0c95ed5
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/particlefire/particle.vert
@@ -0,0 +1,45 @@
+#version 450
+
+layout (location = 0) in vec4 inPos;
+layout (location = 1) in vec4 inColor;
+layout (location = 2) in float inAlpha;
+layout (location = 3) in float inSize;
+layout (location = 4) in float inRotation;
+layout (location = 5) in int inType;
+
+layout (location = 0) out vec4 outColor;
+layout (location = 1) out float outAlpha;
+layout (location = 2) out flat int outType;
+layout (location = 3) out float outRotation;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 modelview;
+	vec2 viewportDim;
+	float pointSize;
+} ubo;
+
+out gl_PerVertex
+{
+	vec4 gl_Position;
+	float gl_PointSize;
+};
+
+void main () 
+{
+	outColor = inColor;
+	outAlpha = inAlpha;
+	outType = inType;
+	outRotation = inRotation;
+	  
+	gl_Position = ubo.projection * ubo.modelview * vec4(inPos.xyz, 1.0);	
+	
+	// Base size of the point sprites
+	float spriteSize = 8.0 * inSize;
+
+	// Scale particle size depending on camera projection
+	vec4 eyePos = ubo.modelview * vec4(inPos.xyz, 1.0);
+	vec4 projectedCorner = ubo.projection * vec4(0.5 * spriteSize, 0.5 * spriteSize, eyePos.z, eyePos.w);
+	gl_PointSize = ubo.viewportDim.x * projectedCorner.x / projectedCorner.w;	
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/particlefire/particle.vert.spv b/external/Vulkan/data/shaders/glsl/particlefire/particle.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..d04e22accf405c29dbc3d9d31486cd892361e3b1
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/particlefire/particle.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/pbrbasic/pbr.frag b/external/Vulkan/data/shaders/glsl/pbrbasic/pbr.frag
new file mode 100644
index 0000000000000000000000000000000000000000..8896cbcb8268ff3a4baa7fd54b763e8b276aa382
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/pbrbasic/pbr.frag
@@ -0,0 +1,126 @@
+#version 450
+
+layout (location = 0) in vec3 inWorldPos;
+layout (location = 1) in vec3 inNormal;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 model;
+	mat4 view;
+	vec3 camPos;
+} ubo;
+
+layout (binding = 1) uniform UBOShared {
+	vec4 lights[4];
+} uboParams;
+
+layout (location = 0) out vec4 outColor;
+
+layout(push_constant) uniform PushConsts {
+	layout(offset = 12) float roughness;
+	layout(offset = 16) float metallic;
+	layout(offset = 20) float r;
+	layout(offset = 24) float g;
+	layout(offset = 28) float b;
+} material;
+
+const float PI = 3.14159265359;
+
+//#define ROUGHNESS_PATTERN 1
+
+vec3 materialcolor()
+{
+	return vec3(material.r, material.g, material.b);
+}
+
+// Normal Distribution function --------------------------------------
+float D_GGX(float dotNH, float roughness)
+{
+	float alpha = roughness * roughness;
+	float alpha2 = alpha * alpha;
+	float denom = dotNH * dotNH * (alpha2 - 1.0) + 1.0;
+	return (alpha2)/(PI * denom*denom); 
+}
+
+// Geometric Shadowing function --------------------------------------
+float G_SchlicksmithGGX(float dotNL, float dotNV, float roughness)
+{
+	float r = (roughness + 1.0);
+	float k = (r*r) / 8.0;
+	float GL = dotNL / (dotNL * (1.0 - k) + k);
+	float GV = dotNV / (dotNV * (1.0 - k) + k);
+	return GL * GV;
+}
+
+// Fresnel function ----------------------------------------------------
+vec3 F_Schlick(float cosTheta, float metallic)
+{
+	vec3 F0 = mix(vec3(0.04), materialcolor(), metallic); // * material.specular
+	vec3 F = F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0); 
+	return F;    
+}
+
+// Specular BRDF composition --------------------------------------------
+
+vec3 BRDF(vec3 L, vec3 V, vec3 N, float metallic, float roughness)
+{
+	// Precalculate vectors and dot products	
+	vec3 H = normalize (V + L);
+	float dotNV = clamp(dot(N, V), 0.0, 1.0);
+	float dotNL = clamp(dot(N, L), 0.0, 1.0);
+	float dotLH = clamp(dot(L, H), 0.0, 1.0);
+	float dotNH = clamp(dot(N, H), 0.0, 1.0);
+
+	// Light color fixed
+	vec3 lightColor = vec3(1.0);
+
+	vec3 color = vec3(0.0);
+
+	if (dotNL > 0.0)
+	{
+		float rroughness = max(0.05, roughness);
+		// D = Normal distribution (Distribution of the microfacets)
+		float D = D_GGX(dotNH, roughness); 
+		// G = Geometric shadowing term (Microfacets shadowing)
+		float G = G_SchlicksmithGGX(dotNL, dotNV, rroughness);
+		// F = Fresnel factor (Reflectance depending on angle of incidence)
+		vec3 F = F_Schlick(dotNV, metallic);
+
+		vec3 spec = D * F * G / (4.0 * dotNL * dotNV);
+
+		color += spec * dotNL * lightColor;
+	}
+
+	return color;
+}
+
+// ----------------------------------------------------------------------------
+void main()
+{		  
+	vec3 N = normalize(inNormal);
+	vec3 V = normalize(ubo.camPos - inWorldPos);
+
+	float roughness = material.roughness;
+
+	// Add striped pattern to roughness based on vertex position
+#ifdef ROUGHNESS_PATTERN
+	roughness = max(roughness, step(fract(inWorldPos.y * 2.02), 0.5));
+#endif
+
+	// Specular contribution
+	vec3 Lo = vec3(0.0);
+	for (int i = 0; i < uboParams.lights.length(); i++) {
+		vec3 L = normalize(uboParams.lights[i].xyz - inWorldPos);
+		Lo += BRDF(L, V, N, material.metallic, roughness);
+	};
+
+	// Combine with ambient
+	vec3 color = materialcolor() * 0.02;
+	color += Lo;
+
+	// Gamma correct
+	color = pow(color, vec3(0.4545));
+
+	outColor = vec4(color, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/pbrbasic/pbr.frag.spv b/external/Vulkan/data/shaders/glsl/pbrbasic/pbr.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..657ca7728fb224a8f524b5ea97bb3882cf0e5984
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/pbrbasic/pbr.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/pbrbasic/pbr.vert b/external/Vulkan/data/shaders/glsl/pbrbasic/pbr.vert
new file mode 100644
index 0000000000000000000000000000000000000000..709f12bf13bd29b36de57caaad62209c6682025d
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/pbrbasic/pbr.vert
@@ -0,0 +1,32 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec3 inNormal;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 model;
+	mat4 view;
+	vec3 camPos;
+} ubo;
+
+layout (location = 0) out vec3 outWorldPos;
+layout (location = 1) out vec3 outNormal;
+
+layout(push_constant) uniform PushConsts {
+	vec3 objPos;
+} pushConsts;
+
+out gl_PerVertex 
+{
+	vec4 gl_Position;
+};
+
+void main() 
+{
+	vec3 locPos = vec3(ubo.model * vec4(inPos, 1.0));
+	outWorldPos = locPos + pushConsts.objPos;
+	outNormal = mat3(ubo.model) * inNormal;
+	gl_Position =  ubo.projection * ubo.view * vec4(outWorldPos, 1.0);
+}
diff --git a/external/Vulkan/data/shaders/glsl/pbrbasic/pbr.vert.spv b/external/Vulkan/data/shaders/glsl/pbrbasic/pbr.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..84274aa1f5c141cdc66603b3de6c66e5ee5af6d5
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/pbrbasic/pbr.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/pbribl/filtercube.vert b/external/Vulkan/data/shaders/glsl/pbribl/filtercube.vert
new file mode 100644
index 0000000000000000000000000000000000000000..1226e28ee44658e535b013d91b660b34e1e88664
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/pbribl/filtercube.vert
@@ -0,0 +1,19 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+
+layout(push_constant) uniform PushConsts {
+	layout (offset = 0) mat4 mvp;
+} pushConsts;
+
+layout (location = 0) out vec3 outUVW;
+
+out gl_PerVertex {
+	vec4 gl_Position;
+};
+
+void main() 
+{
+	outUVW = inPos;
+	gl_Position = pushConsts.mvp * vec4(inPos.xyz, 1.0);
+}
diff --git a/external/Vulkan/data/shaders/glsl/pbribl/filtercube.vert.spv b/external/Vulkan/data/shaders/glsl/pbribl/filtercube.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..220a3df49bf403b63a63d60be2a53cc067fb6ac4
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/pbribl/filtercube.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/pbribl/genbrdflut.frag b/external/Vulkan/data/shaders/glsl/pbribl/genbrdflut.frag
new file mode 100644
index 0000000000000000000000000000000000000000..b6290dd082ecdb94a9deb43f761508a1feea0e5c
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/pbribl/genbrdflut.frag
@@ -0,0 +1,90 @@
+#version 450
+
+layout (location = 0) in vec2 inUV;
+layout (location = 0) out vec4 outColor;
+layout (constant_id = 0) const uint NUM_SAMPLES = 1024u;
+
+const float PI = 3.1415926536;
+
+// Based omn http://byteblacksmith.com/improvements-to-the-canonical-one-liner-glsl-rand-for-opengl-es-2-0/
+float random(vec2 co)
+{
+	float a = 12.9898;
+	float b = 78.233;
+	float c = 43758.5453;
+	float dt= dot(co.xy ,vec2(a,b));
+	float sn= mod(dt,3.14);
+	return fract(sin(sn) * c);
+}
+
+vec2 hammersley2d(uint i, uint N) 
+{
+	// Radical inverse based on http://holger.dammertz.org/stuff/notes_HammersleyOnHemisphere.html
+	uint bits = (i << 16u) | (i >> 16u);
+	bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
+	bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
+	bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
+	bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
+	float rdi = float(bits) * 2.3283064365386963e-10;
+	return vec2(float(i) /float(N), rdi);
+}
+
+// Based on http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_slides.pdf
+vec3 importanceSample_GGX(vec2 Xi, float roughness, vec3 normal) 
+{
+	// Maps a 2D point to a hemisphere with spread based on roughness
+	float alpha = roughness * roughness;
+	float phi = 2.0 * PI * Xi.x + random(normal.xz) * 0.1;
+	float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (alpha*alpha - 1.0) * Xi.y));
+	float sinTheta = sqrt(1.0 - cosTheta * cosTheta);
+	vec3 H = vec3(sinTheta * cos(phi), sinTheta * sin(phi), cosTheta);
+
+	// Tangent space
+	vec3 up = abs(normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0);
+	vec3 tangentX = normalize(cross(up, normal));
+	vec3 tangentY = normalize(cross(normal, tangentX));
+
+	// Convert to world Space
+	return normalize(tangentX * H.x + tangentY * H.y + normal * H.z);
+}
+
+// Geometric Shadowing function
+float G_SchlicksmithGGX(float dotNL, float dotNV, float roughness)
+{
+	float k = (roughness * roughness) / 2.0;
+	float GL = dotNL / (dotNL * (1.0 - k) + k);
+	float GV = dotNV / (dotNV * (1.0 - k) + k);
+	return GL * GV;
+}
+
+vec2 BRDF(float NoV, float roughness)
+{
+	// Normal always points along z-axis for the 2D lookup 
+	const vec3 N = vec3(0.0, 0.0, 1.0);
+	vec3 V = vec3(sqrt(1.0 - NoV*NoV), 0.0, NoV);
+
+	vec2 LUT = vec2(0.0);
+	for(uint i = 0u; i < NUM_SAMPLES; i++) {
+		vec2 Xi = hammersley2d(i, NUM_SAMPLES);
+		vec3 H = importanceSample_GGX(Xi, roughness, N);
+		vec3 L = 2.0 * dot(V, H) * H - V;
+
+		float dotNL = max(dot(N, L), 0.0);
+		float dotNV = max(dot(N, V), 0.0);
+		float dotVH = max(dot(V, H), 0.0); 
+		float dotNH = max(dot(H, N), 0.0);
+
+		if (dotNL > 0.0) {
+			float G = G_SchlicksmithGGX(dotNL, dotNV, roughness);
+			float G_Vis = (G * dotVH) / (dotNH * dotNV);
+			float Fc = pow(1.0 - dotVH, 5.0);
+			LUT += vec2((1.0 - Fc) * G_Vis, Fc * G_Vis);
+		}
+	}
+	return LUT / float(NUM_SAMPLES);
+}
+
+void main() 
+{
+	outColor = vec4(BRDF(inUV.s, 1.0-inUV.t), 0.0, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/pbribl/genbrdflut.frag.spv b/external/Vulkan/data/shaders/glsl/pbribl/genbrdflut.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..a54cdee7393917ef67a37b9b3437cea3b04d20a5
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/pbribl/genbrdflut.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/pbribl/genbrdflut.vert b/external/Vulkan/data/shaders/glsl/pbribl/genbrdflut.vert
new file mode 100644
index 0000000000000000000000000000000000000000..f3dd233520470b0842a9abda45a9807d3498653e
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/pbribl/genbrdflut.vert
@@ -0,0 +1,9 @@
+#version 450
+
+layout (location = 0) out vec2 outUV;
+
+void main()
+{
+	outUV = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
+	gl_Position = vec4(outUV * 2.0f - 1.0f, 0.0f, 1.0f);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/pbribl/genbrdflut.vert.spv b/external/Vulkan/data/shaders/glsl/pbribl/genbrdflut.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..fc2b37dd3ab32deb82e13c433350841265609b03
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/pbribl/genbrdflut.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/pbribl/irradiancecube.frag b/external/Vulkan/data/shaders/glsl/pbribl/irradiancecube.frag
new file mode 100644
index 0000000000000000000000000000000000000000..3232db4808e7db3970b246c43c30b77f5f66c368
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/pbribl/irradiancecube.frag
@@ -0,0 +1,37 @@
+// Generates an irradiance cube from an environment map using convolution
+
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 0) out vec4 outColor;
+layout (binding = 0) uniform samplerCube samplerEnv;
+
+layout(push_constant) uniform PushConsts {
+	layout (offset = 64) float deltaPhi;
+	layout (offset = 68) float deltaTheta;
+} consts;
+
+#define PI 3.1415926535897932384626433832795
+
+void main()
+{
+	vec3 N = normalize(inPos);
+	vec3 up = vec3(0.0, 1.0, 0.0);
+	vec3 right = normalize(cross(up, N));
+	up = cross(N, right);
+
+	const float TWO_PI = PI * 2.0;
+	const float HALF_PI = PI * 0.5;
+
+	vec3 color = vec3(0.0);
+	uint sampleCount = 0u;
+	for (float phi = 0.0; phi < TWO_PI; phi += consts.deltaPhi) {
+		for (float theta = 0.0; theta < HALF_PI; theta += consts.deltaTheta) {
+			vec3 tempVec = cos(phi) * right + sin(phi) * up;
+			vec3 sampleVector = cos(theta) * N + sin(theta) * tempVec;
+			color += texture(samplerEnv, sampleVector).rgb * cos(theta) * sin(theta);
+			sampleCount++;
+		}
+	}
+	outColor = vec4(PI * color / float(sampleCount), 1.0);
+}
diff --git a/external/Vulkan/data/shaders/glsl/pbribl/irradiancecube.frag.spv b/external/Vulkan/data/shaders/glsl/pbribl/irradiancecube.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..a369970e8aa72d0a9a436d5974cd8d597697a6a5
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/pbribl/irradiancecube.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/pbribl/pbribl.frag b/external/Vulkan/data/shaders/glsl/pbribl/pbribl.frag
new file mode 100644
index 0000000000000000000000000000000000000000..40c3aa1924ad860d9885555929589ac92ba1fba3
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/pbribl/pbribl.frag
@@ -0,0 +1,162 @@
+#version 450
+
+layout (location = 0) in vec3 inWorldPos;
+layout (location = 1) in vec3 inNormal;
+layout (location = 2) in vec2 inUV;
+
+layout (binding = 0) uniform UBO {
+	mat4 projection;
+	mat4 model;
+	mat4 view;
+	vec3 camPos;
+} ubo;
+
+layout (binding = 1) uniform UBOParams {
+	vec4 lights[4];
+	float exposure;
+	float gamma;
+} uboParams;
+
+layout(push_constant) uniform PushConsts {
+	layout(offset = 12) float roughness;
+	layout(offset = 16) float metallic;
+	layout(offset = 20) float specular;
+	layout(offset = 24) float r;
+	layout(offset = 28) float g;
+	layout(offset = 32) float b;
+} material;
+
+layout (binding = 2) uniform samplerCube samplerIrradiance;
+layout (binding = 3) uniform sampler2D samplerBRDFLUT;
+layout (binding = 4) uniform samplerCube prefilteredMap;
+
+layout (location = 0) out vec4 outColor;
+
+#define PI 3.1415926535897932384626433832795
+#define ALBEDO vec3(material.r, material.g, material.b)
+
+// From http://filmicgames.com/archives/75
+vec3 Uncharted2Tonemap(vec3 x)
+{
+	float A = 0.15;
+	float B = 0.50;
+	float C = 0.10;
+	float D = 0.20;
+	float E = 0.02;
+	float F = 0.30;
+	return ((x*(A*x+C*B)+D*E)/(x*(A*x+B)+D*F))-E/F;
+}
+
+// Normal Distribution function --------------------------------------
+float D_GGX(float dotNH, float roughness)
+{
+	float alpha = roughness * roughness;
+	float alpha2 = alpha * alpha;
+	float denom = dotNH * dotNH * (alpha2 - 1.0) + 1.0;
+	return (alpha2)/(PI * denom*denom); 
+}
+
+// Geometric Shadowing function --------------------------------------
+float G_SchlicksmithGGX(float dotNL, float dotNV, float roughness)
+{
+	float r = (roughness + 1.0);
+	float k = (r*r) / 8.0;
+	float GL = dotNL / (dotNL * (1.0 - k) + k);
+	float GV = dotNV / (dotNV * (1.0 - k) + k);
+	return GL * GV;
+}
+
+// Fresnel function ----------------------------------------------------
+vec3 F_Schlick(float cosTheta, vec3 F0)
+{
+	return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
+}
+vec3 F_SchlickR(float cosTheta, vec3 F0, float roughness)
+{
+	return F0 + (max(vec3(1.0 - roughness), F0) - F0) * pow(1.0 - cosTheta, 5.0);
+}
+
+vec3 prefilteredReflection(vec3 R, float roughness)
+{
+	const float MAX_REFLECTION_LOD = 9.0; // todo: param/const
+	float lod = roughness * MAX_REFLECTION_LOD;
+	float lodf = floor(lod);
+	float lodc = ceil(lod);
+	vec3 a = textureLod(prefilteredMap, R, lodf).rgb;
+	vec3 b = textureLod(prefilteredMap, R, lodc).rgb;
+	return mix(a, b, lod - lodf);
+}
+
+vec3 specularContribution(vec3 L, vec3 V, vec3 N, vec3 F0, float metallic, float roughness)
+{
+	// Precalculate vectors and dot products	
+	vec3 H = normalize (V + L);
+	float dotNH = clamp(dot(N, H), 0.0, 1.0);
+	float dotNV = clamp(dot(N, V), 0.0, 1.0);
+	float dotNL = clamp(dot(N, L), 0.0, 1.0);
+
+	// Light color fixed
+	vec3 lightColor = vec3(1.0);
+
+	vec3 color = vec3(0.0);
+
+	if (dotNL > 0.0) {
+		// D = Normal distribution (Distribution of the microfacets)
+		float D = D_GGX(dotNH, roughness); 
+		// G = Geometric shadowing term (Microfacets shadowing)
+		float G = G_SchlicksmithGGX(dotNL, dotNV, roughness);
+		// F = Fresnel factor (Reflectance depending on angle of incidence)
+		vec3 F = F_Schlick(dotNV, F0);		
+		vec3 spec = D * F * G / (4.0 * dotNL * dotNV + 0.001);		
+		vec3 kD = (vec3(1.0) - F) * (1.0 - metallic);			
+		color += (kD * ALBEDO / PI + spec) * dotNL;
+	}
+
+	return color;
+}
+
+void main()
+{		
+	vec3 N = normalize(inNormal);
+	vec3 V = normalize(ubo.camPos - inWorldPos);
+	vec3 R = reflect(-V, N); 
+
+	float metallic = material.metallic;
+	float roughness = material.roughness;
+
+	vec3 F0 = vec3(0.04); 
+	F0 = mix(F0, ALBEDO, metallic);
+
+	vec3 Lo = vec3(0.0);
+	for(int i = 0; i < uboParams.lights[i].length(); i++) {
+		vec3 L = normalize(uboParams.lights[i].xyz - inWorldPos);
+		Lo += specularContribution(L, V, N, F0, metallic, roughness);
+	}   
+	
+	vec2 brdf = texture(samplerBRDFLUT, vec2(max(dot(N, V), 0.0), roughness)).rg;
+	vec3 reflection = prefilteredReflection(R, roughness).rgb;	
+	vec3 irradiance = texture(samplerIrradiance, N).rgb;
+
+	// Diffuse based on irradiance
+	vec3 diffuse = irradiance * ALBEDO;	
+
+	vec3 F = F_SchlickR(max(dot(N, V), 0.0), F0, roughness);
+
+	// Specular reflectance
+	vec3 specular = reflection * (F * brdf.x + brdf.y);
+
+	// Ambient part
+	vec3 kD = 1.0 - F;
+	kD *= 1.0 - metallic;	  
+	vec3 ambient = (kD * diffuse + specular);
+	
+	vec3 color = ambient + Lo;
+
+	// Tone mapping
+	color = Uncharted2Tonemap(color * uboParams.exposure);
+	color = color * (1.0f / Uncharted2Tonemap(vec3(11.2f)));	
+	// Gamma correction
+	color = pow(color, vec3(1.0f / uboParams.gamma));
+
+	outColor = vec4(color, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/pbribl/pbribl.frag.spv b/external/Vulkan/data/shaders/glsl/pbribl/pbribl.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..4ae9789918470078b8b26a8d5b3a5821d3c0b677
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/pbribl/pbribl.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/pbribl/pbribl.vert b/external/Vulkan/data/shaders/glsl/pbribl/pbribl.vert
new file mode 100644
index 0000000000000000000000000000000000000000..a287a8339e6462c1e4956a55cb1a449d369bc781
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/pbribl/pbribl.vert
@@ -0,0 +1,36 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec3 inNormal;
+layout (location = 2) in vec2 inUV;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 model;
+	mat4 view;
+	vec3 camPos;
+} ubo;
+
+layout (location = 0) out vec3 outWorldPos;
+layout (location = 1) out vec3 outNormal;
+layout (location = 2) out vec2 outUV;
+
+layout(push_constant) uniform PushConsts {
+	vec3 objPos;
+} pushConsts;
+
+out gl_PerVertex 
+{
+	vec4 gl_Position;
+};
+
+void main() 
+{
+	vec3 locPos = vec3(ubo.model * vec4(inPos, 1.0));
+	outWorldPos = locPos + pushConsts.objPos;
+	outNormal = mat3(ubo.model) * inNormal;
+	outUV = inUV;
+	outUV.t = 1.0 - inUV.t;
+	gl_Position =  ubo.projection * ubo.view * vec4(outWorldPos, 1.0);
+}
diff --git a/external/Vulkan/data/shaders/glsl/pbribl/pbribl.vert.spv b/external/Vulkan/data/shaders/glsl/pbribl/pbribl.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..37d92b7b3fa3c46397675e24102adf67653b7b4c
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/pbribl/pbribl.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/pbribl/prefilterenvmap.frag b/external/Vulkan/data/shaders/glsl/pbribl/prefilterenvmap.frag
new file mode 100644
index 0000000000000000000000000000000000000000..ae1212edc6b15eb131960732cf4142fd6385ebe2
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/pbribl/prefilterenvmap.frag
@@ -0,0 +1,105 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 0) out vec4 outColor;
+
+layout (binding = 0) uniform samplerCube samplerEnv;
+
+layout(push_constant) uniform PushConsts {
+	layout (offset = 64) float roughness;
+	layout (offset = 68) uint numSamples;
+} consts;
+
+const float PI = 3.1415926536;
+
+// Based omn http://byteblacksmith.com/improvements-to-the-canonical-one-liner-glsl-rand-for-opengl-es-2-0/
+float random(vec2 co)
+{
+	float a = 12.9898;
+	float b = 78.233;
+	float c = 43758.5453;
+	float dt= dot(co.xy ,vec2(a,b));
+	float sn= mod(dt,3.14);
+	return fract(sin(sn) * c);
+}
+
+vec2 hammersley2d(uint i, uint N) 
+{
+	// Radical inverse based on http://holger.dammertz.org/stuff/notes_HammersleyOnHemisphere.html
+	uint bits = (i << 16u) | (i >> 16u);
+	bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
+	bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
+	bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
+	bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
+	float rdi = float(bits) * 2.3283064365386963e-10;
+	return vec2(float(i) /float(N), rdi);
+}
+
+// Based on http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_slides.pdf
+vec3 importanceSample_GGX(vec2 Xi, float roughness, vec3 normal) 
+{
+	// Maps a 2D point to a hemisphere with spread based on roughness
+	float alpha = roughness * roughness;
+	float phi = 2.0 * PI * Xi.x + random(normal.xz) * 0.1;
+	float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (alpha*alpha - 1.0) * Xi.y));
+	float sinTheta = sqrt(1.0 - cosTheta * cosTheta);
+	vec3 H = vec3(sinTheta * cos(phi), sinTheta * sin(phi), cosTheta);
+
+	// Tangent space
+	vec3 up = abs(normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0);
+	vec3 tangentX = normalize(cross(up, normal));
+	vec3 tangentY = normalize(cross(normal, tangentX));
+
+	// Convert to world Space
+	return normalize(tangentX * H.x + tangentY * H.y + normal * H.z);
+}
+
+// Normal Distribution function
+float D_GGX(float dotNH, float roughness)
+{
+	float alpha = roughness * roughness;
+	float alpha2 = alpha * alpha;
+	float denom = dotNH * dotNH * (alpha2 - 1.0) + 1.0;
+	return (alpha2)/(PI * denom*denom); 
+}
+
+vec3 prefilterEnvMap(vec3 R, float roughness)
+{
+	vec3 N = R;
+	vec3 V = R;
+	vec3 color = vec3(0.0);
+	float totalWeight = 0.0;
+	float envMapDim = float(textureSize(samplerEnv, 0).s);
+	for(uint i = 0u; i < consts.numSamples; i++) {
+		vec2 Xi = hammersley2d(i, consts.numSamples);
+		vec3 H = importanceSample_GGX(Xi, roughness, N);
+		vec3 L = 2.0 * dot(V, H) * H - V;
+		float dotNL = clamp(dot(N, L), 0.0, 1.0);
+		if(dotNL > 0.0) {
+			// Filtering based on https://placeholderart.wordpress.com/2015/07/28/implementation-notes-runtime-environment-map-filtering-for-image-based-lighting/
+
+			float dotNH = clamp(dot(N, H), 0.0, 1.0);
+			float dotVH = clamp(dot(V, H), 0.0, 1.0);
+
+			// Probability Distribution Function
+			float pdf = D_GGX(dotNH, roughness) * dotNH / (4.0 * dotVH) + 0.0001;
+			// Slid angle of current smple
+			float omegaS = 1.0 / (float(consts.numSamples) * pdf);
+			// Solid angle of 1 pixel across all cube faces
+			float omegaP = 4.0 * PI / (6.0 * envMapDim * envMapDim);
+			// Biased (+1.0) mip level for better result
+			float mipLevel = roughness == 0.0 ? 0.0 : max(0.5 * log2(omegaS / omegaP) + 1.0, 0.0f);
+			color += textureLod(samplerEnv, L, mipLevel).rgb * dotNL;
+			totalWeight += dotNL;
+
+		}
+	}
+	return (color / totalWeight);
+}
+
+
+void main()
+{		
+	vec3 N = normalize(inPos);
+	outColor = vec4(prefilterEnvMap(N, consts.roughness), 1.0);
+}
diff --git a/external/Vulkan/data/shaders/glsl/pbribl/prefilterenvmap.frag.spv b/external/Vulkan/data/shaders/glsl/pbribl/prefilterenvmap.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..aa5955dcb75b4f70fadb48edb0c0a0b862a4b33f
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/pbribl/prefilterenvmap.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/pbribl/skybox.frag b/external/Vulkan/data/shaders/glsl/pbribl/skybox.frag
new file mode 100644
index 0000000000000000000000000000000000000000..e46fdfe09260a0216f5839a096f4c8eae7722a8a
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/pbribl/skybox.frag
@@ -0,0 +1,39 @@
+#version 450
+
+layout (binding = 2) uniform samplerCube samplerEnv;
+
+layout (location = 0) in vec3 inUVW;
+
+layout (location = 0) out vec4 outColor;
+
+layout (binding = 1) uniform UBOParams {
+	vec4 lights[4];
+	float exposure;
+	float gamma;
+} uboParams;
+
+// From http://filmicworlds.com/blog/filmic-tonemapping-operators/
+vec3 Uncharted2Tonemap(vec3 color)
+{
+	float A = 0.15;
+	float B = 0.50;
+	float C = 0.10;
+	float D = 0.20;
+	float E = 0.02;
+	float F = 0.30;
+	float W = 11.2;
+	return ((color*(A*color+C*B)+D*E)/(color*(A*color+B)+D*F))-E/F;
+}
+
+void main() 
+{
+	vec3 color = texture(samplerEnv, inUVW).rgb;
+
+	// Tone mapping
+	color = Uncharted2Tonemap(color * uboParams.exposure);
+	color = color * (1.0f / Uncharted2Tonemap(vec3(11.2f)));	
+	// Gamma correction
+	color = pow(color, vec3(1.0f / uboParams.gamma));
+	
+	outColor = vec4(color, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/pbribl/skybox.frag.spv b/external/Vulkan/data/shaders/glsl/pbribl/skybox.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..ac4c939c6d95469c5ea389780ce09238ce6adefe
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/pbribl/skybox.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/pbribl/skybox.vert b/external/Vulkan/data/shaders/glsl/pbribl/skybox.vert
new file mode 100644
index 0000000000000000000000000000000000000000..b93a3c3587f537cc2bdae619ac3f086834f6e09e
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/pbribl/skybox.vert
@@ -0,0 +1,24 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec3 inNormal;
+layout (location = 2) in vec2 inUV;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 model;
+} ubo;
+
+layout (location = 0) out vec3 outUVW;
+
+out gl_PerVertex 
+{
+	vec4 gl_Position;
+};
+
+void main() 
+{
+	outUVW = inPos;
+	gl_Position = ubo.projection * ubo.model * vec4(inPos.xyz, 1.0);
+}
diff --git a/external/Vulkan/data/shaders/glsl/pbribl/skybox.vert.spv b/external/Vulkan/data/shaders/glsl/pbribl/skybox.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..fcbc5bc4f27bd8c6ca7863a3d5c1a719d047ebbb
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/pbribl/skybox.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/pbrtexture/filtercube.vert b/external/Vulkan/data/shaders/glsl/pbrtexture/filtercube.vert
new file mode 100644
index 0000000000000000000000000000000000000000..1226e28ee44658e535b013d91b660b34e1e88664
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/pbrtexture/filtercube.vert
@@ -0,0 +1,19 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+
+layout(push_constant) uniform PushConsts {
+	layout (offset = 0) mat4 mvp;
+} pushConsts;
+
+layout (location = 0) out vec3 outUVW;
+
+out gl_PerVertex {
+	vec4 gl_Position;
+};
+
+void main() 
+{
+	outUVW = inPos;
+	gl_Position = pushConsts.mvp * vec4(inPos.xyz, 1.0);
+}
diff --git a/external/Vulkan/data/shaders/glsl/pbrtexture/filtercube.vert.spv b/external/Vulkan/data/shaders/glsl/pbrtexture/filtercube.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..220a3df49bf403b63a63d60be2a53cc067fb6ac4
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/pbrtexture/filtercube.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/pbrtexture/genbrdflut.frag b/external/Vulkan/data/shaders/glsl/pbrtexture/genbrdflut.frag
new file mode 100644
index 0000000000000000000000000000000000000000..b6290dd082ecdb94a9deb43f761508a1feea0e5c
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/pbrtexture/genbrdflut.frag
@@ -0,0 +1,90 @@
+#version 450
+
+layout (location = 0) in vec2 inUV;
+layout (location = 0) out vec4 outColor;
+layout (constant_id = 0) const uint NUM_SAMPLES = 1024u;
+
+const float PI = 3.1415926536;
+
+// Based omn http://byteblacksmith.com/improvements-to-the-canonical-one-liner-glsl-rand-for-opengl-es-2-0/
+float random(vec2 co)
+{
+	float a = 12.9898;
+	float b = 78.233;
+	float c = 43758.5453;
+	float dt= dot(co.xy ,vec2(a,b));
+	float sn= mod(dt,3.14);
+	return fract(sin(sn) * c);
+}
+
+vec2 hammersley2d(uint i, uint N) 
+{
+	// Radical inverse based on http://holger.dammertz.org/stuff/notes_HammersleyOnHemisphere.html
+	uint bits = (i << 16u) | (i >> 16u);
+	bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
+	bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
+	bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
+	bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
+	float rdi = float(bits) * 2.3283064365386963e-10;
+	return vec2(float(i) /float(N), rdi);
+}
+
+// Based on http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_slides.pdf
+vec3 importanceSample_GGX(vec2 Xi, float roughness, vec3 normal) 
+{
+	// Maps a 2D point to a hemisphere with spread based on roughness
+	float alpha = roughness * roughness;
+	float phi = 2.0 * PI * Xi.x + random(normal.xz) * 0.1;
+	float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (alpha*alpha - 1.0) * Xi.y));
+	float sinTheta = sqrt(1.0 - cosTheta * cosTheta);
+	vec3 H = vec3(sinTheta * cos(phi), sinTheta * sin(phi), cosTheta);
+
+	// Tangent space
+	vec3 up = abs(normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0);
+	vec3 tangentX = normalize(cross(up, normal));
+	vec3 tangentY = normalize(cross(normal, tangentX));
+
+	// Convert to world Space
+	return normalize(tangentX * H.x + tangentY * H.y + normal * H.z);
+}
+
+// Geometric Shadowing function
+float G_SchlicksmithGGX(float dotNL, float dotNV, float roughness)
+{
+	float k = (roughness * roughness) / 2.0;
+	float GL = dotNL / (dotNL * (1.0 - k) + k);
+	float GV = dotNV / (dotNV * (1.0 - k) + k);
+	return GL * GV;
+}
+
+vec2 BRDF(float NoV, float roughness)
+{
+	// Normal always points along z-axis for the 2D lookup 
+	const vec3 N = vec3(0.0, 0.0, 1.0);
+	vec3 V = vec3(sqrt(1.0 - NoV*NoV), 0.0, NoV);
+
+	vec2 LUT = vec2(0.0);
+	for(uint i = 0u; i < NUM_SAMPLES; i++) {
+		vec2 Xi = hammersley2d(i, NUM_SAMPLES);
+		vec3 H = importanceSample_GGX(Xi, roughness, N);
+		vec3 L = 2.0 * dot(V, H) * H - V;
+
+		float dotNL = max(dot(N, L), 0.0);
+		float dotNV = max(dot(N, V), 0.0);
+		float dotVH = max(dot(V, H), 0.0); 
+		float dotNH = max(dot(H, N), 0.0);
+
+		if (dotNL > 0.0) {
+			float G = G_SchlicksmithGGX(dotNL, dotNV, roughness);
+			float G_Vis = (G * dotVH) / (dotNH * dotNV);
+			float Fc = pow(1.0 - dotVH, 5.0);
+			LUT += vec2((1.0 - Fc) * G_Vis, Fc * G_Vis);
+		}
+	}
+	return LUT / float(NUM_SAMPLES);
+}
+
+void main() 
+{
+	outColor = vec4(BRDF(inUV.s, 1.0-inUV.t), 0.0, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/pbrtexture/genbrdflut.frag.spv b/external/Vulkan/data/shaders/glsl/pbrtexture/genbrdflut.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..a54cdee7393917ef67a37b9b3437cea3b04d20a5
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/pbrtexture/genbrdflut.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/pbrtexture/genbrdflut.vert b/external/Vulkan/data/shaders/glsl/pbrtexture/genbrdflut.vert
new file mode 100644
index 0000000000000000000000000000000000000000..f3dd233520470b0842a9abda45a9807d3498653e
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/pbrtexture/genbrdflut.vert
@@ -0,0 +1,9 @@
+#version 450
+
+layout (location = 0) out vec2 outUV;
+
+void main()
+{
+	outUV = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
+	gl_Position = vec4(outUV * 2.0f - 1.0f, 0.0f, 1.0f);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/pbrtexture/genbrdflut.vert.spv b/external/Vulkan/data/shaders/glsl/pbrtexture/genbrdflut.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..fc2b37dd3ab32deb82e13c433350841265609b03
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/pbrtexture/genbrdflut.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/pbrtexture/irradiancecube.frag b/external/Vulkan/data/shaders/glsl/pbrtexture/irradiancecube.frag
new file mode 100644
index 0000000000000000000000000000000000000000..3232db4808e7db3970b246c43c30b77f5f66c368
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/pbrtexture/irradiancecube.frag
@@ -0,0 +1,37 @@
+// Generates an irradiance cube from an environment map using convolution
+
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 0) out vec4 outColor;
+layout (binding = 0) uniform samplerCube samplerEnv;
+
+layout(push_constant) uniform PushConsts {
+	layout (offset = 64) float deltaPhi;
+	layout (offset = 68) float deltaTheta;
+} consts;
+
+#define PI 3.1415926535897932384626433832795
+
+void main()
+{
+	vec3 N = normalize(inPos);
+	vec3 up = vec3(0.0, 1.0, 0.0);
+	vec3 right = normalize(cross(up, N));
+	up = cross(N, right);
+
+	const float TWO_PI = PI * 2.0;
+	const float HALF_PI = PI * 0.5;
+
+	vec3 color = vec3(0.0);
+	uint sampleCount = 0u;
+	for (float phi = 0.0; phi < TWO_PI; phi += consts.deltaPhi) {
+		for (float theta = 0.0; theta < HALF_PI; theta += consts.deltaTheta) {
+			vec3 tempVec = cos(phi) * right + sin(phi) * up;
+			vec3 sampleVector = cos(theta) * N + sin(theta) * tempVec;
+			color += texture(samplerEnv, sampleVector).rgb * cos(theta) * sin(theta);
+			sampleCount++;
+		}
+	}
+	outColor = vec4(PI * color / float(sampleCount), 1.0);
+}
diff --git a/external/Vulkan/data/shaders/glsl/pbrtexture/irradiancecube.frag.spv b/external/Vulkan/data/shaders/glsl/pbrtexture/irradiancecube.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..a369970e8aa72d0a9a436d5974cd8d597697a6a5
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/pbrtexture/irradiancecube.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/pbrtexture/pbrtexture.frag b/external/Vulkan/data/shaders/glsl/pbrtexture/pbrtexture.frag
new file mode 100644
index 0000000000000000000000000000000000000000..afe04070a88e3673591fec4ad31cf8c1e4fc4722
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/pbrtexture/pbrtexture.frag
@@ -0,0 +1,173 @@
+#version 450
+
+layout (location = 0) in vec3 inWorldPos;
+layout (location = 1) in vec3 inNormal;
+layout (location = 2) in vec2 inUV;
+layout (location = 3) in vec4 inTangent;
+
+layout (binding = 0) uniform UBO {
+	mat4 projection;
+	mat4 model;
+	mat4 view;
+	vec3 camPos;
+} ubo;
+
+layout (binding = 1) uniform UBOParams {
+	vec4 lights[4];
+	float exposure;
+	float gamma;
+} uboParams;
+
+layout (binding = 2) uniform samplerCube samplerIrradiance;
+layout (binding = 3) uniform sampler2D samplerBRDFLUT;
+layout (binding = 4) uniform samplerCube prefilteredMap;
+
+layout (binding = 5) uniform sampler2D albedoMap;
+layout (binding = 6) uniform sampler2D normalMap;
+layout (binding = 7) uniform sampler2D aoMap;
+layout (binding = 8) uniform sampler2D metallicMap;
+layout (binding = 9) uniform sampler2D roughnessMap;
+
+
+layout (location = 0) out vec4 outColor;
+
+#define PI 3.1415926535897932384626433832795
+#define ALBEDO pow(texture(albedoMap, inUV).rgb, vec3(2.2))
+
+// From http://filmicgames.com/archives/75
+vec3 Uncharted2Tonemap(vec3 x)
+{
+	float A = 0.15;
+	float B = 0.50;
+	float C = 0.10;
+	float D = 0.20;
+	float E = 0.02;
+	float F = 0.30;
+	return ((x*(A*x+C*B)+D*E)/(x*(A*x+B)+D*F))-E/F;
+}
+
+// Normal Distribution function --------------------------------------
+float D_GGX(float dotNH, float roughness)
+{
+	float alpha = roughness * roughness;
+	float alpha2 = alpha * alpha;
+	float denom = dotNH * dotNH * (alpha2 - 1.0) + 1.0;
+	return (alpha2)/(PI * denom*denom); 
+}
+
+// Geometric Shadowing function --------------------------------------
+float G_SchlicksmithGGX(float dotNL, float dotNV, float roughness)
+{
+	float r = (roughness + 1.0);
+	float k = (r*r) / 8.0;
+	float GL = dotNL / (dotNL * (1.0 - k) + k);
+	float GV = dotNV / (dotNV * (1.0 - k) + k);
+	return GL * GV;
+}
+
+// Fresnel function ----------------------------------------------------
+vec3 F_Schlick(float cosTheta, vec3 F0)
+{
+	return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
+}
+vec3 F_SchlickR(float cosTheta, vec3 F0, float roughness)
+{
+	return F0 + (max(vec3(1.0 - roughness), F0) - F0) * pow(1.0 - cosTheta, 5.0);
+}
+
+vec3 prefilteredReflection(vec3 R, float roughness)
+{
+	const float MAX_REFLECTION_LOD = 9.0; // todo: param/const
+	float lod = roughness * MAX_REFLECTION_LOD;
+	float lodf = floor(lod);
+	float lodc = ceil(lod);
+	vec3 a = textureLod(prefilteredMap, R, lodf).rgb;
+	vec3 b = textureLod(prefilteredMap, R, lodc).rgb;
+	return mix(a, b, lod - lodf);
+}
+
+vec3 specularContribution(vec3 L, vec3 V, vec3 N, vec3 F0, float metallic, float roughness)
+{
+	// Precalculate vectors and dot products	
+	vec3 H = normalize (V + L);
+	float dotNH = clamp(dot(N, H), 0.0, 1.0);
+	float dotNV = clamp(dot(N, V), 0.0, 1.0);
+	float dotNL = clamp(dot(N, L), 0.0, 1.0);
+
+	// Light color fixed
+	vec3 lightColor = vec3(1.0);
+
+	vec3 color = vec3(0.0);
+
+	if (dotNL > 0.0) {
+		// D = Normal distribution (Distribution of the microfacets)
+		float D = D_GGX(dotNH, roughness); 
+		// G = Geometric shadowing term (Microfacets shadowing)
+		float G = G_SchlicksmithGGX(dotNL, dotNV, roughness);
+		// F = Fresnel factor (Reflectance depending on angle of incidence)
+		vec3 F = F_Schlick(dotNV, F0);		
+		vec3 spec = D * F * G / (4.0 * dotNL * dotNV + 0.001);		
+		vec3 kD = (vec3(1.0) - F) * (1.0 - metallic);			
+		color += (kD * ALBEDO / PI + spec) * dotNL;
+	}
+
+	return color;
+}
+
+vec3 calculateNormal()
+{
+	vec3 tangentNormal = texture(normalMap, inUV).xyz * 2.0 - 1.0;
+
+	vec3 N = normalize(inNormal);
+	vec3 T = normalize(inTangent.xyz);
+	vec3 B = normalize(cross(N, T));
+	mat3 TBN = mat3(T, B, N);
+	return normalize(TBN * tangentNormal);
+}
+
+void main()
+{		
+	vec3 N = calculateNormal();
+
+	vec3 V = normalize(ubo.camPos - inWorldPos);
+	vec3 R = reflect(-V, N); 
+
+	float metallic = texture(metallicMap, inUV).r;
+	float roughness = texture(roughnessMap, inUV).r;
+
+	vec3 F0 = vec3(0.04); 
+	F0 = mix(F0, ALBEDO, metallic);
+
+	vec3 Lo = vec3(0.0);
+	for(int i = 0; i < uboParams.lights[i].length(); i++) {
+		vec3 L = normalize(uboParams.lights[i].xyz - inWorldPos);
+		Lo += specularContribution(L, V, N, F0, metallic, roughness);
+	}   
+	
+	vec2 brdf = texture(samplerBRDFLUT, vec2(max(dot(N, V), 0.0), roughness)).rg;
+	vec3 reflection = prefilteredReflection(R, roughness).rgb;	
+	vec3 irradiance = texture(samplerIrradiance, N).rgb;
+
+	// Diffuse based on irradiance
+	vec3 diffuse = irradiance * ALBEDO;	
+
+	vec3 F = F_SchlickR(max(dot(N, V), 0.0), F0, roughness);
+
+	// Specular reflectance
+	vec3 specular = reflection * (F * brdf.x + brdf.y);
+
+	// Ambient part
+	vec3 kD = 1.0 - F;
+	kD *= 1.0 - metallic;	  
+	vec3 ambient = (kD * diffuse + specular) * texture(aoMap, inUV).rrr;
+	
+	vec3 color = ambient + Lo;
+
+	// Tone mapping
+	color = Uncharted2Tonemap(color * uboParams.exposure);
+	color = color * (1.0f / Uncharted2Tonemap(vec3(11.2f)));	
+	// Gamma correction
+	color = pow(color, vec3(1.0f / uboParams.gamma));
+
+	outColor = vec4(color, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/pbrtexture/pbrtexture.frag.spv b/external/Vulkan/data/shaders/glsl/pbrtexture/pbrtexture.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..9891cd330bc2d60f3db31599cd615e5069314f12
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/pbrtexture/pbrtexture.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/pbrtexture/pbrtexture.vert b/external/Vulkan/data/shaders/glsl/pbrtexture/pbrtexture.vert
new file mode 100644
index 0000000000000000000000000000000000000000..ee6b064ac553478b087f13b0fa089c992c437df4
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/pbrtexture/pbrtexture.vert
@@ -0,0 +1,30 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec3 inNormal;
+layout (location = 2) in vec2 inUV;
+layout (location = 3) in vec4 inTangent;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 model;
+	mat4 view;
+	//mat4 normal;
+	vec3 camPos;
+} ubo;
+
+layout (location = 0) out vec3 outWorldPos;
+layout (location = 1) out vec3 outNormal;
+layout (location = 2) out vec2 outUV;
+layout (location = 3) out vec4 outTangent;
+
+void main() 
+{
+	vec3 locPos = vec3(ubo.model * vec4(inPos, 1.0));
+	outWorldPos = locPos;
+	outNormal = mat3(ubo.model) * inNormal;
+	outTangent = vec4(mat3(ubo.model) * inTangent.xyz, inTangent.w);
+	outUV = inUV;
+	gl_Position =  ubo.projection * ubo.view * vec4(outWorldPos, 1.0);
+}
diff --git a/external/Vulkan/data/shaders/glsl/pbrtexture/pbrtexture.vert.spv b/external/Vulkan/data/shaders/glsl/pbrtexture/pbrtexture.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..0a28f71e97b90c5820552707b9e3ded18f8119da
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/pbrtexture/pbrtexture.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/pbrtexture/prefilterenvmap.frag b/external/Vulkan/data/shaders/glsl/pbrtexture/prefilterenvmap.frag
new file mode 100644
index 0000000000000000000000000000000000000000..ae1212edc6b15eb131960732cf4142fd6385ebe2
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/pbrtexture/prefilterenvmap.frag
@@ -0,0 +1,105 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 0) out vec4 outColor;
+
+layout (binding = 0) uniform samplerCube samplerEnv;
+
+layout(push_constant) uniform PushConsts {
+	layout (offset = 64) float roughness;
+	layout (offset = 68) uint numSamples;
+} consts;
+
+const float PI = 3.1415926536;
+
+// Based omn http://byteblacksmith.com/improvements-to-the-canonical-one-liner-glsl-rand-for-opengl-es-2-0/
+float random(vec2 co)
+{
+	float a = 12.9898;
+	float b = 78.233;
+	float c = 43758.5453;
+	float dt= dot(co.xy ,vec2(a,b));
+	float sn= mod(dt,3.14);
+	return fract(sin(sn) * c);
+}
+
+vec2 hammersley2d(uint i, uint N) 
+{
+	// Radical inverse based on http://holger.dammertz.org/stuff/notes_HammersleyOnHemisphere.html
+	uint bits = (i << 16u) | (i >> 16u);
+	bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
+	bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
+	bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
+	bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
+	float rdi = float(bits) * 2.3283064365386963e-10;
+	return vec2(float(i) /float(N), rdi);
+}
+
+// Based on http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_slides.pdf
+vec3 importanceSample_GGX(vec2 Xi, float roughness, vec3 normal) 
+{
+	// Maps a 2D point to a hemisphere with spread based on roughness
+	float alpha = roughness * roughness;
+	float phi = 2.0 * PI * Xi.x + random(normal.xz) * 0.1;
+	float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (alpha*alpha - 1.0) * Xi.y));
+	float sinTheta = sqrt(1.0 - cosTheta * cosTheta);
+	vec3 H = vec3(sinTheta * cos(phi), sinTheta * sin(phi), cosTheta);
+
+	// Tangent space
+	vec3 up = abs(normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0);
+	vec3 tangentX = normalize(cross(up, normal));
+	vec3 tangentY = normalize(cross(normal, tangentX));
+
+	// Convert to world Space
+	return normalize(tangentX * H.x + tangentY * H.y + normal * H.z);
+}
+
+// Normal Distribution function
+float D_GGX(float dotNH, float roughness)
+{
+	float alpha = roughness * roughness;
+	float alpha2 = alpha * alpha;
+	float denom = dotNH * dotNH * (alpha2 - 1.0) + 1.0;
+	return (alpha2)/(PI * denom*denom); 
+}
+
+vec3 prefilterEnvMap(vec3 R, float roughness)
+{
+	vec3 N = R;
+	vec3 V = R;
+	vec3 color = vec3(0.0);
+	float totalWeight = 0.0;
+	float envMapDim = float(textureSize(samplerEnv, 0).s);
+	for(uint i = 0u; i < consts.numSamples; i++) {
+		vec2 Xi = hammersley2d(i, consts.numSamples);
+		vec3 H = importanceSample_GGX(Xi, roughness, N);
+		vec3 L = 2.0 * dot(V, H) * H - V;
+		float dotNL = clamp(dot(N, L), 0.0, 1.0);
+		if(dotNL > 0.0) {
+			// Filtering based on https://placeholderart.wordpress.com/2015/07/28/implementation-notes-runtime-environment-map-filtering-for-image-based-lighting/
+
+			float dotNH = clamp(dot(N, H), 0.0, 1.0);
+			float dotVH = clamp(dot(V, H), 0.0, 1.0);
+
+			// Probability Distribution Function
+			float pdf = D_GGX(dotNH, roughness) * dotNH / (4.0 * dotVH) + 0.0001;
+			// Slid angle of current smple
+			float omegaS = 1.0 / (float(consts.numSamples) * pdf);
+			// Solid angle of 1 pixel across all cube faces
+			float omegaP = 4.0 * PI / (6.0 * envMapDim * envMapDim);
+			// Biased (+1.0) mip level for better result
+			float mipLevel = roughness == 0.0 ? 0.0 : max(0.5 * log2(omegaS / omegaP) + 1.0, 0.0f);
+			color += textureLod(samplerEnv, L, mipLevel).rgb * dotNL;
+			totalWeight += dotNL;
+
+		}
+	}
+	return (color / totalWeight);
+}
+
+
+void main()
+{		
+	vec3 N = normalize(inPos);
+	outColor = vec4(prefilterEnvMap(N, consts.roughness), 1.0);
+}
diff --git a/external/Vulkan/data/shaders/glsl/pbrtexture/prefilterenvmap.frag.spv b/external/Vulkan/data/shaders/glsl/pbrtexture/prefilterenvmap.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..aa5955dcb75b4f70fadb48edb0c0a0b862a4b33f
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/pbrtexture/prefilterenvmap.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/pbrtexture/skybox.frag b/external/Vulkan/data/shaders/glsl/pbrtexture/skybox.frag
new file mode 100644
index 0000000000000000000000000000000000000000..e46fdfe09260a0216f5839a096f4c8eae7722a8a
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/pbrtexture/skybox.frag
@@ -0,0 +1,39 @@
+#version 450
+
+layout (binding = 2) uniform samplerCube samplerEnv;
+
+layout (location = 0) in vec3 inUVW;
+
+layout (location = 0) out vec4 outColor;
+
+layout (binding = 1) uniform UBOParams {
+	vec4 lights[4];
+	float exposure;
+	float gamma;
+} uboParams;
+
+// From http://filmicworlds.com/blog/filmic-tonemapping-operators/
+vec3 Uncharted2Tonemap(vec3 color)
+{
+	float A = 0.15;
+	float B = 0.50;
+	float C = 0.10;
+	float D = 0.20;
+	float E = 0.02;
+	float F = 0.30;
+	float W = 11.2;
+	return ((color*(A*color+C*B)+D*E)/(color*(A*color+B)+D*F))-E/F;
+}
+
+void main() 
+{
+	vec3 color = texture(samplerEnv, inUVW).rgb;
+
+	// Tone mapping
+	color = Uncharted2Tonemap(color * uboParams.exposure);
+	color = color * (1.0f / Uncharted2Tonemap(vec3(11.2f)));	
+	// Gamma correction
+	color = pow(color, vec3(1.0f / uboParams.gamma));
+	
+	outColor = vec4(color, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/pbrtexture/skybox.frag.spv b/external/Vulkan/data/shaders/glsl/pbrtexture/skybox.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..ac4c939c6d95469c5ea389780ce09238ce6adefe
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/pbrtexture/skybox.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/pbrtexture/skybox.vert b/external/Vulkan/data/shaders/glsl/pbrtexture/skybox.vert
new file mode 100644
index 0000000000000000000000000000000000000000..b93a3c3587f537cc2bdae619ac3f086834f6e09e
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/pbrtexture/skybox.vert
@@ -0,0 +1,24 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec3 inNormal;
+layout (location = 2) in vec2 inUV;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 model;
+} ubo;
+
+layout (location = 0) out vec3 outUVW;
+
+out gl_PerVertex 
+{
+	vec4 gl_Position;
+};
+
+void main() 
+{
+	outUVW = inPos;
+	gl_Position = ubo.projection * ubo.model * vec4(inPos.xyz, 1.0);
+}
diff --git a/external/Vulkan/data/shaders/glsl/pbrtexture/skybox.vert.spv b/external/Vulkan/data/shaders/glsl/pbrtexture/skybox.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..fcbc5bc4f27bd8c6ca7863a3d5c1a719d047ebbb
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/pbrtexture/skybox.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/pipelines/phong.frag b/external/Vulkan/data/shaders/glsl/pipelines/phong.frag
new file mode 100644
index 0000000000000000000000000000000000000000..b207e798d551734cec284517b24589eb4aa11d56
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/pipelines/phong.frag
@@ -0,0 +1,26 @@
+#version 450
+
+layout (binding = 1) uniform sampler2D samplerColorMap;
+
+layout (location = 0) in vec3 inNormal;
+layout (location = 1) in vec3 inColor;
+layout (location = 2) in vec3 inViewVec;
+layout (location = 3) in vec3 inLightVec;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+	// Desaturate color
+    vec3 color = vec3(mix(inColor, vec3(dot(vec3(0.2126,0.7152,0.0722), inColor)), 0.65));	
+
+	// High ambient colors because mesh materials are pretty dark
+	vec3 ambient = color * vec3(1.0);
+	vec3 N = normalize(inNormal);
+	vec3 L = normalize(inLightVec);
+	vec3 V = normalize(inViewVec);
+	vec3 R = reflect(-L, N);
+	vec3 diffuse = max(dot(N, L), 0.0) * color;
+	vec3 specular = pow(max(dot(R, V), 0.0), 32.0) * vec3(0.35);
+	outFragColor = vec4(ambient + diffuse * 1.75 + specular, 1.0);		
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/pipelines/phong.frag.spv b/external/Vulkan/data/shaders/glsl/pipelines/phong.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..d9fbe6c48a524758a438cd219f9dd628b031a702
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/pipelines/phong.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/pipelines/phong.vert b/external/Vulkan/data/shaders/glsl/pipelines/phong.vert
new file mode 100644
index 0000000000000000000000000000000000000000..c0069072bef1c7bdae7db7243c1d81587f99f973
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/pipelines/phong.vert
@@ -0,0 +1,30 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec3 inNormal;
+layout (location = 2) in vec3 inColor;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 model;
+	vec4 lightPos;
+} ubo;
+
+layout (location = 0) out vec3 outNormal;
+layout (location = 1) out vec3 outColor;
+layout (location = 2) out vec3 outViewVec;
+layout (location = 3) out vec3 outLightVec;
+
+void main() 
+{
+	outNormal = inNormal;
+	outColor = inColor;
+	gl_Position = ubo.projection * ubo.model * vec4(inPos.xyz, 1.0);
+	
+	vec4 pos = ubo.model * vec4(inPos, 1.0);
+	outNormal = mat3(ubo.model) * inNormal;
+	vec3 lPos = mat3(ubo.model) * ubo.lightPos.xyz;
+	outLightVec = lPos - pos.xyz;
+	outViewVec = -pos.xyz;		
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/pipelines/phong.vert.spv b/external/Vulkan/data/shaders/glsl/pipelines/phong.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..b22327fb78822e53dedfb153de27c9cd7e3ff9b3
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/pipelines/phong.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/pipelines/toon.frag b/external/Vulkan/data/shaders/glsl/pipelines/toon.frag
new file mode 100644
index 0000000000000000000000000000000000000000..b947da365e0488accc5ccb57fc0c736f023635a4
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/pipelines/toon.frag
@@ -0,0 +1,35 @@
+#version 450
+
+layout (binding = 1) uniform sampler2D samplerColorMap;
+
+layout (location = 0) in vec3 inNormal;
+layout (location = 1) in vec3 inColor;
+layout (location = 2) in vec3 inViewVec;
+layout (location = 3) in vec3 inLightVec;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+	// Desaturate color
+    vec3 color = vec3(mix(inColor, vec3(dot(vec3(0.2126,0.7152,0.0722), inColor)), 0.65));	
+
+	// High ambient colors because mesh materials are pretty dark
+	vec3 ambient = color * vec3(1.0);
+	vec3 N = normalize(inNormal);
+	vec3 L = normalize(inLightVec);
+	vec3 V = normalize(inViewVec);
+	vec3 R = reflect(-L, N);
+	vec3 diffuse = max(dot(N, L), 0.0) * color;
+	vec3 specular = pow(max(dot(R, V), 0.0), 16.0) * vec3(0.75);
+	outFragColor = vec4(ambient + diffuse * 1.75 + specular, 1.0);		
+	
+	float intensity = dot(N,L);
+	float shade = 1.0;
+	shade = intensity < 0.5 ? 0.75 : shade;
+	shade = intensity < 0.35 ? 0.6 : shade;
+	shade = intensity < 0.25 ? 0.5 : shade;
+	shade = intensity < 0.1 ? 0.25 : shade;
+
+	outFragColor.rgb = inColor * 3.0 * shade;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/pipelines/toon.frag.spv b/external/Vulkan/data/shaders/glsl/pipelines/toon.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..e5b75b39372a8e74efef15fcb134211639247493
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/pipelines/toon.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/pipelines/toon.vert b/external/Vulkan/data/shaders/glsl/pipelines/toon.vert
new file mode 100644
index 0000000000000000000000000000000000000000..c0069072bef1c7bdae7db7243c1d81587f99f973
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/pipelines/toon.vert
@@ -0,0 +1,30 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec3 inNormal;
+layout (location = 2) in vec3 inColor;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 model;
+	vec4 lightPos;
+} ubo;
+
+layout (location = 0) out vec3 outNormal;
+layout (location = 1) out vec3 outColor;
+layout (location = 2) out vec3 outViewVec;
+layout (location = 3) out vec3 outLightVec;
+
+void main() 
+{
+	outNormal = inNormal;
+	outColor = inColor;
+	gl_Position = ubo.projection * ubo.model * vec4(inPos.xyz, 1.0);
+	
+	vec4 pos = ubo.model * vec4(inPos, 1.0);
+	outNormal = mat3(ubo.model) * inNormal;
+	vec3 lPos = mat3(ubo.model) * ubo.lightPos.xyz;
+	outLightVec = lPos - pos.xyz;
+	outViewVec = -pos.xyz;		
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/pipelines/toon.vert.spv b/external/Vulkan/data/shaders/glsl/pipelines/toon.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..b22327fb78822e53dedfb153de27c9cd7e3ff9b3
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/pipelines/toon.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/pipelines/wireframe.frag b/external/Vulkan/data/shaders/glsl/pipelines/wireframe.frag
new file mode 100644
index 0000000000000000000000000000000000000000..1ba235763d4be716d520c617e3991a9102959190
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/pipelines/wireframe.frag
@@ -0,0 +1,10 @@
+#version 450
+
+layout (location = 0) in vec3 inColor;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+	outFragColor.rgb = inColor * 1.5;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/pipelines/wireframe.frag.spv b/external/Vulkan/data/shaders/glsl/pipelines/wireframe.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..1f294e24eb8c9935d5b88c868491ada2f58cd941
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/pipelines/wireframe.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/pipelines/wireframe.vert b/external/Vulkan/data/shaders/glsl/pipelines/wireframe.vert
new file mode 100644
index 0000000000000000000000000000000000000000..b70f4c5e8c3b2d656e20f00d37f2b7860c312ec6
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/pipelines/wireframe.vert
@@ -0,0 +1,18 @@
+#version 450
+
+layout (location = 0) in vec4 inPos;
+layout (location = 2) in vec3 inColor;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 model;
+} ubo;
+
+layout (location = 0) out vec3 outColor;
+
+void main() 
+{
+	outColor = inColor;
+	gl_Position = ubo.projection * ubo.model * inPos;
+}
diff --git a/external/Vulkan/data/shaders/glsl/pipelines/wireframe.vert.spv b/external/Vulkan/data/shaders/glsl/pipelines/wireframe.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..58fa481c25119915f9d3da0ef1628ce4fb5214e8
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/pipelines/wireframe.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/pipelinestatistics/scene.frag b/external/Vulkan/data/shaders/glsl/pipelinestatistics/scene.frag
new file mode 100644
index 0000000000000000000000000000000000000000..43f18f5fcac3ee76ae68e0c223aec756f1733888
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/pipelinestatistics/scene.frag
@@ -0,0 +1,19 @@
+#version 450
+
+layout (location = 0) in vec3 inNormal;
+layout (location = 1) in vec3 inColor;
+layout (location = 2) in vec3 inViewVec;
+layout (location = 3) in vec3 inLightVec;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+	vec3 N = normalize(inNormal);
+	vec3 L = normalize(inLightVec);
+	vec3 V = normalize(inViewVec);
+	vec3 R = reflect(-L, N);
+	vec3 diffuse = max(dot(N, L), 0.0) * inColor;
+	vec3 specular = pow(max(dot(R, V), 0.0), 8.0) * vec3(0.75);
+	outFragColor = vec4(diffuse + specular, 0.5);	
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/pipelinestatistics/scene.frag.spv b/external/Vulkan/data/shaders/glsl/pipelinestatistics/scene.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..bf0c7a1016df160eec1a205a8107289585de450f
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/pipelinestatistics/scene.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/pipelinestatistics/scene.tesc b/external/Vulkan/data/shaders/glsl/pipelinestatistics/scene.tesc
new file mode 100644
index 0000000000000000000000000000000000000000..60693281adc7f9bbe3da472064cda3c93cb41c40
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/pipelinestatistics/scene.tesc
@@ -0,0 +1,30 @@
+#version 450
+
+layout (vertices = 3) out;
+
+layout (location = 0) in vec3 inNormal[];
+layout (location = 1) in vec3 inColor[];
+layout (location = 2) in vec3 inViewVec[];
+layout (location = 3) in vec3 inLightVec[];
+
+layout (location = 0) out vec3 outNormal[3];
+layout (location = 1) out vec3 outColor[3];
+layout (location = 2) out vec3 outViewVec[3];
+layout (location = 3) out vec3 outLightVec[3];
+
+void main(void)
+{
+    if (gl_InvocationID == 0)
+    {
+        gl_TessLevelInner[0] = 2.0;
+        gl_TessLevelOuter[0] = 1.0;
+        gl_TessLevelOuter[1] = 1.0;
+        gl_TessLevelOuter[2] = 1.0;
+    }
+
+    gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;
+	outNormal[gl_InvocationID] = inNormal[gl_InvocationID];
+	outColor[gl_InvocationID] = inColor[gl_InvocationID];	
+	outViewVec[gl_InvocationID] = inViewVec[gl_InvocationID];	
+	outLightVec[gl_InvocationID] = inLightVec[gl_InvocationID];	
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/pipelinestatistics/scene.tesc.spv b/external/Vulkan/data/shaders/glsl/pipelinestatistics/scene.tesc.spv
new file mode 100644
index 0000000000000000000000000000000000000000..6c58fd746fbb38ebb9ceb01651d0e8b056b59e52
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/pipelinestatistics/scene.tesc.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/pipelinestatistics/scene.tese b/external/Vulkan/data/shaders/glsl/pipelinestatistics/scene.tese
new file mode 100644
index 0000000000000000000000000000000000000000..39f020d2d8c63c17449dee1aa0b8c382d3c819bc
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/pipelinestatistics/scene.tese
@@ -0,0 +1,24 @@
+#version 450
+
+layout (triangles) in;
+
+layout (location = 0) in vec3 inNormal[];
+layout (location = 1) in vec3 inColor[];
+layout (location = 2) in vec3 inViewVec[];
+layout (location = 3) in vec3 inLightVec[];
+
+layout (location = 0) out vec3 outNormal;
+layout (location = 1) out vec3 outColor;
+layout (location = 2) out vec3 outViewVec;
+layout (location = 3) out vec3 outLightVec;
+
+void main(void)
+{
+    gl_Position = (gl_TessCoord.x * gl_in[2].gl_Position) +
+                  (gl_TessCoord.y * gl_in[1].gl_Position) +
+                  (gl_TessCoord.z * gl_in[0].gl_Position);
+	outNormal = gl_TessCoord.x*inNormal[2] + gl_TessCoord.y*inNormal[1] + gl_TessCoord.z*inNormal[0];
+	outViewVec = gl_TessCoord.x*inViewVec[2] + gl_TessCoord.y*inViewVec[1] + gl_TessCoord.z*inViewVec[0];
+	outLightVec = gl_TessCoord.x*inLightVec[2] + gl_TessCoord.y*inLightVec[1] + gl_TessCoord.z*inLightVec[0];
+	outColor = inColor[0];
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/pipelinestatistics/scene.tese.spv b/external/Vulkan/data/shaders/glsl/pipelinestatistics/scene.tese.spv
new file mode 100644
index 0000000000000000000000000000000000000000..5cb3391a02785f026f125b9c81700bc74e7c69d9
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/pipelinestatistics/scene.tese.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/pipelinestatistics/scene.vert b/external/Vulkan/data/shaders/glsl/pipelinestatistics/scene.vert
new file mode 100644
index 0000000000000000000000000000000000000000..fc54284e45d2877dbc0395b4aa9c8f680b7012ce
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/pipelinestatistics/scene.vert
@@ -0,0 +1,36 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec3 inNormal;
+layout (location = 2) in vec3 inColor;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 modelview;
+	vec4 lightPos;
+} ubo;
+
+layout (location = 0) out vec3 outNormal;
+layout (location = 1) out vec3 outColor;
+layout (location = 2) out vec3 outViewVec;
+layout (location = 3) out vec3 outLightVec;
+
+layout(push_constant) uniform PushConsts {
+	vec3 objPos;
+} pushConsts;
+
+void main() 
+{
+	outNormal = inNormal;
+	outColor = inColor;
+
+	vec3 locPos = vec3(ubo.modelview * vec4(inPos, 1.0));
+	vec3 worldPos = vec3(ubo.modelview * vec4(inPos + pushConsts.objPos, 1.0));
+	gl_Position =  ubo.projection /* ubo.modelview */ * vec4(worldPos, 1.0);
+	
+	vec4 pos = ubo.modelview * vec4(worldPos, 1.0);
+	outNormal = mat3(ubo.modelview) * inNormal;
+	outLightVec = ubo.lightPos.xyz - pos.xyz;
+	outViewVec = -pos.xyz;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/pipelinestatistics/scene.vert.spv b/external/Vulkan/data/shaders/glsl/pipelinestatistics/scene.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..0e282926565e11a4a36887426780f990fa70e9ba
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/pipelinestatistics/scene.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/pushconstants/pushconstants.frag b/external/Vulkan/data/shaders/glsl/pushconstants/pushconstants.frag
new file mode 100644
index 0000000000000000000000000000000000000000..e1646b682353ccfed4a290ab5faa7dfe0498154d
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/pushconstants/pushconstants.frag
@@ -0,0 +1,10 @@
+#version 450
+
+layout (location = 0) in vec3 inColor;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{		
+	outFragColor.rgb = inColor;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/pushconstants/pushconstants.frag.spv b/external/Vulkan/data/shaders/glsl/pushconstants/pushconstants.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..70fab31df5400b3e90671ddcd0743838d654f6cf
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/pushconstants/pushconstants.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/pushconstants/pushconstants.vert b/external/Vulkan/data/shaders/glsl/pushconstants/pushconstants.vert
new file mode 100644
index 0000000000000000000000000000000000000000..2e2d52d8cab26eb8f42422a9ba26c64bb08976e6
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/pushconstants/pushconstants.vert
@@ -0,0 +1,29 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec3 inNormal;
+layout (location = 2) in vec3 inColor;
+
+#define lightCount 6
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 model;
+	mat4 view;
+} ubo;
+
+layout(push_constant) uniform PushConsts {
+	vec4 color;
+	vec4 position;
+} pushConsts;
+
+layout (location = 0) out vec3 outColor;
+
+void main() 
+{
+	outColor = inColor * pushConsts.color.rgb;	
+	vec3 locPos = vec3(ubo.model * vec4(inPos, 1.0));
+	vec3 worldPos = locPos + pushConsts.position.xyz;
+	gl_Position =  ubo.projection * ubo.view * vec4(worldPos, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/pushconstants/pushconstants.vert.spv b/external/Vulkan/data/shaders/glsl/pushconstants/pushconstants.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..8e30557690ee97cc7207e6a860ba24cf3dca69cf
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/pushconstants/pushconstants.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/pushdescriptors/cube.frag b/external/Vulkan/data/shaders/glsl/pushdescriptors/cube.frag
new file mode 100644
index 0000000000000000000000000000000000000000..21a9a4586603294d51f9a5992a7498cbeddf814e
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/pushdescriptors/cube.frag
@@ -0,0 +1,14 @@
+#version 450
+
+layout (set = 0, binding = 2) uniform sampler2D samplerColorMap;
+
+layout (location = 0) in vec3 inNormal;
+layout (location = 1) in vec3 inColor;
+layout (location = 2) in vec2 inUV;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+	outFragColor = texture(samplerColorMap, inUV) * vec4(inColor, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/pushdescriptors/cube.frag.spv b/external/Vulkan/data/shaders/glsl/pushdescriptors/cube.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..c98e777ae4a7f29620590bbcb22ee7bae3fb835c
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/pushdescriptors/cube.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/pushdescriptors/cube.vert b/external/Vulkan/data/shaders/glsl/pushdescriptors/cube.vert
new file mode 100644
index 0000000000000000000000000000000000000000..07d1350280cdfdc53c9d35e30f333b687a8d6131
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/pushdescriptors/cube.vert
@@ -0,0 +1,31 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec3 inNormal;
+layout (location = 2) in vec2 inUV;
+layout (location = 3) in vec3 inColor;
+
+layout (set = 0, binding = 0) uniform UBOScene {
+	mat4 projection;
+	mat4 view;
+} uboCamera;
+
+layout (set = 0, binding = 1) uniform UBOModel {
+	mat4 local;
+} uboModel;
+
+layout (location = 0) out vec3 outNormal;
+layout (location = 1) out vec3 outColor;
+layout (location = 2) out vec2 outUV;
+
+out gl_PerVertex {
+	vec4 gl_Position;
+};
+
+void main() 
+{
+	outNormal = inNormal;
+	outColor = inColor;
+	outUV = inUV;
+	gl_Position = uboCamera.projection * uboCamera.view * uboModel.local * vec4(inPos.xyz, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/pushdescriptors/cube.vert.spv b/external/Vulkan/data/shaders/glsl/pushdescriptors/cube.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..9b5b8b2cb34313a40985427beb0ee581fea7b57f
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/pushdescriptors/cube.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/radialblur/colorpass.frag b/external/Vulkan/data/shaders/glsl/radialblur/colorpass.frag
new file mode 100644
index 0000000000000000000000000000000000000000..440722c650b764882c6aa6384320df66ed79abc0
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/radialblur/colorpass.frag
@@ -0,0 +1,21 @@
+#version 450
+
+layout (binding = 1) uniform sampler2D samplerGradientRamp;
+
+layout (location = 0) in vec3 inColor;
+layout (location = 1) in vec2 inUV;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+	// Use max. color channel value to detect bright glow emitters
+	if ((inColor.r >= 0.9) || (inColor.g >= 0.9) || (inColor.b >= 0.9)) 
+	{
+		outFragColor.rgb = texture(samplerGradientRamp, inUV).rgb;
+	}
+	else
+	{
+		outFragColor.rgb = inColor;
+	}
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/radialblur/colorpass.frag.spv b/external/Vulkan/data/shaders/glsl/radialblur/colorpass.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..07a97ca34a982d26821b41eeb5ff3dc39dcff21a
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/radialblur/colorpass.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/radialblur/colorpass.vert b/external/Vulkan/data/shaders/glsl/radialblur/colorpass.vert
new file mode 100644
index 0000000000000000000000000000000000000000..69a126fd97c04834d8f6f1d2672fe28fb54366e7
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/radialblur/colorpass.vert
@@ -0,0 +1,26 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 2) in vec3 inColor;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 model;
+	float gradientPos;
+} ubo;
+
+layout (location = 0) out vec3 outColor;
+layout (location = 1) out vec2 outUV;
+
+out gl_PerVertex
+{
+	vec4 gl_Position;
+};
+
+void main() 
+{
+	outColor = inColor;
+	outUV = vec2(ubo.gradientPos, 0.0f);
+	gl_Position = ubo.projection * ubo.model * vec4(inPos, 1.0);
+}
diff --git a/external/Vulkan/data/shaders/glsl/radialblur/colorpass.vert.spv b/external/Vulkan/data/shaders/glsl/radialblur/colorpass.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..5635051a3d39cbb28702e7110492eb23521845b9
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/radialblur/colorpass.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/radialblur/phongpass.frag b/external/Vulkan/data/shaders/glsl/radialblur/phongpass.frag
new file mode 100644
index 0000000000000000000000000000000000000000..719a6272eeea4c23c1433ca952cc56e5fee39e45
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/radialblur/phongpass.frag
@@ -0,0 +1,33 @@
+#version 450
+
+layout (binding = 1) uniform sampler2D samplerGradientRamp;
+
+layout (location = 0) in vec3 inNormal;
+layout (location = 1) in vec3 inColor;
+layout (location = 2) in vec3 inEyePos;
+layout (location = 3) in vec3 inLightVec;
+layout (location = 4) in vec2 inUV;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+	// No light calculations for glow color 
+	// Use max. color channel value
+	// to detect bright glow emitters
+	if ((inColor.r >= 0.9) || (inColor.g >= 0.9) || (inColor.b >= 0.9)) 
+	{
+		outFragColor.rgb = texture(samplerGradientRamp, inUV).rgb;
+	}
+	else
+	{
+		vec3 Eye = normalize(-inEyePos);
+		vec3 Reflected = normalize(reflect(-inLightVec, inNormal)); 
+
+		vec4 IAmbient = vec4(0.2, 0.2, 0.2, 1.0);
+		vec4 IDiffuse = vec4(0.5, 0.5, 0.5, 0.5) * max(dot(inNormal, inLightVec), 0.0);
+		float specular = 0.25;
+		vec4 ISpecular = vec4(0.5, 0.5, 0.5, 1.0) * pow(max(dot(Reflected, Eye), 0.0), 4.0) * specular; 
+		outFragColor = vec4((IAmbient + IDiffuse) * vec4(inColor, 1.0) + ISpecular); 
+	}
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/radialblur/phongpass.frag.spv b/external/Vulkan/data/shaders/glsl/radialblur/phongpass.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..33b35eb9ae04304372e6d33870bb2429a60435a8
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/radialblur/phongpass.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/radialblur/phongpass.vert b/external/Vulkan/data/shaders/glsl/radialblur/phongpass.vert
new file mode 100644
index 0000000000000000000000000000000000000000..65233fc4e2467eb460421e8fe8ca65290073531c
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/radialblur/phongpass.vert
@@ -0,0 +1,34 @@
+#version 450
+
+layout (location = 0) in vec4 pos;
+layout (location = 2) in vec3 inColor;
+layout (location = 3) in vec3 inNormal;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 model;
+	float gradientPos;
+} ubo;
+
+layout (location = 0) out vec3 outNormal;
+layout (location = 1) out vec3 outColor;
+layout (location = 2) out vec3 outEyePos;
+layout (location = 3) out vec3 outLightVec;
+layout (location = 4) out vec2 outUV;
+
+out gl_PerVertex
+{
+	vec4 gl_Position;
+};
+
+void main() 
+{
+	outNormal = inNormal;
+	outColor = inColor;
+	outUV = vec2(ubo.gradientPos, 0.0); 
+	gl_Position = ubo.projection * ubo.model * pos;
+	outEyePos = vec3(ubo.model * pos);
+	vec4 lightPos = vec4(0.0, 0.0, -5.0, 1.0);// * ubo.model;
+	outLightVec = normalize(lightPos.xyz - pos.xyz);
+}
diff --git a/external/Vulkan/data/shaders/glsl/radialblur/phongpass.vert.spv b/external/Vulkan/data/shaders/glsl/radialblur/phongpass.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..f265539d28a5d216dbd161495307436492d6ad94
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/radialblur/phongpass.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/radialblur/radialblur.frag b/external/Vulkan/data/shaders/glsl/radialblur/radialblur.frag
new file mode 100644
index 0000000000000000000000000000000000000000..59e7b6666fa26e33cfd8f38c8d88b58001ae9a6b
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/radialblur/radialblur.frag
@@ -0,0 +1,35 @@
+#version 450
+
+layout (binding = 1) uniform sampler2D samplerColor;
+
+layout (binding = 0) uniform UBO 
+{
+	float radialBlurScale;
+	float radialBlurStrength;
+	vec2 radialOrigin;
+} ubo;
+
+layout (location = 0) in vec2 inUV;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+	ivec2 texDim = textureSize(samplerColor, 0);
+	vec2 radialSize = vec2(1.0 / texDim.s, 1.0 / texDim.t); 
+	
+	vec2 UV = inUV;
+ 
+	vec4 color = vec4(0.0, 0.0, 0.0, 0.0);
+	UV += radialSize * 0.5 - ubo.radialOrigin;
+ 
+	#define samples 32
+
+	for (int i = 0; i < samples; i++) 
+	{
+		float scale = 1.0 - ubo.radialBlurScale * (float(i) / float(samples-1));
+		color += texture(samplerColor, UV * scale + ubo.radialOrigin);
+	}
+ 
+	outFragColor = (color / samples) * ubo.radialBlurStrength;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/radialblur/radialblur.frag.spv b/external/Vulkan/data/shaders/glsl/radialblur/radialblur.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..521a3334ff0f3df8aee01a7dadff2231fba733a0
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/radialblur/radialblur.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/radialblur/radialblur.vert b/external/Vulkan/data/shaders/glsl/radialblur/radialblur.vert
new file mode 100644
index 0000000000000000000000000000000000000000..a5d60d0e750f260d3c034db033e15f18fb6a89d8
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/radialblur/radialblur.vert
@@ -0,0 +1,14 @@
+#version 450
+
+layout (location = 0) out vec2 outUV;
+
+out gl_PerVertex
+{
+	vec4 gl_Position;
+};
+
+void main() 
+{
+	outUV = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
+	gl_Position = vec4(outUV * 2.0f - 1.0f, 0.0f, 1.0f);
+}
diff --git a/external/Vulkan/data/shaders/glsl/radialblur/radialblur.vert.spv b/external/Vulkan/data/shaders/glsl/radialblur/radialblur.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..4040f608bd837727d1051a6539a15338d11242ae
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/radialblur/radialblur.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/rayquery/scene.frag b/external/Vulkan/data/shaders/glsl/rayquery/scene.frag
new file mode 100644
index 0000000000000000000000000000000000000000..edbd361729733fff25aa49b64f180e45b89a15eb
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/rayquery/scene.frag
@@ -0,0 +1,37 @@
+#version 460
+#extension GL_EXT_ray_tracing : enable
+#extension GL_EXT_ray_query : enable
+
+layout (binding = 2, set = 0) uniform accelerationStructureEXT topLevelAS;
+
+layout (location = 0) in vec3 inNormal;
+layout (location = 1) in vec3 inColor;
+layout (location = 2) in vec3 inViewVec;
+layout (location = 3) in vec3 inLightVec;
+layout (location = 4) in vec3 inWorldPos;
+
+layout (location = 0) out vec4 outFragColor;
+
+#define ambient 0.1
+
+void main() 
+{	
+	vec3 N = normalize(inNormal);
+	vec3 L = normalize(inLightVec);
+	vec3 V = normalize(inViewVec);
+	vec3 R = normalize(-reflect(L, N));
+	vec3 diffuse = max(dot(N, L), ambient) * inColor;
+
+	outFragColor = vec4(diffuse, 1.0);
+
+	rayQueryEXT rayQuery;
+	rayQueryInitializeEXT(rayQuery, topLevelAS, gl_RayFlagsTerminateOnFirstHitEXT, 0xFF, inWorldPos, 0.01, L, 1000.0);
+
+	// Start the ray traversal, rayQueryProceedEXT returns false if the traversal is complete
+	while (rayQueryProceedEXT(rayQuery)) { }
+
+	// If the intersection has hit a triangle, the fragment is shadowed
+	if (rayQueryGetIntersectionTypeEXT(rayQuery, true) == gl_RayQueryCommittedIntersectionTriangleEXT ) {
+		outFragColor *= 0.1;
+	}
+}
diff --git a/external/Vulkan/data/shaders/glsl/rayquery/scene.frag.spv b/external/Vulkan/data/shaders/glsl/rayquery/scene.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..feae30abae7352d4913e29decc26c3900cbd68ad
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/rayquery/scene.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/rayquery/scene.vert b/external/Vulkan/data/shaders/glsl/rayquery/scene.vert
new file mode 100644
index 0000000000000000000000000000000000000000..0956741260b85105baf1040073d4bc64e9768345
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/rayquery/scene.vert
@@ -0,0 +1,33 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec2 inUV;
+layout (location = 2) in vec3 inColor;
+layout (location = 3) in vec3 inNormal;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 view;
+	mat4 model;
+	vec3 lightPos;
+} ubo;
+
+layout (location = 0) out vec3 outNormal;
+layout (location = 1) out vec3 outColor;
+layout (location = 2) out vec3 outViewVec;
+layout (location = 3) out vec3 outLightVec;
+layout (location = 4) out vec3 outWorldPos;
+
+void main() 
+{
+	outColor = inColor;
+	outNormal = inNormal;
+	gl_Position = ubo.projection * ubo.view * ubo.model * vec4(inPos.xyz, 1.0);
+    vec4 pos = ubo.model * vec4(inPos, 1.0);
+	outWorldPos = vec3(ubo.model * vec4(inPos, 1.0));
+    outNormal = mat3(ubo.model) * inNormal;
+    outLightVec = normalize(ubo.lightPos - inPos);
+    outViewVec = -pos.xyz;
+}
+
diff --git a/external/Vulkan/data/shaders/glsl/rayquery/scene.vert.spv b/external/Vulkan/data/shaders/glsl/rayquery/scene.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..7d9008a8a2a412368805640f7938171ec46e2c62
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/rayquery/scene.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/raytracingbasic/closesthit.rchit b/external/Vulkan/data/shaders/glsl/raytracingbasic/closesthit.rchit
new file mode 100644
index 0000000000000000000000000000000000000000..44936f2057bb296da122319a174b73e4833ec6b0
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/raytracingbasic/closesthit.rchit
@@ -0,0 +1,12 @@
+#version 460
+#extension GL_EXT_ray_tracing : enable
+#extension GL_EXT_nonuniform_qualifier : enable
+
+layout(location = 0) rayPayloadInEXT vec3 hitValue;
+hitAttributeEXT vec2 attribs;
+
+void main()
+{
+  const vec3 barycentricCoords = vec3(1.0f - attribs.x - attribs.y, attribs.x, attribs.y);
+  hitValue = barycentricCoords;
+}
diff --git a/external/Vulkan/data/shaders/glsl/raytracingbasic/closesthit.rchit.spv b/external/Vulkan/data/shaders/glsl/raytracingbasic/closesthit.rchit.spv
new file mode 100644
index 0000000000000000000000000000000000000000..dd8cfc08d62d064b11d3dbfc1aed1084d1c2c06c
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/raytracingbasic/closesthit.rchit.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/raytracingbasic/miss.rmiss b/external/Vulkan/data/shaders/glsl/raytracingbasic/miss.rmiss
new file mode 100644
index 0000000000000000000000000000000000000000..25633fbe43f1e7af755643e94bc9505473803083
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/raytracingbasic/miss.rmiss
@@ -0,0 +1,9 @@
+#version 460
+#extension GL_EXT_ray_tracing : enable
+
+layout(location = 0) rayPayloadInEXT vec3 hitValue;
+
+void main()
+{
+    hitValue = vec3(0.0, 0.0, 0.2);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/raytracingbasic/miss.rmiss.spv b/external/Vulkan/data/shaders/glsl/raytracingbasic/miss.rmiss.spv
new file mode 100644
index 0000000000000000000000000000000000000000..58fb33099899d9bab90e64681a54c5a46199168a
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/raytracingbasic/miss.rmiss.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/raytracingbasic/raygen.rgen b/external/Vulkan/data/shaders/glsl/raytracingbasic/raygen.rgen
new file mode 100644
index 0000000000000000000000000000000000000000..7f803f3541966502cd964b3edb91510352df96ee
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/raytracingbasic/raygen.rgen
@@ -0,0 +1,32 @@
+#version 460
+#extension GL_EXT_ray_tracing : enable
+
+layout(binding = 0, set = 0) uniform accelerationStructureEXT topLevelAS;
+layout(binding = 1, set = 0, rgba8) uniform image2D image;
+layout(binding = 2, set = 0) uniform CameraProperties 
+{
+	mat4 viewInverse;
+	mat4 projInverse;
+} cam;
+
+layout(location = 0) rayPayloadEXT vec3 hitValue;
+
+void main() 
+{
+	const vec2 pixelCenter = vec2(gl_LaunchIDEXT.xy) + vec2(0.5);
+	const vec2 inUV = pixelCenter/vec2(gl_LaunchSizeEXT.xy);
+	vec2 d = inUV * 2.0 - 1.0;
+
+	vec4 origin = cam.viewInverse * vec4(0,0,0,1);
+	vec4 target = cam.projInverse * vec4(d.x, d.y, 1, 1) ;
+	vec4 direction = cam.viewInverse*vec4(normalize(target.xyz), 0) ;
+
+	float tmin = 0.001;
+	float tmax = 10000.0;
+
+    hitValue = vec3(0.0);
+
+    traceRayEXT(topLevelAS, gl_RayFlagsOpaqueEXT, 0xff, 0, 0, 0, origin.xyz, tmin, direction.xyz, tmax, 0);
+
+	imageStore(image, ivec2(gl_LaunchIDEXT.xy), vec4(hitValue, 0.0));
+}
diff --git a/external/Vulkan/data/shaders/glsl/raytracingbasic/raygen.rgen.spv b/external/Vulkan/data/shaders/glsl/raytracingbasic/raygen.rgen.spv
new file mode 100644
index 0000000000000000000000000000000000000000..ce9413a9ff2c238fbe723a44dba8e224d2f56a66
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/raytracingbasic/raygen.rgen.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/raytracingcallable/callable1.rcall b/external/Vulkan/data/shaders/glsl/raytracingcallable/callable1.rcall
new file mode 100644
index 0000000000000000000000000000000000000000..fc50a5ea736fb8f0c411b2b1a08c7159d7b892d7
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/raytracingcallable/callable1.rcall
@@ -0,0 +1,11 @@
+#version 460 core
+#extension GL_EXT_ray_tracing : enable
+
+layout(location = 0) callableDataInEXT vec3 outColor;
+
+void main()
+{
+    // Generate a checker board pattern
+	vec2 pos = vec2(gl_LaunchIDEXT / 8);
+	outColor = vec3(mod(pos.x + mod(pos.y, 2.0), 2.0));
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/raytracingcallable/callable1.rcall.spv b/external/Vulkan/data/shaders/glsl/raytracingcallable/callable1.rcall.spv
new file mode 100644
index 0000000000000000000000000000000000000000..f022a1110ba4e2ac73d013abb97d43598d802dbd
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/raytracingcallable/callable1.rcall.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/raytracingcallable/callable2.rcall b/external/Vulkan/data/shaders/glsl/raytracingcallable/callable2.rcall
new file mode 100644
index 0000000000000000000000000000000000000000..525aab0f7ea297b6c9034bd56fbf5efdc1340b51
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/raytracingcallable/callable2.rcall
@@ -0,0 +1,9 @@
+#version 460 core
+#extension GL_EXT_ray_tracing : enable
+
+layout(location = 0) callableDataInEXT vec3 outColor;
+
+void main()
+{
+    outColor = vec3(0.0, 1.0, 0.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/raytracingcallable/callable2.rcall.spv b/external/Vulkan/data/shaders/glsl/raytracingcallable/callable2.rcall.spv
new file mode 100644
index 0000000000000000000000000000000000000000..da3165a852050b78f81a99f6e261f828bc0480d8
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/raytracingcallable/callable2.rcall.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/raytracingcallable/callable3.rcall b/external/Vulkan/data/shaders/glsl/raytracingcallable/callable3.rcall
new file mode 100644
index 0000000000000000000000000000000000000000..1cc3a70137369bd102dcece8cdf0121b9ebf7894
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/raytracingcallable/callable3.rcall
@@ -0,0 +1,11 @@
+#version 460 core
+#extension GL_EXT_ray_tracing : enable
+
+layout(location = 0) callableDataInEXT vec3 outColor;
+
+void main()
+{
+    // Generate a line pattern
+	vec2 pos = vec2(gl_LaunchIDEXT / 8);
+	outColor = vec3(mod(pos.y, 2.0));
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/raytracingcallable/callable3.rcall.spv b/external/Vulkan/data/shaders/glsl/raytracingcallable/callable3.rcall.spv
new file mode 100644
index 0000000000000000000000000000000000000000..8c3a09331478968d92e9bc1c2aec1f07be611561
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/raytracingcallable/callable3.rcall.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/raytracingcallable/closesthit.rchit b/external/Vulkan/data/shaders/glsl/raytracingcallable/closesthit.rchit
new file mode 100644
index 0000000000000000000000000000000000000000..210a2d5958ee4dc8968a77168cbb46500a11c7e9
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/raytracingcallable/closesthit.rchit
@@ -0,0 +1,17 @@
+#version 460
+#extension GL_EXT_ray_tracing : require
+#extension GL_EXT_nonuniform_qualifier : enable
+
+layout(location = 0) rayPayloadInEXT vec3 hitValue;
+layout(location = 0) callableDataEXT vec3 outColor;
+
+layout(binding = 0, set = 0) uniform accelerationStructureEXT topLevelAS;
+
+void main()
+{
+	// Execute the callable shader indexed by the current geometry being hit
+	// For our sample this means that the first callable shader in the SBT is invoked for the first triangle, the second callable shader for the second triangle, etc.
+	executeCallableEXT(gl_GeometryIndexEXT, 0);
+
+	hitValue = outColor;
+}
diff --git a/external/Vulkan/data/shaders/glsl/raytracingcallable/closesthit.rchit.spv b/external/Vulkan/data/shaders/glsl/raytracingcallable/closesthit.rchit.spv
new file mode 100644
index 0000000000000000000000000000000000000000..c56de05a7cf72a68dfd2aaf8f9a268e5c5dab33f
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/raytracingcallable/closesthit.rchit.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/raytracingcallable/miss.rmiss b/external/Vulkan/data/shaders/glsl/raytracingcallable/miss.rmiss
new file mode 100644
index 0000000000000000000000000000000000000000..577f7e8e7684fd8a6174f661c8fcae75ab1145dd
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/raytracingcallable/miss.rmiss
@@ -0,0 +1,9 @@
+#version 460
+#extension GL_EXT_ray_tracing : require
+
+layout(location = 0) rayPayloadInEXT vec3 hitValue;
+
+void main()
+{
+    hitValue = vec3(0.0, 0.0, 0.2);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/raytracingcallable/miss.rmiss.spv b/external/Vulkan/data/shaders/glsl/raytracingcallable/miss.rmiss.spv
new file mode 100644
index 0000000000000000000000000000000000000000..58fb33099899d9bab90e64681a54c5a46199168a
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/raytracingcallable/miss.rmiss.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/raytracingcallable/raygen.rgen b/external/Vulkan/data/shaders/glsl/raytracingcallable/raygen.rgen
new file mode 100644
index 0000000000000000000000000000000000000000..c8552e8f186b73abaf6e68bab8cb75b09705f3cd
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/raytracingcallable/raygen.rgen
@@ -0,0 +1,32 @@
+#version 460
+#extension GL_EXT_ray_tracing : require
+
+layout(binding = 0, set = 0) uniform accelerationStructureEXT topLevelAS;
+layout(binding = 1, set = 0, rgba8) uniform image2D image;
+layout(binding = 2, set = 0) uniform CameraProperties 
+{
+	mat4 viewInverse;
+	mat4 projInverse;
+} cam;
+
+layout(location = 0) rayPayloadEXT vec3 hitValue;
+
+void main() 
+{
+	const vec2 pixelCenter = vec2(gl_LaunchIDEXT.xy) + vec2(0.5);
+	const vec2 inUV = pixelCenter/vec2(gl_LaunchSizeEXT.xy);
+	vec2 d = inUV * 2.0 - 1.0;
+
+	vec4 origin = cam.viewInverse * vec4(0,0,0,1);
+	vec4 target = cam.projInverse * vec4(d.x, d.y, 1, 1) ;
+	vec4 direction = cam.viewInverse*vec4(normalize(target.xyz / target.w), 0) ;
+
+	uint rayFlags = gl_RayFlagsOpaqueEXT;
+	uint cullMask = 0xff;
+	float tmin = 0.001;
+	float tmax = 10000.0;
+
+	traceRayEXT(topLevelAS, rayFlags, cullMask, 0, 0, 0, origin.xyz, tmin, direction.xyz, tmax, 0);
+
+	imageStore(image, ivec2(gl_LaunchIDEXT.xy), vec4(hitValue, 0.0));
+}
diff --git a/external/Vulkan/data/shaders/glsl/raytracingcallable/raygen.rgen.spv b/external/Vulkan/data/shaders/glsl/raytracingcallable/raygen.rgen.spv
new file mode 100644
index 0000000000000000000000000000000000000000..0f7af95bd53b17c7efa0f971dc84d66b77a3bcbe
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/raytracingcallable/raygen.rgen.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/raytracingreflections/closesthit.rchit b/external/Vulkan/data/shaders/glsl/raytracingreflections/closesthit.rchit
new file mode 100644
index 0000000000000000000000000000000000000000..301686c7365a554f0bdb2f2a72d5875d6e365c41
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/raytracingreflections/closesthit.rchit
@@ -0,0 +1,76 @@
+#version 460
+#extension GL_EXT_ray_tracing : require
+#extension GL_EXT_nonuniform_qualifier : enable
+
+struct RayPayload {
+	vec3 color;
+	float distance;
+	vec3 normal;
+	float reflector;
+};
+
+layout(location = 0) rayPayloadInEXT RayPayload rayPayload;
+
+hitAttributeEXT vec2 attribs;
+
+layout(binding = 0, set = 0) uniform accelerationStructureEXT topLevelAS;
+layout(binding = 2, set = 0) uniform UBO 
+{
+	mat4 viewInverse;
+	mat4 projInverse;
+	vec4 lightPos;
+	int vertexSize;
+} ubo;
+layout(binding = 3, set = 0) buffer Vertices { vec4 v[]; } vertices;
+layout(binding = 4, set = 0) buffer Indices { uint i[]; } indices;
+
+struct Vertex
+{
+  vec3 pos;
+  vec3 normal;
+  vec2 uv;
+  vec4 color;
+  vec4 _pad0; 
+  vec4 _pad1;
+};
+
+Vertex unpack(uint index)
+{
+	// Unpack the vertices from the SSBO using the glTF vertex structure
+	// The multiplier is the size of the vertex divided by four float components (=16 bytes)
+	const int m = ubo.vertexSize / 16;
+
+	vec4 d0 = vertices.v[m * index + 0];
+	vec4 d1 = vertices.v[m * index + 1];
+	vec4 d2 = vertices.v[m * index + 2];
+
+	Vertex v;
+	v.pos = d0.xyz;
+	v.normal = vec3(d0.w, d1.x, d1.y);
+	v.color = vec4(d2.x, d2.y, d2.z, 1.0);
+
+	return v;
+}
+
+void main()
+{
+	ivec3 index = ivec3(indices.i[3 * gl_PrimitiveID], indices.i[3 * gl_PrimitiveID + 1], indices.i[3 * gl_PrimitiveID + 2]);
+
+	Vertex v0 = unpack(index.x);
+	Vertex v1 = unpack(index.y);
+	Vertex v2 = unpack(index.z);
+
+	// Interpolate normal
+	const vec3 barycentricCoords = vec3(1.0f - attribs.x - attribs.y, attribs.x, attribs.y);
+	vec3 normal = normalize(v0.normal * barycentricCoords.x + v1.normal * barycentricCoords.y + v2.normal * barycentricCoords.z);
+
+	// Basic lighting
+	vec3 lightVector = normalize(ubo.lightPos.xyz);
+	float dot_product = max(dot(lightVector, normal), 0.6);
+	rayPayload.color = v0.color.rgb * vec3(dot_product);
+	rayPayload.distance = gl_RayTmaxEXT;
+	rayPayload.normal = normal;
+
+	// Objects with full white vertex color are treated as reflectors
+	rayPayload.reflector = ((v0.color.r == 1.0f) && (v0.color.g == 1.0f) && (v0.color.b == 1.0f)) ? 1.0f : 0.0f; 
+}
diff --git a/external/Vulkan/data/shaders/glsl/raytracingreflections/closesthit.rchit.spv b/external/Vulkan/data/shaders/glsl/raytracingreflections/closesthit.rchit.spv
new file mode 100644
index 0000000000000000000000000000000000000000..148540f303a7250069ebf7d0c461eaf7d194438b
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/raytracingreflections/closesthit.rchit.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/raytracingreflections/miss.rmiss b/external/Vulkan/data/shaders/glsl/raytracingreflections/miss.rmiss
new file mode 100644
index 0000000000000000000000000000000000000000..f2da7971fc33f7b1a26ce1314f36a8667e851186
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/raytracingreflections/miss.rmiss
@@ -0,0 +1,25 @@
+#version 460
+#extension GL_EXT_ray_tracing : require
+
+struct RayPayload {
+	vec3 color;
+	float distance;
+	vec3 normal;
+	float reflector;
+};
+
+layout(location = 0) rayPayloadInEXT RayPayload rayPayload;
+
+void main()
+{
+	// View-independent background gradient to simulate a basic sky background
+	const vec3 gradientStart = vec3(0.5, 0.6, 1.0);
+	const vec3 gradientEnd = vec3(1.0);
+	vec3 unitDir = normalize(gl_WorldRayDirectionEXT);
+	float t = 0.5 * (unitDir.y + 1.0);
+	rayPayload.color = (1.0-t) * gradientStart + t * gradientEnd;
+
+	rayPayload.distance = -1.0f;
+	rayPayload.normal = vec3(0.0f);
+	rayPayload.reflector = 0.0f;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/raytracingreflections/miss.rmiss.spv b/external/Vulkan/data/shaders/glsl/raytracingreflections/miss.rmiss.spv
new file mode 100644
index 0000000000000000000000000000000000000000..c168cc39f20efc196db8c7b91fd7b5701bd55925
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/raytracingreflections/miss.rmiss.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/raytracingreflections/raygen.rgen b/external/Vulkan/data/shaders/glsl/raytracingreflections/raygen.rgen
new file mode 100644
index 0000000000000000000000000000000000000000..b1e6b63dae70a47ded2f78e47aff13150ec246fa
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/raytracingreflections/raygen.rgen
@@ -0,0 +1,62 @@
+#version 460
+#extension GL_EXT_ray_tracing : require
+
+layout(binding = 0, set = 0) uniform accelerationStructureEXT topLevelAS;
+layout(binding = 1, set = 0, rgba8) uniform image2D image;
+layout(binding = 2, set = 0) uniform CameraProperties 
+{
+	mat4 viewInverse;
+	mat4 projInverse;
+	vec4 lightPos;
+} cam;
+
+
+struct RayPayload {
+	vec3 color;
+	float distance;
+	vec3 normal;
+	float reflector;
+};
+
+layout(location = 0) rayPayloadEXT RayPayload rayPayload;
+
+// Max. number of recursion is passed via a specialization constant
+layout (constant_id = 0) const int MAX_RECURSION = 0;
+
+void main() 
+{
+	const vec2 pixelCenter = vec2(gl_LaunchIDEXT.xy) + vec2(0.5);
+	const vec2 inUV = pixelCenter/vec2(gl_LaunchSizeEXT.xy);
+	vec2 d = inUV * 2.0 - 1.0;
+
+	vec4 origin = cam.viewInverse * vec4(0,0,0,1);
+	vec4 target = cam.projInverse * vec4(d.x, d.y, 1, 1) ;
+	vec4 direction = cam.viewInverse*vec4(normalize(target.xyz / target.w), 0);
+
+	uint rayFlags = gl_RayFlagsOpaqueEXT;
+	uint cullMask = 0xff;
+	float tmin = 0.001;
+	float tmax = 10000.0;
+
+	vec3 color = vec3(0.0);
+
+	for (int i = 0; i < MAX_RECURSION; i++) {
+		traceRayEXT(topLevelAS, rayFlags, cullMask, 0, 0, 0, origin.xyz, tmin, direction.xyz, tmax, 0);
+		vec3 hitColor = rayPayload.color;
+
+		if (rayPayload.distance < 0.0f) {
+			color += hitColor;
+			break;
+		} else if (rayPayload.reflector == 1.0f) {
+			const vec4 hitPos = origin + direction * rayPayload.distance;
+			origin.xyz = hitPos.xyz + rayPayload.normal * 0.001f;
+			direction.xyz = reflect(direction.xyz, rayPayload.normal);
+		} else {
+			color += hitColor;
+			break;
+		}
+
+	}
+
+	imageStore(image, ivec2(gl_LaunchIDEXT.xy), vec4(color, 0.0));
+}
diff --git a/external/Vulkan/data/shaders/glsl/raytracingreflections/raygen.rgen.spv b/external/Vulkan/data/shaders/glsl/raytracingreflections/raygen.rgen.spv
new file mode 100644
index 0000000000000000000000000000000000000000..7e2b2d3c5fcc9c53fb17ffc65a8083af62101154
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/raytracingreflections/raygen.rgen.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/raytracingshadows/closesthit.rchit b/external/Vulkan/data/shaders/glsl/raytracingshadows/closesthit.rchit
new file mode 100644
index 0000000000000000000000000000000000000000..25079bdf89e2ec2f4f428a6803a04172967509eb
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/raytracingshadows/closesthit.rchit
@@ -0,0 +1,75 @@
+#version 460
+#extension GL_EXT_ray_tracing : require
+#extension GL_EXT_nonuniform_qualifier : enable
+
+layout(location = 0) rayPayloadInEXT vec3 hitValue;
+layout(location = 2) rayPayloadEXT bool shadowed;
+hitAttributeEXT vec2 attribs;
+
+layout(binding = 0, set = 0) uniform accelerationStructureEXT topLevelAS;
+layout(binding = 2, set = 0) uniform UBO 
+{
+	mat4 viewInverse;
+	mat4 projInverse;
+	vec4 lightPos;
+	int vertexSize;
+} ubo;
+layout(binding = 3, set = 0) buffer Vertices { vec4 v[]; } vertices;
+layout(binding = 4, set = 0) buffer Indices { uint i[]; } indices;
+
+struct Vertex
+{
+  vec3 pos;
+  vec3 normal;
+  vec2 uv;
+  vec4 color;
+  vec4 _pad0;
+  vec4 _pad1;
+ };
+
+Vertex unpack(uint index)
+{
+	// Unpack the vertices from the SSBO using the glTF vertex structure
+	// The multiplier is the size of the vertex divided by four float components (=16 bytes)
+	const int m = ubo.vertexSize / 16;
+
+	vec4 d0 = vertices.v[m * index + 0];
+	vec4 d1 = vertices.v[m * index + 1];
+	vec4 d2 = vertices.v[m * index + 2];
+
+	Vertex v;
+	v.pos = d0.xyz;
+	v.normal = vec3(d0.w, d1.x, d1.y);
+	v.color = vec4(d2.x, d2.y, d2.z, 1.0);
+
+	return v;
+}
+
+void main()
+{
+	ivec3 index = ivec3(indices.i[3 * gl_PrimitiveID], indices.i[3 * gl_PrimitiveID + 1], indices.i[3 * gl_PrimitiveID + 2]);
+
+	Vertex v0 = unpack(index.x);
+	Vertex v1 = unpack(index.y);
+	Vertex v2 = unpack(index.z);
+
+	// Interpolate normal
+	const vec3 barycentricCoords = vec3(1.0f - attribs.x - attribs.y, attribs.x, attribs.y);
+	vec3 normal = normalize(v0.normal * barycentricCoords.x + v1.normal * barycentricCoords.y + v2.normal * barycentricCoords.z);
+
+	// Basic lighting
+	vec3 lightVector = normalize(ubo.lightPos.xyz);
+	float dot_product = max(dot(lightVector, normal), 0.2);
+	hitValue = v0.color.rgb * dot_product;
+ 
+	// Shadow casting
+	float tmin = 0.001;
+	float tmax = 10000.0;
+	vec3 origin = gl_WorldRayOriginEXT + gl_WorldRayDirectionEXT * gl_HitTEXT;
+	shadowed = true;  
+	// Trace shadow ray and offset indices to match shadow hit/miss shader group indices
+	traceRayEXT(topLevelAS, gl_RayFlagsTerminateOnFirstHitEXT | gl_RayFlagsOpaqueEXT | gl_RayFlagsSkipClosestHitShaderEXT, 0xFF, 1, 0, 1, origin, tmin, lightVector, tmax, 2);
+	if (shadowed) {
+		hitValue *= 0.3;
+	}
+}
diff --git a/external/Vulkan/data/shaders/glsl/raytracingshadows/closesthit.rchit.spv b/external/Vulkan/data/shaders/glsl/raytracingshadows/closesthit.rchit.spv
new file mode 100644
index 0000000000000000000000000000000000000000..486571c5d0f9613a198f91e214c32b7ecbf86cab
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/raytracingshadows/closesthit.rchit.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/raytracingshadows/miss.rmiss b/external/Vulkan/data/shaders/glsl/raytracingshadows/miss.rmiss
new file mode 100644
index 0000000000000000000000000000000000000000..577f7e8e7684fd8a6174f661c8fcae75ab1145dd
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/raytracingshadows/miss.rmiss
@@ -0,0 +1,9 @@
+#version 460
+#extension GL_EXT_ray_tracing : require
+
+layout(location = 0) rayPayloadInEXT vec3 hitValue;
+
+void main()
+{
+    hitValue = vec3(0.0, 0.0, 0.2);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/raytracingshadows/miss.rmiss.spv b/external/Vulkan/data/shaders/glsl/raytracingshadows/miss.rmiss.spv
new file mode 100644
index 0000000000000000000000000000000000000000..58fb33099899d9bab90e64681a54c5a46199168a
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/raytracingshadows/miss.rmiss.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/raytracingshadows/raygen.rgen b/external/Vulkan/data/shaders/glsl/raytracingshadows/raygen.rgen
new file mode 100644
index 0000000000000000000000000000000000000000..88b5f3fe9ef7823e9e88c376a287aad9422b1935
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/raytracingshadows/raygen.rgen
@@ -0,0 +1,33 @@
+#version 460
+#extension GL_EXT_ray_tracing : require
+
+layout(binding = 0, set = 0) uniform accelerationStructureEXT topLevelAS;
+layout(binding = 1, set = 0, rgba8) uniform image2D image;
+layout(binding = 2, set = 0) uniform CameraProperties 
+{
+	mat4 viewInverse;
+	mat4 projInverse;
+	vec4 lightPos;
+} cam;
+
+layout(location = 0) rayPayloadEXT vec3 hitValue;
+
+void main() 
+{
+	const vec2 pixelCenter = vec2(gl_LaunchIDEXT.xy) + vec2(0.5);
+	const vec2 inUV = pixelCenter/vec2(gl_LaunchSizeEXT.xy);
+	vec2 d = inUV * 2.0 - 1.0;
+
+	vec4 origin = cam.viewInverse * vec4(0,0,0,1);
+	vec4 target = cam.projInverse * vec4(d.x, d.y, 1, 1) ;
+	vec4 direction = cam.viewInverse*vec4(normalize(target.xyz / target.w), 0) ;
+
+	uint rayFlags = gl_RayFlagsOpaqueEXT;
+	uint cullMask = 0xff;
+	float tmin = 0.001;
+	float tmax = 10000.0;
+
+	traceRayEXT(topLevelAS, rayFlags, cullMask, 0, 0, 0, origin.xyz, tmin, direction.xyz, tmax, 0);
+
+	imageStore(image, ivec2(gl_LaunchIDEXT.xy), vec4(hitValue, 0.0));
+}
diff --git a/external/Vulkan/data/shaders/glsl/raytracingshadows/raygen.rgen.spv b/external/Vulkan/data/shaders/glsl/raytracingshadows/raygen.rgen.spv
new file mode 100644
index 0000000000000000000000000000000000000000..30ab96d6dfe0f1e0b9222e7e43222c8cc56aad96
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/raytracingshadows/raygen.rgen.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/raytracingshadows/shadow.rmiss b/external/Vulkan/data/shaders/glsl/raytracingshadows/shadow.rmiss
new file mode 100644
index 0000000000000000000000000000000000000000..36d9b7ba823e0b79e2b53d2d93185ae46e0c15a0
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/raytracingshadows/shadow.rmiss
@@ -0,0 +1,9 @@
+#version 460
+#extension GL_EXT_ray_tracing : require
+
+layout(location = 2) rayPayloadInEXT bool shadowed;
+
+void main()
+{
+	shadowed = false;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/raytracingshadows/shadow.rmiss.spv b/external/Vulkan/data/shaders/glsl/raytracingshadows/shadow.rmiss.spv
new file mode 100644
index 0000000000000000000000000000000000000000..65b01ba9610f1ccd0371be438bf842bb68f9e3d6
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/raytracingshadows/shadow.rmiss.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/renderheadless/triangle.frag b/external/Vulkan/data/shaders/glsl/renderheadless/triangle.frag
new file mode 100644
index 0000000000000000000000000000000000000000..f3e513b909da2e2c1ce1d64e8f4c39c79172696a
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/renderheadless/triangle.frag
@@ -0,0 +1,10 @@
+#version 450
+
+layout (location = 0) in vec3 inColor;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+  outFragColor = vec4(inColor, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/renderheadless/triangle.frag.spv b/external/Vulkan/data/shaders/glsl/renderheadless/triangle.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..f67f201135c138f22f9bfb7bd9b0a8e1c9227e92
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/renderheadless/triangle.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/renderheadless/triangle.vert b/external/Vulkan/data/shaders/glsl/renderheadless/triangle.vert
new file mode 100644
index 0000000000000000000000000000000000000000..b89716f4e08a685f5fef9966aef41a02a23930d3
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/renderheadless/triangle.vert
@@ -0,0 +1,20 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec3 inColor;
+
+layout (location = 0) out vec3 outColor;
+
+out gl_PerVertex {
+	vec4 gl_Position;   
+};
+
+layout(push_constant) uniform PushConsts {
+	mat4 mvp;
+} pushConsts;
+
+void main() 
+{
+	outColor = inColor;
+	gl_Position = pushConsts.mvp * vec4(inPos.xyz, 1.0);
+}
diff --git a/external/Vulkan/data/shaders/glsl/renderheadless/triangle.vert.spv b/external/Vulkan/data/shaders/glsl/renderheadless/triangle.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..94c1bb68cffae615c8c6032c0bea4eb663fe12c3
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/renderheadless/triangle.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/screenshot/mesh.frag b/external/Vulkan/data/shaders/glsl/screenshot/mesh.frag
new file mode 100644
index 0000000000000000000000000000000000000000..aab33e1d5f35578c5aeae246fd75d4df5810ff0a
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/screenshot/mesh.frag
@@ -0,0 +1,20 @@
+#version 450
+
+layout (location = 0) in vec3 inNormal;
+layout (location = 1) in vec3 inColor;
+layout (location = 2) in vec3 inViewVec;
+layout (location = 3) in vec3 inLightVec;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+	vec3 N = normalize(inNormal);
+	vec3 L = normalize(inLightVec);
+	vec3 V = normalize(inViewVec);
+	vec3 R = reflect(-L, N);
+	vec3 ambient = vec3(0.1);
+	vec3 diffuse = max(dot(N, L), 0.0) * vec3(1.0);
+	vec3 specular = pow(max(dot(R, V), 0.0), 16.0) * vec3(0.75);
+	outFragColor = vec4((ambient + diffuse) * inColor.rgb + specular, 1.0);		
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/screenshot/mesh.frag.spv b/external/Vulkan/data/shaders/glsl/screenshot/mesh.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..33607eb75d5f3c2f287f4f7af3842d156a3aba54
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/screenshot/mesh.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/screenshot/mesh.vert b/external/Vulkan/data/shaders/glsl/screenshot/mesh.vert
new file mode 100644
index 0000000000000000000000000000000000000000..91cbd6fbc441412d37e4e1d8dbddcab4ff1ffeed
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/screenshot/mesh.vert
@@ -0,0 +1,36 @@
+#version 450
+
+layout (location = 0) in vec4 inPos;
+layout (location = 1) in vec3 inNormal;
+layout (location = 2) in vec3 inColor;
+
+layout (set = 0, binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 view;
+	mat4 model;
+} ubo;
+
+layout (location = 0) out vec3 outNormal;
+layout (location = 1) out vec3 outColor;
+layout (location = 2) out vec3 outViewVec;
+layout (location = 3) out vec3 outLightVec;
+
+out gl_PerVertex
+{
+	vec4 gl_Position;
+};
+
+void main() 
+{
+	outNormal = inNormal;
+	outColor = inColor;
+	gl_Position = ubo.projection * ubo.view * ubo.model * inPos;
+
+	vec4 pos = ubo.view * ubo.model * vec4(inPos.xyz, 1.0);
+	outNormal = mat3(ubo.model) * inNormal;
+
+	vec3 lightPos = vec3(1.0f, -1.0f, 1.0f);
+	outLightVec = lightPos.xyz - pos.xyz;
+	outViewVec = -pos.xyz;		
+}
diff --git a/external/Vulkan/data/shaders/glsl/screenshot/mesh.vert.spv b/external/Vulkan/data/shaders/glsl/screenshot/mesh.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..1674220820c714925942af88a0962d0e09f2267b
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/screenshot/mesh.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/shadowmapping/offscreen.frag b/external/Vulkan/data/shaders/glsl/shadowmapping/offscreen.frag
new file mode 100644
index 0000000000000000000000000000000000000000..38d211d76cf9bb9e50399d3eaac2b8d4d227a362
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/shadowmapping/offscreen.frag
@@ -0,0 +1,8 @@
+#version 450
+
+layout(location = 0) out vec4 color;
+
+void main() 
+{	
+	color = vec4(1.0, 0.0, 0.0, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/shadowmapping/offscreen.frag.spv b/external/Vulkan/data/shaders/glsl/shadowmapping/offscreen.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..4d3440f86e1825933e1ea26445c40f8c186990c1
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/shadowmapping/offscreen.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/shadowmapping/offscreen.vert b/external/Vulkan/data/shaders/glsl/shadowmapping/offscreen.vert
new file mode 100644
index 0000000000000000000000000000000000000000..b20c695b7d0f50a10d3255c35b836e351fa8373f
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/shadowmapping/offscreen.vert
@@ -0,0 +1,19 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 depthMVP;
+} ubo;
+
+out gl_PerVertex 
+{
+    vec4 gl_Position;   
+};
+
+ 
+void main()
+{
+	gl_Position =  ubo.depthMVP * vec4(inPos, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/shadowmapping/offscreen.vert.spv b/external/Vulkan/data/shaders/glsl/shadowmapping/offscreen.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..e5c7931d4c640eb30a3813e857256e40ae870bd1
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/shadowmapping/offscreen.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/shadowmapping/quad.frag b/external/Vulkan/data/shaders/glsl/shadowmapping/quad.frag
new file mode 100644
index 0000000000000000000000000000000000000000..afd8389fcffa603de5ba193c8f1abfa3e3c00c03
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/shadowmapping/quad.frag
@@ -0,0 +1,21 @@
+#version 450
+
+layout (binding = 1) uniform sampler2D samplerColor;
+
+layout (location = 0) in vec2 inUV;
+
+layout (location = 0) out vec4 outFragColor;
+
+float LinearizeDepth(float depth)
+{
+  float n = 1.0; // camera z near
+  float f = 128.0; // camera z far
+  float z = depth;
+  return (2.0 * n) / (f + n - z * (f - n));	
+}
+
+void main() 
+{
+	float depth = texture(samplerColor, inUV).r;
+	outFragColor = vec4(vec3(1.0-LinearizeDepth(depth)), 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/shadowmapping/quad.frag.spv b/external/Vulkan/data/shaders/glsl/shadowmapping/quad.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..13c24deef5178a8c4a459614e83ed1c1941e83d6
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/shadowmapping/quad.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/shadowmapping/quad.vert b/external/Vulkan/data/shaders/glsl/shadowmapping/quad.vert
new file mode 100644
index 0000000000000000000000000000000000000000..3a42f3c54083149098d3dcb9fcc22eb529727b15
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/shadowmapping/quad.vert
@@ -0,0 +1,9 @@
+#version 450
+
+layout (location = 0) out vec2 outUV;
+
+void main() 
+{
+	outUV = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
+	gl_Position = vec4(outUV * 2.0f - 1.0f, 0.0f, 1.0f);
+}
diff --git a/external/Vulkan/data/shaders/glsl/shadowmapping/quad.vert.spv b/external/Vulkan/data/shaders/glsl/shadowmapping/quad.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..5683fcb316e0ff06e54a6f73a0c33ca38f7e937c
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/shadowmapping/quad.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/shadowmapping/scene.frag b/external/Vulkan/data/shaders/glsl/shadowmapping/scene.frag
new file mode 100644
index 0000000000000000000000000000000000000000..a8e67f56fa34512c6b00e793a52396f69f5e9711
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/shadowmapping/scene.frag
@@ -0,0 +1,66 @@
+#version 450
+
+layout (binding = 1) uniform sampler2D shadowMap;
+
+layout (location = 0) in vec3 inNormal;
+layout (location = 1) in vec3 inColor;
+layout (location = 2) in vec3 inViewVec;
+layout (location = 3) in vec3 inLightVec;
+layout (location = 4) in vec4 inShadowCoord;
+
+layout (constant_id = 0) const int enablePCF = 0;
+
+layout (location = 0) out vec4 outFragColor;
+
+#define ambient 0.1
+
+float textureProj(vec4 shadowCoord, vec2 off)
+{
+	float shadow = 1.0;
+	if ( shadowCoord.z > -1.0 && shadowCoord.z < 1.0 ) 
+	{
+		float dist = texture( shadowMap, shadowCoord.st + off ).r;
+		if ( shadowCoord.w > 0.0 && dist < shadowCoord.z ) 
+		{
+			shadow = ambient;
+		}
+	}
+	return shadow;
+}
+
+float filterPCF(vec4 sc)
+{
+	ivec2 texDim = textureSize(shadowMap, 0);
+	float scale = 1.5;
+	float dx = scale * 1.0 / float(texDim.x);
+	float dy = scale * 1.0 / float(texDim.y);
+
+	float shadowFactor = 0.0;
+	int count = 0;
+	int range = 1;
+	
+	for (int x = -range; x <= range; x++)
+	{
+		for (int y = -range; y <= range; y++)
+		{
+			shadowFactor += textureProj(sc, vec2(dx*x, dy*y));
+			count++;
+		}
+	
+	}
+	return shadowFactor / count;
+}
+
+void main() 
+{	
+	float shadow = (enablePCF == 1) ? filterPCF(inShadowCoord / inShadowCoord.w) : textureProj(inShadowCoord / inShadowCoord.w, vec2(0.0));
+
+	vec3 N = normalize(inNormal);
+	vec3 L = normalize(inLightVec);
+	vec3 V = normalize(inViewVec);
+	vec3 R = normalize(-reflect(L, N));
+	vec3 diffuse = max(dot(N, L), ambient) * inColor;
+
+	outFragColor = vec4(diffuse * shadow, 1.0);
+
+}
diff --git a/external/Vulkan/data/shaders/glsl/shadowmapping/scene.frag.spv b/external/Vulkan/data/shaders/glsl/shadowmapping/scene.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..3f308890d0cc84689dd11c67e8d8e1fdb158b32e
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/shadowmapping/scene.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/shadowmapping/scene.vert b/external/Vulkan/data/shaders/glsl/shadowmapping/scene.vert
new file mode 100644
index 0000000000000000000000000000000000000000..a9531b867bf0b8475ce449799acfe48acbc8983c
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/shadowmapping/scene.vert
@@ -0,0 +1,48 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec2 inUV;
+layout (location = 2) in vec3 inColor;
+layout (location = 3) in vec3 inNormal;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 view;
+	mat4 model;
+	mat4 lightSpace;
+	vec3 lightPos;
+} ubo;
+
+layout (location = 0) out vec3 outNormal;
+layout (location = 1) out vec3 outColor;
+layout (location = 2) out vec3 outViewVec;
+layout (location = 3) out vec3 outLightVec;
+layout (location = 4) out vec4 outShadowCoord;
+
+out gl_PerVertex 
+{
+    vec4 gl_Position;   
+};
+
+const mat4 biasMat = mat4( 
+	0.5, 0.0, 0.0, 0.0,
+	0.0, 0.5, 0.0, 0.0,
+	0.0, 0.0, 1.0, 0.0,
+	0.5, 0.5, 0.0, 1.0 );
+
+void main() 
+{
+	outColor = inColor;
+	outNormal = inNormal;
+
+	gl_Position = ubo.projection * ubo.view * ubo.model * vec4(inPos.xyz, 1.0);
+	
+    vec4 pos = ubo.model * vec4(inPos, 1.0);
+    outNormal = mat3(ubo.model) * inNormal;
+    outLightVec = normalize(ubo.lightPos - inPos);
+    outViewVec = -pos.xyz;			
+
+	outShadowCoord = ( biasMat * ubo.lightSpace * ubo.model ) * vec4(inPos, 1.0);	
+}
+
diff --git a/external/Vulkan/data/shaders/glsl/shadowmapping/scene.vert.spv b/external/Vulkan/data/shaders/glsl/shadowmapping/scene.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..b9b214072da85980d6a74fa888b41f9f7f7cd7f6
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/shadowmapping/scene.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/shadowmappingcascade/debugshadowmap.frag b/external/Vulkan/data/shaders/glsl/shadowmappingcascade/debugshadowmap.frag
new file mode 100644
index 0000000000000000000000000000000000000000..37bcad80909abbf0964a66b96306ef1b5c943239
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/shadowmappingcascade/debugshadowmap.frag
@@ -0,0 +1,14 @@
+#version 450
+
+layout (binding = 1) uniform sampler2DArray shadowMap;
+
+layout (location = 0) in vec2 inUV;
+layout (location = 1) flat in uint inCascadeIndex;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+	float depth = texture(shadowMap, vec3(inUV, float(inCascadeIndex))).r;
+	outFragColor = vec4(vec3((depth)), 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/shadowmappingcascade/debugshadowmap.frag.spv b/external/Vulkan/data/shaders/glsl/shadowmappingcascade/debugshadowmap.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..caa073abe89f3cf331bf5971f2698271eaee9c62
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/shadowmappingcascade/debugshadowmap.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/shadowmappingcascade/debugshadowmap.vert b/external/Vulkan/data/shaders/glsl/shadowmappingcascade/debugshadowmap.vert
new file mode 100644
index 0000000000000000000000000000000000000000..8db331f2b9e5b13d567ec5a2c30862d860cf66b7
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/shadowmappingcascade/debugshadowmap.vert
@@ -0,0 +1,20 @@
+#version 450
+
+layout(push_constant) uniform PushConsts {
+	vec4 position;
+	uint cascadeIndex;
+} pushConsts;
+
+layout (location = 0) out vec2 outUV;
+layout (location = 1) out uint outCascadeIndex;
+
+out gl_PerVertex {
+	vec4 gl_Position;   
+};
+
+void main() 
+{
+	outUV = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
+	outCascadeIndex = pushConsts.cascadeIndex;
+	gl_Position = vec4(outUV * 2.0f - 1.0f, 0.0f, 1.0f);
+}
diff --git a/external/Vulkan/data/shaders/glsl/shadowmappingcascade/debugshadowmap.vert.spv b/external/Vulkan/data/shaders/glsl/shadowmappingcascade/debugshadowmap.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..d9dad4eb6df11fb680657d56cc844bf50ca7c1c3
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/shadowmappingcascade/debugshadowmap.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/shadowmappingcascade/depthpass.frag b/external/Vulkan/data/shaders/glsl/shadowmappingcascade/depthpass.frag
new file mode 100644
index 0000000000000000000000000000000000000000..2e2393631b7df9b64471ae42309eb511f4a8b81e
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/shadowmappingcascade/depthpass.frag
@@ -0,0 +1,13 @@
+#version 450
+
+layout (set = 1, binding = 0) uniform sampler2D colorMap;
+
+layout (location = 0) in vec2 inUV;
+
+void main() 
+{	
+	float alpha = texture(colorMap, inUV).a;
+	if (alpha < 0.5) {
+		discard;
+	}
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/shadowmappingcascade/depthpass.frag.spv b/external/Vulkan/data/shaders/glsl/shadowmappingcascade/depthpass.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..8863bb9f4a09d302dd66a1b444aec856a9f39521
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/shadowmappingcascade/depthpass.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/shadowmappingcascade/depthpass.vert b/external/Vulkan/data/shaders/glsl/shadowmappingcascade/depthpass.vert
new file mode 100644
index 0000000000000000000000000000000000000000..0abfbda0ae6ec139a6ab41a72cb8e8c9c0a75788
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/shadowmappingcascade/depthpass.vert
@@ -0,0 +1,29 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec2 inUV;
+
+// todo: pass via specialization constant
+#define SHADOW_MAP_CASCADE_COUNT 4
+
+layout(push_constant) uniform PushConsts {
+	vec4 position;
+	uint cascadeIndex;
+} pushConsts;
+
+layout (binding = 0) uniform UBO {
+	mat4[SHADOW_MAP_CASCADE_COUNT] cascadeViewProjMat;
+} ubo;
+
+layout (location = 0) out vec2 outUV;
+
+out gl_PerVertex {
+	vec4 gl_Position;   
+};
+
+void main()
+{
+	outUV = inUV;
+	vec3 pos = inPos + pushConsts.position.xyz;
+	gl_Position =  ubo.cascadeViewProjMat[pushConsts.cascadeIndex] * vec4(pos, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/shadowmappingcascade/depthpass.vert.spv b/external/Vulkan/data/shaders/glsl/shadowmappingcascade/depthpass.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..87255aea5e6524ebe424ab9e63b89b74771d5f9b
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/shadowmappingcascade/depthpass.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/shadowmappingcascade/scene.frag b/external/Vulkan/data/shaders/glsl/shadowmappingcascade/scene.frag
new file mode 100644
index 0000000000000000000000000000000000000000..73c3721ca35cc7babbd0507ed07d4c1ecb99186a
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/shadowmappingcascade/scene.frag
@@ -0,0 +1,123 @@
+#version 450
+
+#define SHADOW_MAP_CASCADE_COUNT 4
+
+layout (set = 0, binding = 1) uniform sampler2DArray shadowMap;
+layout (set = 1, binding = 0) uniform sampler2D colorMap;
+
+layout (location = 0) in vec3 inNormal;
+layout (location = 1) in vec3 inColor;
+layout (location = 2) in vec3 inViewPos;
+layout (location = 3) in vec3 inPos;
+layout (location = 4) in vec2 inUV;
+
+layout (constant_id = 0) const int enablePCF = 0;
+
+layout (location = 0) out vec4 outFragColor;
+
+#define ambient 0.3
+
+layout (set = 0, binding = 2) uniform UBO {
+	vec4 cascadeSplits;
+	mat4 cascadeViewProjMat[SHADOW_MAP_CASCADE_COUNT];
+	mat4 inverseViewMat;
+	vec3 lightDir;
+	float _pad;
+	int colorCascades;
+} ubo;
+
+const mat4 biasMat = mat4( 
+	0.5, 0.0, 0.0, 0.0,
+	0.0, 0.5, 0.0, 0.0,
+	0.0, 0.0, 1.0, 0.0,
+	0.5, 0.5, 0.0, 1.0 
+);
+
+float textureProj(vec4 shadowCoord, vec2 offset, uint cascadeIndex)
+{
+	float shadow = 1.0;
+	float bias = 0.005;
+
+	if ( shadowCoord.z > -1.0 && shadowCoord.z < 1.0 ) {
+		float dist = texture(shadowMap, vec3(shadowCoord.st + offset, cascadeIndex)).r;
+		if (shadowCoord.w > 0 && dist < shadowCoord.z - bias) {
+			shadow = ambient;
+		}
+	}
+	return shadow;
+
+}
+
+float filterPCF(vec4 sc, uint cascadeIndex)
+{
+	ivec2 texDim = textureSize(shadowMap, 0).xy;
+	float scale = 0.75;
+	float dx = scale * 1.0 / float(texDim.x);
+	float dy = scale * 1.0 / float(texDim.y);
+
+	float shadowFactor = 0.0;
+	int count = 0;
+	int range = 1;
+	
+	for (int x = -range; x <= range; x++) {
+		for (int y = -range; y <= range; y++) {
+			shadowFactor += textureProj(sc, vec2(dx*x, dy*y), cascadeIndex);
+			count++;
+		}
+	}
+	return shadowFactor / count;
+}
+
+void main() 
+{	
+	vec4 color = texture(colorMap, inUV);
+	if (color.a < 0.5) {
+		discard;
+	}
+
+	// Get cascade index for the current fragment's view position
+	uint cascadeIndex = 0;
+	for(uint i = 0; i < SHADOW_MAP_CASCADE_COUNT - 1; ++i) {
+		if(inViewPos.z < ubo.cascadeSplits[i]) {	
+			cascadeIndex = i + 1;
+		}
+	}
+
+	// Depth compare for shadowing
+	vec4 shadowCoord = (biasMat * ubo.cascadeViewProjMat[cascadeIndex]) * vec4(inPos, 1.0);	
+
+	float shadow = 0;
+	if (enablePCF == 1) {
+		shadow = filterPCF(shadowCoord / shadowCoord.w, cascadeIndex);
+	} else {
+		shadow = textureProj(shadowCoord / shadowCoord.w, vec2(0.0), cascadeIndex);
+	}
+
+	// Directional light
+	vec3 N = normalize(inNormal);
+	vec3 L = normalize(-ubo.lightDir);
+	vec3 H = normalize(L + inViewPos);
+	float diffuse = max(dot(N, L), ambient);
+	vec3 lightColor = vec3(1.0);
+	outFragColor.rgb = max(lightColor * (diffuse * color.rgb), vec3(0.0));
+	outFragColor.rgb *= shadow;
+	outFragColor.a = color.a;
+
+	// Color cascades (if enabled)
+	if (ubo.colorCascades == 1) {
+		switch(cascadeIndex) {
+			case 0 : 
+				outFragColor.rgb *= vec3(1.0f, 0.25f, 0.25f);
+				break;
+			case 1 : 
+				outFragColor.rgb *= vec3(0.25f, 1.0f, 0.25f);
+				break;
+			case 2 : 
+				outFragColor.rgb *= vec3(0.25f, 0.25f, 1.0f);
+				break;
+			case 3 : 
+				outFragColor.rgb *= vec3(1.0f, 1.0f, 0.25f);
+				break;
+		}
+	}
+}
diff --git a/external/Vulkan/data/shaders/glsl/shadowmappingcascade/scene.frag.spv b/external/Vulkan/data/shaders/glsl/shadowmappingcascade/scene.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..13cb1ce193100f3c282d8791ba62a2b5e05b5153
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/shadowmappingcascade/scene.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/shadowmappingcascade/scene.vert b/external/Vulkan/data/shaders/glsl/shadowmappingcascade/scene.vert
new file mode 100644
index 0000000000000000000000000000000000000000..4ef0b3a9b1be9f4aeb97dbaa21d709480257a549
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/shadowmappingcascade/scene.vert
@@ -0,0 +1,39 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec2 inUV;
+layout (location = 2) in vec3 inColor;
+layout (location = 3) in vec3 inNormal;
+
+layout (binding = 0) uniform UBO {
+	mat4 projection;
+	mat4 view;
+	mat4 model;
+} ubo;
+
+layout (location = 0) out vec3 outNormal;
+layout (location = 1) out vec3 outColor;
+layout (location = 2) out vec3 outViewPos;
+layout (location = 3) out vec3 outPos;
+layout (location = 4) out vec2 outUV;
+
+layout(push_constant) uniform PushConsts {
+	vec4 position;
+	uint cascadeIndex;
+} pushConsts;
+
+out gl_PerVertex {
+	vec4 gl_Position;   
+};
+
+void main() 
+{
+	outColor = inColor;
+	outNormal = inNormal;
+	outUV = inUV;
+	vec3 pos = inPos + pushConsts.position.xyz;
+	outPos = pos;
+	outViewPos = (ubo.view * vec4(pos.xyz, 1.0)).xyz;
+	gl_Position = ubo.projection * ubo.view * ubo.model * vec4(pos.xyz, 1.0);
+}
+
diff --git a/external/Vulkan/data/shaders/glsl/shadowmappingcascade/scene.vert.spv b/external/Vulkan/data/shaders/glsl/shadowmappingcascade/scene.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..9f5c7a6de8f93bde39427d4eaecaf2563e3bdcb5
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/shadowmappingcascade/scene.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/shadowmappingomni/cubemapdisplay.frag b/external/Vulkan/data/shaders/glsl/shadowmappingomni/cubemapdisplay.frag
new file mode 100644
index 0000000000000000000000000000000000000000..b9dfc367828981dad439439e4c843433fba9939b
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/shadowmappingomni/cubemapdisplay.frag
@@ -0,0 +1,54 @@
+#version 450
+
+layout (binding = 1) uniform samplerCube shadowCubeMap;
+
+layout (location = 0) in vec2 inUV;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+	outFragColor.rgb = vec3(0.05);
+	
+	vec3 samplePos = vec3(0.0f);
+	
+	// Crude statement to visualize different cube map faces based on UV coordinates
+	int x = int(floor(inUV.x / 0.25f));
+	int y = int(floor(inUV.y / (1.0 / 3.0))); 
+	if (y == 1) {
+		vec2 uv = vec2(inUV.x * 4.0f, (inUV.y - 1.0/3.0) * 3.0);
+		uv = 2.0 * vec2(uv.x - float(x) * 1.0, uv.y) - 1.0;
+		switch (x) {
+			case 0:	// NEGATIVE_X
+				samplePos = vec3(-1.0f, uv.y, uv.x);
+				break;
+			case 1: // POSITIVE_Z				
+				samplePos = vec3(uv.x, uv.y, 1.0f);
+				break;
+			case 2: // POSITIVE_X
+				samplePos = vec3(1.0, uv.y, -uv.x);
+				break;				
+			case 3: // NEGATIVE_Z
+				samplePos = vec3(-uv.x, uv.y, -1.0f);
+				break;
+		}
+	} else {
+		if (x == 1) { 
+			vec2 uv = vec2((inUV.x - 0.25) * 4.0, (inUV.y - float(y) / 3.0) * 3.0);
+			uv = 2.0 * uv - 1.0;
+			switch (y) {
+				case 0: // NEGATIVE_Y
+					samplePos = vec3(uv.x, -1.0f, uv.y);
+					break;
+				case 2: // POSITIVE_Y
+					samplePos = vec3(uv.x, 1.0f, -uv.y);
+					break;
+			}
+		}
+	}
+
+	if ((samplePos.x != 0.0f) && (samplePos.y != 0.0f)) {
+		float dist = length(texture(shadowCubeMap, samplePos).xyz) * 0.005;
+		outFragColor = vec4(vec3(dist), 1.0);
+	}
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/shadowmappingomni/cubemapdisplay.frag.spv b/external/Vulkan/data/shaders/glsl/shadowmappingomni/cubemapdisplay.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..bb72c916c29e6f0bc2d5ee68ceb4f3682b6ae973
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/shadowmappingomni/cubemapdisplay.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/shadowmappingomni/cubemapdisplay.vert b/external/Vulkan/data/shaders/glsl/shadowmappingomni/cubemapdisplay.vert
new file mode 100644
index 0000000000000000000000000000000000000000..1c39776d274a543969ff6ea51fb83c8b68ebbda2
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/shadowmappingomni/cubemapdisplay.vert
@@ -0,0 +1,17 @@
+#version 450
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 view;
+	mat4 model;
+} ubo;
+
+layout (location = 0) out vec2 outUV;
+
+void main() 
+{
+	outUV = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
+	gl_Position = vec4(outUV.xy * 2.0f - 1.0f, 0.0f, 1.0f);
+}
+
diff --git a/external/Vulkan/data/shaders/glsl/shadowmappingomni/cubemapdisplay.vert.spv b/external/Vulkan/data/shaders/glsl/shadowmappingomni/cubemapdisplay.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..6d7ed10059664b56453beaafd3fe397740f0e9a6
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/shadowmappingomni/cubemapdisplay.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/shadowmappingomni/offscreen.frag b/external/Vulkan/data/shaders/glsl/shadowmappingomni/offscreen.frag
new file mode 100644
index 0000000000000000000000000000000000000000..cccc4f085ff69c3169ba963199affd8db3a81248
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/shadowmappingomni/offscreen.frag
@@ -0,0 +1,13 @@
+#version 450
+
+layout (location = 0) out float outFragColor;
+
+layout (location = 0) in vec4 inPos;
+layout (location = 1) in vec3 inLightPos;
+
+void main() 
+{
+	// Store distance to light as 32 bit float value
+    vec3 lightVec = inPos.xyz - inLightPos;
+    outFragColor = length(lightVec);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/shadowmappingomni/offscreen.frag.spv b/external/Vulkan/data/shaders/glsl/shadowmappingomni/offscreen.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..56001294e35ee241b99890e952645d01345b3f69
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/shadowmappingomni/offscreen.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/shadowmappingomni/offscreen.vert b/external/Vulkan/data/shaders/glsl/shadowmappingomni/offscreen.vert
new file mode 100644
index 0000000000000000000000000000000000000000..e0c30d81f1798e83ac02d81de1bb098e085022d2
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/shadowmappingomni/offscreen.vert
@@ -0,0 +1,32 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+
+layout (location = 0) out vec4 outPos;
+layout (location = 1) out vec3 outLightPos;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 view; 
+	mat4 model;
+	vec4 lightPos;
+} ubo;
+
+layout(push_constant) uniform PushConsts 
+{
+	mat4 view;
+} pushConsts;
+ 
+out gl_PerVertex 
+{
+	vec4 gl_Position;
+};
+ 
+void main()
+{
+	gl_Position = ubo.projection * pushConsts.view * ubo.model * vec4(inPos, 1.0);
+
+	outPos = vec4(inPos, 1.0);	
+	outLightPos = ubo.lightPos.xyz; 
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/shadowmappingomni/offscreen.vert.spv b/external/Vulkan/data/shaders/glsl/shadowmappingomni/offscreen.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..6ec7eaf1429dfb843eb18d9b0c438cd6d068e0e3
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/shadowmappingomni/offscreen.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/shadowmappingomni/scene.frag b/external/Vulkan/data/shaders/glsl/shadowmappingomni/scene.frag
new file mode 100644
index 0000000000000000000000000000000000000000..88b9c8ecc24b6b5ac1789366c73ee96c2c66247d
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/shadowmappingomni/scene.frag
@@ -0,0 +1,40 @@
+#version 450
+
+layout (binding = 1) uniform samplerCube shadowCubeMap;
+
+layout (location = 0) in vec3 inNormal;
+layout (location = 1) in vec3 inColor;
+layout (location = 2) in vec3 inEyePos;
+layout (location = 3) in vec3 inLightVec;
+layout (location = 4) in vec3 inWorldPos;
+layout (location = 5) in vec3 inLightPos;
+
+layout (location = 0) out vec4 outFragColor;
+
+#define EPSILON 0.15
+#define SHADOW_OPACITY 0.5
+
+void main() 
+{
+	// Lighting
+	vec3 N = normalize(inNormal);
+	vec3 L = normalize(vec3(1.0));	
+	
+	vec3 Eye = normalize(-inEyePos);
+	vec3 Reflected = normalize(reflect(-inLightVec, inNormal)); 
+
+	vec4 IAmbient = vec4(vec3(0.05), 1.0);
+	vec4 IDiffuse = vec4(1.0) * max(dot(inNormal, inLightVec), 0.0);
+
+	outFragColor = vec4(IAmbient + IDiffuse * vec4(inColor, 1.0));		
+		
+	// Shadow
+	vec3 lightVec = inWorldPos - inLightPos;
+    float sampledDist = texture(shadowCubeMap, lightVec).r;
+    float dist = length(lightVec);
+
+	// Check if fragment is in shadow
+    float shadow = (dist <= sampledDist + EPSILON) ? 1.0 : SHADOW_OPACITY;
+
+	outFragColor.rgb *= shadow;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/shadowmappingomni/scene.frag.spv b/external/Vulkan/data/shaders/glsl/shadowmappingomni/scene.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..2043a2c22ba993df7b73eeec992cbfc6527a3495
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/shadowmappingomni/scene.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/shadowmappingomni/scene.vert b/external/Vulkan/data/shaders/glsl/shadowmappingomni/scene.vert
new file mode 100644
index 0000000000000000000000000000000000000000..aebcaa1110a99f38b702de914607dccb9a265202
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/shadowmappingomni/scene.vert
@@ -0,0 +1,39 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec3 inColor;
+layout (location = 2) in vec3 inNormal;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 view;
+	mat4 model;
+	vec4 lightPos;
+} ubo;
+
+layout (location = 0) out vec3 outNormal;
+layout (location = 1) out vec3 outColor;
+layout (location = 2) out vec3 outEyePos;
+layout (location = 3) out vec3 outLightVec;
+layout (location = 4) out vec3 outWorldPos;
+layout (location = 5) out vec3 outLightPos;
+
+out gl_PerVertex 
+{
+	vec4 gl_Position;
+};
+
+void main() 
+{
+	outColor = inColor;
+	outNormal = inNormal;
+	
+	gl_Position = ubo.projection * ubo.view * ubo.model * vec4(inPos.xyz, 1.0);
+	outEyePos = vec3(ubo.model * vec4(inPos, 1.0f));
+	outLightVec = normalize(ubo.lightPos.xyz - inPos.xyz);	
+	outWorldPos = inPos;
+	
+	outLightPos = ubo.lightPos.xyz;
+}
+
diff --git a/external/Vulkan/data/shaders/glsl/shadowmappingomni/scene.vert.spv b/external/Vulkan/data/shaders/glsl/shadowmappingomni/scene.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..add79db3f65200ad86914357d7ac075c88f9275d
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/shadowmappingomni/scene.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/specializationconstants/uber.frag b/external/Vulkan/data/shaders/glsl/specializationconstants/uber.frag
new file mode 100644
index 0000000000000000000000000000000000000000..fa1507083a96082ab5a327e19042cf6b46bc1116
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/specializationconstants/uber.frag
@@ -0,0 +1,71 @@
+#version 450
+
+layout (binding = 1) uniform sampler2D samplerColormap;
+layout (binding = 2) uniform sampler2D samplerDiscard;
+
+layout (location = 0) in vec3 inNormal;
+layout (location = 1) in vec3 inColor;
+layout (location = 2) in vec2 inUV;
+layout (location = 3) in vec3 inViewVec;
+layout (location = 4) in vec3 inLightVec;
+
+layout (location = 0) out vec4 outFragColor;
+
+// We use this constant to control the flow of the shader depending on the 
+// lighting model selected at pipeline creation time
+layout (constant_id = 0) const int LIGHTING_MODEL = 0;
+// Parameter for the toon shading part of the shader
+layout (constant_id = 1) const float PARAM_TOON_DESATURATION = 0.0f;
+
+void main() 
+{
+	switch (LIGHTING_MODEL) {
+		case 0: // Phong			
+		{
+			vec3 ambient = inColor * vec3(0.25);
+			vec3 N = normalize(inNormal);
+			vec3 L = normalize(inLightVec);
+			vec3 V = normalize(inViewVec);
+			vec3 R = reflect(-L, N);
+			vec3 diffuse = max(dot(N, L), 0.0) * inColor;
+			vec3 specular = pow(max(dot(R, V), 0.0), 32.0) * vec3(0.75);
+			outFragColor = vec4(ambient + diffuse * 1.75 + specular, 1.0);		
+			break;
+		}
+		case 1: // Toon
+		{
+
+			vec3 N = normalize(inNormal);
+			vec3 L = normalize(inLightVec);
+			float intensity = dot(N,L);
+			vec3 color;
+			if (intensity > 0.98)
+				color = inColor * 1.5;
+			else if  (intensity > 0.9)
+				color = inColor * 1.0;
+			else if (intensity > 0.5)
+				color = inColor * 0.6;
+			else if (intensity > 0.25)
+				color = inColor * 0.4;
+			else
+				color = inColor * 0.2;
+			// Desaturate a bit
+			color = vec3(mix(color, vec3(dot(vec3(0.2126,0.7152,0.0722), color)), PARAM_TOON_DESATURATION));	
+			outFragColor.rgb = color;
+			break;
+		}
+		case 2: // Textured
+		{
+			vec4 color = texture(samplerColormap, inUV).rrra;
+			vec3 ambient = color.rgb * vec3(0.25) * inColor;
+			vec3 N = normalize(inNormal);
+			vec3 L = normalize(inLightVec);
+			vec3 V = normalize(inViewVec);
+			vec3 R = reflect(-L, N);
+			vec3 diffuse = max(dot(N, L), 0.0) * color.rgb;
+			float specular = pow(max(dot(R, V), 0.0), 32.0) * color.a;
+			outFragColor = vec4(ambient + diffuse + vec3(specular), 1.0);		
+			break;
+		}
+	}
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/specializationconstants/uber.frag.spv b/external/Vulkan/data/shaders/glsl/specializationconstants/uber.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..d84677c7758305732db0340c70c9875d7cf8999b
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/specializationconstants/uber.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/specializationconstants/uber.vert b/external/Vulkan/data/shaders/glsl/specializationconstants/uber.vert
new file mode 100644
index 0000000000000000000000000000000000000000..d91395076115f9a327ff1d2dc748bdf95362697e
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/specializationconstants/uber.vert
@@ -0,0 +1,38 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec3 inNormal;
+layout (location = 2) in vec2 inUV;
+layout (location = 3) in vec3 inColor;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 model;
+	vec4 lightPos;
+} ubo;
+
+layout (location = 0) out vec3 outNormal;
+layout (location = 1) out vec3 outColor;
+layout (location = 2) out vec2 outUV;
+layout (location = 3) out vec3 outViewVec;
+layout (location = 4) out vec3 outLightVec;
+
+out gl_PerVertex
+{
+	vec4 gl_Position;
+};
+
+void main() 
+{
+	outNormal = inNormal;
+	outColor = inColor;
+	outUV = inUV;
+	gl_Position = ubo.projection * ubo.model * vec4(inPos.xyz, 1.0);
+	
+	vec4 pos = ubo.model * vec4(inPos, 1.0);
+	outNormal = mat3(ubo.model) * inNormal;
+	vec3 lPos = mat3(ubo.model) * ubo.lightPos.xyz;
+	outLightVec = lPos - pos.xyz;
+	outViewVec = -pos.xyz;		
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/specializationconstants/uber.vert.spv b/external/Vulkan/data/shaders/glsl/specializationconstants/uber.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..82758e686cfd5a35ead56c59b5827cba399c672f
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/specializationconstants/uber.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/sphericalenvmapping/sem.frag b/external/Vulkan/data/shaders/glsl/sphericalenvmapping/sem.frag
new file mode 100644
index 0000000000000000000000000000000000000000..b233bf3751f4bc821d9877c5c2ad351284dc7c7a
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/sphericalenvmapping/sem.frag
@@ -0,0 +1,19 @@
+#version 450
+
+layout (binding = 1) uniform sampler2DArray matCap;
+
+layout (location = 0) in vec3 inColor;
+layout (location = 1) in vec3 inEyePos;
+layout (location = 2) in vec3 inNormal;
+layout (location = 3) in flat int inTexIndex;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main()
+{
+	vec3 r = reflect( inEyePos, inNormal );
+	vec3 r2 = vec3( r.x, r.y, r.z + 1.0 );
+	float m = 2.0 * length( r2 );
+	vec2 vN = r.xy / m + .5;
+	outFragColor = vec4( texture( matCap, vec3(vN, inTexIndex)).rgb * (clamp(inColor.r * 2, 0.0, 1.0)), 1.0 );
+}
diff --git a/external/Vulkan/data/shaders/glsl/sphericalenvmapping/sem.frag.spv b/external/Vulkan/data/shaders/glsl/sphericalenvmapping/sem.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..3c721b2bc9741c7f7e302a77d12183635198cfec
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/sphericalenvmapping/sem.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/sphericalenvmapping/sem.vert b/external/Vulkan/data/shaders/glsl/sphericalenvmapping/sem.vert
new file mode 100644
index 0000000000000000000000000000000000000000..1de7a3d500fc5ea1080b97402733cd51d921602b
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/sphericalenvmapping/sem.vert
@@ -0,0 +1,31 @@
+#version 450
+
+layout (location = 0) in vec4 inPos;
+layout (location = 1) in vec3 inNormal;
+layout (location = 2) in vec3 inColor;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 model;
+	mat4 normal;
+	mat4 view;
+	int texIndex;
+} ubo;
+
+layout (location = 0) out vec3 outColor;
+layout (location = 1) out vec3 outEyePos;
+layout (location = 2) out vec3 outNormal;
+layout (location = 3) out flat int outTexIndex;
+
+void main() 
+{
+	outColor = inColor;
+	mat4 modelView = ubo.view * ubo.model;
+	outEyePos = normalize( vec3( modelView * inPos ) );
+	outTexIndex = ubo.texIndex;
+	outNormal = normalize( mat3(ubo.normal) * inNormal );
+	vec3 r = reflect( outEyePos, outNormal );
+	float m = 2.0 * sqrt( pow(r.x, 2.0) + pow(r.y, 2.0) + pow(r.z + 1.0, 2.0));
+	gl_Position = ubo.projection * modelView * inPos;	
+}
diff --git a/external/Vulkan/data/shaders/glsl/sphericalenvmapping/sem.vert.spv b/external/Vulkan/data/shaders/glsl/sphericalenvmapping/sem.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..da074648daabbb0000d8a2042549f317204cac59
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/sphericalenvmapping/sem.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/ssao/blur.frag b/external/Vulkan/data/shaders/glsl/ssao/blur.frag
new file mode 100644
index 0000000000000000000000000000000000000000..7385a766e1515c8db81fdea4f5c08ac6bc44a340
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/ssao/blur.frag
@@ -0,0 +1,25 @@
+#version 450
+
+layout (binding = 0) uniform sampler2D samplerSSAO;
+
+layout (location = 0) in vec2 inUV;
+
+layout (location = 0) out float outFragColor;
+
+void main() 
+{
+	const int blurRange = 2;
+	int n = 0;
+	vec2 texelSize = 1.0 / vec2(textureSize(samplerSSAO, 0));
+	float result = 0.0;
+	for (int x = -blurRange; x < blurRange; x++) 
+	{
+		for (int y = -blurRange; y < blurRange; y++) 
+		{
+			vec2 offset = vec2(float(x), float(y)) * texelSize;
+			result += texture(samplerSSAO, inUV + offset).r;
+			n++;
+		}
+	}
+	outFragColor = result / (float(n));
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/ssao/blur.frag.spv b/external/Vulkan/data/shaders/glsl/ssao/blur.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..723774358f0f199a2b50b3f28538ae46b796ff72
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/ssao/blur.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/ssao/composition.frag b/external/Vulkan/data/shaders/glsl/ssao/composition.frag
new file mode 100644
index 0000000000000000000000000000000000000000..b90ba57aba4008c1652c14882c61174dc543fb65
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/ssao/composition.frag
@@ -0,0 +1,52 @@
+#version 450
+
+layout (binding = 0) uniform sampler2D samplerposition;
+layout (binding = 1) uniform sampler2D samplerNormal;
+layout (binding = 2) uniform sampler2D samplerAlbedo;
+layout (binding = 3) uniform sampler2D samplerSSAO;
+layout (binding = 4) uniform sampler2D samplerSSAOBlur;
+layout (binding = 5) uniform UBO 
+{
+	mat4 _dummy;
+	int ssao;
+	int ssaoOnly;
+	int ssaoBlur;
+} uboParams;
+
+layout (location = 0) in vec2 inUV;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+	vec3 fragPos = texture(samplerposition, inUV).rgb;
+	vec3 normal = normalize(texture(samplerNormal, inUV).rgb * 2.0 - 1.0);
+	vec4 albedo = texture(samplerAlbedo, inUV);
+	 
+	float ssao = (uboParams.ssaoBlur == 1) ? texture(samplerSSAOBlur, inUV).r : texture(samplerSSAO, inUV).r;
+
+	vec3 lightPos = vec3(0.0);
+	vec3 L = normalize(lightPos - fragPos);
+	float NdotL = max(0.5, dot(normal, L));
+
+	if (uboParams.ssaoOnly == 1)
+	{
+		outFragColor.rgb = ssao.rrr;
+	}
+	else
+	{
+		vec3 baseColor = albedo.rgb * NdotL;
+
+		if (uboParams.ssao == 1)
+		{
+			outFragColor.rgb = ssao.rrr;
+
+			if (uboParams.ssaoOnly != 1)
+				outFragColor.rgb *= baseColor;
+		}
+		else
+		{
+			outFragColor.rgb = baseColor;
+		}
+	}
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/ssao/composition.frag.spv b/external/Vulkan/data/shaders/glsl/ssao/composition.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..b4b65b090717ca01a5233e22ef64e5dfde7dfa21
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/ssao/composition.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/ssao/fullscreen.vert b/external/Vulkan/data/shaders/glsl/ssao/fullscreen.vert
new file mode 100644
index 0000000000000000000000000000000000000000..a5d60d0e750f260d3c034db033e15f18fb6a89d8
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/ssao/fullscreen.vert
@@ -0,0 +1,14 @@
+#version 450
+
+layout (location = 0) out vec2 outUV;
+
+out gl_PerVertex
+{
+	vec4 gl_Position;
+};
+
+void main() 
+{
+	outUV = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
+	gl_Position = vec4(outUV * 2.0f - 1.0f, 0.0f, 1.0f);
+}
diff --git a/external/Vulkan/data/shaders/glsl/ssao/fullscreen.vert.spv b/external/Vulkan/data/shaders/glsl/ssao/fullscreen.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..4040f608bd837727d1051a6539a15338d11242ae
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/ssao/fullscreen.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/ssao/gbuffer.frag b/external/Vulkan/data/shaders/glsl/ssao/gbuffer.frag
new file mode 100644
index 0000000000000000000000000000000000000000..ec7f3682218848996c0f46ba272c47c97e79f182
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/ssao/gbuffer.frag
@@ -0,0 +1,34 @@
+#version 450
+
+layout (location = 0) in vec3 inNormal;
+layout (location = 1) in vec2 inUV;
+layout (location = 2) in vec3 inColor;
+layout (location = 3) in vec3 inPos;
+
+layout (location = 0) out vec4 outPosition;
+layout (location = 1) out vec4 outNormal;
+layout (location = 2) out vec4 outAlbedo;
+
+layout (set = 0, binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 model;
+	mat4 view;
+	float nearPlane;
+	float farPlane;
+} ubo;
+
+layout (set = 1, binding = 0) uniform sampler2D samplerColormap;
+
+float linearDepth(float depth)
+{
+	float z = depth * 2.0f - 1.0f; 
+	return (2.0f * ubo.nearPlane * ubo.farPlane) / (ubo.farPlane + ubo.nearPlane - z * (ubo.farPlane - ubo.nearPlane));	
+}
+
+void main() 
+{
+	outPosition = vec4(inPos, linearDepth(gl_FragCoord.z));
+	outNormal = vec4(normalize(inNormal) * 0.5 + 0.5, 1.0);
+	outAlbedo = texture(samplerColormap, inUV) * vec4(inColor, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/ssao/gbuffer.frag.spv b/external/Vulkan/data/shaders/glsl/ssao/gbuffer.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..40d1af46b735991a513c15eb20aab99d3728837d
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/ssao/gbuffer.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/ssao/gbuffer.vert b/external/Vulkan/data/shaders/glsl/ssao/gbuffer.vert
new file mode 100644
index 0000000000000000000000000000000000000000..e525732241488f528522bc6d07611f460bc0f28c
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/ssao/gbuffer.vert
@@ -0,0 +1,34 @@
+#version 450
+
+layout (location = 0) in vec4 inPos;
+layout (location = 1) in vec2 inUV;
+layout (location = 2) in vec3 inColor;
+layout (location = 3) in vec3 inNormal;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 model;
+	mat4 view;
+} ubo;
+
+layout (location = 0) out vec3 outNormal;
+layout (location = 1) out vec2 outUV;
+layout (location = 2) out vec3 outColor;
+layout (location = 3) out vec3 outPos;
+
+void main() 
+{
+	gl_Position = ubo.projection * ubo.view * ubo.model * inPos;
+	
+	outUV = inUV;
+
+	// Vertex position in view space
+	outPos = vec3(ubo.view * ubo.model * inPos);
+
+	// Normal in view space
+	mat3 normalMatrix = transpose(inverse(mat3(ubo.view * ubo.model)));
+	outNormal = normalMatrix * inNormal;
+
+	outColor = inColor;
+}
diff --git a/external/Vulkan/data/shaders/glsl/ssao/gbuffer.vert.spv b/external/Vulkan/data/shaders/glsl/ssao/gbuffer.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..1d564e07c838a35b7db5d586ab535a3ba0b57bce
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/ssao/gbuffer.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/ssao/ssao.frag b/external/Vulkan/data/shaders/glsl/ssao/ssao.frag
new file mode 100644
index 0000000000000000000000000000000000000000..88aac7e7e51817c80d3a5486524baabc323d5f2e
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/ssao/ssao.frag
@@ -0,0 +1,65 @@
+#version 450
+
+layout (binding = 0) uniform sampler2D samplerPositionDepth;
+layout (binding = 1) uniform sampler2D samplerNormal;
+layout (binding = 2) uniform sampler2D ssaoNoise;
+
+layout (constant_id = 0) const int SSAO_KERNEL_SIZE = 64;
+layout (constant_id = 1) const float SSAO_RADIUS = 0.5;
+
+layout (binding = 3) uniform UBOSSAOKernel
+{
+	vec4 samples[SSAO_KERNEL_SIZE];
+} uboSSAOKernel;
+
+layout (binding = 4) uniform UBO 
+{
+	mat4 projection;
+} ubo;
+
+layout (location = 0) in vec2 inUV;
+
+layout (location = 0) out float outFragColor;
+
+void main() 
+{
+	// Get G-Buffer values
+	vec3 fragPos = texture(samplerPositionDepth, inUV).rgb;
+	vec3 normal = normalize(texture(samplerNormal, inUV).rgb * 2.0 - 1.0);
+
+	// Get a random vector using a noise lookup
+	ivec2 texDim = textureSize(samplerPositionDepth, 0); 
+	ivec2 noiseDim = textureSize(ssaoNoise, 0);
+	const vec2 noiseUV = vec2(float(texDim.x)/float(noiseDim.x), float(texDim.y)/(noiseDim.y)) * inUV;  
+	vec3 randomVec = texture(ssaoNoise, noiseUV).xyz * 2.0 - 1.0;
+	
+	// Create TBN matrix
+	vec3 tangent = normalize(randomVec - normal * dot(randomVec, normal));
+	vec3 bitangent = cross(tangent, normal);
+	mat3 TBN = mat3(tangent, bitangent, normal);
+
+	// Calculate occlusion value
+	float occlusion = 0.0f;
+	// remove banding
+	const float bias = 0.025f;
+	for(int i = 0; i < SSAO_KERNEL_SIZE; i++)
+	{		
+		vec3 samplePos = TBN * uboSSAOKernel.samples[i].xyz; 
+		samplePos = fragPos + samplePos * SSAO_RADIUS; 
+		
+		// project
+		vec4 offset = vec4(samplePos, 1.0f);
+		offset = ubo.projection * offset; 
+		offset.xyz /= offset.w; 
+		offset.xyz = offset.xyz * 0.5f + 0.5f; 
+		
+		float sampleDepth = -texture(samplerPositionDepth, offset.xy).w; 
+
+		float rangeCheck = smoothstep(0.0f, 1.0f, SSAO_RADIUS / abs(fragPos.z - sampleDepth));
+		occlusion += (sampleDepth >= samplePos.z + bias ? 1.0f : 0.0f) * rangeCheck;           
+	}
+	occlusion = 1.0 - (occlusion / float(SSAO_KERNEL_SIZE));
+	
+	outFragColor = occlusion;
+}
+
diff --git a/external/Vulkan/data/shaders/glsl/ssao/ssao.frag.spv b/external/Vulkan/data/shaders/glsl/ssao/ssao.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..fd24339121c1f9a48a199ab96177095732efa62d
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/ssao/ssao.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/stencilbuffer/outline.frag b/external/Vulkan/data/shaders/glsl/stencilbuffer/outline.frag
new file mode 100644
index 0000000000000000000000000000000000000000..eeb56cc31246352fe18d359695421e572700b61d
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/stencilbuffer/outline.frag
@@ -0,0 +1,8 @@
+#version 450
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+	outFragColor = vec4(vec3(1.0), 1.0); 
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/stencilbuffer/outline.frag.spv b/external/Vulkan/data/shaders/glsl/stencilbuffer/outline.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..368f53e281d3d817df3d9b299041e13b6d3a953d
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/stencilbuffer/outline.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/stencilbuffer/outline.vert b/external/Vulkan/data/shaders/glsl/stencilbuffer/outline.vert
new file mode 100644
index 0000000000000000000000000000000000000000..ea6fc17121aa60839e4199f397d78457731eac14
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/stencilbuffer/outline.vert
@@ -0,0 +1,24 @@
+#version 450
+
+layout (location = 0) in vec4 inPos;
+layout (location = 2) in vec3 inNormal;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 model;
+	vec4 lightPos;
+	float outlineWidth;
+} ubo;
+
+out gl_PerVertex
+{
+	vec4 gl_Position;
+};
+
+void main() 
+{
+	// Extrude along normal
+	vec4 pos = vec4(inPos.xyz + inNormal * ubo.outlineWidth, inPos.w);
+	gl_Position = ubo.projection * ubo.model * pos;
+}
diff --git a/external/Vulkan/data/shaders/glsl/stencilbuffer/outline.vert.spv b/external/Vulkan/data/shaders/glsl/stencilbuffer/outline.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..ea2fcb854c86417e274ed59e48e3f7f20639e654
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/stencilbuffer/outline.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/stencilbuffer/toon.frag b/external/Vulkan/data/shaders/glsl/stencilbuffer/toon.frag
new file mode 100644
index 0000000000000000000000000000000000000000..3d6a081a5aa3cca589f364d905085bac4816ade7
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/stencilbuffer/toon.frag
@@ -0,0 +1,30 @@
+#version 450
+
+layout (binding = 1) uniform sampler2D samplerColorMap;
+
+layout (location = 0) in vec3 inNormal;
+layout (location = 1) in vec3 inColor;
+layout (location = 2) in vec3 inLightVec;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+	vec3 color;
+	vec3 N = normalize(inNormal);
+	vec3 L = normalize(inLightVec);
+	float intensity = dot(N,L);
+	if (intensity > 0.98)
+		color = inColor * 1.5;
+	else if  (intensity > 0.9)
+		color = inColor * 1.0;
+	else if (intensity > 0.5)
+		color = inColor * 0.6;
+	else if (intensity > 0.25)
+		color = inColor * 0.4;
+	else
+		color = inColor * 0.2;
+	// Desaturate a bit
+	color = vec3(mix(color, vec3(dot(vec3(0.2126,0.7152,0.0722), color)), 0.1));	
+	outFragColor.rgb = color;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/stencilbuffer/toon.frag.spv b/external/Vulkan/data/shaders/glsl/stencilbuffer/toon.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..79117e4bed5886d9e677740b4a52e21dc21f3d00
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/stencilbuffer/toon.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/stencilbuffer/toon.vert b/external/Vulkan/data/shaders/glsl/stencilbuffer/toon.vert
new file mode 100644
index 0000000000000000000000000000000000000000..a6c00d952f6ace16e8adfd0ac034ec5313931288
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/stencilbuffer/toon.vert
@@ -0,0 +1,31 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec3 inColor;
+layout (location = 2) in vec3 inNormal;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 model;
+	vec4 lightPos;
+} ubo;
+
+layout (location = 0) out vec3 outNormal;
+layout (location = 1) out vec3 outColor;
+layout (location = 2) out vec3 outLightVec;
+
+out gl_PerVertex
+{
+	vec4 gl_Position;
+};
+
+void main() 
+{
+	outColor = vec3(1.0, 0.0, 0.0);
+	gl_Position = ubo.projection * ubo.model * vec4(inPos.xyz, 1.0);
+	outNormal = mat3(ubo.model) * inNormal;
+	vec4 pos = ubo.model * vec4(inPos, 1.0);
+	vec3 lPos = mat3(ubo.model) * ubo.lightPos.xyz;
+	outLightVec = lPos - pos.xyz;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/stencilbuffer/toon.vert.spv b/external/Vulkan/data/shaders/glsl/stencilbuffer/toon.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..9831ce38e6cf17fd18ff8748562d97aa6798ea54
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/stencilbuffer/toon.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/subpasses/composition.frag b/external/Vulkan/data/shaders/glsl/subpasses/composition.frag
new file mode 100644
index 0000000000000000000000000000000000000000..a111d4558571a78db7fa6c7c03b65344885d2be1
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/subpasses/composition.frag
@@ -0,0 +1,70 @@
+#version 450
+
+layout (input_attachment_index = 0, binding = 0) uniform subpassInput samplerposition;
+layout (input_attachment_index = 1, binding = 1) uniform subpassInput samplerNormal;
+layout (input_attachment_index = 2, binding = 2) uniform subpassInput samplerAlbedo;
+
+layout (location = 0) in vec2 inUV;
+
+layout (location = 0) out vec4 outColor;
+
+layout (constant_id = 0) const int NUM_LIGHTS = 64;
+
+struct Light {
+	vec4 position;
+	vec3 color;
+	float radius;
+};
+
+layout (binding = 3) uniform UBO 
+{
+	vec4 viewPos;
+	Light lights[NUM_LIGHTS];
+} ubo;
+
+
+void main() 
+{
+	// Read G-Buffer values from previous sub pass
+	vec3 fragPos = subpassLoad(samplerposition).rgb;
+	vec3 normal = subpassLoad(samplerNormal).rgb;
+	vec4 albedo = subpassLoad(samplerAlbedo);
+	
+	#define ambient 0.15
+	
+	// Ambient part
+	vec3 fragcolor  = albedo.rgb * ambient;
+	
+	for(int i = 0; i < NUM_LIGHTS; ++i)
+	{
+		// Vector to light
+		vec3 L = ubo.lights[i].position.xyz - fragPos;
+		// Distance from light to fragment position
+		float dist = length(L);
+
+		// Viewer to fragment
+		vec3 V = ubo.viewPos.xyz - fragPos;
+		V = normalize(V);
+		
+		// Light to fragment
+		L = normalize(L);
+
+		// Attenuation
+		float atten = ubo.lights[i].radius / (pow(dist, 2.0) + 1.0);
+
+		// Diffuse part
+		vec3 N = normalize(normal);
+		float NdotL = max(0.0, dot(N, L));
+		vec3 diff = ubo.lights[i].color * albedo.rgb * NdotL * atten;
+
+		// Specular part
+		// Specular map values are stored in alpha of albedo mrt
+		vec3 R = reflect(-L, N);
+		float NdotR = max(0.0, dot(R, V));
+		//vec3 spec = ubo.lights[i].color * albedo.a * pow(NdotR, 32.0) * atten;
+
+		fragcolor += diff;// + spec;	
+	}    	
+   
+	outColor = vec4(fragcolor, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/subpasses/composition.frag.spv b/external/Vulkan/data/shaders/glsl/subpasses/composition.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..9257020ccdfef2c0cd9afcbea278e251a0cfc769
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/subpasses/composition.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/subpasses/composition.vert b/external/Vulkan/data/shaders/glsl/subpasses/composition.vert
new file mode 100644
index 0000000000000000000000000000000000000000..1f318185b4a6c2afd5c7a4dd9fe30741f46ec5d5
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/subpasses/composition.vert
@@ -0,0 +1,14 @@
+#version 450
+
+layout (location = 0) out vec2 outUV;
+
+out gl_PerVertex
+{
+	vec4 gl_Position;
+};
+
+void main() 
+{
+	outUV = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
+	gl_Position = vec4(outUV * 2.0f - 1.0f, 0.0f, 1.0f);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/subpasses/composition.vert.spv b/external/Vulkan/data/shaders/glsl/subpasses/composition.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..4040f608bd837727d1051a6539a15338d11242ae
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/subpasses/composition.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/subpasses/gbuffer.frag b/external/Vulkan/data/shaders/glsl/subpasses/gbuffer.frag
new file mode 100644
index 0000000000000000000000000000000000000000..44d319f415109fdd1cc50c0d457ab693599c6b1c
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/subpasses/gbuffer.frag
@@ -0,0 +1,36 @@
+#version 450
+
+layout (location = 0) in vec3 inNormal;
+layout (location = 1) in vec3 inColor;
+layout (location = 2) in vec3 inWorldPos;
+
+layout (location = 0) out vec4 outColor;
+layout (location = 1) out vec4 outPosition;
+layout (location = 2) out vec4 outNormal;
+layout (location = 3) out vec4 outAlbedo;
+
+layout (constant_id = 0) const float NEAR_PLANE = 0.1f;
+layout (constant_id = 1) const float FAR_PLANE = 256.0f;
+
+float linearDepth(float depth)
+{
+	float z = depth * 2.0f - 1.0f; 
+	return (2.0f * NEAR_PLANE * FAR_PLANE) / (FAR_PLANE + NEAR_PLANE - z * (FAR_PLANE - NEAR_PLANE));	
+}
+
+void main() 
+{
+	outPosition = vec4(inWorldPos, 1.0);
+
+	vec3 N = normalize(inNormal);
+	N.y = -N.y;
+	outNormal = vec4(N, 1.0);
+
+	outAlbedo.rgb = inColor;
+
+	// Store linearized depth in alpha component
+	outPosition.a = linearDepth(gl_FragCoord.z);
+
+	// Write color attachments to avoid undefined behaviour (validation error)
+	outColor = vec4(0.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/subpasses/gbuffer.frag.spv b/external/Vulkan/data/shaders/glsl/subpasses/gbuffer.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..5247445a8b54b502ee21893f2ee324d15522dc89
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/subpasses/gbuffer.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/subpasses/gbuffer.vert b/external/Vulkan/data/shaders/glsl/subpasses/gbuffer.vert
new file mode 100644
index 0000000000000000000000000000000000000000..fc36c231d42c6eb2793ca2d727d6031f780c9e91
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/subpasses/gbuffer.vert
@@ -0,0 +1,39 @@
+#version 450
+
+layout (location = 0) in vec4 inPos;
+layout (location = 1) in vec3 inColor;
+layout (location = 2) in vec3 inNormal;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 model;
+	mat4 view;
+} ubo;
+
+layout (location = 0) out vec3 outNormal;
+layout (location = 1) out vec3 outColor;
+layout (location = 2) out vec3 outWorldPos;
+layout (location = 3) out vec3 outTangent;
+
+out gl_PerVertex
+{
+	vec4 gl_Position;
+};
+
+void main() 
+{
+	gl_Position = ubo.projection * ubo.view * ubo.model * inPos;
+	
+	// Vertex position in world space
+	outWorldPos = vec3(ubo.model * inPos);
+	// GL to Vulkan coord space
+	outWorldPos.y = -outWorldPos.y;
+	
+	// Normal in world space
+	mat3 mNormal = transpose(inverse(mat3(ubo.model)));
+	outNormal = mNormal * normalize(inNormal);	
+	
+	// Currently just vertex color
+	outColor = inColor;
+}
diff --git a/external/Vulkan/data/shaders/glsl/subpasses/gbuffer.vert.spv b/external/Vulkan/data/shaders/glsl/subpasses/gbuffer.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..cf2b5b45eb3a357c44879782bd258cd9e910f5a4
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/subpasses/gbuffer.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/subpasses/transparent.frag b/external/Vulkan/data/shaders/glsl/subpasses/transparent.frag
new file mode 100644
index 0000000000000000000000000000000000000000..ca860f551bc137af1b20dfda08fa5372b91c5b4e
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/subpasses/transparent.frag
@@ -0,0 +1,34 @@
+#version 450
+
+layout (input_attachment_index = 0, binding = 1) uniform subpassInput samplerPositionDepth;
+layout (binding = 2) uniform sampler2D samplerTexture;
+
+layout (location = 0) in vec3 inColor;
+layout (location = 1) in vec2 inUV;
+
+layout (location = 0) out vec4 outColor;
+
+layout (constant_id = 0) const float NEAR_PLANE = 0.1f;
+layout (constant_id = 1) const float FAR_PLANE = 256.0f;
+
+float linearDepth(float depth)
+{
+	float z = depth * 2.0f - 1.0f; 
+	return (2.0f * NEAR_PLANE * FAR_PLANE) / (FAR_PLANE + NEAR_PLANE - z * (FAR_PLANE - NEAR_PLANE));	
+}
+
+void main () 
+{
+	// Sample depth from deferred depth buffer and discard if obscured
+	float depth = subpassLoad(samplerPositionDepth).a;
+
+	// Save the sampled texture color before discarding.
+	// This is to avoid implicit derivatives in non-uniform control flow.
+	vec4 sampledColor = texture(samplerTexture, inUV);
+	if ((depth != 0.0) && (linearDepth(gl_FragCoord.z) > depth))
+	{
+		discard;
+	};
+
+	outColor = sampledColor;
+}
diff --git a/external/Vulkan/data/shaders/glsl/subpasses/transparent.frag.spv b/external/Vulkan/data/shaders/glsl/subpasses/transparent.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..42fd097a2ae368a75eeb435100b7b2083feff75c
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/subpasses/transparent.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/subpasses/transparent.vert b/external/Vulkan/data/shaders/glsl/subpasses/transparent.vert
new file mode 100644
index 0000000000000000000000000000000000000000..89e0f1f0fe90510fa57ba75590df0d31bbda11d0
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/subpasses/transparent.vert
@@ -0,0 +1,24 @@
+#version 450
+
+layout (location = 0) in vec4 inPos;
+layout (location = 1) in vec3 inColor;
+layout (location = 2) in vec3 inNormal;
+layout (location = 3) in vec2 inUV;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 model;
+	mat4 view;
+} ubo;
+
+layout (location = 0) out vec3 outColor;
+layout (location = 1) out vec2 outUV;
+
+void main () 
+{
+	outColor = inColor;
+	outUV = inUV;
+	
+	gl_Position = ubo.projection * ubo.view * ubo.model * vec4(inPos.xyz, 1.0);		
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/subpasses/transparent.vert.spv b/external/Vulkan/data/shaders/glsl/subpasses/transparent.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..27d94ea3ee4f56ddcb241cf8bb3790090bb0a616
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/subpasses/transparent.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/terraintessellation/skysphere.frag b/external/Vulkan/data/shaders/glsl/terraintessellation/skysphere.frag
new file mode 100644
index 0000000000000000000000000000000000000000..d9c9f92d49a8dd22eee42d9b129b2c23d161506b
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/terraintessellation/skysphere.frag
@@ -0,0 +1,13 @@
+#version 450 core
+
+layout (location = 0) in vec2 inUV;
+
+layout (set = 0, binding = 1) uniform sampler2D samplerColorMap;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main(void)
+{
+	vec4 color = texture(samplerColorMap, inUV);
+	outFragColor = vec4(color.rgb, 1.0);
+}
diff --git a/external/Vulkan/data/shaders/glsl/terraintessellation/skysphere.frag.spv b/external/Vulkan/data/shaders/glsl/terraintessellation/skysphere.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..925b15060f4bdc8aa6c1d71fcfc92d3402f82c2f
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/terraintessellation/skysphere.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/terraintessellation/skysphere.vert b/external/Vulkan/data/shaders/glsl/terraintessellation/skysphere.vert
new file mode 100644
index 0000000000000000000000000000000000000000..32b0dfd5d8551d6a9c0d03095206b227ce003d6c
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/terraintessellation/skysphere.vert
@@ -0,0 +1,18 @@
+#version 450 core
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec3 inNormal;
+layout (location = 2) in vec2 inUV;
+
+layout (location = 0) out vec2 outUV;
+
+layout (set = 0, binding = 0) uniform UBO 
+{
+	mat4 mvp;
+} ubo;
+
+void main(void)
+{
+	gl_Position = ubo.mvp * vec4(inPos, 1.0);
+	outUV = inUV;
+}
diff --git a/external/Vulkan/data/shaders/glsl/terraintessellation/skysphere.vert.spv b/external/Vulkan/data/shaders/glsl/terraintessellation/skysphere.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..6537b6c482bc766f6c0c3bd2f38359da864dd58a
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/terraintessellation/skysphere.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/terraintessellation/terrain.frag b/external/Vulkan/data/shaders/glsl/terraintessellation/terrain.frag
new file mode 100644
index 0000000000000000000000000000000000000000..0b6bac64f02081586928f90ae1c4a8e20a4468e9
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/terraintessellation/terrain.frag
@@ -0,0 +1,61 @@
+#version 450
+
+layout (set = 0, binding = 1) uniform sampler2D samplerHeight; 
+layout (set = 0, binding = 2) uniform sampler2DArray samplerLayers;
+
+layout (location = 0) in vec3 inNormal;
+layout (location = 1) in vec2 inUV;
+layout (location = 2) in vec3 inViewVec;
+layout (location = 3) in vec3 inLightVec;
+layout (location = 4) in vec3 inEyePos;
+layout (location = 5) in vec3 inWorldPos;
+
+layout (location = 0) out vec4 outFragColor;
+
+vec3 sampleTerrainLayer()
+{
+	// Define some layer ranges for sampling depending on terrain height
+	vec2 layers[6];
+	layers[0] = vec2(-10.0, 10.0);
+	layers[1] = vec2(5.0, 45.0);
+	layers[2] = vec2(45.0, 80.0);
+	layers[3] = vec2(75.0, 100.0);
+	layers[4] = vec2(95.0, 140.0);
+	layers[5] = vec2(140.0, 190.0);
+
+	vec3 color = vec3(0.0);
+	
+	// Get height from displacement map
+	float height = textureLod(samplerHeight, inUV, 0.0).r * 255.0;
+	
+	for (int i = 0; i < 6; i++)
+	{
+		float range = layers[i].y - layers[i].x;
+		float weight = (range - abs(height - layers[i].y)) / range;
+		weight = max(0.0, weight);
+		color += weight * texture(samplerLayers, vec3(inUV * 16.0, i)).rgb;
+	}
+
+	return color;
+}
+
+float fog(float density)
+{
+	const float LOG2 = -1.442695;
+	float dist = gl_FragCoord.z / gl_FragCoord.w * 0.1;
+	float d = density * dist;
+	return 1.0 - clamp(exp2(d * d * LOG2), 0.0, 1.0);
+}
+
+void main()
+{
+	vec3 N = normalize(inNormal);
+	vec3 L = normalize(inLightVec);
+	vec3 ambient = vec3(0.5);
+	vec3 diffuse = max(dot(N, L), 0.0) * vec3(1.0);
+
+	vec4 color = vec4((ambient + diffuse) * sampleTerrainLayer(), 1.0);
+
+	const vec4 fogColor = vec4(0.47, 0.5, 0.67, 0.0);
+	outFragColor  = mix(color, fogColor, fog(0.25));	
+}
diff --git a/external/Vulkan/data/shaders/glsl/terraintessellation/terrain.frag.spv b/external/Vulkan/data/shaders/glsl/terraintessellation/terrain.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..8a0d6a47b05546560cf83402a46571fe531cf990
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/terraintessellation/terrain.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/terraintessellation/terrain.tesc b/external/Vulkan/data/shaders/glsl/terraintessellation/terrain.tesc
new file mode 100644
index 0000000000000000000000000000000000000000..e92dfc828e223ec77648cbbf664a27d99062e3a4
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/terraintessellation/terrain.tesc
@@ -0,0 +1,116 @@
+#version 450
+
+layout(set = 0, binding = 0) uniform UBO
+{
+	mat4 projection;
+	mat4 modelview;
+	vec4 lightPos;
+	vec4 frustumPlanes[6];
+	float displacementFactor;
+	float tessellationFactor;
+	vec2 viewportDim;
+	float tessellatedEdgeSize;
+} ubo;
+
+layout(set = 0, binding = 1) uniform sampler2D samplerHeight;
+
+layout (vertices = 4) out;
+ 
+layout (location = 0) in vec3 inNormal[];
+layout (location = 1) in vec2 inUV[];
+ 
+layout (location = 0) out vec3 outNormal[4];
+layout (location = 1) out vec2 outUV[4];
+ 
+// Calculate the tessellation factor based on screen space
+// dimensions of the edge
+float screenSpaceTessFactor(vec4 p0, vec4 p1)
+{
+	// Calculate edge mid point
+	vec4 midPoint = 0.5 * (p0 + p1);
+	// Sphere radius as distance between the control points
+	float radius = distance(p0, p1) / 2.0;
+
+	// View space
+	vec4 v0 = ubo.modelview  * midPoint;
+
+	// Project into clip space
+	vec4 clip0 = (ubo.projection * (v0 - vec4(radius, vec3(0.0))));
+	vec4 clip1 = (ubo.projection * (v0 + vec4(radius, vec3(0.0))));
+
+	// Get normalized device coordinates
+	clip0 /= clip0.w;
+	clip1 /= clip1.w;
+
+	// Convert to viewport coordinates
+	clip0.xy *= ubo.viewportDim;
+	clip1.xy *= ubo.viewportDim;
+	
+	// Return the tessellation factor based on the screen size 
+	// given by the distance of the two edge control points in screen space
+	// and a reference (min.) tessellation size for the edge set by the application
+	return clamp(distance(clip0, clip1) / ubo.tessellatedEdgeSize * ubo.tessellationFactor, 1.0, 64.0);
+}
+
+// Checks the current's patch visibility against the frustum using a sphere check
+// Sphere radius is given by the patch size
+bool frustumCheck()
+{
+	// Fixed radius (increase if patch size is increased in example)
+	const float radius = 8.0f;
+	vec4 pos = gl_in[gl_InvocationID].gl_Position;
+	pos.y -= textureLod(samplerHeight, inUV[0], 0.0).r * ubo.displacementFactor;
+
+	// Check sphere against frustum planes
+	for (int i = 0; i < 6; i++) {
+		if (dot(pos, ubo.frustumPlanes[i]) + radius < 0.0)
+		{
+			return false;
+		}
+	}
+	return true;
+}
+
+void main()
+{
+	if (gl_InvocationID == 0)
+	{
+		if (!frustumCheck())
+		{
+			gl_TessLevelInner[0] = 0.0;
+			gl_TessLevelInner[1] = 0.0;
+			gl_TessLevelOuter[0] = 0.0;
+			gl_TessLevelOuter[1] = 0.0;
+			gl_TessLevelOuter[2] = 0.0;
+			gl_TessLevelOuter[3] = 0.0;
+		}
+		else
+		{
+			if (ubo.tessellationFactor > 0.0)
+			{
+				gl_TessLevelOuter[0] = screenSpaceTessFactor(gl_in[3].gl_Position, gl_in[0].gl_Position);
+				gl_TessLevelOuter[1] = screenSpaceTessFactor(gl_in[0].gl_Position, gl_in[1].gl_Position);
+				gl_TessLevelOuter[2] = screenSpaceTessFactor(gl_in[1].gl_Position, gl_in[2].gl_Position);
+				gl_TessLevelOuter[3] = screenSpaceTessFactor(gl_in[2].gl_Position, gl_in[3].gl_Position);
+				gl_TessLevelInner[0] = mix(gl_TessLevelOuter[0], gl_TessLevelOuter[3], 0.5);
+				gl_TessLevelInner[1] = mix(gl_TessLevelOuter[2], gl_TessLevelOuter[1], 0.5);
+			}
+			else
+			{
+				// Tessellation factor can be set to zero by example
+				// to demonstrate a simple passthrough
+				gl_TessLevelInner[0] = 1.0;
+				gl_TessLevelInner[1] = 1.0;
+				gl_TessLevelOuter[0] = 1.0;
+				gl_TessLevelOuter[1] = 1.0;
+				gl_TessLevelOuter[2] = 1.0;
+				gl_TessLevelOuter[3] = 1.0;
+			}
+		}
+
+	}
+
+	gl_out[gl_InvocationID].gl_Position =  gl_in[gl_InvocationID].gl_Position;
+	outNormal[gl_InvocationID] = inNormal[gl_InvocationID];
+	outUV[gl_InvocationID] = inUV[gl_InvocationID];
+} 
diff --git a/external/Vulkan/data/shaders/glsl/terraintessellation/terrain.tesc.spv b/external/Vulkan/data/shaders/glsl/terraintessellation/terrain.tesc.spv
new file mode 100644
index 0000000000000000000000000000000000000000..cd855c463dda59b87106307d7e99b48e6072e03c
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/terraintessellation/terrain.tesc.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/terraintessellation/terrain.tese b/external/Vulkan/data/shaders/glsl/terraintessellation/terrain.tese
new file mode 100644
index 0000000000000000000000000000000000000000..088fc5c39bc63f0f7b1c2f8518a4788e65f7d808
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/terraintessellation/terrain.tese
@@ -0,0 +1,54 @@
+#version 450
+
+layout (set = 0, binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 modelview;
+	vec4 lightPos;
+	vec4 frustumPlanes[6];
+	float displacementFactor;
+	float tessellationFactor;
+	vec2 viewportDim;
+	float tessellatedEdgeSize;
+} ubo; 
+
+layout (set = 0, binding = 1) uniform sampler2D displacementMap; 
+
+layout(quads, equal_spacing, cw) in;
+
+layout (location = 0) in vec3 inNormal[];
+layout (location = 1) in vec2 inUV[];
+ 
+layout (location = 0) out vec3 outNormal;
+layout (location = 1) out vec2 outUV;
+layout (location = 2) out vec3 outViewVec;
+layout (location = 3) out vec3 outLightVec;
+layout (location = 4) out vec3 outEyePos;
+layout (location = 5) out vec3 outWorldPos;
+
+void main()
+{
+	// Interpolate UV coordinates
+	vec2 uv1 = mix(inUV[0], inUV[1], gl_TessCoord.x);
+	vec2 uv2 = mix(inUV[3], inUV[2], gl_TessCoord.x);
+	outUV = mix(uv1, uv2, gl_TessCoord.y);
+
+	vec3 n1 = mix(inNormal[0], inNormal[1], gl_TessCoord.x);
+	vec3 n2 = mix(inNormal[3], inNormal[2], gl_TessCoord.x);
+	outNormal = mix(n1, n2, gl_TessCoord.y);
+
+	// Interpolate positions
+	vec4 pos1 = mix(gl_in[0].gl_Position, gl_in[1].gl_Position, gl_TessCoord.x);
+	vec4 pos2 = mix(gl_in[3].gl_Position, gl_in[2].gl_Position, gl_TessCoord.x);
+	vec4 pos = mix(pos1, pos2, gl_TessCoord.y);
+	// Displace
+	pos.y -= textureLod(displacementMap, outUV, 0.0).r * ubo.displacementFactor;
+	// Perspective projection
+	gl_Position = ubo.projection * ubo.modelview * pos;
+
+	// Calculate vectors for lighting based on tessellated position
+	outViewVec = -pos.xyz;
+	outLightVec = normalize(ubo.lightPos.xyz + outViewVec);
+	outWorldPos = pos.xyz;
+	outEyePos = vec3(ubo.modelview * pos);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/terraintessellation/terrain.tese.spv b/external/Vulkan/data/shaders/glsl/terraintessellation/terrain.tese.spv
new file mode 100644
index 0000000000000000000000000000000000000000..cdc6193d1efdbb026fb67c938cf1b1a11c7c0f21
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/terraintessellation/terrain.tese.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/terraintessellation/terrain.vert b/external/Vulkan/data/shaders/glsl/terraintessellation/terrain.vert
new file mode 100644
index 0000000000000000000000000000000000000000..61ef82018feb245a4831d2e1f9e005246e84ab38
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/terraintessellation/terrain.vert
@@ -0,0 +1,15 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec3 inNormal;
+layout (location = 2) in vec2 inUV;
+
+layout (location = 0) out vec3 outNormal;
+layout (location = 1) out vec2 outUV;
+
+void main(void)
+{
+	gl_Position = vec4(inPos.xyz, 1.0);
+	outUV = inUV;
+	outNormal = inNormal;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/terraintessellation/terrain.vert.spv b/external/Vulkan/data/shaders/glsl/terraintessellation/terrain.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..ce67e25d2b6d40166b599e2dca0e68d89fb3d62f
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/terraintessellation/terrain.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/tessellation/base.frag b/external/Vulkan/data/shaders/glsl/tessellation/base.frag
new file mode 100644
index 0000000000000000000000000000000000000000..8e3f204c687da1eae02a0bb41f91013a2a8dd3a9
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/tessellation/base.frag
@@ -0,0 +1,18 @@
+#version 450
+
+layout (set = 1, binding = 0) uniform sampler2D samplerColorMap;
+
+layout (location = 0) in vec3 inNormal;
+layout (location = 1) in vec2 inUV;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main()
+{
+	vec3 N = normalize(inNormal);
+	vec3 L = normalize(vec3(-4.0, -4.0, 0.0));
+
+	vec4 color = texture(samplerColorMap, inUV);
+	
+	outFragColor.rgb = vec3(clamp(max(dot(N,L), 0.0), 0.2, 1.0)) * color.rgb;
+}
diff --git a/external/Vulkan/data/shaders/glsl/tessellation/base.frag.spv b/external/Vulkan/data/shaders/glsl/tessellation/base.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..3822b61325db202bc42254393c5ca49feeaa570e
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/tessellation/base.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/tessellation/base.vert b/external/Vulkan/data/shaders/glsl/tessellation/base.vert
new file mode 100644
index 0000000000000000000000000000000000000000..1e5f8080ed8578af485fc5bba84ac9f9b65e07cb
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/tessellation/base.vert
@@ -0,0 +1,15 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec3 inNormal;
+layout (location = 2) in vec2 inUV;
+
+layout (location = 0) out vec3 outNormal;
+layout (location = 1) out vec2 outUV;
+
+void main(void)
+{
+	gl_Position = vec4(inPos.xyz, 1.0);
+	outNormal = inNormal;
+	outUV = inUV;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/tessellation/base.vert.spv b/external/Vulkan/data/shaders/glsl/tessellation/base.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..55d812e65bf2a1c92bfa0a8e1a5fb31842d56a3d
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/tessellation/base.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/tessellation/passthrough.tesc b/external/Vulkan/data/shaders/glsl/tessellation/passthrough.tesc
new file mode 100644
index 0000000000000000000000000000000000000000..fa74dd0aaee0ddfc16cdb04e89cab2b441e55b38
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/tessellation/passthrough.tesc
@@ -0,0 +1,24 @@
+#version 450
+
+layout (vertices = 3) out;
+
+layout (location = 0) in vec3 inNormal[];
+layout (location = 1) in vec2 inUV[];
+
+layout (location = 0) out vec3 outNormal[3];
+layout (location = 1) out vec2 outUV[3];
+
+void main(void)
+{
+    if (gl_InvocationID == 0)
+    {
+        gl_TessLevelInner[0] = 1.0;
+        gl_TessLevelOuter[0] = 1.0;
+        gl_TessLevelOuter[1] = 1.0;
+        gl_TessLevelOuter[2] = 1.0;
+    }
+
+    gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;
+	outNormal[gl_InvocationID] = inNormal[gl_InvocationID];
+	outUV[gl_InvocationID] = inUV[gl_InvocationID];	
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/tessellation/passthrough.tesc.spv b/external/Vulkan/data/shaders/glsl/tessellation/passthrough.tesc.spv
new file mode 100644
index 0000000000000000000000000000000000000000..51cbea34586f27fece9d9fc5a040bd2c06b55744
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/tessellation/passthrough.tesc.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/tessellation/passthrough.tese b/external/Vulkan/data/shaders/glsl/tessellation/passthrough.tese
new file mode 100644
index 0000000000000000000000000000000000000000..cf1df9a40d1c08a96d3c61a86d28ff9897e5cbf6
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/tessellation/passthrough.tese
@@ -0,0 +1,27 @@
+#version 450
+
+layout (triangles, fractional_odd_spacing, cw) in;
+
+layout (binding = 1) uniform UBO 
+{
+	mat4 projection;
+	mat4 model;
+	float tessAlpha;
+} ubo; 
+
+layout (location = 0) in vec3 inNormal[];
+layout (location = 1) in vec2 inUV[];
+
+layout (location = 0) out vec3 outNormal;
+layout (location = 1) out vec2 outUV;
+
+void main(void)
+{
+    gl_Position = (gl_TessCoord.x * gl_in[0].gl_Position) +
+                  (gl_TessCoord.y * gl_in[1].gl_Position) +
+                  (gl_TessCoord.z * gl_in[2].gl_Position);
+	gl_Position = ubo.projection * ubo.model * gl_Position;
+	
+	outNormal = gl_TessCoord.x*inNormal[0] + gl_TessCoord.y*inNormal[1] + gl_TessCoord.z*inNormal[2];
+	outUV = gl_TessCoord.x*inUV[0] + gl_TessCoord.y*inUV[1] + gl_TessCoord.z*inUV[2];
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/tessellation/passthrough.tese.spv b/external/Vulkan/data/shaders/glsl/tessellation/passthrough.tese.spv
new file mode 100644
index 0000000000000000000000000000000000000000..338d88d1d4ce70ad2edd534d530811fadb6a7b21
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/tessellation/passthrough.tese.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/tessellation/pntriangles.tesc b/external/Vulkan/data/shaders/glsl/tessellation/pntriangles.tesc
new file mode 100644
index 0000000000000000000000000000000000000000..8c3ff4ed6aa521bda32366365eca2af8a8f5f79f
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/tessellation/pntriangles.tesc
@@ -0,0 +1,83 @@
+#version 450
+
+// PN patch data
+struct PnPatch
+{
+ float b210;
+ float b120;
+ float b021;
+ float b012;
+ float b102;
+ float b201;
+ float b111;
+ float n110;
+ float n011;
+ float n101;
+};
+
+// tessellation levels
+layout (binding = 0) uniform UBO 
+{
+	float tessLevel;
+} ubo; 
+
+layout(vertices=3) out;
+
+layout(location = 0) in vec3 inNormal[];
+layout(location = 1) in vec2 inUV[];
+
+layout(location = 0) out vec3 outNormal[3];
+layout(location = 3) out vec2 outUV[3];
+layout(location = 6) out PnPatch outPatch[3];
+
+float wij(int i, int j)
+{
+	return dot(gl_in[j].gl_Position.xyz - gl_in[i].gl_Position.xyz, inNormal[i]);
+}
+
+float vij(int i, int j)
+{
+	vec3 Pj_minus_Pi = gl_in[j].gl_Position.xyz
+					- gl_in[i].gl_Position.xyz;
+	vec3 Ni_plus_Nj  = inNormal[i]+inNormal[j];
+	return 2.0*dot(Pj_minus_Pi, Ni_plus_Nj)/dot(Pj_minus_Pi, Pj_minus_Pi);
+}
+
+void main()
+{
+	// get data
+	gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;
+	outNormal[gl_InvocationID]            = inNormal[gl_InvocationID];
+	outUV[gl_InvocationID]          = inUV[gl_InvocationID];
+
+	// set base 
+	float P0 = gl_in[0].gl_Position[gl_InvocationID];
+	float P1 = gl_in[1].gl_Position[gl_InvocationID];
+	float P2 = gl_in[2].gl_Position[gl_InvocationID];
+	float N0 = inNormal[0][gl_InvocationID];
+	float N1 = inNormal[1][gl_InvocationID];
+	float N2 = inNormal[2][gl_InvocationID];
+
+	// compute control points
+	outPatch[gl_InvocationID].b210 = (2.0*P0 + P1 - wij(0,1)*N0)/3.0;
+	outPatch[gl_InvocationID].b120 = (2.0*P1 + P0 - wij(1,0)*N1)/3.0;
+	outPatch[gl_InvocationID].b021 = (2.0*P1 + P2 - wij(1,2)*N1)/3.0;
+	outPatch[gl_InvocationID].b012 = (2.0*P2 + P1 - wij(2,1)*N2)/3.0;
+	outPatch[gl_InvocationID].b102 = (2.0*P2 + P0 - wij(2,0)*N2)/3.0;
+	outPatch[gl_InvocationID].b201 = (2.0*P0 + P2 - wij(0,2)*N0)/3.0;
+	float E = ( outPatch[gl_InvocationID].b210
+			+ outPatch[gl_InvocationID].b120
+			+ outPatch[gl_InvocationID].b021
+			+ outPatch[gl_InvocationID].b012
+			+ outPatch[gl_InvocationID].b102
+			+ outPatch[gl_InvocationID].b201 ) / 6.0;
+	float V = (P0 + P1 + P2)/3.0;
+	outPatch[gl_InvocationID].b111 = E + (E - V)*0.5;
+	outPatch[gl_InvocationID].n110 = N0+N1-vij(0,1)*(P1-P0);
+	outPatch[gl_InvocationID].n011 = N1+N2-vij(1,2)*(P2-P1);
+	outPatch[gl_InvocationID].n101 = N2+N0-vij(2,0)*(P0-P2);
+
+	// set tess levels
+	gl_TessLevelOuter[gl_InvocationID] = ubo.tessLevel;
+	gl_TessLevelInner[0] = ubo.tessLevel;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/tessellation/pntriangles.tesc.spv b/external/Vulkan/data/shaders/glsl/tessellation/pntriangles.tesc.spv
new file mode 100644
index 0000000000000000000000000000000000000000..906d5894dd431db40963c6822f981f234f847fd3
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/tessellation/pntriangles.tesc.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/tessellation/pntriangles.tese b/external/Vulkan/data/shaders/glsl/tessellation/pntriangles.tese
new file mode 100644
index 0000000000000000000000000000000000000000..0c09363fb14992b1416690dcf9687b54d17ef737
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/tessellation/pntriangles.tese
@@ -0,0 +1,88 @@
+#version 450
+
+// PN patch data
+struct PnPatch
+{
+ float b210;
+ float b120;
+ float b021;
+ float b012;
+ float b102;
+ float b201;
+ float b111;
+ float n110;
+ float n011;
+ float n101;
+};
+
+layout (binding = 1) uniform UBO 
+{
+    mat4 projection;
+    mat4 model;
+    float tessAlpha;
+} ubo;
+
+layout(triangles, fractional_odd_spacing, cw) in;
+
+layout(location = 0) in vec3 iNormal[];
+layout(location = 3) in vec2 iTexCoord[];
+layout(location = 6) in PnPatch iPnPatch[];
+
+layout(location = 0) out vec3 oNormal;
+layout(location = 1) out vec2 oTexCoord;
+
+#define uvw gl_TessCoord
+
+void main()
+{
+    vec3 uvwSquared = uvw * uvw;
+    vec3 uvwCubed   = uvwSquared * uvw;
+
+    // extract control points
+    vec3 b210 = vec3(iPnPatch[0].b210, iPnPatch[1].b210, iPnPatch[2].b210);
+    vec3 b120 = vec3(iPnPatch[0].b120, iPnPatch[1].b120, iPnPatch[2].b120);
+    vec3 b021 = vec3(iPnPatch[0].b021, iPnPatch[1].b021, iPnPatch[2].b021);
+    vec3 b012 = vec3(iPnPatch[0].b012, iPnPatch[1].b012, iPnPatch[2].b012);
+    vec3 b102 = vec3(iPnPatch[0].b102, iPnPatch[1].b102, iPnPatch[2].b102);
+    vec3 b201 = vec3(iPnPatch[0].b201, iPnPatch[1].b201, iPnPatch[2].b201);
+    vec3 b111 = vec3(iPnPatch[0].b111, iPnPatch[1].b111, iPnPatch[2].b111);
+
+    // extract control normals
+    vec3 n110 = normalize(vec3(iPnPatch[0].n110, iPnPatch[1].n110, iPnPatch[2].n110));
+    vec3 n011 = normalize(vec3(iPnPatch[0].n011, iPnPatch[1].n011, iPnPatch[2].n011));
+    vec3 n101 = normalize(vec3(iPnPatch[0].n101, iPnPatch[1].n101, iPnPatch[2].n101));
+
+    // compute texcoords
+    oTexCoord  = gl_TessCoord[2]*iTexCoord[0] + gl_TessCoord[0]*iTexCoord[1] + gl_TessCoord[1]*iTexCoord[2];
+
+    // normal
+    // Barycentric normal
+    vec3 barNormal = gl_TessCoord[2]*iNormal[0] + gl_TessCoord[0]*iNormal[1] + gl_TessCoord[1]*iNormal[2];
+    vec3 pnNormal  = iNormal[0]*uvwSquared[2] + iNormal[1]*uvwSquared[0] + iNormal[2]*uvwSquared[1]
+                   + n110*uvw[2]*uvw[0] + n011*uvw[0]*uvw[1]+ n101*uvw[2]*uvw[1];
+    oNormal = ubo.tessAlpha*pnNormal + (1.0-ubo.tessAlpha) * barNormal;
+
+    // compute interpolated pos
+    vec3 barPos = gl_TessCoord[2]*gl_in[0].gl_Position.xyz
+                + gl_TessCoord[0]*gl_in[1].gl_Position.xyz
+                + gl_TessCoord[1]*gl_in[2].gl_Position.xyz;
+
+    // save some computations
+    uvwSquared *= 3.0;
+
+    // compute PN position
+    vec3 pnPos  = gl_in[0].gl_Position.xyz*uvwCubed[2]
+                + gl_in[1].gl_Position.xyz*uvwCubed[0]
+                + gl_in[2].gl_Position.xyz*uvwCubed[1]
+                + b210*uvwSquared[2]*uvw[0]
+                + b120*uvwSquared[0]*uvw[2]
+                + b201*uvwSquared[2]*uvw[1]
+                + b021*uvwSquared[0]*uvw[1]
+                + b102*uvwSquared[1]*uvw[2]
+                + b012*uvwSquared[1]*uvw[0]
+                + b111*6.0*uvw[0]*uvw[1]*uvw[2];
+
+    // final position and normal
+    vec3 finalPos = (1.0-ubo.tessAlpha)*barPos + ubo.tessAlpha*pnPos;
+ gl_Position   = ubo.projection * ubo.model * vec4(finalPos,1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/tessellation/pntriangles.tese.spv b/external/Vulkan/data/shaders/glsl/tessellation/pntriangles.tese.spv
new file mode 100644
index 0000000000000000000000000000000000000000..330aa4e22e81cb87230f69e20a01b094f3f92e89
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/tessellation/pntriangles.tese.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/textoverlay/mesh.frag b/external/Vulkan/data/shaders/glsl/textoverlay/mesh.frag
new file mode 100644
index 0000000000000000000000000000000000000000..3cde63d13a03bab4a865a9012e26e6db3aeebf01
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/textoverlay/mesh.frag
@@ -0,0 +1,19 @@
+#version 450
+
+layout (location = 0) in vec3 inNormal;
+layout (location = 1) in vec2 inUV;
+layout (location = 2) in vec3 inViewVec;
+layout (location = 3) in vec3 inLightVec;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+	vec3 N = normalize(inNormal);
+	vec3 L = normalize(inLightVec);
+	vec3 V = normalize(inViewVec);
+	vec3 R = reflect(-L, N);
+	float diffuse = max(dot(N, L), 0.0);
+	float specular = pow(max(dot(R, V), 0.0), 1.0);
+	outFragColor = vec4(vec3(diffuse + specular) * vec3(0.25), 1.0);	
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/textoverlay/mesh.frag.spv b/external/Vulkan/data/shaders/glsl/textoverlay/mesh.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..1a61dc9db058cb0ef0098bb8e99787451ebd5702
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/textoverlay/mesh.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/textoverlay/mesh.vert b/external/Vulkan/data/shaders/glsl/textoverlay/mesh.vert
new file mode 100644
index 0000000000000000000000000000000000000000..8c9e6b37e0f2e2f14fd2e4c845383e7f7850b4db
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/textoverlay/mesh.vert
@@ -0,0 +1,35 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec3 inNormal;
+layout (location = 2) in vec2 inUV;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 model;
+	vec4 lightPos;
+} ubo;
+
+layout (location = 0) out vec3 outNormal;
+layout (location = 1) out vec2 outUV;
+layout (location = 2) out vec3 outViewVec;
+layout (location = 3) out vec3 outLightVec;
+
+out gl_PerVertex 
+{
+	vec4 gl_Position;   
+};
+
+void main() 
+{
+	outNormal = inNormal;
+	outUV = inUV;
+	gl_Position = ubo.projection * ubo.model * vec4(inPos.xyz, 1.0);
+	
+	vec4 pos = ubo.model * vec4(inPos, 1.0);
+	outNormal = mat3(transpose(inverse(ubo.model))) * normalize(inNormal);
+	vec3 lPos = mat3(ubo.model) * ubo.lightPos.xyz;
+	outLightVec = lPos - pos.xyz;
+	outViewVec = -pos.xyz;			
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/textoverlay/mesh.vert.spv b/external/Vulkan/data/shaders/glsl/textoverlay/mesh.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..9fc041e777b54cfe2bf4c87d42b01412e0714db2
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/textoverlay/mesh.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/textoverlay/text.frag b/external/Vulkan/data/shaders/glsl/textoverlay/text.frag
new file mode 100644
index 0000000000000000000000000000000000000000..90cad461212ac1df281daaa50d760b9d453c4ff5
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/textoverlay/text.frag
@@ -0,0 +1,13 @@
+#version 450 core
+
+layout (location = 0) in vec2 inUV;
+
+layout (binding = 0) uniform sampler2D samplerFont;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main(void)
+{
+	float color = texture(samplerFont, inUV).r;
+	outFragColor = vec4(color);
+}
diff --git a/external/Vulkan/data/shaders/glsl/textoverlay/text.frag.spv b/external/Vulkan/data/shaders/glsl/textoverlay/text.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..5ee7005168614fab5a80cfee9f8e9f3fd6549198
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/textoverlay/text.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/textoverlay/text.vert b/external/Vulkan/data/shaders/glsl/textoverlay/text.vert
new file mode 100644
index 0000000000000000000000000000000000000000..55b980658009d89f2037e31de20f119fb226452e
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/textoverlay/text.vert
@@ -0,0 +1,17 @@
+#version 450 core
+
+layout (location = 0) in vec2 inPos;
+layout (location = 1) in vec2 inUV;
+
+layout (location = 0) out vec2 outUV;
+
+out gl_PerVertex 
+{
+	vec4 gl_Position;   
+};
+
+void main(void)
+{
+	gl_Position = vec4(inPos, 0.0, 1.0);
+	outUV = inUV;
+}
diff --git a/external/Vulkan/data/shaders/glsl/textoverlay/text.vert.spv b/external/Vulkan/data/shaders/glsl/textoverlay/text.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..07b9000aa0d947dc1f908d13706152457059f77a
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/textoverlay/text.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/texture/texture.frag b/external/Vulkan/data/shaders/glsl/texture/texture.frag
new file mode 100644
index 0000000000000000000000000000000000000000..7f9b132a3f54a9178275ad90155ecf800fbcc788
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/texture/texture.frag
@@ -0,0 +1,25 @@
+#version 450
+
+layout (binding = 1) uniform sampler2D samplerColor;
+
+layout (location = 0) in vec2 inUV;
+layout (location = 1) in float inLodBias;
+layout (location = 2) in vec3 inNormal;
+layout (location = 3) in vec3 inViewVec;
+layout (location = 4) in vec3 inLightVec;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+	vec4 color = texture(samplerColor, inUV, inLodBias);
+
+	vec3 N = normalize(inNormal);
+	vec3 L = normalize(inLightVec);
+	vec3 V = normalize(inViewVec);
+	vec3 R = reflect(-L, N);
+	vec3 diffuse = max(dot(N, L), 0.0) * vec3(1.0);
+	float specular = pow(max(dot(R, V), 0.0), 16.0) * color.a;
+
+	outFragColor = vec4(diffuse * color.rgb + specular, 1.0);	
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/texture/texture.frag.spv b/external/Vulkan/data/shaders/glsl/texture/texture.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..971d3e82857b2c3986451e715e395faa950dd13d
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/texture/texture.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/texture/texture.vert b/external/Vulkan/data/shaders/glsl/texture/texture.vert
new file mode 100644
index 0000000000000000000000000000000000000000..cfad3023a5337d79622970dd6bc104d1e3caa7a1
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/texture/texture.vert
@@ -0,0 +1,41 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec2 inUV;
+layout (location = 2) in vec3 inNormal;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 model;
+	vec4 viewPos;
+	float lodBias;
+} ubo;
+
+layout (location = 0) out vec2 outUV;
+layout (location = 1) out float outLodBias;
+layout (location = 2) out vec3 outNormal;
+layout (location = 3) out vec3 outViewVec;
+layout (location = 4) out vec3 outLightVec;
+
+out gl_PerVertex 
+{
+    vec4 gl_Position;   
+};
+
+void main() 
+{
+	outUV = inUV;
+	outLodBias = ubo.lodBias;
+
+	vec3 worldPos = vec3(ubo.model * vec4(inPos, 1.0));
+
+	gl_Position = ubo.projection * ubo.model * vec4(inPos.xyz, 1.0);
+
+    vec4 pos = ubo.model * vec4(inPos, 1.0);
+	outNormal = mat3(inverse(transpose(ubo.model))) * inNormal;
+	vec3 lightPos = vec3(0.0);
+	vec3 lPos = mat3(ubo.model) * lightPos.xyz;
+    outLightVec = lPos - pos.xyz;
+    outViewVec = ubo.viewPos.xyz - pos.xyz;		
+}
diff --git a/external/Vulkan/data/shaders/glsl/texture/texture.vert.spv b/external/Vulkan/data/shaders/glsl/texture/texture.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..153cd5ddc93c9679f1bac2a523acbf0b333ec43e
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/texture/texture.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/texture3d/texture3d.frag b/external/Vulkan/data/shaders/glsl/texture3d/texture3d.frag
new file mode 100644
index 0000000000000000000000000000000000000000..bb0b81e11e5c8558742b01d657d4d522e9bdaa36
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/texture3d/texture3d.frag
@@ -0,0 +1,25 @@
+#version 450
+
+layout (binding = 1) uniform sampler3D samplerColor;
+
+layout (location = 0) in vec3 inUV;
+layout (location = 1) in float inLodBias;
+layout (location = 2) in vec3 inNormal;
+layout (location = 3) in vec3 inViewVec;
+layout (location = 4) in vec3 inLightVec;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+	vec4 color = texture(samplerColor, inUV);
+
+	vec3 N = normalize(inNormal);
+	vec3 L = normalize(inLightVec);
+	vec3 V = normalize(inViewVec);
+	vec3 R = reflect(-L, N);
+	vec3 diffuse = max(dot(N, L), 0.0) * vec3(1.0);
+	float specular = pow(max(dot(R, V), 0.0), 16.0) * color.r;
+
+	outFragColor = vec4(diffuse * color.r + specular, 1.0);	
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/texture3d/texture3d.frag.spv b/external/Vulkan/data/shaders/glsl/texture3d/texture3d.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..85811c96c67348ab94a229534a2e4f33826a30cb
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/texture3d/texture3d.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/texture3d/texture3d.vert b/external/Vulkan/data/shaders/glsl/texture3d/texture3d.vert
new file mode 100644
index 0000000000000000000000000000000000000000..7522edc4a24ab1eae79b9338684a96223d38f680
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/texture3d/texture3d.vert
@@ -0,0 +1,40 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec2 inUV;
+layout (location = 2) in vec3 inNormal;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 model;
+	vec4 viewPos;
+	float depth;
+} ubo;
+
+layout (location = 0) out vec3 outUV;
+layout (location = 1) out float outLodBias;
+layout (location = 2) out vec3 outNormal;
+layout (location = 3) out vec3 outViewVec;
+layout (location = 4) out vec3 outLightVec;
+
+out gl_PerVertex 
+{
+	vec4 gl_Position;   
+};
+
+void main() 
+{
+	outUV = vec3(inUV, ubo.depth);
+
+	vec3 worldPos = vec3(ubo.model * vec4(inPos, 1.0));
+
+	gl_Position = ubo.projection * ubo.model * vec4(inPos.xyz, 1.0);
+
+	vec4 pos = ubo.model * vec4(inPos, 1.0);
+	outNormal = mat3(inverse(transpose(ubo.model))) * inNormal;
+	vec3 lightPos = vec3(0.0);
+	vec3 lPos = mat3(ubo.model) * lightPos.xyz;
+	outLightVec = lPos - pos.xyz;
+	outViewVec = ubo.viewPos.xyz - pos.xyz;		
+}
diff --git a/external/Vulkan/data/shaders/glsl/texture3d/texture3d.vert.spv b/external/Vulkan/data/shaders/glsl/texture3d/texture3d.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..86f3bc0020b6389aa654ceb2827709169ca2726b
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/texture3d/texture3d.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/texturearray/instancing.frag b/external/Vulkan/data/shaders/glsl/texturearray/instancing.frag
new file mode 100644
index 0000000000000000000000000000000000000000..75b27b1b6fcf307f2efe3fbf9f8e7cbbc2699650
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/texturearray/instancing.frag
@@ -0,0 +1,12 @@
+#version 450
+
+layout (binding = 1) uniform sampler2DArray samplerArray;
+
+layout (location = 0) in vec3 inUV;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+	outFragColor = texture(samplerArray, inUV);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/texturearray/instancing.frag.spv b/external/Vulkan/data/shaders/glsl/texturearray/instancing.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..106300c6229edff98d6f38aa0e02d21783ffd236
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/texturearray/instancing.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/texturearray/instancing.vert b/external/Vulkan/data/shaders/glsl/texturearray/instancing.vert
new file mode 100644
index 0000000000000000000000000000000000000000..45570505e2f2c94bf23e8b76f454196aa26815c7
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/texturearray/instancing.vert
@@ -0,0 +1,26 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec2 inUV;
+
+struct Instance
+{
+	mat4 model;
+	vec4 arrayIndex;
+};
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 view;
+	Instance instance[8];
+} ubo;
+
+layout (location = 0) out vec3 outUV;
+
+void main() 
+{
+	outUV = vec3(inUV, ubo.instance[gl_InstanceIndex].arrayIndex.x);
+	mat4 modelView = ubo.view * ubo.instance[gl_InstanceIndex].model;
+	gl_Position = ubo.projection * modelView * vec4(inPos, 1.0);
+}
diff --git a/external/Vulkan/data/shaders/glsl/texturearray/instancing.vert.spv b/external/Vulkan/data/shaders/glsl/texturearray/instancing.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..363adc9e18a3c94a3022f90410ffb10d727cd927
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/texturearray/instancing.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/texturecubemap/reflect.frag b/external/Vulkan/data/shaders/glsl/texturecubemap/reflect.frag
new file mode 100644
index 0000000000000000000000000000000000000000..cab0153743a39c6b77ba7e36c9ad0f5c5cd000e4
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/texturecubemap/reflect.frag
@@ -0,0 +1,39 @@
+#version 450
+
+layout (binding = 1) uniform samplerCube samplerColor;
+
+layout (binding = 0) uniform UBO
+{
+	mat4 projection;
+	mat4 model;
+	mat4 invModel;
+	float lodBias;
+} ubo;
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec3 inNormal;
+layout (location = 2) in vec3 inViewVec;
+layout (location = 3) in vec3 inLightVec;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main()
+{
+	vec3 cI = normalize (inPos);
+	vec3 cR = reflect (cI, normalize(inNormal));
+
+	cR = vec3(ubo.invModel * vec4(cR, 0.0));
+	// Convert cubemap coordinates into Vulkan coordinate space
+	cR.xy *= -1.0;
+
+	vec4 color = texture(samplerColor, cR, ubo.lodBias);
+
+	vec3 N = normalize(inNormal);
+	vec3 L = normalize(inLightVec);
+	vec3 V = normalize(inViewVec);
+	vec3 R = reflect(-L, N);
+	vec3 ambient = vec3(0.5) * color.rgb;
+	vec3 diffuse = max(dot(N, L), 0.0) * vec3(1.0);
+	vec3 specular = pow(max(dot(R, V), 0.0), 16.0) * vec3(0.5);
+	outFragColor = vec4(ambient + diffuse * color.rgb + specular, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/texturecubemap/reflect.frag.spv b/external/Vulkan/data/shaders/glsl/texturecubemap/reflect.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..40f10cee94c84faa73b91b1801e5fbdc7a6fb462
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/texturecubemap/reflect.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/texturecubemap/reflect.vert b/external/Vulkan/data/shaders/glsl/texturecubemap/reflect.vert
new file mode 100644
index 0000000000000000000000000000000000000000..fc2d84d16117439f281bf513f668857bc504c144
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/texturecubemap/reflect.vert
@@ -0,0 +1,29 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec3 inNormal;
+
+layout (binding = 0) uniform UBO
+{
+	mat4 projection;
+	mat4 model;
+	mat4 invModel;
+	float lodBias;
+} ubo;
+
+layout (location = 0) out vec3 outPos;
+layout (location = 1) out vec3 outNormal;
+layout (location = 2) out vec3 outViewVec;
+layout (location = 3) out vec3 outLightVec;
+
+void main()
+{
+	gl_Position = ubo.projection * ubo.model * vec4(inPos.xyz, 1.0);
+
+	outPos = vec3(ubo.model * vec4(inPos, 1.0));
+	outNormal = mat3(ubo.model) * inNormal;
+
+	vec3 lightPos = vec3(0.0f, -5.0f, 5.0f);
+	outLightVec = lightPos.xyz - outPos.xyz;
+	outViewVec = -outPos.xyz;
+}
diff --git a/external/Vulkan/data/shaders/glsl/texturecubemap/reflect.vert.spv b/external/Vulkan/data/shaders/glsl/texturecubemap/reflect.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..1ac327aae6e7c91da1815b236e35c387878d452b
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/texturecubemap/reflect.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/texturecubemap/skybox.frag b/external/Vulkan/data/shaders/glsl/texturecubemap/skybox.frag
new file mode 100644
index 0000000000000000000000000000000000000000..56b8f42cbb23fec3cc0ce8bb731ef604bb59b61d
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/texturecubemap/skybox.frag
@@ -0,0 +1,12 @@
+#version 450
+
+layout (binding = 1) uniform samplerCube samplerCubeMap;
+
+layout (location = 0) in vec3 inUVW;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+	outFragColor = texture(samplerCubeMap, inUVW);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/texturecubemap/skybox.frag.spv b/external/Vulkan/data/shaders/glsl/texturecubemap/skybox.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..87e4fe98548e84d74720c8c0fd41d79145e3403c
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/texturecubemap/skybox.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/texturecubemap/skybox.vert b/external/Vulkan/data/shaders/glsl/texturecubemap/skybox.vert
new file mode 100644
index 0000000000000000000000000000000000000000..d1f8887defccdbd65bf2ff66c324d9fcade5c2a6
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/texturecubemap/skybox.vert
@@ -0,0 +1,19 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 model;
+} ubo;
+
+layout (location = 0) out vec3 outUVW;
+
+void main() 
+{
+	outUVW = inPos;
+	// Convert cubemap coordinates into Vulkan coordinate space
+	outUVW.xy *= -1.0;
+	gl_Position = ubo.projection * ubo.model * vec4(inPos.xyz, 1.0);
+}
diff --git a/external/Vulkan/data/shaders/glsl/texturecubemap/skybox.vert.spv b/external/Vulkan/data/shaders/glsl/texturecubemap/skybox.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..fc6b9862176375def6964fe8214275107ac1f964
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/texturecubemap/skybox.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/texturecubemaparray/reflect.frag b/external/Vulkan/data/shaders/glsl/texturecubemaparray/reflect.frag
new file mode 100644
index 0000000000000000000000000000000000000000..8982b77db0bf5acaa398f0738ccde11d8b254b2b
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/texturecubemaparray/reflect.frag
@@ -0,0 +1,39 @@
+#version 450
+
+layout (binding = 1) uniform samplerCubeArray samplerCubeMapArray;
+
+layout (binding = 0) uniform UBO
+{
+	mat4 projection;
+	mat4 model;
+	mat4 invModel;
+	float lodBias;
+	int cubeMapIndex;
+} ubo;
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec3 inNormal;
+layout (location = 2) in vec3 inViewVec;
+layout (location = 3) in vec3 inLightVec;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main()
+{
+	vec3 cI = normalize (inPos);
+	vec3 cR = reflect (cI, normalize(inNormal));
+
+	cR = vec3(ubo.invModel * vec4(cR, 0.0));
+	cR.yz *= -1.0;
+
+	vec4 color = textureLod(samplerCubeMapArray, vec4(cR, ubo.cubeMapIndex), ubo.lodBias);
+
+	vec3 N = normalize(inNormal);
+	vec3 L = normalize(inLightVec);
+	vec3 V = normalize(inViewVec);
+	vec3 R = reflect(-L, N);
+	vec3 ambient = vec3(0.5) * color.rgb;
+	vec3 diffuse = max(dot(N, L), 0.0) * vec3(1.0);
+	vec3 specular = pow(max(dot(R, V), 0.0), 16.0) * vec3(0.5);
+	outFragColor = vec4(ambient + diffuse * color.rgb + specular, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/texturecubemaparray/reflect.frag.spv b/external/Vulkan/data/shaders/glsl/texturecubemaparray/reflect.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..5c3fa1bee268db25556858ce8994f50dd51f1322
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/texturecubemaparray/reflect.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/texturecubemaparray/reflect.vert b/external/Vulkan/data/shaders/glsl/texturecubemaparray/reflect.vert
new file mode 100644
index 0000000000000000000000000000000000000000..135ed298469db115786dca471ddf42cd7f629d5f
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/texturecubemaparray/reflect.vert
@@ -0,0 +1,30 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec3 inNormal;
+
+layout (binding = 0) uniform UBO
+{
+	mat4 projection;
+	mat4 model;
+	mat4 invModel;
+	float lodBias;
+	int cubeMapIndex;
+} ubo;
+
+layout (location = 0) out vec3 outPos;
+layout (location = 1) out vec3 outNormal;
+layout (location = 2) out vec3 outViewVec;
+layout (location = 3) out vec3 outLightVec;
+
+void main()
+{
+	gl_Position = ubo.projection * ubo.model * vec4(inPos.xyz, 1.0);
+
+	outPos = vec3(ubo.model * vec4(inPos, 1.0));
+	outNormal = mat3(ubo.model) * inNormal;
+
+	vec3 lightPos = vec3(0.0f, -5.0f, 5.0f);
+	outLightVec = lightPos.xyz - outPos.xyz;
+	outViewVec = -outPos.xyz;
+}
diff --git a/external/Vulkan/data/shaders/glsl/texturecubemaparray/reflect.vert.spv b/external/Vulkan/data/shaders/glsl/texturecubemaparray/reflect.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..6a75efa111942948eac1c68ea9f19302b66cd24a
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/texturecubemaparray/reflect.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/texturecubemaparray/skybox.frag b/external/Vulkan/data/shaders/glsl/texturecubemaparray/skybox.frag
new file mode 100644
index 0000000000000000000000000000000000000000..b43bb0d20f87c68543e89c8aeecaeeb7de77fc9b
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/texturecubemaparray/skybox.frag
@@ -0,0 +1,21 @@
+#version 450
+
+layout (binding = 1) uniform samplerCubeArray samplerCubeMapArray;
+
+layout (binding = 0) uniform UBO
+{
+	mat4 projection;
+	mat4 model;
+	mat4 invModel;
+	float lodBias;
+	int cubeMapIndex;
+} ubo;
+
+layout (location = 0) in vec3 inUVW;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+	outFragColor = textureLod(samplerCubeMapArray, vec4(inUVW, ubo.cubeMapIndex), ubo.lodBias);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/texturecubemaparray/skybox.frag.spv b/external/Vulkan/data/shaders/glsl/texturecubemaparray/skybox.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..35ec681bc02503d7951428b94a329d63baf78007
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/texturecubemaparray/skybox.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/texturecubemaparray/skybox.vert b/external/Vulkan/data/shaders/glsl/texturecubemaparray/skybox.vert
new file mode 100644
index 0000000000000000000000000000000000000000..b4df167b2aad579a367a8fc5742f28567946f043
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/texturecubemaparray/skybox.vert
@@ -0,0 +1,21 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 model;
+	mat4 invModel;
+	float lodBias;
+	int cubeMapIndex;
+} ubo;
+
+layout (location = 0) out vec3 outUVW;
+
+void main() 
+{
+	outUVW = inPos;
+	outUVW.yz *= -1.0f;
+	gl_Position = ubo.projection * ubo.model * vec4(inPos.xyz, 1.0);
+}
diff --git a/external/Vulkan/data/shaders/glsl/texturecubemaparray/skybox.vert.spv b/external/Vulkan/data/shaders/glsl/texturecubemaparray/skybox.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..584d14367a6dba46c8b9c80aa07a719eae6b4cbb
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/texturecubemaparray/skybox.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/texturemipmapgen/texture.frag b/external/Vulkan/data/shaders/glsl/texturemipmapgen/texture.frag
new file mode 100644
index 0000000000000000000000000000000000000000..edb92b59f1941af17bc24a488e4367d5ed54c2e6
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/texturemipmapgen/texture.frag
@@ -0,0 +1,26 @@
+#version 450
+
+layout (set = 0, binding = 1) uniform texture2D textureColor;
+layout (set = 0, binding = 2) uniform sampler samplers[3];
+
+layout (location = 0) in vec2 inUV;
+layout (location = 1) in float inLodBias;
+layout (location = 2) flat in int inSamplerIndex;
+layout (location = 3) in vec3 inNormal;
+layout (location = 4) in vec3 inViewVec;
+layout (location = 5) in vec3 inLightVec;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+	vec4 color = texture(sampler2D(textureColor, samplers[inSamplerIndex]), inUV, inLodBias);
+
+	vec3 N = normalize(inNormal);
+	vec3 L = normalize(inLightVec);
+	vec3 V = normalize(inViewVec);
+	vec3 R = reflect(L, N);
+	vec3 diffuse = max(dot(N, L), 0.65) * vec3(1.0);
+	float specular = pow(max(dot(R, V), 0.0), 16.0) * color.a;
+	outFragColor = vec4(diffuse * color.rgb + specular, 1.0);	
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/texturemipmapgen/texture.frag.spv b/external/Vulkan/data/shaders/glsl/texturemipmapgen/texture.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..52db9580f46fba7901ec2b8da5a360e6b6e9add1
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/texturemipmapgen/texture.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/texturemipmapgen/texture.vert b/external/Vulkan/data/shaders/glsl/texturemipmapgen/texture.vert
new file mode 100644
index 0000000000000000000000000000000000000000..5577088022c67ba79dea7be8cd27318272d90d61
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/texturemipmapgen/texture.vert
@@ -0,0 +1,43 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec2 inUV;
+layout (location = 2) in vec3 inNormal;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 view;
+	mat4 model;
+	vec4 viewPos;
+	float lodBias;
+	int samplerIndex;
+} ubo;
+
+layout (location = 0) out vec2 outUV;
+layout (location = 1) out float outLodBias;
+layout (location = 2) flat out int outSamplerIndex;
+layout (location = 3) out vec3 outNormal;
+layout (location = 4) out vec3 outViewVec;
+layout (location = 5) out vec3 outLightVec;
+
+out gl_PerVertex 
+{
+	vec4 gl_Position;   
+};
+
+void main() 
+{
+	outUV = inUV * vec2(2.0, 1.0);
+	outLodBias = ubo.lodBias;
+	outSamplerIndex = ubo.samplerIndex;
+
+	vec3 worldPos = vec3(ubo.model * vec4(inPos, 1.0));
+
+	gl_Position = ubo.projection * ubo.view * ubo.model * vec4(inPos.xyz, 1.0);
+
+	outNormal = mat3(inverse(transpose(ubo.model))) * inNormal;
+	vec3 lightPos = vec3(-30.0, 0.0, 0.0);
+	outLightVec = worldPos - lightPos;
+	outViewVec = ubo.viewPos.xyz - worldPos;		
+}
diff --git a/external/Vulkan/data/shaders/glsl/texturemipmapgen/texture.vert.spv b/external/Vulkan/data/shaders/glsl/texturemipmapgen/texture.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..234969ce0361e77ee4d97ce680866700066305d1
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/texturemipmapgen/texture.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/texturesparseresidency/sparseresidency.frag b/external/Vulkan/data/shaders/glsl/texturesparseresidency/sparseresidency.frag
new file mode 100644
index 0000000000000000000000000000000000000000..a55916b826915af07d5fa10ccd15b9ff5bc975d5
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/texturesparseresidency/sparseresidency.frag
@@ -0,0 +1,39 @@
+#version 450
+
+#extension GL_ARB_sparse_texture2 : enable
+#extension GL_ARB_sparse_texture_clamp : enable
+
+layout (binding = 1) uniform sampler2D samplerColor;
+
+layout (location = 0) in vec2 inUV;
+layout (location = 1) in float inLodBias;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+	vec4 color = vec4(0.0);
+
+	// Get residency code for current texel
+	int residencyCode = sparseTextureARB(samplerColor, inUV, color, inLodBias);
+
+	// Fetch sparse until we get a valid texel
+	/*
+	float minLod = 1.0;
+	while (!sparseTexelsResidentARB(residencyCode)) 
+	{
+		residencyCode = sparseTextureClampARB(samplerColor, inUV, minLod, color);
+		minLod += 1.0f;
+	}
+	*/
+
+	// Check if texel is resident
+	bool texelResident = sparseTexelsResidentARB(residencyCode);
+
+	if (!texelResident)
+	{
+		color = vec4(0.0, 0.0, 0.0, 0.0);
+	}
+
+	outFragColor = color;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/texturesparseresidency/sparseresidency.frag.spv b/external/Vulkan/data/shaders/glsl/texturesparseresidency/sparseresidency.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..9f661cbe18011a29963e38c054600146478d5aa9
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/texturesparseresidency/sparseresidency.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/texturesparseresidency/sparseresidency.vert b/external/Vulkan/data/shaders/glsl/texturesparseresidency/sparseresidency.vert
new file mode 100644
index 0000000000000000000000000000000000000000..e613226f499fab4d2c3adbc678210996cf15b1c3
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/texturesparseresidency/sparseresidency.vert
@@ -0,0 +1,23 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec3 inNormal;
+layout (location = 2) in vec2 inUV;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 model;
+	vec4 viewPos;
+	float lodBias;
+} ubo;
+
+layout (location = 0) out vec2 outUV;
+layout (location = 1) out float outLodBias;
+
+void main() 
+{
+	outUV = inUV;
+	outLodBias = ubo.lodBias;
+	gl_Position = ubo.projection * ubo.model * vec4(inPos.xyz, 1.0);
+}
diff --git a/external/Vulkan/data/shaders/glsl/texturesparseresidency/sparseresidency.vert.spv b/external/Vulkan/data/shaders/glsl/texturesparseresidency/sparseresidency.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..328462a05356aa287df8e9585f1b26220e8b77b2
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/texturesparseresidency/sparseresidency.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/triangle/triangle.frag b/external/Vulkan/data/shaders/glsl/triangle/triangle.frag
new file mode 100644
index 0000000000000000000000000000000000000000..f3e513b909da2e2c1ce1d64e8f4c39c79172696a
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/triangle/triangle.frag
@@ -0,0 +1,10 @@
+#version 450
+
+layout (location = 0) in vec3 inColor;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+  outFragColor = vec4(inColor, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/triangle/triangle.frag.spv b/external/Vulkan/data/shaders/glsl/triangle/triangle.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..f67f201135c138f22f9bfb7bd9b0a8e1c9227e92
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/triangle/triangle.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/triangle/triangle.vert b/external/Vulkan/data/shaders/glsl/triangle/triangle.vert
new file mode 100644
index 0000000000000000000000000000000000000000..1b14aabc556406c7943430c5fc0708d6ac5c2ba5
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/triangle/triangle.vert
@@ -0,0 +1,25 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec3 inColor;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projectionMatrix;
+	mat4 modelMatrix;
+	mat4 viewMatrix;
+} ubo;
+
+layout (location = 0) out vec3 outColor;
+
+out gl_PerVertex 
+{
+    vec4 gl_Position;   
+};
+
+
+void main() 
+{
+	outColor = inColor;
+	gl_Position = ubo.projectionMatrix * ubo.viewMatrix * ubo.modelMatrix * vec4(inPos.xyz, 1.0);
+}
diff --git a/external/Vulkan/data/shaders/glsl/triangle/triangle.vert.spv b/external/Vulkan/data/shaders/glsl/triangle/triangle.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..552792d175683ea3e8b91a39a1daa2e90220b22d
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/triangle/triangle.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/variablerateshading/scene.frag b/external/Vulkan/data/shaders/glsl/variablerateshading/scene.frag
new file mode 100644
index 0000000000000000000000000000000000000000..1236713a9347668431a0bba7dec779a89ebc4212
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/variablerateshading/scene.frag
@@ -0,0 +1,84 @@
+#version 450
+
+#extension GL_NV_shading_rate_image : require
+
+layout (set = 1, binding = 0) uniform sampler2D samplerColorMap;
+layout (set = 1, binding = 1) uniform sampler2D samplerNormalMap;
+
+layout (location = 0) in vec3 inNormal;
+layout (location = 1) in vec3 inColor;
+layout (location = 2) in vec2 inUV;
+layout (location = 3) in vec3 inViewVec;
+layout (location = 4) in vec3 inLightVec;
+layout (location = 5) in vec4 inTangent;
+
+layout (set = 0, binding = 0) uniform UBOScene 
+{
+	mat4 projection;
+	mat4 view;
+	mat4 model;
+	vec4 lightPos;
+	vec4 viewPos;
+	int colorShadingRates;
+} uboScene;
+
+layout (location = 0) out vec4 outFragColor;
+
+layout (constant_id = 0) const bool ALPHA_MASK = false;
+layout (constant_id = 1) const float ALPHA_MASK_CUTOFF = 0.0f;
+
+void main() 
+{
+	vec4 color = texture(samplerColorMap, inUV) * vec4(inColor, 1.0);
+
+	if (ALPHA_MASK) {
+		if (color.a < ALPHA_MASK_CUTOFF) {
+			discard;
+		}
+	}
+
+	vec3 N = normalize(inNormal);
+	vec3 T = normalize(inTangent.xyz);
+	vec3 B = cross(inNormal, inTangent.xyz) * inTangent.w;
+	mat3 TBN = mat3(T, B, N);
+	N = TBN * normalize(texture(samplerNormalMap, inUV).xyz * 2.0 - vec3(1.0));
+
+	const float ambient = 0.25;
+	vec3 L = normalize(inLightVec);
+	vec3 V = normalize(inViewVec);
+	vec3 R = reflect(-L, N);
+	vec3 diffuse = max(dot(N, L), ambient).rrr;
+	float specular = pow(max(dot(R, V), 0.0), 32.0);
+	outFragColor = vec4(diffuse * color.rgb + specular, color.a);
+
+	if (uboScene.colorShadingRates == 1) {
+		if (gl_FragmentSizeNV.x == 1 && gl_FragmentSizeNV.y == 1) {
+			outFragColor.rgb *= vec3(0.0, 0.8, 0.4);
+			return;
+		}
+		if (gl_FragmentSizeNV.x == 2 && gl_FragmentSizeNV.y == 1) {
+			outFragColor.rgb *= vec3(0.2, 0.6, 1.0);
+			return;
+		}
+		if (gl_FragmentSizeNV.x == 1 && gl_FragmentSizeNV.y == 2) {
+			outFragColor.rgb *= vec3(0.0, 0.4, 0.8);
+			return;
+		}
+		if (gl_FragmentSizeNV.x == 2 && gl_FragmentSizeNV.y == 2) {
+			outFragColor.rgb *= vec3(1.0, 1.0, 0.2);
+			return;
+		}
+		if (gl_FragmentSizeNV.x == 4 && gl_FragmentSizeNV.y == 2) {
+			outFragColor.rgb *= vec3(0.8, 0.8, 0.0);
+			return;
+		}
+		if (gl_FragmentSizeNV.x == 2 && gl_FragmentSizeNV.y == 4) {
+			outFragColor.rgb *= vec3(1.0, 0.4, 0.2);
+			return;
+		}
+		if (gl_FragmentSizeNV.x == 4 && gl_FragmentSizeNV.y == 4) {
+			outFragColor.rgb *= vec3(0.8, 0.0, 0.0);
+			return;
+		}
+	}
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/variablerateshading/scene.frag.spv b/external/Vulkan/data/shaders/glsl/variablerateshading/scene.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..bc40abeee6d32c4bae067c9f1a21e329317894d6
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/variablerateshading/scene.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/variablerateshading/scene.vert b/external/Vulkan/data/shaders/glsl/variablerateshading/scene.vert
new file mode 100644
index 0000000000000000000000000000000000000000..bf4e03ed31044e6c58e26f2b638dcad369c1f881
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/variablerateshading/scene.vert
@@ -0,0 +1,38 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec3 inNormal;
+layout (location = 2) in vec2 inUV;
+layout (location = 3) in vec3 inColor;
+layout (location = 4) in vec4 inTangent;
+
+layout (set = 0, binding = 0) uniform UBOScene 
+{
+	mat4 projection;
+	mat4 view;
+	mat4 model;
+	vec4 lightPos;
+	vec4 viewPos;
+	int colorShadingRates;
+} uboScene;
+
+layout (location = 0) out vec3 outNormal;
+layout (location = 1) out vec3 outColor;
+layout (location = 2) out vec2 outUV;
+layout (location = 3) out vec3 outViewVec;
+layout (location = 4) out vec3 outLightVec;
+layout (location = 5) out vec4 outTangent;
+
+void main() 
+{
+	outNormal = inNormal;
+	outColor = inColor;
+	outUV = inUV;
+	outTangent = inTangent;
+	gl_Position = uboScene.projection * uboScene.view * uboScene.model * vec4(inPos.xyz, 1.0);
+	
+	outNormal = mat3(uboScene.model) * inNormal;
+	vec4 pos = uboScene.model * vec4(inPos, 1.0);
+	outLightVec = uboScene.lightPos.xyz - pos.xyz;
+	outViewVec = uboScene.viewPos.xyz - pos.xyz;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/variablerateshading/scene.vert.spv b/external/Vulkan/data/shaders/glsl/variablerateshading/scene.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..d5c2011b62a8eca60798b357479ec6c3ead204bb
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/variablerateshading/scene.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/viewportarray/multiview.geom b/external/Vulkan/data/shaders/glsl/viewportarray/multiview.geom
new file mode 100644
index 0000000000000000000000000000000000000000..c517d06997722c47f2623dce7ed7636dce4f5b64
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/viewportarray/multiview.geom
@@ -0,0 +1,45 @@
+#version 450
+
+#extension GL_ARB_viewport_array : enable
+
+layout (triangles, invocations = 2) in;
+layout (triangle_strip, max_vertices = 3) out;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection[2];
+	mat4 modelview[2];
+	vec4 lightPos;
+} ubo;
+
+layout (location = 0) in vec3 inNormal[];
+layout (location = 1) in vec3 inColor[];
+
+layout (location = 0) out vec3 outNormal;
+layout (location = 1) out vec3 outColor;
+layout (location = 2) out vec3 outViewVec;
+layout (location = 3) out vec3 outLightVec;
+
+void main(void)
+{	
+	for(int i = 0; i < gl_in.length(); i++)
+	{
+		outNormal = mat3(ubo.modelview[gl_InvocationID]) * inNormal[i];
+		outColor = inColor[i];
+
+		vec4 pos = gl_in[i].gl_Position;
+		vec4 worldPos = (ubo.modelview[gl_InvocationID] * pos);
+		
+		vec3 lPos = vec3(ubo.modelview[gl_InvocationID]  * ubo.lightPos);
+		outLightVec = lPos - worldPos.xyz;
+		outViewVec = -worldPos.xyz;	
+	
+		gl_Position = ubo.projection[gl_InvocationID] * worldPos;
+
+		// Set the viewport index that the vertex will be emitted to
+		gl_ViewportIndex = gl_InvocationID;
+      gl_PrimitiveID = gl_PrimitiveIDIn;
+		EmitVertex();
+	}
+	EndPrimitive();
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/viewportarray/multiview.geom.spv b/external/Vulkan/data/shaders/glsl/viewportarray/multiview.geom.spv
new file mode 100644
index 0000000000000000000000000000000000000000..64fb39b1e0ac483c6c986d43990298d3c36ac0f3
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/viewportarray/multiview.geom.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/viewportarray/scene.frag b/external/Vulkan/data/shaders/glsl/viewportarray/scene.frag
new file mode 100644
index 0000000000000000000000000000000000000000..94d7f9a3aa3b03a564107b4d6b21f50bd546e2a7
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/viewportarray/scene.frag
@@ -0,0 +1,20 @@
+#version 450
+
+layout (location = 0) in vec3 inNormal;
+layout (location = 1) in vec3 inColor;
+layout (location = 2) in vec3 inViewVec;
+layout (location = 3) in vec3 inLightVec;
+
+layout (location = 0) out vec4 outColor;
+
+void main() 
+{
+	vec3 N = normalize(inNormal);
+	vec3 L = normalize(inLightVec);
+	vec3 V = normalize(inViewVec);
+	vec3 R = reflect(-L, N);
+	vec3 ambient = vec3(0.1);
+	vec3 diffuse = max(dot(N, L), 0.0) * vec3(1.0);
+	vec3 specular = pow(max(dot(R, V), 0.0), 16.0) * vec3(0.75);
+	outColor = vec4((ambient + diffuse) * inColor.rgb + specular, 1.0);		
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/viewportarray/scene.frag.spv b/external/Vulkan/data/shaders/glsl/viewportarray/scene.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..1eae642c5f92b14ea02a9d31a97ada9064663ffc
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/viewportarray/scene.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/viewportarray/scene.vert b/external/Vulkan/data/shaders/glsl/viewportarray/scene.vert
new file mode 100644
index 0000000000000000000000000000000000000000..5231157ea5592601e9080ef34b9cb856b9d47c84
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/viewportarray/scene.vert
@@ -0,0 +1,15 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+layout (location = 1) in vec3 inNormal;
+layout (location = 2) in vec3 inColor;
+
+layout (location = 0) out vec3 outNormal;
+layout (location = 1) out vec3 outColor;
+
+void main() 
+{
+	outColor = inColor;
+	outNormal = inNormal;
+	gl_Position = vec4(inPos.xyz, 1.0);	
+}
diff --git a/external/Vulkan/data/shaders/glsl/viewportarray/scene.vert.spv b/external/Vulkan/data/shaders/glsl/viewportarray/scene.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..379e65008e2f8ff5729d0e13cce4a8dafd7dd8f7
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/viewportarray/scene.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/vulkanscene/logo.frag b/external/Vulkan/data/shaders/glsl/vulkanscene/logo.frag
new file mode 100644
index 0000000000000000000000000000000000000000..3d013b9f4f6077ec180f550a80ca1e91287fa1dd
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/vulkanscene/logo.frag
@@ -0,0 +1,22 @@
+#version 450
+
+layout (location = 0) in vec2 inUV;
+layout (location = 1) in vec3 inNormal;
+layout (location = 2) in vec3 inColor;
+layout (location = 3) in vec3 inEyePos;
+layout (location = 4) in vec3 inLightVec;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+  vec3 Eye = normalize(-inEyePos);
+  vec3 Reflected = normalize(reflect(-inLightVec, inNormal));
+
+  vec4 diff = vec4(inColor, 1.0) * max(dot(inNormal, inLightVec), 0.0);
+  float shininess = 0.0;
+  vec4 spec = vec4(1.0, 1.0, 1.0, 1.0) * pow(max(dot(Reflected, Eye), 0.0), 2.5) * shininess;
+
+  outFragColor = diff + spec;
+  outFragColor.a = 1.0; 
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/vulkanscene/logo.frag.spv b/external/Vulkan/data/shaders/glsl/vulkanscene/logo.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..94bf9b8e3cdfa1611c48d01450297bf73e4d781f
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/vulkanscene/logo.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/vulkanscene/logo.vert b/external/Vulkan/data/shaders/glsl/vulkanscene/logo.vert
new file mode 100644
index 0000000000000000000000000000000000000000..e5c9ddf806cc7f1bdf3f543140cd8e902ad2a423
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/vulkanscene/logo.vert
@@ -0,0 +1,34 @@
+#version 450
+
+layout (location = 0) in vec4 inPos;
+layout (location = 1) in vec3 inNormal;
+layout (location = 2) in vec2 inTexCoord;
+layout (location = 3) in vec3 inColor;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 model;
+	mat4 normal;
+	mat4 view;
+	vec3 lightpos;
+} ubo;
+
+layout (location = 0) out vec2 outUV;
+layout (location = 1) out vec3 outNormal;
+layout (location = 2) out vec3 outColor;
+layout (location = 3) out vec3 outEyePos;
+layout (location = 4) out vec3 outLightVec;
+
+void main() 
+{
+	mat4 modelView = ubo.view * ubo.model;
+	vec4 pos = modelView * inPos;
+	outUV = inTexCoord.st;
+	outNormal = normalize(mat3(ubo.normal) * inNormal);
+	outColor = inColor;
+	gl_Position = ubo.projection * pos;
+	outEyePos = vec3(modelView * pos);
+	vec4 lightPos = vec4(1.0, 2.0, 0.0, 1.0) * modelView;
+	outLightVec = normalize(lightPos.xyz - outEyePos);
+}
diff --git a/external/Vulkan/data/shaders/glsl/vulkanscene/logo.vert.spv b/external/Vulkan/data/shaders/glsl/vulkanscene/logo.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..e6183abfc9596c410e73826131d9564467d11aea
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/vulkanscene/logo.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/vulkanscene/mesh.frag b/external/Vulkan/data/shaders/glsl/vulkanscene/mesh.frag
new file mode 100644
index 0000000000000000000000000000000000000000..b418a033280625161eb7092ffbb282767d482f4b
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/vulkanscene/mesh.frag
@@ -0,0 +1,44 @@
+#version 450
+
+layout (binding = 1) uniform sampler2D tex;
+
+layout (location = 0) in vec2 inUV;
+layout (location = 1) in vec3 inNormal;
+layout (location = 2) in vec3 inColor;
+layout (location = 3) in vec3 inEyePos;
+layout (location = 4) in vec3 inLightVec;
+
+layout (location = 0) out vec4 outFragColor;
+
+float specpart(vec3 L, vec3 N, vec3 H)
+{
+	if (dot(N, L) > 0.0)
+	{
+		return pow(clamp(dot(H, N), 0.0, 1.0), 64.0);
+	}
+	return 0.0;
+}
+
+void main() 
+{
+	vec3 Eye = normalize(-inEyePos);
+	vec3 Reflected = normalize(reflect(-inLightVec, inNormal)); 
+
+	vec3 halfVec = normalize(inLightVec + inEyePos);
+	float diff = clamp(dot(inLightVec, inNormal), 0.0, 1.0);
+	float spec = specpart(inLightVec, inNormal, halfVec);
+	float intensity = 0.1 + diff + spec;
+ 
+	vec4 IAmbient = vec4(0.2, 0.2, 0.2, 1.0);
+	vec4 IDiffuse = vec4(0.5, 0.5, 0.5, 0.5) * max(dot(inNormal, inLightVec), 0.0);
+	float shininess = 0.75;
+	vec4 ISpecular = vec4(0.5, 0.5, 0.5, 1.0) * pow(max(dot(Reflected, Eye), 0.0), 2.0) * shininess; 
+
+	outFragColor = vec4((IAmbient + IDiffuse) * vec4(inColor, 1.0) + ISpecular);
+ 
+	// Some manual saturation
+	if (intensity > 0.95)
+		outFragColor *= 2.25;
+	if (intensity < 0.15)
+		outFragColor = vec4(0.1);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/vulkanscene/mesh.frag.spv b/external/Vulkan/data/shaders/glsl/vulkanscene/mesh.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..7ed6d8040d854aeb8db833666412605c72c050ad
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/vulkanscene/mesh.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/vulkanscene/mesh.vert b/external/Vulkan/data/shaders/glsl/vulkanscene/mesh.vert
new file mode 100644
index 0000000000000000000000000000000000000000..80bdf2996fbf967d03d55854807c918ae057897b
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/vulkanscene/mesh.vert
@@ -0,0 +1,34 @@
+#version 450
+
+layout (location = 0) in vec4 inPos;
+layout (location = 1) in vec3 inNormal;
+layout (location = 2) in vec2 inTexCoord;
+layout (location = 3) in vec3 inColor;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 model;
+	mat4 normal;
+	mat4 view;
+	vec3 lightpos;
+} ubo;
+
+layout (location = 0) out vec2 outUV;
+layout (location = 1) out vec3 outNormal;
+layout (location = 2) out vec3 outColor;
+layout (location = 3) out vec3 outEyePos;
+layout (location = 4) out vec3 outLightVec;
+
+void main() 
+{
+	outUV = inTexCoord.st;
+	outNormal = normalize(mat3(ubo.normal) * inNormal);
+	outColor = inColor;
+	mat4 modelView = ubo.view * ubo.model;
+	vec4 pos = modelView * inPos;	
+	gl_Position = ubo.projection * pos;
+	outEyePos = vec3(modelView * pos);
+	vec4 lightPos = vec4(ubo.lightpos, 1.0) * modelView;
+	outLightVec = normalize(lightPos.xyz - outEyePos);
+}
diff --git a/external/Vulkan/data/shaders/glsl/vulkanscene/mesh.vert.spv b/external/Vulkan/data/shaders/glsl/vulkanscene/mesh.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..34457bcf7eba8509574fc9deb107a2a91d5d133c
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/vulkanscene/mesh.vert.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/vulkanscene/skybox.frag b/external/Vulkan/data/shaders/glsl/vulkanscene/skybox.frag
new file mode 100644
index 0000000000000000000000000000000000000000..56b8f42cbb23fec3cc0ce8bb731ef604bb59b61d
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/vulkanscene/skybox.frag
@@ -0,0 +1,12 @@
+#version 450
+
+layout (binding = 1) uniform samplerCube samplerCubeMap;
+
+layout (location = 0) in vec3 inUVW;
+
+layout (location = 0) out vec4 outFragColor;
+
+void main() 
+{
+	outFragColor = texture(samplerCubeMap, inUVW);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/glsl/vulkanscene/skybox.frag.spv b/external/Vulkan/data/shaders/glsl/vulkanscene/skybox.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..54a894ccad057de259cab4c5e03a47bf8e3384de
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/vulkanscene/skybox.frag.spv differ
diff --git a/external/Vulkan/data/shaders/glsl/vulkanscene/skybox.vert b/external/Vulkan/data/shaders/glsl/vulkanscene/skybox.vert
new file mode 100644
index 0000000000000000000000000000000000000000..232b12c020720c3730f757eb1f01f82be3aaf2f3
--- /dev/null
+++ b/external/Vulkan/data/shaders/glsl/vulkanscene/skybox.vert
@@ -0,0 +1,17 @@
+#version 450
+
+layout (location = 0) in vec3 inPos;
+
+layout (binding = 0) uniform UBO 
+{
+	mat4 projection;
+	mat4 model;
+} ubo;
+
+layout (location = 0) out vec3 outUVW;
+
+void main() 
+{
+	outUVW = inPos;
+	gl_Position = ubo.projection * ubo.model * vec4(inPos.xyz, 1.0);
+}
diff --git a/external/Vulkan/data/shaders/glsl/vulkanscene/skybox.vert.spv b/external/Vulkan/data/shaders/glsl/vulkanscene/skybox.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..901a2d511f723832d7f7bbdb2164f0b6bc4eff2f
Binary files /dev/null and b/external/Vulkan/data/shaders/glsl/vulkanscene/skybox.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/README.md b/external/Vulkan/data/shaders/hlsl/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..20a3de5b022265a99457e3483529bbac89256a69
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/README.md
@@ -0,0 +1,18 @@
+## HLSL Shaders
+
+This directory contains a fork of the shaders found in [data/shaders/glsl](https://github.com/SaschaWillems/Vulkan/tree/master/data/shaders/glsl), re-written in HLSL.
+These can be compiled with [DXC](https://github.com/microsoft/DirectXShaderCompiler) using the `compile.py` script.
+
+### Known issues
+
+- specialization constants can't be used to specify array size.
+- `gl_PointCoord` not supported. HLSL has no equivalent. We changed the shaders to calulate the PointCoord manually in the shader. (`computenbody`, `computeparticles`, `particlefire` examples).
+- HLSL doesn't have inverse operation (`deferred`, `hdr`, `instancing`, `skeletalanimation` & `texturecubemap` examples).
+- `modf` causes compilation to fail without errors or warnings. (`modf` not used by any examples, easily confused with fmod)
+- In `specializationconstants` example, shader compilation fails with error:
+    ```
+    --- Error msg: fatal error: failed to optimize SPIR-V: Id 10 is defined more than once
+    ```
+  When multiple constant ids are defined and have different types. We work around this problem by making all constant ids the same type, then use `asfloat`, `asint` or `asuint` to get the original value in the shader.
+- `gl_RayTmaxNV` not supported. (`nv_ray_tracing_*` examples)
+- HLSL interface for sparse residency textures is different from GLSL interface. After translating from HLSL to GLSL the shaders behave slightly different. Most important parts do behave identically though.
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/base/textoverlay.frag b/external/Vulkan/data/shaders/hlsl/base/textoverlay.frag
new file mode 100644
index 0000000000000000000000000000000000000000..e9d9ba763ef4a69fa6ff619230df0a59da6e3be5
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/base/textoverlay.frag
@@ -0,0 +1,10 @@
+// Copyright 2020 Google LLC
+
+Texture2D textureFont : register(t0);
+SamplerState samplerFont : register(s0);
+
+float4 main([[vk::location(0)]]float2 inUV : TEXCOORD0) : SV_TARGET
+{
+	float color = textureFont.Sample(samplerFont, inUV).r;
+	return float4(color.xxx, 1.0);
+}
diff --git a/external/Vulkan/data/shaders/hlsl/base/textoverlay.frag.spv b/external/Vulkan/data/shaders/hlsl/base/textoverlay.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..46f47ac7e46df3bec467bc04581507ac04a6c11e
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/base/textoverlay.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/base/textoverlay.vert b/external/Vulkan/data/shaders/hlsl/base/textoverlay.vert
new file mode 100644
index 0000000000000000000000000000000000000000..a040eaaa0ae9f0546e82a167219d7fe80ee3378d
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/base/textoverlay.vert
@@ -0,0 +1,21 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+	[[vk::location(0)]]float2 Pos : POSITION0;
+	[[vk::location(1)]]float2 UV : TEXCOORD0;
+};
+
+struct VSOutput
+{
+    float4 Pos : SV_POSITION;
+	[[vk::location(0)]]float2 UV : TEXCOORD0;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Pos = float4(input.Pos, 0.0, 1.0);
+	output.UV = input.UV;
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/base/textoverlay.vert.spv b/external/Vulkan/data/shaders/hlsl/base/textoverlay.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..4870eb420dd28d374237042f33359751c4d51ca0
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/base/textoverlay.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/base/uioverlay.frag b/external/Vulkan/data/shaders/hlsl/base/uioverlay.frag
new file mode 100644
index 0000000000000000000000000000000000000000..9bec808da6d5693687ef355880c508d06be93da0
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/base/uioverlay.frag
@@ -0,0 +1,15 @@
+// Copyright 2020 Google LLC
+
+Texture2D fontTexture : register(t0);
+SamplerState fontSampler : register(s0);
+
+struct VSOutput
+{
+	[[vk::location(0)]]float2 UV : TEXCOORD0;
+	[[vk::location(1)]]float4 Color : COLOR0;
+};
+
+float4 main(VSOutput input) : SV_TARGET
+{
+	return input.Color * fontTexture.Sample(fontSampler, input.UV);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/base/uioverlay.frag.spv b/external/Vulkan/data/shaders/hlsl/base/uioverlay.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..282a17fd38c9dac950a8a07a997bf7d9c7b10d79
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/base/uioverlay.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/base/uioverlay.vert b/external/Vulkan/data/shaders/hlsl/base/uioverlay.vert
new file mode 100644
index 0000000000000000000000000000000000000000..be30fd74a451f17cc5d7b2b3a890ae70be5bd0f2
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/base/uioverlay.vert
@@ -0,0 +1,33 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+	[[vk::location(0)]]float2 Pos : POSITION0;
+	[[vk::location(1)]]float2 UV : TEXCOORD0;
+	[[vk::location(2)]]float4 Color : COLOR0;
+};
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+	[[vk::location(0)]]float2 UV : TEXCOORD0;
+	[[vk::location(1)]]float4 Color : COLOR0;
+};
+
+struct PushConstants
+{
+	float2 scale;
+	float2 translate;
+};
+
+[[vk::push_constant]]
+PushConstants pushConstants;
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Pos = float4(input.Pos * pushConstants.scale + pushConstants.translate, 0.0, 1.0);
+	output.UV = input.UV;
+	output.Color = input.Color;
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/base/uioverlay.vert.spv b/external/Vulkan/data/shaders/hlsl/base/uioverlay.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..77519d6aa87dae0de0ae170721bb6fd7d404f2ea
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/base/uioverlay.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/bloom/colorpass.frag b/external/Vulkan/data/shaders/hlsl/bloom/colorpass.frag
new file mode 100644
index 0000000000000000000000000000000000000000..9949fd3f0fd9204bf79fcc0dc0d4f5fd2aa9b2f7
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/bloom/colorpass.frag
@@ -0,0 +1,15 @@
+// Copyright 2020 Google LLC
+
+Texture2D colorMapTexture : register(t1);
+SamplerState colorMapSampler : register(s1);
+
+struct VSOutput
+{
+	[[vk::location(0)]]float3 Color : COLOR0;
+	[[vk::location(1)]]float2 UV : TEXCOORD0;
+};
+
+float4 main(VSOutput input) : SV_TARGET
+{
+	return float4(input.Color, 1);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/bloom/colorpass.frag.spv b/external/Vulkan/data/shaders/hlsl/bloom/colorpass.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..76316d23a51c0c955d7c2046b4fe8e823a7ddc13
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/bloom/colorpass.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/bloom/colorpass.vert b/external/Vulkan/data/shaders/hlsl/bloom/colorpass.vert
new file mode 100644
index 0000000000000000000000000000000000000000..53dad1b233dd45373873d40010b112daaa6eb9b2
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/bloom/colorpass.vert
@@ -0,0 +1,31 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+	[[vk::location(0)]]float4 Pos : POSITION0;
+	[[vk::location(1)]]float2 UV : TEXCOORD0;
+	[[vk::location(2)]]float3 Color : COLOR0;
+};
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+	[[vk::location(0)]]float3 Color : COLOR0;
+	[[vk::location(1)]]float2 UV : TEXCOORD0;
+};
+
+cbuffer UBO : register(b0)
+{
+	float4x4 projection;
+	float4x4 view;
+	float4x4 model;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.UV = input.UV;
+	output.Color = input.Color;
+	output.Pos = mul(projection, mul(view, mul(model, input.Pos)));
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/bloom/colorpass.vert.spv b/external/Vulkan/data/shaders/hlsl/bloom/colorpass.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..2ab6cf7c238f153ce5c491522fc95044ee9543f2
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/bloom/colorpass.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/bloom/gaussblur.frag b/external/Vulkan/data/shaders/hlsl/bloom/gaussblur.frag
new file mode 100644
index 0000000000000000000000000000000000000000..5da50569842b5c7e53264c2e88236c7bec954416
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/bloom/gaussblur.frag
@@ -0,0 +1,43 @@
+// Copyright 2020 Google LLC
+
+Texture2D textureColor : register(t1);
+SamplerState samplerColor : register(s1);
+
+cbuffer UBO : register(b0)
+{
+	float blurScale;
+	float blurStrength;
+};
+
+[[vk::constant_id(0)]] const int blurdirection = 0;
+
+float4 main([[vk::location(0)]] float2 inUV : TEXCOORD0) : SV_TARGET
+{
+	float weight[5];
+	weight[0] = 0.227027;
+	weight[1] = 0.1945946;
+	weight[2] = 0.1216216;
+	weight[3] = 0.054054;
+	weight[4] = 0.016216;
+
+	float2 textureSize;
+	textureColor.GetDimensions(textureSize.x, textureSize.y);
+	float2 tex_offset = 1.0 / textureSize * blurScale; // gets size of single texel
+	float3 result = textureColor.Sample(samplerColor, inUV).rgb * weight[0]; // current fragment's contribution
+	for(int i = 1; i < 5; ++i)
+	{
+		if (blurdirection == 1)
+		{
+			// H
+			result += textureColor.Sample(samplerColor, inUV + float2(tex_offset.x * i, 0.0)).rgb * weight[i] * blurStrength;
+			result += textureColor.Sample(samplerColor, inUV - float2(tex_offset.x * i, 0.0)).rgb * weight[i] * blurStrength;
+		}
+		else
+		{
+			// V
+			result += textureColor.Sample(samplerColor, inUV + float2(0.0, tex_offset.y * i)).rgb * weight[i] * blurStrength;
+			result += textureColor.Sample(samplerColor, inUV - float2(0.0, tex_offset.y * i)).rgb * weight[i] * blurStrength;
+		}
+	}
+	return float4(result, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/bloom/gaussblur.frag.spv b/external/Vulkan/data/shaders/hlsl/bloom/gaussblur.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..c3cc4cfb3dd0e383dd23fb613bcd8f4d0374a10d
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/bloom/gaussblur.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/bloom/gaussblur.vert b/external/Vulkan/data/shaders/hlsl/bloom/gaussblur.vert
new file mode 100644
index 0000000000000000000000000000000000000000..a9c3d551f5ce8b5d91551694a2b03e6e93c8ebe8
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/bloom/gaussblur.vert
@@ -0,0 +1,15 @@
+// Copyright 2020 Google LLC
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+	[[vk::location(0)]] float2 UV : TEXCOORD0;
+};
+
+VSOutput main(uint VertexIndex : SV_VertexID)
+{
+	VSOutput output = (VSOutput)0;
+	output.UV = float2((VertexIndex << 1) & 2, VertexIndex & 2);
+	output.Pos = float4(output.UV * 2.0f - 1.0f, 0.0f, 1.0f);
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/bloom/gaussblur.vert.spv b/external/Vulkan/data/shaders/hlsl/bloom/gaussblur.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..46c4b31fdc1568cc56662cc746d3a2d10f9d0983
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/bloom/gaussblur.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/bloom/phongpass.frag b/external/Vulkan/data/shaders/hlsl/bloom/phongpass.frag
new file mode 100644
index 0000000000000000000000000000000000000000..3daed2c292f67ff55d04808f7e418c43a08a4af0
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/bloom/phongpass.frag
@@ -0,0 +1,32 @@
+// Copyright 2020 Google LLC
+
+Texture2D colorMapTexture : register(t1);
+SamplerState colorMapSampler : register(s1);
+
+struct VSOutput
+{
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+[[vk::location(2)]] float3 Color : COLOR0;
+[[vk::location(3)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(4)]] float3 LightVec : TEXCOORD2;
+};
+
+float4 main(VSOutput input) : SV_TARGET
+{
+	float3 ambient = float3(0.0f, 0.0f, 0.0f);
+
+	// Adjust light calculations for glow color
+	if ((input.Color.r >= 0.9) || (input.Color.g >= 0.9) || (input.Color.b >= 0.9))
+	{
+		ambient = input.Color * 0.25;
+	}
+
+	float3 N = normalize(input.Normal);
+	float3 L = normalize(input.LightVec);
+	float3 V = normalize(input.ViewVec);
+	float3 R = reflect(-L, N);
+	float3 diffuse = max(dot(N, L), 0.0) * input.Color;
+	float3 specular = pow(max(dot(R, V), 0.0), 8.0) * float3(0.75f, 0.75f, 0.75f);
+	return float4(ambient + diffuse + specular, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/bloom/phongpass.frag.spv b/external/Vulkan/data/shaders/hlsl/bloom/phongpass.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..eb61f5bab02f07996ddd3fade3d3a44474bccd99
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/bloom/phongpass.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/bloom/phongpass.vert b/external/Vulkan/data/shaders/hlsl/bloom/phongpass.vert
new file mode 100644
index 0000000000000000000000000000000000000000..198c77c6bd9584c78256b8f43944b24f16fcba9e
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/bloom/phongpass.vert
@@ -0,0 +1,42 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float4 Pos : POSITION0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+[[vk::location(2)]] float3 Color : COLOR0;
+[[vk::location(3)]] float3 Normal : NORMAL0;
+};
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+[[vk::location(2)]] float3 Color : COLOR0;
+[[vk::location(3)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(4)]] float3 LightVec : TEXCOORD2;
+};
+
+cbuffer UBO : register(b0)
+{
+	float4x4 projection;
+	float4x4 view;
+	float4x4 model;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Normal = input.Normal;
+	output.Color = input.Color;
+	output.UV = input.UV;
+	output.Pos = mul(projection, mul(view, mul(model, input.Pos)));
+
+	float3 lightPos = float3(-5.0, -5.0, 0.0);
+	float4 pos = mul(view, mul(model, input.Pos));
+	output.Normal = mul((float4x3)mul(view, model), input.Normal).xyz;
+	output.LightVec = lightPos - pos.xyz;
+	output.ViewVec = -pos.xyz;
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/bloom/phongpass.vert.spv b/external/Vulkan/data/shaders/hlsl/bloom/phongpass.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..ac64d27b51676396f1eab7fe643c3d9dfa2cf48d
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/bloom/phongpass.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/bloom/skybox.frag b/external/Vulkan/data/shaders/hlsl/bloom/skybox.frag
new file mode 100644
index 0000000000000000000000000000000000000000..18460ed91bbad71f135372465af645e73042d77a
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/bloom/skybox.frag
@@ -0,0 +1,9 @@
+// Copyright 2020 Google LLC
+
+TextureCube textureCubeMap : register(t1);
+SamplerState samplerCubeMap : register(s1);
+
+float4 main([[vk::location(0)]] float3 inUVW : NORMAL0) : SV_TARGET
+{
+	return textureCubeMap.Sample(samplerCubeMap, inUVW);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/bloom/skybox.frag.spv b/external/Vulkan/data/shaders/hlsl/bloom/skybox.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..d5394a523b4619286e6ea14893bcbf48fdbcebb3
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/bloom/skybox.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/bloom/skybox.vert b/external/Vulkan/data/shaders/hlsl/bloom/skybox.vert
new file mode 100644
index 0000000000000000000000000000000000000000..212e404f0abfa01ddb26fa136e0f0e239a56890b
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/bloom/skybox.vert
@@ -0,0 +1,22 @@
+// Copyright 2020 Google LLC
+
+cbuffer UBO : register(b0)
+{
+	float4x4 projection;
+	float4x4 view;
+	float4x4 model;
+};
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+	[[vk::location(0)]] float3 UVW : NORMAL0;
+};
+
+VSOutput main([[vk::location(0)]] float3 inPos : POSITION0)
+{
+	VSOutput output = (VSOutput)0;
+	output.UVW = inPos;
+	output.Pos = mul(projection, mul(view, mul(model, float4(inPos.xyz, 1.0))));
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/bloom/skybox.vert.spv b/external/Vulkan/data/shaders/hlsl/bloom/skybox.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..3e732bbc48103225050b6b0d04fc25fe1ee00a61
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/bloom/skybox.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/compile.py b/external/Vulkan/data/shaders/hlsl/compile.py
new file mode 100644
index 0000000000000000000000000000000000000000..2b44f4d287e6f7efeee35f90a692ffdab08bea2f
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/compile.py
@@ -0,0 +1,72 @@
+# Copyright 2020 Google LLC
+
+import argparse
+import fileinput
+import os
+import subprocess
+import sys
+
+parser = argparse.ArgumentParser(description='Compile all .hlsl shaders')
+parser.add_argument('--dxc', type=str, help='path to DXC executable')
+args = parser.parse_args()
+
+def findDXC():
+    def isExe(path):
+        return os.path.isfile(path) and os.access(path, os.X_OK)
+
+    if args.dxc != None and isExe(args.dxc):
+        return args.dxc
+
+    exe_name = "dxc"
+    if os.name == "nt":
+        exe_name += ".exe"
+
+    for exe_dir in os.environ["PATH"].split(os.pathsep):
+        full_path = os.path.join(exe_dir, exe_name)
+        if isExe(full_path):
+            return full_path
+
+    sys.exit("Could not find DXC executable on PATH, and was not specified with --dxc")
+
+dxc_path = findDXC()
+dir_path = os.path.dirname(os.path.realpath(__file__))
+dir_path = dir_path.replace('\\', '/')
+for root, dirs, files in os.walk(dir_path):
+    for file in files:
+        if file.endswith(".vert") or file.endswith(".frag") or file.endswith(".comp") or file.endswith(".geom") or file.endswith(".tesc") or file.endswith(".tese") or file.endswith(".rgen") or file.endswith(".rchit") or file.endswith(".rmiss"):
+            hlsl_file = os.path.join(root, file)
+            spv_out = hlsl_file + ".spv"
+
+            target = ''
+            profile = ''
+            if(hlsl_file.find('.vert') != -1):
+                profile = 'vs_6_1'
+            elif(hlsl_file.find('.frag') != -1):
+                profile = 'ps_6_1'
+            elif(hlsl_file.find('.comp') != -1):
+                profile = 'cs_6_1'
+            elif(hlsl_file.find('.geom') != -1):
+                profile = 'gs_6_1'
+            elif(hlsl_file.find('.tesc') != -1):
+                profile = 'hs_6_1'
+            elif(hlsl_file.find('.tese') != -1):
+                profile = 'ds_6_1'
+            elif(hlsl_file.find('.rgen') != -1 or
+				hlsl_file.find('.rchit') != -1 or
+				hlsl_file.find('.rmiss') != -1):
+                target='-fspv-target-env=vulkan1.2'
+                profile = 'lib_6_3'
+
+            print('Compiling %s' % (hlsl_file))
+            subprocess.check_output([
+                dxc_path,
+                '-spirv',
+                '-T', profile,
+                '-E', 'main',
+                '-fspv-extension=SPV_KHR_ray_tracing',
+                '-fspv-extension=SPV_KHR_multiview',
+                '-fspv-extension=SPV_KHR_shader_draw_parameters',
+                '-fspv-extension=SPV_EXT_descriptor_indexing',
+                target,
+                hlsl_file,
+                '-Fo', spv_out])
diff --git a/external/Vulkan/data/shaders/hlsl/computecloth/cloth.comp b/external/Vulkan/data/shaders/hlsl/computecloth/cloth.comp
new file mode 100644
index 0000000000000000000000000000000000000000..2ef2b88043f5bfefdd63ced7a3e675000dfc883c
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/computecloth/cloth.comp
@@ -0,0 +1,154 @@
+// Copyright 2020 Google LLC
+
+struct Particle {
+	float4 pos;
+	float4 vel;
+	float4 uv;
+	float4 normal;
+	float pinned;
+};
+
+[[vk::binding(0)]]
+StructuredBuffer<Particle> particleIn;
+[[vk::binding(1)]]
+RWStructuredBuffer<Particle> particleOut;
+
+struct UBO
+{
+	float deltaT;
+	float particleMass;
+	float springStiffness;
+	float damping;
+	float restDistH;
+	float restDistV;
+	float restDistD;
+	float sphereRadius;
+	float4 spherePos;
+	float4 gravity;
+	int2 particleCount;
+};
+
+cbuffer ubo : register(b2)
+{
+	UBO params;
+};
+
+struct PushConstants
+{
+	uint calculateNormals;
+};
+
+[[vk::push_constant]]
+PushConstants pushConstants;
+
+float3 springForce(float3 p0, float3 p1, float restDist)
+{
+	float3 dist = p0 - p1;
+	return normalize(dist) * params.springStiffness * (length(dist) - restDist);
+}
+
+[numthreads(10, 10, 1)]
+void main(uint3 id : SV_DispatchThreadID)
+{
+	uint index = id.y * params.particleCount.x + id.x;
+	if (index > params.particleCount.x * params.particleCount.y)
+		return;
+
+	// Pinned?
+	if (particleIn[index].pinned == 1.0) {
+		particleOut[index].pos = particleOut[index].pos;
+		particleOut[index].vel = float4(0, 0, 0, 0);
+		return;
+	}
+
+	// Initial force from gravity
+	float3 force = params.gravity.xyz * params.particleMass;
+
+	float3 pos = particleIn[index].pos.xyz;
+	float3 vel = particleIn[index].vel.xyz;
+
+	// Spring forces from neighboring particles
+	// left
+	if (id.x > 0) {
+		force += springForce(particleIn[index-1].pos.xyz, pos, params.restDistH);
+	}
+	// right
+	if (id.x < params.particleCount.x - 1) {
+		force += springForce(particleIn[index + 1].pos.xyz, pos, params.restDistH);
+	}
+	// upper
+	if (id.y < params.particleCount.y - 1) {
+		force += springForce(particleIn[index + params.particleCount.x].pos.xyz, pos, params.restDistV);
+	}
+	// lower
+	if (id.y > 0) {
+		force += springForce(particleIn[index - params.particleCount.x].pos.xyz, pos, params.restDistV);
+	}
+	// upper-left
+	if ((id.x > 0) && (id.y < params.particleCount.y - 1)) {
+		force += springForce(particleIn[index + params.particleCount.x - 1].pos.xyz, pos, params.restDistD);
+	}
+	// lower-left
+	if ((id.x > 0) && (id.y > 0)) {
+		force += springForce(particleIn[index - params.particleCount.x - 1].pos.xyz, pos, params.restDistD);
+	}
+	// upper-right
+	if ((id.x < params.particleCount.x - 1) && (id.y < params.particleCount.y - 1)) {
+		force += springForce(particleIn[index + params.particleCount.x + 1].pos.xyz, pos, params.restDistD);
+	}
+	// lower-right
+	if ((id.x < params.particleCount.x - 1) && (id.y > 0)) {
+		force += springForce(particleIn[index - params.particleCount.x + 1].pos.xyz, pos, params.restDistD);
+	}
+
+	force += (-params.damping * vel);
+
+	// Integrate
+	float3 f = force * (1.0 / params.particleMass);
+	particleOut[index].pos = float4(pos + vel * params.deltaT + 0.5 * f * params.deltaT * params.deltaT, 1.0);
+	particleOut[index].vel = float4(vel + f * params.deltaT, 0.0);
+
+	// Sphere collision
+	float3 sphereDist = particleOut[index].pos.xyz - params.spherePos.xyz;
+	if (length(sphereDist) < params.sphereRadius + 0.01) {
+		// If the particle is inside the sphere, push it to the outer radius
+		particleOut[index].pos.xyz = params.spherePos.xyz + normalize(sphereDist) * (params.sphereRadius + 0.01);
+		// Cancel out velocity
+		particleOut[index].vel = float4(0, 0, 0, 0);
+	}
+
+	// Normals
+	if (pushConstants.calculateNormals == 1) {
+		float3 normal = float3(0, 0, 0);
+		float3 a, b, c;
+		if (id.y > 0) {
+			if (id.x > 0) {
+				a = particleIn[index - 1].pos.xyz - pos;
+				b = particleIn[index - params.particleCount.x - 1].pos.xyz - pos;
+				c = particleIn[index - params.particleCount.x].pos.xyz - pos;
+				normal += cross(a,b) + cross(b,c);
+			}
+			if (id.x < params.particleCount.x - 1) {
+				a = particleIn[index - params.particleCount.x].pos.xyz - pos;
+				b = particleIn[index - params.particleCount.x + 1].pos.xyz - pos;
+				c = particleIn[index + 1].pos.xyz - pos;
+				normal += cross(a,b) + cross(b,c);
+			}
+		}
+		if (id.y < params.particleCount.y - 1) {
+			if (id.x > 0) {
+				a = particleIn[index + params.particleCount.x].pos.xyz - pos;
+				b = particleIn[index + params.particleCount.x - 1].pos.xyz - pos;
+				c = particleIn[index - 1].pos.xyz - pos;
+				normal += cross(a,b) + cross(b,c);
+			}
+			if (id.x < params.particleCount.x - 1) {
+				a = particleIn[index + 1].pos.xyz - pos;
+				b = particleIn[index + params.particleCount.x + 1].pos.xyz - pos;
+				c = particleIn[index + params.particleCount.x].pos.xyz - pos;
+				normal += cross(a,b) + cross(b,c);
+			}
+		}
+		particleOut[index].normal = float4(normalize(normal), 0.0f);
+	}
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/computecloth/cloth.comp.spv b/external/Vulkan/data/shaders/hlsl/computecloth/cloth.comp.spv
new file mode 100644
index 0000000000000000000000000000000000000000..5e85d78bd47c55a16fbf0e2af9d9dd72853eda19
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/computecloth/cloth.comp.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/computecloth/cloth.frag b/external/Vulkan/data/shaders/hlsl/computecloth/cloth.frag
new file mode 100644
index 0000000000000000000000000000000000000000..46612dc3f3ba12fbad7c578e086051652c934ab4
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/computecloth/cloth.frag
@@ -0,0 +1,24 @@
+// Copyright 2020 Google LLC
+
+Texture2D textureColor : register(t1);
+SamplerState samplerColor : register(s1);
+
+struct VSOutput
+{
+[[vk::location(0)]]float2 UV : TEXCOORD0;
+[[vk::location(1)]]float3 Normal : NORMAL0;
+[[vk::location(2)]]float3 ViewVec : TEXCOORD1;
+[[vk::location(3)]]float3 LightVec : TEXCOORD2;
+};
+
+float4 main (VSOutput input) : SV_TARGET
+{
+	float3 color = textureColor.Sample(samplerColor, input.UV).rgb;
+	float3 N = normalize(input.Normal);
+	float3 L = normalize(input.LightVec);
+	float3 V = normalize(input.ViewVec);
+	float3 R = reflect(-L, N);
+	float3 diffuse = max(dot(N, L), 0.15) * float3(1, 1, 1);
+	float3 specular = pow(max(dot(R, V), 0.0), 8.0) * float3(0.2, 0.2, 0.2);
+	return float4(diffuse * color.rgb + specular, 1.0);
+}
diff --git a/external/Vulkan/data/shaders/hlsl/computecloth/cloth.frag.spv b/external/Vulkan/data/shaders/hlsl/computecloth/cloth.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..7a81f3545487667fa2f0037f563054e30491bcdf
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/computecloth/cloth.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/computecloth/cloth.vert b/external/Vulkan/data/shaders/hlsl/computecloth/cloth.vert
new file mode 100644
index 0000000000000000000000000000000000000000..00d469271c06b08c04b85e33c24fcdd5e34d1da5
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/computecloth/cloth.vert
@@ -0,0 +1,43 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+[[vk::location(2)]] float3 Normal : NORMAL0;
+};
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float2 UV : TEXCOORD0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(3)]] float3 LightVec : TEXCOORD2;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 modelview;
+	float4 lightPos;
+};
+
+cbuffer ubo : register(b0)
+{
+	UBO ubo;
+};
+
+VSOutput main (VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.UV = input.UV;
+	output.Normal = input.Normal.xyz;
+	float4 eyePos = mul(ubo.modelview, float4(input.Pos.x, input.Pos.y, input.Pos.z, 1.0));
+	output.Pos = mul(ubo.projection, eyePos);
+	float4 pos = float4(input.Pos, 1.0);
+	float3 lPos = ubo.lightPos.xyz;
+	output.LightVec = lPos - pos.xyz;
+	output.ViewVec = -pos.xyz;
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/computecloth/cloth.vert.spv b/external/Vulkan/data/shaders/hlsl/computecloth/cloth.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..5e2dcb53aa033d44e0e2e9f6fd7475079512625b
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/computecloth/cloth.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/computecloth/sphere.frag b/external/Vulkan/data/shaders/hlsl/computecloth/sphere.frag
new file mode 100644
index 0000000000000000000000000000000000000000..207db7aa9d3cee2b65c4615f8e2674526dd55903
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/computecloth/sphere.frag
@@ -0,0 +1,20 @@
+// Copyright 2020 Google LLC
+
+struct VSOutput
+{
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 ViewVec : TEXCOORD0;
+[[vk::location(2)]] float3 LightVec : TEXCOORD1;
+};
+
+float4 main (VSOutput input) : SV_TARGET
+{
+	float3 color = float3(0.5, 0.5, 0.5);
+	float3 N = normalize(input.Normal);
+	float3 L = normalize(input.LightVec);
+	float3 V = normalize(input.ViewVec);
+	float3 R = reflect(-L, N);
+	float3 diffuse = max(dot(N, L), 0.15);
+	float3 specular = pow(max(dot(R, V), 0.0), 32.0);
+	return float4(diffuse * color.rgb + specular, 1.0);
+}
diff --git a/external/Vulkan/data/shaders/hlsl/computecloth/sphere.frag.spv b/external/Vulkan/data/shaders/hlsl/computecloth/sphere.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..fbb448bf83c3de7add13ed83892418e4299dad59
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/computecloth/sphere.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/computecloth/sphere.vert b/external/Vulkan/data/shaders/hlsl/computecloth/sphere.vert
new file mode 100644
index 0000000000000000000000000000000000000000..8414cb582829afed102021bff69de556bea4d8d3
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/computecloth/sphere.vert
@@ -0,0 +1,41 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]]float3 Pos : POSITION0;
+[[vk::location(2)]]float3 Normal : NORMAL0;
+};
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 ViewVec : TEXCOORD0;
+[[vk::location(2)]] float3 LightVec : TEXCOORD1;
+};
+
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 modelview;
+	float4 lightPos;
+};
+
+cbuffer ubo : register(b0)
+{
+	UBO ubo;
+};
+
+VSOutput main (VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	float4 eyePos = mul(ubo.modelview, float4(input.Pos.x, input.Pos.y, input.Pos.z, 1.0));
+	output.Pos = mul(ubo.projection, eyePos);
+	float4 pos = float4(input.Pos, 1.0);
+	float3 lPos = ubo.lightPos.xyz;
+	output.LightVec = lPos - pos.xyz;
+	output.ViewVec = -pos.xyz;
+	output.Normal = input.Normal;
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/computecloth/sphere.vert.spv b/external/Vulkan/data/shaders/hlsl/computecloth/sphere.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..a8803b0f80707cb13899cc40298cf3a0898504a2
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/computecloth/sphere.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/computecullandlod/cull.comp b/external/Vulkan/data/shaders/hlsl/computecullandlod/cull.comp
new file mode 100644
index 0000000000000000000000000000000000000000..39d70c7474abff2d29495af6ed99339c21fef5a1
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/computecullandlod/cull.comp
@@ -0,0 +1,115 @@
+// Copyright 2020 Google LLC
+
+#define MAX_LOD_LEVEL_COUNT 6
+[[vk::constant_id(0)]] const int MAX_LOD_LEVEL = 5;
+
+struct InstanceData
+{
+	float3 pos;
+	float scale;
+};
+
+StructuredBuffer<InstanceData> instances : register(t0);
+
+// Same layout as VkDrawIndexedIndirectCommand
+struct IndexedIndirectCommand
+{
+	uint indexCount;
+	uint instanceCount;
+	uint firstIndex;
+	uint vertexOffset;
+	uint firstInstance;
+};
+
+RWStructuredBuffer<IndexedIndirectCommand> indirectDraws : register(u1);
+
+// Binding 2: Uniform block object with matrices
+struct UBO
+{
+	float4x4 projection;
+	float4x4 modelview;
+	float4 cameraPos;
+	float4 frustumPlanes[6];
+};
+
+cbuffer ubo : register(b2) { UBO ubo; }
+
+// Binding 3: Indirect draw stats
+struct UBOOut
+{
+	uint drawCount;
+	uint lodCount[MAX_LOD_LEVEL_COUNT];
+};
+RWStructuredBuffer<UBOOut> uboOut : register(u3);
+
+// Binding 4: level-of-detail information
+struct LOD
+{
+	uint firstIndex;
+	uint indexCount;
+	float distance;
+	float _pad0;
+};
+
+StructuredBuffer<LOD> lods : register(t4);
+
+[numthreads(16, 1, 1)]
+bool frustumCheck(float4 pos, float radius)
+{
+	// Check sphere against frustum planes
+	for (int i = 0; i < 6; i++)
+	{
+		if (dot(pos, ubo.frustumPlanes[i]) + radius < 0.0)
+		{
+			return false;
+		}
+	}
+	return true;
+}
+
+[numthreads(16, 1, 1)]
+void main(uint3 GlobalInvocationID : SV_DispatchThreadID )
+{
+	uint idx = GlobalInvocationID.x;
+	uint temp;
+
+	// Clear stats on first invocation
+	if (idx == 0)
+	{
+		InterlockedExchange(uboOut[0].drawCount, 0, temp);
+		for (uint i = 0; i < MAX_LOD_LEVEL + 1; i++)
+		{
+			InterlockedExchange(uboOut[0].lodCount[i], 0, temp);
+		}
+	}
+
+	float4 pos = float4(instances[idx].pos.xyz, 1.0);
+
+	// Check if object is within current viewing frustum
+	if (frustumCheck(pos, 1.0))
+	{
+		indirectDraws[idx].instanceCount = 1;
+
+		// Increase number of indirect draw counts
+		InterlockedAdd(uboOut[0].drawCount, 1, temp);
+
+		// Select appropriate LOD level based on distance to camera
+		uint lodLevel = MAX_LOD_LEVEL;
+		for (uint i = 0; i < MAX_LOD_LEVEL; i++)
+		{
+			if (distance(instances[idx].pos.xyz, ubo.cameraPos.xyz) < lods[i].distance)
+			{
+				lodLevel = i;
+				break;
+			}
+		}
+		indirectDraws[idx].firstIndex = lods[lodLevel].firstIndex;
+		indirectDraws[idx].indexCount = lods[lodLevel].indexCount;
+		// Update stats
+		InterlockedAdd(uboOut[0].lodCount[lodLevel], 1, temp);
+	}
+	else
+	{
+		indirectDraws[idx].instanceCount = 0;
+	}
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/computecullandlod/cull.comp.spv b/external/Vulkan/data/shaders/hlsl/computecullandlod/cull.comp.spv
new file mode 100644
index 0000000000000000000000000000000000000000..bfaf7a667ab94c7dda71eea8e8f57533aa8f79fb
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/computecullandlod/cull.comp.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/computecullandlod/indirectdraw.frag b/external/Vulkan/data/shaders/hlsl/computecullandlod/indirectdraw.frag
new file mode 100644
index 0000000000000000000000000000000000000000..f4f49dab7d19f1f1b3abae55f50fbf57abdbb570
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/computecullandlod/indirectdraw.frag
@@ -0,0 +1,18 @@
+// Copyright 2020 Google LLC
+
+struct VSOutput
+{
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(3)]] float3 LightVec : TEXCOORD2;
+};
+
+float4 main(VSOutput input) : SV_TARGET
+{
+	float3 N = normalize(input.Normal);
+	float3 L = normalize(input.LightVec);
+	float3 ambient = float3(0.25, 0.25, 0.25);
+	float3 diffuse = max(dot(N, L), 0.0).xxx;
+	return float4((ambient + diffuse) * input.Color, 1.0);
+}
diff --git a/external/Vulkan/data/shaders/hlsl/computecullandlod/indirectdraw.frag.spv b/external/Vulkan/data/shaders/hlsl/computecullandlod/indirectdraw.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..6d46b40761a4efe996be8ad271d6caaa7d92d476
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/computecullandlod/indirectdraw.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/computecullandlod/indirectdraw.vert b/external/Vulkan/data/shaders/hlsl/computecullandlod/indirectdraw.vert
new file mode 100644
index 0000000000000000000000000000000000000000..9893ba84f94ccfcbbc9ba10de7a688fdd2c4b1a1
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/computecullandlod/indirectdraw.vert
@@ -0,0 +1,46 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float4 Pos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float3 Color : COLOR0;
+// Instanced attributes
+[[vk::location(4)]] float3 instancePos : TEXCOORD0;
+[[vk::location(5)]] float instanceScale : TEXCOORD1;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 modelview;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(3)]] float3 LightVec : TEXCOORD2;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Color = input.Color;
+
+	output.Normal = input.Normal;
+
+	float4 pos = float4((input.Pos.xyz * input.instanceScale) + input.instancePos, 1.0);
+
+	output.Pos = mul(ubo.projection, mul(ubo.modelview, pos));
+
+	float4 wPos = mul(ubo.modelview, float4(pos.xyz, 1.0));
+	float4 lPos = float4(0.0, 10.0, 50.0, 1.0);
+	output.LightVec = lPos.xyz - pos.xyz;
+	output.ViewVec = -pos.xyz;
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/computecullandlod/indirectdraw.vert.spv b/external/Vulkan/data/shaders/hlsl/computecullandlod/indirectdraw.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..e76bf181ca9e668914e96d7dd5239154ac5c1de6
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/computecullandlod/indirectdraw.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/computeheadless/headless.comp b/external/Vulkan/data/shaders/hlsl/computeheadless/headless.comp
new file mode 100644
index 0000000000000000000000000000000000000000..827f91598554ed20b2f4703381bfeabf689ab39d
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/computeheadless/headless.comp
@@ -0,0 +1,28 @@
+// Copyright 2020 Google LLC
+
+RWStructuredBuffer<uint> values : register(u0);
+[[vk::constant_id(0)]] const uint BUFFER_ELEMENTS = 32;
+
+uint fibonacci(uint n) {
+	if(n <= 1){
+		return n;
+	}
+	uint curr = 1;
+	uint prev = 1;
+	for(uint i = 2; i < n; ++i) {
+		uint temp = curr;
+		curr += prev;
+		prev = temp;
+	}
+	return curr;
+}
+
+[numthreads(1, 1, 1)]
+void main(uint3 GlobalInvocationID : SV_DispatchThreadID)
+{
+	uint index = GlobalInvocationID.x;
+	if (index >= BUFFER_ELEMENTS)
+		return;
+	values[index] = fibonacci(values[index]);
+}
+
diff --git a/external/Vulkan/data/shaders/hlsl/computeheadless/headless.comp.spv b/external/Vulkan/data/shaders/hlsl/computeheadless/headless.comp.spv
new file mode 100644
index 0000000000000000000000000000000000000000..cf111a2d933baa24791f4bcbb952055a20d188a2
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/computeheadless/headless.comp.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/computenbody/particle.frag b/external/Vulkan/data/shaders/hlsl/computenbody/particle.frag
new file mode 100644
index 0000000000000000000000000000000000000000..dd7bd3b8f15cd35b9b147070ab86abf2e463c4fd
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/computenbody/particle.frag
@@ -0,0 +1,21 @@
+// Copyright 2020 Google LLC
+
+Texture2D textureColorMap : register(t0);
+SamplerState samplerColorMap : register(s0);
+Texture2D textureGradientRamp : register(t1);
+SamplerState samplerGradientRamp : register(s1);
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float GradientPos : POSITION0;
+[[vk::location(1)]] float2 CenterPos : POSITION1;
+[[vk::location(2)]] float PointSize : TEXCOORD0;
+};
+
+float4 main (VSOutput input)  : SV_TARGET
+{
+	float3 color = textureGradientRamp.Sample(samplerGradientRamp, float2(input.GradientPos, 0.0)).rgb;
+	float2 PointCoord = (input.Pos.xy - input.CenterPos.xy) / input.PointSize + 0.5;
+	return float4(textureColorMap.Sample(samplerColorMap, PointCoord).rgb * color, 1);
+}
diff --git a/external/Vulkan/data/shaders/hlsl/computenbody/particle.frag.spv b/external/Vulkan/data/shaders/hlsl/computenbody/particle.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..0304c99370111aff9fa367177dbfaaa49bfb7dc6
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/computenbody/particle.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/computenbody/particle.vert b/external/Vulkan/data/shaders/hlsl/computenbody/particle.vert
new file mode 100644
index 0000000000000000000000000000000000000000..b2a76596b5ec418c1e94b9e8ca861e39711b5c91
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/computenbody/particle.vert
@@ -0,0 +1,41 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float4 Pos : POSITION0;
+[[vk::location(1)]] float4 Vel : TEXCOORD0;
+};
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float GradientPos : POSITION0;
+[[vk::location(1)]] float2 CenterPos : POSITION1;
+[[vk::builtin("PointSize")]] float PSize : PSIZE;
+[[vk::location(2)]] float PointSize : TEXCOORD0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 modelview;
+	float2 screendim;
+};
+
+cbuffer ubo : register(b2) { UBO ubo; }
+
+VSOutput main (VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	const float spriteSize = 0.005 * input.Pos.w; // Point size influenced by mass (stored in input.Pos.w);
+
+	float4 eyePos = mul(ubo.modelview, float4(input.Pos.x, input.Pos.y, input.Pos.z, 1.0));
+	float4 projectedCorner = mul(ubo.projection, float4(0.5 * spriteSize, 0.5 * spriteSize, eyePos.z, eyePos.w));
+	output.PSize = output.PointSize = clamp(ubo.screendim.x * projectedCorner.x / projectedCorner.w, 1.0, 128.0);
+
+	output.Pos = mul(ubo.projection, eyePos);
+	output.CenterPos = ((output.Pos.xy / output.Pos.w) + 1.0) * 0.5 * ubo.screendim;
+
+	output.GradientPos = input.Vel.w;
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/computenbody/particle.vert.spv b/external/Vulkan/data/shaders/hlsl/computenbody/particle.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..df56eaae263af31ba0e82101e84397bb102ada75
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/computenbody/particle.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/computenbody/particle_calculate.comp b/external/Vulkan/data/shaders/hlsl/computenbody/particle_calculate.comp
new file mode 100644
index 0000000000000000000000000000000000000000..3a4e8bbc1e1a420868c07ba78ff0cbda1d2a425f
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/computenbody/particle_calculate.comp
@@ -0,0 +1,70 @@
+// Copyright 2020 Google LLC
+
+struct Particle
+{
+	float4 pos;
+	float4 vel;
+};
+
+// Binding 0 : Position storage buffer
+RWStructuredBuffer<Particle> particles : register(u0);
+
+struct UBO
+{
+	float deltaT;
+	int particleCount;
+};
+
+cbuffer ubo : register(b1) { UBO ubo; }
+
+#define MAX_SHARED_DATA_SIZE 1024
+[[vk::constant_id(0)]] const int SHARED_DATA_SIZE = 512;
+[[vk::constant_id(1)]] const float GRAVITY = 0.002;
+[[vk::constant_id(2)]] const float POWER = 0.75;
+[[vk::constant_id(3)]] const float SOFTEN = 0.0075;
+
+// Share data between computer shader invocations to speed up caluclations
+groupshared float4 sharedData[MAX_SHARED_DATA_SIZE];
+
+[numthreads(256, 1, 1)]
+void main(uint3 GlobalInvocationID : SV_DispatchThreadID, uint3 LocalInvocationID : SV_GroupThreadID)
+{
+	// Current SSBO index
+	uint index = GlobalInvocationID.x;
+	if (index >= ubo.particleCount)
+		return;
+
+	float4 position = particles[index].pos;
+	float4 velocity = particles[index].vel;
+	float4 acceleration = float4(0, 0, 0, 0);
+
+	for (int i = 0; i < ubo.particleCount; i += SHARED_DATA_SIZE)
+	{
+		if (i + LocalInvocationID.x < ubo.particleCount)
+		{
+			sharedData[LocalInvocationID.x] = particles[i + LocalInvocationID.x].pos;
+		}
+		else
+		{
+			sharedData[LocalInvocationID.x] = float4(0, 0, 0, 0);
+		}
+
+		GroupMemoryBarrierWithGroupSync();
+
+		for (int j = 0; j < 256; j++)
+		{
+			float4 other = sharedData[j];
+			float3 len = other.xyz - position.xyz;
+			acceleration.xyz += GRAVITY * len * other.w / pow(dot(len, len) + SOFTEN, POWER);
+		}
+
+		GroupMemoryBarrierWithGroupSync();
+	}
+
+	particles[index].vel.xyz += ubo.deltaT * acceleration.xyz;
+
+	// Gradient texture position
+	particles[index].vel.w += 0.1 * ubo.deltaT;
+	if (particles[index].vel.w > 1.0)
+		particles[index].vel.w -= 1.0;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/computenbody/particle_calculate.comp.spv b/external/Vulkan/data/shaders/hlsl/computenbody/particle_calculate.comp.spv
new file mode 100644
index 0000000000000000000000000000000000000000..f940caef66be7ba68f83fe24e7344f9b0c90a78b
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/computenbody/particle_calculate.comp.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/computenbody/particle_integrate.comp b/external/Vulkan/data/shaders/hlsl/computenbody/particle_integrate.comp
new file mode 100644
index 0000000000000000000000000000000000000000..90065a5bf8bcb9115d4d281aea478020e94205f8
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/computenbody/particle_integrate.comp
@@ -0,0 +1,28 @@
+// Copyright 2020 Google LLC
+
+struct Particle
+{
+	float4 pos;
+	float4 vel;
+};
+
+// Binding 0 : Position storage buffer
+RWStructuredBuffer<Particle> particles : register(u0);
+
+struct UBO
+{
+	float deltaT;
+	int particleCount;
+};
+
+cbuffer ubo : register(b1) { UBO ubo; }
+
+[numthreads(256, 1, 1)]
+void main(uint3 GlobalInvocationID : SV_DispatchThreadID)
+{
+	int index = int(GlobalInvocationID.x);
+	float4 position = particles[index].pos;
+	float4 velocity = particles[index].vel;
+	position += ubo.deltaT * velocity;
+	particles[index].pos = position;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/computenbody/particle_integrate.comp.spv b/external/Vulkan/data/shaders/hlsl/computenbody/particle_integrate.comp.spv
new file mode 100644
index 0000000000000000000000000000000000000000..22e22b91ec475c569bc9966780a053e0ab8dc7a3
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/computenbody/particle_integrate.comp.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/computeparticles/particle.comp b/external/Vulkan/data/shaders/hlsl/computeparticles/particle.comp
new file mode 100644
index 0000000000000000000000000000000000000000..940faffb7a4e87e99d6bae2fd0760d0d40da317c
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/computeparticles/particle.comp
@@ -0,0 +1,74 @@
+// Copyright 2020 Google LLC
+
+struct Particle
+{
+	float2 pos;
+	float2 vel;
+	float4 gradientPos;
+};
+
+// Binding 0 : Position storage buffer
+RWStructuredBuffer<Particle> particles : register(u0);
+
+struct UBO
+{
+	float deltaT;
+	float destX;
+	float destY;
+	int particleCount;
+};
+
+cbuffer ubo : register(b1) { UBO ubo; }
+
+float2 attraction(float2 pos, float2 attractPos)
+{
+    float2 delta = attractPos - pos;
+	const float damp = 0.5;
+    float dDampedDot = dot(delta, delta) + damp;
+    float invDist = 1.0f / sqrt(dDampedDot);
+    float invDistCubed = invDist*invDist*invDist;
+    return delta * invDistCubed * 0.0035;
+}
+
+float2 repulsion(float2 pos, float2 attractPos)
+{
+	float2 delta = attractPos - pos;
+	float targetDistance = sqrt(dot(delta, delta));
+	return delta * (1.0 / (targetDistance * targetDistance * targetDistance)) * -0.000035;
+}
+
+[numthreads(256, 1, 1)]
+void main(uint3 GlobalInvocationID : SV_DispatchThreadID)
+{
+    // Current SSBO index
+    uint index = GlobalInvocationID.x;
+	// Don't try to write beyond particle count
+    if (index >= ubo.particleCount)
+		return;
+
+    // Read position and velocity
+    float2 vVel = particles[index].vel.xy;
+    float2 vPos = particles[index].pos.xy;
+
+    float2 destPos = float2(ubo.destX, ubo.destY);
+
+    float2 delta = destPos - vPos;
+    float targetDistance = sqrt(dot(delta, delta));
+    vVel += repulsion(vPos, destPos.xy) * 0.05;
+
+    // Move by velocity
+    vPos += vVel * ubo.deltaT;
+
+    // collide with boundary
+    if ((vPos.x < -1.0) || (vPos.x > 1.0) || (vPos.y < -1.0) || (vPos.y > 1.0))
+    	vVel = (-vVel * 0.1) + attraction(vPos, destPos) * 12;
+    else
+    	particles[index].pos.xy = vPos;
+
+    // Write back
+    particles[index].vel.xy = vVel;
+	particles[index].gradientPos.x += 0.02 * ubo.deltaT;
+	if (particles[index].gradientPos.x > 1.0)
+		particles[index].gradientPos.x -= 1.0;
+}
+
diff --git a/external/Vulkan/data/shaders/hlsl/computeparticles/particle.comp.spv b/external/Vulkan/data/shaders/hlsl/computeparticles/particle.comp.spv
new file mode 100644
index 0000000000000000000000000000000000000000..7a04747da81919ccdeb37455fa09d8ecea072ca3
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/computeparticles/particle.comp.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/computeparticles/particle.frag b/external/Vulkan/data/shaders/hlsl/computeparticles/particle.frag
new file mode 100644
index 0000000000000000000000000000000000000000..f3c386f81684457d632d0d4be7dcc7c35f63e75b
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/computeparticles/particle.frag
@@ -0,0 +1,22 @@
+// Copyright 2020 Google LLC
+
+Texture2D textureColorMap : register(t0);
+SamplerState samplerColorMap : register(s0);
+Texture2D textureGradientRamp : register(t1);
+SamplerState samplerGradientRamp : register(s1);
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float4 Color : COLOR0;
+[[vk::location(1)]] float GradientPos : POSITION0;
+[[vk::location(2)]] float2 CenterPos : POSITION1;
+[[vk::location(3)]] float PointSize : TEXCOORD0;
+};
+
+float4 main (VSOutput input) : SV_TARGET
+{
+	float3 color = textureGradientRamp.Sample(samplerGradientRamp, float2(input.GradientPos, 0.0)).rgb;
+	float2 PointCoord = (input.Pos.xy - input.CenterPos.xy) / input.PointSize + 0.5;
+	return float4(textureColorMap.Sample(samplerColorMap, PointCoord).rgb * color, 1.0);
+}
diff --git a/external/Vulkan/data/shaders/hlsl/computeparticles/particle.frag.spv b/external/Vulkan/data/shaders/hlsl/computeparticles/particle.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..f976843b90ef456db463ae79e4a2591ccb553a8f
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/computeparticles/particle.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/computeparticles/particle.vert b/external/Vulkan/data/shaders/hlsl/computeparticles/particle.vert
new file mode 100644
index 0000000000000000000000000000000000000000..a19c5990600c10408e09ceffa3ee79f7774296b2
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/computeparticles/particle.vert
@@ -0,0 +1,35 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float2 Pos : POSITION0;
+[[vk::location(1)]] float4 GradientPos : POSITION1;
+};
+
+struct VSOutput
+{
+  float4 Pos : SV_POSITION;
+[[vk::builtin("PointSize")]] float PSize : PSIZE;
+[[vk::location(0)]] float4 Color : COLOR0;
+[[vk::location(1)]] float GradientPos : POSITION0;
+[[vk::location(2)]] float2 CenterPos : POSITION1;
+[[vk::location(3)]] float PointSize : TEXCOORD0;
+};
+
+struct PushConsts
+{
+  float2 screendim;
+};
+
+[[vk::push_constant]] PushConsts pushConstants;
+
+VSOutput main (VSInput input)
+{
+  VSOutput output = (VSOutput)0;
+  output.PSize = output.PointSize = 8.0;
+  output.Color = float4(0.035, 0.035, 0.035, 0.035);
+  output.GradientPos = input.GradientPos.x;
+  output.Pos = float4(input.Pos.xy, 1.0, 1.0);
+	output.CenterPos = ((output.Pos.xy / output.Pos.w) + 1.0) * 0.5 * pushConstants.screendim;
+  return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/computeparticles/particle.vert.spv b/external/Vulkan/data/shaders/hlsl/computeparticles/particle.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..78ce9e1c7e558db2e74795ec43523848e782da59
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/computeparticles/particle.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/computeraytracing/raytracing.comp b/external/Vulkan/data/shaders/hlsl/computeraytracing/raytracing.comp
new file mode 100644
index 0000000000000000000000000000000000000000..9f53c7dcc2f535fa8f3b6865462e88d0705e8e34
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/computeraytracing/raytracing.comp
@@ -0,0 +1,272 @@
+// Copyright 2020 Google LLC
+
+// Shader is looseley based on the ray tracing coding session by Inigo Quilez (www.iquilezles.org)
+
+RWTexture2D<float4> resultImage : register(u0);
+
+#define EPSILON 0.0001
+#define MAXLEN 1000.0
+#define SHADOW 0.5
+#define RAYBOUNCES 2
+#define REFLECTIONS true
+#define REFLECTIONSTRENGTH 0.4
+#define REFLECTIONFALLOFF 0.5
+
+struct Camera
+{
+	float3 pos;
+	float3 lookat;
+	float fov;
+};
+
+struct UBO
+{
+	float3 lightPos;
+	float aspectRatio;
+	float4 fogColor;
+	Camera camera;
+	float4x4 rotMat;
+};
+
+cbuffer ubo : register(b1) { UBO ubo; }
+
+struct Sphere
+{
+	float3 pos;
+	float radius;
+	float3 diffuse;
+	float specular;
+	int id;
+};
+
+struct Plane
+{
+	float3 normal;
+	float distance;
+	float3 diffuse;
+	float specular;
+	int id;
+};
+
+StructuredBuffer<Sphere> spheres : register(t2);
+StructuredBuffer<Plane> planes : register(t3);
+
+void reflectRay(inout float3 rayD, in float3 mormal)
+{
+	rayD = rayD + 2.0 * -dot(mormal, rayD) * mormal;
+}
+
+// Lighting =========================================================
+
+float lightDiffuse(float3 normal, float3 lightDir)
+{
+	return clamp(dot(normal, lightDir), 0.1, 1.0);
+}
+
+float lightSpecular(float3 normal, float3 lightDir, float specularFactor)
+{
+	float3 viewVec = normalize(ubo.camera.pos);
+	float3 halfVec = normalize(lightDir + viewVec);
+	return pow(clamp(dot(normal, halfVec), 0.0, 1.0), specularFactor);
+}
+
+// Sphere ===========================================================
+
+float sphereIntersect(in float3 rayO, in float3 rayD, in Sphere sphere)
+{
+	float3 oc = rayO - sphere.pos;
+	float b = 2.0 * dot(oc, rayD);
+	float c = dot(oc, oc) - sphere.radius*sphere.radius;
+	float h = b*b - 4.0*c;
+	if (h < 0.0)
+	{
+		return -1.0;
+	}
+	float t = (-b - sqrt(h)) / 2.0;
+
+	return t;
+}
+
+float3 sphereNormal(in float3 pos, in Sphere sphere)
+{
+	return (pos - sphere.pos) / sphere.radius;
+}
+
+// Plane ===========================================================
+
+float planeIntersect(float3 rayO, float3 rayD, Plane plane)
+{
+	float d = dot(rayD, plane.normal);
+
+	if (d == 0.0)
+		return 0.0;
+
+	float t = -(plane.distance + dot(rayO, plane.normal)) / d;
+
+	if (t < 0.0)
+		return 0.0;
+
+	return t;
+}
+
+
+int intersect(in float3 rayO, in float3 rayD, inout float resT)
+{
+	int id = -1;
+
+	uint spheresLength;
+	uint spheresStride;
+	spheres.GetDimensions(spheresLength, spheresStride);
+
+	int i;
+	for (i = 0; i < spheresLength; i++)
+	{
+		float tSphere = sphereIntersect(rayO, rayD, spheres[i]);
+		if ((tSphere > EPSILON) && (tSphere < resT))
+		{
+			id = spheres[i].id;
+			resT = tSphere;
+		}
+	}
+
+	uint planesLength;
+	uint planesStride;
+	planes.GetDimensions(planesLength, planesStride);
+
+	for (i = 0; i < planesLength; i++)
+	{
+		float tplane = planeIntersect(rayO, rayD, planes[i]);
+		if ((tplane > EPSILON) && (tplane < resT))
+		{
+			id = planes[i].id;
+			resT = tplane;
+		}
+	}
+
+	return id;
+}
+
+float calcShadow(in float3 rayO, in float3 rayD, in int objectId, inout float t)
+{
+	uint spheresLength;
+	uint spheresStride;
+	spheres.GetDimensions(spheresLength, spheresStride);
+
+	for (int i = 0; i < spheresLength; i++)
+	{
+		if (spheres[i].id == objectId)
+			continue;
+		float tSphere = sphereIntersect(rayO, rayD, spheres[i]);
+		if ((tSphere > EPSILON) && (tSphere < t))
+		{
+			t = tSphere;
+			return SHADOW;
+		}
+	}
+	return 1.0;
+}
+
+float3 fog(in float t, in float3 color)
+{
+	return lerp(color, ubo.fogColor.rgb, clamp(sqrt(t*t)/20.0, 0.0, 1.0));
+}
+
+float3 renderScene(inout float3 rayO, inout float3 rayD, inout int id)
+{
+	float3 color = float3(0, 0, 0);
+	float t = MAXLEN;
+
+	// Get intersected object ID
+	int objectID = intersect(rayO, rayD, t);
+
+	if (objectID == -1)
+	{
+		return color;
+	}
+
+	float3 pos = rayO + t * rayD;
+	float3 lightVec = normalize(ubo.lightPos - pos);
+	float3 normal;
+
+	// Planes
+
+	// Spheres
+
+	uint planesLength;
+	uint planesStride;
+	planes.GetDimensions(planesLength, planesStride);
+
+	int i;
+	for (i = 0; i < planesLength; i++)
+	{
+		if (objectID == planes[i].id)
+		{
+			normal = planes[i].normal;
+			float diffuse = lightDiffuse(normal, lightVec);
+			float specular = lightSpecular(normal, lightVec, planes[i].specular);
+			color = diffuse * planes[i].diffuse + specular;
+		}
+	}
+
+	uint spheresLength;
+	uint spheresStride;
+	spheres.GetDimensions(spheresLength, spheresStride);
+
+	for (i = 0; i < spheresLength; i++)
+	{
+		if (objectID == spheres[i].id)
+		{
+			normal = sphereNormal(pos, spheres[i]);
+			float diffuse = lightDiffuse(normal, lightVec);
+			float specular = lightSpecular(normal, lightVec, spheres[i].specular);
+			color = diffuse * spheres[i].diffuse + specular;
+		}
+	}
+
+	if (id == -1)
+		return color;
+
+	id = objectID;
+
+	// Shadows
+	t = length(ubo.lightPos - pos);
+	color *= calcShadow(pos, lightVec, id, t);
+
+	// Fog
+	color = fog(t, color);
+
+	// Reflect ray for next render pass
+	reflectRay(rayD, normal);
+	rayO = pos;
+
+	return color;
+}
+
+[numthreads(16, 16, 1)]
+void main(uint3 GlobalInvocationID : SV_DispatchThreadID)
+{
+	int2 dim;
+	resultImage.GetDimensions(dim.x, dim.y);
+	float2 uv = float2(GlobalInvocationID.xy) / dim;
+
+	float3 rayO = ubo.camera.pos;
+	float3 rayD = normalize(float3((-1.0 + 2.0 * uv) * float2(ubo.aspectRatio, 1.0), -1.0));
+
+	// Basic color path
+	int id = 0;
+	float3 finalColor = renderScene(rayO, rayD, id);
+
+	// Reflection
+	if (REFLECTIONS)
+	{
+		float reflectionStrength = REFLECTIONSTRENGTH;
+		for (int i = 0; i < RAYBOUNCES; i++)
+		{
+			float3 reflectionColor = renderScene(rayO, rayD, id);
+			finalColor = (1.0 - reflectionStrength) * finalColor + reflectionStrength * lerp(reflectionColor, finalColor, 1.0 - reflectionStrength);
+			reflectionStrength *= REFLECTIONFALLOFF;
+		}
+	}
+
+	resultImage[int2(GlobalInvocationID.xy)] = float4(finalColor, 0.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/computeraytracing/raytracing.comp.spv b/external/Vulkan/data/shaders/hlsl/computeraytracing/raytracing.comp.spv
new file mode 100644
index 0000000000000000000000000000000000000000..d5a9950ef4a776ce8cc6c10ae012ebeb737ad73d
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/computeraytracing/raytracing.comp.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/computeraytracing/texture.frag b/external/Vulkan/data/shaders/hlsl/computeraytracing/texture.frag
new file mode 100644
index 0000000000000000000000000000000000000000..99e548adeac7e2b8b31b31c74914add10af01273
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/computeraytracing/texture.frag
@@ -0,0 +1,9 @@
+// Copyright 2020 Google LLC
+
+Texture2D textureColor : register(t0);
+SamplerState samplerColor : register(s0);
+
+float4 main([[vk::location(0)]] float2 inUV : TEXCOORD0) : SV_TARGET
+{
+  return textureColor.Sample(samplerColor, float2(inUV.x, 1.0 - inUV.y));
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/computeraytracing/texture.frag.spv b/external/Vulkan/data/shaders/hlsl/computeraytracing/texture.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..51a8424800684621d173d40043ec8f9a6f72ac93
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/computeraytracing/texture.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/computeraytracing/texture.vert b/external/Vulkan/data/shaders/hlsl/computeraytracing/texture.vert
new file mode 100644
index 0000000000000000000000000000000000000000..36d824cbb6d274d0a7a66b136f05b5f88702fe5b
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/computeraytracing/texture.vert
@@ -0,0 +1,15 @@
+// Copyright 2020 Google LLC
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float2 UV : TEXCOORD0;
+};
+
+VSOutput main(uint VertexIndex : SV_VertexID)
+{
+	VSOutput output = (VSOutput)0;
+	output.UV = float2((VertexIndex << 1) & 2, VertexIndex & 2);
+	output.Pos = float4(output.UV * 2.0f + -1.0f, 0.0f, 1.0f);
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/computeraytracing/texture.vert.spv b/external/Vulkan/data/shaders/hlsl/computeraytracing/texture.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..d9bb5ce5ac1267cedd8a6e5b42941fd3271f8af8
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/computeraytracing/texture.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/computeshader/edgedetect.comp b/external/Vulkan/data/shaders/hlsl/computeshader/edgedetect.comp
new file mode 100644
index 0000000000000000000000000000000000000000..f3e0d425234559bb87e4fa2594dba556309b4c18
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/computeshader/edgedetect.comp
@@ -0,0 +1,40 @@
+// Copyright 2020 Google LLC
+
+Texture2D inputImage : register(t0);
+RWTexture2D<float4> resultImage : register(u1);
+
+float conv(float kernel[9], in float data[9], in float denom, in float offset)
+{
+   float res = 0.0;
+   for (int i=0; i<9; ++i)
+   {
+      res += kernel[i] * data[i];
+   }
+   return saturate(res/denom + offset);
+}
+
+[numthreads(16, 16, 1)]
+void main(uint3 GlobalInvocationID : SV_DispatchThreadID)
+{
+	float imageData[9];
+	// Fetch neighbouring texels
+	int n = -1;
+	for (int i=-1; i<2; ++i)
+	{
+		for(int j=-1; j<2; ++j)
+		{
+			n++;
+			float3 rgb = inputImage[uint2(GlobalInvocationID.x + i, GlobalInvocationID.y + j)].rgb;
+			imageData[n] = (rgb.r + rgb.g + rgb.b) / 3.0;
+		}
+	}
+
+	float kernel[9];
+	kernel[0] = -1.0/8.0; kernel[1] = -1.0/8.0; kernel[2] = -1.0/8.0;
+	kernel[3] = -1.0/8.0; kernel[4] =  1.0;     kernel[5] = -1.0/8.0;
+	kernel[6] = -1.0/8.0; kernel[7] = -1.0/8.0; kernel[8] = -1.0/8.0;
+
+	float4 res = float4(conv(kernel, imageData, 0.1, 0.0).xxx, 1.0);
+
+	resultImage[int2(GlobalInvocationID.xy)] = res;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/computeshader/edgedetect.comp.spv b/external/Vulkan/data/shaders/hlsl/computeshader/edgedetect.comp.spv
new file mode 100644
index 0000000000000000000000000000000000000000..f06a054baf4ceccdd371b05c54ea539286883520
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/computeshader/edgedetect.comp.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/computeshader/emboss.comp b/external/Vulkan/data/shaders/hlsl/computeshader/emboss.comp
new file mode 100644
index 0000000000000000000000000000000000000000..f2a070277d56489bf08122c3ac57ed0e200b74e1
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/computeshader/emboss.comp
@@ -0,0 +1,40 @@
+// Copyright 2020 Google LLC
+
+Texture2D inputImage : register(t0);
+RWTexture2D<float4> resultImage : register(u1);
+
+float conv(in float kernel[9], in float data[9], in float denom, in float offset)
+{
+   float res = 0.0;
+   for (int i=0; i<9; ++i)
+   {
+      res += kernel[i] * data[i];
+   }
+   return saturate(res/denom + offset);
+}
+
+[numthreads(16, 16, 1)]
+void main(uint3 GlobalInvocationID : SV_DispatchThreadID)
+{
+	float imageData[9];
+	// Fetch neighbouring texels
+	int n = -1;
+	for (int i=-1; i<2; ++i)
+	{
+		for(int j=-1; j<2; ++j)
+		{
+			n++;
+			float3 rgb = inputImage[uint2(GlobalInvocationID.x + i, GlobalInvocationID.y + j)].rgb;
+			imageData[n] = (rgb.r + rgb.g + rgb.b) / 3.0;
+		}
+	}
+
+	float kernel[9];
+	kernel[0] = -1.0; kernel[1] =  0.0; kernel[2] =  0.0;
+	kernel[3] = 0.0; kernel[4] = -1.0; kernel[5] =  0.0;
+	kernel[6] = 0.0; kernel[7] =  0.0; kernel[8] = 2.0;
+
+	float4 res = float4(conv(kernel, imageData, 1.0, 0.50).xxx, 1.0);
+
+	resultImage[int2(GlobalInvocationID.xy)] = res;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/computeshader/emboss.comp.spv b/external/Vulkan/data/shaders/hlsl/computeshader/emboss.comp.spv
new file mode 100644
index 0000000000000000000000000000000000000000..98a666b4b999a097294bf05fb259832a91f655e3
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/computeshader/emboss.comp.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/computeshader/sharpen.comp b/external/Vulkan/data/shaders/hlsl/computeshader/sharpen.comp
new file mode 100644
index 0000000000000000000000000000000000000000..90a58266e193e0b8029fa88cec9cf6f654c810d9
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/computeshader/sharpen.comp
@@ -0,0 +1,49 @@
+// Copyright 2020 Google LLC
+
+Texture2D inputImage : register(t0);
+RWTexture2D<float4> resultImage : register(u1);
+
+float conv(in float kernel[9], in float data[9], in float denom, in float offset)
+{
+   float res = 0.0;
+   for (int i=0; i<9; ++i)
+   {
+      res += kernel[i] * data[i];
+   }
+   return saturate(res/denom + offset);
+}
+
+[numthreads(16, 16, 1)]
+void main(uint3 GlobalInvocationID : SV_DispatchThreadID)
+{
+	float r[9];
+	float g[9];
+	float b[9];
+
+	// Fetch neighbouring texels
+	int n = -1;
+	for (int i=-1; i<2; ++i)
+	{
+		for(int j=-1; j<2; ++j)
+		{
+			n++;
+			float3 rgb = inputImage[uint2(GlobalInvocationID.x + i, GlobalInvocationID.y + j)].rgb;
+			r[n] = rgb.r;
+			g[n] = rgb.g;
+			b[n] = rgb.b;
+		}
+	}
+
+	float kernel[9];
+	kernel[0] = -1.0; kernel[1] = -1.0; kernel[2] = -1.0;
+	kernel[3] = -1.0; kernel[4] =  9.0; kernel[5] = -1.0;
+	kernel[6] = -1.0; kernel[7] = -1.0; kernel[8] = -1.0;
+
+	float4 res = float4(
+		conv(kernel, r, 1.0, 0.0),
+		conv(kernel, g, 1.0, 0.0),
+		conv(kernel, b, 1.0, 0.0),
+		1.0);
+
+	resultImage[int2(GlobalInvocationID.xy)] = res;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/computeshader/sharpen.comp.spv b/external/Vulkan/data/shaders/hlsl/computeshader/sharpen.comp.spv
new file mode 100644
index 0000000000000000000000000000000000000000..b3b013cd6c491f0f08732caff4e315c51338e8a0
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/computeshader/sharpen.comp.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/computeshader/texture.frag b/external/Vulkan/data/shaders/hlsl/computeshader/texture.frag
new file mode 100644
index 0000000000000000000000000000000000000000..997a9419d2ae1002497bb1c7f6de19336651be29
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/computeshader/texture.frag
@@ -0,0 +1,9 @@
+// Copyright 2020 Google LLC
+
+Texture2D textureColor : register(t1);
+SamplerState samplerColor : register(s1);
+
+float4 main([[vk::location(0)]] float2 inUV : TEXCOORD0) : SV_TARGET
+{
+  return textureColor.Sample(samplerColor, inUV);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/computeshader/texture.frag.spv b/external/Vulkan/data/shaders/hlsl/computeshader/texture.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..5a671efa5eba157bb3ed34066e4211b4a0ee9bc0
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/computeshader/texture.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/computeshader/texture.vert b/external/Vulkan/data/shaders/hlsl/computeshader/texture.vert
new file mode 100644
index 0000000000000000000000000000000000000000..8021fe9511259fb2f68de7ba809996c0bc0e3f09
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/computeshader/texture.vert
@@ -0,0 +1,29 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+	[[vk::location(0)]] float2 UV : TEXCOORD0;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.UV = input.UV;
+	output.Pos = mul(ubo.projection, mul(ubo.model, float4(input.Pos.xyz, 1.0)));
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/computeshader/texture.vert.spv b/external/Vulkan/data/shaders/hlsl/computeshader/texture.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..5b4eecdc002919dcab4bc412bdaa2556efc36751
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/computeshader/texture.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/conditionalrender/model.frag b/external/Vulkan/data/shaders/hlsl/conditionalrender/model.frag
new file mode 100644
index 0000000000000000000000000000000000000000..2c14be89d22b2ad92388e59a39d4d05f86f0658c
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/conditionalrender/model.frag
@@ -0,0 +1,21 @@
+// Copyright 2020 Google LLC
+
+struct VSOutput
+{
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(3)]] float3 LightVec : TEXCOORD2;
+};
+
+float4 main(VSOutput input) : SV_TARGET
+{
+	float3 N = normalize(input.Normal);
+	float3 L = normalize(input.LightVec);
+	float3 V = normalize(input.ViewVec);
+	float3 R = reflect(-L, N);
+	float3 ambient = float3(0.1, 0.1, 0.1);
+	float3 diffuse = max(dot(N, L), 0.0) * float3(1.0, 1.0, 1.0);
+	float3 specular = pow(max(dot(R, V), 0.0), 16.0) * float3(0.75, 0.75, 0.75);
+	return float4((ambient + diffuse) * input.Color.rgb + specular, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/conditionalrender/model.frag.spv b/external/Vulkan/data/shaders/hlsl/conditionalrender/model.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..6966e9ad87589fb7abd6317ca898f0e8a94a36b1
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/conditionalrender/model.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/conditionalrender/model.vert b/external/Vulkan/data/shaders/hlsl/conditionalrender/model.vert
new file mode 100644
index 0000000000000000000000000000000000000000..929321e16829b3abfbfabcc58ce53aa77f7b085e
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/conditionalrender/model.vert
@@ -0,0 +1,57 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float3 Color : COLOR0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 view;
+	float4x4 model;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct Node
+{
+	float4x4 transform;
+};
+
+cbuffer NodeBuf : register(b0, space1) { Node node; }
+
+struct PushConstant
+{
+	float4 baseColorFactor;
+};
+
+[[vk::push_constant]] PushConstant material;
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(3)]] float3 LightVec : TEXCOORD2;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Normal = input.Normal;
+	output.Color = material.baseColorFactor.rgb;
+	float4 pos = float4(input.Pos, 1.0);
+	output.Pos = mul(ubo.projection, mul(ubo.view, mul(ubo.model, mul(node.transform, pos))));
+
+	output.Normal = mul((float4x3)mul(ubo.view, mul(ubo.model, node.transform)), input.Normal).xyz;
+
+	float4 localpos = mul(ubo.view, mul(ubo.model, mul(node.transform, pos)));
+	float3 lightPos = float3(10.0f, -10.0f, 10.0f);
+	output.LightVec = lightPos.xyz - localpos.xyz;
+	output.ViewVec = -localpos.xyz;
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/conditionalrender/model.vert.spv b/external/Vulkan/data/shaders/hlsl/conditionalrender/model.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..b64747cc2d645469bb74d85b14a7968ff4c51318
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/conditionalrender/model.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/conservativeraster/fullscreen.frag b/external/Vulkan/data/shaders/hlsl/conservativeraster/fullscreen.frag
new file mode 100644
index 0000000000000000000000000000000000000000..661ddf44838087cfce5664fd8fd01bc24f63b986
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/conservativeraster/fullscreen.frag
@@ -0,0 +1,9 @@
+// Copyright 2020 Google LLC
+
+Texture2D textureColor : register(t1);
+SamplerState samplerColor : register(s1);
+
+float4 main([[vk::location(0)]] float2 inUV : TEXCOORD0) : SV_TARGET
+{
+	return textureColor.Sample(samplerColor, inUV);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/conservativeraster/fullscreen.frag.spv b/external/Vulkan/data/shaders/hlsl/conservativeraster/fullscreen.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..5a671efa5eba157bb3ed34066e4211b4a0ee9bc0
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/conservativeraster/fullscreen.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/conservativeraster/fullscreen.vert b/external/Vulkan/data/shaders/hlsl/conservativeraster/fullscreen.vert
new file mode 100644
index 0000000000000000000000000000000000000000..b13c2bf2353ce1fc1837fef4db32336429c22412
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/conservativeraster/fullscreen.vert
@@ -0,0 +1,15 @@
+// Copyright 2020 Google LLC
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float2 UV : TEXCOORD0;
+};
+
+VSOutput main(uint VertexIndex : SV_VertexID)
+{
+	VSOutput output = (VSOutput)0;
+	output.UV = float2((VertexIndex << 1) & 2, VertexIndex & 2);
+	output.Pos = float4(output.UV * 2.0f - 1.0f, 0.0f, 1.0f);
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/conservativeraster/fullscreen.vert.spv b/external/Vulkan/data/shaders/hlsl/conservativeraster/fullscreen.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..46c4b31fdc1568cc56662cc746d3a2d10f9d0983
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/conservativeraster/fullscreen.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/conservativeraster/triangle.frag b/external/Vulkan/data/shaders/hlsl/conservativeraster/triangle.frag
new file mode 100644
index 0000000000000000000000000000000000000000..2387dd1480c6456efb94be7ad69b1713b92c03c4
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/conservativeraster/triangle.frag
@@ -0,0 +1,6 @@
+// Copyright 2020 Google LLC
+
+float4 main([[vk::location(0)]] float3 Color : COLOR0) : SV_TARGET
+{
+	return float4(Color, 1);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/conservativeraster/triangle.frag.spv b/external/Vulkan/data/shaders/hlsl/conservativeraster/triangle.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..870fb6c12ac71dca568d8358a955b81f6eca7faf
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/conservativeraster/triangle.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/conservativeraster/triangle.vert b/external/Vulkan/data/shaders/hlsl/conservativeraster/triangle.vert
new file mode 100644
index 0000000000000000000000000000000000000000..e7c529be7ad840b63baae05a291391a24db50928
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/conservativeraster/triangle.vert
@@ -0,0 +1,29 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float3 Color : COLOR0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Color : COLOR0;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Color = input.Color;
+	output.Pos = mul(ubo.projection, mul(ubo.model, float4(input.Pos, 1.0)));
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/conservativeraster/triangle.vert.spv b/external/Vulkan/data/shaders/hlsl/conservativeraster/triangle.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..66b032f99823fbc55debfeea60e8770d4c1e4ff1
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/conservativeraster/triangle.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/conservativeraster/triangleoverlay.frag b/external/Vulkan/data/shaders/hlsl/conservativeraster/triangleoverlay.frag
new file mode 100644
index 0000000000000000000000000000000000000000..9892e7093817a40915d6c6127bbd5a0c2dee8fef
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/conservativeraster/triangleoverlay.frag
@@ -0,0 +1,6 @@
+// Copyright 2020 Google LLC
+
+float4 main() : SV_TARGET
+{
+	return float4(1.0, 1.0, 1.0, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/conservativeraster/triangleoverlay.frag.spv b/external/Vulkan/data/shaders/hlsl/conservativeraster/triangleoverlay.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..da33b821eaf29f96aa6488c58e046539c523e972
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/conservativeraster/triangleoverlay.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/debugmarker/colorpass.frag b/external/Vulkan/data/shaders/hlsl/debugmarker/colorpass.frag
new file mode 100644
index 0000000000000000000000000000000000000000..2387dd1480c6456efb94be7ad69b1713b92c03c4
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/debugmarker/colorpass.frag
@@ -0,0 +1,6 @@
+// Copyright 2020 Google LLC
+
+float4 main([[vk::location(0)]] float3 Color : COLOR0) : SV_TARGET
+{
+	return float4(Color, 1);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/debugmarker/colorpass.frag.spv b/external/Vulkan/data/shaders/hlsl/debugmarker/colorpass.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..870fb6c12ac71dca568d8358a955b81f6eca7faf
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/debugmarker/colorpass.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/debugmarker/colorpass.vert b/external/Vulkan/data/shaders/hlsl/debugmarker/colorpass.vert
new file mode 100644
index 0000000000000000000000000000000000000000..089cd426f378f848478804af47e75584ae7826b6
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/debugmarker/colorpass.vert
@@ -0,0 +1,29 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float4 Pos : POSITION0;
+[[vk::location(3)]] float3 Color : COLOR0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Color : COLOR0;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Color = input.Color;
+	output.Pos = mul(ubo.projection, mul(ubo.model, input.Pos));
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/debugmarker/colorpass.vert.spv b/external/Vulkan/data/shaders/hlsl/debugmarker/colorpass.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..cde88b5fcd7a4ae6a9800984cc21bc9f292bf62a
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/debugmarker/colorpass.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/debugmarker/postprocess.frag b/external/Vulkan/data/shaders/hlsl/debugmarker/postprocess.frag
new file mode 100644
index 0000000000000000000000000000000000000000..32bef01b15aeca944789c761b7edf6d21068e7d3
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/debugmarker/postprocess.frag
@@ -0,0 +1,36 @@
+// Copyright 2020 Google LLC
+
+Texture2D textureColor : register(t1);
+SamplerState samplerColor : register(s1);
+
+float4 main([[vk::location(0)]] float2 inUV : TEXCOORD0) : SV_TARGET
+{
+	// Single pass gauss blur
+
+	const float2 texOffset = float2(0.01, 0.01);
+
+	float2 tc0 = inUV + float2(-texOffset.x, -texOffset.y);
+	float2 tc1 = inUV + float2(         0.0, -texOffset.y);
+	float2 tc2 = inUV + float2(+texOffset.x, -texOffset.y);
+	float2 tc3 = inUV + float2(-texOffset.x,          0.0);
+	float2 tc4 = inUV + float2(         0.0,          0.0);
+	float2 tc5 = inUV + float2(+texOffset.x,          0.0);
+	float2 tc6 = inUV + float2(-texOffset.x, +texOffset.y);
+	float2 tc7 = inUV + float2(         0.0, +texOffset.y);
+	float2 tc8 = inUV + float2(+texOffset.x, +texOffset.y);
+
+	float4 col0 = textureColor.Sample(samplerColor, tc0);
+	float4 col1 = textureColor.Sample(samplerColor, tc1);
+	float4 col2 = textureColor.Sample(samplerColor, tc2);
+	float4 col3 = textureColor.Sample(samplerColor, tc3);
+	float4 col4 = textureColor.Sample(samplerColor, tc4);
+	float4 col5 = textureColor.Sample(samplerColor, tc5);
+	float4 col6 = textureColor.Sample(samplerColor, tc6);
+	float4 col7 = textureColor.Sample(samplerColor, tc7);
+	float4 col8 = textureColor.Sample(samplerColor, tc8);
+
+	float4 sum = (1.0 * col0 + 2.0 * col1 + 1.0 * col2 +
+			  2.0 * col3 + 4.0 * col4 + 2.0 * col5 +
+			  1.0 * col6 + 2.0 * col7 + 1.0 * col8) / 16.0;
+	return float4(sum.rgb, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/debugmarker/postprocess.frag.spv b/external/Vulkan/data/shaders/hlsl/debugmarker/postprocess.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..7a15cf74b4db0a0da5ca67e10e6faedd1f134016
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/debugmarker/postprocess.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/debugmarker/postprocess.vert b/external/Vulkan/data/shaders/hlsl/debugmarker/postprocess.vert
new file mode 100644
index 0000000000000000000000000000000000000000..fca6ef512d4626e7d9387af93198cf9e05d67cb2
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/debugmarker/postprocess.vert
@@ -0,0 +1,15 @@
+// Copyright 2020 Google LLC
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float2 UV : TEXCOORD0;
+};
+
+VSOutput main(uint VertexIndex : SV_VertexID)
+{
+	VSOutput output = (VSOutput)0;
+	output.UV = float2((VertexIndex << 1) & 2, VertexIndex & 2);
+	output.Pos = float4(output.UV * float2(2.0f, 2.0f) + float2(-1.0f, -1.0f), 0.0f, 1.0f);
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/debugmarker/postprocess.vert.spv b/external/Vulkan/data/shaders/hlsl/debugmarker/postprocess.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..e17bb01671266c1f9d554ec0437ce57a69b308af
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/debugmarker/postprocess.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/debugmarker/toon.frag b/external/Vulkan/data/shaders/hlsl/debugmarker/toon.frag
new file mode 100644
index 0000000000000000000000000000000000000000..fa2e7a356dea06e491078be40844132a3500dc01
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/debugmarker/toon.frag
@@ -0,0 +1,37 @@
+// Copyright 2020 Google LLC
+
+Texture2D textureColorMap : register(t1);
+SamplerState samplerColorMap : register(s1);
+
+struct VSOutput
+{
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float2 UV : TEXCOORD0;
+[[vk::location(3)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(4)]] float3 LightVec : TEXCOORD2;
+};
+
+float4 main(VSOutput input) : SV_TARGET
+{
+	// Desaturate color
+    float3 color = float3(lerp(input.Color, dot(float3(0.2126,0.7152,0.0722), input.Color).xxx, 0.65));
+
+	// High ambient colors because mesh materials are pretty dark
+	float3 ambient = color * float3(1.0, 1.0, 1.0);
+	float3 N = normalize(input.Normal);
+	float3 L = normalize(input.LightVec);
+	float3 V = normalize(input.ViewVec);
+	float3 R = reflect(-L, N);
+	float3 diffuse = max(dot(N, L), 0.0) * color;
+	float3 specular = pow(max(dot(R, V), 0.0), 16.0) * float3(0.75, 0.75, 0.75);
+
+	float intensity = dot(N,L);
+	float shade = 1.0;
+	shade = intensity < 0.5 ? 0.75 : shade;
+	shade = intensity < 0.35 ? 0.6 : shade;
+	shade = intensity < 0.25 ? 0.5 : shade;
+	shade = intensity < 0.1 ? 0.25 : shade;
+
+	return float4(input.Color * 3.0 * shade, 1);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/debugmarker/toon.frag.spv b/external/Vulkan/data/shaders/hlsl/debugmarker/toon.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..168f5b63a2bb83d33796d0c803701730fbd6b886
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/debugmarker/toon.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/debugmarker/toon.vert b/external/Vulkan/data/shaders/hlsl/debugmarker/toon.vert
new file mode 100644
index 0000000000000000000000000000000000000000..3755608dd89887dfcfb0251e3a1d9a333911061e
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/debugmarker/toon.vert
@@ -0,0 +1,44 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float2 UV : TEXCOORD0;
+[[vk::location(3)]] float3 Color : COLOR0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+	float4 lightPos;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float2 UV : TEXCOORD0;
+[[vk::location(3)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(4)]] float3 LightVec : TEXCOORD2;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Normal = input.Normal;
+	output.Color = input.Color;
+	output.UV = input.UV;
+	output.Pos = mul(ubo.projection, mul(ubo.model, float4(input.Pos.xyz, 1.0)));
+
+	float4 pos = mul(ubo.model, float4(input.Pos, 1.0));
+	output.Normal = mul((float4x3)ubo.model, input.Normal).xyz;
+	float3 lPos = mul((float4x3)ubo.model, ubo.lightPos.xyz).xyz;
+	output.LightVec = lPos - pos.xyz;
+	output.ViewVec = -pos.xyz;
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/debugmarker/toon.vert.spv b/external/Vulkan/data/shaders/hlsl/debugmarker/toon.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..c06a7e59efc7deba345b85a1428ba9b555599367
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/debugmarker/toon.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/deferred/deferred.frag b/external/Vulkan/data/shaders/hlsl/deferred/deferred.frag
new file mode 100644
index 0000000000000000000000000000000000000000..fbcaab67776e0a0c465d975bc7d9f43a2edda139
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/deferred/deferred.frag
@@ -0,0 +1,95 @@
+// Copyright 2020 Google LLC
+
+Texture2D textureposition : register(t1);
+SamplerState samplerposition : register(s1);
+Texture2D textureNormal : register(t2);
+SamplerState samplerNormal : register(s2);
+Texture2D textureAlbedo : register(t3);
+SamplerState samplerAlbedo : register(s3);
+
+struct Light {
+	float4 position;
+	float3 color;
+	float radius;
+};
+
+struct UBO
+{
+	Light lights[6];
+	float4 viewPos;
+	int displayDebugTarget;
+};
+
+cbuffer ubo : register(b4) { UBO ubo; }
+
+
+float4 main([[vk::location(0)]] float2 inUV : TEXCOORD0) : SV_TARGET
+{
+	// Get G-Buffer values
+	float3 fragPos = textureposition.Sample(samplerposition, inUV).rgb;
+	float3 normal = textureNormal.Sample(samplerNormal, inUV).rgb;
+	float4 albedo = textureAlbedo.Sample(samplerAlbedo, inUV);
+
+	float3 fragcolor;
+
+	// Debug display
+	if (ubo.displayDebugTarget > 0) {
+		switch (ubo.displayDebugTarget) {
+			case 1: 
+				fragcolor.rgb = fragPos;
+				break;
+			case 2: 
+				fragcolor.rgb = normal;
+				break;
+			case 3: 
+				fragcolor.rgb = albedo.rgb;
+				break;
+			case 4: 
+				fragcolor.rgb = albedo.aaa;
+				break;
+		}		
+		return float4(fragcolor, 1.0);
+	}
+
+	#define lightCount 6
+	#define ambient 0.0
+
+	// Ambient part
+	fragcolor = albedo.rgb * ambient;
+
+	for(int i = 0; i < lightCount; ++i)
+	{
+		// Vector to light
+		float3 L = ubo.lights[i].position.xyz - fragPos;
+		// Distance from light to fragment position
+		float dist = length(L);
+
+		// Viewer to fragment
+		float3 V = ubo.viewPos.xyz - fragPos;
+		V = normalize(V);
+
+		//if(dist < ubo.lights[i].radius)
+		{
+			// Light to fragment
+			L = normalize(L);
+
+			// Attenuation
+			float atten = ubo.lights[i].radius / (pow(dist, 2.0) + 1.0);
+
+			// Diffuse part
+			float3 N = normalize(normal);
+			float NdotL = max(0.0, dot(N, L));
+			float3 diff = ubo.lights[i].color * albedo.rgb * NdotL * atten;
+
+			// Specular part
+			// Specular map values are stored in alpha of albedo mrt
+			float3 R = reflect(-L, N);
+			float NdotR = max(0.0, dot(R, V));
+			float3 spec = ubo.lights[i].color * albedo.a * pow(NdotR, 16.0) * atten;
+
+			fragcolor += diff + spec;
+		}
+	}
+
+  return float4(fragcolor, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/deferred/deferred.frag.spv b/external/Vulkan/data/shaders/hlsl/deferred/deferred.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..cbaceb9195092b148f1819dc35f6aca65d74ae3a
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/deferred/deferred.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/deferred/deferred.vert b/external/Vulkan/data/shaders/hlsl/deferred/deferred.vert
new file mode 100644
index 0000000000000000000000000000000000000000..b13c2bf2353ce1fc1837fef4db32336429c22412
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/deferred/deferred.vert
@@ -0,0 +1,15 @@
+// Copyright 2020 Google LLC
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float2 UV : TEXCOORD0;
+};
+
+VSOutput main(uint VertexIndex : SV_VertexID)
+{
+	VSOutput output = (VSOutput)0;
+	output.UV = float2((VertexIndex << 1) & 2, VertexIndex & 2);
+	output.Pos = float4(output.UV * 2.0f - 1.0f, 0.0f, 1.0f);
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/deferred/deferred.vert.spv b/external/Vulkan/data/shaders/hlsl/deferred/deferred.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..8754eaa14270ad09ba3893fff6aa51f5732ff9c9
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/deferred/deferred.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/deferred/mrt.frag b/external/Vulkan/data/shaders/hlsl/deferred/mrt.frag
new file mode 100644
index 0000000000000000000000000000000000000000..7b8998215456a02ee71ef76f0239e0009ee898c6
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/deferred/mrt.frag
@@ -0,0 +1,39 @@
+// Copyright 2020 Google LLC
+
+Texture2D textureColor : register(t1);
+SamplerState samplerColor : register(s1);
+Texture2D textureNormalMap : register(t2);
+SamplerState samplerNormalMap : register(s2);
+
+struct VSOutput
+{
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+[[vk::location(2)]] float3 Color : COLOR0;
+[[vk::location(3)]] float3 WorldPos : POSITION0;
+[[vk::location(4)]] float3 Tangent : TEXCOORD1;
+};
+
+struct FSOutput
+{
+	float4 Position : SV_TARGET0;
+	float4 Normal : SV_TARGET1;
+	float4 Albedo : SV_TARGET2;
+};
+
+FSOutput main(VSOutput input)
+{
+	FSOutput output = (FSOutput)0;
+	output.Position = float4(input.WorldPos, 1.0);
+
+	// Calculate normal in tangent space
+	float3 N = normalize(input.Normal);
+	float3 T = normalize(input.Tangent);
+	float3 B = cross(N, T);
+	float3x3 TBN = float3x3(T, B, N);
+	float3 tnorm = mul(normalize(textureNormalMap.Sample(samplerNormalMap, input.UV).xyz * 2.0 - float3(1.0, 1.0, 1.0)), TBN);
+	output.Normal = float4(tnorm, 1.0);
+
+	output.Albedo = textureColor.Sample(samplerColor, input.UV);
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/deferred/mrt.frag.spv b/external/Vulkan/data/shaders/hlsl/deferred/mrt.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..a2085df98b35e94b7fc4c66dae7ff48601335695
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/deferred/mrt.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/deferred/mrt.vert b/external/Vulkan/data/shaders/hlsl/deferred/mrt.vert
new file mode 100644
index 0000000000000000000000000000000000000000..35254573b6cca13bc4b83fcb8addec1fd1fb0d8f
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/deferred/mrt.vert
@@ -0,0 +1,51 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float4 Pos : POSITION0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+[[vk::location(2)]] float3 Color : COLOR0;
+[[vk::location(3)]] float3 Normal : NORMAL0;
+[[vk::location(4)]] float3 Tangent : TEXCOORD1;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+	float4x4 view;
+	float4 instancePos[3];
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+[[vk::location(2)]] float3 Color : COLOR0;
+[[vk::location(3)]] float3 WorldPos : POSITION0;
+[[vk::location(4)]] float3 Tangent : TEXCOORD1;
+};
+
+VSOutput main(VSInput input, uint InstanceIndex : SV_InstanceID)
+{
+	VSOutput output = (VSOutput)0;
+	float4 tmpPos = input.Pos + ubo.instancePos[InstanceIndex];
+
+	output.Pos = mul(ubo.projection, mul(ubo.view, mul(ubo.model, tmpPos)));
+
+	output.UV = input.UV;
+
+	// Vertex position in world space
+	output.WorldPos = mul(ubo.model, tmpPos).xyz;
+
+	// Normal in world space
+	output.Normal = normalize(input.Normal);
+	output.Tangent = normalize(input.Tangent);
+
+	// Currently just vertex color
+	output.Color = input.Color;
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/deferred/mrt.vert.spv b/external/Vulkan/data/shaders/hlsl/deferred/mrt.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..31f2eafb6c7244da1494f3a53d7e1dc32bef7a40
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/deferred/mrt.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/deferredmultisampling/deferred.frag b/external/Vulkan/data/shaders/hlsl/deferredmultisampling/deferred.frag
new file mode 100644
index 0000000000000000000000000000000000000000..ea1081c922633da14e522c1694af0aabcf598a75
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/deferredmultisampling/deferred.frag
@@ -0,0 +1,125 @@
+// Copyright 2020 Google LLC
+
+Texture2DMS<float4> texturePosition : register(t1);
+SamplerState samplerPosition : register(s1);
+Texture2DMS<float4> textureNormal : register(t2);
+SamplerState samplerNormal : register(s2);
+Texture2DMS<float4> textureAlbedo : register(t3);
+SamplerState samplerAlbedo : register(s3);
+
+struct Light {
+	float4 position;
+	float3 color;
+	float radius;
+};
+
+struct UBO
+{
+	Light lights[6];
+	float4 viewPos;
+	int debugDisplayTarget;
+};
+
+cbuffer ubo : register(b4) { UBO ubo; }
+
+[[vk::constant_id(0)]] const int NUM_SAMPLES = 8;
+
+#define NUM_LIGHTS 6
+
+// Manual resolve for MSAA samples
+float4 resolve(Texture2DMS<float4> tex, int2 uv)
+{
+	float4 result = float4(0.0, 0.0, 0.0, 0.0);
+	for (int i = 0; i < NUM_SAMPLES; i++)
+	{
+		uint status = 0;
+		float4 val = tex.Load(uv, i, int2(0, 0), status);
+		result += val;
+	}
+	// Average resolved samples
+	return result / float(NUM_SAMPLES);
+}
+
+float3 calculateLighting(float3 pos, float3 normal, float4 albedo)
+{
+	float3 result = float3(0.0, 0.0, 0.0);
+
+	for(int i = 0; i < NUM_LIGHTS; ++i)
+	{
+		// Vector to light
+		float3 L = ubo.lights[i].position.xyz - pos;
+		// Distance from light to fragment position
+		float dist = length(L);
+
+		// Viewer to fragment
+		float3 V = ubo.viewPos.xyz - pos;
+		V = normalize(V);
+
+		// Light to fragment
+		L = normalize(L);
+
+		// Attenuation
+		float atten = ubo.lights[i].radius / (pow(dist, 2.0) + 1.0);
+
+		// Diffuse part
+		float3 N = normalize(normal);
+		float NdotL = max(0.0, dot(N, L));
+		float3 diff = ubo.lights[i].color * albedo.rgb * NdotL * atten;
+
+		// Specular part
+		float3 R = reflect(-L, N);
+		float NdotR = max(0.0, dot(R, V));
+		float3 spec = ubo.lights[i].color * albedo.a * pow(NdotR, 8.0) * atten;
+
+		result += diff + spec;
+	}
+	return result;
+}
+
+float4 main([[vk::location(0)]] float2 inUV : TEXCOORD0) : SV_TARGET
+{
+	int2 attDim; int sampleCount;
+	texturePosition.GetDimensions(attDim.x, attDim.y, sampleCount);
+	int2 UV = int2(inUV * attDim);
+
+	float3 fragColor;
+	uint status = 0;
+
+	// Debug display
+	if (ubo.debugDisplayTarget > 0) {
+		switch (ubo.debugDisplayTarget) {
+			case 1: 
+				fragColor.rgb = texturePosition.Load(UV, 0, int2(0, 0), status).rgb;
+				break;
+			case 2: 
+				fragColor.rgb = textureNormal.Load(UV, 0, int2(0, 0), status).rgb;
+				break;
+			case 3: 
+				fragColor.rgb = textureAlbedo.Load(UV, 0, int2(0, 0), status).rgb;
+				break;
+			case 4: 
+				fragColor.rgb = textureAlbedo.Load(UV, 0, int2(0, 0), status).aaa;
+				break;
+		}		
+		return float4(fragColor, 1.0);
+	}
+
+	#define ambient 0.15
+
+	// Ambient part
+	float4 alb = resolve(textureAlbedo, UV);
+	fragColor = float3(0.0, 0.0, 0.0);
+
+	// Calualte lighting for every MSAA sample
+	for (int i = 0; i < NUM_SAMPLES; i++)
+	{
+		float3 pos = texturePosition.Load(UV, i, int2(0, 0), status).rgb;
+		float3 normal = textureNormal.Load(UV, i, int2(0, 0), status).rgb;
+		float4 albedo = textureAlbedo.Load(UV, i, int2(0, 0), status);
+		fragColor += calculateLighting(pos, normal, albedo);
+	}
+
+	fragColor = (alb.rgb * ambient) + fragColor / float(NUM_SAMPLES);
+
+	return float4(fragColor, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/deferredmultisampling/deferred.frag.spv b/external/Vulkan/data/shaders/hlsl/deferredmultisampling/deferred.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..253f0e11b183c050d7b45e7d68192486d0d1465d
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/deferredmultisampling/deferred.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/deferredmultisampling/deferred.vert b/external/Vulkan/data/shaders/hlsl/deferredmultisampling/deferred.vert
new file mode 100644
index 0000000000000000000000000000000000000000..b13c2bf2353ce1fc1837fef4db32336429c22412
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/deferredmultisampling/deferred.vert
@@ -0,0 +1,15 @@
+// Copyright 2020 Google LLC
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float2 UV : TEXCOORD0;
+};
+
+VSOutput main(uint VertexIndex : SV_VertexID)
+{
+	VSOutput output = (VSOutput)0;
+	output.UV = float2((VertexIndex << 1) & 2, VertexIndex & 2);
+	output.Pos = float4(output.UV * 2.0f - 1.0f, 0.0f, 1.0f);
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/deferredmultisampling/deferred.vert.spv b/external/Vulkan/data/shaders/hlsl/deferredmultisampling/deferred.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..8754eaa14270ad09ba3893fff6aa51f5732ff9c9
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/deferredmultisampling/deferred.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/deferredmultisampling/mrt.frag b/external/Vulkan/data/shaders/hlsl/deferredmultisampling/mrt.frag
new file mode 100644
index 0000000000000000000000000000000000000000..7b8998215456a02ee71ef76f0239e0009ee898c6
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/deferredmultisampling/mrt.frag
@@ -0,0 +1,39 @@
+// Copyright 2020 Google LLC
+
+Texture2D textureColor : register(t1);
+SamplerState samplerColor : register(s1);
+Texture2D textureNormalMap : register(t2);
+SamplerState samplerNormalMap : register(s2);
+
+struct VSOutput
+{
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+[[vk::location(2)]] float3 Color : COLOR0;
+[[vk::location(3)]] float3 WorldPos : POSITION0;
+[[vk::location(4)]] float3 Tangent : TEXCOORD1;
+};
+
+struct FSOutput
+{
+	float4 Position : SV_TARGET0;
+	float4 Normal : SV_TARGET1;
+	float4 Albedo : SV_TARGET2;
+};
+
+FSOutput main(VSOutput input)
+{
+	FSOutput output = (FSOutput)0;
+	output.Position = float4(input.WorldPos, 1.0);
+
+	// Calculate normal in tangent space
+	float3 N = normalize(input.Normal);
+	float3 T = normalize(input.Tangent);
+	float3 B = cross(N, T);
+	float3x3 TBN = float3x3(T, B, N);
+	float3 tnorm = mul(normalize(textureNormalMap.Sample(samplerNormalMap, input.UV).xyz * 2.0 - float3(1.0, 1.0, 1.0)), TBN);
+	output.Normal = float4(tnorm, 1.0);
+
+	output.Albedo = textureColor.Sample(samplerColor, input.UV);
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/deferredmultisampling/mrt.frag.spv b/external/Vulkan/data/shaders/hlsl/deferredmultisampling/mrt.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..a2085df98b35e94b7fc4c66dae7ff48601335695
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/deferredmultisampling/mrt.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/deferredmultisampling/mrt.vert b/external/Vulkan/data/shaders/hlsl/deferredmultisampling/mrt.vert
new file mode 100644
index 0000000000000000000000000000000000000000..35254573b6cca13bc4b83fcb8addec1fd1fb0d8f
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/deferredmultisampling/mrt.vert
@@ -0,0 +1,51 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float4 Pos : POSITION0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+[[vk::location(2)]] float3 Color : COLOR0;
+[[vk::location(3)]] float3 Normal : NORMAL0;
+[[vk::location(4)]] float3 Tangent : TEXCOORD1;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+	float4x4 view;
+	float4 instancePos[3];
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+[[vk::location(2)]] float3 Color : COLOR0;
+[[vk::location(3)]] float3 WorldPos : POSITION0;
+[[vk::location(4)]] float3 Tangent : TEXCOORD1;
+};
+
+VSOutput main(VSInput input, uint InstanceIndex : SV_InstanceID)
+{
+	VSOutput output = (VSOutput)0;
+	float4 tmpPos = input.Pos + ubo.instancePos[InstanceIndex];
+
+	output.Pos = mul(ubo.projection, mul(ubo.view, mul(ubo.model, tmpPos)));
+
+	output.UV = input.UV;
+
+	// Vertex position in world space
+	output.WorldPos = mul(ubo.model, tmpPos).xyz;
+
+	// Normal in world space
+	output.Normal = normalize(input.Normal);
+	output.Tangent = normalize(input.Tangent);
+
+	// Currently just vertex color
+	output.Color = input.Color;
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/deferredmultisampling/mrt.vert.spv b/external/Vulkan/data/shaders/hlsl/deferredmultisampling/mrt.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..31f2eafb6c7244da1494f3a53d7e1dc32bef7a40
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/deferredmultisampling/mrt.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/deferredshadows/deferred.frag b/external/Vulkan/data/shaders/hlsl/deferredshadows/deferred.frag
new file mode 100644
index 0000000000000000000000000000000000000000..4ef651cc3bd15078923568ba293fb9b087988b7f
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/deferredshadows/deferred.frag
@@ -0,0 +1,174 @@
+// Copyright 2020 Google LLC
+
+Texture2D textureposition : register(t1);
+SamplerState samplerposition : register(s1);
+Texture2D textureNormal : register(t2);
+SamplerState samplerNormal : register(s2);
+Texture2D textureAlbedo : register(t3);
+SamplerState samplerAlbedo : register(s3);
+// Depth from the light's point of view
+//layout (binding = 5) uniform sampler2DShadow samplerShadowMap;
+Texture2DArray textureShadowMap : register(t5);
+SamplerState samplerShadowMap : register(s5);
+
+#define LIGHT_COUNT 3
+#define SHADOW_FACTOR 0.25
+#define AMBIENT_LIGHT 0.1
+#define USE_PCF
+
+struct Light
+{
+	float4 position;
+	float4 target;
+	float4 color;
+	float4x4 viewMatrix;
+};
+
+struct UBO
+{
+	float4 viewPos;
+	Light lights[LIGHT_COUNT];
+	int useShadows;
+	int displayDebugTarget;
+};
+
+cbuffer ubo : register(b4) { UBO ubo; }
+
+float textureProj(float4 P, float layer, float2 offset)
+{
+	float shadow = 1.0;
+	float4 shadowCoord = P / P.w;
+	shadowCoord.xy = shadowCoord.xy * 0.5 + 0.5;
+
+	if (shadowCoord.z > -1.0 && shadowCoord.z < 1.0)
+	{
+		float dist = textureShadowMap.Sample(samplerShadowMap, float3(shadowCoord.xy + offset, layer)).r;
+		if (shadowCoord.w > 0.0 && dist < shadowCoord.z)
+		{
+			shadow = SHADOW_FACTOR;
+		}
+	}
+	return shadow;
+}
+
+float filterPCF(float4 sc, float layer)
+{
+	int2 texDim; int elements; int levels;
+	textureShadowMap.GetDimensions(0, texDim.x, texDim.y, elements, levels);
+	float scale = 1.5;
+	float dx = scale * 1.0 / float(texDim.x);
+	float dy = scale * 1.0 / float(texDim.y);
+
+	float shadowFactor = 0.0;
+	int count = 0;
+	int range = 1;
+
+	for (int x = -range; x <= range; x++)
+	{
+		for (int y = -range; y <= range; y++)
+		{
+			shadowFactor += textureProj(sc, layer, float2(dx*x, dy*y));
+			count++;
+		}
+
+	}
+	return shadowFactor / count;
+}
+
+float3 shadow(float3 fragcolor, float3 fragPos) {
+	for (int i = 0; i < LIGHT_COUNT; ++i)
+	{
+		float4 shadowClip = mul(ubo.lights[i].viewMatrix, float4(fragPos.xyz, 1.0));
+
+		float shadowFactor;
+		#ifdef USE_PCF
+			shadowFactor= filterPCF(shadowClip, i);
+		#else
+			shadowFactor = textureProj(shadowClip, i, float2(0.0, 0.0));
+		#endif
+
+		fragcolor *= shadowFactor;
+	}
+	return fragcolor;
+}
+
+float4 main([[vk::location(0)]] float2 inUV : TEXCOORD0) : SV_TARGET
+{
+	// Get G-Buffer values
+	float3 fragPos = textureposition.Sample(samplerposition, inUV).rgb;
+	float3 normal = textureNormal.Sample(samplerNormal, inUV).rgb;
+	float4 albedo = textureAlbedo.Sample(samplerAlbedo, inUV);
+
+	float3 fragcolor;
+
+	// Debug display
+	if (ubo.displayDebugTarget > 0) {
+		switch (ubo.displayDebugTarget) {
+			case 1: 
+				fragcolor.rgb = shadow(float3(1.0, 1.0, 1.0), fragPos);
+				break;
+			case 2: 
+				fragcolor.rgb = fragPos;
+				break;
+			case 3: 
+				fragcolor.rgb = normal;
+				break;
+			case 4: 
+				fragcolor.rgb = albedo.rgb;
+				break;
+			case 5: 
+				fragcolor.rgb = albedo.aaa;
+				break;
+		}		
+		return float4(fragcolor, 1.0);
+	}
+
+	// Ambient part
+	fragcolor  = albedo.rgb * AMBIENT_LIGHT;
+
+	float3 N = normalize(normal);
+
+	for(int i = 0; i < LIGHT_COUNT; ++i)
+	{
+		// Vector to light
+		float3 L = ubo.lights[i].position.xyz - fragPos;
+		// Distance from light to fragment position
+		float dist = length(L);
+		L = normalize(L);
+
+		// Viewer to fragment
+		float3 V = ubo.viewPos.xyz - fragPos;
+		V = normalize(V);
+
+		float lightCosInnerAngle = cos(radians(15.0));
+		float lightCosOuterAngle = cos(radians(25.0));
+		float lightRange = 100.0;
+
+		// Direction vector from source to target
+		float3 dir = normalize(ubo.lights[i].position.xyz - ubo.lights[i].target.xyz);
+
+		// Dual cone spot light with smooth transition between inner and outer angle
+		float cosDir = dot(L, dir);
+		float spotEffect = smoothstep(lightCosOuterAngle, lightCosInnerAngle, cosDir);
+		float heightAttenuation = smoothstep(lightRange, 0.0f, dist);
+
+		// Diffuse lighting
+		float NdotL = max(0.0, dot(N, L));
+		float3 diff = NdotL.xxx;
+
+		// Specular lighting
+		float3 R = reflect(-L, N);
+		float NdotR = max(0.0, dot(R, V));
+		float3 spec = (pow(NdotR, 16.0) * albedo.a * 2.5).xxx;
+
+		fragcolor += float3((diff + spec) * spotEffect * heightAttenuation) * ubo.lights[i].color.rgb * albedo.rgb;
+	}
+
+	// Shadow calculations in a separate pass
+	if (ubo.useShadows > 0)
+	{
+		fragcolor = shadow(fragcolor, fragPos);
+	}
+
+	return float4(fragcolor, 1);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/deferredshadows/deferred.frag.spv b/external/Vulkan/data/shaders/hlsl/deferredshadows/deferred.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..79ed9acca25858520382ceaa10928bebcbe375e2
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/deferredshadows/deferred.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/deferredshadows/deferred.vert b/external/Vulkan/data/shaders/hlsl/deferredshadows/deferred.vert
new file mode 100644
index 0000000000000000000000000000000000000000..b13c2bf2353ce1fc1837fef4db32336429c22412
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/deferredshadows/deferred.vert
@@ -0,0 +1,15 @@
+// Copyright 2020 Google LLC
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float2 UV : TEXCOORD0;
+};
+
+VSOutput main(uint VertexIndex : SV_VertexID)
+{
+	VSOutput output = (VSOutput)0;
+	output.UV = float2((VertexIndex << 1) & 2, VertexIndex & 2);
+	output.Pos = float4(output.UV * 2.0f - 1.0f, 0.0f, 1.0f);
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/deferredshadows/deferred.vert.spv b/external/Vulkan/data/shaders/hlsl/deferredshadows/deferred.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..8754eaa14270ad09ba3893fff6aa51f5732ff9c9
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/deferredshadows/deferred.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/deferredshadows/mrt.frag b/external/Vulkan/data/shaders/hlsl/deferredshadows/mrt.frag
new file mode 100644
index 0000000000000000000000000000000000000000..7b8998215456a02ee71ef76f0239e0009ee898c6
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/deferredshadows/mrt.frag
@@ -0,0 +1,39 @@
+// Copyright 2020 Google LLC
+
+Texture2D textureColor : register(t1);
+SamplerState samplerColor : register(s1);
+Texture2D textureNormalMap : register(t2);
+SamplerState samplerNormalMap : register(s2);
+
+struct VSOutput
+{
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+[[vk::location(2)]] float3 Color : COLOR0;
+[[vk::location(3)]] float3 WorldPos : POSITION0;
+[[vk::location(4)]] float3 Tangent : TEXCOORD1;
+};
+
+struct FSOutput
+{
+	float4 Position : SV_TARGET0;
+	float4 Normal : SV_TARGET1;
+	float4 Albedo : SV_TARGET2;
+};
+
+FSOutput main(VSOutput input)
+{
+	FSOutput output = (FSOutput)0;
+	output.Position = float4(input.WorldPos, 1.0);
+
+	// Calculate normal in tangent space
+	float3 N = normalize(input.Normal);
+	float3 T = normalize(input.Tangent);
+	float3 B = cross(N, T);
+	float3x3 TBN = float3x3(T, B, N);
+	float3 tnorm = mul(normalize(textureNormalMap.Sample(samplerNormalMap, input.UV).xyz * 2.0 - float3(1.0, 1.0, 1.0)), TBN);
+	output.Normal = float4(tnorm, 1.0);
+
+	output.Albedo = textureColor.Sample(samplerColor, input.UV);
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/deferredshadows/mrt.frag.spv b/external/Vulkan/data/shaders/hlsl/deferredshadows/mrt.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..a2085df98b35e94b7fc4c66dae7ff48601335695
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/deferredshadows/mrt.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/deferredshadows/mrt.vert b/external/Vulkan/data/shaders/hlsl/deferredshadows/mrt.vert
new file mode 100644
index 0000000000000000000000000000000000000000..35254573b6cca13bc4b83fcb8addec1fd1fb0d8f
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/deferredshadows/mrt.vert
@@ -0,0 +1,51 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float4 Pos : POSITION0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+[[vk::location(2)]] float3 Color : COLOR0;
+[[vk::location(3)]] float3 Normal : NORMAL0;
+[[vk::location(4)]] float3 Tangent : TEXCOORD1;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+	float4x4 view;
+	float4 instancePos[3];
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+[[vk::location(2)]] float3 Color : COLOR0;
+[[vk::location(3)]] float3 WorldPos : POSITION0;
+[[vk::location(4)]] float3 Tangent : TEXCOORD1;
+};
+
+VSOutput main(VSInput input, uint InstanceIndex : SV_InstanceID)
+{
+	VSOutput output = (VSOutput)0;
+	float4 tmpPos = input.Pos + ubo.instancePos[InstanceIndex];
+
+	output.Pos = mul(ubo.projection, mul(ubo.view, mul(ubo.model, tmpPos)));
+
+	output.UV = input.UV;
+
+	// Vertex position in world space
+	output.WorldPos = mul(ubo.model, tmpPos).xyz;
+
+	// Normal in world space
+	output.Normal = normalize(input.Normal);
+	output.Tangent = normalize(input.Tangent);
+
+	// Currently just vertex color
+	output.Color = input.Color;
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/deferredshadows/mrt.vert.spv b/external/Vulkan/data/shaders/hlsl/deferredshadows/mrt.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..31f2eafb6c7244da1494f3a53d7e1dc32bef7a40
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/deferredshadows/mrt.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/deferredshadows/shadow.geom b/external/Vulkan/data/shaders/hlsl/deferredshadows/shadow.geom
new file mode 100644
index 0000000000000000000000000000000000000000..e8321771b3ff67fa32ad76f3c8a51361e042d0aa
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/deferredshadows/shadow.geom
@@ -0,0 +1,39 @@
+// Copyright 2020 Google LLC
+
+#define LIGHT_COUNT 3
+
+struct UBO
+{
+	float4x4 mvp[LIGHT_COUNT];
+	float4 instancePos[3];
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] int InstanceIndex : TEXCOORD0;
+};
+
+struct GSOutput
+{
+	float4 Pos : SV_POSITION;
+	int Layer : SV_RenderTargetArrayIndex;
+};
+
+[maxvertexcount(3)]
+[instance(3)]
+void main(triangle VSOutput input[3], uint InvocationID : SV_GSInstanceID, inout TriangleStream<GSOutput> outStream)
+{
+	float4 instancedPos = ubo.instancePos[input[0].InstanceIndex];
+	for (int i = 0; i < 3; i++)
+	{
+		float4 tmpPos = input[i].Pos + instancedPos;
+		GSOutput output = (GSOutput)0;
+		output.Pos = mul(ubo.mvp[InvocationID], tmpPos);
+		output.Layer = InvocationID;
+		outStream.Append( output );
+	}
+	outStream.RestartStrip();
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/deferredshadows/shadow.geom.spv b/external/Vulkan/data/shaders/hlsl/deferredshadows/shadow.geom.spv
new file mode 100644
index 0000000000000000000000000000000000000000..97f85bc9a1ffebb594b2eaea13cd1466d8c7c86f
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/deferredshadows/shadow.geom.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/deferredshadows/shadow.vert b/external/Vulkan/data/shaders/hlsl/deferredshadows/shadow.vert
new file mode 100644
index 0000000000000000000000000000000000000000..f08ec8c758caf85445a48e389aa955b626b77935
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/deferredshadows/shadow.vert
@@ -0,0 +1,15 @@
+// Copyright 2020 Google LLC
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] int InstanceIndex : TEXCOORD0;
+};
+
+VSOutput main([[vk::location(0)]] float4 Pos : POSITION0, uint InstanceIndex : SV_InstanceID)
+{
+	VSOutput output = (VSOutput)0;
+	output.InstanceIndex = InstanceIndex;
+	output.Pos = Pos;
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/deferredshadows/shadow.vert.spv b/external/Vulkan/data/shaders/hlsl/deferredshadows/shadow.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..953ea7d52889db55b1cf86905623944bb131b71f
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/deferredshadows/shadow.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/descriptorindexing/descriptorindexing.frag b/external/Vulkan/data/shaders/hlsl/descriptorindexing/descriptorindexing.frag
new file mode 100644
index 0000000000000000000000000000000000000000..03a1a0f80bb43fa67fd08c7232149cecc39f04cd
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/descriptorindexing/descriptorindexing.frag
@@ -0,0 +1,17 @@
+// Copyright 2021 Sascha Willems
+// Non-uniform access is enabled at compile time via SPV_EXT_descriptor_indexing (see compile.py)
+
+Texture2D textures[] : register(t1);
+SamplerState samplerColorMap : register(s1);
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] int TextureIndex : TEXTUREINDEX0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+};
+
+float4 main(VSOutput input) : SV_TARGET
+{
+	return textures[NonUniformResourceIndex(input.TextureIndex)].Sample(samplerColorMap, input.UV); 
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/descriptorindexing/descriptorindexing.frag.spv b/external/Vulkan/data/shaders/hlsl/descriptorindexing/descriptorindexing.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..e1ef9249ef5361986cc934f782fed165bf655a85
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/descriptorindexing/descriptorindexing.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/descriptorindexing/descriptorindexing.vert b/external/Vulkan/data/shaders/hlsl/descriptorindexing/descriptorindexing.vert
new file mode 100644
index 0000000000000000000000000000000000000000..74cad39d5872b8efe0961789adc6363a46929ea9
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/descriptorindexing/descriptorindexing.vert
@@ -0,0 +1,32 @@
+// Copyright 2021 Sascha Willems
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+[[vk::location(2)]] int TextureIndex : TEXTUREINDEX0;
+};
+
+struct Matrices {
+	float4x4 projection;
+	float4x4 view;
+	float4x4 model;
+};
+
+cbuffer matrices : register(b0) { Matrices matrices; };
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] int TextureIndex : TEXTUREINDEX0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.UV = input.UV;
+	output.TextureIndex = input.TextureIndex;
+	output.Pos = mul(matrices.projection, mul(matrices.view, mul(matrices.model, float4(input.Pos.xyz, 1.0))));
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/descriptorindexing/descriptorindexing.vert.spv b/external/Vulkan/data/shaders/hlsl/descriptorindexing/descriptorindexing.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..cab40d817b1c45896e832896d91339a7f39349e0
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/descriptorindexing/descriptorindexing.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/descriptorsets/cube.frag b/external/Vulkan/data/shaders/hlsl/descriptorsets/cube.frag
new file mode 100644
index 0000000000000000000000000000000000000000..854726c31b6f29ad245ebdf51d38309f3324be34
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/descriptorsets/cube.frag
@@ -0,0 +1,16 @@
+// Copyright 2020 Google LLC
+
+Texture2D textureColorMap : register(t1);
+SamplerState samplerColorMap : register(s1);
+
+struct VSOutput
+{
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float2 UV : TEXCOORD0;
+};
+
+float4 main(VSOutput input) : SV_TARGET
+{
+	return textureColorMap.Sample(samplerColorMap, input.UV) * float4(input.Color, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/descriptorsets/cube.frag.spv b/external/Vulkan/data/shaders/hlsl/descriptorsets/cube.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..58670396edd12fa34cfa993d6ce4210b1b3b75c5
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/descriptorsets/cube.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/descriptorsets/cube.vert b/external/Vulkan/data/shaders/hlsl/descriptorsets/cube.vert
new file mode 100644
index 0000000000000000000000000000000000000000..68f4df3ec2b829176c536289b84fa61c1e73459d
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/descriptorsets/cube.vert
@@ -0,0 +1,35 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float2 UV : TEXCOORD0;
+[[vk::location(3)]] float3 Color : COLOR0;
+};
+
+struct UBOMatrices {
+	float4x4 projection;
+	float4x4 view;
+	float4x4 model;
+};
+
+cbuffer uboMatrices : register(b0) { UBOMatrices uboMatrices; };
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float2 UV : TEXCOORD0;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Normal = input.Normal;
+	output.Color = input.Color;
+	output.UV = input.UV;
+	output.Pos = mul(uboMatrices.projection, mul(uboMatrices.view, mul(uboMatrices.model, float4(input.Pos.xyz, 1.0))));
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/descriptorsets/cube.vert.spv b/external/Vulkan/data/shaders/hlsl/descriptorsets/cube.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..d2df4de2f4a25a40eabfc28d4292cbe13a60cebc
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/descriptorsets/cube.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/displacement/base.frag b/external/Vulkan/data/shaders/hlsl/displacement/base.frag
new file mode 100644
index 0000000000000000000000000000000000000000..c62eec65550d4c0caf9390fe8741b95768e0a6ad
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/displacement/base.frag
@@ -0,0 +1,26 @@
+// Copyright 2020 Google LLC
+
+Texture2D textureColorMap : register(t2);
+SamplerState samplerColorMap : register(s2);
+
+struct DSOutput
+{
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+[[vk::location(2)]] float3 EyePos : POSITION0;
+[[vk::location(3)]] float3 LightVec : TEXCOORD2;
+};
+
+float4 main(DSOutput input) : SV_TARGET
+{
+	float3 N = normalize(input.Normal);
+	float3 L = normalize(float3(1.0, 1.0, 1.0));
+
+	float3 Eye = normalize(-input.EyePos);
+	float3 Reflected = normalize(reflect(-input.LightVec, input.Normal));
+
+	float4 IAmbient = float4(0.0, 0.0, 0.0, 1.0);
+	float4 IDiffuse = float4(1.0, 1.0, 1.0, 1.0) * max(dot(input.Normal, input.LightVec), 0.0);
+
+	return float4((IAmbient + IDiffuse) * float4(textureColorMap.Sample(samplerColorMap, input.UV).rgb, 1.0));
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/displacement/base.frag.spv b/external/Vulkan/data/shaders/hlsl/displacement/base.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..918714cd3528fd83f0c0f047d890a721896f8381
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/displacement/base.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/displacement/base.vert b/external/Vulkan/data/shaders/hlsl/displacement/base.vert
new file mode 100644
index 0000000000000000000000000000000000000000..da02b1cf6c4d4adc64a34dac00a08e6e05ef43ed
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/displacement/base.vert
@@ -0,0 +1,24 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float2 UV : TEXCOORD0;
+};
+
+struct VSOutput
+{
+[[vk::location(2)]]	float4 Pos : POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Pos = float4(input.Pos.xyz, 1.0);
+    output.UV = input.UV;
+	output.Normal = input.Normal;
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/displacement/base.vert.spv b/external/Vulkan/data/shaders/hlsl/displacement/base.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..5497168e3559b6fa64399cecea5816919ca38a94
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/displacement/base.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/displacement/displacement.tesc b/external/Vulkan/data/shaders/hlsl/displacement/displacement.tesc
new file mode 100644
index 0000000000000000000000000000000000000000..470fe7c2814c6274abe0b4f8bc227369010b184f
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/displacement/displacement.tesc
@@ -0,0 +1,53 @@
+// Copyright 2020 Google LLC
+
+struct UBO
+{
+	float tessLevel;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+[[vk::location(2)]]	float4 Pos : POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+};
+
+struct HSOutput
+{
+[[vk::location(2)]]	float4 Pos : POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+};
+
+struct ConstantsHSOutput
+{
+    float TessLevelOuter[3] : SV_TessFactor;
+    float TessLevelInner : SV_InsideTessFactor;
+};
+
+ConstantsHSOutput ConstantsHS(InputPatch<VSOutput, 3> patch, uint InvocationID : SV_PrimitiveID)
+{
+    ConstantsHSOutput output = (ConstantsHSOutput)0;
+    output.TessLevelInner = ubo.tessLevel;
+    output.TessLevelOuter[0] = ubo.tessLevel;
+    output.TessLevelOuter[1] = ubo.tessLevel;
+    output.TessLevelOuter[2] = ubo.tessLevel;
+    return output;
+}
+
+[domain("tri")]
+[partitioning("integer")]
+[outputtopology("triangle_cw")]
+[outputcontrolpoints(3)]
+[patchconstantfunc("ConstantsHS")]
+[maxtessfactor(20.0f)]
+HSOutput main(InputPatch<VSOutput, 3> patch, uint InvocationID : SV_OutputControlPointID)
+{
+	HSOutput output = (HSOutput)0;
+	output.Pos = patch[InvocationID].Pos;
+	output.Normal = patch[InvocationID].Normal;
+	output.UV = patch[InvocationID].UV;
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/displacement/displacement.tesc.spv b/external/Vulkan/data/shaders/hlsl/displacement/displacement.tesc.spv
new file mode 100644
index 0000000000000000000000000000000000000000..49f15d8c3537cfe92c359d94399f7037dd5aec53
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/displacement/displacement.tesc.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/displacement/displacement.tese b/external/Vulkan/data/shaders/hlsl/displacement/displacement.tese
new file mode 100644
index 0000000000000000000000000000000000000000..0732c345981dc0cb6dcfd6c70e6b9cabb688de1e
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/displacement/displacement.tese
@@ -0,0 +1,54 @@
+// Copyright 2020 Google LLC
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+	float4 lightPos;
+	float tessAlpha;
+	float tessStrength;
+};
+
+cbuffer ubo : register(b1) { UBO ubo; }
+
+Texture2D textureDisplacementMap : register(t2);
+SamplerState samplerDisplacementMap : register(s2);
+
+struct HSOutput
+{
+[[vk::location(2)]]	float4 Pos : POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+};
+
+struct ConstantsHSOutput
+{
+    float TessLevelOuter[3] : SV_TessFactor;
+    float TessLevelInner : SV_InsideTessFactor;
+};
+
+struct DSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+[[vk::location(2)]] float3 EyesPos : POSITION0;
+[[vk::location(3)]] float3 LightVec : TEXCOORD1;
+};
+
+[domain("tri")]
+DSOutput main(ConstantsHSOutput input, float3 TessCoord : SV_DomainLocation, const OutputPatch<HSOutput, 3> patch)
+{
+	DSOutput output = (DSOutput)0;
+	output.Pos = (TessCoord.x * patch[0].Pos) + (TessCoord.y * patch[1].Pos) + (TessCoord.z * patch[2].Pos);
+	output.UV = mul(TessCoord.x, patch[0].UV) + mul(TessCoord.y, patch[1].UV) + mul(TessCoord.z, patch[2].UV);
+	output.Normal = TessCoord.x * patch[0].Normal + TessCoord.y * patch[1].Normal + TessCoord.z * patch[2].Normal;
+
+	output.Pos.xyz += normalize(output.Normal) * (max(textureDisplacementMap.SampleLevel(samplerDisplacementMap, output.UV.xy, 0).a, 0.0) * ubo.tessStrength);
+
+	output.EyesPos = output.Pos.xyz;
+	output.LightVec = normalize(ubo.lightPos.xyz - output.EyesPos);
+
+	output.Pos = mul(ubo.projection, mul(ubo.model, output.Pos));
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/displacement/displacement.tese.spv b/external/Vulkan/data/shaders/hlsl/displacement/displacement.tese.spv
new file mode 100644
index 0000000000000000000000000000000000000000..8e360fae9a5160250a70ef69e69949bde699e7a7
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/displacement/displacement.tese.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/distancefieldfonts/bitmap.frag b/external/Vulkan/data/shaders/hlsl/distancefieldfonts/bitmap.frag
new file mode 100644
index 0000000000000000000000000000000000000000..456bfe8653fe26ab2080cce8cf93ce1844b23762
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/distancefieldfonts/bitmap.frag
@@ -0,0 +1,9 @@
+// Copyright 2020 Google LLC
+
+Texture2D textureColor : register(t1);
+SamplerState samplerColor : register(s1);
+
+float4 main([[vk::location(0)]] float2 inUV : TEXCOORD0) : SV_TARGET
+{
+	return textureColor.Sample(samplerColor, inUV).aaaa;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/distancefieldfonts/bitmap.frag.spv b/external/Vulkan/data/shaders/hlsl/distancefieldfonts/bitmap.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..6b7210822e121b3304a4bbe5791ac0cd0e262026
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/distancefieldfonts/bitmap.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/distancefieldfonts/bitmap.vert b/external/Vulkan/data/shaders/hlsl/distancefieldfonts/bitmap.vert
new file mode 100644
index 0000000000000000000000000000000000000000..379daee916d45468ea4f30f53afec1824fe57b43
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/distancefieldfonts/bitmap.vert
@@ -0,0 +1,29 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float2 UV : TEXCOORD0;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.UV = input.UV;
+	output.Pos = mul(ubo.projection, mul(ubo.model, float4(input.Pos.xyz, 1.0)));
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/distancefieldfonts/bitmap.vert.spv b/external/Vulkan/data/shaders/hlsl/distancefieldfonts/bitmap.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..5b4eecdc002919dcab4bc412bdaa2556efc36751
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/distancefieldfonts/bitmap.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/distancefieldfonts/sdf.frag b/external/Vulkan/data/shaders/hlsl/distancefieldfonts/sdf.frag
new file mode 100644
index 0000000000000000000000000000000000000000..104284a9302cc96d1709ac727c760badb18b40d9
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/distancefieldfonts/sdf.frag
@@ -0,0 +1,30 @@
+// Copyright 2020 Google LLC
+
+Texture2D textureColor : register(t1);
+SamplerState samplerColor : register(s1);
+
+struct UBO
+{
+	float4 outlineColor;
+	float outlineWidth;
+	float outline;
+};
+
+cbuffer ubo : register(b2) { UBO ubo; }
+
+float4 main([[vk::location(0)]] float2 inUV : TEXCOORD0) : SV_TARGET
+{
+    float dist = textureColor.Sample(samplerColor, inUV).a;
+    float smoothWidth = fwidth(dist);
+    float alpha = smoothstep(0.5 - smoothWidth, 0.5 + smoothWidth, dist);
+	float3 rgb = alpha.xxx;
+
+	if (ubo.outline > 0.0)
+	{
+		float w = 1.0 - ubo.outlineWidth;
+		alpha = smoothstep(w - smoothWidth, w + smoothWidth, dist);
+        rgb += lerp(alpha.xxx, ubo.outlineColor.rgb, alpha);
+    }
+
+    return float4(rgb, alpha);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/distancefieldfonts/sdf.frag.spv b/external/Vulkan/data/shaders/hlsl/distancefieldfonts/sdf.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..5a1f75cd0b2b09f634a8434beafcff6b8d762561
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/distancefieldfonts/sdf.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/distancefieldfonts/sdf.vert b/external/Vulkan/data/shaders/hlsl/distancefieldfonts/sdf.vert
new file mode 100644
index 0000000000000000000000000000000000000000..379daee916d45468ea4f30f53afec1824fe57b43
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/distancefieldfonts/sdf.vert
@@ -0,0 +1,29 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float2 UV : TEXCOORD0;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.UV = input.UV;
+	output.Pos = mul(ubo.projection, mul(ubo.model, float4(input.Pos.xyz, 1.0)));
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/distancefieldfonts/sdf.vert.spv b/external/Vulkan/data/shaders/hlsl/distancefieldfonts/sdf.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..5b4eecdc002919dcab4bc412bdaa2556efc36751
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/distancefieldfonts/sdf.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/dynamicuniformbuffer/base.frag b/external/Vulkan/data/shaders/hlsl/dynamicuniformbuffer/base.frag
new file mode 100644
index 0000000000000000000000000000000000000000..b800903c4dcc30e26637197345bfd43d65e0bcf1
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/dynamicuniformbuffer/base.frag
@@ -0,0 +1,6 @@
+// Copyright 2020 Google LLC
+
+float4 main([[vk::location(0)]] float3 Color : COLOR0) : SV_TARGET
+{
+	return float4(Color, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/dynamicuniformbuffer/base.frag.spv b/external/Vulkan/data/shaders/hlsl/dynamicuniformbuffer/base.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..870fb6c12ac71dca568d8358a955b81f6eca7faf
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/dynamicuniformbuffer/base.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/dynamicuniformbuffer/base.vert b/external/Vulkan/data/shaders/hlsl/dynamicuniformbuffer/base.vert
new file mode 100644
index 0000000000000000000000000000000000000000..7cb4393386a35a975f69284814042194a0d44a6d
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/dynamicuniformbuffer/base.vert
@@ -0,0 +1,36 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float3 Color : COLOR0;
+};
+
+struct UboView
+{
+	float4x4 projection;
+	float4x4 view;
+};
+cbuffer uboView : register(b0) { UboView uboView; };
+
+struct UboInstance
+{
+	float4x4 model;
+};
+cbuffer uboInstance : register(b1) { UboInstance uboInstance; };
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Color : COLOR0;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Color = input.Color;
+	float4x4 modelView = mul(uboView.view, uboInstance.model);
+	float3 worldPos = mul(modelView, float4(input.Pos, 1.0)).xyz;
+	output.Pos = mul(uboView.projection, mul(modelView, float4(input.Pos.xyz, 1.0)));
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/dynamicuniformbuffer/base.vert.spv b/external/Vulkan/data/shaders/hlsl/dynamicuniformbuffer/base.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..8c66fc5e893233676ea1ecd48eb2a4eebd94a042
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/dynamicuniformbuffer/base.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/gears/gears.frag b/external/Vulkan/data/shaders/hlsl/gears/gears.frag
new file mode 100644
index 0000000000000000000000000000000000000000..276b830a79a5bdaef4893c9dbdb2194e1a2682c5
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/gears/gears.frag
@@ -0,0 +1,22 @@
+// Copyright 2020 Google LLC
+
+struct VSOutput
+{
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float3 EyePos : POSITION0;
+[[vk::location(3)]] float3 LightVec : TEXCOORD2;
+};
+
+float4 main(VSOutput input) : SV_TARGET
+{
+  float3 Eye = normalize(-input.EyePos);
+  float3 Reflected = normalize(reflect(-input.LightVec, input.Normal));
+
+  float4 IAmbient = float4(0.2, 0.2, 0.2, 1.0);
+  float4 IDiffuse = float4(0.5, 0.5, 0.5, 0.5) * max(dot(input.Normal, input.LightVec), 0.0);
+  float specular = 0.25;
+  float4 ISpecular = float4(0.5, 0.5, 0.5, 1.0) * pow(max(dot(Reflected, Eye), 0.0), 0.8) * specular;
+
+  return float4((IAmbient + IDiffuse) * float4(input.Color, 1.0) + ISpecular);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/gears/gears.frag.spv b/external/Vulkan/data/shaders/hlsl/gears/gears.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..59596abb65c47557e0432efebac1cdeb697d927e
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/gears/gears.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/gears/gears.vert b/external/Vulkan/data/shaders/hlsl/gears/gears.vert
new file mode 100644
index 0000000000000000000000000000000000000000..68dd4a2eccf7c17d9bfe848268138ac4abb2a4a2
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/gears/gears.vert
@@ -0,0 +1,42 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float4 Pos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float3 Color : COLOR0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+	float4x4 normal;
+	float4x4 view;
+	float3 lightpos;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float3 EyePos : POSITION0;
+[[vk::location(3)]] float3 LightVec : TEXCOORD2;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Normal = normalize(mul((float4x3)ubo.normal, input.Normal).xyz);
+	output.Color = input.Color;
+	float4x4 modelView = mul(ubo.view, ubo.model);
+	float4 pos = mul(modelView, input.Pos);
+	output.EyePos = mul(modelView, pos).xyz;
+	float4 lightPos = mul(float4(ubo.lightpos, 1.0), modelView);
+	output.LightVec = normalize(lightPos.xyz - output.EyePos);
+	output.Pos = mul(ubo.projection, pos);
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/gears/gears.vert.spv b/external/Vulkan/data/shaders/hlsl/gears/gears.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..7253bfbc50a0d755e1e1b64581621ab78f42f519
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/gears/gears.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/geometryshader/base.frag b/external/Vulkan/data/shaders/hlsl/geometryshader/base.frag
new file mode 100644
index 0000000000000000000000000000000000000000..b800903c4dcc30e26637197345bfd43d65e0bcf1
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/geometryshader/base.frag
@@ -0,0 +1,6 @@
+// Copyright 2020 Google LLC
+
+float4 main([[vk::location(0)]] float3 Color : COLOR0) : SV_TARGET
+{
+	return float4(Color, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/geometryshader/base.frag.spv b/external/Vulkan/data/shaders/hlsl/geometryshader/base.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..870fb6c12ac71dca568d8358a955b81f6eca7faf
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/geometryshader/base.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/geometryshader/base.vert b/external/Vulkan/data/shaders/hlsl/geometryshader/base.vert
new file mode 100644
index 0000000000000000000000000000000000000000..8179b490800b0b33c280fd26a47a555e9d068524
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/geometryshader/base.vert
@@ -0,0 +1,21 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+};
+
+struct VSOutput
+{
+[[vk::location(0)]] float4 Pos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Normal = input.Normal;
+	output.Pos = float4(input.Pos.xyz, 1.0);
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/geometryshader/base.vert.spv b/external/Vulkan/data/shaders/hlsl/geometryshader/base.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..1c7f97693704ce50ff1e516eaff0c583cd7b6106
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/geometryshader/base.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/geometryshader/mesh.frag b/external/Vulkan/data/shaders/hlsl/geometryshader/mesh.frag
new file mode 100644
index 0000000000000000000000000000000000000000..189e99550510fa70dd23e6826aa5b6ed951c289a
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/geometryshader/mesh.frag
@@ -0,0 +1,21 @@
+// Copyright 2020 Google LLC
+
+struct GSOutput
+{
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(3)]] float3 LightVec : TEXCOORD2;
+};
+
+float4 main(GSOutput input) : SV_TARGET
+{
+	float3 N = normalize(input.Normal);
+	float3 L = normalize(input.LightVec);
+	float3 V = normalize(input.ViewVec);
+	float3 R = reflect(-L, N);
+	float3 ambient = float3(0.1, 0.1, 0.1);
+	float3 diffuse = max(dot(N, L), 0.0) * float3(1.0, 1.0, 1.0);
+	float3 specular = pow(max(dot(R, V), 0.0), 16.0) * float3(0.75, 0.75, 0.75);
+	return float4((ambient + diffuse) * input.Color.rgb + specular, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/geometryshader/mesh.frag.spv b/external/Vulkan/data/shaders/hlsl/geometryshader/mesh.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..a2843e1c83bac70e70368c1f832c1a42246d4da4
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/geometryshader/mesh.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/geometryshader/mesh.vert b/external/Vulkan/data/shaders/hlsl/geometryshader/mesh.vert
new file mode 100644
index 0000000000000000000000000000000000000000..cfa1dcf9ecf0ec94b156ff15f51eef7b449102b2
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/geometryshader/mesh.vert
@@ -0,0 +1,40 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float4 Pos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float3 Color : COLOR0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+};
+cbuffer ubo : register(b0) { UBO ubo; };
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(3)]] float3 LightVec : TEXCOORD2;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Normal = input.Normal;
+	output.Color = input.Color;
+	output.Pos = mul(ubo.projection, mul(ubo.model, input.Pos));
+
+	float4 pos = mul(ubo.model, float4(input.Pos.xyz, 1.0));
+	output.Normal = mul((float4x3)ubo.model, input.Normal).xyz;
+
+	float3 lightPos = float3(1.0f, -1.0f, 1.0f);
+	output.LightVec = lightPos.xyz - pos.xyz;
+	output.ViewVec = -pos.xyz;
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/geometryshader/mesh.vert.spv b/external/Vulkan/data/shaders/hlsl/geometryshader/mesh.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..2a233ad24a7730e170febde5ce3bc986da730068
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/geometryshader/mesh.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/geometryshader/normaldebug.geom b/external/Vulkan/data/shaders/hlsl/geometryshader/normaldebug.geom
new file mode 100644
index 0000000000000000000000000000000000000000..7fba0fcdf278dfa87c5067c9c5f35c2d48f8e314
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/geometryshader/normaldebug.geom
@@ -0,0 +1,43 @@
+// Copyright 2020 Google LLC
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+};
+
+cbuffer ubo : register(b1) { UBO ubo; }
+
+struct VSOutput
+{
+[[vk::location(0)]] float4 Pos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+};
+
+struct GSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Color : COLOR0;
+};
+
+[maxvertexcount(6)]
+void main(triangle VSOutput input[3], inout LineStream<GSOutput> outStream)
+{
+	float normalLength = 0.02;
+	for(int i=0; i<3; i++)
+	{
+		float3 pos = input[i].Pos.xyz;
+		float3 normal = input[i].Normal.xyz;
+
+		GSOutput output = (GSOutput)0;
+		output.Pos = mul(ubo.projection, mul(ubo.model, float4(pos, 1.0)));
+		output.Color = float3(1.0, 0.0, 0.0);
+		outStream.Append( output );
+
+		output.Pos = mul(ubo.projection, mul(ubo.model, float4(pos + normal * normalLength, 1.0)));
+		output.Color = float3(0.0, 0.0, 1.0);
+		outStream.Append( output );
+
+		outStream.RestartStrip();
+	}
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/geometryshader/normaldebug.geom.spv b/external/Vulkan/data/shaders/hlsl/geometryshader/normaldebug.geom.spv
new file mode 100644
index 0000000000000000000000000000000000000000..48607edafb834d2a718c2a991682e2738e3f17ec
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/geometryshader/normaldebug.geom.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/gltfloading/mesh.frag b/external/Vulkan/data/shaders/hlsl/gltfloading/mesh.frag
new file mode 100644
index 0000000000000000000000000000000000000000..914421a05efc51c90e8258f41702687e8bca50fb
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/gltfloading/mesh.frag
@@ -0,0 +1,31 @@
+// Copyright 2020 Google LLC
+
+Texture2D textureColorMap : register(t0, space1);
+SamplerState samplerColorMap : register(s0, space1);
+
+struct PushConsts {
+	float4x4 model;
+};
+[[vk::push_constant]] PushConsts primitive;
+
+struct VSOutput
+{
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float2 UV : TEXCOORD0;
+[[vk::location(3)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(4)]] float3 LightVec : TEXCOORD2;
+};
+
+float4 main(VSOutput input) : SV_TARGET
+{
+	float4 color = textureColorMap.Sample(samplerColorMap, input.UV) * float4(input.Color, 1.0);
+
+	float3 N = normalize(input.Normal);
+	float3 L = normalize(input.LightVec);
+	float3 V = normalize(input.ViewVec);
+	float3 R = reflect(-L, N);
+	float3 diffuse = max(dot(N, L), 0.0) * input.Color;
+	float3 specular = pow(max(dot(R, V), 0.0), 16.0) * float3(0.75, 0.75, 0.75);
+	return float4(diffuse * color.rgb + specular, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/gltfloading/mesh.frag.spv b/external/Vulkan/data/shaders/hlsl/gltfloading/mesh.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..a801c5f2114e0f397e424ddabce3fe5173ec1f66
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/gltfloading/mesh.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/gltfloading/mesh.vert b/external/Vulkan/data/shaders/hlsl/gltfloading/mesh.vert
new file mode 100644
index 0000000000000000000000000000000000000000..a47967ace7ce6b28064496b6cd02ef1568c7e51d
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/gltfloading/mesh.vert
@@ -0,0 +1,49 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float2 UV : TEXCOORD0;
+[[vk::location(3)]] float3 Color : COLOR0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 view;
+	float4 lightPos;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct PushConsts {
+	float4x4 model;
+};
+[[vk::push_constant]] PushConsts primitive;
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float2 UV : TEXCOORD0;
+[[vk::location(3)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(4)]] float3 LightVec : TEXCOORD2;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Normal = input.Normal;
+	output.Color = input.Color;
+	output.UV = input.UV;
+	output.Pos = mul(ubo.projection, mul(ubo.view, mul(primitive.model, float4(input.Pos.xyz, 1.0))));
+
+	float4 pos = mul(ubo.view, float4(input.Pos, 1.0));
+	output.Normal = mul((float3x3)ubo.view, input.Normal);
+	float3 lPos = mul((float3x3)ubo.view, ubo.lightPos.xyz);
+	output.LightVec = lPos - pos.xyz;
+	output.ViewVec = -pos.xyz;
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/gltfloading/mesh.vert.spv b/external/Vulkan/data/shaders/hlsl/gltfloading/mesh.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..3061fec4d7aa9180a94a854d3e0f419e3e459ef7
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/gltfloading/mesh.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/gltfscenerendering/scene.frag b/external/Vulkan/data/shaders/hlsl/gltfscenerendering/scene.frag
new file mode 100644
index 0000000000000000000000000000000000000000..62aad5d9d96b02b86baf9eeecba6dcab8a2124ea
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/gltfscenerendering/scene.frag
@@ -0,0 +1,44 @@
+// Copyright 2020 Google LLC
+
+Texture2D textureColorMap : register(t0, space1);
+SamplerState samplerColorMap : register(s0, space1);
+Texture2D textureNormalMap : register(t1, space1);
+SamplerState samplerNormalMap : register(s1, space1);
+
+[[vk::constant_id(0)]] const bool ALPHA_MASK = false;
+[[vk::constant_id(1)]] const float ALPHA_MASK_CUTOFF = 0.0;
+
+struct VSOutput
+{
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float2 UV : TEXCOORD0;
+[[vk::location(3)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(4)]] float3 LightVec : TEXCOORD2;
+[[vk::location(5)]] float4 Tangent : TEXCOORD3;
+};
+
+float4 main(VSOutput input) : SV_TARGET
+{
+	float4 color = textureColorMap.Sample(samplerColorMap, input.UV) * float4(input.Color, 1.0);
+
+	if (ALPHA_MASK) {
+		if (color.a < ALPHA_MASK_CUTOFF) {
+			discard;
+		}
+	}
+
+	float3 N = normalize(input.Normal);
+	float3 T = normalize(input.Tangent.xyz);
+	float3 B = cross(input.Normal, input.Tangent.xyz) * input.Tangent.w;
+	float3x3 TBN = float3x3(T, B, N);
+	N = mul(normalize(textureNormalMap.Sample(samplerNormalMap, input.UV).xyz * 2.0 - float3(1.0, 1.0, 1.0)), TBN);
+
+	const float ambient = 0.1;
+	float3 L = normalize(input.LightVec);
+	float3 V = normalize(input.ViewVec);
+	float3 R = reflect(-L, N);
+	float3 diffuse = max(dot(N, L), ambient).rrr;
+	float3 specular = pow(max(dot(R, V), 0.0), 32.0);
+	return float4(diffuse * color.rgb + specular, color.a);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/gltfscenerendering/scene.frag.spv b/external/Vulkan/data/shaders/hlsl/gltfscenerendering/scene.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..4774d0746faecafff6887d79b8c83e0e3c57d598
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/gltfscenerendering/scene.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/gltfscenerendering/scene.vert b/external/Vulkan/data/shaders/hlsl/gltfscenerendering/scene.vert
new file mode 100644
index 0000000000000000000000000000000000000000..41edd8be7d76c9a4865f349d1f40096e01e28421
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/gltfscenerendering/scene.vert
@@ -0,0 +1,54 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float2 UV : TEXCOORD0;
+[[vk::location(3)]] float3 Color : COLOR0;
+[[vk::location(4)]] float4 Tangent : TEXCOORD1;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 view;
+	float4 lightPos;
+	float4 viewPos;
+};
+cbuffer ubo : register(b0) { UBO ubo; };
+
+struct PushConsts {
+	float4x4 model;
+};
+[[vk::push_constant]] PushConsts primitive;
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float2 UV : TEXCOORD0;
+[[vk::location(3)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(4)]] float3 LightVec : TEXCOORD2;
+[[vk::location(5)]] float4 Tangent : TEXCOORD3;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Normal = input.Normal;
+	output.Color = input.Color;
+	output.UV = input.UV;
+	output.Tangent = input.Tangent;
+
+	float4x4 modelView = mul(ubo.view, primitive.model);
+
+	output.Pos = mul(ubo.projection, mul(modelView, float4(input.Pos.xyz, 1.0)));
+
+	output.Normal = mul((float3x3)primitive.model, input.Normal);
+	float4 pos = mul(primitive.model, float4(input.Pos, 1.0));
+	output.LightVec = ubo.lightPos.xyz - pos.xyz;
+	output.ViewVec = ubo.viewPos.xyz - pos.xyz;
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/gltfscenerendering/scene.vert.spv b/external/Vulkan/data/shaders/hlsl/gltfscenerendering/scene.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..35000d17ccec9d6d1a5dfa94a202ef5331a8a5a3
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/gltfscenerendering/scene.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/hdr/bloom.frag b/external/Vulkan/data/shaders/hlsl/hdr/bloom.frag
new file mode 100644
index 0000000000000000000000000000000000000000..13c5e14a765f0ab620bca6ede23a6aed7e830723
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/hdr/bloom.frag
@@ -0,0 +1,62 @@
+// Copyright 2020 Google LLC
+
+Texture2D textureColor0 : register(t0);
+SamplerState samplerColor0 : register(s0);
+Texture2D textureColor1 : register(t1);
+SamplerState samplerColor1 : register(s1);
+
+[[vk::constant_id(0)]] const int dir = 0;
+
+float4 main([[vk::location(0)]] float2 inUV : TEXCOORD0) : SV_TARGET
+{
+	// From the OpenGL Super bible
+	const float weights[] = {	0.0024499299678342,
+								0.0043538453346397,
+								0.0073599963704157,
+								0.0118349786570722,
+								0.0181026699707781,
+								0.0263392293891488,
+								0.0364543006660986,
+								0.0479932050577658,
+								0.0601029809166942,
+								0.0715974486241365,
+								0.0811305381519717,
+								0.0874493212267511,
+								0.0896631113333857,
+								0.0874493212267511,
+								0.0811305381519717,
+								0.0715974486241365,
+								0.0601029809166942,
+								0.0479932050577658,
+								0.0364543006660986,
+								0.0263392293891488,
+								0.0181026699707781,
+								0.0118349786570722,
+								0.0073599963704157,
+								0.0043538453346397,
+								0.0024499299678342};
+
+
+	const float blurScale = 0.003;
+	const float blurStrength = 1.0;
+
+	float ar = 1.0;
+	// Aspect ratio for vertical blur pass
+	if (dir == 1)
+	{
+		float2 ts;
+		textureColor1.GetDimensions(ts.x, ts.y);
+		ar = ts.y / ts.x;
+	}
+
+	float2 P = inUV.yx - float2(0, (25 >> 1) * ar * blurScale);
+
+	float4 color = float4(0.0, 0.0, 0.0, 0.0);
+	for (int i = 0; i < 25; i++)
+	{
+		float2 dv = float2(0.0, i * blurScale) * ar;
+		color += textureColor1.Sample(samplerColor1, P + dv) * weights[i] * blurStrength;
+	}
+
+	return color;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/hdr/bloom.frag.spv b/external/Vulkan/data/shaders/hlsl/hdr/bloom.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..a9f9034831c6c9feb4d5ce077583802fbec1330c
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/hdr/bloom.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/hdr/bloom.vert b/external/Vulkan/data/shaders/hlsl/hdr/bloom.vert
new file mode 100644
index 0000000000000000000000000000000000000000..b13c2bf2353ce1fc1837fef4db32336429c22412
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/hdr/bloom.vert
@@ -0,0 +1,15 @@
+// Copyright 2020 Google LLC
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float2 UV : TEXCOORD0;
+};
+
+VSOutput main(uint VertexIndex : SV_VertexID)
+{
+	VSOutput output = (VSOutput)0;
+	output.UV = float2((VertexIndex << 1) & 2, VertexIndex & 2);
+	output.Pos = float4(output.UV * 2.0f - 1.0f, 0.0f, 1.0f);
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/hdr/bloom.vert.spv b/external/Vulkan/data/shaders/hlsl/hdr/bloom.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..46c4b31fdc1568cc56662cc746d3a2d10f9d0983
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/hdr/bloom.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/hdr/composition.frag b/external/Vulkan/data/shaders/hlsl/hdr/composition.frag
new file mode 100644
index 0000000000000000000000000000000000000000..41c9be65b6876c3c3bd5d418564919ded5572bb9
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/hdr/composition.frag
@@ -0,0 +1,11 @@
+// Copyright 2020 Google LLC
+
+Texture2D textureColor0 : register(t0);
+SamplerState samplerColor0 : register(s0);
+Texture2D textureColor1 : register(t1);
+SamplerState samplerColor1 : register(s1);
+
+float4 main([[vk::location(0)]] float2 inUV : TEXCOORD0) : SV_TARGET
+{
+	return textureColor0.Sample(samplerColor0, inUV);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/hdr/composition.frag.spv b/external/Vulkan/data/shaders/hlsl/hdr/composition.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..9b0e3a0a25dbf11568915af18cbae987f8b08547
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/hdr/composition.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/hdr/composition.vert b/external/Vulkan/data/shaders/hlsl/hdr/composition.vert
new file mode 100644
index 0000000000000000000000000000000000000000..b13c2bf2353ce1fc1837fef4db32336429c22412
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/hdr/composition.vert
@@ -0,0 +1,15 @@
+// Copyright 2020 Google LLC
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float2 UV : TEXCOORD0;
+};
+
+VSOutput main(uint VertexIndex : SV_VertexID)
+{
+	VSOutput output = (VSOutput)0;
+	output.UV = float2((VertexIndex << 1) & 2, VertexIndex & 2);
+	output.Pos = float4(output.UV * 2.0f - 1.0f, 0.0f, 1.0f);
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/hdr/composition.vert.spv b/external/Vulkan/data/shaders/hlsl/hdr/composition.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..46c4b31fdc1568cc56662cc746d3a2d10f9d0983
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/hdr/composition.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/hdr/gbuffer.frag b/external/Vulkan/data/shaders/hlsl/hdr/gbuffer.frag
new file mode 100644
index 0000000000000000000000000000000000000000..4e412a7533d7d81e76140e8e04acfbbfe987770d
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/hdr/gbuffer.frag
@@ -0,0 +1,108 @@
+// Copyright 2020 Google LLC
+
+TextureCube textureEnvMap : register(t1);
+SamplerState samplerEnvMap : register(s1);
+
+struct VSOutput
+{
+[[vk::location(0)]] float3 UVW : TEXCOORD0;
+[[vk::location(1)]] float3 Pos : POSITION0;
+[[vk::location(2)]] float3 Normal : NORMAL0;
+[[vk::location(3)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(4)]] float3 LightVec : TEXCOORD2;
+};
+
+struct FSOutput
+{
+	float4 Color0 : SV_TARGET0;
+	float4 Color1 : SV_TARGET1;
+};
+
+[[vk::constant_id(0)]] const int type = 0;
+
+#define PI 3.1415926
+#define TwoPI (2.0 * PI)
+
+struct UBO  {
+	float4x4 projection;
+	float4x4 modelview;
+	float4x4 inverseModelview;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+cbuffer Exposure : register(b2)
+{
+	float exposure;
+}
+
+FSOutput main(VSOutput input)
+{
+	FSOutput output = (FSOutput)0;
+	float4 color;
+	float3 wcNormal;
+
+	switch (type) {
+		case 0: // Skybox
+			{
+				float3 normal = normalize(input.UVW);
+				color = textureEnvMap.Sample(samplerEnvMap, normal);
+			}
+			break;
+
+		case 1: // Reflect
+			{
+				float3 wViewVec = mul((float4x3)ubo.inverseModelview, normalize(input.ViewVec)).xyz;
+				float3 normal = normalize(input.Normal);
+				float3 wNormal = mul((float4x3)ubo.inverseModelview, normal).xyz;
+
+				float NdotL = max(dot(normal, input.LightVec), 0.0);
+
+				float3 eyeDir = normalize(input.ViewVec);
+				float3 halfVec = normalize(input.LightVec + eyeDir);
+				float NdotH = max(dot(normal, halfVec), 0.0);
+				float NdotV = max(dot(normal, eyeDir), 0.0);
+				float VdotH = max(dot(eyeDir, halfVec), 0.0);
+
+				// Geometric attenuation
+				float NH2 = 2.0 * NdotH;
+				float g1 = (NH2 * NdotV) / VdotH;
+				float g2 = (NH2 * NdotL) / VdotH;
+				float geoAtt = min(1.0, min(g1, g2));
+
+				const float F0 = 0.6;
+				const float k = 0.2;
+
+				// Fresnel (schlick approximation)
+				float fresnel = pow(1.0 - VdotH, 5.0);
+				fresnel *= (1.0 - F0);
+				fresnel += F0;
+
+				float spec = (fresnel * geoAtt) / (NdotV * NdotL * 3.14);
+
+				color = textureEnvMap.Sample(samplerEnvMap, reflect(-wViewVec, wNormal));
+
+				color = float4(color.rgb * NdotL * (k + spec * (1.0 - k)), 1.0);
+			}
+			break;
+
+		case 2: // Refract
+			{
+				float3 wViewVec = mul((float4x3)ubo.inverseModelview, normalize(input.ViewVec)).xyz;
+				float3 wNormal = mul((float4x3)ubo.inverseModelview, input.Normal).xyz;
+				color = textureEnvMap.Sample(samplerEnvMap, refract(-wViewVec, wNormal, 1.0/1.6));
+			}
+			break;
+	}
+
+
+	// Color with manual exposure into attachment 0
+	output.Color0.rgb = float3(1.0, 1.0, 1.0) - exp(-color.rgb * exposure);
+
+	// Bright parts for bloom into attachment 1
+	float l = dot(output.Color0.rgb, float3(0.2126, 0.7152, 0.0722));
+	float threshold = 0.75;
+	output.Color1.rgb = (l > threshold) ? output.Color0.rgb : float3(0.0, 0.0, 0.0);
+	output.Color1.a = 1.0;
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/hdr/gbuffer.frag.spv b/external/Vulkan/data/shaders/hlsl/hdr/gbuffer.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..5cdc4e28b49cabf69ff50b2f168fd6b6b79560b5
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/hdr/gbuffer.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/hdr/gbuffer.vert b/external/Vulkan/data/shaders/hlsl/hdr/gbuffer.vert
new file mode 100644
index 0000000000000000000000000000000000000000..c833a812510e94e4cfc598671a277f50a87f4ab8
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/hdr/gbuffer.vert
@@ -0,0 +1,51 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+};
+
+[[vk::constant_id(0)]] const int type = 0;
+
+struct UBO  {
+	float4x4 projection;
+	float4x4 modelview;
+	float4x4 inverseModelview;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 UVW : TEXCOORD0;
+[[vk::location(1)]] float3 WorldPos : POSITION0;
+[[vk::location(2)]] float3 Normal : NORMAL0;
+[[vk::location(3)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(4)]] float3 LightVec : TEXCOORD2;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.UVW = input.Pos;
+
+	switch(type) {
+		case 0: // Skybox
+			output.WorldPos = mul((float4x3)ubo.modelview, input.Pos).xyz;
+			output.Pos = mul(ubo.projection, float4(output.WorldPos, 1.0));
+			break;
+		case 1: // Object
+			output.WorldPos = mul(ubo.modelview, float4(input.Pos, 1.0)).xyz;
+			output.Pos = mul(ubo.projection, mul(ubo.modelview, float4(input.Pos.xyz, 1.0)));
+			break;
+	}
+	output.WorldPos = mul(ubo.modelview, float4(input.Pos, 1.0)).xyz;
+	output.Normal = mul((float4x3)ubo.modelview, input.Normal).xyz;
+
+	float3 lightPos = float3(0.0f, -5.0f, 5.0f);
+	output.LightVec = lightPos.xyz - output.WorldPos.xyz;
+	output.ViewVec = -output.WorldPos.xyz;
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/hdr/gbuffer.vert.spv b/external/Vulkan/data/shaders/hlsl/hdr/gbuffer.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..3791964882095c68ec9858b283ad6bce2357bfbf
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/hdr/gbuffer.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/imgui/scene.frag b/external/Vulkan/data/shaders/hlsl/imgui/scene.frag
new file mode 100644
index 0000000000000000000000000000000000000000..cdb2d089bfe14216e126f961132af85ab1879b3c
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/imgui/scene.frag
@@ -0,0 +1,20 @@
+// Copyright 2020 Google LLC
+
+struct VSOutput
+{
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(3)]] float3 LightVec : TEXCOORD2;
+};
+
+float4 main(VSOutput input) : SV_TARGET
+{
+	float3 N = normalize(input.Normal);
+	float3 L = normalize(input.LightVec);
+	float3 V = normalize(input.ViewVec);
+	float3 R = reflect(-L, N);
+	float diffuse = max(dot(N, L), 0.0);
+	float3 specular = pow(max(dot(R, V), 0.0), 16.0) * float3(0.75, 0.75, 0.75);
+	return float4(diffuse * input.Color + specular, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/imgui/scene.frag.spv b/external/Vulkan/data/shaders/hlsl/imgui/scene.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..5893f07345673e5cdff95da3cb6264423763265a
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/imgui/scene.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/imgui/scene.vert b/external/Vulkan/data/shaders/hlsl/imgui/scene.vert
new file mode 100644
index 0000000000000000000000000000000000000000..f7a590991645d815cd820b0bcf6cde2ddfb8d68e
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/imgui/scene.vert
@@ -0,0 +1,41 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float3 Color : COLOR0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+	float4 lightPos;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(3)]] float3 LightVec : TEXCOORD2;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Normal = input.Normal;
+	output.Color = input.Color;
+	output.Pos = mul(ubo.projection, mul(ubo.model, float4(input.Pos.xyz, 1.0)));
+
+	float4 pos = mul(ubo.model, float4(input.Pos, 1.0));
+	output.Normal = mul((float4x3)ubo.model, input.Normal).xyz;
+	float3 lPos = mul((float4x3)ubo.model, ubo.lightPos.xyz).xyz;
+	output.LightVec = lPos - pos.xyz;
+	output.ViewVec = -pos.xyz;
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/imgui/scene.vert.spv b/external/Vulkan/data/shaders/hlsl/imgui/scene.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..c2d908467cbcb877aacc8d64ad0a754d926c376f
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/imgui/scene.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/imgui/ui.frag b/external/Vulkan/data/shaders/hlsl/imgui/ui.frag
new file mode 100644
index 0000000000000000000000000000000000000000..562acfe4c93b14d94b35d6462242e1239d716a66
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/imgui/ui.frag
@@ -0,0 +1,15 @@
+// Copyright 2020 Google LLC
+
+Texture2D fontTexture : register(t0);
+SamplerState fontSampler : register(s0);
+
+struct VSOutput
+{
+[[vk::location(0)]] float2 UV : TEXCOORD0;
+[[vk::location(1)]] float4 Color : COLOR0;
+};
+
+float4 main(VSOutput input) : SV_TARGET
+{
+	return input.Color * fontTexture.Sample(fontSampler, input.UV);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/imgui/ui.frag.spv b/external/Vulkan/data/shaders/hlsl/imgui/ui.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..282a17fd38c9dac950a8a07a997bf7d9c7b10d79
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/imgui/ui.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/imgui/ui.vert b/external/Vulkan/data/shaders/hlsl/imgui/ui.vert
new file mode 100644
index 0000000000000000000000000000000000000000..df4e48eb389cd5dc729756e0fb37dec2f02afd59
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/imgui/ui.vert
@@ -0,0 +1,33 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float2 Pos : POSITION0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+[[vk::location(2)]] float4 Color : COLOR0;
+};
+
+struct PushConstants
+{
+	float2 scale;
+	float2 translate;
+};
+
+[[vk::push_constant]]
+PushConstants pushConstants;
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float2 UV : TEXCOORD0;
+[[vk::location(1)]] float4 Color : COLOR0;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.UV = input.UV;
+	output.Color = input.Color;
+	output.Pos = float4(input.Pos * pushConstants.scale + pushConstants.translate, 0.0, 1.0);
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/imgui/ui.vert.spv b/external/Vulkan/data/shaders/hlsl/imgui/ui.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..86369ec39bd79ff0a0ef701c6a5db0af41ef955f
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/imgui/ui.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/indirectdraw/ground.frag b/external/Vulkan/data/shaders/hlsl/indirectdraw/ground.frag
new file mode 100644
index 0000000000000000000000000000000000000000..9b1a1a6446812094529e8d62177960c028474f2c
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/indirectdraw/ground.frag
@@ -0,0 +1,15 @@
+// Copyright 2020 Google LLC
+
+Texture2D textureColor : register(t2);
+SamplerState samplerColor : register(s2);
+
+struct VSOutput
+{
+[[vk::location(0)]] float2 UV : TEXCOORD0;
+};
+
+float4 main(VSOutput input) : SV_TARGET
+{
+	float4 color = textureColor.Sample(samplerColor, input.UV);
+	return float4(color.rgb, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/indirectdraw/ground.frag.spv b/external/Vulkan/data/shaders/hlsl/indirectdraw/ground.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..4f6047ff35509b6beedfca5c60dd5683cc1dc62c
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/indirectdraw/ground.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/indirectdraw/ground.vert b/external/Vulkan/data/shaders/hlsl/indirectdraw/ground.vert
new file mode 100644
index 0000000000000000000000000000000000000000..c6caf9e744029c7e4358b76ba1faa05b1d32ab64
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/indirectdraw/ground.vert
@@ -0,0 +1,31 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float4 Pos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float2 UV : TEXCOORD0;
+[[vk::location(3)]] float3 Color : COLOR0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 modelview;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float2 UV : TEXCOORD0;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.UV = input.UV * 32.0;
+	output.Pos = mul(ubo.projection, mul(ubo.modelview, float4(input.Pos.xyz, 1.0)));
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/indirectdraw/ground.vert.spv b/external/Vulkan/data/shaders/hlsl/indirectdraw/ground.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..38e2a1144093e12a7bd5fcd3b85b58c8f2598537
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/indirectdraw/ground.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/indirectdraw/indirectdraw.frag b/external/Vulkan/data/shaders/hlsl/indirectdraw/indirectdraw.frag
new file mode 100644
index 0000000000000000000000000000000000000000..395a5a2b8e2eb525d509ff5a6eac30f49b6c393a
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/indirectdraw/indirectdraw.frag
@@ -0,0 +1,29 @@
+// Copyright 2020 Google LLC
+
+Texture2DArray textureArray : register(t1);
+SamplerState samplerArray : register(s1);
+
+struct VSOutput
+{
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float3 UV : TEXCOORD0;
+[[vk::location(3)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(4)]] float3 LightVec : TEXCOORD2;
+};
+
+float4 main(VSOutput input) : SV_TARGET
+{
+	float4 color = textureArray.Sample(samplerArray, input.UV);
+
+	if (color.a < 0.5)
+	{
+		clip(-1);
+	}
+
+	float3 N = normalize(input.Normal);
+	float3 L = normalize(input.LightVec);
+	float3 ambient = float3(0.65, 0.65, 0.65);
+	float3 diffuse = max(dot(N, L), 0.0) * input.Color;
+	return float4((ambient + diffuse) * color.rgb, 1.0);
+}
diff --git a/external/Vulkan/data/shaders/hlsl/indirectdraw/indirectdraw.frag.spv b/external/Vulkan/data/shaders/hlsl/indirectdraw/indirectdraw.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..a738a2abd381baf2e8af68ca42f20261362f6837
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/indirectdraw/indirectdraw.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/indirectdraw/indirectdraw.vert b/external/Vulkan/data/shaders/hlsl/indirectdraw/indirectdraw.vert
new file mode 100644
index 0000000000000000000000000000000000000000..3d2b8afed5fba16a6a2341bb45f293663b764dfb
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/indirectdraw/indirectdraw.vert
@@ -0,0 +1,81 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float4 Pos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float2 UV : TEXCOORD0;
+[[vk::location(3)]] float3 Color : COLOR0;
+[[vk::location(4)]] float3 instancePos : POSITION1;
+[[vk::location(5)]] float3 instanceRot : TEXCOORD1;
+[[vk::location(6)]] float instanceScale : TEXCOORD2;
+[[vk::location(7)]] int instanceTexIndex : TEXCOORD3;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 modelview;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float3 UV : TEXCOORD0;
+[[vk::location(3)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(4)]] float3 LightVec : TEXCOORD2;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Color = input.Color;
+	output.UV = float3(input.UV, input.instanceTexIndex);
+
+	float4x4 mx, my, mz;
+
+	// rotate around x
+	float s = sin(input.instanceRot.x);
+	float c = cos(input.instanceRot.x);
+
+	mx[0] = float4(c, s, 0.0, 0.0);
+	mx[1] = float4(-s, c, 0.0, 0.0);
+	mx[2] = float4(0.0, 0.0, 1.0, 0.0);
+	mx[3] = float4(0.0, 0.0, 0.0, 1.0);
+
+	// rotate around y
+	s = sin(input.instanceRot.y);
+	c = cos(input.instanceRot.y);
+
+	my[0] = float4(c, 0.0, s, 0.0);
+	my[1] = float4(0.0, 1.0, 0.0, 0.0);
+	my[2] = float4(-s, 0.0, c, 0.0);
+	my[3] = float4(0.0, 0.0, 0.0, 1.0);
+
+	// rot around z
+	s = sin(input.instanceRot.z);
+	c = cos(input.instanceRot.z);
+
+	mz[0] = float4(1.0, 0.0, 0.0, 0.0);
+	mz[1] = float4(0.0, c, s, 0.0);
+	mz[2] = float4(0.0, -s, c, 0.0);
+	mz[3] = float4(0.0, 0.0, 0.0, 1.0);
+
+	float4x4 rotMat = mul(mz, mul(my, mx));
+
+	output.Normal = mul((float4x3)rotMat, input.Normal).xyz;
+
+	float4 pos = mul(rotMat, float4((input.Pos.xyz * input.instanceScale) + input.instancePos, 1.0));
+
+	output.Pos = mul(ubo.projection, mul(ubo.modelview, pos));
+
+	float4 wPos = mul(ubo.modelview, float4(pos.xyz, 1.0));
+	float4 lPos = float4(0.0, -5.0, 0.0, 1.0);
+	output.LightVec = lPos.xyz - pos.xyz;
+	output.ViewVec = -pos.xyz;
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/indirectdraw/indirectdraw.vert.spv b/external/Vulkan/data/shaders/hlsl/indirectdraw/indirectdraw.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..1c53a5deb6332358290bcc07b08a9de12f7a9fa5
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/indirectdraw/indirectdraw.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/indirectdraw/skysphere.frag b/external/Vulkan/data/shaders/hlsl/indirectdraw/skysphere.frag
new file mode 100644
index 0000000000000000000000000000000000000000..36aef5966145bce7dc4d8639527cbc22f197ce05
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/indirectdraw/skysphere.frag
@@ -0,0 +1,11 @@
+// Copyright 2020 Google LLC
+
+Texture2D textureColor : register(t2);
+SamplerState samplerColor : register(s2);
+
+float4 main([[vk::location(0)]] float2 inUV : TEXCOORD0) : SV_TARGET
+{
+	const float4 gradientStart = float4(0.93, 0.9, 0.81, 1.0);
+	const float4 gradientEnd = float4(0.35, 0.5, 1.0, 1.0);
+	return lerp(gradientStart, gradientEnd, min(0.5 - (inUV.y + 0.05), 0.5)/0.15 + 0.5);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/indirectdraw/skysphere.frag.spv b/external/Vulkan/data/shaders/hlsl/indirectdraw/skysphere.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..343023a7f9fa2e070bb66f7a5b8b704e575a70d3
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/indirectdraw/skysphere.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/indirectdraw/skysphere.vert b/external/Vulkan/data/shaders/hlsl/indirectdraw/skysphere.vert
new file mode 100644
index 0000000000000000000000000000000000000000..c03aefdb1d5934201b50dbe8c790301f3cc748b3
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/indirectdraw/skysphere.vert
@@ -0,0 +1,30 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float4 Pos : POSITION0;
+[[vk::location(2)]] float2 UV : TEXCOORD0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 modelview;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float2 UV : TEXCOORD0;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.UV = input.UV;
+	// Skysphere always at center, only use rotation part of modelview matrix
+	output.Pos = mul(ubo.projection, float4(mul((float3x3)ubo.modelview, input.Pos.xyz), 1));
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/indirectdraw/skysphere.vert.spv b/external/Vulkan/data/shaders/hlsl/indirectdraw/skysphere.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..8f580a6d513d00fde04b2a9dba3942722cf97b5e
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/indirectdraw/skysphere.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/inlineuniformblocks/pbr.frag b/external/Vulkan/data/shaders/hlsl/inlineuniformblocks/pbr.frag
new file mode 100644
index 0000000000000000000000000000000000000000..ad1bf2840b6188eaae9e5d5a6aacf2b20d1448cc
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/inlineuniformblocks/pbr.frag
@@ -0,0 +1,119 @@
+// Copyright 2020 Google LLC
+
+struct VSOutput
+{
+[[vk::location(0)]] float3 WorldPos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+	float4x4 view;
+	float3 camPos;
+};
+cbuffer ubo : register(b0) { UBO ubo; };
+
+// Inline uniform block
+struct UniformInline {
+	float roughness;
+	float metallic;
+	float r;
+	float g;
+	float b;
+	float ambient;
+};
+cbuffer material : register(b0, space1) { UniformInline material; };
+
+#define PI 3.14159265359
+
+float3 materialcolor()
+{
+	return float3(material.r, material.g, material.b);
+}
+
+// Normal Distribution function --------------------------------------
+float D_GGX(float dotNH, float roughness)
+{
+	float alpha = roughness * roughness;
+	float alpha2 = alpha * alpha;
+	float denom = dotNH * dotNH * (alpha2 - 1.0) + 1.0;
+	return (alpha2)/(PI * denom*denom);
+}
+
+// Geometric Shadowing function --------------------------------------
+float G_SchlicksmithGGX(float dotNL, float dotNV, float roughness)
+{
+	float r = (roughness + 1.0);
+	float k = (r*r) / 8.0;
+	float GL = dotNL / (dotNL * (1.0 - k) + k);
+	float GV = dotNV / (dotNV * (1.0 - k) + k);
+	return GL * GV;
+}
+
+// Fresnel function ----------------------------------------------------
+float3 F_Schlick(float cosTheta, float metallic)
+{
+	float3 F0 = lerp(float3(0.04, 0.04, 0.04), materialcolor(), metallic); // * material.specular
+	float3 F = F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
+	return F;
+}
+
+// Specular BRDF composition --------------------------------------------
+
+float3 BRDF(float3 L, float3 V, float3 N, float metallic, float roughness)
+{
+	// Precalculate vectors and dot products
+	float3 H = normalize (V + L);
+	float dotNV = clamp(dot(N, V), 0.0, 1.0);
+	float dotNL = clamp(dot(N, L), 0.0, 1.0);
+	float dotLH = clamp(dot(L, H), 0.0, 1.0);
+	float dotNH = clamp(dot(N, H), 0.0, 1.0);
+
+	// Light color fixed
+	float3 lightColor = float3(1.0, 1.0, 1.0);
+
+	float3 color = float3(0.0, 0.0, 0.0);
+
+	if (dotNL > 0.0)
+	{
+		float rroughness = max(0.05, roughness);
+		// D = Normal distribution (Distribution of the microfacets)
+		float D = D_GGX(dotNH, rroughness);
+		// G = Geometric shadowing term (Microfacets shadowing)
+		float G = G_SchlicksmithGGX(dotNL, dotNV, rroughness);
+		// F = Fresnel factor (Reflectance depending on angle of incidence)
+		float3 F = F_Schlick(dotNV, metallic);
+
+		float3 spec = D * F * G / (4.0 * dotNL * dotNV);
+
+		color += spec * dotNL * lightColor;
+	}
+
+	return color;
+}
+
+// ----------------------------------------------------------------------------
+float4 main(VSOutput input) : SV_TARGET
+{
+	float3 N = normalize(input.Normal);
+	float3 V = normalize(ubo.camPos - input.WorldPos);
+
+	float roughness = material.roughness;
+
+	// Specular contribution
+	float3 lightPos = float3(0.0f, 0.0f, 10.0f);
+	float3 Lo = float3(0.0, 0.0, 0.0);
+	float3 L = normalize(lightPos.xyz - input.WorldPos);
+	Lo += BRDF(L, V, N, material.metallic, roughness);
+
+	// Combine with ambient
+	float3 color = materialcolor() * material.ambient;
+	color += Lo;
+
+	// Gamma correct
+	color = pow(color, float3(0.4545, 0.4545, 0.4545));
+
+	return float4(color, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/inlineuniformblocks/pbr.frag.spv b/external/Vulkan/data/shaders/hlsl/inlineuniformblocks/pbr.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..94ee147286fc1ff1d1c2db7ec1de49aeb19710a6
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/inlineuniformblocks/pbr.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/inlineuniformblocks/pbr.vert b/external/Vulkan/data/shaders/hlsl/inlineuniformblocks/pbr.vert
new file mode 100644
index 0000000000000000000000000000000000000000..b9f5c1b31cdd8db9d3f24a3c249dee8168b48762
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/inlineuniformblocks/pbr.vert
@@ -0,0 +1,38 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+	float4x4 view;
+	float3 camPos;
+};
+cbuffer ubo : register(b0) { UBO ubo; };
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 WorldPos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+};
+
+struct PushConsts {
+	float3 objPos;
+};
+[[vk::push_constant]] PushConsts pushConsts;
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	float3 locPos = mul(ubo.model, float4(input.Pos, 1.0)).xyz;
+	output.WorldPos = locPos + pushConsts.objPos;
+	output.Normal = mul((float4x3)ubo.model, input.Normal).xyz;
+	output.Pos =  mul(ubo.projection, mul(ubo.view, float4(output.WorldPos, 1.0)));
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/inlineuniformblocks/pbr.vert.spv b/external/Vulkan/data/shaders/hlsl/inlineuniformblocks/pbr.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..2739d7529074e7d4c1bdf42643877c14ab7864f6
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/inlineuniformblocks/pbr.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/inputattachments/attachmentread.frag b/external/Vulkan/data/shaders/hlsl/inputattachments/attachmentread.frag
new file mode 100644
index 0000000000000000000000000000000000000000..28ef33ed40a9baee9ed0dd7b2b3b9bc3e7a99fc9
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/inputattachments/attachmentread.frag
@@ -0,0 +1,35 @@
+// Copyright 2020 Google LLC
+
+[[vk::input_attachment_index(0)]][[vk::binding(0)]] SubpassInput inputColor;
+[[vk::input_attachment_index(1)]][[vk::binding(1)]] SubpassInput inputDepth;
+
+struct UBO  {
+	float2 brightnessContrast;
+	float2 range;
+	int attachmentIndex;
+};
+
+cbuffer ubo : register(b2) { UBO ubo; }
+
+float3 brightnessContrast(float3 color, float brightness, float contrast) {
+	return (color - 0.5) * contrast + 0.5 + brightness;
+}
+
+float4 main() : SV_TARGET
+{
+	// Apply brightness and contrast filer to color input
+	if (ubo.attachmentIndex == 0) {
+		// Read color from previous color input attachment
+		float3 color = inputColor.SubpassLoad().rgb;
+		return float4(brightnessContrast(color, ubo.brightnessContrast[0], ubo.brightnessContrast[1]), 1);
+	}
+
+	// Visualize depth input range
+	if (ubo.attachmentIndex == 1) {
+		// Read depth from previous depth input attachment
+		float depth = inputDepth.SubpassLoad().r;
+		return float4((depth - ubo.range[0]) * 1.0 / (ubo.range[1] - ubo.range[0]).xxx, 1);
+	}
+
+	return 0.xxxx;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/inputattachments/attachmentread.frag.spv b/external/Vulkan/data/shaders/hlsl/inputattachments/attachmentread.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..4450a40590cbbc99e5b0553e77e01cc8edac0641
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/inputattachments/attachmentread.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/inputattachments/attachmentread.vert b/external/Vulkan/data/shaders/hlsl/inputattachments/attachmentread.vert
new file mode 100644
index 0000000000000000000000000000000000000000..fa447fcfb7418d5a62457134438a0b3e700460cf
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/inputattachments/attachmentread.vert
@@ -0,0 +1,6 @@
+// Copyright 2020 Google LLC
+
+float4 main(uint VertexIndex : SV_VertexID) : SV_POSITION
+{
+	return float4(float2((VertexIndex << 1) & 2, VertexIndex & 2) * 2.0f - 1.0f, 0.0f, 1.0f);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/inputattachments/attachmentread.vert.spv b/external/Vulkan/data/shaders/hlsl/inputattachments/attachmentread.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..ef56230746c95a033a10aee5bde2c6fa1cf1bea5
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/inputattachments/attachmentread.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/inputattachments/attachmentwrite.frag b/external/Vulkan/data/shaders/hlsl/inputattachments/attachmentwrite.frag
new file mode 100644
index 0000000000000000000000000000000000000000..35ea3f3a0393e59e96245d2d93099f62c5c9e1e9
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/inputattachments/attachmentwrite.frag
@@ -0,0 +1,24 @@
+// Copyright 2020 Google LLC
+
+struct VSOutput
+{
+[[vk::location(0)]] float3 Color : COLOR0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(3)]] float3 LightVec : TEXCOORD2;
+};
+
+float4 main(VSOutput input) : SV_TARGET
+{
+	// Toon shading color attachment output
+	float intensity = dot(normalize(input.Normal), normalize(input.LightVec));
+	float shade = 1.0;
+	shade = intensity < 0.5 ? 0.75 : shade;
+	shade = intensity < 0.35 ? 0.6 : shade;
+	shade = intensity < 0.25 ? 0.5 : shade;
+	shade = intensity < 0.1 ? 0.25 : shade;
+
+	return float4(input.Color * 3.0 * shade, 1);
+
+	// Depth attachment does not need to be explicitly written
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/inputattachments/attachmentwrite.frag.spv b/external/Vulkan/data/shaders/hlsl/inputattachments/attachmentwrite.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..6db6c47dbfcec0e2da0fa60548ccf79c841f4aaf
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/inputattachments/attachmentwrite.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/inputattachments/attachmentwrite.vert b/external/Vulkan/data/shaders/hlsl/inputattachments/attachmentwrite.vert
new file mode 100644
index 0000000000000000000000000000000000000000..eff5642aec81922b8eaa6b338278873bfef7af9d
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/inputattachments/attachmentwrite.vert
@@ -0,0 +1,36 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float3 Normal : NORMAL0;
+};
+
+struct UBO  {
+	float4x4 projection;
+	float4x4 model;
+	float4x4 view;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Color : COLOR0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(3)]] float3 LightVec : TEXCOORD2;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Pos = mul(ubo.projection, mul(ubo.view, mul(ubo.model, float4(input.Pos, 1.0))));
+	output.Color = input.Color;
+	output.Normal = input.Normal;
+	output.LightVec = float3(0.0f, 5.0f, 15.0f) - input.Pos;
+	output.ViewVec = -input.Pos.xyz;
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/inputattachments/attachmentwrite.vert.spv b/external/Vulkan/data/shaders/hlsl/inputattachments/attachmentwrite.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..c77bfa5b662b87b2140b1da42e97b7dc8df417bc
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/inputattachments/attachmentwrite.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/instancing/instancing.frag b/external/Vulkan/data/shaders/hlsl/instancing/instancing.frag
new file mode 100644
index 0000000000000000000000000000000000000000..1e3a0b7828b9d4600657081e52e106e0a5a6262d
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/instancing/instancing.frag
@@ -0,0 +1,25 @@
+// Copyright 2020 Google LLC
+
+Texture2DArray textureArray : register(t1);
+SamplerState samplerArray : register(s1);
+
+struct VSOutput
+{
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float3 UV : TEXCOORD0;
+[[vk::location(3)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(4)]] float3 LightVec : TEXCOORD2;
+};
+
+float4 main(VSOutput input) : SV_TARGET
+{
+	float4 color = textureArray.Sample(samplerArray, input.UV) * float4(input.Color, 1.0);
+	float3 N = normalize(input.Normal);
+	float3 L = normalize(input.LightVec);
+	float3 V = normalize(input.ViewVec);
+	float3 R = reflect(-L, N);
+	float3 diffuse = max(dot(N, L), 0.1) * input.Color;
+	float3 specular = (dot(N,L) > 0.0) ? pow(max(dot(R, V), 0.0), 16.0) * float3(0.75, 0.75, 0.75) * color.r : float3(0.0, 0.0, 0.0);
+	return float4(diffuse * color.rgb + specular, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/instancing/instancing.frag.spv b/external/Vulkan/data/shaders/hlsl/instancing/instancing.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..76c4aab3504aaf551d3f8a36145d0c9e22078e28
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/instancing/instancing.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/instancing/instancing.vert b/external/Vulkan/data/shaders/hlsl/instancing/instancing.vert
new file mode 100644
index 0000000000000000000000000000000000000000..62e412346f0e9a8a4380ffdaae2d593585972cce
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/instancing/instancing.vert
@@ -0,0 +1,89 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float2 UV : TEXCOORD0;
+[[vk::location(3)]] float3 Color : COLOR0;
+
+// Instanced attributes
+[[vk::location(4)]] float3 instancePos : POSITION1;
+[[vk::location(5)]] float3 instanceRot : TEXCOORD1;
+[[vk::location(6)]] float instanceScale : TEXCOORD2;
+[[vk::location(7)]] int instanceTexIndex : TEXCOORD3;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 modelview;
+	float4 lightPos;
+	float locSpeed;
+	float globSpeed;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float3 UV : TEXCOORD0;
+[[vk::location(3)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(4)]] float3 LightVec : TEXCOORD2;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Color = input.Color;
+	output.UV = float3(input.UV, input.instanceTexIndex);
+
+	// rotate around x
+	float s = sin(input.instanceRot.x + ubo.locSpeed);
+	float c = cos(input.instanceRot.x + ubo.locSpeed);
+
+	float3x3 mx = { c, -s, 0.0,
+					s, c, 0.0,
+					0.0, 0.0, 1.0 };
+
+	// rotate around y
+	s = sin(input.instanceRot.y + ubo.locSpeed);
+	c = cos(input.instanceRot.y + ubo.locSpeed);
+
+	float3x3 my = { c, 0.0, -s,
+					0.0, 1.0, 0.0,
+					s, 0.0, c };
+
+	// rot around z
+	s = sin(input.instanceRot.z + ubo.locSpeed);
+	c = cos(input.instanceRot.z + ubo.locSpeed);
+
+	float3x3 mz = { 1.0, 0.0, 0.0,
+					0.0, c, -s,
+					0.0, s, c };
+
+	float3x3 rotMat = mul(mz, mul(my, mx));
+
+	float4x4 gRotMat;
+	s = sin(input.instanceRot.y + ubo.globSpeed);
+	c = cos(input.instanceRot.y + ubo.globSpeed);
+	gRotMat[0] = float4(c, 0.0, -s, 0.0);
+	gRotMat[1] = float4(0.0, 1.0, 0.0, 0.0);
+	gRotMat[2] = float4(s, 0.0, c, 0.0);
+	gRotMat[3] = float4(0.0, 0.0, 0.0, 1.0);
+
+	float4 locPos = float4(mul(rotMat, input.Pos.xyz), 1.0);
+	float4 pos = float4((locPos.xyz * input.instanceScale) + input.instancePos, 1.0);
+
+	output.Pos = mul(ubo.projection, mul(ubo.modelview, mul(gRotMat, pos)));
+	output.Normal = mul((float3x3)mul(ubo.modelview, gRotMat), mul(rotMat, input.Normal));
+
+	pos = mul(ubo.modelview, float4(input.Pos.xyz + input.instancePos, 1.0));
+	float3 lPos = mul((float3x3)ubo.modelview, ubo.lightPos.xyz);
+	output.LightVec = lPos - pos.xyz;
+	output.ViewVec = -pos.xyz;
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/instancing/instancing.vert.spv b/external/Vulkan/data/shaders/hlsl/instancing/instancing.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..cfd2b12eec2eef5cd2cc2a0c762501bb8f077efd
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/instancing/instancing.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/instancing/planet.frag b/external/Vulkan/data/shaders/hlsl/instancing/planet.frag
new file mode 100644
index 0000000000000000000000000000000000000000..8fca364217894351e3c6d5ab3341ee001f3f2ab0
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/instancing/planet.frag
@@ -0,0 +1,25 @@
+// Copyright 2020 Google LLC
+
+Texture2D textureColorMap : register(t1);
+SamplerState samplerColorMap : register(s1);
+
+struct VSOutput
+{
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float2 UV : TEXCOORD0;
+[[vk::location(3)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(4)]] float3 LightVec : TEXCOORD2;
+};
+
+float4 main(VSOutput input) : SV_TARGET
+{
+	float4 color = textureColorMap.Sample(samplerColorMap, input.UV) * float4(input.Color, 1.0) * 1.5;
+	float3 N = normalize(input.Normal);
+	float3 L = normalize(input.LightVec);
+	float3 V = normalize(input.ViewVec);
+	float3 R = reflect(-L, N);
+	float3 diffuse = max(dot(N, L), 0.0) * input.Color;
+	float3 specular = pow(max(dot(R, V), 0.0), 4.0) * float3(0.5, 0.5, 0.5) * color.r;
+	return float4(diffuse * color.rgb + specular, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/instancing/planet.frag.spv b/external/Vulkan/data/shaders/hlsl/instancing/planet.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..a768f1b209def263824bea7783eacb9775cdb342
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/instancing/planet.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/instancing/planet.vert b/external/Vulkan/data/shaders/hlsl/instancing/planet.vert
new file mode 100644
index 0000000000000000000000000000000000000000..0be1e34e16f7549cf8622311660b50b589c10dc2
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/instancing/planet.vert
@@ -0,0 +1,43 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float2 UV : TEXCOORD0;
+[[vk::location(3)]] float3 Color : COLOR0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 modelview;
+	float4 lightPos;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float2 UV : TEXCOORD0;
+[[vk::location(3)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(4)]] float3 LightVec : TEXCOORD2;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Color = input.Color;
+	output.UV = input.UV;
+	output.Pos = mul(ubo.projection, mul(ubo.modelview, float4(input.Pos.xyz, 1.0)));
+
+	float4 pos = mul(ubo.modelview, float4(input.Pos, 1.0));
+	output.Normal = mul((float3x3)ubo.modelview, input.Normal);
+	float3 lPos = mul((float3x3)ubo.modelview, ubo.lightPos.xyz);
+	output.LightVec = lPos - pos.xyz;
+	output.ViewVec = -pos.xyz;
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/instancing/planet.vert.spv b/external/Vulkan/data/shaders/hlsl/instancing/planet.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..4da7e54762403626ac651d39eafcdf672f894fc0
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/instancing/planet.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/instancing/starfield.frag b/external/Vulkan/data/shaders/hlsl/instancing/starfield.frag
new file mode 100644
index 0000000000000000000000000000000000000000..a32fbaaed4b6e8f477dc895993a9c2f0d84006f9
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/instancing/starfield.frag
@@ -0,0 +1,30 @@
+// Copyright 2020 Google LLC
+
+#define HASHSCALE3 float3(443.897, 441.423, 437.195)
+#define STARFREQUENCY 0.01
+
+// Hash function by Dave Hoskins (https://www.shadertoy.com/view/4djSRW)
+float hash33(float3 p3)
+{
+	p3 = frac(p3 * HASHSCALE3);
+	p3 += dot(p3, p3.yxz+float3(19.19, 19.19, 19.19));
+	return frac((p3.x + p3.y)*p3.z + (p3.x+p3.z)*p3.y + (p3.y+p3.z)*p3.x);
+}
+
+float3 starField(float3 pos)
+{
+	float3 color = float3(0.0, 0.0, 0.0);
+	float threshhold = (1.0 - STARFREQUENCY);
+	float rnd = hash33(pos);
+	if (rnd >= threshhold)
+	{
+		float starCol = pow((rnd - threshhold) / (1.0 - threshhold), 16.0);
+		color += starCol.xxx;
+	}
+	return color;
+}
+
+float4 main([[vk::location(0)]] float3 inUVW : TEXCOORD0) : SV_TARGET
+{
+	return float4(starField(inUVW), 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/instancing/starfield.frag.spv b/external/Vulkan/data/shaders/hlsl/instancing/starfield.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..594b95e851b8136ffa43c02bbb7e0f8f78ac8f82
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/instancing/starfield.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/instancing/starfield.vert b/external/Vulkan/data/shaders/hlsl/instancing/starfield.vert
new file mode 100644
index 0000000000000000000000000000000000000000..cb7a40d21e22592ae771ccc7ad1d40cb5697e678
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/instancing/starfield.vert
@@ -0,0 +1,15 @@
+// Copyright 2020 Google LLC
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 UVW : TEXCOORD0;
+};
+
+VSOutput main(uint VertexIndex : SV_VertexID)
+{
+	VSOutput output = (VSOutput)0;
+	output.UVW = float3((VertexIndex << 1) & 2, VertexIndex & 2, VertexIndex & 2);
+	output.Pos = float4(output.UVW.xy * 2.0f - 1.0f, 0.0f, 1.0f);
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/instancing/starfield.vert.spv b/external/Vulkan/data/shaders/hlsl/instancing/starfield.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..48afecacb57c9621d491ee20f88529aa0df6f5ec
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/instancing/starfield.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/mesh/mesh.frag b/external/Vulkan/data/shaders/hlsl/mesh/mesh.frag
new file mode 100644
index 0000000000000000000000000000000000000000..2bc0a9c2ada56500f6274d041cc43bb576f55197
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/mesh/mesh.frag
@@ -0,0 +1,26 @@
+// Copyright 2020 Google LLC
+
+Texture2D textureColorMap : register(t1);
+SamplerState samplerColorMap : register(s1);
+
+struct VSOutput
+{
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float2 UV : TEXCOORD0;
+[[vk::location(3)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(4)]] float3 LightVec : TEXCOORD2;
+};
+
+float4 main(VSOutput input) : SV_TARGET
+{
+	float4 color = textureColorMap.Sample(samplerColorMap, input.UV) * float4(input.Color, 1.0);
+
+	float3 N = normalize(input.Normal);
+	float3 L = normalize(input.LightVec);
+	float3 V = normalize(input.ViewVec);
+	float3 R = reflect(-L, N);
+	float3 diffuse = max(dot(N, L), 0.0) * input.Color;
+	float3 specular = pow(max(dot(R, V), 0.0), 16.0) * float3(0.75, 0.75, 0.75);
+	return float4(diffuse * color.rgb + specular, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/mesh/mesh.frag.spv b/external/Vulkan/data/shaders/hlsl/mesh/mesh.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..b7626a88c842bf480b89b4e4b2f1d43de5d378c1
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/mesh/mesh.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/mesh/mesh.vert b/external/Vulkan/data/shaders/hlsl/mesh/mesh.vert
new file mode 100644
index 0000000000000000000000000000000000000000..2ea7b03b0fcf9e4a4582ecb250ebc8a47e2a6a6a
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/mesh/mesh.vert
@@ -0,0 +1,44 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float2 UV : TEXCOORD0;
+[[vk::location(3)]] float3 Color : COLOR0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+	float4 lightPos;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float2 UV : TEXCOORD0;
+[[vk::location(3)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(4)]] float3 LightVec : TEXCOORD2;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Normal = input.Normal;
+	output.Color = input.Color;
+	output.UV = input.UV;
+	output.Pos = mul(ubo.projection, mul(ubo.model, float4(input.Pos.xyz, 1.0)));
+
+	float4 pos = mul(ubo.model, float4(input.Pos, 1.0));
+	output.Normal = mul((float3x3)ubo.model, input.Normal);
+	float3 lPos = mul((float3x3)ubo.model, ubo.lightPos.xyz);
+	output.LightVec = lPos - pos.xyz;
+	output.ViewVec = -pos.xyz;
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/mesh/mesh.vert.spv b/external/Vulkan/data/shaders/hlsl/mesh/mesh.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..38769e287b907506b67c1ff7879ccd80085eeb66
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/mesh/mesh.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/multisampling/mesh.frag b/external/Vulkan/data/shaders/hlsl/multisampling/mesh.frag
new file mode 100644
index 0000000000000000000000000000000000000000..2bc0a9c2ada56500f6274d041cc43bb576f55197
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/multisampling/mesh.frag
@@ -0,0 +1,26 @@
+// Copyright 2020 Google LLC
+
+Texture2D textureColorMap : register(t1);
+SamplerState samplerColorMap : register(s1);
+
+struct VSOutput
+{
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float2 UV : TEXCOORD0;
+[[vk::location(3)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(4)]] float3 LightVec : TEXCOORD2;
+};
+
+float4 main(VSOutput input) : SV_TARGET
+{
+	float4 color = textureColorMap.Sample(samplerColorMap, input.UV) * float4(input.Color, 1.0);
+
+	float3 N = normalize(input.Normal);
+	float3 L = normalize(input.LightVec);
+	float3 V = normalize(input.ViewVec);
+	float3 R = reflect(-L, N);
+	float3 diffuse = max(dot(N, L), 0.0) * input.Color;
+	float3 specular = pow(max(dot(R, V), 0.0), 16.0) * float3(0.75, 0.75, 0.75);
+	return float4(diffuse * color.rgb + specular, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/multisampling/mesh.frag.spv b/external/Vulkan/data/shaders/hlsl/multisampling/mesh.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..b7626a88c842bf480b89b4e4b2f1d43de5d378c1
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/multisampling/mesh.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/multisampling/mesh.vert b/external/Vulkan/data/shaders/hlsl/multisampling/mesh.vert
new file mode 100644
index 0000000000000000000000000000000000000000..4e5e9f4e0f50501420feb8231acbab2b07dc10a6
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/multisampling/mesh.vert
@@ -0,0 +1,43 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float2 UV : TEXCOORD0;
+[[vk::location(3)]] float3 Color : COLOR0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+	float4 lightPos;
+};
+cbuffer ubo : register(b0) { UBO ubo; };
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float2 UV : TEXCOORD0;
+[[vk::location(3)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(4)]] float3 LightVec : TEXCOORD2;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Normal = input.Normal;
+	output.Color = input.Color;
+	output.UV = input.UV;
+	output.Pos = mul(ubo.projection, mul(ubo.model, float4(input.Pos.xyz, 1.0)));
+
+	float4 pos = mul(ubo.model, float4(input.Pos, 0.0));
+	output.Normal = mul((float3x3)ubo.model, input.Normal);
+	float3 lPos = mul((float3x3)ubo.model, ubo.lightPos.xyz);
+	output.LightVec = lPos - input.Pos;
+	output.ViewVec = -input.Pos;
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/multisampling/mesh.vert.spv b/external/Vulkan/data/shaders/hlsl/multisampling/mesh.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..96af4636ae4f0b94710d6c19c5911e31e514ece3
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/multisampling/mesh.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/multithreading/phong.frag b/external/Vulkan/data/shaders/hlsl/multithreading/phong.frag
new file mode 100644
index 0000000000000000000000000000000000000000..5212cd15054a8755fab7c31487e1b1c4e307aa9f
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/multithreading/phong.frag
@@ -0,0 +1,20 @@
+// Copyright 2020 Google LLC
+
+struct VSOutput
+{
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(3)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(4)]] float3 LightVec : TEXCOORD2;
+};
+
+float4 main(VSOutput input) : SV_TARGET
+{
+	float3 N = normalize(input.Normal);
+	float3 L = normalize(input.LightVec);
+	float3 V = normalize(input.ViewVec);
+	float3 R = reflect(-L, N);
+	float3 diffuse = max(dot(N, L), 0.0) * input.Color;
+	float3 specular = pow(max(dot(R, V), 0.0), 8.0) * float3(0.75, 0.75, 0.75);
+	return float4(diffuse + specular, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/multithreading/phong.frag.spv b/external/Vulkan/data/shaders/hlsl/multithreading/phong.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..2990c6794f2672eb6095c6183c5e5c19e3b2c19f
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/multithreading/phong.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/multithreading/phong.vert b/external/Vulkan/data/shaders/hlsl/multithreading/phong.vert
new file mode 100644
index 0000000000000000000000000000000000000000..1487ba170ad1df260c74cb19858d418c82a532ce
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/multithreading/phong.vert
@@ -0,0 +1,49 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float3 Color : COLOR0;
+};
+
+struct PushConsts
+{
+	float4x4 mvp;
+	float3 color;
+};
+[[vk::push_constant]]PushConsts pushConsts;
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(3)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(4)]] float3 LightVec : TEXCOORD2;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Normal = input.Normal;
+
+	if ( (input.Color.r == 1.0) && (input.Color.g == 0.0) && (input.Color.b == 0.0))
+	{
+		output.Color = pushConsts.color;
+	}
+	else
+	{
+		output.Color = input.Color;
+	}
+
+	output.Pos = mul(pushConsts.mvp, float4(input.Pos.xyz, 1.0));
+
+    float4 pos = mul(pushConsts.mvp, float4(input.Pos, 1.0));
+    output.Normal = mul((float3x3)pushConsts.mvp, input.Normal);
+//	float3 lPos = ubo.lightPos.xyz;
+float3 lPos = float3(0.0, 0.0, 0.0);
+    output.LightVec = lPos - pos.xyz;
+    output.ViewVec = -pos.xyz;
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/multithreading/phong.vert.spv b/external/Vulkan/data/shaders/hlsl/multithreading/phong.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..b5fd2a3074a078cda25ad424aee3f485bc56d4fa
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/multithreading/phong.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/multithreading/starsphere.frag b/external/Vulkan/data/shaders/hlsl/multithreading/starsphere.frag
new file mode 100644
index 0000000000000000000000000000000000000000..aedb7e1df42cd64e22f9452c45c2b3a686fe69f2
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/multithreading/starsphere.frag
@@ -0,0 +1,35 @@
+// Copyright 2020 Google LLC
+
+#define HASHSCALE3 float3(443.897, 441.423, 437.195)
+#define STARFREQUENCY 0.01
+
+// Hash function by Dave Hoskins (https://www.shadertoy.com/view/4djSRW)
+float hash33(float3 p3)
+{
+	p3 = frac(p3 * HASHSCALE3);
+    p3 += dot(p3, p3.yxz+float3(19.19, 19.19, 19.19));
+    return frac((p3.x + p3.y)*p3.z + (p3.x+p3.z)*p3.y + (p3.y+p3.z)*p3.x);
+}
+
+float3 starField(float3 pos)
+{
+	float3 color = float3(0.0, 0.0, 0.0);
+    float threshhold = (1.0 - STARFREQUENCY);
+    float rnd = hash33(pos);
+    if (rnd >= threshhold)
+    {
+        float starCol = pow((rnd - threshhold) / (1.0 - threshhold), 16.0);
+		color += starCol.xxx;
+    }
+	return color;
+}
+
+float4 main([[vk::location(0)]] float3 inUVW : TEXCOORD0) : SV_TARGET
+{
+	// Fake atmosphere at the bottom
+	float3 atmosphere = clamp(float3(0.1, 0.15, 0.4) * (inUVW.y + 0.25), 0.0, 1.0);
+
+	float3 color = starField(inUVW) + atmosphere;
+
+	return float4(color, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/multithreading/starsphere.frag.spv b/external/Vulkan/data/shaders/hlsl/multithreading/starsphere.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..6a5eddc3bcdcc5e70366a749067008de2a69a4b2
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/multithreading/starsphere.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/multithreading/starsphere.vert b/external/Vulkan/data/shaders/hlsl/multithreading/starsphere.vert
new file mode 100644
index 0000000000000000000000000000000000000000..13e48cb2fae2773a7f601201990d39baba40dc16
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/multithreading/starsphere.vert
@@ -0,0 +1,21 @@
+// Copyright 2020 Google LLC
+
+struct PushConsts
+{
+	float4x4 mvp;
+};
+[[vk::push_constant]]PushConsts pushConsts;
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 UVW : TEXCOORD0;
+};
+
+VSOutput main([[vk::location(0)]] float3 Pos : POSITION0)
+{
+	VSOutput output = (VSOutput)0;
+	output.UVW = Pos;
+	output.Pos = mul(pushConsts.mvp, float4(Pos.xyz, 1.0));
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/multithreading/starsphere.vert.spv b/external/Vulkan/data/shaders/hlsl/multithreading/starsphere.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..815e7257e17247f0a5ead54a3a9b5b10d711d51b
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/multithreading/starsphere.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/multiview/multiview.frag b/external/Vulkan/data/shaders/hlsl/multiview/multiview.frag
new file mode 100644
index 0000000000000000000000000000000000000000..2c14be89d22b2ad92388e59a39d4d05f86f0658c
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/multiview/multiview.frag
@@ -0,0 +1,21 @@
+// Copyright 2020 Google LLC
+
+struct VSOutput
+{
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(3)]] float3 LightVec : TEXCOORD2;
+};
+
+float4 main(VSOutput input) : SV_TARGET
+{
+	float3 N = normalize(input.Normal);
+	float3 L = normalize(input.LightVec);
+	float3 V = normalize(input.ViewVec);
+	float3 R = reflect(-L, N);
+	float3 ambient = float3(0.1, 0.1, 0.1);
+	float3 diffuse = max(dot(N, L), 0.0) * float3(1.0, 1.0, 1.0);
+	float3 specular = pow(max(dot(R, V), 0.0), 16.0) * float3(0.75, 0.75, 0.75);
+	return float4((ambient + diffuse) * input.Color.rgb + specular, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/multiview/multiview.frag.spv b/external/Vulkan/data/shaders/hlsl/multiview/multiview.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..6966e9ad87589fb7abd6317ca898f0e8a94a36b1
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/multiview/multiview.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/multiview/multiview.vert b/external/Vulkan/data/shaders/hlsl/multiview/multiview.vert
new file mode 100644
index 0000000000000000000000000000000000000000..214db9cfb8c80fef6b185bc14d5a94a3c0aa1e17
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/multiview/multiview.vert
@@ -0,0 +1,43 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float3 Color : COLOR0;
+};
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(3)]] float3 LightVec : TEXCOORD2;
+};
+
+struct UBO
+{
+	float4x4 projection[2];
+	float4x4 modelview[2];
+	float4 lightPos;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+VSOutput main(VSInput input, uint ViewIndex : SV_ViewID)
+{
+	VSOutput output = (VSOutput)0;
+	output.Color = input.Color;
+	output.Normal = mul((float3x3)ubo.modelview[ViewIndex], input.Normal);
+
+	float4 pos = float4(input.Pos.xyz, 1.0);
+	float4 worldPos = mul(ubo.modelview[ViewIndex], pos);
+
+	float3 lPos = mul(ubo.modelview[ViewIndex], ubo.lightPos).xyz;
+	output.LightVec = lPos - worldPos.xyz;
+	output.ViewVec = -worldPos.xyz;
+
+	output.Pos = mul(ubo.projection[ViewIndex], worldPos);
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/multiview/multiview.vert.spv b/external/Vulkan/data/shaders/hlsl/multiview/multiview.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..54de06034bba6cd49008ffb08246477f2398c03c
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/multiview/multiview.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/multiview/viewdisplay.frag b/external/Vulkan/data/shaders/hlsl/multiview/viewdisplay.frag
new file mode 100644
index 0000000000000000000000000000000000000000..da365d0037fb599d44bfcff51e09973037535edd
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/multiview/viewdisplay.frag
@@ -0,0 +1,25 @@
+// Copyright 2020 Google LLC
+
+Texture2DArray textureView : register(t1);
+SamplerState samplerView : register(s1);
+
+struct UBO
+{
+	[[vk::offset(272)]] float distortionAlpha;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+[[vk::constant_id(0)]] const float VIEW_LAYER = 0.0f;
+
+float4 main([[vk::location(0)]] float2 inUV : TEXCOORD0) : SV_TARGET
+{
+	const float alpha = ubo.distortionAlpha;
+
+	float2 p1 = float2(2.0 * inUV - 1.0);
+	float2 p2 = p1 / (1.0 - alpha * length(p1));
+	p2 = (p2 + 1.0) * 0.5;
+
+	bool inside = ((p2.x >= 0.0) && (p2.x <= 1.0) && (p2.y >= 0.0 ) && (p2.y <= 1.0));
+	return inside ? textureView.Sample(samplerView, float3(p2, VIEW_LAYER)) : float4(0.0, 0.0, 0.0, 0.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/multiview/viewdisplay.frag.spv b/external/Vulkan/data/shaders/hlsl/multiview/viewdisplay.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..59d1aa2970400fd16c44f4ee5cc0a0c34861b648
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/multiview/viewdisplay.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/multiview/viewdisplay.vert b/external/Vulkan/data/shaders/hlsl/multiview/viewdisplay.vert
new file mode 100644
index 0000000000000000000000000000000000000000..b13c2bf2353ce1fc1837fef4db32336429c22412
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/multiview/viewdisplay.vert
@@ -0,0 +1,15 @@
+// Copyright 2020 Google LLC
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float2 UV : TEXCOORD0;
+};
+
+VSOutput main(uint VertexIndex : SV_VertexID)
+{
+	VSOutput output = (VSOutput)0;
+	output.UV = float2((VertexIndex << 1) & 2, VertexIndex & 2);
+	output.Pos = float4(output.UV * 2.0f - 1.0f, 0.0f, 1.0f);
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/multiview/viewdisplay.vert.spv b/external/Vulkan/data/shaders/hlsl/multiview/viewdisplay.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..46c4b31fdc1568cc56662cc746d3a2d10f9d0983
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/multiview/viewdisplay.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/negativeviewportheight/quad.frag b/external/Vulkan/data/shaders/hlsl/negativeviewportheight/quad.frag
new file mode 100644
index 0000000000000000000000000000000000000000..8236872c84b4ed7599db40d2baf74a9674252335
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/negativeviewportheight/quad.frag
@@ -0,0 +1,9 @@
+// Copyright 2020 Google LLC
+
+Texture2D textureColor : register(t0);
+SamplerState samplerColor : register(s0);
+
+float4 main([[vk::location(0)]] float2 inUV : TEXCOORD0) : SV_TARGET
+{
+	return textureColor.Sample(samplerColor, inUV);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/negativeviewportheight/quad.frag.spv b/external/Vulkan/data/shaders/hlsl/negativeviewportheight/quad.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..6247b5ff71ace1fa7e501c0bfd5c7ee9b5eb85cd
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/negativeviewportheight/quad.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/negativeviewportheight/quad.vert b/external/Vulkan/data/shaders/hlsl/negativeviewportheight/quad.vert
new file mode 100644
index 0000000000000000000000000000000000000000..1864c0beac35f5629b949735ba2cc44124a62cae
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/negativeviewportheight/quad.vert
@@ -0,0 +1,21 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+};
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float2 UV : TEXCOORD0;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.UV = input.UV;
+	output.Pos = float4(input.Pos, 1.0f);
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/negativeviewportheight/quad.vert.spv b/external/Vulkan/data/shaders/hlsl/negativeviewportheight/quad.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..2e4b9563b0719538bcdcbaee8f847990a3dcce60
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/negativeviewportheight/quad.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/occlusionquery/mesh.frag b/external/Vulkan/data/shaders/hlsl/occlusionquery/mesh.frag
new file mode 100644
index 0000000000000000000000000000000000000000..9347ed1038fc6d15cf1d69a7ffbad30b497b5913
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/occlusionquery/mesh.frag
@@ -0,0 +1,28 @@
+// Copyright 2020 Google LLC
+
+struct VSOutput
+{
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float Visible : TEXCOORD3;
+[[vk::location(3)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(4)]] float3 LightVec : TEXCOORD2;
+};
+
+float4 main(VSOutput input) : SV_TARGET
+{
+	if (input.Visible > 0.0)
+	{
+		float3 N = normalize(input.Normal);
+		float3 L = normalize(input.LightVec);
+		float3 V = normalize(input.ViewVec);
+		float3 R = reflect(-L, N);
+		float3 diffuse = max(dot(N, L), 0.0) * input.Color;
+		float3 specular = pow(max(dot(R, V), 0.0), 8.0) * float3(0.75, 0.75, 0.75);
+		return float4(diffuse + specular, 1.0);
+	}
+	else
+	{
+		return float4(float3(0.1, 0.1, 0.1), 1.0);
+	}
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/occlusionquery/mesh.frag.spv b/external/Vulkan/data/shaders/hlsl/occlusionquery/mesh.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..6c02ae552e49844c37e779954cf2fb969075d95b
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/occlusionquery/mesh.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/occlusionquery/mesh.vert b/external/Vulkan/data/shaders/hlsl/occlusionquery/mesh.vert
new file mode 100644
index 0000000000000000000000000000000000000000..4b20662a4f15056a4c9339a5d088c1d7ea6b7c83
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/occlusionquery/mesh.vert
@@ -0,0 +1,44 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float3 Color : COLOR0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 modelview;
+	float4 lightPos;
+	float visible;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float Visible : TEXCOORD3;
+[[vk::location(3)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(4)]] float3 LightVec : TEXCOORD2;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Normal = input.Normal;
+	output.Color = input.Color;
+	output.Visible = ubo.visible;
+
+	output.Pos = mul(ubo.projection, mul(ubo.modelview, float4(input.Pos.xyz, 1.0)));
+
+    float4 pos = mul(ubo.modelview, float4(input.Pos, 1.0));
+    output.Normal = mul((float3x3)ubo.modelview, input.Normal);
+    output.LightVec = ubo.lightPos.xyz - pos.xyz;
+    output.ViewVec = -pos.xyz;
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/occlusionquery/mesh.vert.spv b/external/Vulkan/data/shaders/hlsl/occlusionquery/mesh.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..5329a260634666cab6d389683a6341ec4bf1c9cd
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/occlusionquery/mesh.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/occlusionquery/occluder.frag b/external/Vulkan/data/shaders/hlsl/occlusionquery/occluder.frag
new file mode 100644
index 0000000000000000000000000000000000000000..e18b7b3302882f9fbffaf7142b6c289778636f0e
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/occlusionquery/occluder.frag
@@ -0,0 +1,6 @@
+// Copyright 2020 Google LLC
+
+float4 main([[vk::location(0)]] float3 Color : COLOR0) : SV_TARGET
+{
+    return float4(Color, 0.5);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/occlusionquery/occluder.frag.spv b/external/Vulkan/data/shaders/hlsl/occlusionquery/occluder.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..2325bb39be6785034b5bf44e0abc2d901ef7636e
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/occlusionquery/occluder.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/occlusionquery/occluder.vert b/external/Vulkan/data/shaders/hlsl/occlusionquery/occluder.vert
new file mode 100644
index 0000000000000000000000000000000000000000..dce12c8aae8587502ec675f7bb2cf2b22dbd42e9
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/occlusionquery/occluder.vert
@@ -0,0 +1,31 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float3 Color : COLOR0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 modelview;
+	float4 lightPos;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Color : COLOR0;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Color = input.Color;
+	output.Pos = mul(ubo.projection, mul(ubo.modelview, float4(input.Pos.xyz, 1.0)));
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/occlusionquery/occluder.vert.spv b/external/Vulkan/data/shaders/hlsl/occlusionquery/occluder.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..ef0a47081d33e9a50c485ddc01e231f6ccf634b7
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/occlusionquery/occluder.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/occlusionquery/simple.frag b/external/Vulkan/data/shaders/hlsl/occlusionquery/simple.frag
new file mode 100644
index 0000000000000000000000000000000000000000..a898835c3b23d146d5303b5a846a12cb0c578b73
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/occlusionquery/simple.frag
@@ -0,0 +1,6 @@
+// Copyright 2020 Google LLC
+
+float4 main([[vk::location(0)]] float3 Color : COLOR0) : SV_TARGET
+{
+    return float4(1.0, 1.0, 1.0, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/occlusionquery/simple.frag.spv b/external/Vulkan/data/shaders/hlsl/occlusionquery/simple.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..47bfe270a628c13a076067c0fe5016533649da09
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/occlusionquery/simple.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/occlusionquery/simple.vert b/external/Vulkan/data/shaders/hlsl/occlusionquery/simple.vert
new file mode 100644
index 0000000000000000000000000000000000000000..1618e05bfa5e59b1a441f07fe7693fc84f5cfaa0
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/occlusionquery/simple.vert
@@ -0,0 +1,23 @@
+// Copyright 2020 Google LLC
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 modelview;
+	float4 lightPos;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Color : COLOR0;
+};
+
+VSOutput main([[vk::location(0)]] float3 Pos : POSITION0)
+{
+	VSOutput output = (VSOutput)0;
+	output.Pos = mul(ubo.projection, mul(ubo.modelview, float4(Pos.xyz, 1.0)));
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/occlusionquery/simple.vert.spv b/external/Vulkan/data/shaders/hlsl/occlusionquery/simple.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..2756aedeb775856e17f7c55829447f613a82dc45
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/occlusionquery/simple.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/offscreen/mirror.frag b/external/Vulkan/data/shaders/hlsl/offscreen/mirror.frag
new file mode 100644
index 0000000000000000000000000000000000000000..b9c68b7e50d328867afbf631b32c185ae2adf58d
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/offscreen/mirror.frag
@@ -0,0 +1,42 @@
+// Copyright 2020 Google LLC
+
+Texture2D textureColor : register(t1);
+SamplerState samplerColor : register(s1);
+
+struct VSOutput
+{
+[[vk::location(0)]] float2 UV : TEXCOORD0;
+[[vk::location(1)]] float4 ProjCoord : POSITION0;
+};
+
+float4 main(VSOutput input, bool FrontFacing : SV_IsFrontFace) : SV_TARGET
+{
+	float4 tmp = (1.0 / input.ProjCoord.w).xxxx;
+	float4 projCoord = input.ProjCoord * tmp;
+
+	// Scale and bias
+	projCoord += float4(1.0, 1.0, 1.0, 1.0);
+	projCoord *= float4(0.5, 0.5, 0.5, 0.5);
+
+	// Slow single pass blur
+	// For demonstration purposes only
+	const float blurSize = 1.0 / 512.0;
+
+	float4 color = float4(float3(0.0, 0.0, 0.0), 1.);
+
+	if (FrontFacing)
+	{
+		// Only render mirrored scene on front facing (upper) side of mirror surface
+		float4 reflection = float4(0.0, 0.0, 0.0, 0.0);
+		for (int x = -3; x <= 3; x++)
+		{
+			for (int y = -3; y <= 3; y++)
+			{
+				reflection += textureColor.Sample(samplerColor, float2(projCoord.x + x * blurSize, projCoord.y + y * blurSize)) / 49.0;
+			}
+		}
+		color += reflection;
+	}
+
+	return color;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/offscreen/mirror.frag.spv b/external/Vulkan/data/shaders/hlsl/offscreen/mirror.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..89f23ca2b62227d46b5ee16cf089779a153e130a
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/offscreen/mirror.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/offscreen/mirror.vert b/external/Vulkan/data/shaders/hlsl/offscreen/mirror.vert
new file mode 100644
index 0000000000000000000000000000000000000000..671a3138ef0258af4f14eb29277aa7867861248e
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/offscreen/mirror.vert
@@ -0,0 +1,32 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 view;
+	float4x4 model;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float2 UV : TEXCOORD0;
+[[vk::location(1)]] float4 ProjCoord : POSITION0;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.UV = input.UV;
+	output.ProjCoord = mul(ubo.projection, mul(ubo.view, mul(ubo.model, float4(input.Pos.xyz, 1.0))));
+	output.Pos = output.ProjCoord;
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/offscreen/mirror.vert.spv b/external/Vulkan/data/shaders/hlsl/offscreen/mirror.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..6c1d5e98c607fceb0651fbc19c82d59be0ec62b2
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/offscreen/mirror.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/offscreen/phong.frag b/external/Vulkan/data/shaders/hlsl/offscreen/phong.frag
new file mode 100644
index 0000000000000000000000000000000000000000..62c69695428c5fb73b94b56dca643e2160678d0c
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/offscreen/phong.frag
@@ -0,0 +1,26 @@
+// Copyright 2020 Google LLC
+
+struct VSOutput
+{
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float3 EyePos : POSITION0;
+[[vk::location(3)]] float3 LightVec : TEXCOORD2;
+};
+
+float4 main(VSOutput input) : SV_TARGET
+{
+	float3 Eye = normalize(-input.EyePos);
+	float3 Reflected = normalize(reflect(-input.LightVec, input.Normal));
+
+	float4 IAmbient = float4(0.1, 0.1, 0.1, 1.0);
+	float4 IDiffuse = max(dot(input.Normal, input.LightVec), 0.0).xxxx;
+	float specular = 0.75;
+	float4 ISpecular = float4(0.0, 0.0, 0.0, 0.0);
+	if (dot(input.EyePos, input.Normal) < 0.0)
+	{
+		ISpecular = float4(0.5, 0.5, 0.5, 1.0) * pow(max(dot(Reflected, Eye), 0.0), 16.0) * specular;
+	}
+
+	return float4((IAmbient + IDiffuse) * float4(input.Color, 1.0) + ISpecular);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/offscreen/phong.frag.spv b/external/Vulkan/data/shaders/hlsl/offscreen/phong.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..6dbb99369d0cb29421597460c2ac215693a08226
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/offscreen/phong.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/offscreen/phong.vert b/external/Vulkan/data/shaders/hlsl/offscreen/phong.vert
new file mode 100644
index 0000000000000000000000000000000000000000..b097c55735f62721cdbd3d3ae5f69dc488a06019
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/offscreen/phong.vert
@@ -0,0 +1,43 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float4 Pos : POSITION0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float3 Normal : NORMAL0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 view;
+	float4x4 model;
+	float4 lightPos;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+	float ClipDistance : SV_ClipDistance0;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float3 EyePos : POSITION0;
+[[vk::location(3)]] float3 LightVec : TEXCOORD2;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Normal = input.Normal;
+	output.Color = input.Color;
+	output.Pos = mul(ubo.projection, mul(ubo.view, mul(ubo.model, input.Pos)));
+	output.EyePos = mul(ubo.view, mul(ubo.model, input.Pos)).xyz;
+	output.LightVec = normalize(ubo.lightPos.xyz - output.EyePos);
+
+	// Clip against reflection plane
+	float4 clipPlane = float4(0.0, -1.0, 0.0, 1.5);
+	output.ClipDistance = dot(input.Pos, clipPlane);
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/offscreen/phong.vert.spv b/external/Vulkan/data/shaders/hlsl/offscreen/phong.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..e814c80bdebb9de78712ffa2981f05aa3c083675
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/offscreen/phong.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/offscreen/quad.frag b/external/Vulkan/data/shaders/hlsl/offscreen/quad.frag
new file mode 100644
index 0000000000000000000000000000000000000000..997a9419d2ae1002497bb1c7f6de19336651be29
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/offscreen/quad.frag
@@ -0,0 +1,9 @@
+// Copyright 2020 Google LLC
+
+Texture2D textureColor : register(t1);
+SamplerState samplerColor : register(s1);
+
+float4 main([[vk::location(0)]] float2 inUV : TEXCOORD0) : SV_TARGET
+{
+  return textureColor.Sample(samplerColor, inUV);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/offscreen/quad.frag.spv b/external/Vulkan/data/shaders/hlsl/offscreen/quad.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..5a671efa5eba157bb3ed34066e4211b4a0ee9bc0
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/offscreen/quad.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/offscreen/quad.vert b/external/Vulkan/data/shaders/hlsl/offscreen/quad.vert
new file mode 100644
index 0000000000000000000000000000000000000000..b13c2bf2353ce1fc1837fef4db32336429c22412
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/offscreen/quad.vert
@@ -0,0 +1,15 @@
+// Copyright 2020 Google LLC
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float2 UV : TEXCOORD0;
+};
+
+VSOutput main(uint VertexIndex : SV_VertexID)
+{
+	VSOutput output = (VSOutput)0;
+	output.UV = float2((VertexIndex << 1) & 2, VertexIndex & 2);
+	output.Pos = float4(output.UV * 2.0f - 1.0f, 0.0f, 1.0f);
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/offscreen/quad.vert.spv b/external/Vulkan/data/shaders/hlsl/offscreen/quad.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..8754eaa14270ad09ba3893fff6aa51f5732ff9c9
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/offscreen/quad.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/oit/color.frag b/external/Vulkan/data/shaders/hlsl/oit/color.frag
new file mode 100644
index 0000000000000000000000000000000000000000..d50c11500a0f07a12a789a71ebcb0aaa9b25b1b3
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/oit/color.frag
@@ -0,0 +1,64 @@
+// Copyright 2020 Sascha Willems
+
+#define MAX_FRAGMENT_COUNT 128
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+};
+
+struct Node
+{
+    float4 color;
+    float depth;
+    uint next;
+};
+
+RWTexture2D<uint> headIndexImage : register(u0);
+
+struct Particle
+{
+	float2 pos;
+	float2 vel;
+	float4 gradientPos;
+};
+
+// Binding 0 : Position storage buffer
+RWStructuredBuffer<Node> nodes : register(u1);
+
+float4 main(VSOutput input) : SV_TARGET
+{
+    Node fragments[MAX_FRAGMENT_COUNT];
+    int count = 0;
+
+    uint nodeIdx = headIndexImage[uint2(input.Pos.xy)].r;
+
+    while (nodeIdx != 0xffffffff && count < MAX_FRAGMENT_COUNT)
+    {
+        fragments[count] = nodes[nodeIdx];
+        nodeIdx = fragments[count].next;
+        ++count;
+    }
+    
+    // Do the insertion sort
+    for (uint i = 1; i < count; ++i)
+    {
+        Node insert = fragments[i];
+        uint j = i;
+        while (j > 0 && insert.depth > fragments[j - 1].depth)
+        {
+            fragments[j] = fragments[j-1];
+            --j;
+        }
+        fragments[j] = insert;
+    }
+
+    // Do blending
+    float4 color = float4(0.025, 0.025, 0.025, 1.0f);
+    for (uint f = 0; f < count; ++f)
+    {
+        color = lerp(color, fragments[f].color, fragments[f].color.a);
+    }
+
+    return color;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/oit/color.frag.spv b/external/Vulkan/data/shaders/hlsl/oit/color.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..7e2e62e96e0732bfd2463d04e5fd70f5c8d0c893
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/oit/color.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/oit/color.vert b/external/Vulkan/data/shaders/hlsl/oit/color.vert
new file mode 100644
index 0000000000000000000000000000000000000000..609b360604fe083ba8c10accd00606a33135595a
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/oit/color.vert
@@ -0,0 +1,14 @@
+// Copyright 2020 Sascha Willems
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+};
+
+VSOutput main(uint VertexIndex : SV_VertexID)
+{
+	VSOutput output = (VSOutput)0;
+	float2 UV = float2((VertexIndex << 1) & 2, VertexIndex & 2);
+	output.Pos = float4(UV * 2.0f - 1.0f, 0.0f, 1.0f);
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/oit/color.vert.spv b/external/Vulkan/data/shaders/hlsl/oit/color.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..0315880916f2d6e093356afe527a5cb363b90c37
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/oit/color.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/oit/geometry.frag b/external/Vulkan/data/shaders/hlsl/oit/geometry.frag
new file mode 100644
index 0000000000000000000000000000000000000000..2a997a94c76e5b5a64c8177ba321c2eb0869b8a9
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/oit/geometry.frag
@@ -0,0 +1,52 @@
+// Copyright 2020 Sascha Willems
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+};
+
+struct Node
+{
+    float4 color;
+    float depth;
+    uint next;
+};
+
+struct GeometrySBO
+{
+    uint count;
+    uint maxNodeCount;
+};
+// Binding 0 : Position storage buffer
+RWStructuredBuffer<GeometrySBO> geometrySBO : register(u1);
+
+RWTexture2D<uint> headIndexImage : register(u2);
+
+RWStructuredBuffer<Node> nodes : register(u3);
+
+struct PushConsts {
+	float4x4 model;
+	float4 color;
+};
+[[vk::push_constant]] PushConsts pushConsts;
+
+[earlydepthstencil]
+void main(VSOutput input)
+{
+    // Increase the node count
+    uint nodeIdx;
+    InterlockedAdd(geometrySBO[0].count, 1, nodeIdx);
+
+    // Check LinkedListSBO is full
+    if (nodeIdx < geometrySBO[0].maxNodeCount)
+    {
+        // Exchange new head index and previous head index
+        uint prevHeadIdx;
+        InterlockedExchange(headIndexImage[uint2(input.Pos.xy)], nodeIdx, prevHeadIdx);
+
+        // Store node data
+        nodes[nodeIdx].color = pushConsts.color;
+        nodes[nodeIdx].depth = input.Pos.z;
+        nodes[nodeIdx].next = prevHeadIdx;
+    }
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/oit/geometry.frag.spv b/external/Vulkan/data/shaders/hlsl/oit/geometry.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..01fd554d80b3f81881c03d2980869acc253a115a
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/oit/geometry.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/oit/geometry.vert b/external/Vulkan/data/shaders/hlsl/oit/geometry.vert
new file mode 100644
index 0000000000000000000000000000000000000000..c1d114f3271ac6a2342541a1ec592bc83139d29f
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/oit/geometry.vert
@@ -0,0 +1,32 @@
+// Copyright 2020 Sascha Willems
+
+struct VSInput
+{
+[[vk::location(0)]] float4 Pos : POSITION0;
+};
+
+struct RenderPassUBO
+{
+    float4x4 projection;
+    float4x4 view;
+};
+
+cbuffer renderPassUBO : register(b0) { RenderPassUBO renderPassUBO; }
+
+struct PushConsts {
+	float4x4 model;
+	float4 color;
+};
+[[vk::push_constant]] PushConsts pushConsts;
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Pos = mul(renderPassUBO.projection, mul(renderPassUBO.view, mul(pushConsts.model, input.Pos)));
+    return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/oit/geometry.vert.spv b/external/Vulkan/data/shaders/hlsl/oit/geometry.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..d7b082611a78bc30541b6dc3832330a1cf1758af
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/oit/geometry.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/parallaxmapping/parallax.frag b/external/Vulkan/data/shaders/hlsl/parallaxmapping/parallax.frag
new file mode 100644
index 0000000000000000000000000000000000000000..9451471b4d095227850ea68c4ab12403be8ae367
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/parallaxmapping/parallax.frag
@@ -0,0 +1,110 @@
+// Copyright 2020 Google LLC
+
+Texture2D textureColorMap : register(t1);
+SamplerState samplerColorMap : register(s1);
+Texture2D textureNormalHeightMap : register(t2);
+SamplerState samplerNormalHeightMap : register(s2);
+
+struct UBO
+{
+	float heightScale;
+	float parallaxBias;
+	float numLayers;
+	int mappingMode;
+};
+
+cbuffer ubo : register(b3) { UBO ubo; }
+
+struct VSOutput
+{
+[[vk::location(0)]] float2 UV : TEXCOORD0;
+[[vk::location(1)]] float3 TangentLightPos : TEXCOORD1;
+[[vk::location(2)]] float3 TangentViewPos : TEXCOORD2;
+[[vk::location(3)]] float3 TangentFragPos : TEXCOORD3;
+};
+
+float2 parallaxMapping(float2 uv, float3 viewDir)
+{
+	float height = 1.0 - textureNormalHeightMap.SampleLevel(samplerNormalHeightMap, uv, 0.0).a;
+	float2 p = viewDir.xy * (height * (ubo.heightScale * 0.5) + ubo.parallaxBias) / viewDir.z;
+	return uv - p;
+}
+
+float2 steepParallaxMapping(float2 uv, float3 viewDir)
+{
+	float layerDepth = 1.0 / ubo.numLayers;
+	float currLayerDepth = 0.0;
+	float2 deltaUV = viewDir.xy * ubo.heightScale / (viewDir.z * ubo.numLayers);
+	float2 currUV = uv;
+	float height = 1.0 - textureNormalHeightMap.SampleLevel(samplerNormalHeightMap, currUV, 0.0).a;
+	for (int i = 0; i < ubo.numLayers; i++) {
+		currLayerDepth += layerDepth;
+		currUV -= deltaUV;
+		height = 1.0 - textureNormalHeightMap.SampleLevel(samplerNormalHeightMap, currUV, 0.0).a;
+		if (height < currLayerDepth) {
+			break;
+		}
+	}
+	return currUV;
+}
+
+float2 parallaxOcclusionMapping(float2 uv, float3 viewDir)
+{
+	float layerDepth = 1.0 / ubo.numLayers;
+	float currLayerDepth = 0.0;
+	float2 deltaUV = viewDir.xy * ubo.heightScale / (viewDir.z * ubo.numLayers);
+	float2 currUV = uv;
+	float height = 1.0 - textureNormalHeightMap.SampleLevel(samplerNormalHeightMap, currUV, 0.0).a;
+	for (int i = 0; i < ubo.numLayers; i++) {
+		currLayerDepth += layerDepth;
+		currUV -= deltaUV;
+		height = 1.0 - textureNormalHeightMap.SampleLevel(samplerNormalHeightMap, currUV, 0.0).a;
+		if (height < currLayerDepth) {
+			break;
+		}
+	}
+	float2 prevUV = currUV + deltaUV;
+	float nextDepth = height - currLayerDepth;
+	float prevDepth = 1.0 - textureNormalHeightMap.SampleLevel(samplerNormalHeightMap, prevUV, 0.0).a - currLayerDepth + layerDepth;
+	return lerp(currUV, prevUV, nextDepth / (nextDepth - prevDepth));
+}
+
+float4 main(VSOutput input) : SV_TARGET
+{
+	float3 V = normalize(input.TangentViewPos - input.TangentFragPos);
+	float2 uv = input.UV;
+
+	if (ubo.mappingMode == 0) {
+		// Color only
+		return textureColorMap.Sample(samplerColorMap, input.UV);
+	} else {
+		switch(ubo.mappingMode) {
+			case 2:
+				uv = parallaxMapping(input.UV, V);
+				break;
+			case 3:
+				uv = steepParallaxMapping(input.UV, V);
+				break;
+			case 4:
+				uv = parallaxOcclusionMapping(input.UV, V);
+				break;
+		}
+
+		// Discard fragments at texture border
+		if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) {
+			clip(-1);
+		}
+
+		float3 N = normalize(textureNormalHeightMap.SampleLevel(samplerNormalHeightMap, uv, 0.0).rgb * 2.0 - 1.0);
+		float3 L = normalize(input.TangentLightPos - input.TangentFragPos);
+		float3 R = reflect(-L, N);
+		float3 H = normalize(L + V);
+
+		float3 color = textureColorMap.Sample(samplerColorMap, uv).rgb;
+		float3 ambient = 0.2 * color;
+		float3 diffuse = max(dot(L, N), 0.0) * color;
+		float3 specular = float3(0.15, 0.15, 0.15) * pow(max(dot(N, H), 0.0), 32.0);
+
+		return float4(ambient + diffuse + specular, 1.0f);
+	}
+}
diff --git a/external/Vulkan/data/shaders/hlsl/parallaxmapping/parallax.frag.spv b/external/Vulkan/data/shaders/hlsl/parallaxmapping/parallax.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..cd363d376d1ea692eaf22d6c43f3969cf4ae8cb8
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/parallaxmapping/parallax.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/parallaxmapping/parallax.vert b/external/Vulkan/data/shaders/hlsl/parallaxmapping/parallax.vert
new file mode 100644
index 0000000000000000000000000000000000000000..3328508f25d7674ae1c172e0e61d6f75074a48d6
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/parallaxmapping/parallax.vert
@@ -0,0 +1,46 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+[[vk::location(2)]] float3 Normal : NORMAL0;
+[[vk::location(3)]] float4 Tangent : TEXCOORD1;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 view;
+	float4x4 model;
+	float4 lightPos;
+	float4 cameraPos;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float2 UV : TEXCOORD0;
+[[vk::location(1)]] float3 TangentLightPos : TEXCOORD1;
+[[vk::location(2)]] float3 TangentViewPos : TEXCOORD2;
+[[vk::location(3)]] float3 TangentFragPos : TEXCOORD3;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Pos = mul(ubo.projection, mul(ubo.view, mul(ubo.model, float4(input.Pos, 1.0f))));
+	output.UV = input.UV;
+
+	float3 N = normalize(input.Normal);
+	float3 T = normalize(input.Tangent.xyz);
+	float3 B = normalize(cross(N, T));
+	float3x3 TBN = float3x3(T, B, N);
+
+	output.TangentLightPos = mul(TBN, ubo.lightPos.xyz);
+	output.TangentViewPos  = mul(TBN, ubo.cameraPos.xyz);
+	output.TangentFragPos  = mul(TBN, mul(ubo.model, float4(input.Pos, 1.0)).xyz);
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/parallaxmapping/parallax.vert.spv b/external/Vulkan/data/shaders/hlsl/parallaxmapping/parallax.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..1ead6c4dd057080c0268098f145a47f40acf444a
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/parallaxmapping/parallax.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/particlefire/normalmap.frag b/external/Vulkan/data/shaders/hlsl/particlefire/normalmap.frag
new file mode 100644
index 0000000000000000000000000000000000000000..40ff7358d67bd6378dfbc711445cc8ad6f0d9f89
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/particlefire/normalmap.frag
@@ -0,0 +1,44 @@
+// Copyright 2020 Google LLC
+
+Texture2D textureColorMap : register(t1);
+SamplerState samplerColorMap : register(s1);
+Texture2D textureNormalHeightMap : register(t2);
+SamplerState samplerNormalHeightMap : register(s2);
+
+#define lightRadius 45.0
+
+struct VSOutput
+{
+[[vk::location(0)]] float2 UV : TEXCOORD0;
+[[vk::location(1)]] float3 LightVec : TEXCOORD2;
+[[vk::location(2)]] float3 LightVecB : TEXCOORD3;
+[[vk::location(3)]] float3 LightDir : TEXCOORD4;
+[[vk::location(4)]] float3 ViewVec : TEXCOORD1;
+};
+
+float4 main(VSOutput input) : SV_TARGET
+{
+	float3 specularColor = float3(0.85, 0.5, 0.0);
+
+	float invRadius = 1.0/lightRadius;
+	float ambient = 0.25;
+
+	float3 rgb, normal;
+
+	rgb = textureColorMap.Sample(samplerColorMap, input.UV).rgb;
+	normal = normalize((textureNormalHeightMap.Sample(samplerNormalHeightMap, input.UV).rgb - 0.5) * 2.0);
+
+	float distSqr = dot(input.LightVecB, input.LightVecB);
+	float3 lVec = input.LightVecB * rsqrt(distSqr);
+
+	float atten = max(clamp(1.0 - invRadius * sqrt(distSqr), 0.0, 1.0), ambient);
+	float diffuse = clamp(dot(lVec, normal), 0.0, 1.0);
+
+	float3 light = normalize(-input.LightVec);
+	float3 view = normalize(input.ViewVec);
+	float3 reflectDir = reflect(-light, normal);
+
+	float specular = pow(max(dot(view, reflectDir), 0.0), 4.0);
+
+	return float4((rgb * atten + (diffuse * rgb + 0.5 * specular * specularColor.rgb)) * atten, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/particlefire/normalmap.frag.spv b/external/Vulkan/data/shaders/hlsl/particlefire/normalmap.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..6b753f22459030a8d0a63f791c7d37df73ff00ea
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/particlefire/normalmap.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/particlefire/normalmap.vert b/external/Vulkan/data/shaders/hlsl/particlefire/normalmap.vert
new file mode 100644
index 0000000000000000000000000000000000000000..7c36f723b273a8613686de0cb53f20b73eac8673
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/particlefire/normalmap.vert
@@ -0,0 +1,61 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+[[vk::location(2)]] float3 Normal : NORMAL0;
+[[vk::location(3)]] float4 Tangent : TEXCOORD1;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+	float4x4 normal;
+	float4 lightPos;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float2 UV : TEXCOORD0;
+[[vk::location(1)]] float3 LightVec : TEXCOORD2;
+[[vk::location(2)]] float3 LightVecB : TEXCOORD3;
+[[vk::location(3)]] float3 LightDir : TEXCOORD4;
+[[vk::location(4)]] float3 ViewVec : TEXCOORD1;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	float3 vertexPosition = mul(ubo.model, float4(input.Pos, 1.0)).xyz;
+	output.LightDir = normalize(ubo.lightPos.xyz - vertexPosition);
+
+	float3 biTangent = cross(input.Normal, input.Tangent.xyz);
+
+	// Setup (t)angent-(b)inormal-(n)ormal matrix for converting
+	// object coordinates into tangent space
+	float3x3 tbnMatrix;
+	tbnMatrix[0] =  mul((float3x3)ubo.normal, input.Tangent);
+	tbnMatrix[1] =  mul((float3x3)ubo.normal, biTangent);
+	tbnMatrix[2] =  mul((float3x3)ubo.normal, input.Normal);
+
+	output.LightVec.xyz = mul(float3(ubo.lightPos.xyz - vertexPosition), tbnMatrix);
+
+	float3 lightDist = ubo.lightPos.xyz - input.Pos;
+	output.LightVecB.x = dot(input.Tangent, lightDist);
+	output.LightVecB.y = dot(biTangent, lightDist);
+	output.LightVecB.z = dot(input.Normal, lightDist);
+
+	output.ViewVec.x = dot(input.Tangent, input.Pos);
+	output.ViewVec.y = dot(biTangent, input.Pos);
+	output.ViewVec.z = dot(input.Normal, input.Pos);
+
+	output.UV = input.UV;
+
+	output.Pos = mul(ubo.projection, mul(ubo.model, float4(input.Pos, 1.0)));
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/particlefire/normalmap.vert.spv b/external/Vulkan/data/shaders/hlsl/particlefire/normalmap.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..f0acd276993bfd3d7dbc7502a0f756a2d6da6abb
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/particlefire/normalmap.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/particlefire/particle.frag b/external/Vulkan/data/shaders/hlsl/particlefire/particle.frag
new file mode 100644
index 0000000000000000000000000000000000000000..f2a7d375bec00dfc3f5cb37c631e059fb4541986
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/particlefire/particle.frag
@@ -0,0 +1,52 @@
+// Copyright 2020 Google LLC
+
+Texture2D textureSmoke : register(t1);
+SamplerState samplerSmoke : register(s1);
+Texture2D textureFire : register(t2);
+SamplerState samplerFire : register(s2);
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float4 Color : COLOR0;
+[[vk::location(1)]] float Alpha : TEXCOODR0;
+[[vk::location(2)]] int Type : TEXCOODR1;
+[[vk::location(3)]] float Rotation : TEXCOODR2;
+[[vk::location(4)]] float2 CenterPos : POSITION1;
+[[vk::location(5)]] float PointSize : TEXCOORD3;
+};
+
+float4 main (VSOutput input) : SV_TARGET
+{
+	float4 color;
+	float alpha = (input.Alpha <= 1.0) ? input.Alpha : 2.0 - input.Alpha;
+
+	// Rotate texture coordinates
+	// Rotate UV
+	float rotCenter = 0.5;
+	float rotCos = cos(input.Rotation);
+	float rotSin = sin(input.Rotation);
+
+	float2 PointCoord = (input.Pos.xy - input.CenterPos.xy) / input.PointSize + 0.5;
+
+	float2 rotUV = float2(
+		rotCos * (PointCoord.x - rotCenter) + rotSin * (PointCoord.y - rotCenter) + rotCenter,
+		rotCos * (PointCoord.y - rotCenter) - rotSin * (PointCoord.x - rotCenter) + rotCenter);
+
+	float4 outFragColor;
+	if (input.Type == 0)
+	{
+		// Flame
+		color = textureFire.Sample(samplerFire, rotUV);
+		outFragColor.a = 0.0;
+	}
+	else
+	{
+		// Smoke
+		color = textureSmoke.Sample(samplerSmoke, rotUV);
+		outFragColor.a = color.a * alpha;
+	}
+
+	outFragColor.rgb = color.rgb * input.Color.rgb * alpha;
+	return outFragColor;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/particlefire/particle.frag.spv b/external/Vulkan/data/shaders/hlsl/particlefire/particle.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..294b2310bcf00d2c1b34e7fbc473777fe7b61ae8
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/particlefire/particle.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/particlefire/particle.vert b/external/Vulkan/data/shaders/hlsl/particlefire/particle.vert
new file mode 100644
index 0000000000000000000000000000000000000000..30794139dd368e7f1dfdda6cdba3d8fd62f6540c
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/particlefire/particle.vert
@@ -0,0 +1,54 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float4 Pos : POSITION0;
+[[vk::location(1)]] float4 Color : COLOR0;
+[[vk::location(2)]] float Alpha : TEXCOORD0;
+[[vk::location(3)]] float Size : TEXCOORD1;
+[[vk::location(4)]] float Rotation : TEXCOORD2;
+[[vk::location(5)]] int Type : TEXCOORD3;
+};
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::builtin("PointSize")]] float PSize : PSIZE;
+[[vk::location(0)]] float4 Color : COLOR0;
+[[vk::location(1)]] float Alpha : TEXCOORD0;
+[[vk::location(2)]] int Type : TEXCOORD1;
+[[vk::location(3)]] float Rotation : TEXCOORD2;
+[[vk::location(4)]] float2 CenterPos : POSITION1;
+[[vk::location(5)]] float PointSize : TEXCOORD3;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 modelview;
+	float2 viewportDim;
+	float pointSize;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+VSOutput main (VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Color = input.Color;
+	output.Alpha = input.Alpha;
+	output.Type = input.Type;
+	output.Rotation = input.Rotation;
+
+	output.Pos = mul(ubo.projection, mul(ubo.modelview, float4(input.Pos.xyz, 1.0)));
+
+	// Base size of the point sprites
+	float spriteSize = 8.0 * input.Size;
+
+	// Scale particle size depending on camera projection
+	float4 eyePos = mul(ubo.modelview, float4(input.Pos.xyz, 1.0));
+	float4 projectedCorner = mul(ubo.projection, float4(0.5 * spriteSize, 0.5 * spriteSize, eyePos.z, eyePos.w));
+	output.PointSize = output.PSize = ubo.viewportDim.x * projectedCorner.x / projectedCorner.w;
+	output.CenterPos = ((output.Pos.xy / output.Pos.w) + 1.0) * 0.5 * ubo.viewportDim;
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/particlefire/particle.vert.spv b/external/Vulkan/data/shaders/hlsl/particlefire/particle.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..ad0b55edf0af486ac9af1e2dc4c8941401c438f4
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/particlefire/particle.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/pbrbasic/pbr.frag b/external/Vulkan/data/shaders/hlsl/pbrbasic/pbr.frag
new file mode 100644
index 0000000000000000000000000000000000000000..55e892403c697817a4eb2de1e1c937ed7d835362
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/pbrbasic/pbr.frag
@@ -0,0 +1,133 @@
+// Copyright 2020 Google LLC
+
+struct VSOutput
+{
+[[vk::location(0)]] float3 WorldPos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+	float4x4 view;
+	float3 camPos;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct UBOShared {
+	float4 lights[4];
+};
+
+cbuffer uboParams : register(b1) { UBOShared uboParams; };
+
+struct PushConsts {
+[[vk::offset(12)]] float roughness;
+[[vk::offset(16)]] float metallic;
+[[vk::offset(20)]] float r;
+[[vk::offset(24)]] float g;
+[[vk::offset(28)]] float b;
+};
+
+[[vk::push_constant]] PushConsts material;
+
+static const float PI = 3.14159265359;
+
+//#define ROUGHNESS_PATTERN 1
+
+float3 materialcolor()
+{
+	return float3(material.r, material.g, material.b);
+}
+
+// Normal Distribution function --------------------------------------
+float D_GGX(float dotNH, float roughness)
+{
+	float alpha = roughness * roughness;
+	float alpha2 = alpha * alpha;
+	float denom = dotNH * dotNH * (alpha2 - 1.0) + 1.0;
+	return (alpha2)/(PI * denom*denom);
+}
+
+// Geometric Shadowing function --------------------------------------
+float G_SchlicksmithGGX(float dotNL, float dotNV, float roughness)
+{
+	float r = (roughness + 1.0);
+	float k = (r*r) / 8.0;
+	float GL = dotNL / (dotNL * (1.0 - k) + k);
+	float GV = dotNV / (dotNV * (1.0 - k) + k);
+	return GL * GV;
+}
+
+// Fresnel function ----------------------------------------------------
+float3 F_Schlick(float cosTheta, float metallic)
+{
+	float3 F0 = lerp(float3(0.04, 0.04, 0.04), materialcolor(), metallic); // * material.specular
+	float3 F = F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
+	return F;
+}
+
+// Specular BRDF composition --------------------------------------------
+
+float3 BRDF(float3 L, float3 V, float3 N, float metallic, float roughness)
+{
+	// Precalculate vectors and dot products
+	float3 H = normalize (V + L);
+	float dotNV = clamp(dot(N, V), 0.0, 1.0);
+	float dotNL = clamp(dot(N, L), 0.0, 1.0);
+	float dotLH = clamp(dot(L, H), 0.0, 1.0);
+	float dotNH = clamp(dot(N, H), 0.0, 1.0);
+
+	// Light color fixed
+	float3 lightColor = float3(1.0, 1.0, 1.0);
+
+	float3 color = float3(0.0, 0.0, 0.0);
+
+	if (dotNL > 0.0)
+	{
+		float rroughness = max(0.05, roughness);
+		// D = Normal distribution (Distribution of the microfacets)
+		float D = D_GGX(dotNH, roughness);
+		// G = Geometric shadowing term (Microfacets shadowing)
+		float G = G_SchlicksmithGGX(dotNL, dotNV, rroughness);
+		// F = Fresnel factor (Reflectance depending on angle of incidence)
+		float3 F = F_Schlick(dotNV, metallic);
+
+		float3 spec = D * F * G / (4.0 * dotNL * dotNV);
+
+		color += spec * dotNL * lightColor;
+	}
+
+	return color;
+}
+
+// ----------------------------------------------------------------------------
+float4 main(VSOutput input) : SV_TARGET
+{
+	float3 N = normalize(input.Normal);
+	float3 V = normalize(ubo.camPos - input.WorldPos);
+
+	float roughness = material.roughness;
+
+	// Add striped pattern to roughness based on vertex position
+#ifdef ROUGHNESS_PATTERN
+	roughness = max(roughness, step(frac(input.WorldPos.y * 2.02), 0.5));
+#endif
+
+	// Specular contribution
+	float3 Lo = float3(0.0, 0.0, 0.0);
+	for (int i = 0; i < 4; i++) {
+		float3 L = normalize(uboParams.lights[i].xyz - input.WorldPos);
+		Lo += BRDF(L, V, N, material.metallic, roughness);
+	};
+
+	// Combine with ambient
+	float3 color = materialcolor() * 0.02;
+	color += Lo;
+
+	// Gamma correct
+	color = pow(color, float3(0.4545, 0.4545, 0.4545));
+
+	return float4(color, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/pbrbasic/pbr.frag.spv b/external/Vulkan/data/shaders/hlsl/pbrbasic/pbr.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..1d4f7125ac589daa7b47a9a511f6ac6d300443ea
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/pbrbasic/pbr.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/pbrbasic/pbr.vert b/external/Vulkan/data/shaders/hlsl/pbrbasic/pbr.vert
new file mode 100644
index 0000000000000000000000000000000000000000..c53e7dbb8fc44328bc4f39c268393e77b537e659
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/pbrbasic/pbr.vert
@@ -0,0 +1,39 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+	float4x4 view;
+	float3 camPos;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 WorldPos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+};
+
+struct PushConsts {
+	float3 objPos;
+};
+[[vk::push_constant]] PushConsts pushConsts;
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	float3 locPos = mul(ubo.model, float4(input.Pos, 1.0)).xyz;
+	output.WorldPos = locPos + pushConsts.objPos;
+	output.Normal = mul((float3x3)ubo.model, input.Normal);
+	output.Pos = mul(ubo.projection, mul(ubo.view, float4(output.WorldPos, 1.0)));
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/pbrbasic/pbr.vert.spv b/external/Vulkan/data/shaders/hlsl/pbrbasic/pbr.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..06802f512cced699fd7f6c8cbb1e22d328e28ac4
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/pbrbasic/pbr.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/pbribl/filtercube.vert b/external/Vulkan/data/shaders/hlsl/pbribl/filtercube.vert
new file mode 100644
index 0000000000000000000000000000000000000000..514b4dd2d2e02fb00c8613177b8fe71dfa39f58e
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/pbribl/filtercube.vert
@@ -0,0 +1,25 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+};
+
+struct PushConsts {
+[[vk::offset(0)]] float4x4 mvp;
+};
+[[vk::push_constant]] PushConsts pushConsts;
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 UVW : TEXCOORD0;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.UVW = input.Pos;
+	output.Pos = mul(pushConsts.mvp, float4(input.Pos.xyz, 1.0));
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/pbribl/filtercube.vert.spv b/external/Vulkan/data/shaders/hlsl/pbribl/filtercube.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..9b32c2ddea8e5e10473192e2816938ebffb71795
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/pbribl/filtercube.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/pbribl/genbrdflut.frag b/external/Vulkan/data/shaders/hlsl/pbribl/genbrdflut.frag
new file mode 100644
index 0000000000000000000000000000000000000000..ca18d842c30504b29a80ba8bca77ef9c3004c5ad
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/pbribl/genbrdflut.frag
@@ -0,0 +1,88 @@
+// Copyright 2020 Google LLC
+
+[[vk::constant_id(0)]] const uint NUM_SAMPLES = 1024u;
+
+#define PI 3.1415926536
+
+// Based omn http://byteblacksmith.com/improvements-to-the-canonical-one-liner-glsl-rand-for-opengl-es-2-0/
+float random(float2 co)
+{
+	float a = 12.9898;
+	float b = 78.233;
+	float c = 43758.5453;
+	float dt= dot(co.xy ,float2(a,b));
+	float sn= fmod(dt,3.14);
+	return frac(sin(sn) * c);
+}
+
+float2 hammersley2d(uint i, uint N)
+{
+	// Radical inverse based on http://holger.dammertz.org/stuff/notes_HammersleyOnHemisphere.html
+	uint bits = (i << 16u) | (i >> 16u);
+	bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
+	bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
+	bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
+	bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
+	float rdi = float(bits) * 2.3283064365386963e-10;
+	return float2(float(i) /float(N), rdi);
+}
+
+// Based on http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_slides.pdf
+float3 importanceSample_GGX(float2 Xi, float roughness, float3 normal)
+{
+	// Maps a 2D point to a hemisphere with spread based on roughness
+	float alpha = roughness * roughness;
+	float phi = 2.0 * PI * Xi.x + random(normal.xz) * 0.1;
+	float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (alpha*alpha - 1.0) * Xi.y));
+	float sinTheta = sqrt(1.0 - cosTheta * cosTheta);
+	float3 H = float3(sinTheta * cos(phi), sinTheta * sin(phi), cosTheta);
+
+	// Tangent space
+	float3 up = abs(normal.z) < 0.999 ? float3(0.0, 0.0, 1.0) : float3(1.0, 0.0, 0.0);
+	float3 tangentX = normalize(cross(up, normal));
+	float3 tangentY = normalize(cross(normal, tangentX));
+
+	// Convert to world Space
+	return normalize(tangentX * H.x + tangentY * H.y + normal * H.z);
+}
+
+// Geometric Shadowing function
+float G_SchlicksmithGGX(float dotNL, float dotNV, float roughness)
+{
+	float k = (roughness * roughness) / 2.0;
+	float GL = dotNL / (dotNL * (1.0 - k) + k);
+	float GV = dotNV / (dotNV * (1.0 - k) + k);
+	return GL * GV;
+}
+
+float2 BRDF(float NoV, float roughness)
+{
+	// Normal always points along z-axis for the 2D lookup
+	const float3 N = float3(0.0, 0.0, 1.0);
+	float3 V = float3(sqrt(1.0 - NoV*NoV), 0.0, NoV);
+
+	float2 LUT = float2(0.0, 0.0);
+	for(uint i = 0u; i < NUM_SAMPLES; i++) {
+		float2 Xi = hammersley2d(i, NUM_SAMPLES);
+		float3 H = importanceSample_GGX(Xi, roughness, N);
+		float3 L = 2.0 * dot(V, H) * H - V;
+
+		float dotNL = max(dot(N, L), 0.0);
+		float dotNV = max(dot(N, V), 0.0);
+		float dotVH = max(dot(V, H), 0.0);
+		float dotNH = max(dot(H, N), 0.0);
+
+		if (dotNL > 0.0) {
+			float G = G_SchlicksmithGGX(dotNL, dotNV, roughness);
+			float G_Vis = (G * dotVH) / (dotNH * dotNV);
+			float Fc = pow(1.0 - dotVH, 5.0);
+			LUT += float2((1.0 - Fc) * G_Vis, Fc * G_Vis);
+		}
+	}
+	return LUT / float(NUM_SAMPLES);
+}
+
+float4 main([[vk::location(0)]] float2 inUV : TEXCOORD0) : SV_TARGET
+{
+	return float4(BRDF(inUV.x, 1.0-inUV.y), 0.0, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/pbribl/genbrdflut.frag.spv b/external/Vulkan/data/shaders/hlsl/pbribl/genbrdflut.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..ecd11746007e34b9ad7466a32fbb05b20ecb3afa
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/pbribl/genbrdflut.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/pbribl/genbrdflut.vert b/external/Vulkan/data/shaders/hlsl/pbribl/genbrdflut.vert
new file mode 100644
index 0000000000000000000000000000000000000000..188b72989e70e69a38a4fa88d015f67c7709cbdc
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/pbribl/genbrdflut.vert
@@ -0,0 +1,15 @@
+// Copyright 2020 Google LLC
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float2 UV : TEXCOORD0;
+};
+
+VSOutput main(uint VertexIndex : SV_VertexID)
+{
+	VSOutput output = (VSOutput)0;
+	output.UV = float2((VertexIndex << 1) & 2, VertexIndex & 2);
+	output.Pos = float4(output.UV * 2.0f - 1.0f, 0.0f, 1.0f);
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/pbribl/genbrdflut.vert.spv b/external/Vulkan/data/shaders/hlsl/pbribl/genbrdflut.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..46c4b31fdc1568cc56662cc746d3a2d10f9d0983
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/pbribl/genbrdflut.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/pbribl/irradiancecube.frag b/external/Vulkan/data/shaders/hlsl/pbribl/irradiancecube.frag
new file mode 100644
index 0000000000000000000000000000000000000000..66d63bf6ea0210161f0ebac65e76d33aac74b324
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/pbribl/irradiancecube.frag
@@ -0,0 +1,35 @@
+// Copyright 2020 Google LLC
+
+TextureCube textureEnv : register(t0);
+SamplerState samplerEnv : register(s0);
+
+struct PushConsts {
+[[vk::offset(64)]] float deltaPhi;
+[[vk::offset(68)]] float deltaTheta;
+};
+[[vk::push_constant]]PushConsts consts;
+
+#define PI 3.1415926535897932384626433832795
+
+float4 main([[vk::location(0)]] float3 inPos : TEXCOORD0) : SV_TARGET
+{
+	float3 N = normalize(inPos);
+	float3 up = float3(0.0, 1.0, 0.0);
+	float3 right = normalize(cross(up, N));
+	up = cross(N, right);
+
+	const float TWO_PI = PI * 2.0;
+	const float HALF_PI = PI * 0.5;
+
+	float3 color = float3(0.0, 0.0, 0.0);
+	uint sampleCount = 0u;
+	for (float phi = 0.0; phi < TWO_PI; phi += consts.deltaPhi) {
+		for (float theta = 0.0; theta < HALF_PI; theta += consts.deltaTheta) {
+			float3 tempVec = cos(phi) * right + sin(phi) * up;
+			float3 sampleVector = cos(theta) * N + sin(theta) * tempVec;
+			color += textureEnv.Sample(samplerEnv, sampleVector).rgb * cos(theta) * sin(theta);
+			sampleCount++;
+		}
+	}
+	return float4(PI * color / float(sampleCount), 1.0);
+}
diff --git a/external/Vulkan/data/shaders/hlsl/pbribl/irradiancecube.frag.spv b/external/Vulkan/data/shaders/hlsl/pbribl/irradiancecube.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..b3def200eefad232741ca0f54047f4f49101e263
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/pbribl/irradiancecube.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/pbribl/pbribl.frag b/external/Vulkan/data/shaders/hlsl/pbribl/pbribl.frag
new file mode 100644
index 0000000000000000000000000000000000000000..4f135c4d16cd75ab44b14524ac9f8a7f138b5359
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/pbribl/pbribl.frag
@@ -0,0 +1,170 @@
+// Copyright 2020 Google LLC
+
+struct VSOutput
+{
+[[vk::location(0)]] float3 WorldPos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float2 UV : TEXCOORD0;
+};
+
+struct UBO  {
+	float4x4 projection;
+	float4x4 model;
+	float4x4 view;
+	float3 camPos;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct UBOParams {
+	float4 lights[4];
+	float exposure;
+	float gamma;
+};
+cbuffer uboParams : register(b1) { UBOParams uboParams; };
+
+struct PushConsts {
+[[vk::offset(12)]] float roughness;
+[[vk::offset(16)]] float metallic;
+[[vk::offset(20)]] float specular;
+[[vk::offset(24)]] float r;
+[[vk::offset(28)]] float g;
+[[vk::offset(32)]] float b;
+};
+[[vk::push_constant]] PushConsts material;
+
+TextureCube textureIrradiance : register(t2);
+SamplerState samplerIrradiance : register(s2);
+Texture2D textureBRDFLUT : register(t3);
+SamplerState samplerBRDFLUT : register(s3);
+TextureCube prefilteredMapTexture : register(t4);
+SamplerState prefilteredMapSampler : register(s4);
+
+#define PI 3.1415926535897932384626433832795
+#define ALBEDO float3(material.r, material.g, material.b)
+
+// From http://filmicgames.com/archives/75
+float3 Uncharted2Tonemap(float3 x)
+{
+	float A = 0.15;
+	float B = 0.50;
+	float C = 0.10;
+	float D = 0.20;
+	float E = 0.02;
+	float F = 0.30;
+	return ((x*(A*x+C*B)+D*E)/(x*(A*x+B)+D*F))-E/F;
+}
+
+// Normal Distribution function --------------------------------------
+float D_GGX(float dotNH, float roughness)
+{
+	float alpha = roughness * roughness;
+	float alpha2 = alpha * alpha;
+	float denom = dotNH * dotNH * (alpha2 - 1.0) + 1.0;
+	return (alpha2)/(PI * denom*denom);
+}
+
+// Geometric Shadowing function --------------------------------------
+float G_SchlicksmithGGX(float dotNL, float dotNV, float roughness)
+{
+	float r = (roughness + 1.0);
+	float k = (r*r) / 8.0;
+	float GL = dotNL / (dotNL * (1.0 - k) + k);
+	float GV = dotNV / (dotNV * (1.0 - k) + k);
+	return GL * GV;
+}
+
+// Fresnel function ----------------------------------------------------
+float3 F_Schlick(float cosTheta, float3 F0)
+{
+	return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
+}
+float3 F_SchlickR(float cosTheta, float3 F0, float roughness)
+{
+	return F0 + (max((1.0 - roughness).xxx, F0) - F0) * pow(1.0 - cosTheta, 5.0);
+}
+
+float3 prefilteredReflection(float3 R, float roughness)
+{
+	const float MAX_REFLECTION_LOD = 9.0; // todo: param/const
+	float lod = roughness * MAX_REFLECTION_LOD;
+	float lodf = floor(lod);
+	float lodc = ceil(lod);
+	float3 a = prefilteredMapTexture.SampleLevel(prefilteredMapSampler, R, lodf).rgb;
+	float3 b = prefilteredMapTexture.SampleLevel(prefilteredMapSampler, R, lodc).rgb;
+	return lerp(a, b, lod - lodf);
+}
+
+float3 specularContribution(float3 L, float3 V, float3 N, float3 F0, float metallic, float roughness)
+{
+	// Precalculate vectors and dot products
+	float3 H = normalize (V + L);
+	float dotNH = clamp(dot(N, H), 0.0, 1.0);
+	float dotNV = clamp(dot(N, V), 0.0, 1.0);
+	float dotNL = clamp(dot(N, L), 0.0, 1.0);
+
+	// Light color fixed
+	float3 lightColor = float3(1.0, 1.0, 1.0);
+
+	float3 color = float3(0.0, 0.0, 0.0);
+
+	if (dotNL > 0.0) {
+		// D = Normal distribution (Distribution of the microfacets)
+		float D = D_GGX(dotNH, roughness);
+		// G = Geometric shadowing term (Microfacets shadowing)
+		float G = G_SchlicksmithGGX(dotNL, dotNV, roughness);
+		// F = Fresnel factor (Reflectance depending on angle of incidence)
+		float3 F = F_Schlick(dotNV, F0);
+		float3 spec = D * F * G / (4.0 * dotNL * dotNV + 0.001);
+		float3 kD = (float3(1.0, 1.0, 1.0) - F) * (1.0 - metallic);
+		color += (kD * ALBEDO / PI + spec) * dotNL;
+	}
+
+	return color;
+}
+
+float4 main(VSOutput input) : SV_TARGET
+{
+	float3 N = normalize(input.Normal);
+	float3 V = normalize(ubo.camPos - input.WorldPos);
+	float3 R = reflect(-V, N);
+
+	float metallic = material.metallic;
+	float roughness = material.roughness;
+
+	float3 F0 = float3(0.04, 0.04, 0.04);
+	F0 = lerp(F0, ALBEDO, metallic);
+
+	float3 Lo = float3(0.0, 0.0, 0.0);
+	for(int i = 0; i < 4; i++) {
+		float3 L = normalize(uboParams.lights[i].xyz - input.WorldPos);
+		Lo += specularContribution(L, V, N, F0, metallic, roughness);
+	}
+
+	float2 brdf = textureBRDFLUT.Sample(samplerBRDFLUT, float2(max(dot(N, V), 0.0), roughness)).rg;
+	float3 reflection = prefilteredReflection(R, roughness).rgb;
+	float3 irradiance = textureIrradiance.Sample(samplerIrradiance, N).rgb;
+
+	// Diffuse based on irradiance
+	float3 diffuse = irradiance * ALBEDO;
+
+	float3 F = F_SchlickR(max(dot(N, V), 0.0), F0, roughness);
+
+	// Specular reflectance
+	float3 specular = reflection * (F * brdf.x + brdf.y);
+
+	// Ambient part
+	float3 kD = 1.0 - F;
+	kD *= 1.0 - metallic;
+	float3 ambient = (kD * diffuse + specular);
+
+	float3 color = ambient + Lo;
+
+	// Tone mapping
+	color = Uncharted2Tonemap(color * uboParams.exposure);
+	color = color * (1.0f / Uncharted2Tonemap((11.2f).xxx));
+	// Gamma correction
+	color = pow(color, (1.0f / uboParams.gamma).xxx);
+
+	return float4(color, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/pbribl/pbribl.frag.spv b/external/Vulkan/data/shaders/hlsl/pbribl/pbribl.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..2cca9866a36de6a6f46015e446df62e71ffca9af
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/pbribl/pbribl.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/pbribl/pbribl.vert b/external/Vulkan/data/shaders/hlsl/pbribl/pbribl.vert
new file mode 100644
index 0000000000000000000000000000000000000000..12d2e6e7cefa5e4aa94275ee167bfcd600383370
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/pbribl/pbribl.vert
@@ -0,0 +1,43 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float2 UV : TEXCOORD0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+	float4x4 view;
+	float3 camPos;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 WorldPos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float2 UV : TEXCOORD0;
+};
+
+struct PushConsts {
+	float3 objPos;
+};
+[[vk::push_constant]] PushConsts pushConsts;
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	float3 locPos = mul(ubo.model, float4(input.Pos, 1.0)).xyz;
+	output.WorldPos = locPos + pushConsts.objPos;
+	output.Normal = mul((float3x3)ubo.model, input.Normal);
+	output.UV = input.UV;
+	output.UV.y = 1.0 - input.UV.y;
+	output.Pos = mul(ubo.projection, mul(ubo.view, float4(output.WorldPos, 1.0)));
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/pbribl/pbribl.vert.spv b/external/Vulkan/data/shaders/hlsl/pbribl/pbribl.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..67a8450e8242123ec87dce0cefddca2f388af50a
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/pbribl/pbribl.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/pbribl/prefilterenvmap.frag b/external/Vulkan/data/shaders/hlsl/pbribl/prefilterenvmap.frag
new file mode 100644
index 0000000000000000000000000000000000000000..1f605d04a9f88afbf6d7d68718c44853dc94146a
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/pbribl/prefilterenvmap.frag
@@ -0,0 +1,106 @@
+// Copyright 2020 Google LLC
+
+TextureCube textureEnv : register(t0);
+SamplerState samplerEnv : register(s0);
+
+struct PushConsts {
+[[vk::offset(64)]] float roughness;
+[[vk::offset(68)]] uint numSamples;
+};
+[[vk::push_constant]] PushConsts consts;
+
+#define PI 3.1415926536
+
+// Based omn http://byteblacksmith.com/improvements-to-the-canonical-one-liner-glsl-rand-for-opengl-es-2-0/
+float random(float2 co)
+{
+	float a = 12.9898;
+	float b = 78.233;
+	float c = 43758.5453;
+	float dt= dot(co.xy ,float2(a,b));
+	float sn= fmod(dt,3.14);
+	return frac(sin(sn) * c);
+}
+
+float2 hammersley2d(uint i, uint N)
+{
+	// Radical inverse based on http://holger.dammertz.org/stuff/notes_HammersleyOnHemisphere.html
+	uint bits = (i << 16u) | (i >> 16u);
+	bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
+	bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
+	bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
+	bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
+	float rdi = float(bits) * 2.3283064365386963e-10;
+	return float2(float(i) /float(N), rdi);
+}
+
+// Based on http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_slides.pdf
+float3 importanceSample_GGX(float2 Xi, float roughness, float3 normal)
+{
+	// Maps a 2D point to a hemisphere with spread based on roughness
+	float alpha = roughness * roughness;
+	float phi = 2.0 * PI * Xi.x + random(normal.xz) * 0.1;
+	float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (alpha*alpha - 1.0) * Xi.y));
+	float sinTheta = sqrt(1.0 - cosTheta * cosTheta);
+	float3 H = float3(sinTheta * cos(phi), sinTheta * sin(phi), cosTheta);
+
+	// Tangent space
+	float3 up = abs(normal.z) < 0.999 ? float3(0.0, 0.0, 1.0) : float3(1.0, 0.0, 0.0);
+	float3 tangentX = normalize(cross(up, normal));
+	float3 tangentY = normalize(cross(normal, tangentX));
+
+	// Convert to world Space
+	return normalize(tangentX * H.x + tangentY * H.y + normal * H.z);
+}
+
+// Normal Distribution function
+float D_GGX(float dotNH, float roughness)
+{
+	float alpha = roughness * roughness;
+	float alpha2 = alpha * alpha;
+	float denom = dotNH * dotNH * (alpha2 - 1.0) + 1.0;
+	return (alpha2)/(PI * denom*denom);
+}
+
+float3 prefilterEnvMap(float3 R, float roughness)
+{
+	float3 N = R;
+	float3 V = R;
+	float3 color = float3(0.0, 0.0, 0.0);
+	float totalWeight = 0.0;
+	int2 envMapDims;
+	textureEnv.GetDimensions(envMapDims.x, envMapDims.y);
+	float envMapDim = float(envMapDims.x);
+	for(uint i = 0u; i < consts.numSamples; i++) {
+		float2 Xi = hammersley2d(i, consts.numSamples);
+		float3 H = importanceSample_GGX(Xi, roughness, N);
+		float3 L = 2.0 * dot(V, H) * H - V;
+		float dotNL = clamp(dot(N, L), 0.0, 1.0);
+		if(dotNL > 0.0) {
+			// Filtering based on https://placeholderart.wordpress.com/2015/07/28/implementation-notes-runtime-environment-map-filtering-for-image-based-lighting/
+
+			float dotNH = clamp(dot(N, H), 0.0, 1.0);
+			float dotVH = clamp(dot(V, H), 0.0, 1.0);
+
+			// Probability Distribution Function
+			float pdf = D_GGX(dotNH, roughness) * dotNH / (4.0 * dotVH) + 0.0001;
+			// Slid angle of current smple
+			float omegaS = 1.0 / (float(consts.numSamples) * pdf);
+			// Solid angle of 1 pixel across all cube faces
+			float omegaP = 4.0 * PI / (6.0 * envMapDim * envMapDim);
+			// Biased (+1.0) mip level for better result
+			float mipLevel = roughness == 0.0 ? 0.0 : max(0.5 * log2(omegaS / omegaP) + 1.0, 0.0f);
+			color += textureEnv.SampleLevel(samplerEnv, L, mipLevel).rgb * dotNL;
+			totalWeight += dotNL;
+
+		}
+	}
+	return (color / totalWeight);
+}
+
+
+float4 main([[vk::location(0)]] float3 inPos : POSITION0) : SV_TARGET
+{
+	float3 N = normalize(inPos);
+	return float4(prefilterEnvMap(N, consts.roughness), 1.0);
+}
diff --git a/external/Vulkan/data/shaders/hlsl/pbribl/prefilterenvmap.frag.spv b/external/Vulkan/data/shaders/hlsl/pbribl/prefilterenvmap.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..027bdf84ec64f28dca5aeb70d26db53999cddd92
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/pbribl/prefilterenvmap.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/pbribl/skybox.frag b/external/Vulkan/data/shaders/hlsl/pbribl/skybox.frag
new file mode 100644
index 0000000000000000000000000000000000000000..439c6287386c010f9fe7d3a91cb1d40ae96c7ca8
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/pbribl/skybox.frag
@@ -0,0 +1,37 @@
+// Copyright 2020 Google LLC
+
+TextureCube textureEnv : register(t2);
+SamplerState samplerEnv : register(s2);
+
+struct UBOParams {
+	float4 lights[4];
+	float exposure;
+	float gamma;
+};
+cbuffer uboParams : register(b1) { UBOParams uboParams; };
+
+// From http://filmicworlds.com/blog/filmic-tonemapping-operators/
+float3 Uncharted2Tonemap(float3 color)
+{
+	float A = 0.15;
+	float B = 0.50;
+	float C = 0.10;
+	float D = 0.20;
+	float E = 0.02;
+	float F = 0.30;
+	float W = 11.2;
+	return ((color*(A*color+C*B)+D*E)/(color*(A*color+B)+D*F))-E/F;
+}
+
+float4 main([[vk::location(0)]] float3 inUVW : POSITION0) : SV_TARGET
+{
+	float3 color = textureEnv.Sample(samplerEnv, inUVW).rgb;
+
+	// Tone mapping
+	color = Uncharted2Tonemap(color * uboParams.exposure);
+	color = color * (1.0f / Uncharted2Tonemap((11.2f).xxx));
+	// Gamma correction
+	color = pow(color, (1.0f / uboParams.gamma).xxx);
+
+	return float4(color, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/pbribl/skybox.frag.spv b/external/Vulkan/data/shaders/hlsl/pbribl/skybox.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..9731fbbce3dcc78768196ff5ce1ecdc08a1871af
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/pbribl/skybox.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/pbribl/skybox.vert b/external/Vulkan/data/shaders/hlsl/pbribl/skybox.vert
new file mode 100644
index 0000000000000000000000000000000000000000..26e3fb6dd18928150ae1f8530fc169ca6d748f2c
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/pbribl/skybox.vert
@@ -0,0 +1,30 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float2 UV : TEXCOORD0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 UVW : TEXCOORD0;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.UVW = input.Pos;
+	output.Pos = mul(ubo.projection, mul(ubo.model, float4(input.Pos.xyz, 1.0)));
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/pbribl/skybox.vert.spv b/external/Vulkan/data/shaders/hlsl/pbribl/skybox.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..698235d99a0273c252543604def972cd8e6f0b76
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/pbribl/skybox.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/pbrtexture/filtercube.vert b/external/Vulkan/data/shaders/hlsl/pbrtexture/filtercube.vert
new file mode 100644
index 0000000000000000000000000000000000000000..514b4dd2d2e02fb00c8613177b8fe71dfa39f58e
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/pbrtexture/filtercube.vert
@@ -0,0 +1,25 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+};
+
+struct PushConsts {
+[[vk::offset(0)]] float4x4 mvp;
+};
+[[vk::push_constant]] PushConsts pushConsts;
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 UVW : TEXCOORD0;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.UVW = input.Pos;
+	output.Pos = mul(pushConsts.mvp, float4(input.Pos.xyz, 1.0));
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/pbrtexture/filtercube.vert.spv b/external/Vulkan/data/shaders/hlsl/pbrtexture/filtercube.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..9b32c2ddea8e5e10473192e2816938ebffb71795
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/pbrtexture/filtercube.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/pbrtexture/genbrdflut.frag b/external/Vulkan/data/shaders/hlsl/pbrtexture/genbrdflut.frag
new file mode 100644
index 0000000000000000000000000000000000000000..ca18d842c30504b29a80ba8bca77ef9c3004c5ad
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/pbrtexture/genbrdflut.frag
@@ -0,0 +1,88 @@
+// Copyright 2020 Google LLC
+
+[[vk::constant_id(0)]] const uint NUM_SAMPLES = 1024u;
+
+#define PI 3.1415926536
+
+// Based omn http://byteblacksmith.com/improvements-to-the-canonical-one-liner-glsl-rand-for-opengl-es-2-0/
+float random(float2 co)
+{
+	float a = 12.9898;
+	float b = 78.233;
+	float c = 43758.5453;
+	float dt= dot(co.xy ,float2(a,b));
+	float sn= fmod(dt,3.14);
+	return frac(sin(sn) * c);
+}
+
+float2 hammersley2d(uint i, uint N)
+{
+	// Radical inverse based on http://holger.dammertz.org/stuff/notes_HammersleyOnHemisphere.html
+	uint bits = (i << 16u) | (i >> 16u);
+	bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
+	bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
+	bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
+	bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
+	float rdi = float(bits) * 2.3283064365386963e-10;
+	return float2(float(i) /float(N), rdi);
+}
+
+// Based on http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_slides.pdf
+float3 importanceSample_GGX(float2 Xi, float roughness, float3 normal)
+{
+	// Maps a 2D point to a hemisphere with spread based on roughness
+	float alpha = roughness * roughness;
+	float phi = 2.0 * PI * Xi.x + random(normal.xz) * 0.1;
+	float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (alpha*alpha - 1.0) * Xi.y));
+	float sinTheta = sqrt(1.0 - cosTheta * cosTheta);
+	float3 H = float3(sinTheta * cos(phi), sinTheta * sin(phi), cosTheta);
+
+	// Tangent space
+	float3 up = abs(normal.z) < 0.999 ? float3(0.0, 0.0, 1.0) : float3(1.0, 0.0, 0.0);
+	float3 tangentX = normalize(cross(up, normal));
+	float3 tangentY = normalize(cross(normal, tangentX));
+
+	// Convert to world Space
+	return normalize(tangentX * H.x + tangentY * H.y + normal * H.z);
+}
+
+// Geometric Shadowing function
+float G_SchlicksmithGGX(float dotNL, float dotNV, float roughness)
+{
+	float k = (roughness * roughness) / 2.0;
+	float GL = dotNL / (dotNL * (1.0 - k) + k);
+	float GV = dotNV / (dotNV * (1.0 - k) + k);
+	return GL * GV;
+}
+
+float2 BRDF(float NoV, float roughness)
+{
+	// Normal always points along z-axis for the 2D lookup
+	const float3 N = float3(0.0, 0.0, 1.0);
+	float3 V = float3(sqrt(1.0 - NoV*NoV), 0.0, NoV);
+
+	float2 LUT = float2(0.0, 0.0);
+	for(uint i = 0u; i < NUM_SAMPLES; i++) {
+		float2 Xi = hammersley2d(i, NUM_SAMPLES);
+		float3 H = importanceSample_GGX(Xi, roughness, N);
+		float3 L = 2.0 * dot(V, H) * H - V;
+
+		float dotNL = max(dot(N, L), 0.0);
+		float dotNV = max(dot(N, V), 0.0);
+		float dotVH = max(dot(V, H), 0.0);
+		float dotNH = max(dot(H, N), 0.0);
+
+		if (dotNL > 0.0) {
+			float G = G_SchlicksmithGGX(dotNL, dotNV, roughness);
+			float G_Vis = (G * dotVH) / (dotNH * dotNV);
+			float Fc = pow(1.0 - dotVH, 5.0);
+			LUT += float2((1.0 - Fc) * G_Vis, Fc * G_Vis);
+		}
+	}
+	return LUT / float(NUM_SAMPLES);
+}
+
+float4 main([[vk::location(0)]] float2 inUV : TEXCOORD0) : SV_TARGET
+{
+	return float4(BRDF(inUV.x, 1.0-inUV.y), 0.0, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/pbrtexture/genbrdflut.frag.spv b/external/Vulkan/data/shaders/hlsl/pbrtexture/genbrdflut.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..ecd11746007e34b9ad7466a32fbb05b20ecb3afa
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/pbrtexture/genbrdflut.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/pbrtexture/genbrdflut.vert b/external/Vulkan/data/shaders/hlsl/pbrtexture/genbrdflut.vert
new file mode 100644
index 0000000000000000000000000000000000000000..188b72989e70e69a38a4fa88d015f67c7709cbdc
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/pbrtexture/genbrdflut.vert
@@ -0,0 +1,15 @@
+// Copyright 2020 Google LLC
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float2 UV : TEXCOORD0;
+};
+
+VSOutput main(uint VertexIndex : SV_VertexID)
+{
+	VSOutput output = (VSOutput)0;
+	output.UV = float2((VertexIndex << 1) & 2, VertexIndex & 2);
+	output.Pos = float4(output.UV * 2.0f - 1.0f, 0.0f, 1.0f);
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/pbrtexture/genbrdflut.vert.spv b/external/Vulkan/data/shaders/hlsl/pbrtexture/genbrdflut.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..46c4b31fdc1568cc56662cc746d3a2d10f9d0983
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/pbrtexture/genbrdflut.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/pbrtexture/irradiancecube.frag b/external/Vulkan/data/shaders/hlsl/pbrtexture/irradiancecube.frag
new file mode 100644
index 0000000000000000000000000000000000000000..66d63bf6ea0210161f0ebac65e76d33aac74b324
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/pbrtexture/irradiancecube.frag
@@ -0,0 +1,35 @@
+// Copyright 2020 Google LLC
+
+TextureCube textureEnv : register(t0);
+SamplerState samplerEnv : register(s0);
+
+struct PushConsts {
+[[vk::offset(64)]] float deltaPhi;
+[[vk::offset(68)]] float deltaTheta;
+};
+[[vk::push_constant]]PushConsts consts;
+
+#define PI 3.1415926535897932384626433832795
+
+float4 main([[vk::location(0)]] float3 inPos : TEXCOORD0) : SV_TARGET
+{
+	float3 N = normalize(inPos);
+	float3 up = float3(0.0, 1.0, 0.0);
+	float3 right = normalize(cross(up, N));
+	up = cross(N, right);
+
+	const float TWO_PI = PI * 2.0;
+	const float HALF_PI = PI * 0.5;
+
+	float3 color = float3(0.0, 0.0, 0.0);
+	uint sampleCount = 0u;
+	for (float phi = 0.0; phi < TWO_PI; phi += consts.deltaPhi) {
+		for (float theta = 0.0; theta < HALF_PI; theta += consts.deltaTheta) {
+			float3 tempVec = cos(phi) * right + sin(phi) * up;
+			float3 sampleVector = cos(theta) * N + sin(theta) * tempVec;
+			color += textureEnv.Sample(samplerEnv, sampleVector).rgb * cos(theta) * sin(theta);
+			sampleCount++;
+		}
+	}
+	return float4(PI * color / float(sampleCount), 1.0);
+}
diff --git a/external/Vulkan/data/shaders/hlsl/pbrtexture/irradiancecube.frag.spv b/external/Vulkan/data/shaders/hlsl/pbrtexture/irradiancecube.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..b3def200eefad232741ca0f54047f4f49101e263
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/pbrtexture/irradiancecube.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/pbrtexture/pbrtexture.frag b/external/Vulkan/data/shaders/hlsl/pbrtexture/pbrtexture.frag
new file mode 100644
index 0000000000000000000000000000000000000000..6f86e5b34840d93290b8390d5e59ca5103c0dcac
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/pbrtexture/pbrtexture.frag
@@ -0,0 +1,184 @@
+// Copyright 2020 Google LLC
+
+struct VSOutput
+{
+[[vk::location(0)]] float3 WorldPos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float2 UV : TEXCOORD0;
+[[vk::location(3)]] float3 Tangent : TEXCOORD1;
+};
+
+struct UBO  {
+	float4x4 projection;
+	float4x4 model;
+	float4x4 view;
+	float3 camPos;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct UBOParams {
+	float4 lights[4];
+	float exposure;
+	float gamma;
+};
+cbuffer uboParams : register(b1) { UBOParams uboParams; };
+
+TextureCube textureIrradiance : register(t2);
+SamplerState samplerIrradiance : register(s2);
+Texture2D textureBRDFLUT : register(t3);
+SamplerState samplerBRDFLUT : register(s3);
+TextureCube prefilteredMapTexture : register(t4);
+SamplerState prefilteredMapSampler : register(s4);
+
+Texture2D albedoMapTexture : register(t5);
+SamplerState albedoMapSampler : register(s5);
+Texture2D normalMapTexture : register(t6);
+SamplerState normalMapSampler : register(s6);
+Texture2D aoMapTexture : register(t7);
+SamplerState aoMapSampler : register(s7);
+Texture2D metallicMapTexture : register(t8);
+SamplerState metallicMapSampler : register(s8);
+Texture2D roughnessMapTexture : register(t9);
+SamplerState roughnessMapSampler : register(s9);
+
+#define PI 3.1415926535897932384626433832795
+#define ALBEDO(uv) pow(albedoMapTexture.Sample(albedoMapSampler, uv).rgb, float3(2.2, 2.2, 2.2))
+
+// From http://filmicgames.com/archives/75
+float3 Uncharted2Tonemap(float3 x)
+{
+	float A = 0.15;
+	float B = 0.50;
+	float C = 0.10;
+	float D = 0.20;
+	float E = 0.02;
+	float F = 0.30;
+	return ((x*(A*x+C*B)+D*E)/(x*(A*x+B)+D*F))-E/F;
+}
+
+// Normal Distribution function --------------------------------------
+float D_GGX(float dotNH, float roughness)
+{
+	float alpha = roughness * roughness;
+	float alpha2 = alpha * alpha;
+	float denom = dotNH * dotNH * (alpha2 - 1.0) + 1.0;
+	return (alpha2)/(PI * denom*denom);
+}
+
+// Geometric Shadowing function --------------------------------------
+float G_SchlicksmithGGX(float dotNL, float dotNV, float roughness)
+{
+	float r = (roughness + 1.0);
+	float k = (r*r) / 8.0;
+	float GL = dotNL / (dotNL * (1.0 - k) + k);
+	float GV = dotNV / (dotNV * (1.0 - k) + k);
+	return GL * GV;
+}
+
+// Fresnel function ----------------------------------------------------
+float3 F_Schlick(float cosTheta, float3 F0)
+{
+	return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
+}
+float3 F_SchlickR(float cosTheta, float3 F0, float roughness)
+{
+	return F0 + (max((1.0 - roughness).xxx, F0) - F0) * pow(1.0 - cosTheta, 5.0);
+}
+
+float3 prefilteredReflection(float3 R, float roughness)
+{
+	const float MAX_REFLECTION_LOD = 9.0; // todo: param/const
+	float lod = roughness * MAX_REFLECTION_LOD;
+	float lodf = floor(lod);
+	float lodc = ceil(lod);
+	float3 a = prefilteredMapTexture.SampleLevel(prefilteredMapSampler, R, lodf).rgb;
+	float3 b = prefilteredMapTexture.SampleLevel(prefilteredMapSampler, R, lodc).rgb;
+	return lerp(a, b, lod - lodf);
+}
+
+float3 specularContribution(float2 inUV, float3 L, float3 V, float3 N, float3 F0, float metallic, float roughness)
+{
+	// Precalculate vectors and dot products
+	float3 H = normalize (V + L);
+	float dotNH = clamp(dot(N, H), 0.0, 1.0);
+	float dotNV = clamp(dot(N, V), 0.0, 1.0);
+	float dotNL = clamp(dot(N, L), 0.0, 1.0);
+
+	// Light color fixed
+	float3 lightColor = float3(1.0, 1.0, 1.0);
+
+	float3 color = float3(0.0, 0.0, 0.0);
+
+	if (dotNL > 0.0) {
+		// D = Normal distribution (Distribution of the microfacets)
+		float D = D_GGX(dotNH, roughness);
+		// G = Geometric shadowing term (Microfacets shadowing)
+		float G = G_SchlicksmithGGX(dotNL, dotNV, roughness);
+		// F = Fresnel factor (Reflectance depending on angle of incidence)
+		float3 F = F_Schlick(dotNV, F0);
+		float3 spec = D * F * G / (4.0 * dotNL * dotNV + 0.001);
+		float3 kD = (float3(1.0, 1.0, 1.0) - F) * (1.0 - metallic);
+		color += (kD * ALBEDO(inUV) / PI + spec) * dotNL;
+	}
+
+	return color;
+}
+
+float3 calculateNormal(VSOutput input)
+{
+	float3 tangentNormal = normalMapTexture.Sample(normalMapSampler, input.UV).xyz * 2.0 - 1.0;
+
+	float3 N = normalize(input.Normal);
+	float3 T = normalize(input.Tangent);
+	float3 B = normalize(cross(N, T));
+	float3x3 TBN = transpose(float3x3(T, B, N));
+
+	return normalize(mul(TBN, tangentNormal));
+}
+
+float4 main(VSOutput input) : SV_TARGET
+{
+	float3 N = calculateNormal(input);
+	float3 V = normalize(ubo.camPos - input.WorldPos);
+	float3 R = reflect(-V, N);
+
+	float metallic = metallicMapTexture.Sample(metallicMapSampler, input.UV).r;
+	float roughness = roughnessMapTexture.Sample(roughnessMapSampler, input.UV).r;
+
+	float3 F0 = float3(0.04, 0.04, 0.04);
+	F0 = lerp(F0, ALBEDO(input.UV), metallic);
+
+	float3 Lo = float3(0.0, 0.0, 0.0);
+	for(int i = 0; i < 4; i++) {
+		float3 L = normalize(uboParams.lights[i].xyz - input.WorldPos);
+		Lo += specularContribution(input.UV, L, V, N, F0, metallic, roughness);
+	}
+
+	float2 brdf = textureBRDFLUT.Sample(samplerBRDFLUT, float2(max(dot(N, V), 0.0), roughness)).rg;
+	float3 reflection = prefilteredReflection(R, roughness).rgb;
+	float3 irradiance = textureIrradiance.Sample(samplerIrradiance, N).rgb;
+
+	// Diffuse based on irradiance
+	float3 diffuse = irradiance * ALBEDO(input.UV);
+
+	float3 F = F_SchlickR(max(dot(N, V), 0.0), F0, roughness);
+
+	// Specular reflectance
+	float3 specular = reflection * (F * brdf.x + brdf.y);
+
+	// Ambient part
+	float3 kD = 1.0 - F;
+	kD *= 1.0 - metallic;
+	float3 ambient = (kD * diffuse + specular) * aoMapTexture.Sample(aoMapSampler, input.UV).rrr;
+
+	float3 color = ambient + Lo;
+
+	// Tone mapping
+	color = Uncharted2Tonemap(color * uboParams.exposure);
+	color = color * (1.0f / Uncharted2Tonemap((11.2f).xxx));
+	// Gamma correction
+	color = pow(color, (1.0f / uboParams.gamma).xxx);
+
+	return float4(color, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/pbrtexture/pbrtexture.frag.spv b/external/Vulkan/data/shaders/hlsl/pbrtexture/pbrtexture.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..fea7d0667598231642e2a4e1a661866e8cbfa080
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/pbrtexture/pbrtexture.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/pbrtexture/pbrtexture.vert b/external/Vulkan/data/shaders/hlsl/pbrtexture/pbrtexture.vert
new file mode 100644
index 0000000000000000000000000000000000000000..1a13886ebe02c3f113b04e26693aef1fa46edbee
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/pbrtexture/pbrtexture.vert
@@ -0,0 +1,40 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float2 UV : TEXCOORD0;
+[[vk::location(3)]] float4 Tangent : TEXCOORD1;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+	float4x4 view;
+	float3 camPos;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 WorldPos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float2 UV : TEXCOORD0;
+[[vk::location(3)]] float3 Tangent : TEXCOORD1;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	float3 locPos = mul(ubo.model, float4(input.Pos, 1.0)).xyz;
+	output.WorldPos = locPos;
+	output.Normal = mul((float3x3)ubo.model, input.Normal);
+	output.Tangent = mul((float3x3)ubo.model, input.Tangent);
+	output.UV = input.UV;
+	output.Pos = mul(ubo.projection, mul(ubo.view, float4(output.WorldPos, 1.0)));
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/pbrtexture/pbrtexture.vert.spv b/external/Vulkan/data/shaders/hlsl/pbrtexture/pbrtexture.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..44e2c7f908545f3258fdceb383d709c5598e3767
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/pbrtexture/pbrtexture.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/pbrtexture/prefilterenvmap.frag b/external/Vulkan/data/shaders/hlsl/pbrtexture/prefilterenvmap.frag
new file mode 100644
index 0000000000000000000000000000000000000000..1f605d04a9f88afbf6d7d68718c44853dc94146a
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/pbrtexture/prefilterenvmap.frag
@@ -0,0 +1,106 @@
+// Copyright 2020 Google LLC
+
+TextureCube textureEnv : register(t0);
+SamplerState samplerEnv : register(s0);
+
+struct PushConsts {
+[[vk::offset(64)]] float roughness;
+[[vk::offset(68)]] uint numSamples;
+};
+[[vk::push_constant]] PushConsts consts;
+
+#define PI 3.1415926536
+
+// Based omn http://byteblacksmith.com/improvements-to-the-canonical-one-liner-glsl-rand-for-opengl-es-2-0/
+float random(float2 co)
+{
+	float a = 12.9898;
+	float b = 78.233;
+	float c = 43758.5453;
+	float dt= dot(co.xy ,float2(a,b));
+	float sn= fmod(dt,3.14);
+	return frac(sin(sn) * c);
+}
+
+float2 hammersley2d(uint i, uint N)
+{
+	// Radical inverse based on http://holger.dammertz.org/stuff/notes_HammersleyOnHemisphere.html
+	uint bits = (i << 16u) | (i >> 16u);
+	bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
+	bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
+	bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
+	bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
+	float rdi = float(bits) * 2.3283064365386963e-10;
+	return float2(float(i) /float(N), rdi);
+}
+
+// Based on http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_slides.pdf
+float3 importanceSample_GGX(float2 Xi, float roughness, float3 normal)
+{
+	// Maps a 2D point to a hemisphere with spread based on roughness
+	float alpha = roughness * roughness;
+	float phi = 2.0 * PI * Xi.x + random(normal.xz) * 0.1;
+	float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (alpha*alpha - 1.0) * Xi.y));
+	float sinTheta = sqrt(1.0 - cosTheta * cosTheta);
+	float3 H = float3(sinTheta * cos(phi), sinTheta * sin(phi), cosTheta);
+
+	// Tangent space
+	float3 up = abs(normal.z) < 0.999 ? float3(0.0, 0.0, 1.0) : float3(1.0, 0.0, 0.0);
+	float3 tangentX = normalize(cross(up, normal));
+	float3 tangentY = normalize(cross(normal, tangentX));
+
+	// Convert to world Space
+	return normalize(tangentX * H.x + tangentY * H.y + normal * H.z);
+}
+
+// Normal Distribution function
+float D_GGX(float dotNH, float roughness)
+{
+	float alpha = roughness * roughness;
+	float alpha2 = alpha * alpha;
+	float denom = dotNH * dotNH * (alpha2 - 1.0) + 1.0;
+	return (alpha2)/(PI * denom*denom);
+}
+
+float3 prefilterEnvMap(float3 R, float roughness)
+{
+	float3 N = R;
+	float3 V = R;
+	float3 color = float3(0.0, 0.0, 0.0);
+	float totalWeight = 0.0;
+	int2 envMapDims;
+	textureEnv.GetDimensions(envMapDims.x, envMapDims.y);
+	float envMapDim = float(envMapDims.x);
+	for(uint i = 0u; i < consts.numSamples; i++) {
+		float2 Xi = hammersley2d(i, consts.numSamples);
+		float3 H = importanceSample_GGX(Xi, roughness, N);
+		float3 L = 2.0 * dot(V, H) * H - V;
+		float dotNL = clamp(dot(N, L), 0.0, 1.0);
+		if(dotNL > 0.0) {
+			// Filtering based on https://placeholderart.wordpress.com/2015/07/28/implementation-notes-runtime-environment-map-filtering-for-image-based-lighting/
+
+			float dotNH = clamp(dot(N, H), 0.0, 1.0);
+			float dotVH = clamp(dot(V, H), 0.0, 1.0);
+
+			// Probability Distribution Function
+			float pdf = D_GGX(dotNH, roughness) * dotNH / (4.0 * dotVH) + 0.0001;
+			// Slid angle of current smple
+			float omegaS = 1.0 / (float(consts.numSamples) * pdf);
+			// Solid angle of 1 pixel across all cube faces
+			float omegaP = 4.0 * PI / (6.0 * envMapDim * envMapDim);
+			// Biased (+1.0) mip level for better result
+			float mipLevel = roughness == 0.0 ? 0.0 : max(0.5 * log2(omegaS / omegaP) + 1.0, 0.0f);
+			color += textureEnv.SampleLevel(samplerEnv, L, mipLevel).rgb * dotNL;
+			totalWeight += dotNL;
+
+		}
+	}
+	return (color / totalWeight);
+}
+
+
+float4 main([[vk::location(0)]] float3 inPos : POSITION0) : SV_TARGET
+{
+	float3 N = normalize(inPos);
+	return float4(prefilterEnvMap(N, consts.roughness), 1.0);
+}
diff --git a/external/Vulkan/data/shaders/hlsl/pbrtexture/prefilterenvmap.frag.spv b/external/Vulkan/data/shaders/hlsl/pbrtexture/prefilterenvmap.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..027bdf84ec64f28dca5aeb70d26db53999cddd92
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/pbrtexture/prefilterenvmap.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/pbrtexture/skybox.frag b/external/Vulkan/data/shaders/hlsl/pbrtexture/skybox.frag
new file mode 100644
index 0000000000000000000000000000000000000000..439c6287386c010f9fe7d3a91cb1d40ae96c7ca8
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/pbrtexture/skybox.frag
@@ -0,0 +1,37 @@
+// Copyright 2020 Google LLC
+
+TextureCube textureEnv : register(t2);
+SamplerState samplerEnv : register(s2);
+
+struct UBOParams {
+	float4 lights[4];
+	float exposure;
+	float gamma;
+};
+cbuffer uboParams : register(b1) { UBOParams uboParams; };
+
+// From http://filmicworlds.com/blog/filmic-tonemapping-operators/
+float3 Uncharted2Tonemap(float3 color)
+{
+	float A = 0.15;
+	float B = 0.50;
+	float C = 0.10;
+	float D = 0.20;
+	float E = 0.02;
+	float F = 0.30;
+	float W = 11.2;
+	return ((color*(A*color+C*B)+D*E)/(color*(A*color+B)+D*F))-E/F;
+}
+
+float4 main([[vk::location(0)]] float3 inUVW : POSITION0) : SV_TARGET
+{
+	float3 color = textureEnv.Sample(samplerEnv, inUVW).rgb;
+
+	// Tone mapping
+	color = Uncharted2Tonemap(color * uboParams.exposure);
+	color = color * (1.0f / Uncharted2Tonemap((11.2f).xxx));
+	// Gamma correction
+	color = pow(color, (1.0f / uboParams.gamma).xxx);
+
+	return float4(color, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/pbrtexture/skybox.frag.spv b/external/Vulkan/data/shaders/hlsl/pbrtexture/skybox.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..9731fbbce3dcc78768196ff5ce1ecdc08a1871af
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/pbrtexture/skybox.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/pbrtexture/skybox.vert b/external/Vulkan/data/shaders/hlsl/pbrtexture/skybox.vert
new file mode 100644
index 0000000000000000000000000000000000000000..26e3fb6dd18928150ae1f8530fc169ca6d748f2c
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/pbrtexture/skybox.vert
@@ -0,0 +1,30 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float2 UV : TEXCOORD0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 UVW : TEXCOORD0;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.UVW = input.Pos;
+	output.Pos = mul(ubo.projection, mul(ubo.model, float4(input.Pos.xyz, 1.0)));
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/pbrtexture/skybox.vert.spv b/external/Vulkan/data/shaders/hlsl/pbrtexture/skybox.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..698235d99a0273c252543604def972cd8e6f0b76
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/pbrtexture/skybox.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/pipelines/phong.frag b/external/Vulkan/data/shaders/hlsl/pipelines/phong.frag
new file mode 100644
index 0000000000000000000000000000000000000000..f9c98a57a02c6c2b187d2fad02dd2ffdf4743b81
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/pipelines/phong.frag
@@ -0,0 +1,28 @@
+// Copyright 2020 Google LLC
+
+Texture2D textureColorMap : register(t1);
+SamplerState samplerColorMap : register(s1);
+
+struct VSOutput
+{
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(3)]] float3 LightVec : TEXCOORD2;
+};
+
+float4 main(VSOutput input) : SV_TARGET
+{
+	// Desaturate color
+    float3 color = lerp(input.Color, dot(float3(0.2126,0.7152,0.0722), input.Color).xxx, 0.65);
+
+	// High ambient colors because mesh materials are pretty dark
+	float3 ambient = color * float3(1.0, 1.0, 1.0);
+	float3 N = normalize(input.Normal);
+	float3 L = normalize(input.LightVec);
+	float3 V = normalize(input.ViewVec);
+	float3 R = reflect(-L, N);
+	float3 diffuse = max(dot(N, L), 0.0) * color;
+	float3 specular = pow(max(dot(R, V), 0.0), 32.0) * float3(0.35, 0.35, 0.35);
+	return float4(ambient + diffuse * 1.75 + specular, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/pipelines/phong.frag.spv b/external/Vulkan/data/shaders/hlsl/pipelines/phong.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..fc9604e6a341de94b58c9086a6be1aee879f8ffb
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/pipelines/phong.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/pipelines/phong.vert b/external/Vulkan/data/shaders/hlsl/pipelines/phong.vert
new file mode 100644
index 0000000000000000000000000000000000000000..de0aa1106f25ccf881d53cc6bab2ffb25870e67e
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/pipelines/phong.vert
@@ -0,0 +1,41 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float3 Color : COLOR0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+	float4 lightPos;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(3)]] float3 LightVec : TEXCOORD2;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Normal = input.Normal;
+	output.Color = input.Color;
+	output.Pos = mul(ubo.projection, mul(ubo.model, float4(input.Pos.xyz, 1.0)));
+
+	float4 pos = mul(ubo.model, float4(input.Pos, 1.0));
+	output.Normal = mul((float3x3)ubo.model, input.Normal);
+	float3 lPos = mul((float3x3)ubo.model, ubo.lightPos.xyz);
+	output.LightVec = lPos - pos.xyz;
+	output.ViewVec = -pos.xyz;
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/pipelines/phong.vert.spv b/external/Vulkan/data/shaders/hlsl/pipelines/phong.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..6d9f4243dc414f30b2dbf0d6163b2be3d5c3eb88
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/pipelines/phong.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/pipelines/toon.frag b/external/Vulkan/data/shaders/hlsl/pipelines/toon.frag
new file mode 100644
index 0000000000000000000000000000000000000000..7f91459edd86e240c4671932f053709b95244196
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/pipelines/toon.frag
@@ -0,0 +1,36 @@
+// Copyright 2020 Google LLC
+
+Texture2D textureColorMap : register(t1);
+SamplerState samplerColorMap : register(s1);
+
+struct VSOutput
+{
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(3)]] float3 LightVec : TEXCOORD2;
+};
+
+float4 main(VSOutput input) : SV_TARGET
+{
+	// Desaturate color
+    float3 color = lerp(input.Color, dot(float3(0.2126,0.7152,0.0722), input.Color).xxx, 0.65);
+
+	// High ambient colors because mesh materials are pretty dark
+	float3 ambient = color * float3(1.0, 1.0, 1.0);
+	float3 N = normalize(input.Normal);
+	float3 L = normalize(input.LightVec);
+	float3 V = normalize(input.ViewVec);
+	float3 R = reflect(-L, N);
+	float3 diffuse = max(dot(N, L), 0.0) * color;
+	float3 specular = pow(max(dot(R, V), 0.0), 16.0) * float3(0.75, 0.75, 0.75);
+
+	float intensity = dot(N,L);
+	float shade = 1.0;
+	shade = intensity < 0.5 ? 0.75 : shade;
+	shade = intensity < 0.35 ? 0.6 : shade;
+	shade = intensity < 0.25 ? 0.5 : shade;
+	shade = intensity < 0.1 ? 0.25 : shade;
+
+	return float4(input.Color * 3.0 * shade, 1);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/pipelines/toon.frag.spv b/external/Vulkan/data/shaders/hlsl/pipelines/toon.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..92714cc16c361ed2a47830dd713f641dae31bb0d
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/pipelines/toon.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/pipelines/toon.vert b/external/Vulkan/data/shaders/hlsl/pipelines/toon.vert
new file mode 100644
index 0000000000000000000000000000000000000000..de0aa1106f25ccf881d53cc6bab2ffb25870e67e
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/pipelines/toon.vert
@@ -0,0 +1,41 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float3 Color : COLOR0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+	float4 lightPos;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(3)]] float3 LightVec : TEXCOORD2;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Normal = input.Normal;
+	output.Color = input.Color;
+	output.Pos = mul(ubo.projection, mul(ubo.model, float4(input.Pos.xyz, 1.0)));
+
+	float4 pos = mul(ubo.model, float4(input.Pos, 1.0));
+	output.Normal = mul((float3x3)ubo.model, input.Normal);
+	float3 lPos = mul((float3x3)ubo.model, ubo.lightPos.xyz);
+	output.LightVec = lPos - pos.xyz;
+	output.ViewVec = -pos.xyz;
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/pipelines/toon.vert.spv b/external/Vulkan/data/shaders/hlsl/pipelines/toon.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..6d9f4243dc414f30b2dbf0d6163b2be3d5c3eb88
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/pipelines/toon.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/pipelines/wireframe.frag b/external/Vulkan/data/shaders/hlsl/pipelines/wireframe.frag
new file mode 100644
index 0000000000000000000000000000000000000000..0ff24dd0946705efafb5048645f9337d0f082b03
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/pipelines/wireframe.frag
@@ -0,0 +1,6 @@
+// Copyright 2020 Google LLC
+
+float4 main([[vk::location(0)]] float3 Color : COLOR0) : SV_TARGET
+{
+	return float4(Color * 1.5, 1);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/pipelines/wireframe.frag.spv b/external/Vulkan/data/shaders/hlsl/pipelines/wireframe.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..7b7a846043e07cfe4ed559f0a79943b3f7d5dbbd
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/pipelines/wireframe.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/pipelines/wireframe.vert b/external/Vulkan/data/shaders/hlsl/pipelines/wireframe.vert
new file mode 100644
index 0000000000000000000000000000000000000000..7f89ba045482065690f72b61131af2d19e098015
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/pipelines/wireframe.vert
@@ -0,0 +1,29 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float4 Pos : POSITION0;
+[[vk::location(2)]] float3 Color : COLOR0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Color : COLOR0;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Color = input.Color;
+	output.Pos = mul(ubo.projection, mul(ubo.model, input.Pos));
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/pipelines/wireframe.vert.spv b/external/Vulkan/data/shaders/hlsl/pipelines/wireframe.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..00e07dc8a33dce32e50fddd7f4e3fc1341579155
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/pipelines/wireframe.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/pipelinestatistics/scene.frag b/external/Vulkan/data/shaders/hlsl/pipelinestatistics/scene.frag
new file mode 100644
index 0000000000000000000000000000000000000000..4cd20bf566eee5f70c9c2d4f4ef9e30ba062276b
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/pipelinestatistics/scene.frag
@@ -0,0 +1,20 @@
+// Copyright 2020 Google LLC
+
+struct VSOutput
+{
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(3)]] float3 LightVec : TEXCOORD2;
+};
+
+float4 main(VSOutput input) : SV_TARGET
+{
+	float3 N = normalize(input.Normal);
+	float3 L = normalize(input.LightVec);
+	float3 V = normalize(input.ViewVec);
+	float3 R = reflect(-L, N);
+	float3 diffuse = max(dot(N, L), 0.0) * input.Color;
+	float3 specular = pow(max(dot(R, V), 0.0), 8.0) * float3(0.75, 0.75, 0.75);
+	return float4(diffuse + specular, 0.5);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/pipelinestatistics/scene.frag.spv b/external/Vulkan/data/shaders/hlsl/pipelinestatistics/scene.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..2e7c9af5dd1ff3fb10cf89efd915c02a73f32b73
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/pipelinestatistics/scene.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/pipelinestatistics/scene.tesc b/external/Vulkan/data/shaders/hlsl/pipelinestatistics/scene.tesc
new file mode 100644
index 0000000000000000000000000000000000000000..bed96a0e227703922db0f3e1d905d7caec8d243d
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/pipelinestatistics/scene.tesc
@@ -0,0 +1,52 @@
+// Copyright 2020 Google LLC
+
+struct VSOutput
+{
+    float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(3)]] float3 LightVec : TEXCOORD2;
+};
+
+struct HSOutput
+{
+    float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(3)]] float3 LightVec : TEXCOORD2;
+};
+
+struct ConstantsHSOutput
+{
+    float TessLevelOuter[3] : SV_TessFactor;
+    float TessLevelInner : SV_InsideTessFactor;
+};
+
+ConstantsHSOutput ConstantsHS(InputPatch<VSOutput, 3> patch, uint InvocationID : SV_PrimitiveID)
+{
+    ConstantsHSOutput output = (ConstantsHSOutput)0;
+    output.TessLevelInner = 2.0;
+    output.TessLevelOuter[0] = 1.0;
+    output.TessLevelOuter[1] = 1.0;
+    output.TessLevelOuter[2] = 1.0;
+    return output;
+}
+
+[domain("tri")]
+[partitioning("integer")]
+[outputtopology("triangle_ccw")]
+[outputcontrolpoints(3)]
+[patchconstantfunc("ConstantsHS")]
+[maxtessfactor(20.0f)]
+HSOutput main(InputPatch<VSOutput, 3> patch, uint InvocationID : SV_OutputControlPointID)
+{
+	HSOutput output = (HSOutput)0;
+	output.Pos = patch[InvocationID].Pos;
+	output.Normal = patch[InvocationID].Normal;
+	output.Color = patch[InvocationID].Color;
+	output.ViewVec = patch[InvocationID].ViewVec;
+	output.LightVec = patch[InvocationID].LightVec;
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/pipelinestatistics/scene.tesc.spv b/external/Vulkan/data/shaders/hlsl/pipelinestatistics/scene.tesc.spv
new file mode 100644
index 0000000000000000000000000000000000000000..41cc5673570ed1717a76f7ca8284ae03db5dbddd
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/pipelinestatistics/scene.tesc.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/pipelinestatistics/scene.tese b/external/Vulkan/data/shaders/hlsl/pipelinestatistics/scene.tese
new file mode 100644
index 0000000000000000000000000000000000000000..1cccb2b237fc9f7ca5b7cc5a0310b09f804f2906
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/pipelinestatistics/scene.tese
@@ -0,0 +1,39 @@
+// Copyright 2020 Google LLC
+
+struct HSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(3)]] float3 LightVec : TEXCOORD2;
+};
+
+struct ConstantsHSOutput
+{
+    float TessLevelOuter[3] : SV_TessFactor;
+    float TessLevelInner : SV_InsideTessFactor;
+};
+
+struct DSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(3)]] float3 LightVec : TEXCOORD2;
+};
+
+[domain("tri")]
+DSOutput main(ConstantsHSOutput input, float3 TessCoord : SV_DomainLocation, const OutputPatch<HSOutput, 3> patch)
+{
+	DSOutput output = (DSOutput)0;
+	output.Pos = 	(TessCoord.x * patch[2].Pos) +
+					(TessCoord.y * patch[1].Pos) +
+					(TessCoord.z * patch[0].Pos);
+	output.Normal = TessCoord.x * patch[2].Normal + TessCoord.y * patch[1].Normal + TessCoord.z * patch[0].Normal;
+	output.ViewVec = TessCoord.x * patch[2].ViewVec + TessCoord.y * patch[1].ViewVec + TessCoord.z * patch[0].ViewVec;
+	output.LightVec = TessCoord.x * patch[2].LightVec + TessCoord.y * patch[1].LightVec + TessCoord.z * patch[0].LightVec;
+	output.Color = patch[0].Color;
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/pipelinestatistics/scene.tese.spv b/external/Vulkan/data/shaders/hlsl/pipelinestatistics/scene.tese.spv
new file mode 100644
index 0000000000000000000000000000000000000000..665ad2d918956fc869c83bf8d516b67a49cf58dc
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/pipelinestatistics/scene.tese.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/pipelinestatistics/scene.vert b/external/Vulkan/data/shaders/hlsl/pipelinestatistics/scene.vert
new file mode 100644
index 0000000000000000000000000000000000000000..a11fe37cffa05f32e262797725a625a0902fbc98
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/pipelinestatistics/scene.vert
@@ -0,0 +1,48 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float3 Color : COLOR0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 modelview;
+	float4 lightPos;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(3)]] float3 LightVec : TEXCOORD2;
+};
+
+struct PushConsts {
+	float3 objPos;
+};
+[[vk::push_constant]] PushConsts pushConsts;
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Normal = input.Normal;
+	output.Color = input.Color;
+
+	float3 locPos = mul(ubo.modelview, float4(input.Pos, 1.0)).xyz;
+	float3 worldPos = mul(ubo.modelview, float4(input.Pos + pushConsts.objPos, 1.0)).xyz;
+	output.Pos = mul(ubo.projection, float4(worldPos, 1.0));
+
+	float4 pos = mul(ubo.modelview, float4(worldPos, 1.0));
+	output.Normal = mul((float3x3)ubo.modelview, input.Normal);
+	output.LightVec = ubo.lightPos.xyz - pos.xyz;
+	output.ViewVec = -pos.xyz;
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/pipelinestatistics/scene.vert.spv b/external/Vulkan/data/shaders/hlsl/pipelinestatistics/scene.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..f9af87c1c3309924ea3ec64fc25e26d1d98c52d4
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/pipelinestatistics/scene.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/pushconstants/pushconstants.frag b/external/Vulkan/data/shaders/hlsl/pushconstants/pushconstants.frag
new file mode 100644
index 0000000000000000000000000000000000000000..505af88a3bdd01178834e234c4985ad0f24f5d0e
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/pushconstants/pushconstants.frag
@@ -0,0 +1,11 @@
+// Copyright 2020 Google LLC
+
+struct VSOutput
+{
+[[vk::location(0)]] float3 Color : COLOR0;
+};
+
+float4 main(VSOutput input) : SV_TARGET
+{
+	return float4(input.Color, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/pushconstants/pushconstants.frag.spv b/external/Vulkan/data/shaders/hlsl/pushconstants/pushconstants.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..572c1e3ef941e2c4177fc9cc0c8bfdd2e3b51882
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/pushconstants/pushconstants.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/pushconstants/pushconstants.vert b/external/Vulkan/data/shaders/hlsl/pushconstants/pushconstants.vert
new file mode 100644
index 0000000000000000000000000000000000000000..c6c7f8e74cf375d7c9efd12b1295f1c2955977c0
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/pushconstants/pushconstants.vert
@@ -0,0 +1,41 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float3 Color : COLOR0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+	float4x4 view;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct PushConsts {
+	float4 color;
+	float4 position;
+};
+[[vk::push_constant]] PushConsts pushConsts;
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Color : COLOR0;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Color = input.Color * pushConsts.color.rgb;
+	
+	float3 locPos = float3(mul(ubo.model, float4(input.Pos.xyz, 1.0)).xyz);
+	float3 worldPos = locPos + pushConsts.position.xyz;
+	output.Pos = mul(ubo.projection, mul(ubo.view, float4(worldPos.xyz, 1.0)));
+	
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/pushconstants/pushconstants.vert.spv b/external/Vulkan/data/shaders/hlsl/pushconstants/pushconstants.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..8ed9d921da4c5a81975f7122db44a9e62e703855
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/pushconstants/pushconstants.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/pushdescriptors/cube.frag b/external/Vulkan/data/shaders/hlsl/pushdescriptors/cube.frag
new file mode 100644
index 0000000000000000000000000000000000000000..69ede0b8d006e091207321263120f68a48c016b1
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/pushdescriptors/cube.frag
@@ -0,0 +1,16 @@
+// Copyright 2020 Google LLC
+
+Texture2D textureColorMap : register(t2);
+SamplerState samplerColorMap : register(s2);
+
+struct VSOutput
+{
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float2 UV : TEXCOORD0;
+};
+
+float4 main(VSOutput input) : SV_TARGET
+{
+	return textureColorMap.Sample(samplerColorMap, input.UV) * float4(input.Color, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/pushdescriptors/cube.frag.spv b/external/Vulkan/data/shaders/hlsl/pushdescriptors/cube.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..1bf942431570b8370f7cb272175d3e2e634d78a4
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/pushdescriptors/cube.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/pushdescriptors/cube.vert b/external/Vulkan/data/shaders/hlsl/pushdescriptors/cube.vert
new file mode 100644
index 0000000000000000000000000000000000000000..9a8366a02e5f88e22bbdc3089346613a0c920fae
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/pushdescriptors/cube.vert
@@ -0,0 +1,38 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float2 UV : TEXCOORD0;
+[[vk::location(3)]] float3 Color : COLOR0;
+};
+
+struct UBOScene {
+	float4x4 projection;
+	float4x4 view;
+};
+cbuffer uboCamera : register(b0) { UBOScene uboCamera; };
+
+struct UBOModel {
+	float4x4 local;
+};
+cbuffer uboModel : register(b1) { UBOModel uboModel; };
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float2 UV : TEXCOORD0;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Normal = input.Normal;
+	output.Color = input.Color;
+	output.UV = input.UV;
+	output.Pos = mul(uboCamera.projection, mul(uboCamera.view, mul(uboModel.local, float4(input.Pos.xyz, 1.0))));
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/pushdescriptors/cube.vert.spv b/external/Vulkan/data/shaders/hlsl/pushdescriptors/cube.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..d5f4a64e6fbc5c85915e480f592cd07c65eea6e2
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/pushdescriptors/cube.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/radialblur/colorpass.frag b/external/Vulkan/data/shaders/hlsl/radialblur/colorpass.frag
new file mode 100644
index 0000000000000000000000000000000000000000..08577814eac87341c6630566ab31d53056f7a204
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/radialblur/colorpass.frag
@@ -0,0 +1,23 @@
+// Copyright 2020 Google LLC
+
+Texture2D textureGradientRamp : register(t1);
+SamplerState samplerGradientRamp : register(s1);
+
+struct VSOutput
+{
+[[vk::location(0)]] float3 Color : COLOR0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+};
+
+float4 main(VSOutput input) : SV_TARGET
+{
+	// Use max. color channel value to detect bright glow emitters
+	if ((input.Color.r >= 0.9) || (input.Color.g >= 0.9) || (input.Color.b >= 0.9))
+	{
+		return float4(textureGradientRamp.Sample(samplerGradientRamp, input.UV).rgb, 1);
+	}
+	else
+	{
+		return float4(input.Color, 1);
+	}
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/radialblur/colorpass.frag.spv b/external/Vulkan/data/shaders/hlsl/radialblur/colorpass.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..7dc782fa23892e963ee0e4a1d40dbd24bab75212
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/radialblur/colorpass.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/radialblur/colorpass.vert b/external/Vulkan/data/shaders/hlsl/radialblur/colorpass.vert
new file mode 100644
index 0000000000000000000000000000000000000000..a82eedea4dff7dbf9fe7ae440363f8492461f04a
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/radialblur/colorpass.vert
@@ -0,0 +1,32 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(2)]] float3 Color : COLOR0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+	float gradientPos;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Color : COLOR0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Color = input.Color;
+	output.UV = float2(ubo.gradientPos, 0.0f);
+	output.Pos = mul(ubo.projection, mul(ubo.model, float4(input.Pos, 1.0)));
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/radialblur/colorpass.vert.spv b/external/Vulkan/data/shaders/hlsl/radialblur/colorpass.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..5d849c47f8f3ac3d363f5c2c6423f300eeb97ae4
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/radialblur/colorpass.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/radialblur/phongpass.frag b/external/Vulkan/data/shaders/hlsl/radialblur/phongpass.frag
new file mode 100644
index 0000000000000000000000000000000000000000..1360ff30d650215fd2ddfbd1973d8fd133df8195
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/radialblur/phongpass.frag
@@ -0,0 +1,35 @@
+// Copyright 2020 Google LLC
+
+Texture2D textureGradientRamp : register(t1);
+SamplerState samplerGradientRamp : register(s1);
+
+struct VSOutput
+{
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float3 EyePos : POSITION0;
+[[vk::location(3)]] float3 LightVec : TEXCOORD2;
+[[vk::location(4)]] float2 UV : TEXCOORD0;
+};
+
+float4 main(VSOutput input) : SV_TARGET
+{
+	// No light calculations for glow color
+	// Use max. color channel value
+	// to detect bright glow emitters
+	if ((input.Color.r >= 0.9) || (input.Color.g >= 0.9) || (input.Color.b >= 0.9))
+	{
+		return float4(textureGradientRamp.Sample(samplerGradientRamp, input.UV).rgb, 1);
+	}
+	else
+	{
+		float3 Eye = normalize(-input.EyePos);
+		float3 Reflected = normalize(reflect(-input.LightVec, input.Normal));
+
+		float4 IAmbient = float4(0.2, 0.2, 0.2, 1.0);
+		float4 IDiffuse = float4(0.5, 0.5, 0.5, 0.5) * max(dot(input.Normal, input.LightVec), 0.0);
+		float specular = 0.25;
+		float4 ISpecular = float4(0.5, 0.5, 0.5, 1.0) * pow(max(dot(Reflected, Eye), 0.0), 4.0) * specular;
+		return float4((IAmbient + IDiffuse) * float4(input.Color, 1.0) + ISpecular);
+	}
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/radialblur/phongpass.frag.spv b/external/Vulkan/data/shaders/hlsl/radialblur/phongpass.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..f5f3b3f6b31d5ada4198131a916bb7e3c4c7239f
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/radialblur/phongpass.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/radialblur/phongpass.vert b/external/Vulkan/data/shaders/hlsl/radialblur/phongpass.vert
new file mode 100644
index 0000000000000000000000000000000000000000..22b8974b46a5da1948989ae6cac6cb8e23a6069e
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/radialblur/phongpass.vert
@@ -0,0 +1,40 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float4 Pos : POSITION0;
+[[vk::location(2)]] float3 Color : COLOR0;
+[[vk::location(3)]] float3 Normal : NORMAL0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+	float gradientPos;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float3 EyePos : POSITION0;
+[[vk::location(3)]] float3 LightVec : TEXCOORD2;
+[[vk::location(4)]] float2 UV : TEXCOORD0;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Normal = input.Normal;
+	output.Color = input.Color;
+	output.UV = float2(ubo.gradientPos, 0.0);
+	output.Pos = mul(ubo.projection, mul(ubo.model, input.Pos));
+	output.EyePos = mul(ubo.model, input.Pos).xyz;
+	float4 lightPos = float4(0.0, 0.0, -5.0, 1.0);// * ubo.model;
+	output.LightVec = normalize(lightPos.xyz - input.Pos.xyz);
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/radialblur/phongpass.vert.spv b/external/Vulkan/data/shaders/hlsl/radialblur/phongpass.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..a9d4eb24db209ba81985f9b13d90b113ee6f4d8a
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/radialblur/phongpass.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/radialblur/radialblur.frag b/external/Vulkan/data/shaders/hlsl/radialblur/radialblur.frag
new file mode 100644
index 0000000000000000000000000000000000000000..3e41875e5fcb370b05408f49fba4a0d41567bc61
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/radialblur/radialblur.frag
@@ -0,0 +1,35 @@
+// Copyright 2020 Google LLC
+
+Texture2D textureColor : register(t1);
+SamplerState samplerColor : register(s1);
+
+struct UBO
+{
+	float radialBlurScale;
+	float radialBlurStrength;
+	float2 radialOrigin;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+float4 main([[vk::location(0)]] float2 inUV : TEXCOORD0) : SV_TARGET
+{
+	int2 texDim;
+	textureColor.GetDimensions(texDim.x, texDim.y);
+	float2 radialSize = float2(1.0 / texDim.x, 1.0 / texDim.y);
+
+	float2 UV = inUV;
+
+	float4 color = float4(0.0, 0.0, 0.0, 0.0);
+	UV += radialSize * 0.5 - ubo.radialOrigin;
+
+	#define samples 32
+
+	for (int i = 0; i < samples; i++)
+	{
+		float scale = 1.0 - ubo.radialBlurScale * (float(i) / float(samples-1));
+		color += textureColor.Sample(samplerColor, UV * scale + ubo.radialOrigin);
+	}
+
+	return (color / samples) * ubo.radialBlurStrength;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/radialblur/radialblur.frag.spv b/external/Vulkan/data/shaders/hlsl/radialblur/radialblur.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..34db5e2b1ef79bcd48a760885aefee7ec09fabfc
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/radialblur/radialblur.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/radialblur/radialblur.vert b/external/Vulkan/data/shaders/hlsl/radialblur/radialblur.vert
new file mode 100644
index 0000000000000000000000000000000000000000..b13c2bf2353ce1fc1837fef4db32336429c22412
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/radialblur/radialblur.vert
@@ -0,0 +1,15 @@
+// Copyright 2020 Google LLC
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float2 UV : TEXCOORD0;
+};
+
+VSOutput main(uint VertexIndex : SV_VertexID)
+{
+	VSOutput output = (VSOutput)0;
+	output.UV = float2((VertexIndex << 1) & 2, VertexIndex & 2);
+	output.Pos = float4(output.UV * 2.0f - 1.0f, 0.0f, 1.0f);
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/radialblur/radialblur.vert.spv b/external/Vulkan/data/shaders/hlsl/radialblur/radialblur.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..46c4b31fdc1568cc56662cc746d3a2d10f9d0983
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/radialblur/radialblur.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/raytracingbasic/closesthit.rchit b/external/Vulkan/data/shaders/hlsl/raytracingbasic/closesthit.rchit
new file mode 100644
index 0000000000000000000000000000000000000000..2a8921ce5dcabbf9a94acde9aa152c7f39f44d37
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/raytracingbasic/closesthit.rchit
@@ -0,0 +1,18 @@
+// Copyright 2020 Google LLC
+
+struct Attribute
+{
+  float2 attribs;
+};
+
+struct Payload
+{
+[[vk::location(0)]] float3 hitValue;
+};
+
+[shader("closesthit")]
+void main(inout Payload p, in float2 attribs)
+{
+  const float3 barycentricCoords = float3(1.0f - attribs.x - attribs.y, attribs.x, attribs.y);
+  p.hitValue = barycentricCoords;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/raytracingbasic/closesthit.rchit.spv b/external/Vulkan/data/shaders/hlsl/raytracingbasic/closesthit.rchit.spv
new file mode 100644
index 0000000000000000000000000000000000000000..95890dd79693ee41f7859fe0afe7b25b97cc7e3b
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/raytracingbasic/closesthit.rchit.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/raytracingbasic/miss.rmiss b/external/Vulkan/data/shaders/hlsl/raytracingbasic/miss.rmiss
new file mode 100644
index 0000000000000000000000000000000000000000..d9e274c042a8c3874061ed4fb3f4304ce01e469a
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/raytracingbasic/miss.rmiss
@@ -0,0 +1,12 @@
+// Copyright 2020 Google LLC
+
+struct Payload
+{
+[[vk::location(0)]] float3 hitValue;
+};
+
+[shader("miss")]
+void main(inout Payload p)
+{
+    p.hitValue = float3(0.0, 0.0, 0.2);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/raytracingbasic/miss.rmiss.spv b/external/Vulkan/data/shaders/hlsl/raytracingbasic/miss.rmiss.spv
new file mode 100644
index 0000000000000000000000000000000000000000..5eacf24aaff07e587ce51b8d68dba4e870e195e9
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/raytracingbasic/miss.rmiss.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/raytracingbasic/raygen.rgen b/external/Vulkan/data/shaders/hlsl/raytracingbasic/raygen.rgen
new file mode 100644
index 0000000000000000000000000000000000000000..d30bb92eabf97c9fd00532380bd705c0bf921c71
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/raytracingbasic/raygen.rgen
@@ -0,0 +1,39 @@
+// Copyright 2020 Google LLC
+
+RaytracingAccelerationStructure rs : register(t0);
+RWTexture2D<float4> image : register(u1);
+
+struct CameraProperties
+{
+	float4x4 viewInverse;
+	float4x4 projInverse;
+};
+cbuffer cam : register(b2) { CameraProperties cam; };
+
+struct Payload
+{
+[[vk::location(0)]] float3 hitValue;
+};
+
+[shader("raygeneration")]
+void main()
+{
+	uint3 LaunchID = DispatchRaysIndex();
+	uint3 LaunchSize = DispatchRaysDimensions();
+
+	const float2 pixelCenter = float2(LaunchID.xy) + float2(0.5, 0.5);
+	const float2 inUV = pixelCenter/float2(LaunchSize.xy);
+	float2 d = inUV * 2.0 - 1.0;
+	float4 target = mul(cam.projInverse, float4(d.x, d.y, 1, 1));
+
+	RayDesc rayDesc;
+	rayDesc.Origin = mul(cam.viewInverse, float4(0,0,0,1)).xyz;
+	rayDesc.Direction = mul(cam.viewInverse, float4(normalize(target.xyz), 0)).xyz;
+	rayDesc.TMin = 0.001;
+	rayDesc.TMax = 10000.0;
+
+	Payload payload;
+	TraceRay(rs, RAY_FLAG_FORCE_OPAQUE, 0xff, 0, 0, 0, rayDesc, payload);
+
+	image[int2(LaunchID.xy)] = float4(payload.hitValue, 0.0);
+}
diff --git a/external/Vulkan/data/shaders/hlsl/raytracingbasic/raygen.rgen.spv b/external/Vulkan/data/shaders/hlsl/raytracingbasic/raygen.rgen.spv
new file mode 100644
index 0000000000000000000000000000000000000000..f0a0a140230a13aa1e966fec469bdacea0aff45a
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/raytracingbasic/raygen.rgen.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/raytracingcallable/callable1.rcall b/external/Vulkan/data/shaders/hlsl/raytracingcallable/callable1.rcall
new file mode 100644
index 0000000000000000000000000000000000000000..5a40044d5fb060ad0a551d3f1b0133654ee87a77
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/raytracingcallable/callable1.rcall
@@ -0,0 +1,15 @@
+// Copyright 2021 Sascha Willems
+
+struct CallData
+{
+    float3 outColor;
+};
+
+[shader("callable")]
+void main(inout CallData data)
+{
+    // Generate a checker board pattern
+	float2 pos = float2(DispatchRaysIndex().x / 8, DispatchRaysIndex().y / 8);
+	float col = (pos.x + (pos.y % 2.0)) % 2.0;
+	data.outColor = float3(col, col, col);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/raytracingcallable/callable1.rcall.spv b/external/Vulkan/data/shaders/hlsl/raytracingcallable/callable1.rcall.spv
new file mode 100644
index 0000000000000000000000000000000000000000..79fe2d1927b655cecf246f6c926b339e297ac1ac
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/raytracingcallable/callable1.rcall.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/raytracingcallable/callable2.rcall b/external/Vulkan/data/shaders/hlsl/raytracingcallable/callable2.rcall
new file mode 100644
index 0000000000000000000000000000000000000000..867893250850cef3fbdcda405e72ea5a77351e43
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/raytracingcallable/callable2.rcall
@@ -0,0 +1,12 @@
+// Copyright 2021 Sascha Willems
+
+struct CallData
+{
+    float3 outColor;
+};
+
+[shader("callable")]
+void main(inout CallData data)
+{
+	data.outColor = float3(0.0, 1.0, 0.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/raytracingcallable/callable2.rcall.spv b/external/Vulkan/data/shaders/hlsl/raytracingcallable/callable2.rcall.spv
new file mode 100644
index 0000000000000000000000000000000000000000..fb4c4f39b2f69e26b837d00533a9e2bffe7bdddc
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/raytracingcallable/callable2.rcall.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/raytracingcallable/callable3.rcall b/external/Vulkan/data/shaders/hlsl/raytracingcallable/callable3.rcall
new file mode 100644
index 0000000000000000000000000000000000000000..3fe4fd851333293eaf541308bb514a29b9a4de4b
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/raytracingcallable/callable3.rcall
@@ -0,0 +1,15 @@
+// Copyright 2021 Sascha Willems
+
+struct CallData
+{
+    float3 outColor;
+};
+
+[shader("callable")]
+void main(inout CallData data)
+{
+    // Generate a checker board pattern
+	float2 pos = float2(DispatchRaysIndex().x / 8, DispatchRaysIndex().y / 8);
+	float col = pos.y % 2.0;
+	data.outColor = float3(col, col, col);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/raytracingcallable/callable3.rcall.spv b/external/Vulkan/data/shaders/hlsl/raytracingcallable/callable3.rcall.spv
new file mode 100644
index 0000000000000000000000000000000000000000..417942748a5bc9db76c384584145321f2a5497c2
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/raytracingcallable/callable3.rcall.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/raytracingcallable/closesthit.rchit b/external/Vulkan/data/shaders/hlsl/raytracingcallable/closesthit.rchit
new file mode 100644
index 0000000000000000000000000000000000000000..80cd773d69bbfe207a6990ba7d73e312c772f938
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/raytracingcallable/closesthit.rchit
@@ -0,0 +1,21 @@
+// Copyright 2021 Sascha Willems
+
+struct Payload
+{
+[[vk::location(0)]] float3 hitValue;
+};
+
+struct CallData
+{
+    float3 outColor;
+};
+
+[shader("closesthit")]
+void main(inout Payload p, in float2 attribs)
+{
+	// Execute the callable shader indexed by the current geometry being hit
+	// For our sample this means that the first callable shader in the SBT is invoked for the first triangle, the second callable shader for the second triangle, etc.
+	CallData callData;
+	CallShader(GeometryIndex(), callData);
+	p.hitValue = callData.outColor;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/raytracingcallable/closesthit.rchit.spv b/external/Vulkan/data/shaders/hlsl/raytracingcallable/closesthit.rchit.spv
new file mode 100644
index 0000000000000000000000000000000000000000..8e3fca2c6b90f2f13bdee12e305766b26042b711
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/raytracingcallable/closesthit.rchit.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/raytracingcallable/miss.rmiss b/external/Vulkan/data/shaders/hlsl/raytracingcallable/miss.rmiss
new file mode 100644
index 0000000000000000000000000000000000000000..3342b168ba87d20eacf8779027b0cdf9e944ab8f
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/raytracingcallable/miss.rmiss
@@ -0,0 +1,12 @@
+// Copyright 2021 Sascha Willems
+
+struct Payload
+{
+[[vk::location(0)]] float3 hitValue;
+};
+
+[shader("miss")]
+void main(inout Payload p)
+{
+    p.hitValue = float3(0.0, 0.0, 0.2);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/raytracingcallable/miss.rmiss.spv b/external/Vulkan/data/shaders/hlsl/raytracingcallable/miss.rmiss.spv
new file mode 100644
index 0000000000000000000000000000000000000000..839732c55af412eca38c51031c853731c97d6520
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/raytracingcallable/miss.rmiss.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/raytracingcallable/raygen.rgen b/external/Vulkan/data/shaders/hlsl/raytracingcallable/raygen.rgen
new file mode 100644
index 0000000000000000000000000000000000000000..6e08cd050427dd281bf984c68f6f8bed2f70622b
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/raytracingcallable/raygen.rgen
@@ -0,0 +1,39 @@
+// Copyright 2021 Sascha Willems
+
+RaytracingAccelerationStructure rs : register(t0);
+RWTexture2D<float4> image : register(u1);
+
+struct CameraProperties
+{
+	float4x4 viewInverse;
+	float4x4 projInverse;
+};
+cbuffer cam : register(b2) { CameraProperties cam; };
+
+struct Payload
+{
+[[vk::location(0)]] float3 hitValue;
+};
+
+[shader("raygeneration")]
+void main()
+{
+	uint3 LaunchID = DispatchRaysIndex();
+	uint3 LaunchSize = DispatchRaysDimensions();
+
+	const float2 pixelCenter = float2(LaunchID.xy) + float2(0.5, 0.5);
+	const float2 inUV = pixelCenter/float2(LaunchSize.xy);
+	float2 d = inUV * 2.0 - 1.0;
+	float4 target = mul(cam.projInverse, float4(d.x, d.y, 1, 1));
+
+	RayDesc rayDesc;
+	rayDesc.Origin = mul(cam.viewInverse, float4(0,0,0,1)).xyz;
+	rayDesc.Direction = mul(cam.viewInverse, float4(normalize(target.xyz), 0)).xyz;
+	rayDesc.TMin = 0.001;
+	rayDesc.TMax = 10000.0;
+
+	Payload payload;
+	TraceRay(rs, RAY_FLAG_FORCE_OPAQUE, 0xff, 0, 0, 0, rayDesc, payload);
+
+	image[int2(LaunchID.xy)] = float4(payload.hitValue, 0.0);
+}
diff --git a/external/Vulkan/data/shaders/hlsl/raytracingcallable/raygen.rgen.spv b/external/Vulkan/data/shaders/hlsl/raytracingcallable/raygen.rgen.spv
new file mode 100644
index 0000000000000000000000000000000000000000..611d1de15f639dd9562c2da18ea20d55961c052e
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/raytracingcallable/raygen.rgen.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/raytracingreflections/closesthit.rchit b/external/Vulkan/data/shaders/hlsl/raytracingreflections/closesthit.rchit
new file mode 100644
index 0000000000000000000000000000000000000000..643bfbe9f3f8326d17bf284d7d146642f52ab856
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/raytracingreflections/closesthit.rchit
@@ -0,0 +1,75 @@
+// Copyright 2020 Google LLC
+
+struct RayPayload
+{
+	float3 color;
+	float distance;
+	float3 normal;
+	float reflector;
+};
+
+RaytracingAccelerationStructure topLevelAS : register(t0);
+struct UBO
+{
+	float4x4 viewInverse;
+	float4x4 projInverse;
+	float4 lightPos;
+	int vertexSize;
+};
+cbuffer ubo : register(b2) { UBO ubo; };
+
+StructuredBuffer<float4> vertices : register(t3);
+StructuredBuffer<uint> indices : register(t4);
+
+struct Vertex
+{
+  float3 pos;
+  float3 normal;
+  float2 uv;
+  float4 color;
+  float4 _pad0; 
+  float4 _pad1;
+};
+
+Vertex unpack(uint index)
+{
+	// Unpack the vertices from the SSBO using the glTF vertex structure
+	// The multiplier is the size of the vertex divided by four float components (=16 bytes)
+	const int m = ubo.vertexSize / 16;
+
+	float4 d0 = vertices[m * index + 0];
+	float4 d1 = vertices[m * index + 1];
+	float4 d2 = vertices[m * index + 2];
+
+	Vertex v;
+	v.pos = d0.xyz;
+	v.normal = float3(d0.w, d1.x, d1.y);
+	v.color = float4(d2.x, d2.y, d2.z, 1.0);
+
+	return v;
+}
+
+[shader("closesthit")]
+void main(inout RayPayload rayPayload, in float2 attribs)
+{
+	uint PrimitiveID = PrimitiveIndex();
+	int3 index = int3(indices[3 * PrimitiveID], indices[3 * PrimitiveID + 1], indices[3 * PrimitiveID + 2]);
+
+	Vertex v0 = unpack(index.x);
+	Vertex v1 = unpack(index.y);
+	Vertex v2 = unpack(index.z);
+
+	// Interpolate normal
+	const float3 barycentricCoords = float3(1.0f - attribs.x - attribs.y, attribs.x, attribs.y);
+	float3 normal = normalize(v0.normal * barycentricCoords.x + v1.normal * barycentricCoords.y + v2.normal * barycentricCoords.z);
+
+	// Basic lighting
+	float3 lightVector = normalize(ubo.lightPos.xyz);
+	float dot_product = max(dot(lightVector, normal), 0.6);
+	rayPayload.color.rgb = v0.color * dot_product;
+	rayPayload.distance = RayTCurrent();
+	rayPayload.normal = normal;
+
+	// Objects with full white vertex color are treated as reflectors
+	rayPayload.reflector = ((v0.color.r == 1.0f) && (v0.color.g == 1.0f) && (v0.color.b == 1.0f)) ? 1.0f : 0.0f;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/raytracingreflections/closesthit.rchit.spv b/external/Vulkan/data/shaders/hlsl/raytracingreflections/closesthit.rchit.spv
new file mode 100644
index 0000000000000000000000000000000000000000..50673731617dc1a39bd8934f2bb5dc4335b6b4e1
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/raytracingreflections/closesthit.rchit.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/raytracingreflections/miss.rmiss b/external/Vulkan/data/shaders/hlsl/raytracingreflections/miss.rmiss
new file mode 100644
index 0000000000000000000000000000000000000000..8f75824afeb0a474ce9c2aa7321a689099d72671
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/raytracingreflections/miss.rmiss
@@ -0,0 +1,25 @@
+// Copyright 2020 Google LLC
+
+struct RayPayload {
+	float3 color;
+	float distance;
+	float3 normal;
+	float reflector;
+};
+
+[shader("miss")]
+void main(inout RayPayload rayPayload)
+{
+	float3 worldRayDirection = WorldRayDirection();
+
+	// View-independent background gradient to simulate a basic sky background
+	const float3 gradientStart = float3(0.5, 0.6, 1.0);
+	const float3 gradientEnd = float3(1.0, 1.0, 1.0);
+	float3 unitDir = normalize(worldRayDirection);
+	float t = 0.5 * (unitDir.y + 1.0);
+	rayPayload.color = (1.0-t) * gradientStart + t * gradientEnd;
+
+	rayPayload.distance = -1.0f;
+	rayPayload.normal = float3(0, 0, 0);
+	rayPayload.reflector = 0.0f;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/raytracingreflections/miss.rmiss.spv b/external/Vulkan/data/shaders/hlsl/raytracingreflections/miss.rmiss.spv
new file mode 100644
index 0000000000000000000000000000000000000000..959cfebcb4abe489d520d80b0f6936b3a75cf87b
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/raytracingreflections/miss.rmiss.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/raytracingreflections/raygen.rgen b/external/Vulkan/data/shaders/hlsl/raytracingreflections/raygen.rgen
new file mode 100644
index 0000000000000000000000000000000000000000..23d2cea8df31aa583beabe442f1c0a21fa6cb26c
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/raytracingreflections/raygen.rgen
@@ -0,0 +1,64 @@
+// Copyright 2020 Google LLC
+
+RaytracingAccelerationStructure rs : register(t0);
+RWTexture2D<float4> image : register(u1);
+
+struct CameraProperties
+{
+	float4x4 viewInverse;
+	float4x4 projInverse;
+	float4 lightPos;
+};
+cbuffer cam : register(b2) { CameraProperties cam; };
+
+
+struct RayPayload {
+	float3 color;
+	float distance;
+	float3 normal;
+	float reflector;
+};
+
+// Max. number of recursion is passed via a specialization constant
+[[vk::constant_id(0)]] const int MAX_RECURSION = 0;
+
+[shader("raygeneration")]
+void main()
+{
+	uint3 LaunchID = DispatchRaysIndex();
+	uint3 LaunchSize = DispatchRaysDimensions();
+
+	const float2 pixelCenter = float2(LaunchID.xy) + float2(0.5, 0.5);
+	const float2 inUV = pixelCenter/float2(LaunchSize.xy);
+	float2 d = inUV * 2.0 - 1.0;
+	float4 target = mul(cam.projInverse, float4(d.x, d.y, 1, 1));
+
+	RayDesc rayDesc;
+	rayDesc.Origin = mul(cam.viewInverse, float4(0,0,0,1)).xyz;
+	rayDesc.Direction = mul(cam.viewInverse, float4(normalize(target.xyz), 0)).xyz;
+	rayDesc.TMin = 0.001;
+	rayDesc.TMax = 10000.0;
+
+	float3 color = float3(0.0, 0.0, 0.0);
+
+	for (int i = 0; i < MAX_RECURSION; i++) {
+		RayPayload rayPayload;
+		TraceRay(rs, RAY_FLAG_FORCE_OPAQUE, 0xff, 0, 0, 0, rayDesc, rayPayload);
+		float3 hitColor = rayPayload.color;
+
+		if (rayPayload.distance < 0.0f) {
+			color += hitColor;
+			break;
+		} else if (rayPayload.reflector == 1.0f) {
+			const float3 hitPos = rayDesc.Origin + rayDesc.Direction * rayPayload.distance;
+			rayDesc.Origin = hitPos + rayPayload.normal * 0.001f;
+			rayDesc.Direction = reflect(rayDesc.Direction, rayPayload.normal);
+		} else {
+			color += hitColor;
+			break;
+		}
+
+	}
+
+	image[int2(LaunchID.xy)] = float4(color, 0.0);
+}
diff --git a/external/Vulkan/data/shaders/hlsl/raytracingreflections/raygen.rgen.spv b/external/Vulkan/data/shaders/hlsl/raytracingreflections/raygen.rgen.spv
new file mode 100644
index 0000000000000000000000000000000000000000..d95bddeb80c96b9e351642d51ea465d0ca6e8388
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/raytracingreflections/raygen.rgen.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/raytracingshadows/closesthit.rchit b/external/Vulkan/data/shaders/hlsl/raytracingshadows/closesthit.rchit
new file mode 100644
index 0000000000000000000000000000000000000000..4408f55cb326aa9ce645234ef050910dc0aff19d
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/raytracingshadows/closesthit.rchit
@@ -0,0 +1,85 @@
+// Copyright 2020 Google LLC
+
+struct InPayload
+{
+	[[vk::location(0)]] float3 hitValue;
+};
+
+struct InOutPayload
+{
+	[[vk::location(2)]] bool shadowed;
+};
+
+RaytracingAccelerationStructure topLevelAS : register(t0);
+struct UBO
+{
+	float4x4 viewInverse;
+	float4x4 projInverse;
+	float4 lightPos;
+	int vertexSize;
+};
+cbuffer ubo : register(b2) { UBO ubo; };
+
+StructuredBuffer<float4> vertices : register(t3);
+StructuredBuffer<uint> indices : register(t4);
+
+struct Vertex
+{
+  float3 pos;
+  float3 normal;
+  float2 uv;
+  float4 color;
+  float4 _pad0; 
+  float4 _pad1;
+};
+
+Vertex unpack(uint index)
+{
+	// Unpack the vertices from the SSBO using the glTF vertex structure
+	// The multiplier is the size of the vertex divided by four float components (=16 bytes)
+	const int m = ubo.vertexSize / 16;
+
+	float4 d0 = vertices[m * index + 0];
+	float4 d1 = vertices[m * index + 1];
+	float4 d2 = vertices[m * index + 2];
+
+	Vertex v;
+	v.pos = d0.xyz;
+	v.normal = float3(d0.w, d1.x, d1.y);
+	v.color = float4(d2.x, d2.y, d2.z, 1.0);
+
+	return v;
+}
+
+[shader("closesthit")]
+void main(in InPayload inPayload, inout InOutPayload inOutPayload, in float2 attribs)
+{
+	uint PrimitiveID = PrimitiveIndex();
+	int3 index = int3(indices[3 * PrimitiveID], indices[3 * PrimitiveID + 1], indices[3 * PrimitiveID + 2]);
+
+	Vertex v0 = unpack(index.x);
+	Vertex v1 = unpack(index.y);
+	Vertex v2 = unpack(index.z);
+
+	// Interpolate normal
+	const float3 barycentricCoords = float3(1.0f - attribs.x - attribs.y, attribs.x, attribs.y);
+	float3 normal = normalize(v0.normal * barycentricCoords.x + v1.normal * barycentricCoords.y + v2.normal * barycentricCoords.z);
+
+	// Basic lighting
+	float3 lightVector = normalize(ubo.lightPos.xyz);
+	float dot_product = max(dot(lightVector, normal), 0.2);
+	inPayload.hitValue = v0.color.rgb * dot_product;
+
+	RayDesc rayDesc;
+	rayDesc.Origin = WorldRayOrigin() + WorldRayDirection() * RayTCurrent();
+	rayDesc.Direction = lightVector;
+	rayDesc.TMin = 0.001;
+	rayDesc.TMax = 100.0;
+
+	inOutPayload.shadowed = true;
+	// Offset indices to match shadow hit/miss index
+	TraceRay(topLevelAS, RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH | RAY_FLAG_FORCE_OPAQUE | RAY_FLAG_SKIP_CLOSEST_HIT_SHADER, 0xff, 1, 0, 1, rayDesc, inOutPayload);
+	if (inOutPayload.shadowed) {
+		inPayload.hitValue *= 0.3;
+	}
+}
diff --git a/external/Vulkan/data/shaders/hlsl/raytracingshadows/closesthit.rchit.spv b/external/Vulkan/data/shaders/hlsl/raytracingshadows/closesthit.rchit.spv
new file mode 100644
index 0000000000000000000000000000000000000000..225df9759e12c492e9da6f50a7fcc3af18931b67
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/raytracingshadows/closesthit.rchit.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/raytracingshadows/miss.rmiss b/external/Vulkan/data/shaders/hlsl/raytracingshadows/miss.rmiss
new file mode 100644
index 0000000000000000000000000000000000000000..b142967a25254783e17cfce4259ebd7362c1d49c
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/raytracingshadows/miss.rmiss
@@ -0,0 +1,7 @@
+// Copyright 2020 Google LLC
+
+[shader("miss")]
+void main([[vk::location(0)]] in float3 hitValue)
+{
+    hitValue = float3(0.0, 0.0, 0.2);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/raytracingshadows/miss.rmiss.spv b/external/Vulkan/data/shaders/hlsl/raytracingshadows/miss.rmiss.spv
new file mode 100644
index 0000000000000000000000000000000000000000..fbbb81219decaa52c8abc45cd3c487e50cca65c5
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/raytracingshadows/miss.rmiss.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/raytracingshadows/raygen.rgen b/external/Vulkan/data/shaders/hlsl/raytracingshadows/raygen.rgen
new file mode 100644
index 0000000000000000000000000000000000000000..0ccf0e4406f57153d0eff0089c1bd6946e9f8452
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/raytracingshadows/raygen.rgen
@@ -0,0 +1,40 @@
+// Copyright 2020 Google LLC
+
+RaytracingAccelerationStructure rs : register(t0);
+RWTexture2D<float4> image : register(u1);
+
+struct CameraProperties
+{
+	float4x4 viewInverse;
+	float4x4 projInverse;
+	float4 lightPos;
+};
+cbuffer cam : register(b2) { CameraProperties cam; };
+
+struct Payload
+{
+	[[vk::location(0)]] float3 hitValue;
+};
+
+[shader("raygeneration")]
+void main()
+{
+	uint3 LaunchID = DispatchRaysIndex();
+	uint3 LaunchSize = DispatchRaysDimensions();
+
+	const float2 pixelCenter = float2(LaunchID.xy) + float2(0.5, 0.5);
+	const float2 inUV = pixelCenter/float2(LaunchSize.xy);
+	float2 d = inUV * 2.0 - 1.0;
+	float4 target = mul(cam.projInverse, float4(d.x, d.y, 1, 1));
+
+	RayDesc rayDesc;
+	rayDesc.Origin = mul(cam.viewInverse, float4(0,0,0,1)).xyz;
+	rayDesc.Direction = mul(cam.viewInverse, float4(normalize(target.xyz), 0)).xyz;
+	rayDesc.TMin = 0.001;
+	rayDesc.TMax = 10000.0;
+
+	Payload payload;
+	TraceRay(rs, RAY_FLAG_FORCE_OPAQUE, 0xff, 0, 0, 0, rayDesc, payload);
+
+	image[int2(LaunchID.xy)] = float4(payload.hitValue, 0.0);
+}
diff --git a/external/Vulkan/data/shaders/hlsl/raytracingshadows/raygen.rgen.spv b/external/Vulkan/data/shaders/hlsl/raytracingshadows/raygen.rgen.spv
new file mode 100644
index 0000000000000000000000000000000000000000..ffc2f8a8315e86272a6919a7b26fa24efb9b8bc5
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/raytracingshadows/raygen.rgen.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/raytracingshadows/shadow.rmiss b/external/Vulkan/data/shaders/hlsl/raytracingshadows/shadow.rmiss
new file mode 100644
index 0000000000000000000000000000000000000000..7a104b6bcff7db8848d7724e444357da7998b050
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/raytracingshadows/shadow.rmiss
@@ -0,0 +1,7 @@
+// Copyright 2020 Google LLC
+
+[shader("miss")]
+void main([[vk::location(2)]] in bool shadowed)
+{
+	shadowed = false;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/raytracingshadows/shadow.rmiss.spv b/external/Vulkan/data/shaders/hlsl/raytracingshadows/shadow.rmiss.spv
new file mode 100644
index 0000000000000000000000000000000000000000..5d37172695adebda427913a5b6ff2416bca6ff6b
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/raytracingshadows/shadow.rmiss.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/renderheadless/triangle.frag b/external/Vulkan/data/shaders/hlsl/renderheadless/triangle.frag
new file mode 100644
index 0000000000000000000000000000000000000000..8a7faf06abc734ae0d82ddcf894b2fc7a77eb795
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/renderheadless/triangle.frag
@@ -0,0 +1,6 @@
+// Copyright 2020 Google LLC
+
+float4 main([[vk::location(0)]] float3 Color : COLOR0) : SV_TARGET
+{
+  return float4(Color, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/renderheadless/triangle.frag.spv b/external/Vulkan/data/shaders/hlsl/renderheadless/triangle.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..870fb6c12ac71dca568d8358a955b81f6eca7faf
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/renderheadless/triangle.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/renderheadless/triangle.vert b/external/Vulkan/data/shaders/hlsl/renderheadless/triangle.vert
new file mode 100644
index 0000000000000000000000000000000000000000..6e5c220798b0c8b954890d808757cb6e6dc66a4c
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/renderheadless/triangle.vert
@@ -0,0 +1,26 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float3 Color : COLOR0;
+};
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Color : COLOR0;
+};
+
+struct PushConsts {
+	float4x4 mvp;
+};
+[[vk::push_constant]] PushConsts pushConsts;
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Color = input.Color;
+	output.Pos = mul(pushConsts.mvp, float4(input.Pos.xyz, 1.0));
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/renderheadless/triangle.vert.spv b/external/Vulkan/data/shaders/hlsl/renderheadless/triangle.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..8598544fcf110e5c7ed6416c0477a5d7730f003f
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/renderheadless/triangle.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/screenshot/mesh.frag b/external/Vulkan/data/shaders/hlsl/screenshot/mesh.frag
new file mode 100644
index 0000000000000000000000000000000000000000..2c14be89d22b2ad92388e59a39d4d05f86f0658c
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/screenshot/mesh.frag
@@ -0,0 +1,21 @@
+// Copyright 2020 Google LLC
+
+struct VSOutput
+{
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(3)]] float3 LightVec : TEXCOORD2;
+};
+
+float4 main(VSOutput input) : SV_TARGET
+{
+	float3 N = normalize(input.Normal);
+	float3 L = normalize(input.LightVec);
+	float3 V = normalize(input.ViewVec);
+	float3 R = reflect(-L, N);
+	float3 ambient = float3(0.1, 0.1, 0.1);
+	float3 diffuse = max(dot(N, L), 0.0) * float3(1.0, 1.0, 1.0);
+	float3 specular = pow(max(dot(R, V), 0.0), 16.0) * float3(0.75, 0.75, 0.75);
+	return float4((ambient + diffuse) * input.Color.rgb + specular, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/screenshot/mesh.frag.spv b/external/Vulkan/data/shaders/hlsl/screenshot/mesh.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..6966e9ad87589fb7abd6317ca898f0e8a94a36b1
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/screenshot/mesh.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/screenshot/mesh.vert b/external/Vulkan/data/shaders/hlsl/screenshot/mesh.vert
new file mode 100644
index 0000000000000000000000000000000000000000..38f9bc7944b4518fe2e9eed897818368e68d26e6
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/screenshot/mesh.vert
@@ -0,0 +1,41 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float4 Pos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float3 Color : COLOR0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 view;
+	float4x4 model;
+};
+cbuffer ubo : register(b0) { UBO ubo; };
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(3)]] float3 LightVec : TEXCOORD2;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Normal = input.Normal;
+	output.Color = input.Color;
+	output.Pos = mul(ubo.projection, mul(ubo.view, mul(ubo.model, input.Pos)));
+
+	float4 pos = mul(ubo.view, mul(ubo.model, float4(input.Pos.xyz, 1.0)));
+	output.Normal = mul((float3x3)ubo.model, input.Normal);
+
+	float3 lightPos = float3(1.0f, -1.0f, 1.0f);
+	output.LightVec = lightPos.xyz - pos.xyz;
+	output.ViewVec = -pos.xyz;
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/screenshot/mesh.vert.spv b/external/Vulkan/data/shaders/hlsl/screenshot/mesh.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..76f9ef3d547c77202c878d5575698465be35a554
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/screenshot/mesh.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/shadowmapping/offscreen.frag b/external/Vulkan/data/shaders/hlsl/shadowmapping/offscreen.frag
new file mode 100644
index 0000000000000000000000000000000000000000..54d4ac241b9ef84a709054857d74c96549b5233a
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/shadowmapping/offscreen.frag
@@ -0,0 +1,6 @@
+// Copyright 2020 Google LLC
+
+float4 main() : SV_TARGET
+{
+	return float4(1.0, 0.0, 0.0, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/shadowmapping/offscreen.frag.spv b/external/Vulkan/data/shaders/hlsl/shadowmapping/offscreen.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..199649f35fb9f146c7c92ea4bc0edb23ae68eb13
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/shadowmapping/offscreen.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/shadowmapping/offscreen.vert b/external/Vulkan/data/shaders/hlsl/shadowmapping/offscreen.vert
new file mode 100644
index 0000000000000000000000000000000000000000..b20b1d1f13891081568b89ec9c04bcf1db2d960e
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/shadowmapping/offscreen.vert
@@ -0,0 +1,13 @@
+// Copyright 2020 Google LLC
+
+struct UBO
+{
+	float4x4 depthMVP;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+float4 main([[vk::location(0)]] float3 Pos : POSITION0) : SV_POSITION
+{
+	return mul(ubo.depthMVP, float4(Pos, 1.0));
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/shadowmapping/offscreen.vert.spv b/external/Vulkan/data/shaders/hlsl/shadowmapping/offscreen.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..886d2353cd9b91df1b4e21cb2dc320f96d7b44a9
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/shadowmapping/offscreen.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/shadowmapping/quad.frag b/external/Vulkan/data/shaders/hlsl/shadowmapping/quad.frag
new file mode 100644
index 0000000000000000000000000000000000000000..fb90f429cff3e829baececab8ecd907e918f138b
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/shadowmapping/quad.frag
@@ -0,0 +1,18 @@
+// Copyright 2020 Google LLC
+
+Texture2D textureColor : register(t1);
+SamplerState samplerColor : register(s1);
+
+float LinearizeDepth(float depth)
+{
+  float n = 1.0; // camera z near
+  float f = 128.0; // camera z far
+  float z = depth;
+  return (2.0 * n) / (f + n - z * (f - n));
+}
+
+float4 main([[vk::location(0)]] float2 inUV : TEXCOORD0) : SV_TARGET
+{
+	float depth = textureColor.Sample(samplerColor, inUV).r;
+	return float4((1.0-LinearizeDepth(depth)).xxx, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/shadowmapping/quad.frag.spv b/external/Vulkan/data/shaders/hlsl/shadowmapping/quad.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..10e04bc33ac09f5fd919936584e46cadccc68478
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/shadowmapping/quad.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/shadowmapping/quad.vert b/external/Vulkan/data/shaders/hlsl/shadowmapping/quad.vert
new file mode 100644
index 0000000000000000000000000000000000000000..b13c2bf2353ce1fc1837fef4db32336429c22412
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/shadowmapping/quad.vert
@@ -0,0 +1,15 @@
+// Copyright 2020 Google LLC
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float2 UV : TEXCOORD0;
+};
+
+VSOutput main(uint VertexIndex : SV_VertexID)
+{
+	VSOutput output = (VSOutput)0;
+	output.UV = float2((VertexIndex << 1) & 2, VertexIndex & 2);
+	output.Pos = float4(output.UV * 2.0f - 1.0f, 0.0f, 1.0f);
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/shadowmapping/quad.vert.spv b/external/Vulkan/data/shaders/hlsl/shadowmapping/quad.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..8754eaa14270ad09ba3893fff6aa51f5732ff9c9
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/shadowmapping/quad.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/shadowmapping/scene.frag b/external/Vulkan/data/shaders/hlsl/shadowmapping/scene.frag
new file mode 100644
index 0000000000000000000000000000000000000000..5ae6167d9a486efc950bb81b59be7d41355ba077
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/shadowmapping/scene.frag
@@ -0,0 +1,68 @@
+// Copyright 2020 Google LLC
+
+Texture2D shadowMapTexture : register(t1);
+SamplerState shadowMapSampler : register(s1);
+
+struct VSOutput
+{
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(3)]] float3 LightVec : TEXCOORD2;
+[[vk::location(4)]] float4 ShadowCoord : TEXCOORD3;
+};
+
+[[vk::constant_id(0)]] const int enablePCF = 0;
+
+#define ambient 0.1
+
+float textureProj(float4 shadowCoord, float2 off)
+{
+	float shadow = 1.0;
+	if ( shadowCoord.z > -1.0 && shadowCoord.z < 1.0 )
+	{
+		float dist = shadowMapTexture.Sample( shadowMapSampler, shadowCoord.xy + off ).r;
+		if ( shadowCoord.w > 0.0 && dist < shadowCoord.z )
+		{
+			shadow = ambient;
+		}
+	}
+	return shadow;
+}
+
+float filterPCF(float4 sc)
+{
+	int2 texDim;
+	shadowMapTexture.GetDimensions(texDim.x, texDim.y);
+	float scale = 1.5;
+	float dx = scale * 1.0 / float(texDim.x);
+	float dy = scale * 1.0 / float(texDim.y);
+
+	float shadowFactor = 0.0;
+	int count = 0;
+	int range = 1;
+
+	for (int x = -range; x <= range; x++)
+	{
+		for (int y = -range; y <= range; y++)
+		{
+			shadowFactor += textureProj(sc, float2(dx*x, dy*y));
+			count++;
+		}
+
+	}
+	return shadowFactor / count;
+}
+
+float4 main(VSOutput input) : SV_TARGET
+{
+	float shadow = (enablePCF == 1) ? filterPCF(input.ShadowCoord / input.ShadowCoord.w) : textureProj(input.ShadowCoord / input.ShadowCoord.w, float2(0.0, 0.0));
+
+	float3 N = normalize(input.Normal);
+	float3 L = normalize(input.LightVec);
+	float3 V = normalize(input.ViewVec);
+	float3 R = normalize(-reflect(L, N));
+	float3 diffuse = max(dot(N, L), ambient) * input.Color;
+
+	return float4(diffuse * shadow, 1.0);
+}
diff --git a/external/Vulkan/data/shaders/hlsl/shadowmapping/scene.frag.spv b/external/Vulkan/data/shaders/hlsl/shadowmapping/scene.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..db20337facf441bd15f5c9dc32cd34e70a72c470
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/shadowmapping/scene.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/shadowmapping/scene.vert b/external/Vulkan/data/shaders/hlsl/shadowmapping/scene.vert
new file mode 100644
index 0000000000000000000000000000000000000000..583b6a8e107cbcb65061239db0d6df80836aea10
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/shadowmapping/scene.vert
@@ -0,0 +1,54 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+[[vk::location(2)]] float3 Color : COLOR0;
+[[vk::location(3)]] float3 Normal : NORMAL0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 view;
+	float4x4 model;
+	float4x4 lightSpace;
+	float3 lightPos;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(3)]] float3 LightVec : TEXCOORD2;
+[[vk::location(4)]] float4 ShadowCoord : TEXCOORD3;
+};
+
+static const float4x4 biasMat = float4x4(
+	0.5, 0.0, 0.0, 0.5,
+	0.0, 0.5, 0.0, 0.5,
+	0.0, 0.0, 1.0, 0.0,
+	0.0, 0.0, 0.0, 1.0 );
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Color = input.Color;
+	output.Normal = input.Normal;
+
+	output.Pos = mul(ubo.projection, mul(ubo.view, mul(ubo.model, float4(input.Pos.xyz, 1.0))));
+
+    float4 pos = mul(ubo.model, float4(input.Pos, 1.0));
+    output.Normal = mul((float3x3)ubo.model, input.Normal);
+    output.LightVec = normalize(ubo.lightPos - input.Pos);
+    output.ViewVec = -pos.xyz;
+
+	output.ShadowCoord = mul(biasMat, mul(ubo.lightSpace, mul(ubo.model, float4(input.Pos, 1.0))));
+	return output;
+}
+
diff --git a/external/Vulkan/data/shaders/hlsl/shadowmapping/scene.vert.spv b/external/Vulkan/data/shaders/hlsl/shadowmapping/scene.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..5ca9d59db5fedbd488d2db54a294c05f0e333aea
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/shadowmapping/scene.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/shadowmappingcascade/debugshadowmap.frag b/external/Vulkan/data/shaders/hlsl/shadowmappingcascade/debugshadowmap.frag
new file mode 100644
index 0000000000000000000000000000000000000000..3287180432dc13f4fb20e0d1a5bf56afb9c9bb79
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/shadowmappingcascade/debugshadowmap.frag
@@ -0,0 +1,16 @@
+// Copyright 2020 Google LLC
+
+Texture2DArray shadowMapTexture : register(t1);
+SamplerState shadowMapSampler : register(s1);
+
+struct VSOutput
+{
+[[vk::location(0)]] float2 UV : TEXCOORD0;
+[[vk::location(1)]] uint CascadeIndex : TEXCOORD1;
+};
+
+float4 main(VSOutput input) : SV_TARGET
+{
+	float depth = shadowMapTexture.Sample(shadowMapSampler, float3(input.UV, float(input.CascadeIndex))).r;
+	return float4(depth.xxx, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/shadowmappingcascade/debugshadowmap.frag.spv b/external/Vulkan/data/shaders/hlsl/shadowmappingcascade/debugshadowmap.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..25a86e551c87d0e47ee27118d3c913457bd3d14b
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/shadowmappingcascade/debugshadowmap.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/shadowmappingcascade/debugshadowmap.vert b/external/Vulkan/data/shaders/hlsl/shadowmappingcascade/debugshadowmap.vert
new file mode 100644
index 0000000000000000000000000000000000000000..6e8ae835974bd9a0842fa85e9c3311d302c14936
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/shadowmappingcascade/debugshadowmap.vert
@@ -0,0 +1,23 @@
+// Copyright 2020 Google LLC
+
+struct PushConsts {
+	float4 position;
+	uint cascadeIndex;
+};
+[[vk::push_constant]] PushConsts pushConsts;
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float2 UV : TEXCOORD0;
+[[vk::location(1)]] uint CascadeIndex : TEXCOORD1;
+};
+
+VSOutput main(uint VertexIndex : SV_VertexID)
+{
+	VSOutput output = (VSOutput)0;
+	output.UV = float2((VertexIndex << 1) & 2, VertexIndex & 2);
+	output.CascadeIndex = pushConsts.cascadeIndex;
+	output.Pos = float4(output.UV * 2.0f - 1.0f, 0.0f, 1.0f);
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/shadowmappingcascade/debugshadowmap.vert.spv b/external/Vulkan/data/shaders/hlsl/shadowmappingcascade/debugshadowmap.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..6d67026bc7025bfdc425983556ec3fc845c1a34c
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/shadowmappingcascade/debugshadowmap.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/shadowmappingcascade/depthpass.frag b/external/Vulkan/data/shaders/hlsl/shadowmappingcascade/depthpass.frag
new file mode 100644
index 0000000000000000000000000000000000000000..4393920017f4366460acffa1fd2222e20dedfc92
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/shadowmappingcascade/depthpass.frag
@@ -0,0 +1,12 @@
+// Copyright 2020 Google LLC
+
+Texture2D colorMapTexture : register(t0, space1);
+SamplerState colorMapSampler : register(s0, space1);
+
+void main([[vk::location(0)]] float2 inUV : TEXCOORD0)
+{
+	float alpha = colorMapTexture.Sample(colorMapSampler, inUV).a;
+	if (alpha < 0.5) {
+		clip(-1);
+	}
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/shadowmappingcascade/depthpass.frag.spv b/external/Vulkan/data/shaders/hlsl/shadowmappingcascade/depthpass.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..ac6f294189f7616b4e8102c89a9eed883cbfa45e
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/shadowmappingcascade/depthpass.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/shadowmappingcascade/depthpass.vert b/external/Vulkan/data/shaders/hlsl/shadowmappingcascade/depthpass.vert
new file mode 100644
index 0000000000000000000000000000000000000000..78e70e5b38a0e788232e8d5cf323e82eb1fa39bc
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/shadowmappingcascade/depthpass.vert
@@ -0,0 +1,36 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+};
+// todo: pass via specialization constant
+#define SHADOW_MAP_CASCADE_COUNT 4
+
+struct PushConsts {
+	float4 position;
+	uint cascadeIndex;
+};
+[[vk::push_constant]] PushConsts pushConsts;
+
+struct UBO  {
+	float4x4 cascadeViewProjMat[SHADOW_MAP_CASCADE_COUNT];
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float2 UV : TEXCOORD0;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.UV = input.UV;
+	float3 pos = input.Pos + pushConsts.position.xyz;
+	output.Pos = mul(ubo.cascadeViewProjMat[pushConsts.cascadeIndex], float4(pos, 1.0));
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/shadowmappingcascade/depthpass.vert.spv b/external/Vulkan/data/shaders/hlsl/shadowmappingcascade/depthpass.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..de1756812400e08f17bf234787e2ff2a7dc96c67
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/shadowmappingcascade/depthpass.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/shadowmappingcascade/scene.frag b/external/Vulkan/data/shaders/hlsl/shadowmappingcascade/scene.frag
new file mode 100644
index 0000000000000000000000000000000000000000..6a7388f442d0c5c4651180b2f4a34a89b720d238
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/shadowmappingcascade/scene.frag
@@ -0,0 +1,131 @@
+// Copyright 2020 Google LLC
+
+#define SHADOW_MAP_CASCADE_COUNT 4
+
+Texture2DArray shadowMapTexture : register(t1);
+SamplerState shadowMapSampler : register(s1);
+Texture2D colorMapTexture : register(t0, space1);
+SamplerState colorMapSampler : register(s0, space1);
+
+struct VSOutput
+{
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float3 ViewPos : POSITION1;
+[[vk::location(3)]] float3 Pos : POSITION0;
+[[vk::location(4)]] float2 UV : TEXCOORD0;
+};
+
+[[vk::constant_id(0)]] const int enablePCF = 0;
+
+#define ambient 0.3
+
+struct UBO {
+	float4 cascadeSplits;
+	float4x4 cascadeViewProjMat[SHADOW_MAP_CASCADE_COUNT];
+	float4x4 inverseViewMat;
+	float3 lightDir;
+	float _pad;
+	int colorCascades;
+};
+cbuffer ubo : register(b2) { UBO ubo; };
+
+static const float4x4 biasMat = float4x4(
+	0.5, 0.0, 0.0, 0.5,
+	0.0, 0.5, 0.0, 0.5,
+	0.0, 0.0, 1.0, 0.0,
+	0.0, 0.0, 0.0, 1.0
+);
+
+float textureProj(float4 shadowCoord, float2 offset, uint cascadeIndex)
+{
+	float shadow = 1.0;
+	float bias = 0.005;
+
+	if ( shadowCoord.z > -1.0 && shadowCoord.z < 1.0 ) {
+		float dist = shadowMapTexture.Sample(shadowMapSampler, float3(shadowCoord.xy + offset, cascadeIndex)).r;
+		if (shadowCoord.w > 0 && dist < shadowCoord.z - bias) {
+			shadow = ambient;
+		}
+	}
+	return shadow;
+
+}
+
+float filterPCF(float4 sc, uint cascadeIndex)
+{
+	int3 texDim;
+	shadowMapTexture.GetDimensions(texDim.x, texDim.y, texDim.z);
+	float scale = 0.75;
+	float dx = scale * 1.0 / float(texDim.x);
+	float dy = scale * 1.0 / float(texDim.y);
+
+	float shadowFactor = 0.0;
+	int count = 0;
+	int range = 1;
+
+	for (int x = -range; x <= range; x++) {
+		for (int y = -range; y <= range; y++) {
+			shadowFactor += textureProj(sc, float2(dx*x, dy*y), cascadeIndex);
+			count++;
+		}
+	}
+	return shadowFactor / count;
+}
+
+float4 main(VSOutput input) : SV_TARGET
+{
+	float4 outFragColor;
+	float4 color = colorMapTexture.Sample(colorMapSampler, input.UV);
+	if (color.a < 0.5) {
+		clip(-1);
+	}
+
+	// Get cascade index for the current fragment's view position
+	uint cascadeIndex = 0;
+	for(uint i = 0; i < SHADOW_MAP_CASCADE_COUNT - 1; ++i) {
+		if(input.ViewPos.z < ubo.cascadeSplits[i]) {
+			cascadeIndex = i + 1;
+		}
+	}
+
+	// Depth compare for shadowing
+	float4 shadowCoord = mul(biasMat, mul(ubo.cascadeViewProjMat[cascadeIndex], float4(input.Pos, 1.0)));
+
+	float shadow = 0;
+	if (enablePCF == 1) {
+		shadow = filterPCF(shadowCoord / shadowCoord.w, cascadeIndex);
+	} else {
+		shadow = textureProj(shadowCoord / shadowCoord.w, float2(0.0, 0.0), cascadeIndex);
+	}
+
+	// Directional light
+	float3 N = normalize(input.Normal);
+	float3 L = normalize(-ubo.lightDir);
+	float3 H = normalize(L + input.ViewPos);
+	float diffuse = max(dot(N, L), ambient);
+	float3 lightColor = float3(1.0, 1.0, 1.0);
+	outFragColor.rgb = max(lightColor * (diffuse * color.rgb), float3(0.0, 0.0, 0.0));
+	outFragColor.rgb *= shadow;
+	outFragColor.a = color.a;
+
+	// Color cascades (if enabled)
+	if (ubo.colorCascades == 1) {
+		switch(cascadeIndex) {
+			case 0 :
+				outFragColor.rgb *= float3(1.0f, 0.25f, 0.25f);
+				break;
+			case 1 :
+				outFragColor.rgb *= float3(0.25f, 1.0f, 0.25f);
+				break;
+			case 2 :
+				outFragColor.rgb *= float3(0.25f, 0.25f, 1.0f);
+				break;
+			case 3 :
+				outFragColor.rgb *= float3(1.0f, 1.0f, 0.25f);
+				break;
+		}
+	}
+
+	return outFragColor;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/shadowmappingcascade/scene.frag.spv b/external/Vulkan/data/shaders/hlsl/shadowmappingcascade/scene.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..38cb207c55b25bac53b11a4e9ed59201dd7e16a9
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/shadowmappingcascade/scene.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/shadowmappingcascade/scene.vert b/external/Vulkan/data/shaders/hlsl/shadowmappingcascade/scene.vert
new file mode 100644
index 0000000000000000000000000000000000000000..957845b223d3f1c7095375b34333e79bb2e59583
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/shadowmappingcascade/scene.vert
@@ -0,0 +1,47 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+[[vk::location(2)]] float3 Color : COLOR0;
+[[vk::location(3)]] float3 Normal : NORMAL0;
+};
+
+struct UBO  {
+	float4x4 projection;
+	float4x4 view;
+	float4x4 model;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float3 ViewPos : POSITION1;
+[[vk::location(3)]] float3 WorldPos : POSITION0;
+[[vk::location(4)]] float2 UV : TEXCOORD0;
+};
+
+struct PushConsts {
+	float4 position;
+	uint cascadeIndex;
+};
+[[vk::push_constant]] PushConsts pushConsts;
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Color = input.Color;
+	output.Normal = input.Normal;
+	output.UV = input.UV;
+	float3 pos = input.Pos + pushConsts.position.xyz;
+	output.WorldPos = pos;
+	output.ViewPos = mul(ubo.view, float4(pos.xyz, 1.0)).xyz;
+	output.Pos = mul(ubo.projection, mul(ubo.view, mul(ubo.model, float4(pos.xyz, 1.0))));
+	return output;
+}
+
diff --git a/external/Vulkan/data/shaders/hlsl/shadowmappingcascade/scene.vert.spv b/external/Vulkan/data/shaders/hlsl/shadowmappingcascade/scene.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..d3fdc479cc7857a7ead86f105ecc7dec1355dea6
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/shadowmappingcascade/scene.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/shadowmappingomni/cubemapdisplay.frag b/external/Vulkan/data/shaders/hlsl/shadowmappingomni/cubemapdisplay.frag
new file mode 100644
index 0000000000000000000000000000000000000000..7e8bdc77dd91d7634b1dafee4a13ee5637e285c1
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/shadowmappingomni/cubemapdisplay.frag
@@ -0,0 +1,53 @@
+// Copyright 2020 Google LLC
+
+TextureCube shadowCubeMapTexture : register(t1);
+SamplerState shadowCubeMapSampler : register(s1);
+
+float4 main([[vk::location(0)]] float2 inUV : TEXCOORD0) : SV_TARGET
+{
+	float4 outFragColor = float4(0, 0, 0, 0);
+	outFragColor.rgb = float3(0.05, 0.05, 0.05);
+
+	float3 samplePos = float3(0, 0, 0);
+
+	// Crude statement to visualize different cube map faces based on UV coordinates
+	int x = int(floor(inUV.x / 0.25f));
+	int y = int(floor(inUV.y / (1.0 / 3.0)));
+	if (y == 1) {
+		float2 uv = float2(inUV.x * 4.0f, (inUV.y - 1.0/3.0) * 3.0);
+		uv = 2.0 * float2(uv.x - float(x) * 1.0, uv.y) - 1.0;
+		switch (x) {
+			case 0:	// NEGATIVE_X
+				samplePos = float3(-1.0f, uv.y, uv.x);
+				break;
+			case 1: // POSITIVE_Z
+				samplePos = float3(uv.x, uv.y, 1.0f);
+				break;
+			case 2: // POSITIVE_X
+				samplePos = float3(1.0, uv.y, -uv.x);
+				break;
+			case 3: // NEGATIVE_Z
+				samplePos = float3(-uv.x, uv.y, -1.0f);
+				break;
+		}
+	} else {
+		if (x == 1) {
+			float2 uv = float2((inUV.x - 0.25) * 4.0, (inUV.y - float(y) / 3.0) * 3.0);
+			uv = 2.0 * uv - 1.0;
+			switch (y) {
+				case 0: // NEGATIVE_Y
+					samplePos = float3(uv.x, -1.0f, uv.y);
+					break;
+				case 2: // POSITIVE_Y
+					samplePos = float3(uv.x, 1.0f, -uv.y);
+					break;
+			}
+		}
+	}
+
+	if ((samplePos.x != 0.0f) && (samplePos.y != 0.0f)) {
+		float dist = length(shadowCubeMapTexture.Sample(shadowCubeMapSampler, samplePos).xyz) * 0.005;
+		outFragColor = float4(dist.xxx, 1.0);
+	}
+	return outFragColor;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/shadowmappingomni/cubemapdisplay.frag.spv b/external/Vulkan/data/shaders/hlsl/shadowmappingomni/cubemapdisplay.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..c448af9bf1269a03b79dec54a4e12a47cb508e88
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/shadowmappingomni/cubemapdisplay.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/shadowmappingomni/cubemapdisplay.vert b/external/Vulkan/data/shaders/hlsl/shadowmappingomni/cubemapdisplay.vert
new file mode 100644
index 0000000000000000000000000000000000000000..41068e73d08a6ae70f1395881e138882abdcaf30
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/shadowmappingomni/cubemapdisplay.vert
@@ -0,0 +1,25 @@
+// Copyright 2020 Google LLC
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 view;
+	float4x4 model;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float2 UV : TEXCOORD0;
+};
+
+VSOutput main(uint VertexIndex : SV_VertexID)
+{
+	VSOutput output = (VSOutput)0;
+	output.UV = float2((VertexIndex << 1) & 2, VertexIndex & 2);
+	output.Pos = float4(output.UV.xy * 2.0f - 1.0f, 0.0f, 1.0f);
+	return output;
+}
+
diff --git a/external/Vulkan/data/shaders/hlsl/shadowmappingomni/cubemapdisplay.vert.spv b/external/Vulkan/data/shaders/hlsl/shadowmappingomni/cubemapdisplay.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..8754eaa14270ad09ba3893fff6aa51f5732ff9c9
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/shadowmappingomni/cubemapdisplay.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/shadowmappingomni/offscreen.frag b/external/Vulkan/data/shaders/hlsl/shadowmappingomni/offscreen.frag
new file mode 100644
index 0000000000000000000000000000000000000000..82c9fa9b1c04e7040cb4ff99e1c457f227468996
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/shadowmappingomni/offscreen.frag
@@ -0,0 +1,14 @@
+// Copyright 2020 Google LLC
+
+struct VSOutput
+{
+[[vk::location(0)]] float4 Pos : POSITION0;
+[[vk::location(1)]] float3 LightPos : POSITION1;
+};
+
+float main(VSOutput input) : SV_TARGET
+{
+	// Store distance to light as 32 bit float value
+    float3 lightVec = input.Pos.xyz - input.LightPos;
+    return length(lightVec);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/shadowmappingomni/offscreen.frag.spv b/external/Vulkan/data/shaders/hlsl/shadowmappingomni/offscreen.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..36577de8b811e2ea9320efaf4a1110f1d6aa5459
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/shadowmappingomni/offscreen.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/shadowmappingomni/offscreen.vert b/external/Vulkan/data/shaders/hlsl/shadowmappingomni/offscreen.vert
new file mode 100644
index 0000000000000000000000000000000000000000..3c50410494b557cddeb0bd089a2d364951dd69fd
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/shadowmappingomni/offscreen.vert
@@ -0,0 +1,34 @@
+// Copyright 2020 Google LLC
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float4 WorldPos : POSITION0;
+[[vk::location(1)]] float3 LightPos : POSITION1;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 view;
+	float4x4 model;
+	float4 lightPos;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct PushConsts
+{
+	float4x4 view;
+};
+[[vk::push_constant]] PushConsts pushConsts;
+
+VSOutput main([[vk::location(0)]] float3 Pos : POSITION0)
+{
+	VSOutput output = (VSOutput)0;
+	output.Pos = mul(ubo.projection, mul(pushConsts.view, mul(ubo.model, float4(Pos, 1.0))));
+
+	output.WorldPos = float4(Pos, 1.0);
+	output.LightPos = ubo.lightPos.xyz;
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/shadowmappingomni/offscreen.vert.spv b/external/Vulkan/data/shaders/hlsl/shadowmappingomni/offscreen.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..b6db5f0e4f7e892c43cd17f52e6995f276ab216c
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/shadowmappingomni/offscreen.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/shadowmappingomni/scene.frag b/external/Vulkan/data/shaders/hlsl/shadowmappingomni/scene.frag
new file mode 100644
index 0000000000000000000000000000000000000000..840d8298856eb180bc109ddcdd1607474ef9f67e
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/shadowmappingomni/scene.frag
@@ -0,0 +1,43 @@
+// Copyright 2020 Google LLC
+
+TextureCube shadowCubeMapTexture : register(t1);
+SamplerState shadowCubeMapSampler : register(s1);
+
+struct VSOutput
+{
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float3 EyePos : POSITION0;
+[[vk::location(3)]] float3 LightVec : TEXCOORD2;
+[[vk::location(4)]] float3 WorldPos : POSITION1;
+[[vk::location(5)]] float3 LightPos : POSITION2;
+};
+
+#define EPSILON 0.15
+#define SHADOW_OPACITY 0.5
+
+float4 main(VSOutput input) : SV_TARGET
+{
+	// Lighting
+	float3 N = normalize(input.Normal);
+	float3 L = normalize(float3(1.0, 1.0, 1.0));
+
+	float3 Eye = normalize(-input.EyePos);
+	float3 Reflected = normalize(reflect(-input.LightVec, input.Normal));
+
+	float4 IAmbient = float4(float3(0.05, 0.05, 0.05), 1.0);
+	float4 IDiffuse = float4(1.0, 1.0, 1.0, 1.0) * max(dot(input.Normal, input.LightVec), 0.0);
+
+	float4 outFragColor = float4(IAmbient + IDiffuse * float4(input.Color, 1.0));
+
+	// Shadow
+	float3 lightVec = input.WorldPos - input.LightPos;
+    float sampledDist = shadowCubeMapTexture.Sample(shadowCubeMapSampler, lightVec).r;
+    float dist = length(lightVec);
+
+	// Check if fragment is in shadow
+    float shadow = (dist <= sampledDist + EPSILON) ? 1.0 : SHADOW_OPACITY;
+
+	outFragColor.rgb *= shadow;
+	return outFragColor;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/shadowmappingomni/scene.frag.spv b/external/Vulkan/data/shaders/hlsl/shadowmappingomni/scene.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..19301a3ab8cb887314d97257cb53eed686085de5
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/shadowmappingomni/scene.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/shadowmappingomni/scene.vert b/external/Vulkan/data/shaders/hlsl/shadowmappingomni/scene.vert
new file mode 100644
index 0000000000000000000000000000000000000000..be1915a714cefe3c85cfcf3bf89892c8a89e7c54
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/shadowmappingomni/scene.vert
@@ -0,0 +1,45 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float3 Normal : NORMAL0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 view;
+	float4x4 model;
+	float4 lightPos;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float3 EyePos : POSITION0;
+[[vk::location(3)]] float3 LightVec : TEXCOORD2;
+[[vk::location(4)]] float3 WorldPos : POSITION1;
+[[vk::location(5)]] float3 LightPos : POSITION2;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Color = input.Color;
+	output.Normal = input.Normal;
+
+	output.Pos = mul(ubo.projection, mul(ubo.view, mul(ubo.model, float4(input.Pos.xyz, 1.0))));
+	output.EyePos = mul(ubo.model, float4(input.Pos, 1.0f)).xyz;
+	output.LightVec = normalize(ubo.lightPos.xyz - input.Pos.xyz);
+	output.WorldPos = input.Pos;
+
+	output.LightPos = ubo.lightPos.xyz;
+	return output;
+}
+
diff --git a/external/Vulkan/data/shaders/hlsl/shadowmappingomni/scene.vert.spv b/external/Vulkan/data/shaders/hlsl/shadowmappingomni/scene.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..58e5ddc889b98e4b016cb7d36504cc66f1914770
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/shadowmappingomni/scene.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/specializationconstants/uber.frag b/external/Vulkan/data/shaders/hlsl/specializationconstants/uber.frag
new file mode 100644
index 0000000000000000000000000000000000000000..57ac65e66a6a72116cb866dbdb74526bda45939a
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/specializationconstants/uber.frag
@@ -0,0 +1,73 @@
+// Copyright 2020 Google LLC
+
+Texture2D textureColormap : register(t1);
+SamplerState samplerColormap : register(s1);
+Texture2D textureDiscard : register(t2);
+SamplerState samplerDiscard : register(s2);
+
+struct VSOutput
+{
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float2 UV : TEXCOORD0;
+[[vk::location(3)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(4)]] float3 LightVec : TEXCOORD2;
+};
+
+// We use this constant to control the flow of the shader depending on the
+// lighting model selected at pipeline creation time
+[[vk::constant_id(0)]] const int LIGHTING_MODEL = 0;
+// Parameter for the toon shading part of the shader
+[[vk::constant_id(1)]] const /*float*/int PARAM_TOON_DESATURATION = 0.0f;
+
+float4 main(VSOutput input) : SV_TARGET
+{
+	switch (LIGHTING_MODEL) {
+		case 0: // Phong
+		{
+			float3 ambient = input.Color * float3(0.25, 0.25, 0.25);
+			float3 N = normalize(input.Normal);
+			float3 L = normalize(input.LightVec);
+			float3 V = normalize(input.ViewVec);
+			float3 R = reflect(-L, N);
+			float3 diffuse = max(dot(N, L), 0.0) * input.Color;
+			float3 specular = pow(max(dot(R, V), 0.0), 32.0) * float3(0.75, 0.75, 0.75);
+			return float4(ambient + diffuse * 1.75 + specular, 1.0);
+		}
+		case 1: // Toon
+		{
+
+			float3 N = normalize(input.Normal);
+			float3 L = normalize(input.LightVec);
+			float intensity = dot(N,L);
+			float3 color;
+			if (intensity > 0.98)
+				color = input.Color * 1.5;
+			else if  (intensity > 0.9)
+				color = input.Color * 1.0;
+			else if (intensity > 0.5)
+				color = input.Color * 0.6;
+			else if (intensity > 0.25)
+				color = input.Color * 0.4;
+			else
+				color = input.Color * 0.2;
+			// Desaturate a bit
+			color = float3(lerp(color, dot(float3(0.2126,0.7152,0.0722), color).xxx, asfloat(PARAM_TOON_DESATURATION)));
+			return float4(color, 1);
+		}
+		case 2: // Textured
+		{
+			float4 color = textureColormap.Sample(samplerColormap, input.UV).rrra;
+			float3 ambient = color.rgb * float3(0.25, 0.25, 0.25) * input.Color;
+			float3 N = normalize(input.Normal);
+			float3 L = normalize(input.LightVec);
+			float3 V = normalize(input.ViewVec);
+			float3 R = reflect(-L, N);
+			float3 diffuse = max(dot(N, L), 0.0) * color.rgb;
+			float specular = pow(max(dot(R, V), 0.0), 32.0) * color.a;
+			return float4(ambient + diffuse + specular.xxx, 1.0);
+		}
+	}
+
+	return float4(0, 0, 0, 0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/specializationconstants/uber.frag.spv b/external/Vulkan/data/shaders/hlsl/specializationconstants/uber.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..f864debadfc1cec31fcc39549deb216e0ca410e7
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/specializationconstants/uber.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/specializationconstants/uber.vert b/external/Vulkan/data/shaders/hlsl/specializationconstants/uber.vert
new file mode 100644
index 0000000000000000000000000000000000000000..2ea7b03b0fcf9e4a4582ecb250ebc8a47e2a6a6a
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/specializationconstants/uber.vert
@@ -0,0 +1,44 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float2 UV : TEXCOORD0;
+[[vk::location(3)]] float3 Color : COLOR0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+	float4 lightPos;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float2 UV : TEXCOORD0;
+[[vk::location(3)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(4)]] float3 LightVec : TEXCOORD2;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Normal = input.Normal;
+	output.Color = input.Color;
+	output.UV = input.UV;
+	output.Pos = mul(ubo.projection, mul(ubo.model, float4(input.Pos.xyz, 1.0)));
+
+	float4 pos = mul(ubo.model, float4(input.Pos, 1.0));
+	output.Normal = mul((float3x3)ubo.model, input.Normal);
+	float3 lPos = mul((float3x3)ubo.model, ubo.lightPos.xyz);
+	output.LightVec = lPos - pos.xyz;
+	output.ViewVec = -pos.xyz;
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/specializationconstants/uber.vert.spv b/external/Vulkan/data/shaders/hlsl/specializationconstants/uber.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..38769e287b907506b67c1ff7879ccd80085eeb66
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/specializationconstants/uber.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/sphericalenvmapping/sem.frag b/external/Vulkan/data/shaders/hlsl/sphericalenvmapping/sem.frag
new file mode 100644
index 0000000000000000000000000000000000000000..7bee1db67d4b0f7540fbdbc53d9ba3898092f92f
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/sphericalenvmapping/sem.frag
@@ -0,0 +1,21 @@
+// Copyright 2020 Google LLC
+
+Texture2DArray matCapTexture : register(t1);
+SamplerState matCapSampler : register(s1);
+
+struct VSOutput
+{
+[[vk::location(0)]] float3 Color : COLOR0;
+[[vk::location(1)]] float3 EyePos : POSITION0;
+[[vk::location(2)]] float3 Normal : NORMAL0;
+[[vk::location(3)]] int TexIndex : TEXCOORD1;
+};
+
+float4 main(VSOutput input) : SV_TARGET
+{
+	float3 r = reflect( input.EyePos, input.Normal );
+	float3 r2 = float3( r.x, r.y, r.z + 1.0 );
+	float m = 2.0 * length( r2 );
+	float2 vN = r.xy / m + .5;
+	return float4( matCapTexture.Sample( matCapSampler, float3(vN, input.TexIndex)).rgb * (clamp(input.Color.r * 2, 0.0, 1.0)), 1.0 );
+}
diff --git a/external/Vulkan/data/shaders/hlsl/sphericalenvmapping/sem.frag.spv b/external/Vulkan/data/shaders/hlsl/sphericalenvmapping/sem.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..a1bb8a4a50ffbd64652082e4f54253026dc59d04
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/sphericalenvmapping/sem.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/sphericalenvmapping/sem.vert b/external/Vulkan/data/shaders/hlsl/sphericalenvmapping/sem.vert
new file mode 100644
index 0000000000000000000000000000000000000000..5514c35b9f91796d700cf7d50bf03567c65b4461
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/sphericalenvmapping/sem.vert
@@ -0,0 +1,42 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float4 Pos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float3 Color : COLOR0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+	float4x4 normal;
+	float4x4 view;
+	int texIndex;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Color : COLOR0;
+[[vk::location(1)]] float3 EyePos : POSITION0;
+[[vk::location(2)]] float3 Normal : NORMAL0;
+[[vk::location(3)]] int TexIndex : TEXCOORD1;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Color = input.Color;
+	float4x4 modelView = mul(ubo.view, ubo.model);
+	output.EyePos = normalize( mul(modelView, input.Pos ).xyz );
+	output.TexIndex = ubo.texIndex;
+	output.Normal = normalize( mul((float3x3)ubo.normal, input.Normal) );
+	float3 r = reflect( output.EyePos, output.Normal );
+	float m = 2.0 * sqrt( pow(r.x, 2.0) + pow(r.y, 2.0) + pow(r.z + 1.0, 2.0));
+	output.Pos = mul(ubo.projection, mul(modelView, input.Pos));
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/sphericalenvmapping/sem.vert.spv b/external/Vulkan/data/shaders/hlsl/sphericalenvmapping/sem.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..313387a520ec1796463c744b6cc0f5d9908d1cf5
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/sphericalenvmapping/sem.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/ssao/blur.frag b/external/Vulkan/data/shaders/hlsl/ssao/blur.frag
new file mode 100644
index 0000000000000000000000000000000000000000..e370ddd6c7133b26b153ed0bd76d58e65fc88290
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/ssao/blur.frag
@@ -0,0 +1,24 @@
+// Copyright 2020 Google LLC
+
+Texture2D textureSSAO : register(t0);
+SamplerState samplerSSAO : register(s0);
+
+float4 main([[vk::location(0)]] float2 inUV : TEXCOORD0) : SV_TARGET
+{
+	const int blurRange = 2;
+	int n = 0;
+	int2 texDim;
+	textureSSAO.GetDimensions(texDim.x, texDim.y);
+	float2 texelSize = 1.0 / (float2)texDim;
+	float result = 0.0;
+	for (int x = -blurRange; x < blurRange; x++)
+	{
+		for (int y = -blurRange; y < blurRange; y++)
+		{
+			float2 offset = float2(float(x), float(y)) * texelSize;
+			result += textureSSAO.Sample(samplerSSAO, inUV + offset).r;
+			n++;
+		}
+	}
+	return result / (float(n));
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/ssao/blur.frag.spv b/external/Vulkan/data/shaders/hlsl/ssao/blur.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..05136819ffac14892ddcd688629d24df72538681
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/ssao/blur.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/ssao/composition.frag b/external/Vulkan/data/shaders/hlsl/ssao/composition.frag
new file mode 100644
index 0000000000000000000000000000000000000000..b89db5250caacdbb37256768d4de46bd51d0a141
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/ssao/composition.frag
@@ -0,0 +1,56 @@
+// Copyright 2020 Google LLC
+
+Texture2D textureposition : register(t0);
+SamplerState samplerposition : register(s0);
+Texture2D textureNormal : register(t1);
+SamplerState samplerNormal : register(s1);
+Texture2D textureAlbedo : register(t2);
+SamplerState samplerAlbedo : register(s2);
+Texture2D textureSSAO : register(t3);
+SamplerState samplerSSAO : register(s3);
+Texture2D textureSSAOBlur : register(t4);
+SamplerState samplerSSAOBlur : register(s4);
+struct UBO
+{
+	float4x4 _dummy;
+	int ssao;
+	int ssaoOnly;
+	int ssaoBlur;
+};
+cbuffer uboParams : register(b5) { UBO uboParams; };
+
+float4 main([[vk::location(0)]] float2 inUV : TEXCOORD0) : SV_TARGET
+{
+	float3 fragPos = textureposition.Sample(samplerposition, inUV).rgb;
+	float3 normal = normalize(textureNormal.Sample(samplerNormal, inUV).rgb * 2.0 - 1.0);
+	float4 albedo = textureAlbedo.Sample(samplerAlbedo, inUV);
+
+	float ssao = (uboParams.ssaoBlur == 1) ? textureSSAOBlur.Sample(samplerSSAOBlur, inUV).r : textureSSAO.Sample(samplerSSAO, inUV).r;
+
+	float3 lightPos = float3(0.0, 0.0, 0.0);
+	float3 L = normalize(lightPos - fragPos);
+	float NdotL = max(0.5, dot(normal, L));
+
+	float4 outFragColor;
+	if (uboParams.ssaoOnly == 1)
+	{
+		outFragColor.rgb = ssao.rrr;
+	}
+	else
+	{
+		float3 baseColor = albedo.rgb * NdotL;
+
+		if (uboParams.ssao == 1)
+		{
+			outFragColor.rgb = ssao.rrr;
+
+			if (uboParams.ssaoOnly != 1)
+				outFragColor.rgb *= baseColor;
+		}
+		else
+		{
+			outFragColor.rgb = baseColor;
+		}
+	}
+	return outFragColor;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/ssao/composition.frag.spv b/external/Vulkan/data/shaders/hlsl/ssao/composition.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..ba67a68df86e32c8c4816030d595c896a5a7c865
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/ssao/composition.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/ssao/fullscreen.vert b/external/Vulkan/data/shaders/hlsl/ssao/fullscreen.vert
new file mode 100644
index 0000000000000000000000000000000000000000..b13c2bf2353ce1fc1837fef4db32336429c22412
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/ssao/fullscreen.vert
@@ -0,0 +1,15 @@
+// Copyright 2020 Google LLC
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float2 UV : TEXCOORD0;
+};
+
+VSOutput main(uint VertexIndex : SV_VertexID)
+{
+	VSOutput output = (VSOutput)0;
+	output.UV = float2((VertexIndex << 1) & 2, VertexIndex & 2);
+	output.Pos = float4(output.UV * 2.0f - 1.0f, 0.0f, 1.0f);
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/ssao/fullscreen.vert.spv b/external/Vulkan/data/shaders/hlsl/ssao/fullscreen.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..46c4b31fdc1568cc56662cc746d3a2d10f9d0983
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/ssao/fullscreen.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/ssao/gbuffer.frag b/external/Vulkan/data/shaders/hlsl/ssao/gbuffer.frag
new file mode 100644
index 0000000000000000000000000000000000000000..6b59dd50362eeae5c1d2acd17f2be3bb7c035c82
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/ssao/gbuffer.frag
@@ -0,0 +1,46 @@
+// Copyright 2020 Google LLC
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+[[vk::location(2)]] float3 Color : COLOR0;
+[[vk::location(3)]] float3 WorldPos : POSITION0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+	float4x4 view;
+	float nearPlane;
+	float farPlane;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+Texture2D textureColorMap : register(t0, space1);
+SamplerState samplerColorMap : register(s0, space1);
+
+struct FSOutput
+{
+	float4 Position : SV_TARGET0;
+	float4 Normal : SV_TARGET1;
+	float4 Albedo : SV_TARGET2;
+};
+
+float linearDepth(float depth)
+{
+	float z = depth * 2.0f - 1.0f;
+	return (2.0f * ubo.nearPlane * ubo.farPlane) / (ubo.farPlane + ubo.nearPlane - z * (ubo.farPlane - ubo.nearPlane));
+}
+
+FSOutput main(VSOutput input)
+{
+	FSOutput output = (FSOutput)0;
+	output.Position = float4(input.WorldPos, linearDepth(input.Pos.z));
+	output.Normal = float4(normalize(input.Normal) * 0.5 + 0.5, 1.0);
+	output.Albedo = textureColorMap.Sample(samplerColorMap, input.UV) * float4(input.Color, 1.0);
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/ssao/gbuffer.frag.spv b/external/Vulkan/data/shaders/hlsl/ssao/gbuffer.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..8ab0e21d99de48cdd4d6019d192610c7dcfda1b0
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/ssao/gbuffer.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/ssao/gbuffer.vert b/external/Vulkan/data/shaders/hlsl/ssao/gbuffer.vert
new file mode 100644
index 0000000000000000000000000000000000000000..85f7e07d534c1935dc4fbd31dee911e79e22b740
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/ssao/gbuffer.vert
@@ -0,0 +1,45 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float4 Pos : POSITION0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+[[vk::location(2)]] float3 Color : COLOR0;
+[[vk::location(3)]] float3 Normal : NORMAL0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+	float4x4 view;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+[[vk::location(2)]] float3 Color : COLOR0;
+[[vk::location(3)]] float3 WorldPos : POSITION0;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Pos = mul(ubo.projection, mul(ubo.view, mul(ubo.model, input.Pos)));
+
+	output.UV = input.UV;
+
+	// Vertex position in view space
+	output.WorldPos = mul(ubo.view, mul(ubo.model, input.Pos)).xyz;
+
+	// Normal in view space
+	float3x3 normalMatrix = (float3x3)mul(ubo.view, ubo.model);
+	output.Normal = mul(normalMatrix, input.Normal);
+
+	output.Color = input.Color;
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/ssao/gbuffer.vert.spv b/external/Vulkan/data/shaders/hlsl/ssao/gbuffer.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..b862daa7929e1f3ca04e658d8fd8ced1819da9dd
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/ssao/gbuffer.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/ssao/ssao.frag b/external/Vulkan/data/shaders/hlsl/ssao/ssao.frag
new file mode 100644
index 0000000000000000000000000000000000000000..7bb496813e667272b0b47162593c5df8cd0e374d
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/ssao/ssao.frag
@@ -0,0 +1,67 @@
+// Copyright 2020 Google LLC
+
+Texture2D texturePositionDepth : register(t0);
+SamplerState samplerPositionDepth : register(s0);
+Texture2D textureNormal : register(t1);
+SamplerState samplerNormal : register(s1);
+Texture2D ssaoNoiseTexture : register(t2);
+SamplerState ssaoNoiseSampler : register(s2);
+
+#define SSAO_KERNEL_ARRAY_SIZE 64
+[[vk::constant_id(0)]] const int SSAO_KERNEL_SIZE = 64;
+[[vk::constant_id(1)]] const float SSAO_RADIUS = 0.5;
+
+struct UBOSSAOKernel
+{
+	float4 samples[SSAO_KERNEL_ARRAY_SIZE];
+};
+cbuffer uboSSAOKernel : register(b3) { UBOSSAOKernel uboSSAOKernel; };
+
+struct UBO
+{
+	float4x4 projection;
+};
+cbuffer ubo : register(b4) { UBO ubo; };
+
+float main([[vk::location(0)]] float2 inUV : TEXCOORD0) : SV_TARGET
+{
+	// Get G-Buffer values
+	float3 fragPos = texturePositionDepth.Sample(samplerPositionDepth, inUV).rgb;
+	float3 normal = normalize(textureNormal.Sample(samplerNormal, inUV).rgb * 2.0 - 1.0);
+
+	// Get a random vector using a noise lookup
+	int2 texDim;
+	texturePositionDepth.GetDimensions(texDim.x, texDim.y);
+	int2 noiseDim;
+	ssaoNoiseTexture.GetDimensions(noiseDim.x, noiseDim.y);
+	const float2 noiseUV = float2(float(texDim.x)/float(noiseDim.x), float(texDim.y)/(noiseDim.y)) * inUV;
+	float3 randomVec = ssaoNoiseTexture.Sample(ssaoNoiseSampler, noiseUV).xyz * 2.0 - 1.0;
+
+	// Create TBN matrix
+	float3 tangent = normalize(randomVec - normal * dot(randomVec, normal));
+	float3 bitangent = cross(tangent, normal);
+	float3x3 TBN = transpose(float3x3(tangent, bitangent, normal));
+
+	// Calculate occlusion value
+	float occlusion = 0.0f;
+	for(int i = 0; i < SSAO_KERNEL_SIZE; i++)
+	{
+		float3 samplePos = mul(TBN, uboSSAOKernel.samples[i].xyz);
+		samplePos = fragPos + samplePos * SSAO_RADIUS;
+
+		// project
+		float4 offset = float4(samplePos, 1.0f);
+		offset = mul(ubo.projection, offset);
+		offset.xyz /= offset.w;
+		offset.xyz = offset.xyz * 0.5f + 0.5f;
+
+		float sampleDepth = -texturePositionDepth.Sample(samplerPositionDepth, offset.xy).w;
+
+		float rangeCheck = smoothstep(0.0f, 1.0f, SSAO_RADIUS / abs(fragPos.z - sampleDepth));
+		occlusion += (sampleDepth >= samplePos.z ? 1.0f : 0.0f) * rangeCheck;
+	}
+	occlusion = 1.0 - (occlusion / float(SSAO_KERNEL_SIZE));
+
+	return occlusion;
+}
+
diff --git a/external/Vulkan/data/shaders/hlsl/ssao/ssao.frag.spv b/external/Vulkan/data/shaders/hlsl/ssao/ssao.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..f0b88c686aad76af26afefaebc0e5d068fbfff2c
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/ssao/ssao.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/stencilbuffer/outline.frag b/external/Vulkan/data/shaders/hlsl/stencilbuffer/outline.frag
new file mode 100644
index 0000000000000000000000000000000000000000..9892e7093817a40915d6c6127bbd5a0c2dee8fef
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/stencilbuffer/outline.frag
@@ -0,0 +1,6 @@
+// Copyright 2020 Google LLC
+
+float4 main() : SV_TARGET
+{
+	return float4(1.0, 1.0, 1.0, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/stencilbuffer/outline.frag.spv b/external/Vulkan/data/shaders/hlsl/stencilbuffer/outline.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..da33b821eaf29f96aa6488c58e046539c523e972
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/stencilbuffer/outline.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/stencilbuffer/outline.vert b/external/Vulkan/data/shaders/hlsl/stencilbuffer/outline.vert
new file mode 100644
index 0000000000000000000000000000000000000000..7f1f17a74020897ba1fcdf4d975f6b434290eb7b
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/stencilbuffer/outline.vert
@@ -0,0 +1,24 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float4 Pos : POSITION0;
+[[vk::location(2)]] float3 Normal : NORMAL0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+	float4 lightPos;
+	float outlineWidth;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+float4 main(VSInput input) : SV_POSITION
+{
+	// Extrude along normal
+	float4 pos = float4(input.Pos.xyz + input.Normal * ubo.outlineWidth, input.Pos.w);
+	return mul(ubo.projection, mul(ubo.model, pos));
+}
diff --git a/external/Vulkan/data/shaders/hlsl/stencilbuffer/outline.vert.spv b/external/Vulkan/data/shaders/hlsl/stencilbuffer/outline.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..4941da1cff1948724c703b2dbba3d40e97eed86c
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/stencilbuffer/outline.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/stencilbuffer/toon.frag b/external/Vulkan/data/shaders/hlsl/stencilbuffer/toon.frag
new file mode 100644
index 0000000000000000000000000000000000000000..990105ff8d5a05616c9d7aa8f182fd247e6ff30b
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/stencilbuffer/toon.frag
@@ -0,0 +1,32 @@
+// Copyright 2020 Google LLC
+
+Texture2D textureColorMap : register(t1);
+SamplerState samplerColorMap : register(s1);
+
+struct VSOutput
+{
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float3 LightVec : TEXCOORD2;
+};
+
+float4 main(VSOutput input) : SV_TARGET
+{
+	float3 color;
+	float3 N = normalize(input.Normal);
+	float3 L = normalize(input.LightVec);
+	float intensity = dot(N,L);
+	if (intensity > 0.98)
+		color = input.Color * 1.5;
+	else if  (intensity > 0.9)
+		color = input.Color * 1.0;
+	else if (intensity > 0.5)
+		color = input.Color * 0.6;
+	else if (intensity > 0.25)
+		color = input.Color * 0.4;
+	else
+		color = input.Color * 0.2;
+	// Desaturate a bit
+	color = lerp(color, dot(float3(0.2126,0.7152,0.0722), color).xxx, 0.1);
+	return float4(color, 1);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/stencilbuffer/toon.frag.spv b/external/Vulkan/data/shaders/hlsl/stencilbuffer/toon.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..bc7b26085b414732b926ca7dfa438e7edf2a5528
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/stencilbuffer/toon.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/stencilbuffer/toon.vert b/external/Vulkan/data/shaders/hlsl/stencilbuffer/toon.vert
new file mode 100644
index 0000000000000000000000000000000000000000..7ab4fdfed10e13cb9c9c885f41f40d39711c52a8
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/stencilbuffer/toon.vert
@@ -0,0 +1,37 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float3 Normal : NORMAL0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+	float4 lightPos;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float3 LightVec : TEXCOORD2;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Color = float3(1.0, 0.0, 0.0);
+	output.Pos = mul(ubo.projection, mul(ubo.model, float4(input.Pos.xyz, 1.0)));
+	output.Normal = mul((float3x3)ubo.model, input.Normal);
+	float4 pos = mul(ubo.model, float4(input.Pos, 1.0));
+	float3 lPos = mul((float3x3)ubo.model, ubo.lightPos.xyz);
+	output.LightVec = lPos - pos.xyz;
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/stencilbuffer/toon.vert.spv b/external/Vulkan/data/shaders/hlsl/stencilbuffer/toon.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..608fddb7182b368e5dbb8dfd1dc856c8b8260022
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/stencilbuffer/toon.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/subpasses/composition.frag b/external/Vulkan/data/shaders/hlsl/subpasses/composition.frag
new file mode 100644
index 0000000000000000000000000000000000000000..29fd3113b7a0d03ce790c10f7bab19eb2a348061
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/subpasses/composition.frag
@@ -0,0 +1,69 @@
+// Copyright 2020 Google LLC
+
+[[vk::input_attachment_index(0)]][[vk::binding(0)]] SubpassInput samplerposition;
+[[vk::input_attachment_index(1)]][[vk::binding(1)]] SubpassInput samplerNormal;
+[[vk::input_attachment_index(2)]][[vk::binding(2)]] SubpassInput samplerAlbedo;
+
+#define MAX_NUM_LIGHTS 64
+[[vk::constant_id(0)]] const int NUM_LIGHTS = 64;
+
+struct Light {
+	float4 position;
+	float3 color;
+	float radius;
+};
+
+struct UBO
+{
+	float4 viewPos;
+	Light lights[MAX_NUM_LIGHTS];
+};
+
+cbuffer ubo : register(b3) { UBO ubo; }
+
+
+float4 main([[vk::location(0)]] float2 inUV : TEXCOORD) : SV_TARGET
+{
+	// Read G-Buffer values from previous sub pass
+	float3 fragPos = samplerposition.SubpassLoad().rgb;
+	float3 normal = samplerNormal.SubpassLoad().rgb;
+	float4 albedo = samplerAlbedo.SubpassLoad();
+
+	#define ambient 0.15
+
+	// Ambient part
+	float3 fragcolor  = albedo.rgb * ambient;
+
+	for(int i = 0; i < NUM_LIGHTS; ++i)
+	{
+		// Vector to light
+		float3 L = ubo.lights[i].position.xyz - fragPos;
+		// Distance from light to fragment position
+		float dist = length(L);
+
+		// Viewer to fragment
+		float3 V = ubo.viewPos.xyz - fragPos;
+		V = normalize(V);
+
+		// Light to fragment
+		L = normalize(L);
+
+		// Attenuation
+		float atten = ubo.lights[i].radius / (pow(dist, 2.0) + 1.0);
+
+		// Diffuse part
+		float3 N = normalize(normal);
+		float NdotL = max(0.0, dot(N, L));
+		float3 diff = ubo.lights[i].color * albedo.rgb * NdotL * atten;
+
+		// Specular part
+		// Specular map values are stored in alpha of albedo mrt
+		float3 R = reflect(-L, N);
+		float NdotR = max(0.0, dot(R, V));
+		//float3 spec = ubo.lights[i].color * albedo.a * pow(NdotR, 32.0) * atten;
+
+		fragcolor += diff;// + spec;
+	}
+
+	return float4(fragcolor, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/subpasses/composition.frag.spv b/external/Vulkan/data/shaders/hlsl/subpasses/composition.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..380ebe6dbf698d578c4975cce9bf1335bc279b52
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/subpasses/composition.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/subpasses/composition.vert b/external/Vulkan/data/shaders/hlsl/subpasses/composition.vert
new file mode 100644
index 0000000000000000000000000000000000000000..188b72989e70e69a38a4fa88d015f67c7709cbdc
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/subpasses/composition.vert
@@ -0,0 +1,15 @@
+// Copyright 2020 Google LLC
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float2 UV : TEXCOORD0;
+};
+
+VSOutput main(uint VertexIndex : SV_VertexID)
+{
+	VSOutput output = (VSOutput)0;
+	output.UV = float2((VertexIndex << 1) & 2, VertexIndex & 2);
+	output.Pos = float4(output.UV * 2.0f - 1.0f, 0.0f, 1.0f);
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/subpasses/composition.vert.spv b/external/Vulkan/data/shaders/hlsl/subpasses/composition.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..46c4b31fdc1568cc56662cc746d3a2d10f9d0983
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/subpasses/composition.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/subpasses/gbuffer.frag b/external/Vulkan/data/shaders/hlsl/subpasses/gbuffer.frag
new file mode 100644
index 0000000000000000000000000000000000000000..2e278903cfa80242b91b49411368ee89dc0b6ff7
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/subpasses/gbuffer.frag
@@ -0,0 +1,45 @@
+// Copyright 2020 Google LLC
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float3 WorldPos : POSITION0;
+};
+
+struct FSOutput
+{
+[[vk::location(0)]] float4 Color : SV_TARGET0;
+[[vk::location(1)]] float4 Position : SV_TARGET1;
+[[vk::location(2)]] float4 Normal : SV_TARGET2;
+[[vk::location(3)]] float4 Albedo : SV_TARGET3;
+};
+
+[[vk::constant_id(0)]] const float NEAR_PLANE = 0.1f;
+[[vk::constant_id(1)]] const float FAR_PLANE = 256.0f;
+
+float linearDepth(float depth)
+{
+	float z = depth * 2.0f - 1.0f;
+	return (2.0f * NEAR_PLANE * FAR_PLANE) / (FAR_PLANE + NEAR_PLANE - z * (FAR_PLANE - NEAR_PLANE));
+}
+
+FSOutput main(VSOutput input)
+{
+	FSOutput output = (FSOutput)0;
+	output.Position = float4(input.WorldPos, 1.0);
+
+	float3 N = normalize(input.Normal);
+	N.y = -N.y;
+	output.Normal = float4(N, 1.0);
+
+	output.Albedo.rgb = input.Color;
+
+	// Store linearized depth in alpha component
+	output.Position.a = linearDepth(input.Pos.z);
+
+	// Write color attachments to avoid undefined behaviour (validation error)
+	output.Color = float4(0.0, 0.0, 0.0, 0.0);
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/subpasses/gbuffer.frag.spv b/external/Vulkan/data/shaders/hlsl/subpasses/gbuffer.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..ec48ef2d367659f1437725951793a74cbf304105
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/subpasses/gbuffer.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/subpasses/gbuffer.vert b/external/Vulkan/data/shaders/hlsl/subpasses/gbuffer.vert
new file mode 100644
index 0000000000000000000000000000000000000000..d4f7d73efe903839dba72f4e77de100aef0c6242
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/subpasses/gbuffer.vert
@@ -0,0 +1,44 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float4 Pos : POSITION0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float3 Normal : NORMAL0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+	float4x4 view;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float3 WorldPos : POSITION0;
+[[vk::location(3)]] float3 Tangent : TEXCOORD1;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Pos = mul(ubo.projection, mul(ubo.view, mul(ubo.model, input.Pos)));
+
+	// Vertex position in world space
+	output.WorldPos = mul(ubo.model, input.Pos).xyz;
+	// GL to Vulkan coord space
+	output.WorldPos.y = -output.WorldPos.y;
+
+	// Normal in world space
+	output.Normal = mul((float3x3)ubo.model, normalize(input.Normal));
+
+	// Currently just vertex color
+	output.Color = input.Color;
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/subpasses/gbuffer.vert.spv b/external/Vulkan/data/shaders/hlsl/subpasses/gbuffer.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..33166e7f369a69346af2fa1ac0659f92efc38a4c
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/subpasses/gbuffer.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/subpasses/transparent.frag b/external/Vulkan/data/shaders/hlsl/subpasses/transparent.frag
new file mode 100644
index 0000000000000000000000000000000000000000..28b46bc1eddde22a82eafc448cbdf311ff6566a2
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/subpasses/transparent.frag
@@ -0,0 +1,33 @@
+// Copyright 2020 Google LLC
+
+[[vk::input_attachment_index(0)]][[vk::binding(1)]] SubpassInput samplerPositionDepth;
+Texture2D textureTexture : register(t2);
+SamplerState samplerTexture : register(s2);
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Color : COLOR0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+};
+
+[[vk::constant_id(0)]] const float NEAR_PLANE = 0.1f;
+[[vk::constant_id(1)]] const float FAR_PLANE = 256.0f;
+
+float linearDepth(float depth)
+{
+	float z = depth * 2.0f - 1.0f;
+	return (2.0f * NEAR_PLANE * FAR_PLANE) / (FAR_PLANE + NEAR_PLANE - z * (FAR_PLANE - NEAR_PLANE));
+}
+
+float4 main (VSOutput input) : SV_TARGET
+{
+	// Sample depth from deferred depth buffer and discard if obscured
+	float depth = samplerPositionDepth.SubpassLoad().a;
+	if ((depth != 0.0) && (linearDepth(input.Pos.z) > depth))
+	{
+		clip(-1);
+	};
+
+	return textureTexture.Sample(samplerTexture, input.UV);
+}
diff --git a/external/Vulkan/data/shaders/hlsl/subpasses/transparent.frag.spv b/external/Vulkan/data/shaders/hlsl/subpasses/transparent.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..e385546f73368d29b057dba525e9e023893bed31
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/subpasses/transparent.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/subpasses/transparent.vert b/external/Vulkan/data/shaders/hlsl/subpasses/transparent.vert
new file mode 100644
index 0000000000000000000000000000000000000000..4d32741092b6d2acbe770a902a5db622f0db8776
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/subpasses/transparent.vert
@@ -0,0 +1,35 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float4 Pos : POSITION0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float3 Normal : NORMAL0;
+[[vk::location(3)]] float2 UV : TEXCOORD0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+	float4x4 view;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Color : COLOR0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+};
+
+VSOutput main (VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Color = input.Color;
+	output.UV = input.UV;
+
+	output.Pos = mul(ubo.projection, mul(ubo.view, mul(ubo.model, float4(input.Pos.xyz, 1.0))));
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/subpasses/transparent.vert.spv b/external/Vulkan/data/shaders/hlsl/subpasses/transparent.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..fb8eee0eee973397468595f60fb2d9609e6117ad
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/subpasses/transparent.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/terraintessellation/skysphere.frag b/external/Vulkan/data/shaders/hlsl/terraintessellation/skysphere.frag
new file mode 100644
index 0000000000000000000000000000000000000000..a40c1b1396a5a12b1a94f0e946be9d349c8131a4
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/terraintessellation/skysphere.frag
@@ -0,0 +1,10 @@
+// Copyright 2020 Google LLC
+
+Texture2D textureColorMap : register(t1);
+SamplerState samplerColorMap : register(s1);
+
+float4 main([[vk::location(0)]] float2 inUV : TEXCOODR0) : SV_TARGET
+{
+	float4 color = textureColorMap.Sample(samplerColorMap, inUV);
+	return float4(color.rgb, 1.0);
+}
diff --git a/external/Vulkan/data/shaders/hlsl/terraintessellation/skysphere.frag.spv b/external/Vulkan/data/shaders/hlsl/terraintessellation/skysphere.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..824f9756691210adb62639d059356f5ceb2f4458
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/terraintessellation/skysphere.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/terraintessellation/skysphere.vert b/external/Vulkan/data/shaders/hlsl/terraintessellation/skysphere.vert
new file mode 100644
index 0000000000000000000000000000000000000000..3b7151ff99d27d9f8911359578666131cb41c4d3
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/terraintessellation/skysphere.vert
@@ -0,0 +1,28 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float2 UV : TEXCOORD0;
+};
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float2 UV : TEXCOORD0;
+};
+
+struct UBO
+{
+	float4x4 mvp;
+};
+cbuffer ubo : register(b0) { UBO ubo; };
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Pos = mul(ubo.mvp, float4(input.Pos, 1.0));
+	output.UV = input.UV;
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/terraintessellation/skysphere.vert.spv b/external/Vulkan/data/shaders/hlsl/terraintessellation/skysphere.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..0ea62408d7207d376c5a276e327b4dedeb1cccbb
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/terraintessellation/skysphere.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/terraintessellation/terrain.frag b/external/Vulkan/data/shaders/hlsl/terraintessellation/terrain.frag
new file mode 100644
index 0000000000000000000000000000000000000000..ee1a299132e079d0954167b3c0ec169c25559da9
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/terraintessellation/terrain.frag
@@ -0,0 +1,65 @@
+// Copyright 2020 Google LLC
+
+Texture2D textureHeight : register(t1);
+SamplerState samplerHeight : register(s1);
+Texture2DArray textureLayers : register(t2);
+SamplerState samplerLayers : register(s2);
+
+struct DSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+[[vk::location(2)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(3)]] float3 LightVec : TEXCOORD2;
+[[vk::location(4)]] float3 EyePos : POSITION1;
+[[vk::location(5)]] float3 WorldPos : POSITION0;
+};
+
+float3 sampleTerrainLayer(float2 inUV)
+{
+	// Define some layer ranges for sampling depending on terrain height
+	float2 layers[6];
+	layers[0] = float2(-10.0, 10.0);
+	layers[1] = float2(5.0, 45.0);
+	layers[2] = float2(45.0, 80.0);
+	layers[3] = float2(75.0, 100.0);
+	layers[4] = float2(95.0, 140.0);
+	layers[5] = float2(140.0, 190.0);
+
+	float3 color = float3(0.0, 0.0, 0.0);
+
+	// Get height from displacement map
+	float height = textureHeight.SampleLevel(samplerHeight, inUV, 0.0).r * 255.0;
+
+	for (int i = 0; i < 6; i++)
+	{
+		float range = layers[i].y - layers[i].x;
+		float weight = (range - abs(height - layers[i].y)) / range;
+		weight = max(0.0, weight);
+		color += weight * textureLayers.Sample(samplerLayers, float3(inUV * 16.0, i)).rgb;
+	}
+
+	return color;
+}
+
+float fog(float density, float4 FragCoord)
+{
+	const float LOG2 = -1.442695;
+	float dist = FragCoord.z / FragCoord.w * 0.1;
+	float d = density * dist;
+	return 1.0 - clamp(exp2(d * d * LOG2), 0.0, 1.0);
+}
+
+float4 main(DSOutput input) : SV_TARGET
+{
+	float3 N = normalize(input.Normal);
+	float3 L = normalize(input.LightVec);
+	float3 ambient = float3(0.5, 0.5, 0.5);
+	float3 diffuse = max(dot(N, L), 0.0) * float3(1.0, 1.0, 1.0);
+
+	float4 color = float4((ambient + diffuse) * sampleTerrainLayer(input.UV), 1.0);
+
+	const float4 fogColor = float4(0.47, 0.5, 0.67, 0.0);
+	return lerp(color, fogColor, fog(0.25, input.Pos));
+}
diff --git a/external/Vulkan/data/shaders/hlsl/terraintessellation/terrain.frag.spv b/external/Vulkan/data/shaders/hlsl/terraintessellation/terrain.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..f6fc2f2a718bf7ab9328a847f06c2b76ba61a784
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/terraintessellation/terrain.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/terraintessellation/terrain.tesc b/external/Vulkan/data/shaders/hlsl/terraintessellation/terrain.tesc
new file mode 100644
index 0000000000000000000000000000000000000000..595546f5bdcabc77694f51e39f1479d6cd0c212a
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/terraintessellation/terrain.tesc
@@ -0,0 +1,141 @@
+// Copyright 2020 Google LLC
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 modelview;
+	float4 lightPos;
+	float4 frustumPlanes[6];
+	float displacementFactor;
+	float tessellationFactor;
+	float2 viewportDim;
+	float tessellatedEdgeSize;
+};
+cbuffer ubo : register(b0) { UBO ubo; };
+
+Texture2D textureHeight : register(t1);
+SamplerState samplerHeight : register(s1);
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+};
+
+struct HSOutput
+{
+[[vk::location(2)]]	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+};
+
+struct ConstantsHSOutput
+{
+    float TessLevelOuter[4] : SV_TessFactor;
+    float TessLevelInner[2] : SV_InsideTessFactor;
+};
+
+// Calculate the tessellation factor based on screen space
+// dimensions of the edge
+float screenSpaceTessFactor(float4 p0, float4 p1)
+{
+	// Calculate edge mid point
+	float4 midPoint = 0.5 * (p0 + p1);
+	// Sphere radius as distance between the control points
+	float radius = distance(p0, p1) / 2.0;
+
+	// View space
+	float4 v0 = mul(ubo.modelview, midPoint);
+
+	// Project into clip space
+	float4 clip0 = mul(ubo.projection, (v0 - float4(radius, float3(0.0, 0.0, 0.0))));
+	float4 clip1 = mul(ubo.projection, (v0 + float4(radius, float3(0.0, 0.0, 0.0))));
+
+	// Get normalized device coordinates
+	clip0 /= clip0.w;
+	clip1 /= clip1.w;
+
+	// Convert to viewport coordinates
+	clip0.xy *= ubo.viewportDim;
+	clip1.xy *= ubo.viewportDim;
+
+	// Return the tessellation factor based on the screen size
+	// given by the distance of the two edge control points in screen space
+	// and a reference (min.) tessellation size for the edge set by the application
+	return clamp(distance(clip0, clip1) / ubo.tessellatedEdgeSize * ubo.tessellationFactor, 1.0, 64.0);
+}
+
+// Checks the current's patch visibility against the frustum using a sphere check
+// Sphere radius is given by the patch size
+bool frustumCheck(float4 Pos, float2 inUV)
+{
+	// Fixed radius (increase if patch size is increased in example)
+	const float radius = 8.0f;
+	float4 pos = Pos;
+	pos.y -= textureHeight.SampleLevel(samplerHeight, inUV, 0.0).r * ubo.displacementFactor;
+
+	// Check sphere against frustum planes
+	for (int i = 0; i < 6; i++) {
+		if (dot(pos, ubo.frustumPlanes[i]) + radius < 0.0)
+		{
+			return false;
+		}
+	}
+	return true;
+}
+
+ConstantsHSOutput ConstantsHS(InputPatch<VSOutput, 4> patch)
+{
+    ConstantsHSOutput output = (ConstantsHSOutput)0;
+
+	if (!frustumCheck(patch[0].Pos, patch[0].UV))
+	{
+		output.TessLevelInner[0] = 0.0;
+		output.TessLevelInner[1] = 0.0;
+		output.TessLevelOuter[0] = 0.0;
+		output.TessLevelOuter[1] = 0.0;
+		output.TessLevelOuter[2] = 0.0;
+		output.TessLevelOuter[3] = 0.0;
+	}
+	else
+	{
+		if (ubo.tessellationFactor > 0.0)
+		{
+			output.TessLevelOuter[0] = screenSpaceTessFactor(patch[3].Pos, patch[0].Pos);
+			output.TessLevelOuter[1] = screenSpaceTessFactor(patch[0].Pos, patch[1].Pos);
+			output.TessLevelOuter[2] = screenSpaceTessFactor(patch[1].Pos, patch[2].Pos);
+			output.TessLevelOuter[3] = screenSpaceTessFactor(patch[2].Pos, patch[3].Pos);
+			output.TessLevelInner[0] = lerp(output.TessLevelOuter[0], output.TessLevelOuter[3], 0.5);
+			output.TessLevelInner[1] = lerp(output.TessLevelOuter[2], output.TessLevelOuter[1], 0.5);
+		}
+		else
+		{
+			// Tessellation factor can be set to zero by example
+			// to demonstrate a simple passthrough
+			output.TessLevelInner[0] = 1.0;
+			output.TessLevelInner[1] = 1.0;
+			output.TessLevelOuter[0] = 1.0;
+			output.TessLevelOuter[1] = 1.0;
+			output.TessLevelOuter[2] = 1.0;
+			output.TessLevelOuter[3] = 1.0;
+		}
+	}
+
+    return output;
+}
+
+[domain("quad")]
+[partitioning("integer")]
+[outputtopology("triangle_cw")]
+[outputcontrolpoints(4)]
+[patchconstantfunc("ConstantsHS")]
+[maxtessfactor(20.0f)]
+HSOutput main(InputPatch<VSOutput, 4> patch, uint InvocationID : SV_OutputControlPointID)
+{
+	HSOutput output = (HSOutput)0;
+	output.Pos = patch[InvocationID].Pos;
+	output.Normal = patch[InvocationID].Normal;
+	output.UV = patch[InvocationID].UV;
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/terraintessellation/terrain.tesc.spv b/external/Vulkan/data/shaders/hlsl/terraintessellation/terrain.tesc.spv
new file mode 100644
index 0000000000000000000000000000000000000000..676d3259d822e3a1b9461a21b14167d31eef42a3
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/terraintessellation/terrain.tesc.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/terraintessellation/terrain.tese b/external/Vulkan/data/shaders/hlsl/terraintessellation/terrain.tese
new file mode 100644
index 0000000000000000000000000000000000000000..c964610814f0ee5ad6f2672b91db514f647a9e59
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/terraintessellation/terrain.tese
@@ -0,0 +1,71 @@
+// Copyright 2020 Google LLC
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 modelview;
+	float4 lightPos;
+	float4 frustumPlanes[6];
+	float displacementFactor;
+	float tessellationFactor;
+	float2 viewportDim;
+	float tessellatedEdgeSize;
+};
+cbuffer ubo : register(b0) { UBO ubo; };
+
+Texture2D displacementMapTexture : register(t1);
+SamplerState displacementMapSampler : register(s1);
+
+struct HSOutput
+{
+[[vk::location(2)]]	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+};
+
+struct ConstantsHSOutput
+{
+    float TessLevelOuter[4] : SV_TessFactor;
+    float TessLevelInner[2] : SV_InsideTessFactor;
+};
+
+struct DSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+[[vk::location(2)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(3)]] float3 LightVec : TEXCOORD2;
+[[vk::location(4)]] float3 EyePos : POSITION1;
+[[vk::location(5)]] float3 WorldPos : POSITION0;
+};
+
+[domain("quad")]
+DSOutput main(ConstantsHSOutput input, float2 TessCoord : SV_DomainLocation, const OutputPatch<HSOutput, 4> patch)
+{
+	// Interpolate UV coordinates
+	DSOutput output = (DSOutput)0;
+	float2 uv1 = lerp(patch[0].UV, patch[1].UV, TessCoord.x);
+	float2 uv2 = lerp(patch[3].UV, patch[2].UV, TessCoord.x);
+	output.UV = lerp(uv1, uv2, TessCoord.y);
+
+	float3 n1 = lerp(patch[0].Normal, patch[1].Normal, TessCoord.x);
+	float3 n2 = lerp(patch[3].Normal, patch[2].Normal, TessCoord.x);
+	output.Normal = lerp(n1, n2, TessCoord.y);
+
+	// Interpolate positions
+	float4 pos1 = lerp(patch[0].Pos, patch[1].Pos, TessCoord.x);
+	float4 pos2 = lerp(patch[3].Pos, patch[2].Pos, TessCoord.x);
+	float4 pos = lerp(pos1, pos2, TessCoord.y);
+	// Displace
+	pos.y -= displacementMapTexture.SampleLevel(displacementMapSampler, output.UV, 0.0).r * ubo.displacementFactor;
+	// Perspective projection
+	output.Pos = mul(ubo.projection, mul(ubo.modelview, pos));
+
+	// Calculate vectors for lighting based on tessellated position
+	output.ViewVec = -pos.xyz;
+	output.LightVec = normalize(ubo.lightPos.xyz + output.ViewVec);
+	output.WorldPos = pos.xyz;
+	output.EyePos = mul(ubo.modelview, pos).xyz;
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/terraintessellation/terrain.tese.spv b/external/Vulkan/data/shaders/hlsl/terraintessellation/terrain.tese.spv
new file mode 100644
index 0000000000000000000000000000000000000000..fc0840dcca26a061a525a96c6027cee76118b759
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/terraintessellation/terrain.tese.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/terraintessellation/terrain.vert b/external/Vulkan/data/shaders/hlsl/terraintessellation/terrain.vert
new file mode 100644
index 0000000000000000000000000000000000000000..06898dfebaece5ebf6670a6cf84c67273fac7434
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/terraintessellation/terrain.vert
@@ -0,0 +1,24 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float2 UV : TEXCOORD0;
+};
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Pos = float4(input.Pos.xyz, 1.0);
+	output.UV = input.UV;
+	output.Normal = input.Normal;
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/terraintessellation/terrain.vert.spv b/external/Vulkan/data/shaders/hlsl/terraintessellation/terrain.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..d3f97743920d30f0b53bac7f276e5d3305672f49
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/terraintessellation/terrain.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/tessellation/base.frag b/external/Vulkan/data/shaders/hlsl/tessellation/base.frag
new file mode 100644
index 0000000000000000000000000000000000000000..0a34282bd1e2e6587562d296ed7e69674adebd36
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/tessellation/base.frag
@@ -0,0 +1,20 @@
+// Copyright 2020 Google LLC
+
+Texture2D textureColorMap : register(t2);
+SamplerState samplerColorMap : register(s2);
+
+struct DSOutput
+{
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+};
+
+float4 main(DSOutput input) : SV_TARGET
+{
+	float3 N = normalize(input.Normal);
+	float3 L = normalize(float3(0.0, -4.0, 4.0));
+
+	float4 color = textureColorMap.Sample(samplerColorMap, input.UV);
+
+	return float4(clamp(max(dot(N,L), 0.0), 0.2, 1.0) * color.rgb * 1.5, 1);
+}
diff --git a/external/Vulkan/data/shaders/hlsl/tessellation/base.frag.spv b/external/Vulkan/data/shaders/hlsl/tessellation/base.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..94b5f80576d30e0122042f1acec39adbd1676ae6
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/tessellation/base.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/tessellation/base.vert b/external/Vulkan/data/shaders/hlsl/tessellation/base.vert
new file mode 100644
index 0000000000000000000000000000000000000000..3fcb8aec1ad7dd0fa419db42e570142859007562
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/tessellation/base.vert
@@ -0,0 +1,24 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float2 UV : TEXCOORD0;
+};
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Pos = float4(input.Pos.xyz, 1.0);
+	output.Normal = input.Normal;
+	output.UV = input.UV;
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/tessellation/base.vert.spv b/external/Vulkan/data/shaders/hlsl/tessellation/base.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..86de6853367cece9dbc41dcb419a8380f328dad9
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/tessellation/base.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/tessellation/passthrough.tesc b/external/Vulkan/data/shaders/hlsl/tessellation/passthrough.tesc
new file mode 100644
index 0000000000000000000000000000000000000000..738b2229f080133f833759e269ef118781f14eeb
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/tessellation/passthrough.tesc
@@ -0,0 +1,46 @@
+// Copyright 2020 Google LLC
+
+struct VSOutput
+{
+    float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+};
+
+struct HSOutput
+{
+    float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+};
+
+struct ConstantsHSOutput
+{
+    float TessLevelOuter[3] : SV_TessFactor;
+    float TessLevelInner : SV_InsideTessFactor;
+};
+
+ConstantsHSOutput ConstantsHS(InputPatch<VSOutput, 3> patch, uint InvocationID : SV_PrimitiveID)
+{
+    ConstantsHSOutput output = (ConstantsHSOutput)0;
+    output.TessLevelInner = 1;
+    output.TessLevelOuter[0] = 1;
+    output.TessLevelOuter[1] = 1;
+    output.TessLevelOuter[2] = 1;
+    return output;
+}
+
+[domain("tri")]
+[partitioning("integer")]
+[outputtopology("triangle_ccw")]
+[outputcontrolpoints(3)]
+[patchconstantfunc("ConstantsHS")]
+[maxtessfactor(20.0f)]
+HSOutput main(InputPatch<VSOutput, 3> patch, uint InvocationID : SV_OutputControlPointID)
+{
+    HSOutput output = (HSOutput)0;
+    output.Pos = patch[InvocationID].Pos;
+	output.Normal = patch[InvocationID].Normal;
+	output.UV = patch[InvocationID].UV;
+    return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/tessellation/passthrough.tesc.spv b/external/Vulkan/data/shaders/hlsl/tessellation/passthrough.tesc.spv
new file mode 100644
index 0000000000000000000000000000000000000000..8eac17020c8069ca6239822d6bc486babe5de4af
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/tessellation/passthrough.tesc.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/tessellation/passthrough.tese b/external/Vulkan/data/shaders/hlsl/tessellation/passthrough.tese
new file mode 100644
index 0000000000000000000000000000000000000000..535a55ad9330f0bc2a60c23870c1b37fb54bb4d7
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/tessellation/passthrough.tese
@@ -0,0 +1,44 @@
+// Copyright 2020 Google LLC
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+	float tessAlpha;
+};
+
+cbuffer ubo : register(b1) { UBO ubo; }
+
+struct HSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+};
+
+struct ConstantsHSOutput
+{
+    float TessLevelOuter[3] : SV_TessFactor;
+    float TessLevelInner : SV_InsideTessFactor;
+};
+
+struct DSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+};
+
+[domain("tri")]
+DSOutput main(ConstantsHSOutput input, float3 TessCoord : SV_DomainLocation, const OutputPatch<HSOutput, 3> patch)
+{
+	DSOutput output = (DSOutput)0;
+    output.Pos = (TessCoord.x * patch[0].Pos) +
+                  (TessCoord.y * patch[1].Pos) +
+                  (TessCoord.z * patch[2].Pos);
+	output.Pos = mul(ubo.projection, mul(ubo.model, output.Pos));
+
+	output.Normal = TessCoord.x*patch[0].Normal + TessCoord.y*patch[1].Normal + TessCoord.z*patch[2].Normal;
+	output.UV = TessCoord.x*patch[0].UV + TessCoord.y*patch[1].UV + TessCoord.z*patch[2].UV;
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/tessellation/passthrough.tese.spv b/external/Vulkan/data/shaders/hlsl/tessellation/passthrough.tese.spv
new file mode 100644
index 0000000000000000000000000000000000000000..12cdd336b304dca0163caea18400be1a16c025ee
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/tessellation/passthrough.tese.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/tessellation/pntriangles.tesc b/external/Vulkan/data/shaders/hlsl/tessellation/pntriangles.tesc
new file mode 100644
index 0000000000000000000000000000000000000000..37f52f0eed20169ce278c28e7beeb648452cd061
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/tessellation/pntriangles.tesc
@@ -0,0 +1,128 @@
+// Copyright 2020 Google LLC
+
+// PN patch data
+struct PnPatch
+{
+	float b210;
+	float b120;
+	float b021;
+	float b012;
+	float b102;
+	float b201;
+	float b111;
+	float n110;
+	float n011;
+	float n101;
+};
+
+// tessellation levels
+struct UBO
+{
+	float tessLevel;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+};
+
+struct HSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(3)]] float2 UV : TEXCOORD0;
+[[vk::location(6)]]	float pnPatch[10] : TEXCOORD6;
+};
+
+struct ConstantsHSOutput
+{
+    float TessLevelOuter[3] : SV_TessFactor;
+    float TessLevelInner : SV_InsideTessFactor;
+};
+
+void SetPnPatch(out float output[10], PnPatch patch)
+{
+	output[0] = patch.b210;
+	output[1] = patch.b120;
+	output[2] = patch.b021;
+	output[3] = patch.b012;
+	output[4] = patch.b102;
+	output[5] = patch.b201;
+	output[6] = patch.b111;
+	output[7] = patch.n110;
+	output[8] = patch.n011;
+	output[9] = patch.n101;
+}
+
+float wij(float4 iPos, float3 iNormal, float4 jPos)
+{
+	return dot(jPos.xyz - iPos.xyz, iNormal);
+}
+
+float vij(float4 iPos, float3 iNormal, float4 jPos, float3 jNormal)
+{
+	float3 Pj_minus_Pi = jPos.xyz
+					- iPos.xyz;
+	float3 Ni_plus_Nj  = iNormal+jNormal;
+	return 2.0*dot(Pj_minus_Pi, Ni_plus_Nj)/dot(Pj_minus_Pi, Pj_minus_Pi);
+}
+
+ConstantsHSOutput ConstantsHS(InputPatch<VSOutput, 3> patch, uint InvocationID : SV_PrimitiveID)
+{
+    ConstantsHSOutput output = (ConstantsHSOutput)0;
+	output.TessLevelOuter[0] = ubo.tessLevel;
+	output.TessLevelOuter[1] = ubo.tessLevel;
+	output.TessLevelOuter[2] = ubo.tessLevel;
+	output.TessLevelInner = ubo.tessLevel;
+    return output;
+}
+
+[domain("tri")]
+[partitioning("fractional_odd")]
+[outputtopology("triangle_ccw")]
+[outputcontrolpoints(3)]
+[patchconstantfunc("ConstantsHS")]
+[maxtessfactor(20.0f)]
+HSOutput main(InputPatch<VSOutput, 3> patch, uint InvocationID : SV_OutputControlPointID)
+{
+	HSOutput output = (HSOutput)0;
+	// get data
+	output.Pos = patch[InvocationID].Pos;
+	output.Normal = patch[InvocationID].Normal;
+	output.UV = patch[InvocationID].UV;
+
+	// set base
+	float P0 = patch[0].Pos[InvocationID];
+	float P1 = patch[1].Pos[InvocationID];
+	float P2 = patch[2].Pos[InvocationID];
+	float N0 = patch[0].Normal[InvocationID];
+	float N1 = patch[1].Normal[InvocationID];
+	float N2 = patch[2].Normal[InvocationID];
+
+	// compute control points
+	PnPatch pnPatch;
+	pnPatch.b210 = (2.0*P0 + P1 - wij(patch[0].Pos, patch[0].Normal, patch[1].Pos)*N0)/3.0;
+	pnPatch.b120 = (2.0*P1 + P0 - wij(patch[1].Pos, patch[1].Normal, patch[0].Pos)*N1)/3.0;
+	pnPatch.b021 = (2.0*P1 + P2 - wij(patch[1].Pos, patch[1].Normal, patch[2].Pos)*N1)/3.0;
+	pnPatch.b012 = (2.0*P2 + P1 - wij(patch[2].Pos, patch[2].Normal, patch[1].Pos)*N2)/3.0;
+	pnPatch.b102 = (2.0*P2 + P0 - wij(patch[2].Pos, patch[2].Normal, patch[0].Pos)*N2)/3.0;
+	pnPatch.b201 = (2.0*P0 + P2 - wij(patch[0].Pos, patch[0].Normal, patch[2].Pos)*N0)/3.0;
+	float E = ( pnPatch.b210
+			+ pnPatch.b120
+			+ pnPatch.b021
+			+ pnPatch.b012
+			+ pnPatch.b102
+			+ pnPatch.b201 ) / 6.0;
+	float V = (P0 + P1 + P2)/3.0;
+	pnPatch.b111 = E + (E - V)*0.5;
+	pnPatch.n110 = N0+N1-vij(patch[0].Pos, patch[0].Normal, patch[1].Pos, patch[1].Normal)*(P1-P0);
+	pnPatch.n011 = N1+N2-vij(patch[1].Pos, patch[1].Normal, patch[2].Pos, patch[2].Normal)*(P2-P1);
+	pnPatch.n101 = N2+N0-vij(patch[2].Pos, patch[2].Normal, patch[0].Pos, patch[0].Normal)*(P0-P2);
+	SetPnPatch(output.pnPatch, pnPatch);
+
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/tessellation/pntriangles.tesc.spv b/external/Vulkan/data/shaders/hlsl/tessellation/pntriangles.tesc.spv
new file mode 100644
index 0000000000000000000000000000000000000000..8d30af5c823c0318d041179eacef63ad1a4da516
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/tessellation/pntriangles.tesc.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/tessellation/pntriangles.tese b/external/Vulkan/data/shaders/hlsl/tessellation/pntriangles.tese
new file mode 100644
index 0000000000000000000000000000000000000000..2969504d3870a5357f160be0a55c2e86027017f4
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/tessellation/pntriangles.tese
@@ -0,0 +1,126 @@
+// Copyright 2020 Google LLC
+
+// PN patch data
+struct PnPatch
+{
+ float b210;
+ float b120;
+ float b021;
+ float b012;
+ float b102;
+ float b201;
+ float b111;
+ float n110;
+ float n011;
+ float n101;
+};
+
+struct UBO
+{
+    float4x4 projection;
+    float4x4 model;
+    float tessAlpha;
+};
+
+cbuffer ubo : register(b1) { UBO ubo; }
+
+struct HSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(3)]] float2 UV : TEXCOORD0;
+[[vk::location(6)]] float pnPatch[10] : TEXCOORD6;
+};
+
+struct ConstantsHSOutput
+{
+    float TessLevelOuter[3] : SV_TessFactor;
+    float TessLevelInner : SV_InsideTessFactor;
+};
+
+struct DSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+};
+
+#define uvw TessCoord
+
+PnPatch GetPnPatch(float pnPatch[10])
+{
+    PnPatch output;
+    output.b210 = pnPatch[0];
+	output.b120 = pnPatch[1];
+	output.b021 = pnPatch[2];
+	output.b012 = pnPatch[3];
+	output.b102 = pnPatch[4];
+	output.b201 = pnPatch[5];
+	output.b111 = pnPatch[6];
+	output.n110 = pnPatch[7];
+	output.n011 = pnPatch[8];
+	output.n101 = pnPatch[9];
+    return output;
+}
+
+[domain("tri")]
+DSOutput main(ConstantsHSOutput input, float3 TessCoord : SV_DomainLocation, const OutputPatch<HSOutput, 3> patch)
+{
+    PnPatch pnPatch[3];
+    pnPatch[0] = GetPnPatch(patch[0].pnPatch);
+    pnPatch[1] = GetPnPatch(patch[1].pnPatch);
+    pnPatch[2] = GetPnPatch(patch[2].pnPatch);
+
+    DSOutput output = (DSOutput)0;
+    float3 uvwSquared = uvw * uvw;
+    float3 uvwCubed   = uvwSquared * uvw;
+
+    // extract control points
+    float3 b210 = float3(pnPatch[0].b210, pnPatch[1].b210, pnPatch[2].b210);
+    float3 b120 = float3(pnPatch[0].b120, pnPatch[1].b120, pnPatch[2].b120);
+    float3 b021 = float3(pnPatch[0].b021, pnPatch[1].b021, pnPatch[2].b021);
+    float3 b012 = float3(pnPatch[0].b012, pnPatch[1].b012, pnPatch[2].b012);
+    float3 b102 = float3(pnPatch[0].b102, pnPatch[1].b102, pnPatch[2].b102);
+    float3 b201 = float3(pnPatch[0].b201, pnPatch[1].b201, pnPatch[2].b201);
+    float3 b111 = float3(pnPatch[0].b111, pnPatch[1].b111, pnPatch[2].b111);
+
+    // extract control normals
+    float3 n110 = normalize(float3(pnPatch[0].n110, pnPatch[1].n110, pnPatch[2].n110));
+    float3 n011 = normalize(float3(pnPatch[0].n011, pnPatch[1].n011, pnPatch[2].n011));
+    float3 n101 = normalize(float3(pnPatch[0].n101, pnPatch[1].n101, pnPatch[2].n101));
+
+    // compute texcoords
+    output.UV  = TessCoord[2]*patch[0].UV + TessCoord[0]*patch[1].UV + TessCoord[1]*patch[2].UV;
+
+    // normal
+    // Barycentric normal
+    float3 barNormal = TessCoord[2]*patch[0].Normal + TessCoord[0]*patch[1].Normal + TessCoord[1]*patch[2].Normal;
+    float3 pnNormal  = patch[0].Normal*uvwSquared[2] + patch[1].Normal*uvwSquared[0] + patch[2].Normal*uvwSquared[1]
+                   + n110*uvw[2]*uvw[0] + n011*uvw[0]*uvw[1]+ n101*uvw[2]*uvw[1];
+    output.Normal = ubo.tessAlpha*pnNormal + (1.0-ubo.tessAlpha) * barNormal;
+
+    // compute interpolated pos
+    float3 barPos = TessCoord[2]*patch[0].Pos.xyz
+                + TessCoord[0]*patch[1].Pos.xyz
+                + TessCoord[1]*patch[2].Pos.xyz;
+
+    // save some computations
+    uvwSquared *= 3.0;
+
+    // compute PN position
+    float3 pnPos  = patch[0].Pos.xyz*uvwCubed[2]
+                + patch[1].Pos.xyz*uvwCubed[0]
+                + patch[2].Pos.xyz*uvwCubed[1]
+                + b210*uvwSquared[2]*uvw[0]
+                + b120*uvwSquared[0]*uvw[2]
+                + b201*uvwSquared[2]*uvw[1]
+                + b021*uvwSquared[0]*uvw[1]
+                + b102*uvwSquared[1]*uvw[2]
+                + b012*uvwSquared[1]*uvw[0]
+                + b111*6.0*uvw[0]*uvw[1]*uvw[2];
+
+    // final position and normal
+    float3 finalPos = (1.0-ubo.tessAlpha)*barPos + ubo.tessAlpha*pnPos;
+    output.Pos = mul(ubo.projection, mul(ubo.model, float4(finalPos,1.0)));
+    return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/tessellation/pntriangles.tese.spv b/external/Vulkan/data/shaders/hlsl/tessellation/pntriangles.tese.spv
new file mode 100644
index 0000000000000000000000000000000000000000..f2cce16820bd65e660750f06f9c7073a9de3ce96
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/tessellation/pntriangles.tese.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/textoverlay/mesh.frag b/external/Vulkan/data/shaders/hlsl/textoverlay/mesh.frag
new file mode 100644
index 0000000000000000000000000000000000000000..f2eb9b6fc9c28684b83ad9b6df1facb588824873
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/textoverlay/mesh.frag
@@ -0,0 +1,20 @@
+// Copyright 2020 Google LLC
+
+struct VSOutput
+{
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+[[vk::location(2)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(3)]] float3 LightVec : TEXCOORD2;
+};
+
+float4 main(VSOutput input) : SV_TARGET
+{
+	float3 N = normalize(input.Normal);
+	float3 L = normalize(input.LightVec);
+	float3 V = normalize(input.ViewVec);
+	float3 R = reflect(-L, N);
+	float diffuse = max(dot(N, L), 0.0);
+	float specular = pow(max(dot(R, V), 0.0), 1.0);
+	return float4(((diffuse + specular) * 0.25).xxx, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/textoverlay/mesh.frag.spv b/external/Vulkan/data/shaders/hlsl/textoverlay/mesh.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..6e2c26afcfddcb36a24cf4c99c0832651958510f
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/textoverlay/mesh.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/textoverlay/mesh.vert b/external/Vulkan/data/shaders/hlsl/textoverlay/mesh.vert
new file mode 100644
index 0000000000000000000000000000000000000000..2abf875ac658f0e5f7deab977ce632c90ea73738
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/textoverlay/mesh.vert
@@ -0,0 +1,41 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float2 UV : TEXCOORD0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+	float4 lightPos;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+[[vk::location(2)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(3)]] float3 LightVec : TEXCOORD2;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Normal = input.Normal;
+	output.UV = input.UV;
+	output.Pos = mul(ubo.projection, mul(ubo.model, float4(input.Pos.xyz, 1.0)));
+
+	float4 pos = mul(ubo.model, float4(input.Pos, 1.0));
+	output.Normal = mul((float3x3)ubo.model, normalize(input.Normal));
+	float3 lPos = mul((float3x3)ubo.model, ubo.lightPos.xyz);
+	output.LightVec = lPos - pos.xyz;
+	output.ViewVec = -pos.xyz;
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/textoverlay/mesh.vert.spv b/external/Vulkan/data/shaders/hlsl/textoverlay/mesh.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..8430788df5f6850f111bc3c73f92ea188d51ae40
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/textoverlay/mesh.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/textoverlay/text.frag b/external/Vulkan/data/shaders/hlsl/textoverlay/text.frag
new file mode 100644
index 0000000000000000000000000000000000000000..7525bd199de4285d666eaebdd818314a301da2d9
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/textoverlay/text.frag
@@ -0,0 +1,10 @@
+// Copyright 2020 Google LLC
+
+Texture2D textureFont : register(t0);
+SamplerState samplerFont : register(s0);
+
+float4 main([[vk::location(0)]] float2 inUV : TEXCOORD0) : SV_TARGET
+{
+	float color = textureFont.Sample(samplerFont, inUV).r;
+	return color.xxxx;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/textoverlay/text.frag.spv b/external/Vulkan/data/shaders/hlsl/textoverlay/text.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..eb9d32312d23287e304d0b9afc6bae760d2cccb5
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/textoverlay/text.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/textoverlay/text.vert b/external/Vulkan/data/shaders/hlsl/textoverlay/text.vert
new file mode 100644
index 0000000000000000000000000000000000000000..5de884819d5b54fda8d95ed63eca4ac4e5a3da50
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/textoverlay/text.vert
@@ -0,0 +1,21 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float2 Pos : POSITION0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+};
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float2 UV : TEXCOORD0;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Pos = float4(input.Pos, 0.0, 1.0);
+	output.UV = input.UV;
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/textoverlay/text.vert.spv b/external/Vulkan/data/shaders/hlsl/textoverlay/text.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..4870eb420dd28d374237042f33359751c4d51ca0
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/textoverlay/text.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/texture/texture.frag b/external/Vulkan/data/shaders/hlsl/texture/texture.frag
new file mode 100644
index 0000000000000000000000000000000000000000..78ce0f939b4b26b1c315af85d8e14b3438a19d96
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/texture/texture.frag
@@ -0,0 +1,27 @@
+// Copyright 2020 Google LLC
+
+Texture2D textureColor : register(t1);
+SamplerState samplerColor : register(s1);
+
+struct VSOutput
+{
+[[vk::location(0)]] float2 UV : TEXCOORD0;
+[[vk::location(1)]] float LodBias : TEXCOORD3;
+[[vk::location(2)]] float3 Normal : NORMAL0;
+[[vk::location(3)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(4)]] float3 LightVec : TEXCOORD2;
+};
+
+float4 main(VSOutput input) : SV_TARGET
+{
+	float4 color = textureColor.SampleLevel(samplerColor, input.UV, input.LodBias);
+
+	float3 N = normalize(input.Normal);
+	float3 L = normalize(input.LightVec);
+	float3 V = normalize(input.ViewVec);
+	float3 R = reflect(-L, N);
+	float3 diffuse = max(dot(N, L), 0.0) * float3(1.0, 1.0, 1.0);
+	float specular = pow(max(dot(R, V), 0.0), 16.0) * color.a;
+
+	return float4(diffuse * color.rgb + specular, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/texture/texture.frag.spv b/external/Vulkan/data/shaders/hlsl/texture/texture.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..83be30a4df360725ffe492ea4b26dbe4151f73ba
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/texture/texture.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/texture/texture.vert b/external/Vulkan/data/shaders/hlsl/texture/texture.vert
new file mode 100644
index 0000000000000000000000000000000000000000..133d53b16a0c9e456fe5f169fbfbf0cc3c8e5658
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/texture/texture.vert
@@ -0,0 +1,47 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+[[vk::location(2)]] float3 Normal : NORMAL0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+	float4 viewPos;
+	float lodBias;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float2 UV : TEXCOORD0;
+[[vk::location(1)]] float LodBias : TEXCOORD3;
+[[vk::location(2)]] float3 Normal : NORMAL0;
+[[vk::location(3)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(4)]] float3 LightVec : TEXCOORD2;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.UV = input.UV;
+	output.LodBias = ubo.lodBias;
+
+	float3 worldPos = mul(ubo.model, float4(input.Pos, 1.0)).xyz;
+
+	output.Pos = mul(ubo.projection, mul(ubo.model, float4(input.Pos.xyz, 1.0)));
+
+    float4 pos = mul(ubo.model, float4(input.Pos, 1.0));
+	output.Normal = mul((float3x3)ubo.model, input.Normal);
+	float3 lightPos = float3(0.0, 0.0, 0.0);
+	float3 lPos = mul((float3x3)ubo.model, lightPos.xyz);
+    output.LightVec = lPos - pos.xyz;
+    output.ViewVec = ubo.viewPos.xyz - pos.xyz;
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/texture/texture.vert.spv b/external/Vulkan/data/shaders/hlsl/texture/texture.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..61289a7dc06146b668f82618063c4853bf34b76d
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/texture/texture.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/texture3d/texture3d.frag b/external/Vulkan/data/shaders/hlsl/texture3d/texture3d.frag
new file mode 100644
index 0000000000000000000000000000000000000000..f9375205d1d2525842a31250454a52ab9b08a39e
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/texture3d/texture3d.frag
@@ -0,0 +1,27 @@
+// Copyright 2020 Google LLC
+
+Texture3D textureColor : register(t1);
+SamplerState samplerColor : register(s1);
+
+struct VSOutput
+{
+[[vk::location(0)]] float3 UV : TEXCOORD0;
+[[vk::location(1)]] float LodBias : TEXCOORD3;
+[[vk::location(2)]] float3 Normal : NORMAL0;
+[[vk::location(3)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(4)]] float3 LightVec : TEXCOORD2;
+};
+
+float4 main(VSOutput input) : SV_TARGET
+{
+	float4 color = textureColor.Sample(samplerColor, input.UV);
+
+	float3 N = normalize(input.Normal);
+	float3 L = normalize(input.LightVec);
+	float3 V = normalize(input.ViewVec);
+	float3 R = reflect(-L, N);
+	float3 diffuse = max(dot(N, L), 0.0) * float3(1.0, 1.0, 1.0);
+	float specular = pow(max(dot(R, V), 0.0), 16.0) * color.r;
+
+	return float4(diffuse * color.r + specular, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/texture3d/texture3d.frag.spv b/external/Vulkan/data/shaders/hlsl/texture3d/texture3d.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..064b49cb03d12b5ca642d96278854effe1d29abe
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/texture3d/texture3d.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/texture3d/texture3d.vert b/external/Vulkan/data/shaders/hlsl/texture3d/texture3d.vert
new file mode 100644
index 0000000000000000000000000000000000000000..39c9681cb6a8cbe8af361fd5ab119687cc93babf
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/texture3d/texture3d.vert
@@ -0,0 +1,46 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+[[vk::location(2)]] float3 Normal : NORMAL0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+	float4 viewPos;
+	float depth;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 UV : TEXCOORD0;
+[[vk::location(1)]] float LodBias : TEXCOORD3;
+[[vk::location(2)]] float3 Normal : NORMAL0;
+[[vk::location(3)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(4)]] float3 LightVec : TEXCOORD2;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.UV = float3(input.UV, ubo.depth);
+
+	float3 worldPos = mul(ubo.model, float4(input.Pos, 1.0)).xyz;
+
+	output.Pos = mul(ubo.projection, mul(ubo.model, float4(input.Pos.xyz, 1.0)));
+
+	float4 pos = mul(ubo.model, float4(input.Pos, 1.0));
+	output.Normal = mul((float3x3)ubo.model, input.Normal);
+	float3 lightPos = float3(0.0, 0.0, 0.0);
+	float3 lPos = mul((float3x3)ubo.model, lightPos.xyz);
+	output.LightVec = lPos - pos.xyz;
+	output.ViewVec = ubo.viewPos.xyz - pos.xyz;
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/texture3d/texture3d.vert.spv b/external/Vulkan/data/shaders/hlsl/texture3d/texture3d.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..b9f084eee6fb31363301eab6ce9a45049eb7480a
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/texture3d/texture3d.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/texturearray/instancing.frag b/external/Vulkan/data/shaders/hlsl/texturearray/instancing.frag
new file mode 100644
index 0000000000000000000000000000000000000000..85ed6eae31c306bb513538b40ce6ea3034101ee3
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/texturearray/instancing.frag
@@ -0,0 +1,9 @@
+// Copyright 2020 Google LLC
+
+Texture2DArray textureArray : register(t1);
+SamplerState samplerArray : register(s1);
+
+float4 main([[vk::location(0)]] float3 inUV : TEXCOORD0) : SV_TARGET
+{
+	return textureArray.Sample(samplerArray, inUV);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/texturearray/instancing.frag.spv b/external/Vulkan/data/shaders/hlsl/texturearray/instancing.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..66562c2542a8c554ad39e93c839c4e319bfaf439
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/texturearray/instancing.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/texturearray/instancing.vert b/external/Vulkan/data/shaders/hlsl/texturearray/instancing.vert
new file mode 100644
index 0000000000000000000000000000000000000000..d9d77d524caab5c44cf8b2fa9c3b6c1c513c69ed
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/texturearray/instancing.vert
@@ -0,0 +1,37 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+};
+
+struct Instance
+{
+	float4x4 model;
+	float4 arrayIndex;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 view;
+	Instance instance[8];
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 UV : TEXCOORD0;
+};
+
+VSOutput main(VSInput input, uint InstanceIndex : SV_InstanceID)
+{
+	VSOutput output = (VSOutput)0;
+	output.UV = float3(input.UV, ubo.instance[InstanceIndex].arrayIndex.x);
+	float4x4 modelView = mul(ubo.view, ubo.instance[InstanceIndex].model);
+	output.Pos = mul(ubo.projection, mul(modelView, float4(input.Pos, 1.0)));
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/texturearray/instancing.vert.spv b/external/Vulkan/data/shaders/hlsl/texturearray/instancing.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..ad916322b8b2e1792ae5511647757a54fcbdf92c
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/texturearray/instancing.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/texturecubemap/reflect.frag b/external/Vulkan/data/shaders/hlsl/texturecubemap/reflect.frag
new file mode 100644
index 0000000000000000000000000000000000000000..7cf3efd0e4e6e0146a4443c83abad6cdfe593922
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/texturecubemap/reflect.frag
@@ -0,0 +1,44 @@
+// Copyright 2020 Google LLC
+
+TextureCube textureColor : register(t1);
+SamplerState samplerColor : register(s1);
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+	float4x4 invModel;
+	float lodBias;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float LodBias : TEXCOORD3;
+[[vk::location(3)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(4)]] float3 LightVec : TEXCOORD2;
+};
+
+float4 main(VSOutput input) : SV_TARGET
+{
+	float3 cI = normalize (input.ViewVec);
+	float3 cR = reflect (cI, normalize(input.Normal));
+
+	cR = mul(ubo.invModel, float4(cR, 0.0)).xyz;
+	// Convert cubemap coordinates into Vulkan coordinate space
+	cR.z *= -1.0;
+
+	float4 color = textureColor.SampleLevel(samplerColor, cR, input.LodBias);
+
+	float3 N = normalize(input.Normal);
+	float3 L = normalize(input.LightVec);
+	float3 V = normalize(input.ViewVec);
+	float3 R = reflect(-L, N);
+	float3 ambient = float3(0.5, 0.5, 0.5) * color.rgb;
+	float3 diffuse = max(dot(N, L), 0.0) * float3(1.0, 1.0, 1.0);
+	float3 specular = pow(max(dot(R, V), 0.0), 16.0) * float3(0.5, 0.5, 0.5);
+	return float4(ambient + diffuse * color.rgb + specular, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/texturecubemap/reflect.frag.spv b/external/Vulkan/data/shaders/hlsl/texturecubemap/reflect.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..02fa6b35eba228decf8a108f10a6113b51e9f20a
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/texturecubemap/reflect.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/texturecubemap/reflect.vert b/external/Vulkan/data/shaders/hlsl/texturecubemap/reflect.vert
new file mode 100644
index 0000000000000000000000000000000000000000..9323ee4c2cae71a40c03413727a1fc45fc7ef669
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/texturecubemap/reflect.vert
@@ -0,0 +1,42 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+	float4x4 invModel;
+	float lodBias;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 WorldPos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float LodBias : TEXCOORD3;
+[[vk::location(3)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(4)]] float3 LightVec : TEXCOORD2;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Pos = mul(ubo.projection, mul(ubo.model, float4(input.Pos.xyz, 1.0)));
+
+	output.WorldPos = mul(ubo.model, float4(input.Pos, 1.0)).xyz;
+	output.Normal = mul((float3x3)ubo.model, input.Normal);
+	output.LodBias = ubo.lodBias;
+
+	float3 lightPos = float3(0.0f, -5.0f, 5.0f);
+	output.LightVec = lightPos.xyz - output.WorldPos.xyz;
+	output.ViewVec = -output.WorldPos;
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/texturecubemap/reflect.vert.spv b/external/Vulkan/data/shaders/hlsl/texturecubemap/reflect.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..85b14c3582eba6fcc4af3f82ff928b112fdfc2f7
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/texturecubemap/reflect.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/texturecubemap/skybox.frag b/external/Vulkan/data/shaders/hlsl/texturecubemap/skybox.frag
new file mode 100644
index 0000000000000000000000000000000000000000..bd2ba96d6b4f269ab1ec15e3e971978245e438fc
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/texturecubemap/skybox.frag
@@ -0,0 +1,9 @@
+// Copyright 2020 Google LLC
+
+TextureCube textureCubeMap : register(t1);
+SamplerState samplerCubeMap : register(s1);
+
+float4 main([[vk::location(0)]] float3 inUVW : TEXCOORD0) : SV_TARGET
+{
+	return textureCubeMap.Sample(samplerCubeMap, inUVW);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/texturecubemap/skybox.frag.spv b/external/Vulkan/data/shaders/hlsl/texturecubemap/skybox.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..94b27658a44084b1a17c0ec55a9630545fd6b4ba
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/texturecubemap/skybox.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/texturecubemap/skybox.vert b/external/Vulkan/data/shaders/hlsl/texturecubemap/skybox.vert
new file mode 100644
index 0000000000000000000000000000000000000000..632f3cea713303bdf8fa43c60782cec4e77442e6
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/texturecubemap/skybox.vert
@@ -0,0 +1,25 @@
+// Copyright 2020 Google LLC
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 UVW : TEXCOORD0;
+};
+
+VSOutput main([[vk::location(0)]] float3 Pos : POSITION0)
+{
+	VSOutput output = (VSOutput)0;
+	output.UVW = Pos;
+	// Convert cubemap coordinates into Vulkan coordinate space
+	output.UVW.xy *= -1.0;
+	output.Pos = mul(ubo.projection, mul(ubo.model, float4(Pos.xyz, 1.0)));
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/texturecubemap/skybox.vert.spv b/external/Vulkan/data/shaders/hlsl/texturecubemap/skybox.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..e95a2708bd7b45afa728bc967f8756015759899f
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/texturecubemap/skybox.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/texturecubemaparray/reflect.frag b/external/Vulkan/data/shaders/hlsl/texturecubemaparray/reflect.frag
new file mode 100644
index 0000000000000000000000000000000000000000..4fe024c9a419f4118457a96bdbd0783874185e07
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/texturecubemaparray/reflect.frag
@@ -0,0 +1,44 @@
+// Copyright 2020 Google LLC
+
+TextureCubeArray textureCubeMapArray : register(t1);
+SamplerState samplerCubeMapArray : register(s1);
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+	float4x4 invModel;
+	float lodBias;
+	int cubeMapIndex;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float LodBias : TEXCOORD3;
+[[vk::location(3)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(4)]] float3 LightVec : TEXCOORD2;
+};
+
+float4 main(VSOutput input) : SV_TARGET
+{
+	float3 cI = normalize (input.Pos);
+	float3 cR = reflect (cI, normalize(input.Normal));
+
+	cR = mul(ubo.invModel, float4(cR, 0.0)).xyz;
+	cR *= float3(1.0, -1.0, -1.0);
+
+	float4 color = textureCubeMapArray.SampleLevel(samplerCubeMapArray, float4(cR, ubo.cubeMapIndex), input.LodBias);
+
+	float3 N = normalize(input.Normal);
+	float3 L = normalize(input.LightVec);
+	float3 V = normalize(input.ViewVec);
+	float3 R = reflect(-L, N);
+	float3 ambient = float3(0.5, 0.5, 0.5) * color.rgb;
+	float3 diffuse = max(dot(N, L), 0.0) * float3(1.0, 1.0, 1.0);
+	float3 specular = pow(max(dot(R, V), 0.0), 16.0) * float3(0.5, 0.5, 0.5);
+	return float4(ambient + diffuse * color.rgb + specular, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/texturecubemaparray/reflect.frag.spv b/external/Vulkan/data/shaders/hlsl/texturecubemaparray/reflect.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..11ae34e15f2d1bccbe0e4f1016f80f0fddaa2e01
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/texturecubemaparray/reflect.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/texturecubemaparray/reflect.vert b/external/Vulkan/data/shaders/hlsl/texturecubemaparray/reflect.vert
new file mode 100644
index 0000000000000000000000000000000000000000..9323ee4c2cae71a40c03413727a1fc45fc7ef669
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/texturecubemaparray/reflect.vert
@@ -0,0 +1,42 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+	float4x4 invModel;
+	float lodBias;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 WorldPos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float LodBias : TEXCOORD3;
+[[vk::location(3)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(4)]] float3 LightVec : TEXCOORD2;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Pos = mul(ubo.projection, mul(ubo.model, float4(input.Pos.xyz, 1.0)));
+
+	output.WorldPos = mul(ubo.model, float4(input.Pos, 1.0)).xyz;
+	output.Normal = mul((float3x3)ubo.model, input.Normal);
+	output.LodBias = ubo.lodBias;
+
+	float3 lightPos = float3(0.0f, -5.0f, 5.0f);
+	output.LightVec = lightPos.xyz - output.WorldPos.xyz;
+	output.ViewVec = -output.WorldPos;
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/texturecubemaparray/reflect.vert.spv b/external/Vulkan/data/shaders/hlsl/texturecubemaparray/reflect.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..85b14c3582eba6fcc4af3f82ff928b112fdfc2f7
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/texturecubemaparray/reflect.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/texturecubemaparray/skybox.frag b/external/Vulkan/data/shaders/hlsl/texturecubemaparray/skybox.frag
new file mode 100644
index 0000000000000000000000000000000000000000..10807de6aee316b08a89da9ae0785f27fee55af2
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/texturecubemaparray/skybox.frag
@@ -0,0 +1,20 @@
+// Copyright 2020 Google LLC
+
+TextureCubeArray textureCubeMapArray : register(t1);
+SamplerState samplerCubeMapArray : register(s1);
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+	float4x4 invModel;
+	float lodBias;
+	int cubeMapIndex;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+float4 main([[vk::location(0)]] float3 inUVW : TEXCOORD0) : SV_TARGET
+{
+	return textureCubeMapArray.SampleLevel(samplerCubeMapArray, float4(inUVW, ubo.cubeMapIndex), ubo.lodBias);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/texturecubemaparray/skybox.frag.spv b/external/Vulkan/data/shaders/hlsl/texturecubemaparray/skybox.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..111db475b95626c9fe21c2a62716ba190aa0d0a8
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/texturecubemaparray/skybox.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/texturecubemaparray/skybox.vert b/external/Vulkan/data/shaders/hlsl/texturecubemaparray/skybox.vert
new file mode 100644
index 0000000000000000000000000000000000000000..37f07a082b3451d5979645a1cf5edc9373d5d71a
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/texturecubemaparray/skybox.vert
@@ -0,0 +1,27 @@
+// Copyright 2020 Google LLC
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+	float4x4 invModel;
+	float lodBias;
+	int cubeMapIndex;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 UVW : TEXCOORD0;
+};
+
+VSOutput main([[vk::location(0)]] float3 Pos : POSITION0)
+{
+	VSOutput output = (VSOutput)0;
+	output.UVW = Pos;
+	output.UVW.yz *= -1.0;
+	output.Pos = mul(ubo.projection, mul(ubo.model, float4(Pos.xyz, 1.0)));
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/texturecubemaparray/skybox.vert.spv b/external/Vulkan/data/shaders/hlsl/texturecubemaparray/skybox.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..a086e9d619932fb2c8b24c7355f593f24a790573
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/texturecubemaparray/skybox.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/texturemipmapgen/texture.frag b/external/Vulkan/data/shaders/hlsl/texturemipmapgen/texture.frag
new file mode 100644
index 0000000000000000000000000000000000000000..9f58e19d1e44086ad3f5965e44f07db07c0a9272
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/texturemipmapgen/texture.frag
@@ -0,0 +1,27 @@
+// Copyright 2020 Google LLC
+
+Texture2D textureColor : register(t1);
+SamplerState samplers[3] : register(s2);
+
+struct VSOutput
+{
+[[vk::location(0)]] float2 UV : TEXCOORD0;
+[[vk::location(1)]] float LodBias : TEXCOORD3;
+[[vk::location(2)]] int SamplerIndex : TEXCOORD4;
+[[vk::location(3)]] float3 Normal : NORMAL0;
+[[vk::location(4)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(5)]] float3 LightVec : TEXCOORD2;
+};
+
+float4 main(VSOutput input) : SV_TARGET
+{
+	float4 color = textureColor.Sample(samplers[input.SamplerIndex], input.UV, int2(0, 0), input.LodBias);
+
+	float3 N = normalize(input.Normal);
+	float3 L = normalize(input.LightVec);
+	float3 V = normalize(input.ViewVec);
+	float3 R = reflect(L, N);
+	float3 diffuse = max(dot(N, L), 0.65) * float3(1.0, 1.0, 1.0);
+	float specular = pow(max(dot(R, V), 0.0), 16.0) * color.a;
+	return float4(diffuse * color.rgb + specular, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/texturemipmapgen/texture.frag.spv b/external/Vulkan/data/shaders/hlsl/texturemipmapgen/texture.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..89a191605803fc93bcf1b4422f94dc211a5a77aa
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/texturemipmapgen/texture.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/texturemipmapgen/texture.vert b/external/Vulkan/data/shaders/hlsl/texturemipmapgen/texture.vert
new file mode 100644
index 0000000000000000000000000000000000000000..8b5f303693956528fd223a6cb07cfdffb08a3d2c
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/texturemipmapgen/texture.vert
@@ -0,0 +1,49 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float2 UV : TEXCOORD0;
+[[vk::location(2)]] float3 Normal : NORMAL0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 view;
+	float4x4 model;
+	float4 viewPos;
+	float lodBias;
+	int samplerIndex;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float2 UV : TEXCOORD0;
+[[vk::location(1)]] float LodBias : TEXCOORD3;
+[[vk::location(2)]] int SamplerIndex : TEXCOORD4;
+[[vk::location(3)]] float3 Normal : NORMAL0;
+[[vk::location(4)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(5)]] float3 LightVec : TEXCOORD2;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.UV = input.UV * float2(2.0, 1.0);
+	output.LodBias = ubo.lodBias;
+	output.SamplerIndex = ubo.samplerIndex;
+
+	float3 worldPos = mul(ubo.model, float4(input.Pos, 1.0)).xyz;
+
+	output.Pos = mul(ubo.projection, mul(ubo.view, mul(ubo.model, float4(input.Pos.xyz, 1.0))));
+
+	output.Normal = mul((float3x3)ubo.model, input.Normal);
+	float3 lightPos = float3(-30.0, 0.0, 0.0);
+	output.LightVec = worldPos - lightPos;
+	output.ViewVec = ubo.viewPos.xyz - worldPos;
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/texturemipmapgen/texture.vert.spv b/external/Vulkan/data/shaders/hlsl/texturemipmapgen/texture.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..de4cb54486f9eb5d6da2507997ff422fb91d7abb
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/texturemipmapgen/texture.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/texturesparseresidency/sparseresidency.frag b/external/Vulkan/data/shaders/hlsl/texturesparseresidency/sparseresidency.frag
new file mode 100644
index 0000000000000000000000000000000000000000..2745854579f67bd36ce64f6ab193dcfb11e019c5
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/texturesparseresidency/sparseresidency.frag
@@ -0,0 +1,36 @@
+// Copyright 2020 Google LLC
+
+Texture2D textureColor : register(t1);
+SamplerState samplerColor : register(s1);
+
+struct VSOutput
+{
+[[vk::location(0)]] float2 UV : TEXCOORD0;
+[[vk::location(1)]] float LodBias : TEXCOORD3;
+[[vk::location(2)]] float3 Normal : NORMAL0;
+[[vk::location(3)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(4)]] float3 LightVec : TEXCOORD2;
+};
+
+float4 main(VSOutput input) : SV_TARGET
+{
+	float4 color = float4(0.0, 0.0, 0.0, 0.0);
+
+	// Fetch sparse until we get a valid texel
+	uint status;
+	float minLod = input.LodBias;
+	do
+	{
+		color = textureColor.SampleLevel(samplerColor, input.UV, minLod, 0, status);
+		minLod += 1.0f;
+	} while(!CheckAccessFullyMapped(status));
+
+	float3 N = normalize(input.Normal);
+
+	N = normalize((input.Normal - 0.5) * 2.0);
+
+	float3 L = normalize(input.LightVec);
+	float3 R = reflect(-L, N);
+	float3 diffuse = max(dot(N, L), 0.25) * color.rgb;
+	return float4(diffuse, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/texturesparseresidency/sparseresidency.frag.spv b/external/Vulkan/data/shaders/hlsl/texturesparseresidency/sparseresidency.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..4249b2a20e77544270311f529557cb11eee8adfb
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/texturesparseresidency/sparseresidency.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/texturesparseresidency/sparseresidency.vert b/external/Vulkan/data/shaders/hlsl/texturesparseresidency/sparseresidency.vert
new file mode 100644
index 0000000000000000000000000000000000000000..b5fb1236310d018166ece6c0faa23f1aee7c5ed2
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/texturesparseresidency/sparseresidency.vert
@@ -0,0 +1,45 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float2 UV : TEXCOORD0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+	float4 viewPos;
+	float lodBias;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float2 UV : TEXCOORD0;
+[[vk::location(1)]] float LodBias : TEXCOORD3;
+[[vk::location(2)]] float3 Normal : NORMAL0;
+[[vk::location(3)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(4)]] float3 LightVec : TEXCOORD2;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.UV = input.UV;
+	output.LodBias = ubo.lodBias;
+	output.Normal = input.Normal;
+
+	float3 worldPos = mul(ubo.model, float4(input.Pos, 1.0)).xyz;
+
+	output.Pos = mul(ubo.projection, mul(ubo.model, float4(input.Pos.xyz, 1.0)));
+
+	float3 lightPos = float3(0.0, 50.0f, 0.0f);
+	output.LightVec = lightPos - input.Pos.xyz;
+	output.ViewVec = ubo.viewPos.xyz - worldPos.xyz;
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/texturesparseresidency/sparseresidency.vert.spv b/external/Vulkan/data/shaders/hlsl/texturesparseresidency/sparseresidency.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..4a01d7cbea5e5f8d6cc9b9e372c4d87cb50f1497
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/texturesparseresidency/sparseresidency.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/triangle/triangle.frag b/external/Vulkan/data/shaders/hlsl/triangle/triangle.frag
new file mode 100644
index 0000000000000000000000000000000000000000..8a7faf06abc734ae0d82ddcf894b2fc7a77eb795
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/triangle/triangle.frag
@@ -0,0 +1,6 @@
+// Copyright 2020 Google LLC
+
+float4 main([[vk::location(0)]] float3 Color : COLOR0) : SV_TARGET
+{
+  return float4(Color, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/triangle/triangle.frag.spv b/external/Vulkan/data/shaders/hlsl/triangle/triangle.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..870fb6c12ac71dca568d8358a955b81f6eca7faf
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/triangle/triangle.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/triangle/triangle.vert b/external/Vulkan/data/shaders/hlsl/triangle/triangle.vert
new file mode 100644
index 0000000000000000000000000000000000000000..881674c87252b9e9112724f2da068029712cf328
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/triangle/triangle.vert
@@ -0,0 +1,30 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float3 Color : COLOR0;
+};
+
+struct UBO
+{
+	float4x4 projectionMatrix;
+	float4x4 modelMatrix;
+	float4x4 viewMatrix;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Color : COLOR0;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Color = input.Color;
+	output.Pos = mul(ubo.projectionMatrix, mul(ubo.viewMatrix, mul(ubo.modelMatrix, float4(input.Pos.xyz, 1.0))));
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/triangle/triangle.vert.spv b/external/Vulkan/data/shaders/hlsl/triangle/triangle.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..ebd52799bf0ba00ad10a93283900216be8ad17a0
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/triangle/triangle.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/variablerateshading/scene.frag b/external/Vulkan/data/shaders/hlsl/variablerateshading/scene.frag
new file mode 100644
index 0000000000000000000000000000000000000000..823089944eaa1a442bbaa7ab174cedfb473352bc
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/variablerateshading/scene.frag
@@ -0,0 +1,83 @@
+// Copyright 2020 Sascha Willems
+
+Texture2D textureColorMap : register(t0, space1);
+SamplerState samplerColorMap : register(s0, space1);
+Texture2D textureNormalMap : register(t1, space1);
+SamplerState samplerNormalMap : register(s1, space1);
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 view;
+	float4x4 model;
+	float4 lightPos;
+	float4 viewPos;
+	int colorShadingRates;
+};
+cbuffer ubo : register(b0) { UBO ubo; };
+
+[[vk::constant_id(0)]] const bool ALPHA_MASK = false;
+[[vk::constant_id(1)]] const float ALPHA_MASK_CUTOFF = 0.0;
+
+struct VSOutput
+{
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float2 UV : TEXCOORD0;
+[[vk::location(3)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(4)]] float3 LightVec : TEXCOORD2;
+[[vk::location(5)]] float4 Tangent : TEXCOORD3;
+};
+
+float4 main(VSOutput input, uint shadingRate : SV_ShadingRate) : SV_TARGET
+{
+	float4 color = textureColorMap.Sample(samplerColorMap, input.UV) * float4(input.Color, 1.0);
+
+	if (ALPHA_MASK) {
+		if (color.a < ALPHA_MASK_CUTOFF) {
+			discard;
+		}
+	}
+
+	float3 N = normalize(input.Normal);
+	float3 T = normalize(input.Tangent.xyz);
+	float3 B = cross(input.Normal, input.Tangent.xyz) * input.Tangent.w;
+	float3x3 TBN = float3x3(T, B, N);
+	N = mul(normalize(textureNormalMap.Sample(samplerNormalMap, input.UV).xyz * 2.0 - float3(1.0, 1.0, 1.0)), TBN);
+
+	const float ambient = 0.1;
+	float3 L = normalize(input.LightVec);
+	float3 V = normalize(input.ViewVec);
+	float3 R = reflect(-L, N);
+	float3 diffuse = max(dot(N, L), ambient).rrr;
+	float3 specular = pow(max(dot(R, V), 0.0), 32.0);
+	color =  float4(diffuse * color.rgb + specular, color.a);
+
+    const uint SHADING_RATE_PER_PIXEL = 0x0;
+    const uint SHADING_RATE_PER_2X1_PIXELS = 6;
+    const uint SHADING_RATE_PER_1X2_PIXELS = 7;
+    const uint SHADING_RATE_PER_2X2_PIXELS = 8;
+    const uint SHADING_RATE_PER_4X2_PIXELS = 9;
+    const uint SHADING_RATE_PER_2X4_PIXELS = 10;
+
+	if (ubo.colorShadingRates == 1) {
+		switch(shadingRate) {
+			case SHADING_RATE_PER_PIXEL:
+				return color * float4(0.0, 0.8, 0.4, 1.0);
+			case SHADING_RATE_PER_2X1_PIXELS:
+				return color * float4(0.2, 0.6, 1.0, 1.0);
+			case SHADING_RATE_PER_1X2_PIXELS:
+				return color * float4(0.0, 0.4, 0.8, 1.0);
+			case SHADING_RATE_PER_2X2_PIXELS:
+				return color * float4(1.0, 1.0, 0.2, 1.0);
+			case SHADING_RATE_PER_4X2_PIXELS:
+				return color * float4(0.8, 0.8, 0.0, 1.0);
+			case SHADING_RATE_PER_2X4_PIXELS:
+				return color * float4(1.0, 0.4, 0.2, 1.0);
+		default:
+			return color * float4(0.8, 0.0, 0.0, 1.0);
+		}
+	}
+
+	return color;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/variablerateshading/scene.frag.spv b/external/Vulkan/data/shaders/hlsl/variablerateshading/scene.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..48d484d817bc940c3210bcc57e6d214269105522
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/variablerateshading/scene.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/variablerateshading/scene.vert b/external/Vulkan/data/shaders/hlsl/variablerateshading/scene.vert
new file mode 100644
index 0000000000000000000000000000000000000000..eeffc129aa117d72429e45f0e4a776afab8d7377
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/variablerateshading/scene.vert
@@ -0,0 +1,51 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float2 UV : TEXCOORD0;
+[[vk::location(3)]] float3 Color : COLOR0;
+[[vk::location(4)]] float4 Tangent : TEXCOORD1;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 view;
+	float4x4 model;
+	float4 lightPos;
+	float4 viewPos;
+	int colorShadingRates;
+};
+cbuffer ubo : register(b0) { UBO ubo; };
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float2 UV : TEXCOORD0;
+[[vk::location(3)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(4)]] float3 LightVec : TEXCOORD2;
+[[vk::location(5)]] float4 Tangent : TEXCOORD3;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Normal = input.Normal;
+	output.Color = input.Color;
+	output.UV = input.UV;
+	output.Tangent = input.Tangent;
+
+	float4x4 modelView = mul(ubo.view, ubo.model);
+
+	output.Pos = mul(ubo.projection, mul(modelView, float4(input.Pos.xyz, 1.0)));
+
+	output.Normal = mul((float3x3)ubo.model, input.Normal);
+	float4 pos = mul(ubo.model, float4(input.Pos, 1.0));
+	output.LightVec = ubo.lightPos.xyz - pos.xyz;
+	output.ViewVec = ubo.viewPos.xyz - pos.xyz;
+	return output;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/variablerateshading/scene.vert.spv b/external/Vulkan/data/shaders/hlsl/variablerateshading/scene.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..ca7e3a385570134097c99c0b4d679cdf5b41e43b
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/variablerateshading/scene.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/viewportarray/multiview.geom b/external/Vulkan/data/shaders/hlsl/viewportarray/multiview.geom
new file mode 100644
index 0000000000000000000000000000000000000000..b1e609fdbcf810b0135b0b02f001bcdb1968fc90
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/viewportarray/multiview.geom
@@ -0,0 +1,56 @@
+// Copyright 2020 Google LLC
+
+struct UBO
+{
+	float4x4 projection[2];
+	float4x4 modelview[2];
+	float4 lightPos;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+};
+
+struct GSOutput
+{
+	float4 Pos : SV_POSITION;
+	uint ViewportIndex : SV_ViewportArrayIndex;
+	uint PrimitiveID : SV_PrimitiveID;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float3 ViewVec : TEXCOOR1;
+[[vk::location(3)]] float3 LightVec : TEXCOOR2;
+};
+
+[maxvertexcount(3)]
+[instance(2)]
+void main(triangle VSOutput input[3], inout TriangleStream<GSOutput> outStream, uint InvocationID : SV_GSInstanceID, uint PrimitiveID : SV_PrimitiveID)
+{
+	for(int i = 0; i < 3; i++)
+	{
+		GSOutput output = (GSOutput)0;
+		output.Normal = mul((float3x3)ubo.modelview[InvocationID], input[i].Normal);
+		output.Color = input[i].Color;
+
+		float4 pos = input[i].Pos;
+		float4 worldPos = mul(ubo.modelview[InvocationID], pos);
+
+		float3 lPos = mul(ubo.modelview[InvocationID], ubo.lightPos).xyz;
+		output.LightVec = lPos - worldPos.xyz;
+		output.ViewVec = -worldPos.xyz;
+
+		output.Pos = mul(ubo.projection[InvocationID], worldPos);
+
+		// Set the viewport index that the vertex will be emitted to
+		output.ViewportIndex = InvocationID;
+      	output.PrimitiveID = PrimitiveID;
+		outStream.Append( output );
+	}
+
+	outStream.RestartStrip();
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/viewportarray/multiview.geom.spv b/external/Vulkan/data/shaders/hlsl/viewportarray/multiview.geom.spv
new file mode 100644
index 0000000000000000000000000000000000000000..da192bb19678bf74fc3898f73245fa8b91aebbcd
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/viewportarray/multiview.geom.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/viewportarray/scene.frag b/external/Vulkan/data/shaders/hlsl/viewportarray/scene.frag
new file mode 100644
index 0000000000000000000000000000000000000000..189e99550510fa70dd23e6826aa5b6ed951c289a
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/viewportarray/scene.frag
@@ -0,0 +1,21 @@
+// Copyright 2020 Google LLC
+
+struct GSOutput
+{
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+[[vk::location(2)]] float3 ViewVec : TEXCOORD1;
+[[vk::location(3)]] float3 LightVec : TEXCOORD2;
+};
+
+float4 main(GSOutput input) : SV_TARGET
+{
+	float3 N = normalize(input.Normal);
+	float3 L = normalize(input.LightVec);
+	float3 V = normalize(input.ViewVec);
+	float3 R = reflect(-L, N);
+	float3 ambient = float3(0.1, 0.1, 0.1);
+	float3 diffuse = max(dot(N, L), 0.0) * float3(1.0, 1.0, 1.0);
+	float3 specular = pow(max(dot(R, V), 0.0), 16.0) * float3(0.75, 0.75, 0.75);
+	return float4((ambient + diffuse) * input.Color.rgb + specular, 1.0);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/viewportarray/scene.frag.spv b/external/Vulkan/data/shaders/hlsl/viewportarray/scene.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..a2843e1c83bac70e70368c1f832c1a42246d4da4
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/viewportarray/scene.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/viewportarray/scene.vert b/external/Vulkan/data/shaders/hlsl/viewportarray/scene.vert
new file mode 100644
index 0000000000000000000000000000000000000000..1aeca8328c4d6b3ec4ca8b9164e6c346db68a2b7
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/viewportarray/scene.vert
@@ -0,0 +1,24 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float3 Pos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float3 Color : COLOR0;
+};
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 Normal : NORMAL0;
+[[vk::location(1)]] float3 Color : COLOR0;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.Color = input.Color;
+	output.Normal = input.Normal;
+	output.Pos = float4(input.Pos.xyz, 1.0);
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/viewportarray/scene.vert.spv b/external/Vulkan/data/shaders/hlsl/viewportarray/scene.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..54e85bdaf9b3366ef0a3ea6431f5d6e77a00eb22
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/viewportarray/scene.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/vulkanscene/logo.frag b/external/Vulkan/data/shaders/hlsl/vulkanscene/logo.frag
new file mode 100644
index 0000000000000000000000000000000000000000..e4a8c9dfee8583048040dbfa186e48fa0b958a7a
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/vulkanscene/logo.frag
@@ -0,0 +1,22 @@
+// Copyright 2020 Google LLC
+
+struct VSOutput
+{
+[[vk::location(0)]] float2 UV : TEXCOORD0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float3 Color : COLOR0;
+[[vk::location(3)]] float3 EyePos : POSITION0;
+[[vk::location(4)]] float3 LightVec : TEXCOORD2;
+};
+
+float4 main(VSOutput input) : SV_TARGET
+{
+  float3 Eye = normalize(-input.EyePos);
+  float3 Reflected = normalize(reflect(-input.LightVec, input.Normal));
+
+  float4 diff = float4(input.Color, 1.0) * max(dot(input.Normal, input.LightVec), 0.0);
+  float shininess = 0.0;
+  float4 spec = float4(1.0, 1.0, 1.0, 1.0) * pow(max(dot(Reflected, Eye), 0.0), 2.5) * shininess;
+
+  return float4((diff + spec).rgb, 1);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/vulkanscene/logo.frag.spv b/external/Vulkan/data/shaders/hlsl/vulkanscene/logo.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..8408252ebad6ddef19dc5d54120bf63286cbb7a9
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/vulkanscene/logo.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/vulkanscene/logo.vert b/external/Vulkan/data/shaders/hlsl/vulkanscene/logo.vert
new file mode 100644
index 0000000000000000000000000000000000000000..dc458eee27fb789fb08c65c05dc72287220ab25f
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/vulkanscene/logo.vert
@@ -0,0 +1,45 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float4 Pos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float2 TexCoord : TEXCOORD0;
+[[vk::location(3)]] float3 Color : COLOR0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+	float4x4 normal;
+	float4x4 view;
+	float3 lightpos;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float2 UV : TEXCOORD0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float3 Color : COLOR0;
+[[vk::location(3)]] float3 EyePos : POSITION0;
+[[vk::location(4)]] float3 LightVec : TEXCOORD2;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	float4x4 modelView = mul(ubo.view, ubo.model);
+	float4 pos = mul(modelView, input.Pos);
+	output.UV = input.TexCoord.xy;
+	output.Normal = normalize(mul((float3x3)ubo.normal, input.Normal));
+	output.Color = input.Color;
+	output.Pos = mul(ubo.projection, pos);
+	output.EyePos = mul(modelView, pos).xyz;
+	float4 lightPos = mul(modelView, float4(1.0, 2.0, 0.0, 1.0));
+	output.LightVec = normalize(lightPos.xyz - output.EyePos);
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/vulkanscene/logo.vert.spv b/external/Vulkan/data/shaders/hlsl/vulkanscene/logo.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..1c78bd853be349afbd7503223856fda13a790351
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/vulkanscene/logo.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/vulkanscene/mesh.frag b/external/Vulkan/data/shaders/hlsl/vulkanscene/mesh.frag
new file mode 100644
index 0000000000000000000000000000000000000000..5770537a427b1c00e9611ac20e60fc329a67c050
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/vulkanscene/mesh.frag
@@ -0,0 +1,48 @@
+// Copyright 2020 Google LLC
+
+Texture2D tex : register(t1);
+SamplerState samp : register(s1);
+
+struct VSOutput
+{
+[[vk::location(0)]] float2 UV : TEXCOORD0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float3 Color : COLOR0;
+[[vk::location(3)]] float3 EyePos : POSITION0;
+[[vk::location(4)]] float3 LightVec : TEXCOORD2;
+};
+
+float specpart(float3 L, float3 N, float3 H)
+{
+	if (dot(N, L) > 0.0)
+	{
+		return pow(clamp(dot(H, N), 0.0, 1.0), 64.0);
+	}
+	return 0.0;
+}
+
+float4 main(VSOutput input) : SV_TARGET
+{
+	float3 Eye = normalize(-input.EyePos);
+	float3 Reflected = normalize(reflect(-input.LightVec, input.Normal));
+
+	float3 halfVec = normalize(input.LightVec + input.EyePos);
+	float diff = clamp(dot(input.LightVec, input.Normal), 0.0, 1.0);
+	float spec = specpart(input.LightVec, input.Normal, halfVec);
+	float intensity = 0.1 + diff + spec;
+
+	float4 IAmbient = float4(0.2, 0.2, 0.2, 1.0);
+	float4 IDiffuse = float4(0.5, 0.5, 0.5, 0.5) * max(dot(input.Normal, input.LightVec), 0.0);
+	float shininess = 0.75;
+	float4 ISpecular = float4(0.5, 0.5, 0.5, 1.0) * pow(max(dot(Reflected, Eye), 0.0), 2.0) * shininess;
+
+	float4 outFragColor = float4((IAmbient + IDiffuse) * float4(input.Color, 1.0) + ISpecular);
+
+	// Some manual saturation
+	if (intensity > 0.95)
+		outFragColor *= 2.25;
+	if (intensity < 0.15)
+		outFragColor = float4(0.1, 0.1, 0.1, 0.1);
+
+	return outFragColor;
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/vulkanscene/mesh.frag.spv b/external/Vulkan/data/shaders/hlsl/vulkanscene/mesh.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..21cf637e52b047f2e5fb0664daa90da1cb348821
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/vulkanscene/mesh.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/vulkanscene/mesh.vert b/external/Vulkan/data/shaders/hlsl/vulkanscene/mesh.vert
new file mode 100644
index 0000000000000000000000000000000000000000..77490e4662d3f265e7150ae85afdb576c17045a8
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/vulkanscene/mesh.vert
@@ -0,0 +1,45 @@
+// Copyright 2020 Google LLC
+
+struct VSInput
+{
+[[vk::location(0)]] float4 Pos : POSITION0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float2 TexCoord : TEXCOORD0;
+[[vk::location(3)]] float3 Color : COLOR0;
+};
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+	float4x4 normal;
+	float4x4 view;
+	float3 lightpos;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float2 UV : TEXCOORD0;
+[[vk::location(1)]] float3 Normal : NORMAL0;
+[[vk::location(2)]] float3 Color : COLOR0;
+[[vk::location(3)]] float3 EyePos : POSITION0;
+[[vk::location(4)]] float3 LightVec : TEXCOORD2;
+};
+
+VSOutput main(VSInput input)
+{
+	VSOutput output = (VSOutput)0;
+	output.UV = input.TexCoord.xy;
+	output.Normal = normalize(mul((float3x3)ubo.normal, input.Normal));
+	output.Color = input.Color;
+	float4x4 modelView = mul(ubo.view, ubo.model);
+	float4 pos = mul(modelView, input.Pos);
+	output.Pos = mul(ubo.projection, pos);
+	output.EyePos = mul(modelView, pos).xyz;
+	float4 lightPos = mul(modelView, float4(ubo.lightpos, 1.0));
+	output.LightVec = normalize(lightPos.xyz - output.EyePos);
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/vulkanscene/mesh.vert.spv b/external/Vulkan/data/shaders/hlsl/vulkanscene/mesh.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..b621bf60e14bb8e79823a1a661ac08b33cd6b969
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/vulkanscene/mesh.vert.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/vulkanscene/skybox.frag b/external/Vulkan/data/shaders/hlsl/vulkanscene/skybox.frag
new file mode 100644
index 0000000000000000000000000000000000000000..bd2ba96d6b4f269ab1ec15e3e971978245e438fc
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/vulkanscene/skybox.frag
@@ -0,0 +1,9 @@
+// Copyright 2020 Google LLC
+
+TextureCube textureCubeMap : register(t1);
+SamplerState samplerCubeMap : register(s1);
+
+float4 main([[vk::location(0)]] float3 inUVW : TEXCOORD0) : SV_TARGET
+{
+	return textureCubeMap.Sample(samplerCubeMap, inUVW);
+}
\ No newline at end of file
diff --git a/external/Vulkan/data/shaders/hlsl/vulkanscene/skybox.frag.spv b/external/Vulkan/data/shaders/hlsl/vulkanscene/skybox.frag.spv
new file mode 100644
index 0000000000000000000000000000000000000000..4437977490e40138333585532152d62895da5773
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/vulkanscene/skybox.frag.spv differ
diff --git a/external/Vulkan/data/shaders/hlsl/vulkanscene/skybox.vert b/external/Vulkan/data/shaders/hlsl/vulkanscene/skybox.vert
new file mode 100644
index 0000000000000000000000000000000000000000..ebc7739d19603b16422e9cc3075f3d37a899d026
--- /dev/null
+++ b/external/Vulkan/data/shaders/hlsl/vulkanscene/skybox.vert
@@ -0,0 +1,23 @@
+// Copyright 2020 Google LLC
+
+struct UBO
+{
+	float4x4 projection;
+	float4x4 model;
+};
+
+cbuffer ubo : register(b0) { UBO ubo; }
+
+struct VSOutput
+{
+	float4 Pos : SV_POSITION;
+[[vk::location(0)]] float3 UVW : TEXCOORD0;
+};
+
+VSOutput main([[vk::location(0)]] float3 Pos : POSITION0)
+{
+	VSOutput output = (VSOutput)0;
+	output.UVW = Pos;
+	output.Pos = mul(ubo.projection, mul(ubo.model, float4(Pos.xyz, 1.0)));
+	return output;
+}
diff --git a/external/Vulkan/data/shaders/hlsl/vulkanscene/skybox.vert.spv b/external/Vulkan/data/shaders/hlsl/vulkanscene/skybox.vert.spv
new file mode 100644
index 0000000000000000000000000000000000000000..7981301a9b43af246d298159dc52fabdf59233be
Binary files /dev/null and b/external/Vulkan/data/shaders/hlsl/vulkanscene/skybox.vert.spv differ
diff --git a/external/Vulkan/download_assets.py b/external/Vulkan/download_assets.py
new file mode 100755
index 0000000000000000000000000000000000000000..93a509bfbfcdc83270d66af0eb0116b31e4621bd
--- /dev/null
+++ b/external/Vulkan/download_assets.py
@@ -0,0 +1,31 @@
+#!/usr/bin/env python3
+
+import sys
+from urllib.request import urlretrieve
+from zipfile import ZipFile
+
+ASSET_PACK_URL = 'http://vulkan.gpuinfo.org/downloads/vulkan_asset_pack_gltf.zip'
+ASSET_PACK_FILE_NAME = 'vulkan_asset_pack_gltf.zip'
+
+print("Downloading asset pack from '%s'" % ASSET_PACK_URL)    
+
+def reporthook(blocknum, blocksize, totalsize):
+    bytesread = blocknum * blocksize
+    if totalsize > 0:
+        percent = bytesread * 1e2 / totalsize
+        s = "\r%5.1f%% (%*d / %d bytes)" % (percent, len(str(totalsize)), bytesread, totalsize)
+        sys.stderr.write(s)
+        if bytesread >= totalsize:
+            sys.stderr.write("\n")
+    else:
+        sys.stderr.write("read %d\n" % (bytesread,))
+
+urlretrieve(ASSET_PACK_URL, ASSET_PACK_FILE_NAME, reporthook)
+
+print("Download finished")
+
+print("Extracting assets")
+
+zip = ZipFile(ASSET_PACK_FILE_NAME, 'r')
+zip.extractall("./")
+zip.close()
diff --git a/external/Vulkan/examples/CMakeLists.txt b/external/Vulkan/examples/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..1a4e49c56782d73a7010100d2c12b4a78a83d0ef
--- /dev/null
+++ b/external/Vulkan/examples/CMakeLists.txt
@@ -0,0 +1,143 @@
+# Function for building single example
+function(buildExample EXAMPLE_NAME)
+	SET(EXAMPLE_FOLDER ${CMAKE_CURRENT_SOURCE_DIR}/${EXAMPLE_NAME})
+	message(STATUS "Generating project file for example in ${EXAMPLE_FOLDER}")
+	# Main
+	file(GLOB SOURCE *.cpp ${BASE_HEADERS} ${EXAMPLE_FOLDER}/*.cpp)
+	SET(MAIN_CPP ${EXAMPLE_FOLDER}/${EXAMPLE_NAME}.cpp)
+	if(EXISTS ${EXAMPLE_FOLDER}/main.cpp)
+		SET(MAIN_CPP ${EXAMPLE_FOLDER}/main.cpp)
+	ENDIF()
+	if(EXISTS ${EXAMPLE_FOLDER}/${EXAMPLE_NAME}.h)
+		SET(MAIN_HEADER ${EXAMPLE_FOLDER}/${EXAMPLE_NAME}.h)
+	ENDIF()
+	find_package(OpenMP)
+	# imgui example requires additional source files
+	IF(${EXAMPLE_NAME} STREQUAL "imgui")
+		file(GLOB ADD_SOURCE "../external/imgui/*.cpp")
+		SET(SOURCE ${SOURCE} ${ADD_SOURCE})
+	ENDIF()
+	# wayland requires additional source files
+	IF(USE_WAYLAND_WSI)
+		SET(SOURCE ${SOURCE} ${CMAKE_BINARY_DIR}/xdg-shell-client-protocol.h ${CMAKE_BINARY_DIR}/xdg-shell-protocol.c)
+	ENDIF()
+	# Add shaders
+	set(SHADER_DIR_GLSL "../data/shaders/glsl/${EXAMPLE_NAME}")
+	file(GLOB SHADERS_GLSL "${SHADER_DIR_GLSL}/*.vert" "${SHADER_DIR_GLSL}/*.frag" "${SHADER_DIR_GLSL}/*.comp" "${SHADER_DIR_GLSL}/*.geom" "${SHADER_DIR_GLSL}/*.tesc" "${SHADER_DIR_GLSL}/*.tese" "${SHADER_DIR_GLSL}/*.mesh" "${SHADER_DIR_GLSL}/*.task" "${SHADER_DIR_GLSL}/*.rgen" "${SHADER_DIR_GLSL}/*.rchit" "${SHADER_DIR_GLSL}/*.rmiss" "${SHADER_DIR_GLSL}/*.rcall")
+	set(SHADER_DIR_HLSL "../data/shaders/hlsl/${EXAMPLE_NAME}")
+	file(GLOB SHADERS_HLSL "${SHADER_DIR_HLSL}/*.vert" "${SHADER_DIR_HLSL}/*.frag" "${SHADER_DIR_HLSL}/*.comp" "${SHADER_DIR_HLSL}/*.geom" "${SHADER_DIR_HLSL}/*.tesc" "${SHADER_DIR_HLSL}/*.tese" "${SHADER_DIR_HLSL}/*.mesh" "${SHADER_DIR_HLSL}/*.task" "${SHADER_DIR_HLSL}/*.rgen" "${SHADER_DIR_HLSL}/*.rchit" "${SHADER_DIR_HLSL}/*.rmiss" "${SHADER_DIR_HLSL}/*.rcall")
+	source_group("Shaders\\GLSL" FILES ${SHADERS_GLSL})
+	source_group("Shaders\\HLSL" FILES ${SHADERS_HLSL})
+	# Add optional readme / tutorial
+	file(GLOB README_FILES "${EXAMPLE_FOLDER}/*.md")
+	if(WIN32)
+		add_executable(${EXAMPLE_NAME} WIN32 ${MAIN_CPP} ${SOURCE} ${MAIN_HEADER} ${SHADERS_GLSL} ${SHADERS_HLSL} ${README_FILES})
+		target_link_libraries(${EXAMPLE_NAME} base ${Vulkan_LIBRARY} ${WINLIBS})
+	else(WIN32)
+		add_executable(${EXAMPLE_NAME} ${MAIN_CPP} ${SOURCE} ${MAIN_HEADER} ${SHADERS_GLSL} ${SHADERS_HLSL} ${README_FILES})
+		target_link_libraries(${EXAMPLE_NAME} base )
+	endif(WIN32)
+
+	set_target_properties(${EXAMPLE_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/bin)
+	if(OpenMP_CXX_FOUND)
+		target_compile_options(${EXAMPLE_NAME} PRIVATE ${OpenMP_CXX_FLAGS})
+		IF(${EXAMPLE_NAME} STREQUAL "texture3d")
+			if(OpenMP_CXX_FOUND)
+    			target_link_libraries(${EXAMPLE_NAME} OpenMP::OpenMP_CXX)
+			endif()	
+		endif()
+	endif()
+
+	if(RESOURCE_INSTALL_DIR)
+		install(TARGETS ${EXAMPLE_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR})
+	endif()
+endfunction(buildExample)
+
+# Build all examples
+function(buildExamples)
+	foreach(EXAMPLE ${EXAMPLES})
+		buildExample(${EXAMPLE})
+	endforeach(EXAMPLE)
+endfunction(buildExamples)
+
+set(EXAMPLES
+	bloom
+	computecloth
+	computecullandlod
+	computeheadless
+	computenbody
+	computeparticles
+	computeraytracing
+	computeshader
+	conditionalrender
+	conservativeraster
+	debugmarker
+	deferred
+	deferredmultisampling
+	deferredshadows
+	descriptorindexing
+	descriptorsets
+	displacement
+	distancefieldfonts
+	dynamicrendering
+	dynamicuniformbuffer
+	gears
+	geometryshader
+	gltfloading
+	gltfscenerendering
+	gltfskinning
+	hdr
+	imgui
+	indirectdraw
+	inlineuniformblocks
+	inputattachments
+	instancing
+	multisampling
+	multithreading
+	multiview
+	negativeviewportheight	
+	occlusionquery
+	offscreen
+	oit
+	parallaxmapping
+	particlefire
+	pbrbasic
+	pbribl
+	pbrtexture
+	pipelines
+	pipelinestatistics
+	pushconstants
+	pushdescriptors
+	radialblur
+	rayquery
+	raytracingbasic
+	raytracingcallable
+	raytracingreflections
+	raytracingshadows	
+	renderheadless
+	screenshot
+	shadowmapping
+	shadowmappingomni
+	shadowmappingcascade
+	specializationconstants
+	sphericalenvmapping
+	ssao
+	stencilbuffer
+	subpasses
+	terraintessellation
+	tessellation
+	textoverlay
+	texture
+	texture3d
+	texturearray
+	texturecubemap
+	texturecubemaparray
+	texturemipmapgen
+	texturesparseresidency
+	triangle
+	variablerateshading
+	viewportarray
+	vulkanscene
+)
+
+buildExamples()
diff --git a/external/Vulkan/examples/bloom/bloom.cpp b/external/Vulkan/examples/bloom/bloom.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a775c00b8d9b457df7617f77424d0ba1c7769c03
--- /dev/null
+++ b/external/Vulkan/examples/bloom/bloom.cpp
@@ -0,0 +1,730 @@
+/*
+* Vulkan Example - Implements a separable two-pass fullscreen blur (also known as bloom)
+*
+* Copyright (C) Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkanexamplebase.h"
+#include "VulkanglTFModel.h"
+
+#define ENABLE_VALIDATION false
+
+// Offscreen frame buffer properties
+#define FB_DIM 256
+#define FB_COLOR_FORMAT VK_FORMAT_R8G8B8A8_UNORM
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	bool bloom = true;
+
+	vks::TextureCubeMap cubemap;
+
+	struct {
+		vkglTF::Model ufo;
+		vkglTF::Model ufoGlow;
+		vkglTF::Model skyBox;
+	} models;
+
+	struct {
+		vks::Buffer scene;
+		vks::Buffer skyBox;
+		vks::Buffer blurParams;
+	} uniformBuffers;
+
+	struct UBO {
+		glm::mat4 projection;
+		glm::mat4 view;
+		glm::mat4 model;
+	};
+
+	struct UBOBlurParams {
+		float blurScale = 1.0f;
+		float blurStrength = 1.5f;
+	};
+
+	struct {
+		UBO scene, skyBox;
+		UBOBlurParams blurParams;
+	} ubos;
+
+	struct {
+		VkPipeline blurVert;
+		VkPipeline blurHorz;
+		VkPipeline glowPass;
+		VkPipeline phongPass;
+		VkPipeline skyBox;
+	} pipelines;
+
+	struct {
+		VkPipelineLayout blur;
+		VkPipelineLayout scene;
+	} pipelineLayouts;
+
+	struct {
+		VkDescriptorSet blurVert;
+		VkDescriptorSet blurHorz;
+		VkDescriptorSet scene;
+		VkDescriptorSet skyBox;
+	} descriptorSets;
+
+	struct {
+		VkDescriptorSetLayout blur;
+		VkDescriptorSetLayout scene;
+	} descriptorSetLayouts;
+
+	// Framebuffer for offscreen rendering
+	struct FrameBufferAttachment {
+		VkImage image;
+		VkDeviceMemory mem;
+		VkImageView view;
+	};
+	struct FrameBuffer {
+		VkFramebuffer framebuffer;
+		FrameBufferAttachment color, depth;
+		VkDescriptorImageInfo descriptor;
+	};
+	struct OffscreenPass {
+		int32_t width, height;
+		VkRenderPass renderPass;
+		VkSampler sampler;
+		std::array<FrameBuffer, 2> framebuffers;
+	} offscreenPass;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Bloom (offscreen rendering)";
+		timerSpeed *= 0.5f;
+		camera.type = Camera::CameraType::lookat;
+		camera.setPosition(glm::vec3(0.0f, 0.0f, -10.25f));
+		camera.setRotation(glm::vec3(7.5f, -343.0f, 0.0f));
+		camera.setPerspective(45.0f, (float)width / (float)height, 0.1f, 256.0f);
+	}
+
+	~VulkanExample()
+	{
+		// Clean up used Vulkan resources
+		// Note : Inherited destructor cleans up resources stored in base class
+
+		vkDestroySampler(device, offscreenPass.sampler, nullptr);
+
+		// Frame buffer
+		for (auto& framebuffer : offscreenPass.framebuffers)
+		{
+			// Attachments
+			vkDestroyImageView(device, framebuffer.color.view, nullptr);
+			vkDestroyImage(device, framebuffer.color.image, nullptr);
+			vkFreeMemory(device, framebuffer.color.mem, nullptr);
+			vkDestroyImageView(device, framebuffer.depth.view, nullptr);
+			vkDestroyImage(device, framebuffer.depth.image, nullptr);
+			vkFreeMemory(device, framebuffer.depth.mem, nullptr);
+
+			vkDestroyFramebuffer(device, framebuffer.framebuffer, nullptr);
+		}
+		vkDestroyRenderPass(device, offscreenPass.renderPass, nullptr);
+
+		vkDestroyPipeline(device, pipelines.blurHorz, nullptr);
+		vkDestroyPipeline(device, pipelines.blurVert, nullptr);
+		vkDestroyPipeline(device, pipelines.phongPass, nullptr);
+		vkDestroyPipeline(device, pipelines.glowPass, nullptr);
+		vkDestroyPipeline(device, pipelines.skyBox, nullptr);
+
+		vkDestroyPipelineLayout(device, pipelineLayouts.blur , nullptr);
+		vkDestroyPipelineLayout(device, pipelineLayouts.scene, nullptr);
+
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.blur, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.scene, nullptr);
+
+		// Uniform buffers
+		uniformBuffers.scene.destroy();
+		uniformBuffers.skyBox.destroy();
+		uniformBuffers.blurParams.destroy();
+
+		cubemap.destroy();
+	}
+
+	// Setup the offscreen framebuffer for rendering the mirrored scene
+	// The color attachment of this framebuffer will then be sampled from
+	void prepareOffscreenFramebuffer(FrameBuffer *frameBuf, VkFormat colorFormat, VkFormat depthFormat)
+	{
+		// Color attachment
+		VkImageCreateInfo image = vks::initializers::imageCreateInfo();
+		image.imageType = VK_IMAGE_TYPE_2D;
+		image.format = colorFormat;
+		image.extent.width = FB_DIM;
+		image.extent.height = FB_DIM;
+		image.extent.depth = 1;
+		image.mipLevels = 1;
+		image.arrayLayers = 1;
+		image.samples = VK_SAMPLE_COUNT_1_BIT;
+		image.tiling = VK_IMAGE_TILING_OPTIMAL;
+		// We will sample directly from the color attachment
+		image.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
+
+		VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo();
+		VkMemoryRequirements memReqs;
+
+		VkImageViewCreateInfo colorImageView = vks::initializers::imageViewCreateInfo();
+		colorImageView.viewType = VK_IMAGE_VIEW_TYPE_2D;
+		colorImageView.format = colorFormat;
+		colorImageView.flags = 0;
+		colorImageView.subresourceRange = {};
+		colorImageView.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+		colorImageView.subresourceRange.baseMipLevel = 0;
+		colorImageView.subresourceRange.levelCount = 1;
+		colorImageView.subresourceRange.baseArrayLayer = 0;
+		colorImageView.subresourceRange.layerCount = 1;
+
+		VK_CHECK_RESULT(vkCreateImage(device, &image, nullptr, &frameBuf->color.image));
+		vkGetImageMemoryRequirements(device, frameBuf->color.image, &memReqs);
+		memAlloc.allocationSize = memReqs.size;
+		memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+		VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &frameBuf->color.mem));
+		VK_CHECK_RESULT(vkBindImageMemory(device, frameBuf->color.image, frameBuf->color.mem, 0));
+
+		colorImageView.image = frameBuf->color.image;
+		VK_CHECK_RESULT(vkCreateImageView(device, &colorImageView, nullptr, &frameBuf->color.view));
+
+		// Depth stencil attachment
+		image.format = depthFormat;
+		image.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
+
+		VkImageViewCreateInfo depthStencilView = vks::initializers::imageViewCreateInfo();
+		depthStencilView.viewType = VK_IMAGE_VIEW_TYPE_2D;
+		depthStencilView.format = depthFormat;
+		depthStencilView.flags = 0;
+		depthStencilView.subresourceRange = {};
+		depthStencilView.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
+		depthStencilView.subresourceRange.baseMipLevel = 0;
+		depthStencilView.subresourceRange.levelCount = 1;
+		depthStencilView.subresourceRange.baseArrayLayer = 0;
+		depthStencilView.subresourceRange.layerCount = 1;
+
+		VK_CHECK_RESULT(vkCreateImage(device, &image, nullptr, &frameBuf->depth.image));
+		vkGetImageMemoryRequirements(device, frameBuf->depth.image, &memReqs);
+		memAlloc.allocationSize = memReqs.size;
+		memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+		VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &frameBuf->depth.mem));
+		VK_CHECK_RESULT(vkBindImageMemory(device, frameBuf->depth.image, frameBuf->depth.mem, 0));
+
+		depthStencilView.image = frameBuf->depth.image;
+		VK_CHECK_RESULT(vkCreateImageView(device, &depthStencilView, nullptr, &frameBuf->depth.view));
+
+		VkImageView attachments[2];
+		attachments[0] = frameBuf->color.view;
+		attachments[1] = frameBuf->depth.view;
+
+		VkFramebufferCreateInfo fbufCreateInfo = vks::initializers::framebufferCreateInfo();
+		fbufCreateInfo.renderPass = offscreenPass.renderPass;
+		fbufCreateInfo.attachmentCount = 2;
+		fbufCreateInfo.pAttachments = attachments;
+		fbufCreateInfo.width = FB_DIM;
+		fbufCreateInfo.height = FB_DIM;
+		fbufCreateInfo.layers = 1;
+
+		VK_CHECK_RESULT(vkCreateFramebuffer(device, &fbufCreateInfo, nullptr, &frameBuf->framebuffer));
+
+		// Fill a descriptor for later use in a descriptor set
+		frameBuf->descriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+		frameBuf->descriptor.imageView = frameBuf->color.view;
+		frameBuf->descriptor.sampler = offscreenPass.sampler;
+	}
+
+	// Prepare the offscreen framebuffers used for the vertical- and horizontal blur
+	void prepareOffscreen()
+	{
+		offscreenPass.width = FB_DIM;
+		offscreenPass.height = FB_DIM;
+
+		// Find a suitable depth format
+		VkFormat fbDepthFormat;
+		VkBool32 validDepthFormat = vks::tools::getSupportedDepthFormat(physicalDevice, &fbDepthFormat);
+		assert(validDepthFormat);
+
+		// Create a separate render pass for the offscreen rendering as it may differ from the one used for scene rendering
+
+		std::array<VkAttachmentDescription, 2> attchmentDescriptions = {};
+		// Color attachment
+		attchmentDescriptions[0].format = FB_COLOR_FORMAT;
+		attchmentDescriptions[0].samples = VK_SAMPLE_COUNT_1_BIT;
+		attchmentDescriptions[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+		attchmentDescriptions[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+		attchmentDescriptions[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+		attchmentDescriptions[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+		attchmentDescriptions[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+		attchmentDescriptions[0].finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+		// Depth attachment
+		attchmentDescriptions[1].format = fbDepthFormat;
+		attchmentDescriptions[1].samples = VK_SAMPLE_COUNT_1_BIT;
+		attchmentDescriptions[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+		attchmentDescriptions[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+		attchmentDescriptions[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+		attchmentDescriptions[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+		attchmentDescriptions[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+		attchmentDescriptions[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+
+		VkAttachmentReference colorReference = { 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
+		VkAttachmentReference depthReference = { 1, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL };
+
+		VkSubpassDescription subpassDescription = {};
+		subpassDescription.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+		subpassDescription.colorAttachmentCount = 1;
+		subpassDescription.pColorAttachments = &colorReference;
+		subpassDescription.pDepthStencilAttachment = &depthReference;
+
+		// Use subpass dependencies for layout transitions
+		std::array<VkSubpassDependency, 2> dependencies;
+
+		dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
+		dependencies[0].dstSubpass = 0;
+		dependencies[0].srcStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
+		dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+		dependencies[0].srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
+		dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+		dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+		dependencies[1].srcSubpass = 0;
+		dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
+		dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+		dependencies[1].dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
+		dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+		dependencies[1].dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
+		dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+		// Create the actual renderpass
+		VkRenderPassCreateInfo renderPassInfo = {};
+		renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
+		renderPassInfo.attachmentCount = static_cast<uint32_t>(attchmentDescriptions.size());
+		renderPassInfo.pAttachments = attchmentDescriptions.data();
+		renderPassInfo.subpassCount = 1;
+		renderPassInfo.pSubpasses = &subpassDescription;
+		renderPassInfo.dependencyCount = static_cast<uint32_t>(dependencies.size());
+		renderPassInfo.pDependencies = dependencies.data();
+
+		VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassInfo, nullptr, &offscreenPass.renderPass));
+
+		// Create sampler to sample from the color attachments
+		VkSamplerCreateInfo sampler = vks::initializers::samplerCreateInfo();
+		sampler.magFilter = VK_FILTER_LINEAR;
+		sampler.minFilter = VK_FILTER_LINEAR;
+		sampler.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+		sampler.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+		sampler.addressModeV = sampler.addressModeU;
+		sampler.addressModeW = sampler.addressModeU;
+		sampler.mipLodBias = 0.0f;
+		sampler.maxAnisotropy = 1.0f;
+		sampler.minLod = 0.0f;
+		sampler.maxLod = 1.0f;
+		sampler.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
+		VK_CHECK_RESULT(vkCreateSampler(device, &sampler, nullptr, &offscreenPass.sampler));
+
+		// Create two frame buffers
+		prepareOffscreenFramebuffer(&offscreenPass.framebuffers[0], FB_COLOR_FORMAT, fbDepthFormat);
+		prepareOffscreenFramebuffer(&offscreenPass.framebuffers[1], FB_COLOR_FORMAT, fbDepthFormat);
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		VkViewport viewport;
+		VkRect2D scissor;
+
+		/*
+			The blur method used in this example is multi pass and renders the vertical blur first and then the horizontal one
+			While it's possible to blur in one pass, this method is widely used as it requires far less samples to generate the blur
+		*/
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			if (bloom) {
+				clearValues[0].color = { { 0.0f, 0.0f, 0.0f, 1.0f } };
+				clearValues[1].depthStencil = { 1.0f, 0 };
+
+				VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+				renderPassBeginInfo.renderPass = offscreenPass.renderPass;
+				renderPassBeginInfo.framebuffer = offscreenPass.framebuffers[0].framebuffer;
+				renderPassBeginInfo.renderArea.extent.width = offscreenPass.width;
+				renderPassBeginInfo.renderArea.extent.height = offscreenPass.height;
+				renderPassBeginInfo.clearValueCount = 2;
+				renderPassBeginInfo.pClearValues = clearValues;
+
+				viewport = vks::initializers::viewport((float)offscreenPass.width, (float)offscreenPass.height, 0.0f, 1.0f);
+				vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+				scissor = vks::initializers::rect2D(offscreenPass.width, offscreenPass.height, 0, 0);
+				vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+				/*
+					First render pass: Render glow parts of the model (separate mesh) to an offscreen frame buffer
+				*/
+
+				vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+				vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayouts.scene, 0, 1, &descriptorSets.scene, 0, NULL);
+				vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.glowPass);
+
+				models.ufoGlow.draw(drawCmdBuffers[i]);
+
+				vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+				/*
+					Second render pass: Vertical blur
+
+					Render contents of the first pass into a second framebuffer and apply a vertical blur
+					This is the first blur pass, the horizontal blur is applied when rendering on top of the scene
+				*/
+
+				renderPassBeginInfo.framebuffer = offscreenPass.framebuffers[1].framebuffer;
+
+				vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+				vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayouts.blur, 0, 1, &descriptorSets.blurVert, 0, NULL);
+				vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.blurVert);
+				vkCmdDraw(drawCmdBuffers[i], 3, 1, 0, 0);
+
+				vkCmdEndRenderPass(drawCmdBuffers[i]);
+			}
+
+			/*
+				Note: Explicit synchronization is not required between the render pass, as this is done implicit via sub pass dependencies
+			*/
+
+			/*
+				Third render pass: Scene rendering with applied vertical blur
+
+				Renders the scene and the (vertically blurred) contents of the second framebuffer and apply a horizontal blur
+
+			*/
+			{
+				clearValues[0].color = defaultClearColor;
+				clearValues[1].depthStencil = { 1.0f, 0 };
+
+				VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+				renderPassBeginInfo.renderPass = renderPass;
+				renderPassBeginInfo.framebuffer = frameBuffers[i];
+				renderPassBeginInfo.renderArea.extent.width = width;
+				renderPassBeginInfo.renderArea.extent.height = height;
+				renderPassBeginInfo.clearValueCount = 2;
+				renderPassBeginInfo.pClearValues = clearValues;
+
+				vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+				VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+				vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+				VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
+				vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+				VkDeviceSize offsets[1] = { 0 };
+
+				// Skybox
+				vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayouts.scene, 0, 1, &descriptorSets.skyBox, 0, NULL);
+				vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.skyBox);
+				models.skyBox.draw(drawCmdBuffers[i]);
+
+
+				// 3D scene
+				vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayouts.scene, 0, 1, &descriptorSets.scene, 0, NULL);
+				vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.phongPass);
+				models.ufo.draw(drawCmdBuffers[i]);
+
+				if (bloom)
+				{
+					vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayouts.blur, 0, 1, &descriptorSets.blurHorz, 0, NULL);
+					vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.blurHorz);
+					vkCmdDraw(drawCmdBuffers[i], 3, 1, 0, 0);
+				}
+
+				drawUI(drawCmdBuffers[i]);
+
+				vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			}
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void loadAssets()
+	{
+		const uint32_t glTFLoadingFlags = vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::PreMultiplyVertexColors | vkglTF::FileLoadingFlags::FlipY;
+		models.ufo.loadFromFile(getAssetPath() + "models/retroufo.gltf", vulkanDevice, queue, glTFLoadingFlags);
+		models.ufoGlow.loadFromFile(getAssetPath() + "models/retroufo_glow.gltf", vulkanDevice, queue, glTFLoadingFlags);
+		models.skyBox.loadFromFile(getAssetPath() + "models/cube.gltf", vulkanDevice, queue, glTFLoadingFlags);
+		cubemap.loadFromFile(getAssetPath() + "textures/cubemap_space.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue);
+	}
+
+	void setupDescriptorPool()
+	{
+		std::vector<VkDescriptorPoolSize> poolSizes = {
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 8),
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 6)
+		};
+		VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 5);
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+	}
+
+	void setupDescriptorSetLayout()
+	{
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings;
+		VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo;
+		VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo;
+
+		// Fullscreen blur
+		setLayoutBindings = {
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_FRAGMENT_BIT, 0),			// Binding 0: Fragment shader uniform buffer
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1)	// Binding 1: Fragment shader image sampler
+		};
+		descriptorSetLayoutCreateInfo = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings.data(), static_cast<uint32_t>(setLayoutBindings.size()));
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorSetLayoutCreateInfo, nullptr, &descriptorSetLayouts.blur));
+		pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayouts.blur, 1);
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayouts.blur));
+
+		// Scene rendering
+		setLayoutBindings = {
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0),			// Binding 0 : Vertex shader uniform buffer
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1),	// Binding 1 : Fragment shader image sampler
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_FRAGMENT_BIT, 2),			// Binding 2 : Fragment shader image sampler
+		};
+
+		descriptorSetLayoutCreateInfo = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings.data(), setLayoutBindings.size());
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorSetLayoutCreateInfo, nullptr, &descriptorSetLayouts.scene));
+		pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayouts.scene, 1);
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayouts.scene));
+	}
+
+	void setupDescriptorSet()
+	{
+		VkDescriptorSetAllocateInfo descriptorSetAllocInfo;
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets;
+
+		// Full screen blur
+		// Vertical
+		descriptorSetAllocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.blur, 1);
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &descriptorSetAllocInfo, &descriptorSets.blurVert));
+		writeDescriptorSets = {
+			vks::initializers::writeDescriptorSet(descriptorSets.blurVert, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.blurParams.descriptor),				// Binding 0: Fragment shader uniform buffer
+			vks::initializers::writeDescriptorSet(descriptorSets.blurVert, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &offscreenPass.framebuffers[0].descriptor),	// Binding 1: Fragment shader texture sampler
+		};
+		vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, NULL);
+		// Horizontal
+		descriptorSetAllocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.blur, 1);
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &descriptorSetAllocInfo, &descriptorSets.blurHorz));
+		writeDescriptorSets = {
+			vks::initializers::writeDescriptorSet(descriptorSets.blurHorz, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.blurParams.descriptor),				// Binding 0: Fragment shader uniform buffer
+			vks::initializers::writeDescriptorSet(descriptorSets.blurHorz, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &offscreenPass.framebuffers[1].descriptor),	// Binding 1: Fragment shader texture sampler
+		};
+		vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, NULL);
+
+		// Scene rendering
+		descriptorSetAllocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.scene, 1);
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &descriptorSetAllocInfo, &descriptorSets.scene));
+		writeDescriptorSets = {
+			vks::initializers::writeDescriptorSet(descriptorSets.scene, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.scene.descriptor)							// Binding 0: Vertex shader uniform buffer
+		};
+		vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, NULL);
+
+		// Skybox
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &descriptorSetAllocInfo, &descriptorSets.skyBox));
+		writeDescriptorSets = {
+			vks::initializers::writeDescriptorSet(descriptorSets.skyBox, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.skyBox.descriptor),						// Binding 0: Vertex shader uniform buffer
+			vks::initializers::writeDescriptorSet(descriptorSets.skyBox, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,	1, &cubemap.descriptor),							// Binding 1: Fragment shader texture sampler
+		};
+		vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, NULL);
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyStateCI = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationStateCI = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendStateCI = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilStateCI = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportStateCI = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+		VkPipelineMultisampleStateCreateInfo multisampleStateCI = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
+		std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
+		VkPipelineDynamicStateCreateInfo dynamicStateCI = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables.data(), dynamicStateEnables.size(), 0);
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayouts.blur, renderPass, 0);
+		pipelineCI.pInputAssemblyState = &inputAssemblyStateCI;
+		pipelineCI.pRasterizationState = &rasterizationStateCI;
+		pipelineCI.pColorBlendState = &colorBlendStateCI;
+		pipelineCI.pMultisampleState = &multisampleStateCI;
+		pipelineCI.pViewportState = &viewportStateCI;
+		pipelineCI.pDepthStencilState = &depthStencilStateCI;
+		pipelineCI.pDynamicState = &dynamicStateCI;
+		pipelineCI.stageCount = shaderStages.size();
+		pipelineCI.pStages = shaderStages.data();
+
+		// Blur pipelines
+		shaderStages[0] = loadShader(getShadersPath() + "bloom/gaussblur.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "bloom/gaussblur.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		// Empty vertex input state
+		VkPipelineVertexInputStateCreateInfo emptyInputState = vks::initializers::pipelineVertexInputStateCreateInfo();
+		pipelineCI.pVertexInputState = &emptyInputState;
+		pipelineCI.layout = pipelineLayouts.blur;
+		// Additive blending
+		blendAttachmentState.colorWriteMask = 0xF;
+		blendAttachmentState.blendEnable = VK_TRUE;
+		blendAttachmentState.colorBlendOp = VK_BLEND_OP_ADD;
+		blendAttachmentState.srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
+		blendAttachmentState.dstColorBlendFactor = VK_BLEND_FACTOR_ONE;
+		blendAttachmentState.alphaBlendOp = VK_BLEND_OP_ADD;
+		blendAttachmentState.srcAlphaBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
+		blendAttachmentState.dstAlphaBlendFactor = VK_BLEND_FACTOR_DST_ALPHA;
+
+		// Use specialization constants to select between horizontal and vertical blur
+		uint32_t blurdirection = 0;
+		VkSpecializationMapEntry specializationMapEntry = vks::initializers::specializationMapEntry(0, 0, sizeof(uint32_t));
+		VkSpecializationInfo specializationInfo = vks::initializers::specializationInfo(1, &specializationMapEntry, sizeof(uint32_t), &blurdirection);
+		shaderStages[1].pSpecializationInfo = &specializationInfo;
+		// Vertical blur pipeline
+		pipelineCI.renderPass = offscreenPass.renderPass;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.blurVert));
+		// Horizontal blur pipeline
+		blurdirection = 1;
+		pipelineCI.renderPass = renderPass;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.blurHorz));
+
+		// Phong pass (3D model)
+		pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({vkglTF::VertexComponent::Position, vkglTF::VertexComponent::UV, vkglTF::VertexComponent::Color, vkglTF::VertexComponent::Normal});
+		pipelineCI.layout = pipelineLayouts.scene;
+		shaderStages[0] = loadShader(getShadersPath() + "bloom/phongpass.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "bloom/phongpass.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		blendAttachmentState.blendEnable = VK_FALSE;
+		depthStencilStateCI.depthWriteEnable = VK_TRUE;
+		rasterizationStateCI.cullMode = VK_CULL_MODE_BACK_BIT;
+		pipelineCI.renderPass = renderPass;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.phongPass));
+
+		// Color only pass (offscreen blur base)
+		shaderStages[0] = loadShader(getShadersPath() + "bloom/colorpass.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "bloom/colorpass.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		pipelineCI.renderPass = offscreenPass.renderPass;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.glowPass));
+
+		// Skybox (cubemap)
+		shaderStages[0] = loadShader(getShadersPath() + "bloom/skybox.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "bloom/skybox.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		depthStencilStateCI.depthWriteEnable = VK_FALSE;
+		rasterizationStateCI.cullMode = VK_CULL_MODE_FRONT_BIT;
+		pipelineCI.renderPass = renderPass;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.skyBox));
+	}
+
+	// Prepare and initialize uniform buffer containing shader uniforms
+	void prepareUniformBuffers()
+	{
+		// Phong and color pass vertex shader uniform buffer
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.scene,
+			sizeof(ubos.scene)));
+
+		// Blur parameters uniform buffers
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.blurParams,
+			sizeof(ubos.blurParams)));
+
+		// Skybox
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.skyBox,
+			sizeof(ubos.skyBox)));
+
+		// Map persistent
+		VK_CHECK_RESULT(uniformBuffers.scene.map());
+		VK_CHECK_RESULT(uniformBuffers.blurParams.map());
+		VK_CHECK_RESULT(uniformBuffers.skyBox.map());
+
+		// Initialize uniform buffers
+		updateUniformBuffersScene();
+		updateUniformBuffersBlur();
+	}
+
+	// Update uniform buffers for rendering the 3D scene
+	void updateUniformBuffersScene()
+	{
+		// UFO
+		ubos.scene.projection = camera.matrices.perspective;
+		ubos.scene.view = camera.matrices.view;
+
+		ubos.scene.model = glm::translate(glm::mat4(1.0f), glm::vec3(sin(glm::radians(timer * 360.0f)) * 0.25f, -1.0f, cos(glm::radians(timer * 360.0f)) * 0.25f));
+		ubos.scene.model = glm::rotate(ubos.scene.model, -sinf(glm::radians(timer * 360.0f)) * 0.15f, glm::vec3(1.0f, 0.0f, 0.0f));
+		ubos.scene.model = glm::rotate(ubos.scene.model, glm::radians(timer * 360.0f), glm::vec3(0.0f, 1.0f, 0.0f));
+
+		memcpy(uniformBuffers.scene.mapped, &ubos.scene, sizeof(ubos.scene));
+
+		// Skybox
+		ubos.skyBox.projection = glm::perspective(glm::radians(45.0f), (float)width / (float)height, 0.1f, 256.0f);
+		ubos.skyBox.view = glm::mat4(glm::mat3(camera.matrices.view));
+		ubos.skyBox.model = glm::mat4(1.0f);
+
+		memcpy(uniformBuffers.skyBox.mapped, &ubos.skyBox, sizeof(ubos.skyBox));
+	}
+
+	// Update blur pass parameter uniform buffer
+	void updateUniformBuffersBlur()
+	{
+		memcpy(uniformBuffers.blurParams.mapped, &ubos.blurParams, sizeof(ubos.blurParams));
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+		VulkanExampleBase::submitFrame();
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		loadAssets();
+		prepareUniformBuffers();
+		prepareOffscreen();
+		setupDescriptorSetLayout();
+		preparePipelines();
+		setupDescriptorPool();
+		setupDescriptorSet();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+		if (!paused || camera.updated)
+		{
+			updateUniformBuffersScene();
+		}
+	}
+
+	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
+	{
+		if (overlay->header("Settings")) {
+			if (overlay->checkBox("Bloom", &bloom)) {
+				buildCommandBuffers();
+			}
+			if (overlay->inputFloat("Scale", &ubos.blurParams.blurScale, 0.1f, 2)) {
+				updateUniformBuffersBlur();
+			}
+		}
+	}
+};
+
+VULKAN_EXAMPLE_MAIN()
diff --git a/external/Vulkan/examples/computecloth/computecloth.cpp b/external/Vulkan/examples/computecloth/computecloth.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b08fd55c1a6e10cd36084f6e401547bcdaa9a728
--- /dev/null
+++ b/external/Vulkan/examples/computecloth/computecloth.cpp
@@ -0,0 +1,788 @@
+/*
+* Vulkan Example - Compute shader sloth simulation
+*
+* Copyright (C) 2016-2017 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkanexamplebase.h"
+#include "VulkanglTFModel.h"
+
+#define ENABLE_VALIDATION false
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	uint32_t sceneSetup = 0;
+	uint32_t readSet = 0;
+	uint32_t indexCount;
+	bool simulateWind = false;
+	bool specializedComputeQueue = false;
+
+	vks::Texture2D textureCloth;
+	vkglTF::Model modelSphere;
+
+	// Resources for the graphics part of the example
+	struct {
+		VkDescriptorSetLayout descriptorSetLayout;
+		VkDescriptorSet descriptorSet;
+		VkPipelineLayout pipelineLayout;
+		struct Pipelines {
+			VkPipeline cloth;
+			VkPipeline sphere;
+		} pipelines;
+		vks::Buffer indices;
+		vks::Buffer uniformBuffer;
+		struct graphicsUBO {
+			glm::mat4 projection;
+			glm::mat4 view;
+			glm::vec4 lightPos = glm::vec4(-2.0f, 4.0f, -2.0f, 1.0f);
+		} ubo;
+	} graphics;
+
+	// Resources for the compute part of the example
+	struct {
+		struct StorageBuffers {
+			vks::Buffer input;
+			vks::Buffer output;
+		} storageBuffers;
+		struct Semaphores {
+			VkSemaphore ready{ 0L };
+			VkSemaphore complete{ 0L };
+		} semaphores;
+		vks::Buffer uniformBuffer;
+		VkQueue queue;
+		VkCommandPool commandPool;
+		std::array<VkCommandBuffer,2> commandBuffers;
+		VkDescriptorSetLayout descriptorSetLayout;
+		std::array<VkDescriptorSet,2> descriptorSets;
+		VkPipelineLayout pipelineLayout;
+		VkPipeline pipeline;
+		struct computeUBO {
+			float deltaT = 0.0f;
+			float particleMass = 0.1f;
+			float springStiffness = 2000.0f;
+			float damping = 0.25f;
+			float restDistH;
+			float restDistV;
+			float restDistD;
+			float sphereRadius = 1.0f;
+			glm::vec4 spherePos = glm::vec4(0.0f, 0.0f, 0.0f, 0.0f);
+			glm::vec4 gravity = glm::vec4(0.0f, 9.8f, 0.0f, 0.0f);
+			glm::ivec2 particleCount;
+		} ubo;
+	} compute;
+
+	// SSBO cloth grid particle declaration
+	struct Particle {
+		glm::vec4 pos;
+		glm::vec4 vel;
+		glm::vec4 uv;
+		glm::vec4 normal;
+		float pinned;
+		glm::vec3 _pad0;
+	};
+
+	struct Cloth {
+		glm::uvec2 gridsize = glm::uvec2(60, 60);
+		glm::vec2 size = glm::vec2(5.0f);
+	} cloth;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Compute shader cloth simulation";
+		camera.type = Camera::CameraType::lookat;
+		camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 512.0f);
+		camera.setRotation(glm::vec3(-30.0f, -45.0f, 0.0f));
+		camera.setTranslation(glm::vec3(0.0f, 0.0f, -5.0f));
+	}
+
+	~VulkanExample()
+	{
+		// Graphics
+		graphics.uniformBuffer.destroy();
+		vkDestroyPipeline(device, graphics.pipelines.cloth, nullptr);
+		vkDestroyPipeline(device, graphics.pipelines.sphere, nullptr);
+		vkDestroyPipelineLayout(device, graphics.pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, graphics.descriptorSetLayout, nullptr);
+		textureCloth.destroy();
+
+		// Compute
+		compute.storageBuffers.input.destroy();
+		compute.storageBuffers.output.destroy();
+		compute.uniformBuffer.destroy();
+		vkDestroyPipelineLayout(device, compute.pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, compute.descriptorSetLayout, nullptr);
+		vkDestroyPipeline(device, compute.pipeline, nullptr);
+		vkDestroySemaphore(device, compute.semaphores.ready, nullptr);
+		vkDestroySemaphore(device, compute.semaphores.complete, nullptr);
+		vkDestroyCommandPool(device, compute.commandPool, nullptr);
+	}
+
+	// Enable physical device features required for this example
+	virtual void getEnabledFeatures()
+	{
+		if (deviceFeatures.samplerAnisotropy) {
+			enabledFeatures.samplerAnisotropy = VK_TRUE;
+		}
+	};
+
+	void loadAssets()
+	{
+		const uint32_t glTFLoadingFlags = vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::PreMultiplyVertexColors | vkglTF::FileLoadingFlags::FlipY;
+		modelSphere.loadFromFile(getAssetPath() + "models/sphere.gltf", vulkanDevice, queue, glTFLoadingFlags);
+		textureCloth.loadFromFile(getAssetPath() + "textures/vulkan_cloth_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue);
+	}
+
+	void addGraphicsToComputeBarriers(VkCommandBuffer commandBuffer)
+	{
+		if (specializedComputeQueue) {
+			VkBufferMemoryBarrier bufferBarrier = vks::initializers::bufferMemoryBarrier();
+			bufferBarrier.srcAccessMask = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT;
+			bufferBarrier.dstAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
+			bufferBarrier.srcQueueFamilyIndex = vulkanDevice->queueFamilyIndices.graphics;
+			bufferBarrier.dstQueueFamilyIndex = vulkanDevice->queueFamilyIndices.compute;
+			bufferBarrier.size = VK_WHOLE_SIZE;
+
+			std::vector<VkBufferMemoryBarrier> bufferBarriers;
+			bufferBarrier.buffer = compute.storageBuffers.input.buffer;
+			bufferBarriers.push_back(bufferBarrier);
+			bufferBarrier.buffer = compute.storageBuffers.output.buffer;
+			bufferBarriers.push_back(bufferBarrier);
+			vkCmdPipelineBarrier(commandBuffer,
+				VK_PIPELINE_STAGE_VERTEX_INPUT_BIT,
+				VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+				VK_FLAGS_NONE,
+				0, nullptr,
+				static_cast<uint32_t>(bufferBarriers.size()), bufferBarriers.data(),
+				0, nullptr);
+		}
+	}
+
+	void addComputeToComputeBarriers(VkCommandBuffer commandBuffer)
+	{
+		VkBufferMemoryBarrier bufferBarrier = vks::initializers::bufferMemoryBarrier();
+		bufferBarrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
+		bufferBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
+		bufferBarrier.srcQueueFamilyIndex = vulkanDevice->queueFamilyIndices.compute;
+		bufferBarrier.dstQueueFamilyIndex = vulkanDevice->queueFamilyIndices.compute;
+		bufferBarrier.size = VK_WHOLE_SIZE;
+		std::vector<VkBufferMemoryBarrier> bufferBarriers;
+		bufferBarrier.buffer = compute.storageBuffers.input.buffer;
+		bufferBarriers.push_back(bufferBarrier);
+		bufferBarrier.buffer = compute.storageBuffers.output.buffer;
+		bufferBarriers.push_back(bufferBarrier);
+		vkCmdPipelineBarrier(
+			commandBuffer,
+			VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+			VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+			VK_FLAGS_NONE,
+			0, nullptr,
+			static_cast<uint32_t>(bufferBarriers.size()), bufferBarriers.data(),
+			0, nullptr);
+	}
+
+	void addComputeToGraphicsBarriers(VkCommandBuffer commandBuffer)
+	{
+		if (specializedComputeQueue) {
+			VkBufferMemoryBarrier bufferBarrier = vks::initializers::bufferMemoryBarrier();
+			bufferBarrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
+			bufferBarrier.dstAccessMask = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT;
+			bufferBarrier.srcQueueFamilyIndex = vulkanDevice->queueFamilyIndices.compute;
+			bufferBarrier.dstQueueFamilyIndex = vulkanDevice->queueFamilyIndices.graphics;
+			bufferBarrier.size = VK_WHOLE_SIZE;
+			std::vector<VkBufferMemoryBarrier> bufferBarriers;
+			bufferBarrier.buffer = compute.storageBuffers.input.buffer;
+			bufferBarriers.push_back(bufferBarrier);
+			bufferBarrier.buffer = compute.storageBuffers.output.buffer;
+			bufferBarriers.push_back(bufferBarrier);
+			vkCmdPipelineBarrier(
+				commandBuffer,
+				VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+				VK_PIPELINE_STAGE_VERTEX_INPUT_BIT,
+				VK_FLAGS_NONE,
+				0, nullptr,
+				static_cast<uint32_t>(bufferBarriers.size()), bufferBarriers.data(),
+				0, nullptr);
+		}
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		clearValues[0].color = { { 0.0f, 0.0f, 0.0f, 1.0f } };;
+		clearValues[1].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.offset.x = 0;
+		renderPassBeginInfo.renderArea.offset.y = 0;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		renderPassBeginInfo.clearValueCount = 2;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			// Set target frame buffer
+			renderPassBeginInfo.framebuffer = frameBuffers[i];
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			// Acquire storage buffers from compute queue
+			addComputeToGraphicsBarriers(drawCmdBuffers[i]);
+
+			// Draw the particle system using the update vertex buffer
+
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+			VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+			VkDeviceSize offsets[1] = { 0 };
+
+			// Render sphere
+			if (sceneSetup == 0) {
+				vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphics.pipelines.sphere);
+				vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphics.pipelineLayout, 0, 1, &graphics.descriptorSet, 0, NULL);
+				modelSphere.draw(drawCmdBuffers[i]);
+			}
+
+			// Render cloth
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphics.pipelines.cloth);
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphics.pipelineLayout, 0, 1, &graphics.descriptorSet, 0, NULL);
+			vkCmdBindIndexBuffer(drawCmdBuffers[i], graphics.indices.buffer, 0, VK_INDEX_TYPE_UINT32);
+			vkCmdBindVertexBuffers(drawCmdBuffers[i], 0, 1, &compute.storageBuffers.output.buffer, offsets);
+			vkCmdDrawIndexed(drawCmdBuffers[i], indexCount, 1, 0, 0, 0);
+
+			drawUI(drawCmdBuffers[i]);
+
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			// release the storage buffers to the compute queue
+			addGraphicsToComputeBarriers(drawCmdBuffers[i]);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+
+	}
+
+	// todo: check barriers (validation, separate compute queue)
+	void buildComputeCommandBuffer()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+		cmdBufInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT;
+
+		for (uint32_t i = 0; i < 2; i++) {
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(compute.commandBuffers[i], &cmdBufInfo));
+
+			// Acquire the storage buffers from the graphics queue
+			addGraphicsToComputeBarriers(compute.commandBuffers[i]);
+
+			vkCmdBindPipeline(compute.commandBuffers[i], VK_PIPELINE_BIND_POINT_COMPUTE, compute.pipeline);
+
+			uint32_t calculateNormals = 0;
+			vkCmdPushConstants(compute.commandBuffers[i], compute.pipelineLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(uint32_t), &calculateNormals);
+
+			// Dispatch the compute job
+			const uint32_t iterations = 64;
+			for (uint32_t j = 0; j < iterations; j++) {
+				readSet = 1 - readSet;
+				vkCmdBindDescriptorSets(compute.commandBuffers[i], VK_PIPELINE_BIND_POINT_COMPUTE, compute.pipelineLayout, 0, 1, &compute.descriptorSets[readSet], 0, 0);
+
+				if (j == iterations - 1) {
+					calculateNormals = 1;
+					vkCmdPushConstants(compute.commandBuffers[i], compute.pipelineLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(uint32_t), &calculateNormals);
+				}
+
+				vkCmdDispatch(compute.commandBuffers[i], cloth.gridsize.x / 10, cloth.gridsize.y / 10, 1);
+
+				// Don't add a barrier on the last iteration of the loop, since we'll have an explicit release to the graphics queue
+				if (j != iterations - 1) {
+					addComputeToComputeBarriers(compute.commandBuffers[i]);
+				}
+
+			}
+
+			// release the storage buffers back to the graphics queue
+			addComputeToGraphicsBarriers(compute.commandBuffers[i]);
+			vkEndCommandBuffer(compute.commandBuffers[i]);
+		}
+	}
+
+	// Setup and fill the compute shader storage buffers containing the particles
+	void prepareStorageBuffers()
+	{
+		std::vector<Particle> particleBuffer(cloth.gridsize.x *  cloth.gridsize.y);
+
+		float dx =  cloth.size.x / (cloth.gridsize.x - 1);
+		float dy =  cloth.size.y / (cloth.gridsize.y - 1);
+		float du = 1.0f / (cloth.gridsize.x - 1);
+		float dv = 1.0f / (cloth.gridsize.y - 1);
+
+		switch (sceneSetup) {
+			case 0 :
+			{
+				// Horz. cloth falls onto sphere
+				glm::mat4 transM = glm::translate(glm::mat4(1.0f), glm::vec3(- cloth.size.x / 2.0f, -2.0f, - cloth.size.y / 2.0f));
+				for (uint32_t i = 0; i <  cloth.gridsize.y; i++) {
+					for (uint32_t j = 0; j <  cloth.gridsize.x; j++) {
+						particleBuffer[i + j * cloth.gridsize.y].pos = transM * glm::vec4(dx * j, 0.0f, dy * i, 1.0f);
+						particleBuffer[i + j * cloth.gridsize.y].vel = glm::vec4(0.0f);
+						particleBuffer[i + j * cloth.gridsize.y].uv = glm::vec4(1.0f - du * i, dv * j, 0.0f, 0.0f);
+					}
+				}
+				break;
+			}
+			case 1:
+			{
+				// Vert. Pinned cloth
+				glm::mat4 transM = glm::translate(glm::mat4(1.0f), glm::vec3(- cloth.size.x / 2.0f, - cloth.size.y / 2.0f, 0.0f));
+				for (uint32_t i = 0; i <  cloth.gridsize.y; i++) {
+					for (uint32_t j = 0; j <  cloth.gridsize.x; j++) {
+						particleBuffer[i + j * cloth.gridsize.y].pos = transM * glm::vec4(dx * j, dy * i, 0.0f, 1.0f);
+						particleBuffer[i + j * cloth.gridsize.y].vel = glm::vec4(0.0f);
+						particleBuffer[i + j * cloth.gridsize.y].uv = glm::vec4(du * j, dv * i, 0.0f, 0.0f);
+						// Pin some particles
+						particleBuffer[i + j * cloth.gridsize.y].pinned = (i == 0) && ((j == 0) || (j ==  cloth.gridsize.x / 3) || (j ==  cloth.gridsize.x -  cloth.gridsize.x / 3) || (j ==  cloth.gridsize.x - 1));
+						// Remove sphere
+						compute.ubo.spherePos.z = -10.0f;
+					}
+				}
+				break;
+			}
+		}
+
+		VkDeviceSize storageBufferSize = particleBuffer.size() * sizeof(Particle);
+
+		// Staging
+		// SSBO won't be changed on the host after upload so copy to device local memory
+
+		vks::Buffer stagingBuffer;
+
+		vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&stagingBuffer,
+			storageBufferSize,
+			particleBuffer.data());
+
+		vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
+			VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
+			&compute.storageBuffers.input,
+			storageBufferSize);
+
+		vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
+			VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
+			&compute.storageBuffers.output,
+			storageBufferSize);
+
+		// Copy from staging buffer
+		VkCommandBuffer copyCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+		VkBufferCopy copyRegion = {};
+		copyRegion.size = storageBufferSize;
+		vkCmdCopyBuffer(copyCmd, stagingBuffer.buffer, compute.storageBuffers.input.buffer, 1, &copyRegion);
+		vkCmdCopyBuffer(copyCmd, stagingBuffer.buffer, compute.storageBuffers.output.buffer, 1, &copyRegion);
+		// Add an initial release barrier to the graphics queue,
+		// so that when the compute command buffer executes for the first time
+		// it doesn't complain about a lack of a corresponding "release" to its "acquire"
+		addGraphicsToComputeBarriers(copyCmd);
+		vulkanDevice->flushCommandBuffer(copyCmd, queue, true);
+
+		stagingBuffer.destroy();
+
+		// Indices
+		std::vector<uint32_t> indices;
+		for (uint32_t y = 0; y <  cloth.gridsize.y - 1; y++) {
+			for (uint32_t x = 0; x <  cloth.gridsize.x; x++) {
+				indices.push_back((y + 1) *  cloth.gridsize.x + x);
+				indices.push_back((y)*  cloth.gridsize.x + x);
+			}
+			// Primitive restart (signaled by special value 0xFFFFFFFF)
+			indices.push_back(0xFFFFFFFF);
+		}
+		uint32_t indexBufferSize = static_cast<uint32_t>(indices.size()) * sizeof(uint32_t);
+		indexCount = static_cast<uint32_t>(indices.size());
+
+		vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&stagingBuffer,
+			indexBufferSize,
+			indices.data());
+
+		vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
+			VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
+			&graphics.indices,
+			indexBufferSize);
+
+		// Copy from staging buffer
+		copyCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+		copyRegion = {};
+		copyRegion.size = indexBufferSize;
+		vkCmdCopyBuffer(copyCmd, stagingBuffer.buffer, graphics.indices.buffer, 1, &copyRegion);
+		vulkanDevice->flushCommandBuffer(copyCmd, queue, true);
+
+		stagingBuffer.destroy();
+	}
+
+	void setupDescriptorPool()
+	{
+		std::vector<VkDescriptorPoolSize> poolSizes = {
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 3),
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 4),
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2)
+		};
+
+		VkDescriptorPoolCreateInfo descriptorPoolInfo =
+			vks::initializers::descriptorPoolCreateInfo(poolSizes, 3);
+
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+	}
+
+	void setupLayoutsAndDescriptors()
+	{
+		// Set layout
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0),
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1)
+		};
+		VkDescriptorSetLayoutCreateInfo descriptorLayout =
+			vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &graphics.descriptorSetLayout));
+
+		VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo =
+			vks::initializers::pipelineLayoutCreateInfo(&graphics.descriptorSetLayout, 1);
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &graphics.pipelineLayout));
+
+		// Set
+		VkDescriptorSetAllocateInfo allocInfo =
+			vks::initializers::descriptorSetAllocateInfo(descriptorPool, &graphics.descriptorSetLayout, 1);
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &graphics.descriptorSet));
+
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets = {
+			vks::initializers::writeDescriptorSet(graphics.descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &graphics.uniformBuffer.descriptor),
+			vks::initializers::writeDescriptorSet(graphics.descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &textureCloth.descriptor)
+		};
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL);
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState =
+			vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, 0, VK_TRUE);
+
+		VkPipelineRasterizationStateCreateInfo rasterizationState =
+			vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
+
+		VkPipelineColorBlendAttachmentState blendAttachmentState =
+			vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+
+		VkPipelineColorBlendStateCreateInfo colorBlendState =
+			vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+
+		VkPipelineDepthStencilStateCreateInfo depthStencilState =
+			vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+
+		VkPipelineViewportStateCreateInfo viewportState =
+			vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+
+		VkPipelineMultisampleStateCreateInfo multisampleState =
+			vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
+
+		std::vector<VkDynamicState> dynamicStateEnables = {
+			VK_DYNAMIC_STATE_VIEWPORT,
+			VK_DYNAMIC_STATE_SCISSOR
+		};
+		VkPipelineDynamicStateCreateInfo dynamicState =
+			vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables, 0);
+
+		// Rendering pipeline
+		std::array<VkPipelineShaderStageCreateInfo,2> shaderStages;
+
+		shaderStages[0] = loadShader(getShadersPath() + "computecloth/cloth.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "computecloth/cloth.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+
+		VkGraphicsPipelineCreateInfo pipelineCreateInfo = vks::initializers::pipelineCreateInfo(graphics.pipelineLayout, renderPass);
+
+		// Input attributes
+
+		// Binding description
+		std::vector<VkVertexInputBindingDescription> inputBindings = {
+			vks::initializers::vertexInputBindingDescription(0, sizeof(Particle), VK_VERTEX_INPUT_RATE_VERTEX)
+		};
+
+		// Attribute descriptions
+		std::vector<VkVertexInputAttributeDescription> inputAttributes = {
+			vks::initializers::vertexInputAttributeDescription(0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Particle, pos)),
+			vks::initializers::vertexInputAttributeDescription(0, 1, VK_FORMAT_R32G32_SFLOAT, offsetof(Particle, uv)),
+			vks::initializers::vertexInputAttributeDescription(0, 2, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Particle, normal))
+		};
+
+		// Assign to vertex buffer
+		VkPipelineVertexInputStateCreateInfo inputState = vks::initializers::pipelineVertexInputStateCreateInfo();
+		inputState.vertexBindingDescriptionCount = static_cast<uint32_t>(inputBindings.size());
+		inputState.pVertexBindingDescriptions = inputBindings.data();
+		inputState.vertexAttributeDescriptionCount = static_cast<uint32_t>(inputAttributes.size());
+		inputState.pVertexAttributeDescriptions = inputAttributes.data();
+
+		pipelineCreateInfo.pVertexInputState = &inputState;
+		pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState;
+		pipelineCreateInfo.pRasterizationState = &rasterizationState;
+		pipelineCreateInfo.pColorBlendState = &colorBlendState;
+		pipelineCreateInfo.pMultisampleState = &multisampleState;
+		pipelineCreateInfo.pViewportState = &viewportState;
+		pipelineCreateInfo.pDepthStencilState = &depthStencilState;
+		pipelineCreateInfo.pDynamicState = &dynamicState;
+		pipelineCreateInfo.stageCount = static_cast<uint32_t>(shaderStages.size());
+		pipelineCreateInfo.pStages = shaderStages.data();
+		pipelineCreateInfo.renderPass = renderPass;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &graphics.pipelines.cloth));
+
+		// Sphere rendering pipeline
+		pipelineCreateInfo.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position, vkglTF::VertexComponent::UV, vkglTF::VertexComponent::Normal });
+		inputState.vertexAttributeDescriptionCount = static_cast<uint32_t>(inputAttributes.size());
+		inputAssemblyState.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
+		inputAssemblyState.primitiveRestartEnable = VK_FALSE;
+		rasterizationState.polygonMode = VK_POLYGON_MODE_FILL;
+		shaderStages[0] = loadShader(getShadersPath() + "computecloth/sphere.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "computecloth/sphere.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &graphics.pipelines.sphere));
+	}
+
+	void prepareCompute()
+	{
+		// Create a compute capable device queue
+		vkGetDeviceQueue(device, vulkanDevice->queueFamilyIndices.compute, 0, &compute.queue);
+
+		// Create compute pipeline
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_SHADER_STAGE_COMPUTE_BIT, 0),
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_SHADER_STAGE_COMPUTE_BIT, 1),
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_COMPUTE_BIT, 2),
+		};
+
+		VkDescriptorSetLayoutCreateInfo descriptorLayout =
+			vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
+
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device,	&descriptorLayout, nullptr,	&compute.descriptorSetLayout));
+
+		VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo =
+			vks::initializers::pipelineLayoutCreateInfo(&compute.descriptorSetLayout, 1);
+
+		// Push constants used to pass some parameters
+		VkPushConstantRange pushConstantRange =
+			vks::initializers::pushConstantRange(VK_SHADER_STAGE_COMPUTE_BIT, sizeof(uint32_t), 0);
+		pipelineLayoutCreateInfo.pushConstantRangeCount = 1;
+		pipelineLayoutCreateInfo.pPushConstantRanges = &pushConstantRange;
+
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr,	&compute.pipelineLayout));
+
+		VkDescriptorSetAllocateInfo allocInfo =
+			vks::initializers::descriptorSetAllocateInfo(descriptorPool, &compute.descriptorSetLayout, 1);
+
+		// Create two descriptor sets with input and output buffers switched
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &compute.descriptorSets[0]));
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &compute.descriptorSets[1]));
+
+		std::vector<VkWriteDescriptorSet> computeWriteDescriptorSets = {
+			vks::initializers::writeDescriptorSet(compute.descriptorSets[0], VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 0, &compute.storageBuffers.input.descriptor),
+			vks::initializers::writeDescriptorSet(compute.descriptorSets[0], VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, &compute.storageBuffers.output.descriptor),
+			vks::initializers::writeDescriptorSet(compute.descriptorSets[0], VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2, &compute.uniformBuffer.descriptor),
+
+			vks::initializers::writeDescriptorSet(compute.descriptorSets[1], VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 0, &compute.storageBuffers.output.descriptor),
+			vks::initializers::writeDescriptorSet(compute.descriptorSets[1], VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, &compute.storageBuffers.input.descriptor),
+			vks::initializers::writeDescriptorSet(compute.descriptorSets[1], VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2, &compute.uniformBuffer.descriptor)
+		};
+
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(computeWriteDescriptorSets.size()), computeWriteDescriptorSets.data(), 0, NULL);
+
+		// Create pipeline
+		VkComputePipelineCreateInfo computePipelineCreateInfo = vks::initializers::computePipelineCreateInfo(compute.pipelineLayout, 0);
+		computePipelineCreateInfo.stage = loadShader(getShadersPath() + "computecloth/cloth.comp.spv", VK_SHADER_STAGE_COMPUTE_BIT);
+		VK_CHECK_RESULT(vkCreateComputePipelines(device, pipelineCache, 1, &computePipelineCreateInfo, nullptr, &compute.pipeline));
+
+		// Separate command pool as queue family for compute may be different than graphics
+		VkCommandPoolCreateInfo cmdPoolInfo = {};
+		cmdPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
+		cmdPoolInfo.queueFamilyIndex = vulkanDevice->queueFamilyIndices.compute;
+		cmdPoolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
+		VK_CHECK_RESULT(vkCreateCommandPool(device, &cmdPoolInfo, nullptr, &compute.commandPool));
+
+		// Create a command buffer for compute operations
+		VkCommandBufferAllocateInfo cmdBufAllocateInfo =
+			vks::initializers::commandBufferAllocateInfo(compute.commandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, 2);
+
+		VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, &compute.commandBuffers[0]));
+
+		// Semaphores for graphics / compute synchronization
+		VkSemaphoreCreateInfo semaphoreCreateInfo = vks::initializers::semaphoreCreateInfo();
+		VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &compute.semaphores.ready));
+		VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &compute.semaphores.complete));
+
+		// Build a single command buffer containing the compute dispatch commands
+		buildComputeCommandBuffer();
+	}
+
+	// Prepare and initialize uniform buffer containing shader uniforms
+	void prepareUniformBuffers()
+	{
+		// Compute shader uniform buffer block
+		vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&compute.uniformBuffer,
+			sizeof(compute.ubo));
+		VK_CHECK_RESULT(compute.uniformBuffer.map());
+
+		// Initial values
+		float dx = cloth.size.x / (cloth.gridsize.x - 1);
+		float dy = cloth.size.y / (cloth.gridsize.y - 1);
+
+		compute.ubo.restDistH = dx;
+		compute.ubo.restDistV = dy;
+		compute.ubo.restDistD = sqrtf(dx * dx + dy * dy);
+		compute.ubo.particleCount = cloth.gridsize;
+
+		updateComputeUBO();
+
+		// Vertex shader uniform buffer block
+		vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&graphics.uniformBuffer,
+			sizeof(graphics.ubo));
+		VK_CHECK_RESULT(graphics.uniformBuffer.map());
+
+		updateGraphicsUBO();
+	}
+
+	void updateComputeUBO()
+	{
+		if (!paused) {
+			compute.ubo.deltaT = 0.000005f;
+			// todo: base on frametime
+			//compute.ubo.deltaT = frameTimer * 0.0075f;
+
+			if (simulateWind) {
+				std::default_random_engine rndEngine(benchmark.active ? 0 : (unsigned)time(nullptr));
+				std::uniform_real_distribution<float> rd(1.0f, 6.0f);
+				compute.ubo.gravity.x = cos(glm::radians(-timer * 360.0f)) * (rd(rndEngine) - rd(rndEngine));
+				compute.ubo.gravity.z = sin(glm::radians(timer * 360.0f)) * (rd(rndEngine) - rd(rndEngine));
+			}
+			else {
+				compute.ubo.gravity.x = 0.0f;
+				compute.ubo.gravity.z = 0.0f;
+			}
+		}
+		else {
+			compute.ubo.deltaT = 0.0f;
+		}
+		memcpy(compute.uniformBuffer.mapped, &compute.ubo, sizeof(compute.ubo));
+	}
+
+	void updateGraphicsUBO()
+	{
+		graphics.ubo.projection = camera.matrices.perspective;
+		graphics.ubo.view = camera.matrices.view;
+		memcpy(graphics.uniformBuffer.mapped, &graphics.ubo, sizeof(graphics.ubo));
+	}
+
+	void draw()
+	{
+		static bool firstDraw = true;
+		VkSubmitInfo computeSubmitInfo = vks::initializers::submitInfo();
+		// FIXME find a better way to do this (without using fences, which is much slower)
+		VkPipelineStageFlags computeWaitDstStageMask = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
+		if (!firstDraw) {
+			computeSubmitInfo.waitSemaphoreCount = 1;
+			computeSubmitInfo.pWaitSemaphores = &compute.semaphores.ready;
+			computeSubmitInfo.pWaitDstStageMask = &computeWaitDstStageMask;
+		} else {
+			firstDraw = false;
+		}
+		computeSubmitInfo.signalSemaphoreCount = 1;
+		computeSubmitInfo.pSignalSemaphores = &compute.semaphores.complete;
+		computeSubmitInfo.commandBufferCount = 1;
+		computeSubmitInfo.pCommandBuffers = &compute.commandBuffers[readSet];
+
+		VK_CHECK_RESULT( vkQueueSubmit( compute.queue, 1, &computeSubmitInfo, VK_NULL_HANDLE) );
+
+		// Submit graphics commands
+		VulkanExampleBase::prepareFrame();
+
+		VkPipelineStageFlags waitDstStageMask[2] = {
+			submitPipelineStages, VK_PIPELINE_STAGE_VERTEX_INPUT_BIT
+		};
+		VkSemaphore waitSemaphores[2] = {
+			semaphores.presentComplete, compute.semaphores.complete
+		};
+		VkSemaphore signalSemaphores[2] = {
+			semaphores.renderComplete, compute.semaphores.ready
+		};
+
+		submitInfo.waitSemaphoreCount = 2;
+		submitInfo.pWaitDstStageMask = waitDstStageMask;
+		submitInfo.pWaitSemaphores = waitSemaphores;
+		submitInfo.signalSemaphoreCount = 2;
+		submitInfo.pSignalSemaphores = signalSemaphores;
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+
+		VulkanExampleBase::submitFrame();
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		// Make sure the code works properly both with different queues families for graphics and compute and the same queue family
+#ifdef DEBUG_FORCE_SHARED_GRAPHICS_COMPUTE_QUEUE
+		vulkanDevice->queueFamilyIndices.compute = vulkanDevice->queueFamilyIndices.graphics;
+#endif
+		// Check whether the compute queue family is distinct from the graphics queue family
+		specializedComputeQueue = vulkanDevice->queueFamilyIndices.graphics != vulkanDevice->queueFamilyIndices.compute;
+		loadAssets();
+		prepareStorageBuffers();
+		prepareUniformBuffers();
+		setupDescriptorPool();
+		setupLayoutsAndDescriptors();
+		preparePipelines();
+		prepareCompute();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+
+		updateComputeUBO();
+	}
+
+	virtual void viewChanged()
+	{
+		updateGraphicsUBO();
+	}
+
+	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
+	{
+		if (overlay->header("Settings")) {
+			overlay->checkBox("Simulate wind", &simulateWind);
+		}
+	}
+};
+
+VULKAN_EXAMPLE_MAIN()
diff --git a/external/Vulkan/examples/computecullandlod/computecullandlod.cpp b/external/Vulkan/examples/computecullandlod/computecullandlod.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c18886598a9d39fdb1321a20fbb852fcd86ed796
--- /dev/null
+++ b/external/Vulkan/examples/computecullandlod/computecullandlod.cpp
@@ -0,0 +1,729 @@
+/*
+* Vulkan Example - Compute shader culling and LOD using indirect rendering
+*
+* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*
+*/
+
+#include "vulkanexamplebase.h"
+#include "VulkanglTFModel.h"
+#include "frustum.hpp"
+
+#define VERTEX_BUFFER_BIND_ID 0
+#define INSTANCE_BUFFER_BIND_ID 1
+#define ENABLE_VALIDATION false
+
+// Total number of objects (^3) in the scene
+#if defined(__ANDROID__)
+#define OBJECT_COUNT 32
+#else
+#define OBJECT_COUNT 64
+#endif
+
+#define MAX_LOD_LEVEL 5
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	bool fixedFrustum = false;
+
+	// The model contains multiple versions of a single object with different levels of detail
+	vkglTF::Model lodModel;
+
+	// Per-instance data block
+	struct InstanceData {
+		glm::vec3 pos;
+		float scale;
+	};
+
+	// Contains the instanced data
+	vks::Buffer instanceBuffer;
+	// Contains the indirect drawing commands
+	vks::Buffer indirectCommandsBuffer;
+	vks::Buffer indirectDrawCountBuffer;
+
+	// Indirect draw statistics (updated via compute)
+	struct {
+		uint32_t drawCount;						// Total number of indirect draw counts to be issued
+		uint32_t lodCount[MAX_LOD_LEVEL + 1];	// Statistics for number of draws per LOD level (written by compute shader)
+	} indirectStats;
+
+	// Store the indirect draw commands containing index offsets and instance count per object
+	std::vector<VkDrawIndexedIndirectCommand> indirectCommands;
+
+	struct {
+		glm::mat4 projection;
+		glm::mat4 modelview;
+		glm::vec4 cameraPos;
+		glm::vec4 frustumPlanes[6];
+	} uboScene;
+
+	struct {
+		vks::Buffer scene;
+	} uniformData;
+
+	struct {
+		VkPipeline plants;
+	} pipelines;
+
+	VkPipelineLayout pipelineLayout;
+	VkDescriptorSet descriptorSet;
+	VkDescriptorSetLayout descriptorSetLayout;
+
+	// Resources for the compute part of the example
+	struct {
+		vks::Buffer lodLevelsBuffers;				// Contains index start and counts for the different lod levels
+		VkQueue queue;								// Separate queue for compute commands (queue family may differ from the one used for graphics)
+		VkCommandPool commandPool;					// Use a separate command pool (queue family may differ from the one used for graphics)
+		VkCommandBuffer commandBuffer;				// Command buffer storing the dispatch commands and barriers
+		VkFence fence;								// Synchronization fence to avoid rewriting compute CB if still in use
+		VkSemaphore semaphore;						// Used as a wait semaphore for graphics submission
+		VkDescriptorSetLayout descriptorSetLayout;	// Compute shader binding layout
+		VkDescriptorSet descriptorSet;				// Compute shader bindings
+		VkPipelineLayout pipelineLayout;			// Layout of the compute pipeline
+		VkPipeline pipeline;						// Compute pipeline for updating particle positions
+	} compute;
+
+	// View frustum for culling invisible objects
+	vks::Frustum frustum;
+
+	uint32_t objectCount = 0;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Vulkan Example - Compute cull and lod";
+		camera.type = Camera::CameraType::firstperson;
+		camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 512.0f);
+		camera.setTranslation(glm::vec3(0.5f, 0.0f, 0.0f));
+		camera.movementSpeed = 5.0f;
+		memset(&indirectStats, 0, sizeof(indirectStats));
+	}
+
+	~VulkanExample()
+	{
+		vkDestroyPipeline(device, pipelines.plants, nullptr);
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+		instanceBuffer.destroy();
+		indirectCommandsBuffer.destroy();
+		uniformData.scene.destroy();
+		indirectDrawCountBuffer.destroy();
+		compute.lodLevelsBuffers.destroy();
+		vkDestroyPipelineLayout(device, compute.pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, compute.descriptorSetLayout, nullptr);
+		vkDestroyPipeline(device, compute.pipeline, nullptr);
+		vkDestroyFence(device, compute.fence, nullptr);
+		vkDestroyCommandPool(device, compute.commandPool, nullptr);
+		vkDestroySemaphore(device, compute.semaphore, nullptr);
+	}
+
+	virtual void getEnabledFeatures()
+	{
+		// Enable multi draw indirect if supported
+		if (deviceFeatures.multiDrawIndirect) {
+			enabledFeatures.multiDrawIndirect = VK_TRUE;
+		}
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		clearValues[0].color = { { 0.18f, 0.27f, 0.5f, 0.0f } };
+		clearValues[1].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		renderPassBeginInfo.clearValueCount = 2;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			// Set target frame buffer
+			renderPassBeginInfo.framebuffer = frameBuffers[i];
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+			VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+			VkDeviceSize offsets[1] = { 0 };
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL);
+
+			// Mesh containing the LODs
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.plants);
+			vkCmdBindVertexBuffers(drawCmdBuffers[i], VERTEX_BUFFER_BIND_ID, 1, &lodModel.vertices.buffer, offsets);
+			vkCmdBindVertexBuffers(drawCmdBuffers[i], INSTANCE_BUFFER_BIND_ID, 1, &instanceBuffer.buffer, offsets);
+
+			vkCmdBindIndexBuffer(drawCmdBuffers[i], lodModel.indices.buffer, 0, VK_INDEX_TYPE_UINT32);
+
+			if (vulkanDevice->features.multiDrawIndirect)
+			{
+				vkCmdDrawIndexedIndirect(drawCmdBuffers[i], indirectCommandsBuffer.buffer, 0, indirectCommands.size(), sizeof(VkDrawIndexedIndirectCommand));
+			}
+			else
+			{
+				// If multi draw is not available, we must issue separate draw commands
+				for (auto j = 0; j < indirectCommands.size(); j++)
+				{
+					vkCmdDrawIndexedIndirect(drawCmdBuffers[i], indirectCommandsBuffer.buffer, j * sizeof(VkDrawIndexedIndirectCommand), 1, sizeof(VkDrawIndexedIndirectCommand));
+				}
+			}
+
+			drawUI(drawCmdBuffers[i]);
+
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void loadAssets()
+	{
+		const uint32_t glTFLoadingFlags = vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::PreMultiplyVertexColors | vkglTF::FileLoadingFlags::FlipY;
+		lodModel.loadFromFile(getAssetPath() + "models/suzanne_lods.gltf", vulkanDevice, queue, glTFLoadingFlags);
+	}
+
+	void buildComputeCommandBuffer()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VK_CHECK_RESULT(vkBeginCommandBuffer(compute.commandBuffer, &cmdBufInfo));
+
+		// Add memory barrier to ensure that the indirect commands have been consumed before the compute shader updates them
+		VkBufferMemoryBarrier bufferBarrier = vks::initializers::bufferMemoryBarrier();
+		bufferBarrier.buffer = indirectCommandsBuffer.buffer;
+		bufferBarrier.size = indirectCommandsBuffer.descriptor.range;
+		bufferBarrier.srcAccessMask = VK_ACCESS_INDIRECT_COMMAND_READ_BIT;
+		bufferBarrier.dstAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
+		bufferBarrier.srcQueueFamilyIndex = vulkanDevice->queueFamilyIndices.graphics;
+		bufferBarrier.dstQueueFamilyIndex = vulkanDevice->queueFamilyIndices.compute;
+
+		vkCmdPipelineBarrier(
+			compute.commandBuffer,
+			VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT,
+			VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+			VK_FLAGS_NONE,
+			0, nullptr,
+			1, &bufferBarrier,
+			0, nullptr);
+
+		vkCmdBindPipeline(compute.commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, compute.pipeline);
+		vkCmdBindDescriptorSets(compute.commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, compute.pipelineLayout, 0, 1, &compute.descriptorSet, 0, 0);
+
+		// Dispatch the compute job
+		// The compute shader will do the frustum culling and adjust the indirect draw calls depending on object visibility.
+		// It also determines the lod to use depending on distance to the viewer.
+		vkCmdDispatch(compute.commandBuffer, objectCount / 16, 1, 1);
+
+		// Add memory barrier to ensure that the compute shader has finished writing the indirect command buffer before it's consumed
+		bufferBarrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
+		bufferBarrier.dstAccessMask = VK_ACCESS_INDIRECT_COMMAND_READ_BIT;
+		bufferBarrier.buffer = indirectCommandsBuffer.buffer;
+		bufferBarrier.size = indirectCommandsBuffer.descriptor.range;
+		bufferBarrier.srcQueueFamilyIndex = vulkanDevice->queueFamilyIndices.compute;
+		bufferBarrier.dstQueueFamilyIndex = vulkanDevice->queueFamilyIndices.graphics;
+
+		vkCmdPipelineBarrier(
+			compute.commandBuffer,
+			VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+			VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT,
+			VK_FLAGS_NONE,
+			0, nullptr,
+			1, &bufferBarrier,
+			0, nullptr);
+
+		// todo: barrier for indirect stats buffer?
+
+		vkEndCommandBuffer(compute.commandBuffer);
+	}
+
+	void setupDescriptorPool()
+	{
+		std::vector<VkDescriptorPoolSize> poolSizes = {
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2),
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 4)
+		};
+		VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 2);
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+	}
+
+	void setupDescriptorSetLayout()
+	{
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			// Binding 0: Vertex shader uniform buffer
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT,0),
+		};
+		VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout));
+
+		VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1);		 
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayout));
+	}
+
+	void setupDescriptorSet()
+	{
+		VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1);
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet));
+
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets = {
+			// Binding 0: Vertex shader uniform buffer
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformData.scene.descriptor),
+		};
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr);
+	}
+
+	void preparePipelines()
+	{
+		// This example uses two different input states, one for the instanced part and one for non-instanced rendering
+		VkPipelineVertexInputStateCreateInfo inputState = vks::initializers::pipelineVertexInputStateCreateInfo();
+		std::vector<VkVertexInputBindingDescription> bindingDescriptions;
+		std::vector<VkVertexInputAttributeDescription> attributeDescriptions;
+
+		// Vertex input bindings
+		// The instancing pipeline uses a vertex input state with two bindings
+		bindingDescriptions = {
+		    // Binding point 0: Mesh vertex layout description at per-vertex rate
+		    vks::initializers::vertexInputBindingDescription(VERTEX_BUFFER_BIND_ID, sizeof(vkglTF::Vertex), VK_VERTEX_INPUT_RATE_VERTEX),
+		    // Binding point 1: Instanced data at per-instance rate
+		    vks::initializers::vertexInputBindingDescription(INSTANCE_BUFFER_BIND_ID, sizeof(InstanceData), VK_VERTEX_INPUT_RATE_INSTANCE)
+		};
+
+		// Vertex attribute bindings
+		attributeDescriptions = {
+		    // Per-vertex attributes
+		    // These are advanced for each vertex fetched by the vertex shader
+		    vks::initializers::vertexInputAttributeDescription(VERTEX_BUFFER_BIND_ID, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(vkglTF::Vertex, pos)),	// Location 0: Position
+		    vks::initializers::vertexInputAttributeDescription(VERTEX_BUFFER_BIND_ID, 1, VK_FORMAT_R32G32B32_SFLOAT, offsetof(vkglTF::Vertex, normal)),	// Location 1: Normal
+		    vks::initializers::vertexInputAttributeDescription(VERTEX_BUFFER_BIND_ID, 2, VK_FORMAT_R32G32B32_SFLOAT, offsetof(vkglTF::Vertex, color)),	// Location 2: Texture coordinates
+		    // Per-Instance attributes
+		    // These are fetched for each instance rendered
+		    vks::initializers::vertexInputAttributeDescription(INSTANCE_BUFFER_BIND_ID, 4, VK_FORMAT_R32G32B32_SFLOAT, offsetof(InstanceData, pos)),	// Location 4: Position
+		    vks::initializers::vertexInputAttributeDescription(INSTANCE_BUFFER_BIND_ID, 5, VK_FORMAT_R32_SFLOAT, offsetof(InstanceData, scale)),		// Location 5: Scale
+		};
+		inputState.pVertexBindingDescriptions = bindingDescriptions.data();
+		inputState.pVertexAttributeDescriptions = attributeDescriptions.data();
+		inputState.vertexBindingDescriptionCount = static_cast<uint32_t>(bindingDescriptions.size());
+		inputState.vertexAttributeDescriptionCount = static_cast<uint32_t>(attributeDescriptions.size());
+
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+		VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
+		std::vector<VkDynamicState> dynamicStateEnables = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
+		VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+
+		VkGraphicsPipelineCreateInfo pipelineCreateInfo = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass);
+		pipelineCreateInfo.pVertexInputState = &inputState;
+		pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState;
+		pipelineCreateInfo.pRasterizationState = &rasterizationState;
+		pipelineCreateInfo.pColorBlendState = &colorBlendState;
+		pipelineCreateInfo.pMultisampleState = &multisampleState;
+		pipelineCreateInfo.pViewportState = &viewportState;
+		pipelineCreateInfo.pDepthStencilState = &depthStencilState;
+		pipelineCreateInfo.pDynamicState = &dynamicState;
+		pipelineCreateInfo.stageCount = static_cast<uint32_t>(shaderStages.size());
+		pipelineCreateInfo.pStages = shaderStages.data();
+
+		// Indirect (and instanced) pipeline for the plants
+		shaderStages[0] = loadShader(getShadersPath() + "computecullandlod/indirectdraw.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "computecullandlod/indirectdraw.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipelines.plants));
+	}
+
+	void prepareBuffers()
+	{
+		objectCount = OBJECT_COUNT * OBJECT_COUNT * OBJECT_COUNT;
+
+		vks::Buffer stagingBuffer;
+
+		std::vector<InstanceData> instanceData(objectCount);
+		indirectCommands.resize(objectCount);
+
+		// Indirect draw commands
+		for (uint32_t x = 0; x < OBJECT_COUNT; x++)
+		{
+			for (uint32_t y = 0; y < OBJECT_COUNT; y++)
+			{
+				for (uint32_t z = 0; z < OBJECT_COUNT; z++)
+				{
+					uint32_t index = x + y * OBJECT_COUNT + z * OBJECT_COUNT * OBJECT_COUNT;
+					indirectCommands[index].instanceCount = 1;
+					indirectCommands[index].firstInstance = index;
+					// firstIndex and indexCount are written by the compute shader
+				}
+			}
+		}
+
+		indirectStats.drawCount = static_cast<uint32_t>(indirectCommands.size());
+
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&stagingBuffer,
+			indirectCommands.size() * sizeof(VkDrawIndexedIndirectCommand),
+			indirectCommands.data()));
+
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
+			VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
+			&indirectCommandsBuffer,
+			stagingBuffer.size));
+
+		vulkanDevice->copyBuffer(&stagingBuffer, &indirectCommandsBuffer, queue);
+
+		stagingBuffer.destroy();
+
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&indirectDrawCountBuffer,
+			sizeof(indirectStats)));
+
+		// Map for host access
+		VK_CHECK_RESULT(indirectDrawCountBuffer.map());
+
+		// Instance data
+		for (uint32_t x = 0; x < OBJECT_COUNT; x++)
+		{
+			for (uint32_t y = 0; y < OBJECT_COUNT; y++)
+			{
+				for (uint32_t z = 0; z < OBJECT_COUNT; z++)
+				{
+					uint32_t index = x + y * OBJECT_COUNT + z * OBJECT_COUNT * OBJECT_COUNT;
+					instanceData[index].pos = glm::vec3((float)x, (float)y, (float)z) - glm::vec3((float)OBJECT_COUNT / 2.0f);
+					instanceData[index].scale = 2.0f;
+				}
+			}
+		}
+
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&stagingBuffer,
+			instanceData.size() * sizeof(InstanceData),
+			instanceData.data()));
+
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
+			VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
+			&instanceBuffer,
+			stagingBuffer.size));
+
+		vulkanDevice->copyBuffer(&stagingBuffer, &instanceBuffer, queue);
+
+		stagingBuffer.destroy();
+
+		// Shader storage buffer containing index offsets and counts for the LODs
+		struct LOD
+		{
+			uint32_t firstIndex;
+			uint32_t indexCount;
+			float distance;
+			float _pad0;
+		};
+		std::vector<LOD> LODLevels;
+		uint32_t n = 0;
+		for (auto node : lodModel.nodes)
+		{
+			LOD lod;
+			lod.firstIndex = node->mesh->primitives[0]->firstIndex;	// First index for this LOD
+			lod.indexCount = node->mesh->primitives[0]->indexCount;	// Index count for this LOD
+			lod.distance = 5.0f + n * 5.0f;							// Starting distance (to viewer) for this LOD
+			n++;
+			LODLevels.push_back(lod);
+		}
+
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&stagingBuffer,
+			LODLevels.size() * sizeof(LOD),
+			LODLevels.data()));
+
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
+			VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
+			&compute.lodLevelsBuffers,
+			stagingBuffer.size));
+
+		vulkanDevice->copyBuffer(&stagingBuffer, &compute.lodLevelsBuffers, queue);
+
+		stagingBuffer.destroy();
+
+		// Scene uniform buffer
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformData.scene,
+			sizeof(uboScene)));
+
+		VK_CHECK_RESULT(uniformData.scene.map());
+
+		updateUniformBuffer(true);
+	}
+
+	void prepareCompute()
+	{
+		// Get a compute capable device queue
+		vkGetDeviceQueue(device, vulkanDevice->queueFamilyIndices.compute, 0, &compute.queue);
+
+		// Create compute pipeline
+		// Compute pipelines are created separate from graphics pipelines even if they use the same queue (family index)
+
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			// Binding 0: Instance input data buffer
+			vks::initializers::descriptorSetLayoutBinding(
+				VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
+				VK_SHADER_STAGE_COMPUTE_BIT,
+				0),
+			// Binding 1: Indirect draw command output buffer (input)
+			vks::initializers::descriptorSetLayoutBinding(
+				VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
+				VK_SHADER_STAGE_COMPUTE_BIT,
+				1),
+			// Binding 2: Uniform buffer with global matrices (input)
+			vks::initializers::descriptorSetLayoutBinding(
+				VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+				VK_SHADER_STAGE_COMPUTE_BIT,
+				2),
+			// Binding 3: Indirect draw stats (output)
+			vks::initializers::descriptorSetLayoutBinding(
+				VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
+				VK_SHADER_STAGE_COMPUTE_BIT,
+				3),
+			// Binding 4: LOD info (input)
+			vks::initializers::descriptorSetLayoutBinding(
+				VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
+				VK_SHADER_STAGE_COMPUTE_BIT,
+				4),
+		};
+
+		VkDescriptorSetLayoutCreateInfo descriptorLayout =
+			vks::initializers::descriptorSetLayoutCreateInfo(
+				setLayoutBindings.data(),
+				static_cast<uint32_t>(setLayoutBindings.size()));
+
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &compute.descriptorSetLayout));
+
+		VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo =
+			vks::initializers::pipelineLayoutCreateInfo(
+				&compute.descriptorSetLayout,
+				1);
+
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &compute.pipelineLayout));
+
+		VkDescriptorSetAllocateInfo allocInfo =
+			vks::initializers::descriptorSetAllocateInfo(
+				descriptorPool,
+				&compute.descriptorSetLayout,
+				1);
+
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &compute.descriptorSet));
+
+		std::vector<VkWriteDescriptorSet> computeWriteDescriptorSets =
+		{
+			// Binding 0: Instance input data buffer
+			vks::initializers::writeDescriptorSet(
+				compute.descriptorSet,
+				VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
+				0,
+				&instanceBuffer.descriptor),
+			// Binding 1: Indirect draw command output buffer
+			vks::initializers::writeDescriptorSet(
+				compute.descriptorSet,
+				VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
+				1,
+				&indirectCommandsBuffer.descriptor),
+			// Binding 2: Uniform buffer with global matrices
+			vks::initializers::writeDescriptorSet(
+				compute.descriptorSet,
+				VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+				2,
+				&uniformData.scene.descriptor),
+			// Binding 3: Atomic counter (written in shader)
+			vks::initializers::writeDescriptorSet(
+				compute.descriptorSet,
+				VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
+				3,
+				&indirectDrawCountBuffer.descriptor),
+			// Binding 4: LOD info
+			vks::initializers::writeDescriptorSet(
+				compute.descriptorSet,
+				VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
+				4,
+				&compute.lodLevelsBuffers.descriptor)
+		};
+
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(computeWriteDescriptorSets.size()), computeWriteDescriptorSets.data(), 0, NULL);
+
+		// Create pipeline
+		VkComputePipelineCreateInfo computePipelineCreateInfo = vks::initializers::computePipelineCreateInfo(compute.pipelineLayout, 0);
+		computePipelineCreateInfo.stage = loadShader(getShadersPath() + "computecullandlod/cull.comp.spv", VK_SHADER_STAGE_COMPUTE_BIT);
+
+		// Use specialization constants to pass max. level of detail (determined by no. of meshes)
+		VkSpecializationMapEntry specializationEntry{};
+		specializationEntry.constantID = 0;
+		specializationEntry.offset = 0;
+		specializationEntry.size = sizeof(uint32_t);
+
+		uint32_t specializationData = static_cast<uint32_t>(lodModel.nodes.size()) - 1;
+
+		VkSpecializationInfo specializationInfo;
+		specializationInfo.mapEntryCount = 1;
+		specializationInfo.pMapEntries = &specializationEntry;
+		specializationInfo.dataSize = sizeof(specializationData);
+		specializationInfo.pData = &specializationData;
+
+		computePipelineCreateInfo.stage.pSpecializationInfo = &specializationInfo;
+
+		VK_CHECK_RESULT(vkCreateComputePipelines(device, pipelineCache, 1, &computePipelineCreateInfo, nullptr, &compute.pipeline));
+
+		// Separate command pool as queue family for compute may be different than graphics
+		VkCommandPoolCreateInfo cmdPoolInfo = {};
+		cmdPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
+		cmdPoolInfo.queueFamilyIndex = vulkanDevice->queueFamilyIndices.compute;
+		cmdPoolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
+		VK_CHECK_RESULT(vkCreateCommandPool(device, &cmdPoolInfo, nullptr, &compute.commandPool));
+
+		// Create a command buffer for compute operations
+		VkCommandBufferAllocateInfo cmdBufAllocateInfo =
+			vks::initializers::commandBufferAllocateInfo(
+				compute.commandPool,
+				VK_COMMAND_BUFFER_LEVEL_PRIMARY,
+				1);
+
+		VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, &compute.commandBuffer));
+
+		// Fence for compute CB sync
+		VkFenceCreateInfo fenceCreateInfo = vks::initializers::fenceCreateInfo(VK_FENCE_CREATE_SIGNALED_BIT);
+		VK_CHECK_RESULT(vkCreateFence(device, &fenceCreateInfo, nullptr, &compute.fence));
+
+		VkSemaphoreCreateInfo semaphoreCreateInfo = vks::initializers::semaphoreCreateInfo();
+		VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &compute.semaphore));
+
+		// Build a single command buffer containing the compute dispatch commands
+		buildComputeCommandBuffer();
+	}
+
+	void updateUniformBuffer(bool viewChanged)
+	{
+		if (viewChanged)
+		{
+			uboScene.projection = camera.matrices.perspective;
+			uboScene.modelview = camera.matrices.view;
+			if (!fixedFrustum)
+			{
+				uboScene.cameraPos = glm::vec4(camera.position, 1.0f) * -1.0f;
+				frustum.update(uboScene.projection * uboScene.modelview);
+				memcpy(uboScene.frustumPlanes, frustum.planes.data(), sizeof(glm::vec4) * 6);
+			}
+		}
+
+		memcpy(uniformData.scene.mapped, &uboScene, sizeof(uboScene));
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+
+		// Submit compute shader for frustum culling
+
+		// Wait for fence to ensure that compute buffer writes have finished
+		vkWaitForFences(device, 1, &compute.fence, VK_TRUE, UINT64_MAX);
+		vkResetFences(device, 1, &compute.fence);
+
+		VkSubmitInfo computeSubmitInfo = vks::initializers::submitInfo();
+		computeSubmitInfo.commandBufferCount = 1;
+		computeSubmitInfo.pCommandBuffers = &compute.commandBuffer;
+		computeSubmitInfo.signalSemaphoreCount = 1;
+		computeSubmitInfo.pSignalSemaphores = &compute.semaphore;
+
+		VK_CHECK_RESULT(vkQueueSubmit(compute.queue, 1, &computeSubmitInfo, VK_NULL_HANDLE));
+
+		// Submit graphics command buffer
+
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+
+		// Wait on present and compute semaphores
+		std::array<VkPipelineStageFlags,2> stageFlags = {
+			VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
+			VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+		};
+		std::array<VkSemaphore,2> waitSemaphores = {
+			semaphores.presentComplete,						// Wait for presentation to finished
+			compute.semaphore								// Wait for compute to finish
+		};
+
+		submitInfo.pWaitSemaphores = waitSemaphores.data();
+		submitInfo.waitSemaphoreCount = static_cast<uint32_t>(waitSemaphores.size());
+		submitInfo.pWaitDstStageMask = stageFlags.data();
+
+		// Submit to queue
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, compute.fence));
+
+		VulkanExampleBase::submitFrame();
+
+		// Get draw count from compute
+		memcpy(&indirectStats, indirectDrawCountBuffer.mapped, sizeof(indirectStats));
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		loadAssets();
+		prepareBuffers();
+		setupDescriptorSetLayout();
+		preparePipelines();
+		setupDescriptorPool();
+		setupDescriptorSet();
+		prepareCompute();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+		{
+			return;
+		}
+		draw();
+		if (camera.updated)
+		{
+			updateUniformBuffer(true);
+		}
+	}
+
+	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
+	{
+		if (overlay->header("Settings")) {
+			if (overlay->checkBox("Freeze frustum", &fixedFrustum)) {
+				updateUniformBuffer(true);
+			}
+		}
+		if (overlay->header("Statistics")) {
+			overlay->text("Visible objects: %d", indirectStats.drawCount);
+			for (uint32_t i = 0; i < MAX_LOD_LEVEL + 1; i++) {
+				overlay->text("LOD %d: %d", i, indirectStats.lodCount[i]);
+			}
+		}
+	}
+};
+
+VULKAN_EXAMPLE_MAIN()
\ No newline at end of file
diff --git a/external/Vulkan/examples/computeheadless/computeheadless.cpp b/external/Vulkan/examples/computeheadless/computeheadless.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5095222d914b3c91227630912d301bf971c0112f
--- /dev/null
+++ b/external/Vulkan/examples/computeheadless/computeheadless.cpp
@@ -0,0 +1,565 @@
+/*
+* Vulkan Example - Minimal headless compute example
+*
+* Copyright (C) 2017 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+// TODO: separate transfer queue (if not supported by compute queue) including buffer ownership transfer
+
+#if defined(_WIN32)
+#pragma comment(linker, "/subsystem:console")
+#elif defined(VK_USE_PLATFORM_ANDROID_KHR)
+#include <android/native_activity.h>
+#include <android/asset_manager.h>
+#include <android_native_app_glue.h>
+#include <android/log.h>
+#include "VulkanAndroid.h"
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <vector>
+#include <iostream>
+#include <algorithm>
+
+#include <vulkan/vulkan.h>
+#include "VulkanTools.h"
+
+#if defined(VK_USE_PLATFORM_ANDROID_KHR)
+android_app* androidapp;
+#endif
+
+#define DEBUG (!NDEBUG)
+
+#define BUFFER_ELEMENTS 32
+
+#if defined(VK_USE_PLATFORM_ANDROID_KHR)
+#define LOG(...) ((void)__android_log_print(ANDROID_LOG_INFO, "vulkanExample", __VA_ARGS__))
+#else
+#define LOG(...) printf(__VA_ARGS__)
+#endif
+
+static VKAPI_ATTR VkBool32 VKAPI_CALL debugMessageCallback(
+	VkDebugReportFlagsEXT flags,
+	VkDebugReportObjectTypeEXT objectType,
+	uint64_t object,
+	size_t location,
+	int32_t messageCode,
+	const char* pLayerPrefix,
+	const char* pMessage,
+	void* pUserData)
+{
+	LOG("[VALIDATION]: %s - %s\n", pLayerPrefix, pMessage);
+	return VK_FALSE;
+}
+
+class VulkanExample
+{
+public:
+	VkInstance instance;
+	VkPhysicalDevice physicalDevice;
+	VkDevice device;
+	uint32_t queueFamilyIndex;
+	VkPipelineCache pipelineCache;
+	VkQueue queue;
+	VkCommandPool commandPool;
+	VkCommandBuffer commandBuffer;
+	VkFence fence;
+	VkDescriptorPool descriptorPool;
+	VkDescriptorSetLayout descriptorSetLayout;
+	VkDescriptorSet descriptorSet;
+	VkPipelineLayout pipelineLayout;
+	VkPipeline pipeline;
+	VkShaderModule shaderModule;
+
+	VkDebugReportCallbackEXT debugReportCallback{};
+
+	VkResult createBuffer(VkBufferUsageFlags usageFlags, VkMemoryPropertyFlags memoryPropertyFlags, VkBuffer *buffer, VkDeviceMemory *memory, VkDeviceSize size, void *data = nullptr)
+	{
+		// Create the buffer handle
+		VkBufferCreateInfo bufferCreateInfo = vks::initializers::bufferCreateInfo(usageFlags, size);
+		bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+		VK_CHECK_RESULT(vkCreateBuffer(device, &bufferCreateInfo, nullptr, buffer));
+
+		// Create the memory backing up the buffer handle
+		VkPhysicalDeviceMemoryProperties deviceMemoryProperties;
+		vkGetPhysicalDeviceMemoryProperties(physicalDevice, &deviceMemoryProperties);
+		VkMemoryRequirements memReqs;
+		VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo();
+		vkGetBufferMemoryRequirements(device, *buffer, &memReqs);
+		memAlloc.allocationSize = memReqs.size;
+		// Find a memory type index that fits the properties of the buffer
+		bool memTypeFound = false;
+		for (uint32_t i = 0; i < deviceMemoryProperties.memoryTypeCount; i++) {
+			if ((memReqs.memoryTypeBits & 1) == 1) {
+				if ((deviceMemoryProperties.memoryTypes[i].propertyFlags & memoryPropertyFlags) == memoryPropertyFlags) {
+					memAlloc.memoryTypeIndex = i;
+					memTypeFound = true;
+				}
+			}
+			memReqs.memoryTypeBits >>= 1;
+		}
+		assert(memTypeFound);
+		VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, memory));
+
+		if (data != nullptr) {
+			void *mapped;
+			VK_CHECK_RESULT(vkMapMemory(device, *memory, 0, size, 0, &mapped));
+			memcpy(mapped, data, size);
+			vkUnmapMemory(device, *memory);
+		}
+
+		VK_CHECK_RESULT(vkBindBufferMemory(device, *buffer, *memory, 0));
+
+		return VK_SUCCESS;
+	}
+
+	VulkanExample()
+	{
+		LOG("Running headless compute example\n");
+
+#if defined(VK_USE_PLATFORM_ANDROID_KHR)
+		LOG("loading vulkan lib");
+		vks::android::loadVulkanLibrary();
+#endif
+
+		VkApplicationInfo appInfo = {};
+		appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
+		appInfo.pApplicationName = "Vulkan headless example";
+		appInfo.pEngineName = "VulkanExample";
+		appInfo.apiVersion = VK_API_VERSION_1_0;
+
+		/*
+			Vulkan instance creation (without surface extensions)
+		*/
+		VkInstanceCreateInfo instanceCreateInfo = {};
+		instanceCreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
+		instanceCreateInfo.pApplicationInfo = &appInfo;
+
+		uint32_t layerCount = 0;
+#if defined(VK_USE_PLATFORM_ANDROID_KHR)
+		const char* validationLayers[] = { "VK_LAYER_GOOGLE_threading",	"VK_LAYER_LUNARG_parameter_validation",	"VK_LAYER_LUNARG_object_tracker","VK_LAYER_LUNARG_core_validation",	"VK_LAYER_LUNARG_swapchain", "VK_LAYER_GOOGLE_unique_objects" };
+		layerCount = 6;
+#else
+		const char* validationLayers[] = { "VK_LAYER_LUNARG_standard_validation" };
+		layerCount = 1;
+#endif
+#if DEBUG
+		// Check if layers are available
+		uint32_t instanceLayerCount;
+		vkEnumerateInstanceLayerProperties(&instanceLayerCount, nullptr);
+		std::vector<VkLayerProperties> instanceLayers(instanceLayerCount);
+		vkEnumerateInstanceLayerProperties(&instanceLayerCount, instanceLayers.data());
+
+		bool layersAvailable = true;
+		for (auto layerName : validationLayers) {
+			bool layerAvailable = false;
+			for (auto instanceLayer : instanceLayers) {
+				if (strcmp(instanceLayer.layerName, layerName) == 0) {
+					layerAvailable = true;
+					break;
+				}
+			}
+			if (!layerAvailable) {
+				layersAvailable = false;
+				break;
+			}
+		}
+
+		const char *validationExt = VK_EXT_DEBUG_REPORT_EXTENSION_NAME;
+		if (layersAvailable) {
+			instanceCreateInfo.ppEnabledLayerNames = validationLayers;
+			instanceCreateInfo.enabledLayerCount = layerCount;
+			instanceCreateInfo.enabledExtensionCount = 1;
+			instanceCreateInfo.ppEnabledExtensionNames = &validationExt;
+		}
+#endif
+		VK_CHECK_RESULT(vkCreateInstance(&instanceCreateInfo, nullptr, &instance));
+
+#if defined(VK_USE_PLATFORM_ANDROID_KHR)
+		vks::android::loadVulkanFunctions(instance);
+#endif
+#if DEBUG
+		if (layersAvailable) {
+			VkDebugReportCallbackCreateInfoEXT debugReportCreateInfo = {};
+			debugReportCreateInfo.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT;
+			debugReportCreateInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT;
+			debugReportCreateInfo.pfnCallback = (PFN_vkDebugReportCallbackEXT)debugMessageCallback;
+
+			// We have to explicitly load this function.
+			PFN_vkCreateDebugReportCallbackEXT vkCreateDebugReportCallbackEXT = reinterpret_cast<PFN_vkCreateDebugReportCallbackEXT>(vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT"));
+			assert(vkCreateDebugReportCallbackEXT);
+			VK_CHECK_RESULT(vkCreateDebugReportCallbackEXT(instance, &debugReportCreateInfo, nullptr, &debugReportCallback));
+		}
+#endif
+
+		/*
+			Vulkan device creation
+		*/
+		// Physical device (always use first)
+		uint32_t deviceCount = 0;
+		VK_CHECK_RESULT(vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr));
+		std::vector<VkPhysicalDevice> physicalDevices(deviceCount);
+		VK_CHECK_RESULT(vkEnumeratePhysicalDevices(instance, &deviceCount, physicalDevices.data()));
+		physicalDevice = physicalDevices[0];
+
+		VkPhysicalDeviceProperties deviceProperties;
+		vkGetPhysicalDeviceProperties(physicalDevice, &deviceProperties);
+		LOG("GPU: %s\n", deviceProperties.deviceName);
+
+		// Request a single compute queue
+		const float defaultQueuePriority(0.0f);
+		VkDeviceQueueCreateInfo queueCreateInfo = {};
+		uint32_t queueFamilyCount;
+		vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, nullptr);
+		std::vector<VkQueueFamilyProperties> queueFamilyProperties(queueFamilyCount);
+		vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, queueFamilyProperties.data());
+		for (uint32_t i = 0; i < static_cast<uint32_t>(queueFamilyProperties.size()); i++) {
+			if (queueFamilyProperties[i].queueFlags & VK_QUEUE_COMPUTE_BIT) {
+				queueFamilyIndex = i;
+				queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
+				queueCreateInfo.queueFamilyIndex = i;
+				queueCreateInfo.queueCount = 1;
+				queueCreateInfo.pQueuePriorities = &defaultQueuePriority;
+				break;
+			}
+		}
+		// Create logical device
+		VkDeviceCreateInfo deviceCreateInfo = {};
+		deviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
+		deviceCreateInfo.queueCreateInfoCount = 1;
+		deviceCreateInfo.pQueueCreateInfos = &queueCreateInfo;
+		VK_CHECK_RESULT(vkCreateDevice(physicalDevice, &deviceCreateInfo, nullptr, &device));
+
+		// Get a compute queue
+		vkGetDeviceQueue(device, queueFamilyIndex, 0, &queue);
+
+		// Compute command pool
+		VkCommandPoolCreateInfo cmdPoolInfo = {};
+		cmdPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
+		cmdPoolInfo.queueFamilyIndex = queueFamilyIndex;
+		cmdPoolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
+		VK_CHECK_RESULT(vkCreateCommandPool(device, &cmdPoolInfo, nullptr, &commandPool));
+
+		/*
+			Prepare storage buffers
+		*/
+		std::vector<uint32_t> computeInput(BUFFER_ELEMENTS);
+		std::vector<uint32_t> computeOutput(BUFFER_ELEMENTS);
+
+		// Fill input data
+		uint32_t n = 0;
+		std::generate(computeInput.begin(), computeInput.end(), [&n] { return n++; });
+
+		const VkDeviceSize bufferSize = BUFFER_ELEMENTS * sizeof(uint32_t);
+
+		VkBuffer deviceBuffer, hostBuffer;
+		VkDeviceMemory deviceMemory, hostMemory;
+
+		// Copy input data to VRAM using a staging buffer
+		{
+			createBuffer(
+				VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
+				VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT,
+				&hostBuffer,
+				&hostMemory,
+				bufferSize,
+				computeInput.data());
+
+			// Flush writes to host visible buffer
+			void* mapped;
+			vkMapMemory(device, hostMemory, 0, VK_WHOLE_SIZE, 0, &mapped);
+			VkMappedMemoryRange mappedRange = vks::initializers::mappedMemoryRange();
+			mappedRange.memory = hostMemory;
+			mappedRange.offset = 0;
+			mappedRange.size = VK_WHOLE_SIZE;
+			vkFlushMappedMemoryRanges(device, 1, &mappedRange);
+			vkUnmapMemory(device, hostMemory);
+
+			createBuffer(
+				VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
+				VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
+				&deviceBuffer,
+				&deviceMemory,
+				bufferSize);
+
+			// Copy to staging buffer
+			VkCommandBufferAllocateInfo cmdBufAllocateInfo = vks::initializers::commandBufferAllocateInfo(commandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1);
+			VkCommandBuffer copyCmd;
+			VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, &copyCmd));
+			VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+			VK_CHECK_RESULT(vkBeginCommandBuffer(copyCmd, &cmdBufInfo));
+
+			VkBufferCopy copyRegion = {};
+			copyRegion.size = bufferSize;
+			vkCmdCopyBuffer(copyCmd, hostBuffer, deviceBuffer, 1, &copyRegion);
+			VK_CHECK_RESULT(vkEndCommandBuffer(copyCmd));
+
+			VkSubmitInfo submitInfo = vks::initializers::submitInfo();
+			submitInfo.commandBufferCount = 1;
+			submitInfo.pCommandBuffers = &copyCmd;
+			VkFenceCreateInfo fenceInfo = vks::initializers::fenceCreateInfo(VK_FLAGS_NONE);
+			VkFence fence;
+			VK_CHECK_RESULT(vkCreateFence(device, &fenceInfo, nullptr, &fence));
+
+			// Submit to the queue
+			VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, fence));
+			VK_CHECK_RESULT(vkWaitForFences(device, 1, &fence, VK_TRUE, UINT64_MAX));
+
+			vkDestroyFence(device, fence, nullptr);
+			vkFreeCommandBuffers(device, commandPool, 1, &copyCmd);
+		}
+
+		/*
+			Prepare compute pipeline
+		*/
+		{
+			std::vector<VkDescriptorPoolSize> poolSizes = {
+				vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1),
+			};
+
+			VkDescriptorPoolCreateInfo descriptorPoolInfo =
+				vks::initializers::descriptorPoolCreateInfo(static_cast<uint32_t>(poolSizes.size()), poolSizes.data(), 1);
+			VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+
+			std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+				vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_SHADER_STAGE_COMPUTE_BIT, 0),
+			};
+			VkDescriptorSetLayoutCreateInfo descriptorLayout =
+				vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
+			VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout));
+
+			VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo =
+				vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1);
+			VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout));
+
+			VkDescriptorSetAllocateInfo allocInfo =
+				vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1);
+			VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet));
+
+			VkDescriptorBufferInfo bufferDescriptor = { deviceBuffer, 0, VK_WHOLE_SIZE };
+			std::vector<VkWriteDescriptorSet> computeWriteDescriptorSets = {
+				vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 0, &bufferDescriptor),
+			};
+			vkUpdateDescriptorSets(device, static_cast<uint32_t>(computeWriteDescriptorSets.size()), computeWriteDescriptorSets.data(), 0, NULL);
+
+			VkPipelineCacheCreateInfo pipelineCacheCreateInfo = {};
+			pipelineCacheCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO;
+			VK_CHECK_RESULT(vkCreatePipelineCache(device, &pipelineCacheCreateInfo, nullptr, &pipelineCache));
+
+			// Create pipeline
+			VkComputePipelineCreateInfo computePipelineCreateInfo = vks::initializers::computePipelineCreateInfo(pipelineLayout, 0);
+
+			// Pass SSBO size via specialization constant
+			struct SpecializationData {
+				uint32_t BUFFER_ELEMENT_COUNT = BUFFER_ELEMENTS;
+			} specializationData;
+			VkSpecializationMapEntry specializationMapEntry = vks::initializers::specializationMapEntry(0, 0, sizeof(uint32_t));
+			VkSpecializationInfo specializationInfo = vks::initializers::specializationInfo(1, &specializationMapEntry, sizeof(SpecializationData), &specializationData);
+
+			// TODO: There is no command line arguments parsing (nor Android settings) for this
+			// example, so we have no way of picking between GLSL or HLSL shaders.
+			// Hard-code to glsl for now.
+			const std::string shadersPath = getAssetPath() + "shaders/glsl/computeheadless/";
+
+			VkPipelineShaderStageCreateInfo shaderStage = {};
+			shaderStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+			shaderStage.stage = VK_SHADER_STAGE_COMPUTE_BIT;
+#if defined(VK_USE_PLATFORM_ANDROID_KHR)
+			shaderStage.module = vks::tools::loadShader(androidapp->activity->assetManager, (shadersPath + "headless.comp.spv").c_str(), device);
+#else
+			shaderStage.module = vks::tools::loadShader((shadersPath + "headless.comp.spv").c_str(), device);
+#endif
+			shaderStage.pName = "main";
+			shaderStage.pSpecializationInfo = &specializationInfo;
+			shaderModule = shaderStage.module;
+
+			assert(shaderStage.module != VK_NULL_HANDLE);
+			computePipelineCreateInfo.stage = shaderStage;
+			VK_CHECK_RESULT(vkCreateComputePipelines(device, pipelineCache, 1, &computePipelineCreateInfo, nullptr, &pipeline));
+
+			// Create a command buffer for compute operations
+			VkCommandBufferAllocateInfo cmdBufAllocateInfo =
+				vks::initializers::commandBufferAllocateInfo(commandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1);
+			VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, &commandBuffer));
+
+			// Fence for compute CB sync
+			VkFenceCreateInfo fenceCreateInfo = vks::initializers::fenceCreateInfo(VK_FENCE_CREATE_SIGNALED_BIT);
+			VK_CHECK_RESULT(vkCreateFence(device, &fenceCreateInfo, nullptr, &fence));
+		}
+
+		/*
+			Command buffer creation (for compute work submission)
+		*/
+		{
+			VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(commandBuffer, &cmdBufInfo));
+
+			// Barrier to ensure that input buffer transfer is finished before compute shader reads from it
+			VkBufferMemoryBarrier bufferBarrier = vks::initializers::bufferMemoryBarrier();
+			bufferBarrier.buffer = deviceBuffer;
+			bufferBarrier.size = VK_WHOLE_SIZE;
+			bufferBarrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT;
+			bufferBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
+			bufferBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+			bufferBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+
+			vkCmdPipelineBarrier(
+				commandBuffer,
+				VK_PIPELINE_STAGE_HOST_BIT,
+				VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+				VK_FLAGS_NONE,
+				0, nullptr,
+				1, &bufferBarrier,
+				0, nullptr);
+
+			vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline);
+			vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipelineLayout, 0, 1, &descriptorSet, 0, 0);
+
+			vkCmdDispatch(commandBuffer, BUFFER_ELEMENTS, 1, 1);
+
+			// Barrier to ensure that shader writes are finished before buffer is read back from GPU
+			bufferBarrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
+			bufferBarrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
+			bufferBarrier.buffer = deviceBuffer;
+			bufferBarrier.size = VK_WHOLE_SIZE;
+			bufferBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+			bufferBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+
+			vkCmdPipelineBarrier(
+				commandBuffer,
+				VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+				VK_PIPELINE_STAGE_TRANSFER_BIT,
+				VK_FLAGS_NONE,
+				0, nullptr,
+				1, &bufferBarrier,
+				0, nullptr);
+
+			// Read back to host visible buffer
+			VkBufferCopy copyRegion = {};
+			copyRegion.size = bufferSize;
+			vkCmdCopyBuffer(commandBuffer, deviceBuffer, hostBuffer, 1, &copyRegion);
+
+			// Barrier to ensure that buffer copy is finished before host reading from it
+			bufferBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+			bufferBarrier.dstAccessMask = VK_ACCESS_HOST_READ_BIT;
+			bufferBarrier.buffer = hostBuffer;
+			bufferBarrier.size = VK_WHOLE_SIZE;
+			bufferBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+			bufferBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+
+			vkCmdPipelineBarrier(
+				commandBuffer,
+				VK_PIPELINE_STAGE_TRANSFER_BIT,
+				VK_PIPELINE_STAGE_HOST_BIT,
+				VK_FLAGS_NONE,
+				0, nullptr,
+				1, &bufferBarrier,
+				0, nullptr);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(commandBuffer));
+
+			// Submit compute work
+			vkResetFences(device, 1, &fence);
+			const VkPipelineStageFlags waitStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT;
+			VkSubmitInfo computeSubmitInfo = vks::initializers::submitInfo();
+			computeSubmitInfo.pWaitDstStageMask = &waitStageMask;
+			computeSubmitInfo.commandBufferCount = 1;
+			computeSubmitInfo.pCommandBuffers = &commandBuffer;
+			VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &computeSubmitInfo, fence));
+			VK_CHECK_RESULT(vkWaitForFences(device, 1, &fence, VK_TRUE, UINT64_MAX));
+
+			// Make device writes visible to the host
+			void *mapped;
+			vkMapMemory(device, hostMemory, 0, VK_WHOLE_SIZE, 0, &mapped);
+			VkMappedMemoryRange mappedRange = vks::initializers::mappedMemoryRange();
+			mappedRange.memory = hostMemory;
+			mappedRange.offset = 0;
+			mappedRange.size = VK_WHOLE_SIZE;
+			vkInvalidateMappedMemoryRanges(device, 1, &mappedRange);
+
+			// Copy to output
+			memcpy(computeOutput.data(), mapped, bufferSize);
+			vkUnmapMemory(device, hostMemory);
+		}
+
+		vkQueueWaitIdle(queue);
+
+		// Output buffer contents
+		LOG("Compute input:\n");
+		for (auto v : computeInput) {
+			LOG("%d \t", v);
+		}
+		std::cout << std::endl;
+
+		LOG("Compute output:\n");
+		for (auto v : computeOutput) {
+			LOG("%d \t", v);
+		}
+		std::cout << std::endl;
+
+		// Clean up
+		vkDestroyBuffer(device, deviceBuffer, nullptr);
+		vkFreeMemory(device, deviceMemory, nullptr);
+		vkDestroyBuffer(device, hostBuffer, nullptr);
+		vkFreeMemory(device, hostMemory, nullptr);
+	}
+
+	~VulkanExample()
+	{
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+		vkDestroyDescriptorPool(device, descriptorPool, nullptr);
+		vkDestroyPipeline(device, pipeline, nullptr);
+		vkDestroyPipelineCache(device, pipelineCache, nullptr);
+		vkDestroyFence(device, fence, nullptr);
+		vkDestroyCommandPool(device, commandPool, nullptr);
+		vkDestroyShaderModule(device, shaderModule, nullptr);
+		vkDestroyDevice(device, nullptr);
+#if DEBUG
+		if (debugReportCallback) {
+			PFN_vkDestroyDebugReportCallbackEXT vkDestroyDebugReportCallback = reinterpret_cast<PFN_vkDestroyDebugReportCallbackEXT>(vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT"));
+			assert(vkDestroyDebugReportCallback);
+			vkDestroyDebugReportCallback(instance, debugReportCallback, nullptr);
+		}
+#endif
+		vkDestroyInstance(instance, nullptr);
+	}
+};
+
+#if defined(VK_USE_PLATFORM_ANDROID_KHR)
+void handleAppCommand(android_app * app, int32_t cmd) {
+	if (cmd == APP_CMD_INIT_WINDOW) {
+		VulkanExample *vulkanExample = new VulkanExample();
+		delete(vulkanExample);
+		ANativeActivity_finish(app->activity);
+	}
+}
+void android_main(android_app* state) {
+	androidapp = state;
+	androidapp->onAppCmd = handleAppCommand;
+	int ident, events;
+	struct android_poll_source* source;
+	while ((ident = ALooper_pollAll(-1, NULL, &events, (void**)&source)) >= 0) {
+		if (source != NULL)	{
+			source->process(androidapp, source);
+		}
+		if (androidapp->destroyRequested != 0) {
+			break;
+		}
+	}
+}
+#else
+int main() {
+	VulkanExample *vulkanExample = new VulkanExample();
+	std::cout << "Finished. Press enter to terminate...";
+	getchar();
+	delete(vulkanExample);
+	return 0;
+}
+#endif
\ No newline at end of file
diff --git a/external/Vulkan/examples/computenbody/computenbody.cpp b/external/Vulkan/examples/computenbody/computenbody.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..0d9b2a3282e2a3d30d667f035298fa12a7d3817c
--- /dev/null
+++ b/external/Vulkan/examples/computenbody/computenbody.cpp
@@ -0,0 +1,904 @@
+/*
+* Vulkan Example - Compute shader N-body simulation using two passes and shared compute shader memory
+*
+* Copyright (C) by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkanexamplebase.h"
+
+#define VERTEX_BUFFER_BIND_ID 0
+#define ENABLE_VALIDATION false
+#if defined(__ANDROID__)
+// Lower particle count on Android for performance reasons
+#define PARTICLES_PER_ATTRACTOR 3 * 1024
+#else
+#define PARTICLES_PER_ATTRACTOR 4 * 1024
+#endif
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	uint32_t numParticles;
+
+	struct {
+		vks::Texture2D particle;
+		vks::Texture2D gradient;
+	} textures;
+
+	struct {
+		VkPipelineVertexInputStateCreateInfo inputState;
+		std::vector<VkVertexInputBindingDescription> bindingDescriptions;
+		std::vector<VkVertexInputAttributeDescription> attributeDescriptions;
+	} vertices;
+
+	// Resources for the graphics part of the example
+	struct {
+		uint32_t queueFamilyIndex;					// Used to check if compute and graphics queue families differ and require additional barriers
+		vks::Buffer uniformBuffer;					// Contains scene matrices
+		VkDescriptorSetLayout descriptorSetLayout;	// Particle system rendering shader binding layout
+		VkDescriptorSet descriptorSet;				// Particle system rendering shader bindings
+		VkPipelineLayout pipelineLayout;			// Layout of the graphics pipeline
+		VkPipeline pipeline;						// Particle rendering pipeline
+		VkSemaphore semaphore;                      // Execution dependency between compute & graphic submission
+		struct {
+			glm::mat4 projection;
+			glm::mat4 view;
+			glm::vec2 screenDim;
+		} ubo;
+	} graphics;
+
+	// Resources for the compute part of the example
+	struct {
+		uint32_t queueFamilyIndex;					// Used to check if compute and graphics queue families differ and require additional barriers
+		vks::Buffer storageBuffer;					// (Shader) storage buffer object containing the particles
+		vks::Buffer uniformBuffer;					// Uniform buffer object containing particle system parameters
+		VkQueue queue;								// Separate queue for compute commands (queue family may differ from the one used for graphics)
+		VkCommandPool commandPool;					// Use a separate command pool (queue family may differ from the one used for graphics)
+		VkCommandBuffer commandBuffer;				// Command buffer storing the dispatch commands and barriers
+		VkSemaphore semaphore;                      // Execution dependency between compute & graphic submission
+		VkDescriptorSetLayout descriptorSetLayout;	// Compute shader binding layout
+		VkDescriptorSet descriptorSet;				// Compute shader bindings
+		VkPipelineLayout pipelineLayout;			// Layout of the compute pipeline
+		VkPipeline pipelineCalculate;				// Compute pipeline for N-Body velocity calculation (1st pass)
+		VkPipeline pipelineIntegrate;				// Compute pipeline for euler integration (2nd pass)
+		VkPipeline blur;
+		VkPipelineLayout pipelineLayoutBlur;
+		VkDescriptorSetLayout descriptorSetLayoutBlur;
+		VkDescriptorSet descriptorSetBlur;
+		struct computeUBO {							// Compute shader uniform block object
+			float deltaT;							//		Frame delta time
+			int32_t particleCount;
+		} ubo;
+	} compute;
+
+	// SSBO particle declaration
+	struct Particle {
+		glm::vec4 pos;								// xyz = position, w = mass
+		glm::vec4 vel;								// xyz = velocity, w = gradient texture position
+	};
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Compute shader N-body system";
+		camera.type = Camera::CameraType::lookat;
+		camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 512.0f);
+		camera.setRotation(glm::vec3(-26.0f, 75.0f, 0.0f));
+		camera.setTranslation(glm::vec3(0.0f, 0.0f, -14.0f));
+		camera.movementSpeed = 2.5f;
+	}
+
+	~VulkanExample()
+	{
+		// Graphics
+		graphics.uniformBuffer.destroy();
+		vkDestroyPipeline(device, graphics.pipeline, nullptr);
+		vkDestroyPipelineLayout(device, graphics.pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, graphics.descriptorSetLayout, nullptr);
+		vkDestroySemaphore(device, graphics.semaphore, nullptr);
+
+		// Compute
+		compute.storageBuffer.destroy();
+		compute.uniformBuffer.destroy();
+		vkDestroyPipelineLayout(device, compute.pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, compute.descriptorSetLayout, nullptr);
+		vkDestroyPipeline(device, compute.pipelineCalculate, nullptr);
+		vkDestroyPipeline(device, compute.pipelineIntegrate, nullptr);
+		vkDestroySemaphore(device, compute.semaphore, nullptr);
+		vkDestroyCommandPool(device, compute.commandPool, nullptr);
+
+		textures.particle.destroy();
+		textures.gradient.destroy();
+	}
+
+	void loadAssets()
+	{
+		textures.particle.loadFromFile(getAssetPath() + "textures/particle01_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue);
+		textures.gradient.loadFromFile(getAssetPath() + "textures/particle_gradient_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue);
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		clearValues[0].color = { {0.0f, 0.0f, 0.0f, 1.0f} };
+		clearValues[1].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.offset.x = 0;
+		renderPassBeginInfo.renderArea.offset.y = 0;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		renderPassBeginInfo.clearValueCount = 2;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			// Set target frame buffer
+			renderPassBeginInfo.framebuffer = frameBuffers[i];
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			// Acquire barrier
+			if (graphics.queueFamilyIndex != compute.queueFamilyIndex)
+			{
+				VkBufferMemoryBarrier buffer_barrier =
+				{
+					VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
+					nullptr,
+					0,
+					VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT,
+					compute.queueFamilyIndex,
+					graphics.queueFamilyIndex,
+					compute.storageBuffer.buffer,
+					0,
+					compute.storageBuffer.size
+				};
+
+				vkCmdPipelineBarrier(
+					drawCmdBuffers[i],
+					VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+					VK_PIPELINE_STAGE_VERTEX_INPUT_BIT,
+					0,
+					0, nullptr,
+					1, &buffer_barrier,
+					0, nullptr);
+			}
+
+			// Draw the particle system using the update vertex buffer
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+			VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphics.pipeline);
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphics.pipelineLayout, 0, 1, &graphics.descriptorSet, 0, nullptr);
+
+			VkDeviceSize offsets[1] = { 0 };
+			vkCmdBindVertexBuffers(drawCmdBuffers[i], VERTEX_BUFFER_BIND_ID, 1, &compute.storageBuffer.buffer, offsets);
+			vkCmdDraw(drawCmdBuffers[i], numParticles, 1, 0, 0);
+
+			drawUI(drawCmdBuffers[i]);
+
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			// Release barrier
+			if (graphics.queueFamilyIndex != compute.queueFamilyIndex)
+			{
+				VkBufferMemoryBarrier buffer_barrier =
+				{
+					VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
+					nullptr,
+					VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT,
+					0,
+					graphics.queueFamilyIndex,
+					compute.queueFamilyIndex,
+					compute.storageBuffer.buffer,
+					0,
+					compute.storageBuffer.size
+				};
+
+				vkCmdPipelineBarrier(
+					drawCmdBuffers[i],
+					VK_PIPELINE_STAGE_VERTEX_INPUT_BIT,
+					VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+					0,
+					0, nullptr,
+					1, &buffer_barrier,
+					0, nullptr);
+			}
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+
+	}
+
+	void buildComputeCommandBuffer()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VK_CHECK_RESULT(vkBeginCommandBuffer(compute.commandBuffer, &cmdBufInfo));
+
+		// Acquire barrier
+		if (graphics.queueFamilyIndex != compute.queueFamilyIndex)
+		{
+			VkBufferMemoryBarrier buffer_barrier =
+			{
+				VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
+				nullptr,
+				0,
+				VK_ACCESS_SHADER_WRITE_BIT,
+				graphics.queueFamilyIndex,
+				compute.queueFamilyIndex,
+				compute.storageBuffer.buffer,
+				0,
+				compute.storageBuffer.size
+			};
+
+			vkCmdPipelineBarrier(
+				compute.commandBuffer,
+				VK_PIPELINE_STAGE_VERTEX_INPUT_BIT,
+				VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+				0,
+				0, nullptr,
+				1, &buffer_barrier,
+				0, nullptr);
+		}
+
+		// First pass: Calculate particle movement
+		// -------------------------------------------------------------------------------------------------------
+		vkCmdBindPipeline(compute.commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, compute.pipelineCalculate);
+		vkCmdBindDescriptorSets(compute.commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, compute.pipelineLayout, 0, 1, &compute.descriptorSet, 0, 0);
+		vkCmdDispatch(compute.commandBuffer, numParticles / 256, 1, 1);
+
+		// Add memory barrier to ensure that the computer shader has finished writing to the buffer
+		VkBufferMemoryBarrier bufferBarrier = vks::initializers::bufferMemoryBarrier();
+		bufferBarrier.buffer = compute.storageBuffer.buffer;
+		bufferBarrier.size = compute.storageBuffer.descriptor.range;
+		bufferBarrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
+		bufferBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
+		// Transfer ownership if compute and graphics queue family indices differ
+		bufferBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+		bufferBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+
+		vkCmdPipelineBarrier(
+			compute.commandBuffer,
+			VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+			VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+			VK_FLAGS_NONE,
+			0, nullptr,
+			1, &bufferBarrier,
+			0, nullptr);
+
+		// Second pass: Integrate particles
+		// -------------------------------------------------------------------------------------------------------
+		vkCmdBindPipeline(compute.commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, compute.pipelineIntegrate);
+		vkCmdDispatch(compute.commandBuffer, numParticles / 256, 1, 1);
+
+		// Release barrier
+		if (graphics.queueFamilyIndex != compute.queueFamilyIndex)
+		{
+			VkBufferMemoryBarrier buffer_barrier =
+			{
+				VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
+				nullptr,
+				VK_ACCESS_SHADER_WRITE_BIT,
+				0,
+				compute.queueFamilyIndex,
+				graphics.queueFamilyIndex,
+				compute.storageBuffer.buffer,
+				0,
+				compute.storageBuffer.size
+			};
+
+			vkCmdPipelineBarrier(
+				compute.commandBuffer,
+				VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+				VK_PIPELINE_STAGE_VERTEX_INPUT_BIT,
+				0,
+				0, nullptr,
+				1, &buffer_barrier,
+				0, nullptr);
+		}
+
+		vkEndCommandBuffer(compute.commandBuffer);
+	}
+
+	// Setup and fill the compute shader storage buffers containing the particles
+	void prepareStorageBuffers()
+	{
+#if 0
+		std::vector<glm::vec3> attractors = {
+			glm::vec3(2.5f, 1.5f, 0.0f),
+			glm::vec3(-2.5f, -1.5f, 0.0f),
+		};
+#else
+		std::vector<glm::vec3> attractors = {
+			glm::vec3(5.0f, 0.0f, 0.0f),
+			glm::vec3(-5.0f, 0.0f, 0.0f),
+			glm::vec3(0.0f, 0.0f, 5.0f),
+			glm::vec3(0.0f, 0.0f, -5.0f),
+			glm::vec3(0.0f, 4.0f, 0.0f),
+			glm::vec3(0.0f, -8.0f, 0.0f),
+		};
+#endif
+
+		numParticles = static_cast<uint32_t>(attractors.size()) * PARTICLES_PER_ATTRACTOR;
+
+		// Initial particle positions
+		std::vector<Particle> particleBuffer(numParticles);
+
+		std::default_random_engine rndEngine(benchmark.active ? 0 : (unsigned)time(nullptr));
+		std::normal_distribution<float> rndDist(0.0f, 1.0f);
+
+		for (uint32_t i = 0; i < static_cast<uint32_t>(attractors.size()); i++)
+		{
+			for (uint32_t j = 0; j < PARTICLES_PER_ATTRACTOR; j++)
+			{
+				Particle &particle = particleBuffer[i * PARTICLES_PER_ATTRACTOR + j];
+
+				// First particle in group as heavy center of gravity
+				if (j == 0)
+				{
+					particle.pos = glm::vec4(attractors[i] * 1.5f, 90000.0f);
+					particle.vel = glm::vec4(glm::vec4(0.0f));
+				}
+				else
+				{
+					// Position
+					glm::vec3 position(attractors[i] + glm::vec3(rndDist(rndEngine), rndDist(rndEngine), rndDist(rndEngine)) * 0.75f);
+					float len = glm::length(glm::normalize(position - attractors[i]));
+					position.y *= 2.0f - (len * len);
+
+					// Velocity
+					glm::vec3 angular = glm::vec3(0.5f, 1.5f, 0.5f) * (((i % 2) == 0) ? 1.0f : -1.0f);
+					glm::vec3 velocity = glm::cross((position - attractors[i]), angular) + glm::vec3(rndDist(rndEngine), rndDist(rndEngine), rndDist(rndEngine) * 0.025f);
+
+					float mass = (rndDist(rndEngine) * 0.5f + 0.5f) * 75.0f;
+					particle.pos = glm::vec4(position, mass);
+					particle.vel = glm::vec4(velocity, 0.0f);
+				}
+
+				// Color gradient offset
+				particle.vel.w = (float)i * 1.0f / static_cast<uint32_t>(attractors.size());
+			}
+		}
+
+		compute.ubo.particleCount = numParticles;
+
+		VkDeviceSize storageBufferSize = particleBuffer.size() * sizeof(Particle);
+
+		// Staging
+		// SSBO won't be changed on the host after upload so copy to device local memory
+
+		vks::Buffer stagingBuffer;
+
+		vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&stagingBuffer,
+			storageBufferSize,
+			particleBuffer.data());
+
+		vulkanDevice->createBuffer(
+			// The SSBO will be used as a storage buffer for the compute pipeline and as a vertex buffer in the graphics pipeline
+			VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
+			VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
+			&compute.storageBuffer,
+			storageBufferSize);
+
+		// Copy from staging buffer to storage buffer
+		VkCommandBuffer copyCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+		VkBufferCopy copyRegion = {};
+		copyRegion.size = storageBufferSize;
+		vkCmdCopyBuffer(copyCmd, stagingBuffer.buffer, compute.storageBuffer.buffer, 1, &copyRegion);
+		// Execute a transfer barrier to the compute queue, if necessary
+		if (graphics.queueFamilyIndex != compute.queueFamilyIndex)
+		{
+			VkBufferMemoryBarrier buffer_barrier =
+			{
+				VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
+				nullptr,
+				VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT,
+				0,
+				graphics.queueFamilyIndex,
+				compute.queueFamilyIndex,
+				compute.storageBuffer.buffer,
+				0,
+				compute.storageBuffer.size
+			};
+
+			vkCmdPipelineBarrier(
+				copyCmd,
+				VK_PIPELINE_STAGE_VERTEX_INPUT_BIT,
+				VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+				0,
+				0, nullptr,
+				1, &buffer_barrier,
+				0, nullptr);
+		}
+		vulkanDevice->flushCommandBuffer(copyCmd, queue, true);
+
+		stagingBuffer.destroy();
+
+		// Binding description
+		vertices.bindingDescriptions.resize(1);
+		vertices.bindingDescriptions[0] =
+			vks::initializers::vertexInputBindingDescription(
+				VERTEX_BUFFER_BIND_ID,
+				sizeof(Particle),
+				VK_VERTEX_INPUT_RATE_VERTEX);
+
+		// Attribute descriptions
+		// Describes memory layout and shader positions
+		vertices.attributeDescriptions.resize(2);
+		// Location 0 : Position
+		vertices.attributeDescriptions[0] =
+			vks::initializers::vertexInputAttributeDescription(
+				VERTEX_BUFFER_BIND_ID,
+				0,
+				VK_FORMAT_R32G32B32A32_SFLOAT,
+				offsetof(Particle, pos));
+		// Location 1 : Velocity (used for gradient lookup)
+		vertices.attributeDescriptions[1] =
+			vks::initializers::vertexInputAttributeDescription(
+				VERTEX_BUFFER_BIND_ID,
+				1,
+				VK_FORMAT_R32G32B32A32_SFLOAT,
+				offsetof(Particle, vel));
+
+		// Assign to vertex buffer
+		vertices.inputState = vks::initializers::pipelineVertexInputStateCreateInfo();
+		vertices.inputState.vertexBindingDescriptionCount = static_cast<uint32_t>(vertices.bindingDescriptions.size());
+		vertices.inputState.pVertexBindingDescriptions = vertices.bindingDescriptions.data();
+		vertices.inputState.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertices.attributeDescriptions.size());
+		vertices.inputState.pVertexAttributeDescriptions = vertices.attributeDescriptions.data();
+	}
+
+	void setupDescriptorPool()
+	{
+		std::vector<VkDescriptorPoolSize> poolSizes =
+		{
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2),
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1),
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2)
+		};
+
+		VkDescriptorPoolCreateInfo descriptorPoolInfo =
+			vks::initializers::descriptorPoolCreateInfo(
+				static_cast<uint32_t>(poolSizes.size()),
+				poolSizes.data(),
+				2);
+
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+	}
+
+	void setupDescriptorSetLayout()
+	{
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings;
+		setLayoutBindings = {
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0),
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1),
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 2),
+		};
+
+		VkDescriptorSetLayoutCreateInfo descriptorLayout =
+			vks::initializers::descriptorSetLayoutCreateInfo(
+				setLayoutBindings.data(),
+				static_cast<uint32_t>(setLayoutBindings.size()));
+
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &graphics.descriptorSetLayout));
+
+		VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo =
+			vks::initializers::pipelineLayoutCreateInfo(
+				&graphics.descriptorSetLayout,
+				1);
+
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &graphics.pipelineLayout));
+	}
+
+	void setupDescriptorSet()
+	{
+		VkDescriptorSetAllocateInfo allocInfo =
+			vks::initializers::descriptorSetAllocateInfo(
+				descriptorPool,
+				&graphics.descriptorSetLayout,
+				1);
+
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &graphics.descriptorSet));
+
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets;
+		writeDescriptorSets = {
+			vks::initializers::writeDescriptorSet(graphics.descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &textures.particle.descriptor),
+			vks::initializers::writeDescriptorSet(graphics.descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &textures.gradient.descriptor),
+			vks::initializers::writeDescriptorSet(graphics.descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2, &graphics.uniformBuffer.descriptor),
+		};
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr);
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState =
+			vks::initializers::pipelineInputAssemblyStateCreateInfo(
+				VK_PRIMITIVE_TOPOLOGY_POINT_LIST,
+				0,
+				VK_FALSE);
+
+		VkPipelineRasterizationStateCreateInfo rasterizationState =
+			vks::initializers::pipelineRasterizationStateCreateInfo(
+				VK_POLYGON_MODE_FILL,
+				VK_CULL_MODE_NONE,
+				VK_FRONT_FACE_COUNTER_CLOCKWISE,
+				0);
+
+		VkPipelineColorBlendAttachmentState blendAttachmentState =
+			vks::initializers::pipelineColorBlendAttachmentState(
+				0xf,
+				VK_FALSE);
+
+		VkPipelineColorBlendStateCreateInfo colorBlendState =
+			vks::initializers::pipelineColorBlendStateCreateInfo(
+				1,
+				&blendAttachmentState);
+
+		VkPipelineDepthStencilStateCreateInfo depthStencilState =
+			vks::initializers::pipelineDepthStencilStateCreateInfo(
+				VK_FALSE,
+				VK_FALSE,
+				VK_COMPARE_OP_ALWAYS);
+
+		VkPipelineViewportStateCreateInfo viewportState =
+			vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+
+		VkPipelineMultisampleStateCreateInfo multisampleState =
+			vks::initializers::pipelineMultisampleStateCreateInfo(
+				VK_SAMPLE_COUNT_1_BIT,
+				0);
+
+		std::vector<VkDynamicState> dynamicStateEnables = {
+			VK_DYNAMIC_STATE_VIEWPORT,
+			VK_DYNAMIC_STATE_SCISSOR
+		};
+		VkPipelineDynamicStateCreateInfo dynamicState =
+			vks::initializers::pipelineDynamicStateCreateInfo(
+				dynamicStateEnables.data(),
+				static_cast<uint32_t>(dynamicStateEnables.size()),
+				0);
+
+		// Rendering pipeline
+		// Load shaders
+		std::array<VkPipelineShaderStageCreateInfo,2> shaderStages;
+
+		shaderStages[0] = loadShader(getShadersPath() + "computenbody/particle.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "computenbody/particle.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+
+		VkGraphicsPipelineCreateInfo pipelineCreateInfo =
+			vks::initializers::pipelineCreateInfo(
+				graphics.pipelineLayout,
+				renderPass,
+				0);
+
+		pipelineCreateInfo.pVertexInputState = &vertices.inputState;
+		pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState;
+		pipelineCreateInfo.pRasterizationState = &rasterizationState;
+		pipelineCreateInfo.pColorBlendState = &colorBlendState;
+		pipelineCreateInfo.pMultisampleState = &multisampleState;
+		pipelineCreateInfo.pViewportState = &viewportState;
+		pipelineCreateInfo.pDepthStencilState = &depthStencilState;
+		pipelineCreateInfo.pDynamicState = &dynamicState;
+		pipelineCreateInfo.stageCount = static_cast<uint32_t>(shaderStages.size());
+		pipelineCreateInfo.pStages = shaderStages.data();
+		pipelineCreateInfo.renderPass = renderPass;
+
+		// Additive blending
+		blendAttachmentState.colorWriteMask = 0xF;
+		blendAttachmentState.blendEnable = VK_TRUE;
+		blendAttachmentState.colorBlendOp = VK_BLEND_OP_ADD;
+		blendAttachmentState.srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
+		blendAttachmentState.dstColorBlendFactor = VK_BLEND_FACTOR_ONE;
+		blendAttachmentState.alphaBlendOp = VK_BLEND_OP_ADD;
+		blendAttachmentState.srcAlphaBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
+		blendAttachmentState.dstAlphaBlendFactor = VK_BLEND_FACTOR_DST_ALPHA;
+
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &graphics.pipeline));
+	}
+
+	void prepareGraphics()
+	{
+		prepareStorageBuffers();
+		prepareUniformBuffers();
+		setupDescriptorSetLayout();
+		preparePipelines();
+		setupDescriptorSet();
+
+		// Semaphore for compute & graphics sync
+		VkSemaphoreCreateInfo semaphoreCreateInfo = vks::initializers::semaphoreCreateInfo();
+		VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &graphics.semaphore));
+	}
+
+	void prepareCompute()
+	{
+		// Create a compute capable device queue
+		// The VulkanDevice::createLogicalDevice functions finds a compute capable queue and prefers queue families that only support compute
+		// Depending on the implementation this may result in different queue family indices for graphics and computes,
+		// requiring proper synchronization (see the memory barriers in buildComputeCommandBuffer)
+		vkGetDeviceQueue(device, compute.queueFamilyIndex, 0, &compute.queue);
+
+		// Create compute pipeline
+		// Compute pipelines are created separate from graphics pipelines even if they use the same queue (family index)
+
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			// Binding 0 : Particle position storage buffer
+			vks::initializers::descriptorSetLayoutBinding(
+				VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
+				VK_SHADER_STAGE_COMPUTE_BIT,
+				0),
+			// Binding 1 : Uniform buffer
+			vks::initializers::descriptorSetLayoutBinding(
+				VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+				VK_SHADER_STAGE_COMPUTE_BIT,
+				1),
+		};
+
+		VkDescriptorSetLayoutCreateInfo descriptorLayout =
+			vks::initializers::descriptorSetLayoutCreateInfo(
+				setLayoutBindings.data(),
+				static_cast<uint32_t>(setLayoutBindings.size()));
+
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device,	&descriptorLayout, nullptr,	&compute.descriptorSetLayout));
+
+		VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo =
+			vks::initializers::pipelineLayoutCreateInfo(
+				&compute.descriptorSetLayout,
+				1);
+
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr,	&compute.pipelineLayout));
+
+		VkDescriptorSetAllocateInfo allocInfo =
+			vks::initializers::descriptorSetAllocateInfo(
+				descriptorPool,
+				&compute.descriptorSetLayout,
+				1);
+
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &compute.descriptorSet));
+
+		std::vector<VkWriteDescriptorSet> computeWriteDescriptorSets =
+		{
+			// Binding 0 : Particle position storage buffer
+			vks::initializers::writeDescriptorSet(
+				compute.descriptorSet,
+				VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
+				0,
+				&compute.storageBuffer.descriptor),
+			// Binding 1 : Uniform buffer
+			vks::initializers::writeDescriptorSet(
+				compute.descriptorSet,
+				VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+				1,
+				&compute.uniformBuffer.descriptor)
+		};
+
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(computeWriteDescriptorSets.size()), computeWriteDescriptorSets.data(), 0, nullptr);
+
+		// Create pipelines
+		VkComputePipelineCreateInfo computePipelineCreateInfo = vks::initializers::computePipelineCreateInfo(compute.pipelineLayout, 0);
+
+		// 1st pass
+		computePipelineCreateInfo.stage = loadShader(getShadersPath() + "computenbody/particle_calculate.comp.spv", VK_SHADER_STAGE_COMPUTE_BIT);
+
+		// Set shader parameters via specialization constants
+		struct SpecializationData {
+			uint32_t sharedDataSize;
+			float gravity;
+			float power;
+			float soften;
+		} specializationData;
+
+		std::vector<VkSpecializationMapEntry> specializationMapEntries;
+		specializationMapEntries.push_back(vks::initializers::specializationMapEntry(0, offsetof(SpecializationData, sharedDataSize), sizeof(uint32_t)));
+		specializationMapEntries.push_back(vks::initializers::specializationMapEntry(1, offsetof(SpecializationData, gravity), sizeof(float)));
+		specializationMapEntries.push_back(vks::initializers::specializationMapEntry(2, offsetof(SpecializationData, power), sizeof(float)));
+		specializationMapEntries.push_back(vks::initializers::specializationMapEntry(3, offsetof(SpecializationData, soften), sizeof(float)));
+
+		specializationData.sharedDataSize = std::min((uint32_t)1024, (uint32_t)(vulkanDevice->properties.limits.maxComputeSharedMemorySize / sizeof(glm::vec4)));
+
+		specializationData.gravity = 0.002f;
+		specializationData.power = 0.75f;
+		specializationData.soften = 0.05f;
+
+		VkSpecializationInfo specializationInfo =
+			vks::initializers::specializationInfo(static_cast<uint32_t>(specializationMapEntries.size()), specializationMapEntries.data(), sizeof(specializationData), &specializationData);
+		computePipelineCreateInfo.stage.pSpecializationInfo = &specializationInfo;
+
+		VK_CHECK_RESULT(vkCreateComputePipelines(device, pipelineCache, 1, &computePipelineCreateInfo, nullptr, &compute.pipelineCalculate));
+
+		// 2nd pass
+		computePipelineCreateInfo.stage = loadShader(getShadersPath() + "computenbody/particle_integrate.comp.spv", VK_SHADER_STAGE_COMPUTE_BIT);
+		VK_CHECK_RESULT(vkCreateComputePipelines(device, pipelineCache, 1, &computePipelineCreateInfo, nullptr, &compute.pipelineIntegrate));
+
+		// Separate command pool as queue family for compute may be different than graphics
+		VkCommandPoolCreateInfo cmdPoolInfo = {};
+		cmdPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
+		cmdPoolInfo.queueFamilyIndex = compute.queueFamilyIndex;
+		cmdPoolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
+		VK_CHECK_RESULT(vkCreateCommandPool(device, &cmdPoolInfo, nullptr, &compute.commandPool));
+
+		// Create a command buffer for compute operations
+		compute.commandBuffer = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, compute.commandPool);
+
+		// Semaphore for compute & graphics sync
+		VkSemaphoreCreateInfo semaphoreCreateInfo = vks::initializers::semaphoreCreateInfo();
+		VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &compute.semaphore));
+
+		// Signal the semaphore
+		VkSubmitInfo submitInfo = vks::initializers::submitInfo();
+		submitInfo.signalSemaphoreCount = 1;
+		submitInfo.pSignalSemaphores = &compute.semaphore;
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+		VK_CHECK_RESULT(vkQueueWaitIdle(queue));
+
+		// Build a single command buffer containing the compute dispatch commands
+		buildComputeCommandBuffer();
+
+		// If graphics and compute queue family indices differ, acquire and immediately release the storage buffer, so that the initial acquire from the graphics command buffers are matched up properly
+		if (graphics.queueFamilyIndex != compute.queueFamilyIndex)
+		{
+			// Create a transient command buffer for setting up the initial buffer transfer state
+			VkCommandBuffer transferCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, compute.commandPool, true);
+
+			VkBufferMemoryBarrier acquire_buffer_barrier =
+			{
+				VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
+				nullptr,
+				0,
+				VK_ACCESS_SHADER_WRITE_BIT,
+				graphics.queueFamilyIndex,
+				compute.queueFamilyIndex,
+				compute.storageBuffer.buffer,
+				0,
+				compute.storageBuffer.size
+			};
+			vkCmdPipelineBarrier(
+				transferCmd,
+				VK_PIPELINE_STAGE_VERTEX_INPUT_BIT,
+				VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+				0,
+				0, nullptr,
+				1, &acquire_buffer_barrier,
+				0, nullptr);
+
+			VkBufferMemoryBarrier release_buffer_barrier =
+			{
+				VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
+				nullptr,
+				VK_ACCESS_SHADER_WRITE_BIT,
+				0,
+				compute.queueFamilyIndex,
+				graphics.queueFamilyIndex,
+				compute.storageBuffer.buffer,
+				0,
+				compute.storageBuffer.size
+			};
+			vkCmdPipelineBarrier(
+				transferCmd,
+				VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+				VK_PIPELINE_STAGE_VERTEX_INPUT_BIT,
+				0,
+				0, nullptr,
+				1, &release_buffer_barrier,
+				0, nullptr);
+
+			vulkanDevice->flushCommandBuffer(transferCmd, compute.queue, compute.commandPool);
+		}
+	}
+
+	// Prepare and initialize uniform buffer containing shader uniforms
+	void prepareUniformBuffers()
+	{
+		// Compute shader uniform buffer block
+		vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&compute.uniformBuffer,
+			sizeof(compute.ubo));
+
+		// Map for host access
+		VK_CHECK_RESULT(compute.uniformBuffer.map());
+
+		// Vertex shader uniform buffer block
+		vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&graphics.uniformBuffer,
+			sizeof(graphics.ubo));
+
+		// Map for host access
+		VK_CHECK_RESULT(graphics.uniformBuffer.map());
+
+		updateComputeUniformBuffers();
+		updateGraphicsUniformBuffers();
+	}
+
+	void updateComputeUniformBuffers()
+	{
+		compute.ubo.deltaT = paused ? 0.0f : frameTimer * 0.05f;
+		memcpy(compute.uniformBuffer.mapped, &compute.ubo, sizeof(compute.ubo));
+	}
+
+	void updateGraphicsUniformBuffers()
+	{
+		graphics.ubo.projection = camera.matrices.perspective;
+		graphics.ubo.view = camera.matrices.view;
+		graphics.ubo.screenDim = glm::vec2((float)width, (float)height);
+		memcpy(graphics.uniformBuffer.mapped, &graphics.ubo, sizeof(graphics.ubo));
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+
+		VkPipelineStageFlags graphicsWaitStageMasks[] = { VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
+		VkSemaphore graphicsWaitSemaphores[] = { compute.semaphore, semaphores.presentComplete };
+		VkSemaphore graphicsSignalSemaphores[] = { graphics.semaphore, semaphores.renderComplete };
+
+		// Submit graphics commands
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+		submitInfo.waitSemaphoreCount = 2;
+		submitInfo.pWaitSemaphores = graphicsWaitSemaphores;
+		submitInfo.pWaitDstStageMask = graphicsWaitStageMasks;
+		submitInfo.signalSemaphoreCount = 2;
+		submitInfo.pSignalSemaphores = graphicsSignalSemaphores;
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+
+		VulkanExampleBase::submitFrame();
+
+		// Wait for rendering finished
+		VkPipelineStageFlags waitStageMask = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
+
+		// Submit compute commands
+		VkSubmitInfo computeSubmitInfo = vks::initializers::submitInfo();
+		computeSubmitInfo.commandBufferCount = 1;
+		computeSubmitInfo.pCommandBuffers = &compute.commandBuffer;
+		computeSubmitInfo.waitSemaphoreCount = 1;
+		computeSubmitInfo.pWaitSemaphores = &graphics.semaphore;
+		computeSubmitInfo.pWaitDstStageMask = &waitStageMask;
+		computeSubmitInfo.signalSemaphoreCount = 1;
+		computeSubmitInfo.pSignalSemaphores = &compute.semaphore;
+		VK_CHECK_RESULT(vkQueueSubmit(compute.queue, 1, &computeSubmitInfo, VK_NULL_HANDLE));
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		// We will be using the queue family indices to check if graphics and compute queue families differ
+		// If that's the case, we need additional barriers for acquiring and releasing resources
+		graphics.queueFamilyIndex = vulkanDevice->queueFamilyIndices.graphics;
+		compute.queueFamilyIndex = vulkanDevice->queueFamilyIndices.compute;
+		loadAssets();
+		setupDescriptorPool();
+		prepareGraphics();
+		prepareCompute();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+		updateComputeUniformBuffers();
+		if (camera.updated) {
+			updateGraphicsUniformBuffers();
+		}
+	}
+};
+
+VULKAN_EXAMPLE_MAIN()
\ No newline at end of file
diff --git a/external/Vulkan/examples/computeparticles/computeparticles.cpp b/external/Vulkan/examples/computeparticles/computeparticles.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..48bfe07eab4377ebfed7f445e646d85d9607c2c7
--- /dev/null
+++ b/external/Vulkan/examples/computeparticles/computeparticles.cpp
@@ -0,0 +1,818 @@
+/*
+* Vulkan Example - Attraction based compute shader particle system
+*
+* Updated compute shader by Lukas Bergdoll (https://github.com/Voultapher)
+*
+* Copyright (C) 2016-2021 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkanexamplebase.h"
+
+#define VERTEX_BUFFER_BIND_ID 0
+#define ENABLE_VALIDATION false
+#if defined(__ANDROID__)
+// Lower particle count on Android for performance reasons
+#define PARTICLE_COUNT 128 * 1024
+#else
+#define PARTICLE_COUNT 256 * 1024
+#endif
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	float timer = 0.0f;
+	float animStart = 20.0f;
+	bool attachToCursor = false;
+
+	struct {
+		vks::Texture2D particle;
+		vks::Texture2D gradient;
+	} textures;
+
+	struct {
+		VkPipelineVertexInputStateCreateInfo inputState;
+		std::vector<VkVertexInputBindingDescription> bindingDescriptions;
+		std::vector<VkVertexInputAttributeDescription> attributeDescriptions;
+	} vertices;
+
+	// Resources for the graphics part of the example
+	struct {
+		uint32_t queueFamilyIndex;					// Used to check if compute and graphics queue families differ and require additional barriers
+		VkDescriptorSetLayout descriptorSetLayout;	// Particle system rendering shader binding layout
+		VkDescriptorSet descriptorSet;				// Particle system rendering shader bindings
+		VkPipelineLayout pipelineLayout;			// Layout of the graphics pipeline
+		VkPipeline pipeline;						// Particle rendering pipeline
+		VkSemaphore semaphore;                      // Execution dependency between compute & graphic submission
+	} graphics;
+
+	// Resources for the compute part of the example
+	struct {
+		uint32_t queueFamilyIndex;					// Used to check if compute and graphics queue families differ and require additional barriers
+		vks::Buffer storageBuffer;					// (Shader) storage buffer object containing the particles
+		vks::Buffer uniformBuffer;					// Uniform buffer object containing particle system parameters
+		VkQueue queue;								// Separate queue for compute commands (queue family may differ from the one used for graphics)
+		VkCommandPool commandPool;					// Use a separate command pool (queue family may differ from the one used for graphics)
+		VkCommandBuffer commandBuffer;				// Command buffer storing the dispatch commands and barriers
+		VkSemaphore semaphore;                      // Execution dependency between compute & graphic submission
+		VkDescriptorSetLayout descriptorSetLayout;	// Compute shader binding layout
+		VkDescriptorSet descriptorSet;				// Compute shader bindings
+		VkPipelineLayout pipelineLayout;			// Layout of the compute pipeline
+		VkPipeline pipeline;						// Compute pipeline for updating particle positions
+		struct computeUBO {							// Compute shader uniform block object
+			float deltaT;							//		Frame delta time
+			float destX;							//		x position of the attractor
+			float destY;							//		y position of the attractor
+			int32_t particleCount = PARTICLE_COUNT;
+		} ubo;
+	} compute;
+
+	// SSBO particle declaration
+	struct Particle {
+		glm::vec2 pos;								// Particle position
+		glm::vec2 vel;								// Particle velocity
+		glm::vec4 gradientPos;						// Texture coordinates for the gradient ramp map
+	};
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Compute shader particle system";
+	}
+
+	~VulkanExample()
+	{
+		// Graphics
+		vkDestroyPipeline(device, graphics.pipeline, nullptr);
+		vkDestroyPipelineLayout(device, graphics.pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, graphics.descriptorSetLayout, nullptr);
+
+		// Compute
+		compute.storageBuffer.destroy();
+		compute.uniformBuffer.destroy();
+		vkDestroyPipelineLayout(device, compute.pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, compute.descriptorSetLayout, nullptr);
+		vkDestroyPipeline(device, compute.pipeline, nullptr);
+		vkDestroySemaphore(device, compute.semaphore, nullptr);
+		vkDestroyCommandPool(device, compute.commandPool, nullptr);
+
+		textures.particle.destroy();
+		textures.gradient.destroy();
+	}
+
+	void loadAssets()
+	{
+		textures.particle.loadFromFile(getAssetPath() + "textures/particle01_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue);
+		textures.gradient.loadFromFile(getAssetPath() + "textures/particle_gradient_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue);
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		clearValues[0].color = defaultClearColor;
+		clearValues[1].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.offset.x = 0;
+		renderPassBeginInfo.renderArea.offset.y = 0;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		renderPassBeginInfo.clearValueCount = 2;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			// Set target frame buffer
+			renderPassBeginInfo.framebuffer = frameBuffers[i];
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			// Acquire barrier
+			if (graphics.queueFamilyIndex != compute.queueFamilyIndex)
+			{
+				VkBufferMemoryBarrier buffer_barrier =
+				{
+					VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
+					nullptr,
+					0,
+					VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT,
+					compute.queueFamilyIndex,
+					graphics.queueFamilyIndex,
+					compute.storageBuffer.buffer,
+					0,
+					compute.storageBuffer.size
+				};
+
+				vkCmdPipelineBarrier(
+					drawCmdBuffers[i],
+					VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+					VK_PIPELINE_STAGE_VERTEX_INPUT_BIT,
+					0,
+					0, nullptr,
+					1, &buffer_barrier,
+					0, nullptr);
+			}
+
+			// Draw the particle system using the update vertex buffer
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+			VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphics.pipeline);
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphics.pipelineLayout, 0, 1, &graphics.descriptorSet, 0, NULL);
+
+			VkDeviceSize offsets[1] = { 0 };
+			vkCmdBindVertexBuffers(drawCmdBuffers[i], VERTEX_BUFFER_BIND_ID, 1, &compute.storageBuffer.buffer, offsets);
+			vkCmdDraw(drawCmdBuffers[i], PARTICLE_COUNT, 1, 0, 0);
+
+			drawUI(drawCmdBuffers[i]);
+
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			// Release barrier
+			if (graphics.queueFamilyIndex != compute.queueFamilyIndex)
+			{
+				VkBufferMemoryBarrier buffer_barrier =
+				{
+					VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
+					nullptr,
+					VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT,
+					0,
+					graphics.queueFamilyIndex,
+					compute.queueFamilyIndex,
+					compute.storageBuffer.buffer,
+					0,
+					compute.storageBuffer.size
+				};
+
+				vkCmdPipelineBarrier(
+					drawCmdBuffers[i],
+					VK_PIPELINE_STAGE_VERTEX_INPUT_BIT,
+					VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+					0,
+					0, nullptr,
+					1, &buffer_barrier,
+					0, nullptr);
+			}
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+
+	}
+
+	void buildComputeCommandBuffer()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VK_CHECK_RESULT(vkBeginCommandBuffer(compute.commandBuffer, &cmdBufInfo));
+
+		// Compute particle movement
+
+		// Add memory barrier to ensure that the (graphics) vertex shader has fetched attributes before compute starts to write to the buffer
+		if (graphics.queueFamilyIndex != compute.queueFamilyIndex)
+		{
+			VkBufferMemoryBarrier buffer_barrier =
+			{
+				VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
+				nullptr,
+				0,
+				VK_ACCESS_SHADER_WRITE_BIT,
+				graphics.queueFamilyIndex,
+				compute.queueFamilyIndex,
+				compute.storageBuffer.buffer,
+				0,
+				compute.storageBuffer.size
+			};
+
+			vkCmdPipelineBarrier(
+				compute.commandBuffer,
+				VK_PIPELINE_STAGE_VERTEX_INPUT_BIT,
+				VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+				0,
+				0, nullptr,
+				1, &buffer_barrier,
+				0, nullptr);
+		}
+
+		// Dispatch the compute job
+		vkCmdBindPipeline(compute.commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, compute.pipeline);
+		vkCmdBindDescriptorSets(compute.commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, compute.pipelineLayout, 0, 1, &compute.descriptorSet, 0, 0);
+		vkCmdDispatch(compute.commandBuffer, PARTICLE_COUNT / 256, 1, 1);
+
+		// Add barrier to ensure that compute shader has finished writing to the buffer
+		// Without this the (rendering) vertex shader may display incomplete results (partial data from last frame)
+		if (graphics.queueFamilyIndex != compute.queueFamilyIndex)
+		{
+			VkBufferMemoryBarrier buffer_barrier =
+			{
+				VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
+				nullptr,
+				VK_ACCESS_SHADER_WRITE_BIT,
+				0,
+				compute.queueFamilyIndex,
+				graphics.queueFamilyIndex,
+				compute.storageBuffer.buffer,
+				0,
+				compute.storageBuffer.size
+			};
+
+			vkCmdPipelineBarrier(
+				compute.commandBuffer,
+				VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+				VK_PIPELINE_STAGE_VERTEX_INPUT_BIT,
+				0,
+				0, nullptr,
+				1, &buffer_barrier,
+				0, nullptr);
+		}
+
+		vkEndCommandBuffer(compute.commandBuffer);
+	}
+
+	// Setup and fill the compute shader storage buffers containing the particles
+	void prepareStorageBuffers()
+	{
+		std::default_random_engine rndEngine(benchmark.active ? 0 : (unsigned)time(nullptr));
+		std::uniform_real_distribution<float> rndDist(-1.0f, 1.0f);
+
+		// Initial particle positions
+		std::vector<Particle> particleBuffer(PARTICLE_COUNT);
+		for (auto& particle : particleBuffer) {
+			particle.pos = glm::vec2(rndDist(rndEngine), rndDist(rndEngine));
+			particle.vel = glm::vec2(0.0f);
+			particle.gradientPos.x = particle.pos.x / 2.0f;
+		}
+
+		VkDeviceSize storageBufferSize = particleBuffer.size() * sizeof(Particle);
+
+		// Staging
+		// SSBO won't be changed on the host after upload so copy to device local memory
+
+		vks::Buffer stagingBuffer;
+
+		vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&stagingBuffer,
+			storageBufferSize,
+			particleBuffer.data());
+
+		vulkanDevice->createBuffer(
+			// The SSBO will be used as a storage buffer for the compute pipeline and as a vertex buffer in the graphics pipeline
+			VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
+			VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
+			&compute.storageBuffer,
+			storageBufferSize);
+
+		// Copy from staging buffer to storage buffer
+		VkCommandBuffer copyCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+		VkBufferCopy copyRegion = {};
+		copyRegion.size = storageBufferSize;
+		vkCmdCopyBuffer(copyCmd, stagingBuffer.buffer, compute.storageBuffer.buffer, 1, &copyRegion);
+		// Execute a transfer barrier to the compute queue, if necessary
+		if (graphics.queueFamilyIndex != compute.queueFamilyIndex)
+		{
+			VkBufferMemoryBarrier buffer_barrier =
+			{
+				VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
+				nullptr,
+				VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT,
+				0,
+				graphics.queueFamilyIndex,
+				compute.queueFamilyIndex,
+				compute.storageBuffer.buffer,
+				0,
+				compute.storageBuffer.size
+			};
+
+			vkCmdPipelineBarrier(
+				copyCmd,
+				VK_PIPELINE_STAGE_VERTEX_INPUT_BIT,
+				VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+				0,
+				0, nullptr,
+				1, &buffer_barrier,
+				0, nullptr);
+		}
+		vulkanDevice->flushCommandBuffer(copyCmd, queue, true);
+
+		stagingBuffer.destroy();
+
+		// Binding description
+		vertices.bindingDescriptions.resize(1);
+		vertices.bindingDescriptions[0] =
+			vks::initializers::vertexInputBindingDescription(
+				VERTEX_BUFFER_BIND_ID,
+				sizeof(Particle),
+				VK_VERTEX_INPUT_RATE_VERTEX);
+
+		// Attribute descriptions
+		// Describes memory layout and shader positions
+		vertices.attributeDescriptions.resize(2);
+		// Location 0 : Position
+		vertices.attributeDescriptions[0] =
+			vks::initializers::vertexInputAttributeDescription(
+				VERTEX_BUFFER_BIND_ID,
+				0,
+				VK_FORMAT_R32G32_SFLOAT,
+				offsetof(Particle, pos));
+		// Location 1 : Gradient position
+		vertices.attributeDescriptions[1] =
+			vks::initializers::vertexInputAttributeDescription(
+				VERTEX_BUFFER_BIND_ID,
+				1,
+				VK_FORMAT_R32G32B32A32_SFLOAT,
+				offsetof(Particle, gradientPos));
+
+		// Assign to vertex buffer
+		vertices.inputState = vks::initializers::pipelineVertexInputStateCreateInfo();
+		vertices.inputState.vertexBindingDescriptionCount = static_cast<uint32_t>(vertices.bindingDescriptions.size());
+		vertices.inputState.pVertexBindingDescriptions = vertices.bindingDescriptions.data();
+		vertices.inputState.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertices.attributeDescriptions.size());
+		vertices.inputState.pVertexAttributeDescriptions = vertices.attributeDescriptions.data();
+	}
+
+	void setupDescriptorPool()
+	{
+		std::vector<VkDescriptorPoolSize> poolSizes =
+		{
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1),
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1),
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2)
+		};
+
+		VkDescriptorPoolCreateInfo descriptorPoolInfo =
+			vks::initializers::descriptorPoolCreateInfo(
+				static_cast<uint32_t>(poolSizes.size()),
+				poolSizes.data(),
+				2);
+
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+	}
+
+	void setupDescriptorSetLayout()
+	{
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings;
+		// Binding 0 : Particle color map
+		setLayoutBindings.push_back(vks::initializers::descriptorSetLayoutBinding(
+			VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+			VK_SHADER_STAGE_FRAGMENT_BIT,
+			0));
+		// Binding 1 : Particle gradient ramp
+		setLayoutBindings.push_back(vks::initializers::descriptorSetLayoutBinding(
+			VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+			VK_SHADER_STAGE_FRAGMENT_BIT,
+			1));
+
+		VkDescriptorSetLayoutCreateInfo descriptorLayout =
+			vks::initializers::descriptorSetLayoutCreateInfo(
+				setLayoutBindings.data(),
+				static_cast<uint32_t>(setLayoutBindings.size()));
+
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &graphics.descriptorSetLayout));
+
+		VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo =
+			vks::initializers::pipelineLayoutCreateInfo(
+				&graphics.descriptorSetLayout,
+				1);
+
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &graphics.pipelineLayout));
+	}
+
+	void setupDescriptorSet()
+	{
+		VkDescriptorSetAllocateInfo allocInfo =
+			vks::initializers::descriptorSetAllocateInfo(
+				descriptorPool,
+				&graphics.descriptorSetLayout,
+				1);
+
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &graphics.descriptorSet));
+
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets;
+		// Binding 0 : Particle color map
+		writeDescriptorSets.push_back(vks::initializers::writeDescriptorSet(
+			graphics.descriptorSet,
+			VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+			0,
+			&textures.particle.descriptor));
+		// Binding 1 : Particle gradient ramp
+		writeDescriptorSets.push_back(vks::initializers::writeDescriptorSet(
+			graphics.descriptorSet,
+			VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+			1,
+			&textures.gradient.descriptor));
+
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL);
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState =
+			vks::initializers::pipelineInputAssemblyStateCreateInfo(
+				VK_PRIMITIVE_TOPOLOGY_POINT_LIST,
+				0,
+				VK_FALSE);
+
+		VkPipelineRasterizationStateCreateInfo rasterizationState =
+			vks::initializers::pipelineRasterizationStateCreateInfo(
+				VK_POLYGON_MODE_FILL,
+				VK_CULL_MODE_NONE,
+				VK_FRONT_FACE_COUNTER_CLOCKWISE,
+				0);
+
+		VkPipelineColorBlendAttachmentState blendAttachmentState =
+			vks::initializers::pipelineColorBlendAttachmentState(
+				0xf,
+				VK_FALSE);
+
+		VkPipelineColorBlendStateCreateInfo colorBlendState =
+			vks::initializers::pipelineColorBlendStateCreateInfo(
+				1,
+				&blendAttachmentState);
+
+		VkPipelineDepthStencilStateCreateInfo depthStencilState =
+			vks::initializers::pipelineDepthStencilStateCreateInfo(
+				VK_FALSE,
+				VK_FALSE,
+				VK_COMPARE_OP_ALWAYS);
+
+		VkPipelineViewportStateCreateInfo viewportState =
+			vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+
+		VkPipelineMultisampleStateCreateInfo multisampleState =
+			vks::initializers::pipelineMultisampleStateCreateInfo(
+				VK_SAMPLE_COUNT_1_BIT,
+				0);
+
+		std::vector<VkDynamicState> dynamicStateEnables = {
+			VK_DYNAMIC_STATE_VIEWPORT,
+			VK_DYNAMIC_STATE_SCISSOR
+		};
+		VkPipelineDynamicStateCreateInfo dynamicState =
+			vks::initializers::pipelineDynamicStateCreateInfo(
+				dynamicStateEnables.data(),
+				static_cast<uint32_t>(dynamicStateEnables.size()),
+				0);
+
+		// Rendering pipeline
+		// Load shaders
+		std::array<VkPipelineShaderStageCreateInfo,2> shaderStages;
+
+		shaderStages[0] = loadShader(getShadersPath() + "computeparticles/particle.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "computeparticles/particle.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+
+		VkGraphicsPipelineCreateInfo pipelineCreateInfo =
+			vks::initializers::pipelineCreateInfo(
+				graphics.pipelineLayout,
+				renderPass,
+				0);
+
+		pipelineCreateInfo.pVertexInputState = &vertices.inputState;
+		pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState;
+		pipelineCreateInfo.pRasterizationState = &rasterizationState;
+		pipelineCreateInfo.pColorBlendState = &colorBlendState;
+		pipelineCreateInfo.pMultisampleState = &multisampleState;
+		pipelineCreateInfo.pViewportState = &viewportState;
+		pipelineCreateInfo.pDepthStencilState = &depthStencilState;
+		pipelineCreateInfo.pDynamicState = &dynamicState;
+		pipelineCreateInfo.stageCount = static_cast<uint32_t>(shaderStages.size());
+		pipelineCreateInfo.pStages = shaderStages.data();
+		pipelineCreateInfo.renderPass = renderPass;
+
+		// Additive blending
+		blendAttachmentState.colorWriteMask = 0xF;
+		blendAttachmentState.blendEnable = VK_TRUE;
+		blendAttachmentState.colorBlendOp = VK_BLEND_OP_ADD;
+		blendAttachmentState.srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
+		blendAttachmentState.dstColorBlendFactor = VK_BLEND_FACTOR_ONE;
+		blendAttachmentState.alphaBlendOp = VK_BLEND_OP_ADD;
+		blendAttachmentState.srcAlphaBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
+		blendAttachmentState.dstAlphaBlendFactor = VK_BLEND_FACTOR_DST_ALPHA;
+
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &graphics.pipeline));
+	}
+
+	void prepareGraphics()
+	{
+		prepareStorageBuffers();
+		prepareUniformBuffers();
+		setupDescriptorSetLayout();
+		preparePipelines();
+		setupDescriptorSet();
+
+		// Semaphore for compute & graphics sync
+		VkSemaphoreCreateInfo semaphoreCreateInfo = vks::initializers::semaphoreCreateInfo();
+		VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &graphics.semaphore));
+	}
+
+	void prepareCompute()
+	{
+		// Create a compute capable device queue
+		// The VulkanDevice::createLogicalDevice functions finds a compute capable queue and prefers queue families that only support compute
+		// Depending on the implementation this may result in different queue family indices for graphics and computes,
+		// requiring proper synchronization (see the memory and pipeline barriers)
+		vkGetDeviceQueue(device, compute.queueFamilyIndex, 0, &compute.queue);
+
+		// Create compute pipeline
+		// Compute pipelines are created separate from graphics pipelines even if they use the same queue (family index)
+
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			// Binding 0 : Particle position storage buffer
+			vks::initializers::descriptorSetLayoutBinding(
+				VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
+				VK_SHADER_STAGE_COMPUTE_BIT,
+				0),
+			// Binding 1 : Uniform buffer
+			vks::initializers::descriptorSetLayoutBinding(
+				VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+				VK_SHADER_STAGE_COMPUTE_BIT,
+				1),
+		};
+
+		VkDescriptorSetLayoutCreateInfo descriptorLayout =
+			vks::initializers::descriptorSetLayoutCreateInfo(
+				setLayoutBindings.data(),
+				static_cast<uint32_t>(setLayoutBindings.size()));
+
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device,	&descriptorLayout, nullptr,	&compute.descriptorSetLayout));
+
+		VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo =
+			vks::initializers::pipelineLayoutCreateInfo(
+				&compute.descriptorSetLayout,
+				1);
+
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr,	&compute.pipelineLayout));
+
+		VkDescriptorSetAllocateInfo allocInfo =
+			vks::initializers::descriptorSetAllocateInfo(
+				descriptorPool,
+				&compute.descriptorSetLayout,
+				1);
+
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &compute.descriptorSet));
+
+		std::vector<VkWriteDescriptorSet> computeWriteDescriptorSets =
+		{
+			// Binding 0 : Particle position storage buffer
+			vks::initializers::writeDescriptorSet(
+				compute.descriptorSet,
+				VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
+				0,
+				&compute.storageBuffer.descriptor),
+			// Binding 1 : Uniform buffer
+			vks::initializers::writeDescriptorSet(
+				compute.descriptorSet,
+				VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+				1,
+				&compute.uniformBuffer.descriptor)
+		};
+
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(computeWriteDescriptorSets.size()), computeWriteDescriptorSets.data(), 0, NULL);
+
+		// Create pipeline
+		VkComputePipelineCreateInfo computePipelineCreateInfo = vks::initializers::computePipelineCreateInfo(compute.pipelineLayout, 0);
+		computePipelineCreateInfo.stage = loadShader(getShadersPath() + "computeparticles/particle.comp.spv", VK_SHADER_STAGE_COMPUTE_BIT);
+		VK_CHECK_RESULT(vkCreateComputePipelines(device, pipelineCache, 1, &computePipelineCreateInfo, nullptr, &compute.pipeline));
+
+		// Separate command pool as queue family for compute may be different than graphics
+		VkCommandPoolCreateInfo cmdPoolInfo = {};
+		cmdPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
+		cmdPoolInfo.queueFamilyIndex = compute.queueFamilyIndex;
+		cmdPoolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
+		VK_CHECK_RESULT(vkCreateCommandPool(device, &cmdPoolInfo, nullptr, &compute.commandPool));
+
+		// Create a command buffer for compute operations
+		compute.commandBuffer = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, compute.commandPool);
+
+		// Semaphore for compute & graphics sync
+		VkSemaphoreCreateInfo semaphoreCreateInfo = vks::initializers::semaphoreCreateInfo();
+		VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &compute.semaphore));
+
+		// Signal the semaphore
+		VkSubmitInfo submitInfo = vks::initializers::submitInfo();
+		submitInfo.signalSemaphoreCount = 1;
+		submitInfo.pSignalSemaphores = &compute.semaphore;
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+		VK_CHECK_RESULT(vkQueueWaitIdle(queue));
+
+		// Build a single command buffer containing the compute dispatch commands
+		buildComputeCommandBuffer();
+
+		// If graphics and compute queue family indices differ, acquire and immediately release the storage buffer, so that the initial acquire from the graphics command buffers are matched up properly
+		if (graphics.queueFamilyIndex != compute.queueFamilyIndex)
+		{
+			// Create a transient command buffer for setting up the initial buffer transfer state
+			VkCommandBuffer transferCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, compute.commandPool, true);
+
+			VkBufferMemoryBarrier acquire_buffer_barrier =
+			{
+				VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
+				nullptr,
+				0,
+				VK_ACCESS_SHADER_WRITE_BIT,
+				graphics.queueFamilyIndex,
+				compute.queueFamilyIndex,
+				compute.storageBuffer.buffer,
+				0,
+				compute.storageBuffer.size
+			};
+			vkCmdPipelineBarrier(
+				transferCmd,
+				VK_PIPELINE_STAGE_VERTEX_INPUT_BIT,
+				VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+				0,
+				0, nullptr,
+				1, &acquire_buffer_barrier,
+				0, nullptr);
+
+			VkBufferMemoryBarrier release_buffer_barrier =
+			{
+				VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
+				nullptr,
+				VK_ACCESS_SHADER_WRITE_BIT,
+				0,
+				compute.queueFamilyIndex,
+				graphics.queueFamilyIndex,
+				compute.storageBuffer.buffer,
+				0,
+				compute.storageBuffer.size
+			};
+			vkCmdPipelineBarrier(
+				transferCmd,
+				VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+				VK_PIPELINE_STAGE_VERTEX_INPUT_BIT,
+				0,
+				0, nullptr,
+				1, &release_buffer_barrier,
+				0, nullptr);
+
+			vulkanDevice->flushCommandBuffer(transferCmd, compute.queue, compute.commandPool);
+		}
+	}
+
+	// Prepare and initialize uniform buffer containing shader uniforms
+	void prepareUniformBuffers()
+	{
+		// Compute shader uniform buffer block
+		vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&compute.uniformBuffer,
+			sizeof(compute.ubo));
+
+		// Map for host access
+		VK_CHECK_RESULT(compute.uniformBuffer.map());
+
+		updateUniformBuffers();
+	}
+
+	void updateUniformBuffers()
+	{
+		compute.ubo.deltaT = frameTimer * 2.5f;
+		if (!attachToCursor)
+		{
+			compute.ubo.destX = sin(glm::radians(timer * 360.0f)) * 0.75f;
+			compute.ubo.destY = 0.0f;
+		}
+		else
+		{
+			float normalizedMx = (mousePos.x - static_cast<float>(width / 2)) / static_cast<float>(width / 2);
+			float normalizedMy = (mousePos.y - static_cast<float>(height / 2)) / static_cast<float>(height / 2);
+			compute.ubo.destX = normalizedMx;
+			compute.ubo.destY = normalizedMy;
+		}
+
+		memcpy(compute.uniformBuffer.mapped, &compute.ubo, sizeof(compute.ubo));
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+
+		VkPipelineStageFlags graphicsWaitStageMasks[] = { VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
+		VkSemaphore graphicsWaitSemaphores[] = { compute.semaphore, semaphores.presentComplete };
+		VkSemaphore graphicsSignalSemaphores[] = { graphics.semaphore, semaphores.renderComplete };
+
+		// Submit graphics commands
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+		submitInfo.waitSemaphoreCount = 2;
+		submitInfo.pWaitSemaphores = graphicsWaitSemaphores;
+		submitInfo.pWaitDstStageMask = graphicsWaitStageMasks;
+		submitInfo.signalSemaphoreCount = 2;
+		submitInfo.pSignalSemaphores = graphicsSignalSemaphores;
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+
+		VulkanExampleBase::submitFrame();
+
+		// Wait for rendering finished
+		VkPipelineStageFlags waitStageMask = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
+
+		// Submit compute commands
+		VkSubmitInfo computeSubmitInfo = vks::initializers::submitInfo();
+		computeSubmitInfo.commandBufferCount = 1;
+		computeSubmitInfo.pCommandBuffers = &compute.commandBuffer;
+		computeSubmitInfo.waitSemaphoreCount = 1;
+		computeSubmitInfo.pWaitSemaphores = &graphics.semaphore;
+		computeSubmitInfo.pWaitDstStageMask = &waitStageMask;
+		computeSubmitInfo.signalSemaphoreCount = 1;
+		computeSubmitInfo.pSignalSemaphores = &compute.semaphore;
+		VK_CHECK_RESULT(vkQueueSubmit(compute.queue, 1, &computeSubmitInfo, VK_NULL_HANDLE));
+
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		// We will be using the queue family indices to check if graphics and compute queue families differ
+		// If that's the case, we need additional barriers for acquiring and releasing resources
+		graphics.queueFamilyIndex = vulkanDevice->queueFamilyIndices.graphics;
+		compute.queueFamilyIndex = vulkanDevice->queueFamilyIndices.compute;
+		loadAssets();
+		setupDescriptorPool();
+		prepareGraphics();
+		prepareCompute();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+
+		if (!attachToCursor)
+		{
+			if (animStart > 0.0f)
+			{
+				animStart -= frameTimer * 5.0f;
+			}
+			else if (animStart <= 0.0f)
+			{
+				timer += frameTimer * 0.04f;
+				if (timer > 1.f)
+					timer = 0.f;
+			}
+		}
+
+		updateUniformBuffers();
+	}
+
+	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
+	{
+		if (overlay->header("Settings")) {
+			overlay->checkBox("Attach attractor to cursor", &attachToCursor);
+		}
+	}
+};
+
+VULKAN_EXAMPLE_MAIN()
diff --git a/external/Vulkan/examples/computeraytracing/computeraytracing.cpp b/external/Vulkan/examples/computeraytracing/computeraytracing.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..6a9036c24bda03c4c96665c86a2b02a7935615c7
--- /dev/null
+++ b/external/Vulkan/examples/computeraytracing/computeraytracing.cpp
@@ -0,0 +1,722 @@
+/*
+* Vulkan Example - Compute shader ray tracing
+*
+* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkanexamplebase.h"
+
+#define VERTEX_BUFFER_BIND_ID 0
+#define ENABLE_VALIDATION false
+
+#if defined(__ANDROID__)
+#define TEX_DIM 1024
+#else
+#define TEX_DIM 2048
+#endif
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	vks::Texture textureComputeTarget;
+
+	// Resources for the graphics part of the example
+	struct {
+		VkDescriptorSetLayout descriptorSetLayout;	// Raytraced image display shader binding layout
+		VkDescriptorSet descriptorSetPreCompute;	// Raytraced image display shader bindings before compute shader image manipulation
+		VkDescriptorSet descriptorSet;				// Raytraced image display shader bindings after compute shader image manipulation
+		VkPipeline pipeline;						// Raytraced image display pipeline
+		VkPipelineLayout pipelineLayout;			// Layout of the graphics pipeline
+	} graphics;
+
+	// Resources for the compute part of the example
+	struct {
+		struct {
+			vks::Buffer spheres;						// (Shader) storage buffer object with scene spheres
+			vks::Buffer planes;						// (Shader) storage buffer object with scene planes
+		} storageBuffers;
+		vks::Buffer uniformBuffer;					// Uniform buffer object containing scene data
+		VkQueue queue;								// Separate queue for compute commands (queue family may differ from the one used for graphics)
+		VkCommandPool commandPool;					// Use a separate command pool (queue family may differ from the one used for graphics)
+		VkCommandBuffer commandBuffer;				// Command buffer storing the dispatch commands and barriers
+		VkFence fence;								// Synchronization fence to avoid rewriting compute CB if still in use
+		VkDescriptorSetLayout descriptorSetLayout;	// Compute shader binding layout
+		VkDescriptorSet descriptorSet;				// Compute shader bindings
+		VkPipelineLayout pipelineLayout;			// Layout of the compute pipeline
+		VkPipeline pipeline;						// Compute raytracing pipeline
+		struct UBOCompute {							// Compute shader uniform block object
+			glm::vec3 lightPos;
+			float aspectRatio;						// Aspect ratio of the viewport
+			glm::vec4 fogColor = glm::vec4(0.0f);
+			struct {
+				glm::vec3 pos = glm::vec3(0.0f, 0.0f, 4.0f);
+				glm::vec3 lookat = glm::vec3(0.0f, 0.5f, 0.0f);
+				float fov = 10.0f;
+			} camera;
+		} ubo;
+	} compute;
+
+	// SSBO sphere declaration
+	struct Sphere {									// Shader uses std140 layout (so we only use vec4 instead of vec3)
+		glm::vec3 pos;
+		float radius;
+		glm::vec3 diffuse;
+		float specular;
+		uint32_t id;								// Id used to identify sphere for raytracing
+		glm::ivec3 _pad;
+	};
+
+	// SSBO plane declaration
+	struct Plane {
+		glm::vec3 normal;
+		float distance;
+		glm::vec3 diffuse;
+		float specular;
+		uint32_t id;
+		glm::ivec3 _pad;
+	};
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Compute shader ray tracing";
+		compute.ubo.aspectRatio = (float)width / (float)height;
+		timerSpeed *= 0.25f;
+
+		camera.type = Camera::CameraType::lookat;
+		camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 512.0f);
+		camera.setRotation(glm::vec3(0.0f, 0.0f, 0.0f));
+		camera.setTranslation(glm::vec3(0.0f, 0.0f, -4.0f));
+		camera.rotationSpeed = 0.0f;
+		camera.movementSpeed = 2.5f;
+	}
+
+	~VulkanExample()
+	{
+		// Graphics
+		vkDestroyPipeline(device, graphics.pipeline, nullptr);
+		vkDestroyPipelineLayout(device, graphics.pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, graphics.descriptorSetLayout, nullptr);
+
+		// Compute
+		vkDestroyPipeline(device, compute.pipeline, nullptr);
+		vkDestroyPipelineLayout(device, compute.pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, compute.descriptorSetLayout, nullptr);
+		vkDestroyFence(device, compute.fence, nullptr);
+		vkDestroyCommandPool(device, compute.commandPool, nullptr);
+		compute.uniformBuffer.destroy();
+		compute.storageBuffers.spheres.destroy();
+		compute.storageBuffers.planes.destroy();
+
+		textureComputeTarget.destroy();
+	}
+
+	// Prepare a texture target that is used to store compute shader calculations
+	void prepareTextureTarget(vks::Texture *tex, uint32_t width, uint32_t height, VkFormat format)
+	{
+		// Get device properties for the requested texture format
+		VkFormatProperties formatProperties;
+		vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &formatProperties);
+		// Check if requested image format supports image storage operations
+		assert(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT);
+
+		// Prepare blit target texture
+		tex->width = width;
+		tex->height = height;
+
+		VkImageCreateInfo imageCreateInfo = vks::initializers::imageCreateInfo();
+		imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
+		imageCreateInfo.format = format;
+		imageCreateInfo.extent = { width, height, 1 };
+		imageCreateInfo.mipLevels = 1;
+		imageCreateInfo.arrayLayers = 1;
+		imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
+		imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
+		imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+		// Image will be sampled in the fragment shader and used as storage target in the compute shader
+		imageCreateInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT;
+		imageCreateInfo.flags = 0;
+
+		VkMemoryAllocateInfo memAllocInfo = vks::initializers::memoryAllocateInfo();
+		VkMemoryRequirements memReqs;
+
+		VK_CHECK_RESULT(vkCreateImage(device, &imageCreateInfo, nullptr, &tex->image));
+		vkGetImageMemoryRequirements(device, tex->image, &memReqs);
+		memAllocInfo.allocationSize = memReqs.size;
+		memAllocInfo.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+		VK_CHECK_RESULT(vkAllocateMemory(device, &memAllocInfo, nullptr, &tex->deviceMemory));
+		VK_CHECK_RESULT(vkBindImageMemory(device, tex->image, tex->deviceMemory, 0));
+
+		VkCommandBuffer layoutCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+
+		tex->imageLayout = VK_IMAGE_LAYOUT_GENERAL;
+		vks::tools::setImageLayout(
+			layoutCmd,
+			tex->image,
+			VK_IMAGE_ASPECT_COLOR_BIT,
+			VK_IMAGE_LAYOUT_UNDEFINED,
+			tex->imageLayout);
+
+		vulkanDevice->flushCommandBuffer(layoutCmd, queue, true);
+
+		// Create sampler
+		VkSamplerCreateInfo sampler = vks::initializers::samplerCreateInfo();
+		sampler.magFilter = VK_FILTER_LINEAR;
+		sampler.minFilter = VK_FILTER_LINEAR;
+		sampler.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+		sampler.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
+		sampler.addressModeV = sampler.addressModeU;
+		sampler.addressModeW = sampler.addressModeU;
+		sampler.mipLodBias = 0.0f;
+		sampler.maxAnisotropy = 1.0f;
+		sampler.compareOp = VK_COMPARE_OP_NEVER;
+		sampler.minLod = 0.0f;
+		sampler.maxLod = 0.0f;
+		sampler.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
+		VK_CHECK_RESULT(vkCreateSampler(device, &sampler, nullptr, &tex->sampler));
+
+		// Create image view
+		VkImageViewCreateInfo view = vks::initializers::imageViewCreateInfo();
+		view.viewType = VK_IMAGE_VIEW_TYPE_2D;
+		view.format = format;
+		view.components = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A };
+		view.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 };
+		view.image = tex->image;
+		VK_CHECK_RESULT(vkCreateImageView(device, &view, nullptr, &tex->view));
+
+		// Initialize a descriptor for later use
+		tex->descriptor.imageLayout = tex->imageLayout;
+		tex->descriptor.imageView = tex->view;
+		tex->descriptor.sampler = tex->sampler;
+		tex->device = vulkanDevice;
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		clearValues[0].color = defaultClearColor;
+		clearValues[1].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.offset.x = 0;
+		renderPassBeginInfo.renderArea.offset.y = 0;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		renderPassBeginInfo.clearValueCount = 2;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			// Set target frame buffer
+			renderPassBeginInfo.framebuffer = frameBuffers[i];
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			// Image memory barrier to make sure that compute shader writes are finished before sampling from the texture
+			VkImageMemoryBarrier imageMemoryBarrier = {};
+			imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+			imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL;
+			imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_GENERAL;
+			imageMemoryBarrier.image = textureComputeTarget.image;
+			imageMemoryBarrier.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 };
+			imageMemoryBarrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
+			imageMemoryBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
+			vkCmdPipelineBarrier(
+				drawCmdBuffers[i],
+				VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+				VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
+				VK_FLAGS_NONE,
+				0, nullptr,
+				0, nullptr,
+				1, &imageMemoryBarrier);
+
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+			VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+			// Display ray traced image generated by compute shader as a full screen quad
+			// Quad vertices are generated in the vertex shader
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphics.pipelineLayout, 0, 1, &graphics.descriptorSet, 0, NULL);
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphics.pipeline);
+			vkCmdDraw(drawCmdBuffers[i], 3, 1, 0, 0);
+
+			drawUI(drawCmdBuffers[i]);
+
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+
+	}
+
+	void buildComputeCommandBuffer()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VK_CHECK_RESULT(vkBeginCommandBuffer(compute.commandBuffer, &cmdBufInfo));
+
+		vkCmdBindPipeline(compute.commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, compute.pipeline);
+		vkCmdBindDescriptorSets(compute.commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, compute.pipelineLayout, 0, 1, &compute.descriptorSet, 0, 0);
+
+		vkCmdDispatch(compute.commandBuffer, textureComputeTarget.width / 16, textureComputeTarget.height / 16, 1);
+
+		vkEndCommandBuffer(compute.commandBuffer);
+	}
+
+	uint32_t currentId = 0;	// Id used to identify objects by the ray tracing shader
+
+	Sphere newSphere(glm::vec3 pos, float radius, glm::vec3 diffuse, float specular)
+	{
+		Sphere sphere;
+		sphere.id = currentId++;
+		sphere.pos = pos;
+		sphere.radius = radius;
+		sphere.diffuse = diffuse;
+		sphere.specular = specular;
+		return sphere;
+	}
+
+	Plane newPlane(glm::vec3 normal, float distance, glm::vec3 diffuse, float specular)
+	{
+		Plane plane;
+		plane.id = currentId++;
+		plane.normal = normal;
+		plane.distance = distance;
+		plane.diffuse = diffuse;
+		plane.specular = specular;
+		return plane;
+	}
+
+	// Setup and fill the compute shader storage buffers containing primitives for the raytraced scene
+	void prepareStorageBuffers()
+	{
+		// Spheres
+		std::vector<Sphere> spheres;
+		spheres.push_back(newSphere(glm::vec3(1.75f, -0.5f, 0.0f), 1.0f, glm::vec3(0.0f, 1.0f, 0.0f), 32.0f));
+		spheres.push_back(newSphere(glm::vec3(0.0f, 1.0f, -0.5f), 1.0f, glm::vec3(0.65f, 0.77f, 0.97f), 32.0f));
+		spheres.push_back(newSphere(glm::vec3(-1.75f, -0.75f, -0.5f), 1.25f, glm::vec3(0.9f, 0.76f, 0.46f), 32.0f));
+		VkDeviceSize storageBufferSize = spheres.size() * sizeof(Sphere);
+
+		// Stage
+		vks::Buffer stagingBuffer;
+
+		vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&stagingBuffer,
+			storageBufferSize,
+			spheres.data());
+
+		vulkanDevice->createBuffer(
+			// The SSBO will be used as a storage buffer for the compute pipeline and as a vertex buffer in the graphics pipeline
+			VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
+			VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
+			&compute.storageBuffers.spheres,
+			storageBufferSize);
+
+		// Copy to staging buffer
+		VkCommandBuffer copyCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+		VkBufferCopy copyRegion = {};
+		copyRegion.size = storageBufferSize;
+		vkCmdCopyBuffer(copyCmd, stagingBuffer.buffer, compute.storageBuffers.spheres.buffer, 1, &copyRegion);
+		vulkanDevice->flushCommandBuffer(copyCmd, queue, true);
+
+		stagingBuffer.destroy();
+
+		// Planes
+		std::vector<Plane> planes;
+		const float roomDim = 4.0f;
+		planes.push_back(newPlane(glm::vec3(0.0f, 1.0f, 0.0f), roomDim, glm::vec3(1.0f), 32.0f));
+		planes.push_back(newPlane(glm::vec3(0.0f, -1.0f, 0.0f), roomDim, glm::vec3(1.0f), 32.0f));
+		planes.push_back(newPlane(glm::vec3(0.0f, 0.0f, 1.0f), roomDim, glm::vec3(1.0f), 32.0f));
+		planes.push_back(newPlane(glm::vec3(0.0f, 0.0f, -1.0f), roomDim, glm::vec3(0.0f), 32.0f));
+		planes.push_back(newPlane(glm::vec3(-1.0f, 0.0f, 0.0f), roomDim, glm::vec3(1.0f, 0.0f, 0.0f), 32.0f));
+		planes.push_back(newPlane(glm::vec3(1.0f, 0.0f, 0.0f), roomDim, glm::vec3(0.0f, 1.0f, 0.0f), 32.0f));
+		storageBufferSize = planes.size() * sizeof(Plane);
+
+		// Stage
+		vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&stagingBuffer,
+			storageBufferSize,
+			planes.data());
+
+		vulkanDevice->createBuffer(
+			// The SSBO will be used as a storage buffer for the compute pipeline and as a vertex buffer in the graphics pipeline
+			VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
+			VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
+			&compute.storageBuffers.planes,
+			storageBufferSize);
+
+		// Copy to staging buffer
+		copyCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+		copyRegion.size = storageBufferSize;
+		vkCmdCopyBuffer(copyCmd, stagingBuffer.buffer, compute.storageBuffers.planes.buffer, 1, &copyRegion);
+		vulkanDevice->flushCommandBuffer(copyCmd, queue, true);
+
+		stagingBuffer.destroy();
+	}
+
+	void setupDescriptorPool()
+	{
+		std::vector<VkDescriptorPoolSize> poolSizes =
+		{
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2),			// Compute UBO
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 4),	// Graphics image samplers
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1),				// Storage image for ray traced image output
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 2),			// Storage buffer for the scene primitives
+		};
+
+		VkDescriptorPoolCreateInfo descriptorPoolInfo =
+			vks::initializers::descriptorPoolCreateInfo(
+				poolSizes.size(),
+				poolSizes.data(),
+				3);
+
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+	}
+
+	void setupDescriptorSetLayout()
+	{
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings =
+		{
+			// Binding 0 : Fragment shader image sampler
+			vks::initializers::descriptorSetLayoutBinding(
+				VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+				VK_SHADER_STAGE_FRAGMENT_BIT,
+				0)
+		};
+
+		VkDescriptorSetLayoutCreateInfo descriptorLayout =
+			vks::initializers::descriptorSetLayoutCreateInfo(
+				setLayoutBindings.data(),
+				setLayoutBindings.size());
+
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &graphics.descriptorSetLayout));
+
+		VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo =
+			vks::initializers::pipelineLayoutCreateInfo(
+				&graphics.descriptorSetLayout,
+				1);
+
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &graphics.pipelineLayout));
+	}
+
+	void setupDescriptorSet()
+	{
+		VkDescriptorSetAllocateInfo allocInfo =
+			vks::initializers::descriptorSetAllocateInfo(
+				descriptorPool,
+				&graphics.descriptorSetLayout,
+				1);
+
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &graphics.descriptorSet));
+
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets =
+		{
+			// Binding 0 : Fragment shader texture sampler
+			vks::initializers::writeDescriptorSet(
+				graphics.descriptorSet,
+				VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+				0,
+				&textureComputeTarget.descriptor)
+		};
+
+		vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, NULL);
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState =
+			vks::initializers::pipelineInputAssemblyStateCreateInfo(
+				VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
+				0,
+				VK_FALSE);
+
+		VkPipelineRasterizationStateCreateInfo rasterizationState =
+			vks::initializers::pipelineRasterizationStateCreateInfo(
+				VK_POLYGON_MODE_FILL,
+				VK_CULL_MODE_FRONT_BIT,
+				VK_FRONT_FACE_COUNTER_CLOCKWISE,
+				0);
+
+		VkPipelineColorBlendAttachmentState blendAttachmentState =
+			vks::initializers::pipelineColorBlendAttachmentState(
+				0xf,
+				VK_FALSE);
+
+		VkPipelineColorBlendStateCreateInfo colorBlendState =
+			vks::initializers::pipelineColorBlendStateCreateInfo(
+				1,
+				&blendAttachmentState);
+
+		VkPipelineDepthStencilStateCreateInfo depthStencilState =
+			vks::initializers::pipelineDepthStencilStateCreateInfo(
+				VK_FALSE,
+				VK_FALSE,
+				VK_COMPARE_OP_LESS_OR_EQUAL);
+
+		VkPipelineViewportStateCreateInfo viewportState =
+			vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+
+		VkPipelineMultisampleStateCreateInfo multisampleState =
+			vks::initializers::pipelineMultisampleStateCreateInfo(
+				VK_SAMPLE_COUNT_1_BIT,
+				0);
+
+		std::vector<VkDynamicState> dynamicStateEnables = {
+			VK_DYNAMIC_STATE_VIEWPORT,
+			VK_DYNAMIC_STATE_SCISSOR
+		};
+		VkPipelineDynamicStateCreateInfo dynamicState =
+			vks::initializers::pipelineDynamicStateCreateInfo(
+				dynamicStateEnables.data(),
+				dynamicStateEnables.size(),
+				0);
+
+		// Display pipeline
+		std::array<VkPipelineShaderStageCreateInfo,2> shaderStages;
+
+		shaderStages[0] = loadShader(getShadersPath() + "computeraytracing/texture.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "computeraytracing/texture.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+
+		VkGraphicsPipelineCreateInfo pipelineCreateInfo =
+			vks::initializers::pipelineCreateInfo(
+				graphics.pipelineLayout,
+				renderPass,
+				0);
+
+		VkPipelineVertexInputStateCreateInfo emptyInputState{};
+		emptyInputState.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
+		emptyInputState.vertexAttributeDescriptionCount = 0;
+		emptyInputState.pVertexAttributeDescriptions = nullptr;
+		emptyInputState.vertexBindingDescriptionCount = 0;
+		emptyInputState.pVertexBindingDescriptions = nullptr;
+		pipelineCreateInfo.pVertexInputState = &emptyInputState;
+
+		pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState;
+		pipelineCreateInfo.pRasterizationState = &rasterizationState;
+		pipelineCreateInfo.pColorBlendState = &colorBlendState;
+		pipelineCreateInfo.pMultisampleState = &multisampleState;
+		pipelineCreateInfo.pViewportState = &viewportState;
+		pipelineCreateInfo.pDepthStencilState = &depthStencilState;
+		pipelineCreateInfo.pDynamicState = &dynamicState;
+		pipelineCreateInfo.stageCount = shaderStages.size();
+		pipelineCreateInfo.pStages = shaderStages.data();
+		pipelineCreateInfo.renderPass = renderPass;
+
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &graphics.pipeline));
+	}
+
+	// Prepare the compute pipeline that generates the ray traced image
+	void prepareCompute()
+	{
+		// Create a compute capable device queue
+		// The VulkanDevice::createLogicalDevice functions finds a compute capable queue and prefers queue families that only support compute
+		// Depending on the implementation this may result in different queue family indices for graphics and computes,
+		// requiring proper synchronization (see the memory barriers in buildComputeCommandBuffer)
+		VkDeviceQueueCreateInfo queueCreateInfo = {};
+		queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
+		queueCreateInfo.pNext = NULL;
+		queueCreateInfo.queueFamilyIndex = vulkanDevice->queueFamilyIndices.compute;
+		queueCreateInfo.queueCount = 1;
+		vkGetDeviceQueue(device, vulkanDevice->queueFamilyIndices.compute, 0, &compute.queue);
+
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			// Binding 0: Storage image (raytraced output)
+			vks::initializers::descriptorSetLayoutBinding(
+				VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
+				VK_SHADER_STAGE_COMPUTE_BIT,
+				0),
+			// Binding 1: Uniform buffer block
+			vks::initializers::descriptorSetLayoutBinding(
+				VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+				VK_SHADER_STAGE_COMPUTE_BIT,
+				1),
+			// Binding 1: Shader storage buffer for the spheres
+			vks::initializers::descriptorSetLayoutBinding(
+				VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
+				VK_SHADER_STAGE_COMPUTE_BIT,
+				2),
+			// Binding 1: Shader storage buffer for the planes
+			vks::initializers::descriptorSetLayoutBinding(
+				VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
+				VK_SHADER_STAGE_COMPUTE_BIT,
+				3)
+		};
+
+		VkDescriptorSetLayoutCreateInfo descriptorLayout =
+			vks::initializers::descriptorSetLayoutCreateInfo(
+				setLayoutBindings.data(),
+				setLayoutBindings.size());
+
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr,	&compute.descriptorSetLayout));
+
+		VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo =
+			vks::initializers::pipelineLayoutCreateInfo(
+				&compute.descriptorSetLayout,
+				1);
+
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &compute.pipelineLayout));
+
+		VkDescriptorSetAllocateInfo allocInfo =
+			vks::initializers::descriptorSetAllocateInfo(
+				descriptorPool,
+				&compute.descriptorSetLayout,
+				1);
+
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &compute.descriptorSet));
+
+		std::vector<VkWriteDescriptorSet> computeWriteDescriptorSets =
+		{
+			// Binding 0: Output storage image
+			vks::initializers::writeDescriptorSet(
+				compute.descriptorSet,
+				VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
+				0,
+				&textureComputeTarget.descriptor),
+			// Binding 1: Uniform buffer block
+			vks::initializers::writeDescriptorSet(
+				compute.descriptorSet,
+				VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+				1,
+				&compute.uniformBuffer.descriptor),
+			// Binding 2: Shader storage buffer for the spheres
+			vks::initializers::writeDescriptorSet(
+				compute.descriptorSet,
+				VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
+				2,
+				&compute.storageBuffers.spheres.descriptor),
+			// Binding 2: Shader storage buffer for the planes
+			vks::initializers::writeDescriptorSet(
+				compute.descriptorSet,
+				VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
+				3,
+				&compute.storageBuffers.planes.descriptor)
+		};
+
+		vkUpdateDescriptorSets(device, computeWriteDescriptorSets.size(), computeWriteDescriptorSets.data(), 0, NULL);
+
+		// Create compute shader pipelines
+		VkComputePipelineCreateInfo computePipelineCreateInfo =
+			vks::initializers::computePipelineCreateInfo(
+				compute.pipelineLayout,
+				0);
+
+		computePipelineCreateInfo.stage = loadShader(getShadersPath() + "computeraytracing/raytracing.comp.spv", VK_SHADER_STAGE_COMPUTE_BIT);
+		VK_CHECK_RESULT(vkCreateComputePipelines(device, pipelineCache, 1, &computePipelineCreateInfo, nullptr, &compute.pipeline));
+
+		// Separate command pool as queue family for compute may be different than graphics
+		VkCommandPoolCreateInfo cmdPoolInfo = {};
+		cmdPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
+		cmdPoolInfo.queueFamilyIndex = vulkanDevice->queueFamilyIndices.compute;
+		cmdPoolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
+		VK_CHECK_RESULT(vkCreateCommandPool(device, &cmdPoolInfo, nullptr, &compute.commandPool));
+
+		// Create a command buffer for compute operations
+		VkCommandBufferAllocateInfo cmdBufAllocateInfo =
+			vks::initializers::commandBufferAllocateInfo(
+				compute.commandPool,
+				VK_COMMAND_BUFFER_LEVEL_PRIMARY,
+				1);
+
+		VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, &compute.commandBuffer));
+
+		// Fence for compute CB sync
+		VkFenceCreateInfo fenceCreateInfo = vks::initializers::fenceCreateInfo(VK_FENCE_CREATE_SIGNALED_BIT);
+		VK_CHECK_RESULT(vkCreateFence(device, &fenceCreateInfo, nullptr, &compute.fence));
+
+		// Build a single command buffer containing the compute dispatch commands
+		buildComputeCommandBuffer();
+	}
+
+	// Prepare and initialize uniform buffer containing shader uniforms
+	void prepareUniformBuffers()
+	{
+		// Compute shader parameter uniform buffer block
+		vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&compute.uniformBuffer,
+			sizeof(compute.ubo));
+
+		updateUniformBuffers();
+	}
+
+	void updateUniformBuffers()
+	{
+		compute.ubo.lightPos.x = 0.0f + sin(glm::radians(timer * 360.0f)) * cos(glm::radians(timer * 360.0f)) * 2.0f;
+		compute.ubo.lightPos.y = 0.0f + sin(glm::radians(timer * 360.0f)) * 2.0f;
+		compute.ubo.lightPos.z = 0.0f + cos(glm::radians(timer * 360.0f)) * 2.0f;
+		compute.ubo.camera.pos = camera.position * -1.0f;
+		VK_CHECK_RESULT(compute.uniformBuffer.map());
+		memcpy(compute.uniformBuffer.mapped, &compute.ubo, sizeof(compute.ubo));
+		compute.uniformBuffer.unmap();
+	}
+
+	void draw()
+	{
+		// Submit compute commands
+		// Use a fence to ensure that compute command buffer has finished executing before using it again
+		vkWaitForFences(device, 1, &compute.fence, VK_TRUE, UINT64_MAX);
+		vkResetFences(device, 1, &compute.fence);
+
+		VkSubmitInfo computeSubmitInfo = vks::initializers::submitInfo();
+		computeSubmitInfo.commandBufferCount = 1;
+		computeSubmitInfo.pCommandBuffers = &compute.commandBuffer;
+
+		VK_CHECK_RESULT(vkQueueSubmit(compute.queue, 1, &computeSubmitInfo, compute.fence));
+		
+		VulkanExampleBase::prepareFrame();
+
+		// Command buffer to be submitted to the queue
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+
+		VulkanExampleBase::submitFrame();		
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		prepareStorageBuffers();
+		prepareUniformBuffers();
+		prepareTextureTarget(&textureComputeTarget, TEX_DIM, TEX_DIM, VK_FORMAT_R8G8B8A8_UNORM);
+		setupDescriptorSetLayout();
+		preparePipelines();
+		setupDescriptorPool();
+		setupDescriptorSet();
+		prepareCompute();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+		if (!paused)
+		{
+			updateUniformBuffers();
+		}
+	}
+
+	virtual void viewChanged()
+	{
+		compute.ubo.aspectRatio = (float)width / (float)height;
+		updateUniformBuffers();
+	}
+};
+
+VULKAN_EXAMPLE_MAIN()
diff --git a/external/Vulkan/examples/computeshader/computeshader.cpp b/external/Vulkan/examples/computeshader/computeshader.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..7e9b4d47bc55b26785c98372f752ed59a2dc4268
--- /dev/null
+++ b/external/Vulkan/examples/computeshader/computeshader.cpp
@@ -0,0 +1,667 @@
+/*
+* Vulkan Example - Compute shader image processing
+*
+* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkanexamplebase.h"
+
+#define VERTEX_BUFFER_BIND_ID 0
+#define ENABLE_VALIDATION false
+
+// Vertex layout for this example
+struct Vertex {
+	float pos[3];
+	float uv[2];
+};
+
+class VulkanExample : public VulkanExampleBase
+{
+private:
+	vks::Texture2D textureColorMap;
+	vks::Texture2D textureComputeTarget;
+public:
+	struct {
+		VkPipelineVertexInputStateCreateInfo inputState;
+		std::vector<VkVertexInputBindingDescription> bindingDescriptions;
+		std::vector<VkVertexInputAttributeDescription> attributeDescriptions;
+	} vertices;
+
+	// Resources for the graphics part of the example
+	struct {
+		VkDescriptorSetLayout descriptorSetLayout;	// Image display shader binding layout
+		VkDescriptorSet descriptorSetPreCompute;	// Image display shader bindings before compute shader image manipulation
+		VkDescriptorSet descriptorSetPostCompute;	// Image display shader bindings after compute shader image manipulation
+		VkPipeline pipeline;						// Image display pipeline
+		VkPipelineLayout pipelineLayout;			// Layout of the graphics pipeline
+		VkSemaphore semaphore;                      // Execution dependency between compute & graphic submission
+	} graphics;
+
+	// Resources for the compute part of the example
+	struct Compute {
+		VkQueue queue;								// Separate queue for compute commands (queue family may differ from the one used for graphics)
+		VkCommandPool commandPool;					// Use a separate command pool (queue family may differ from the one used for graphics)
+		VkCommandBuffer commandBuffer;				// Command buffer storing the dispatch commands and barriers
+		VkSemaphore semaphore;                      // Execution dependency between compute & graphic submission
+		VkDescriptorSetLayout descriptorSetLayout;	// Compute shader binding layout
+		VkDescriptorSet descriptorSet;				// Compute shader bindings
+		VkPipelineLayout pipelineLayout;			// Layout of the compute pipeline
+		std::vector<VkPipeline> pipelines;			// Compute pipelines for image filters
+		int32_t pipelineIndex = 0;					// Current image filtering compute pipeline index
+	} compute;
+
+	vks::Buffer vertexBuffer;
+	vks::Buffer indexBuffer;
+	uint32_t indexCount;
+
+	vks::Buffer uniformBufferVS;
+
+	struct {
+		glm::mat4 projection;
+		glm::mat4 modelView;
+	} uboVS;
+
+	int vertexBufferSize;
+
+	std::vector<std::string> shaderNames;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Compute shader image load/store";
+		camera.type = Camera::CameraType::lookat;
+		camera.setPosition(glm::vec3(0.0f, 0.0f, -2.0f));
+		camera.setRotation(glm::vec3(0.0f));
+		camera.setPerspective(60.0f, (float)width * 0.5f / (float)height, 1.0f, 256.0f);
+	}
+
+	~VulkanExample()
+	{
+		// Graphics
+		vkDestroyPipeline(device, graphics.pipeline, nullptr);
+		vkDestroyPipelineLayout(device, graphics.pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, graphics.descriptorSetLayout, nullptr);
+		vkDestroySemaphore(device, graphics.semaphore, nullptr);
+
+		// Compute
+		for (auto& pipeline : compute.pipelines)
+		{
+			vkDestroyPipeline(device, pipeline, nullptr);
+		}
+		vkDestroyPipelineLayout(device, compute.pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, compute.descriptorSetLayout, nullptr);
+		vkDestroySemaphore(device, compute.semaphore, nullptr);
+		vkDestroyCommandPool(device, compute.commandPool, nullptr);
+
+		vertexBuffer.destroy();
+		indexBuffer.destroy();
+		uniformBufferVS.destroy();
+
+		textureColorMap.destroy();
+		textureComputeTarget.destroy();
+	}
+
+	// Prepare a texture target that is used to store compute shader calculations
+	void prepareTextureTarget(vks::Texture *tex, uint32_t width, uint32_t height, VkFormat format)
+	{
+		VkFormatProperties formatProperties;
+
+		// Get device properties for the requested texture format
+		vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &formatProperties);
+		// Check if requested image format supports image storage operations
+		assert(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT);
+
+		// Prepare blit target texture
+		tex->width = width;
+		tex->height = height;
+
+		VkImageCreateInfo imageCreateInfo = vks::initializers::imageCreateInfo();
+		imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
+		imageCreateInfo.format = format;
+		imageCreateInfo.extent = { width, height, 1 };
+		imageCreateInfo.mipLevels = 1;
+		imageCreateInfo.arrayLayers = 1;
+		imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
+		imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
+		// Image will be sampled in the fragment shader and used as storage target in the compute shader
+		imageCreateInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT;
+		imageCreateInfo.flags = 0;
+		// If compute and graphics queue family indices differ, we create an image that can be shared between them
+		// This can result in worse performance than exclusive sharing mode, but save some synchronization to keep the sample simple
+		std::vector<uint32_t> queueFamilyIndices;
+		if (vulkanDevice->queueFamilyIndices.graphics != vulkanDevice->queueFamilyIndices.compute) {
+			queueFamilyIndices = {
+				vulkanDevice->queueFamilyIndices.graphics,
+				vulkanDevice->queueFamilyIndices.compute
+			};
+			imageCreateInfo.sharingMode = VK_SHARING_MODE_CONCURRENT;
+			imageCreateInfo.queueFamilyIndexCount = 2;
+			imageCreateInfo.pQueueFamilyIndices = queueFamilyIndices.data();
+		}
+
+		VkMemoryAllocateInfo memAllocInfo = vks::initializers::memoryAllocateInfo();
+		VkMemoryRequirements memReqs;
+
+		VK_CHECK_RESULT(vkCreateImage(device, &imageCreateInfo, nullptr, &tex->image));
+
+		vkGetImageMemoryRequirements(device, tex->image, &memReqs);
+		memAllocInfo.allocationSize = memReqs.size;
+		memAllocInfo.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+		VK_CHECK_RESULT(vkAllocateMemory(device, &memAllocInfo, nullptr, &tex->deviceMemory));
+		VK_CHECK_RESULT(vkBindImageMemory(device, tex->image, tex->deviceMemory, 0));
+
+		VkCommandBuffer layoutCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+
+		tex->imageLayout = VK_IMAGE_LAYOUT_GENERAL;
+		vks::tools::setImageLayout(
+			layoutCmd, tex->image,
+			VK_IMAGE_ASPECT_COLOR_BIT,
+			VK_IMAGE_LAYOUT_UNDEFINED,
+			tex->imageLayout);
+
+		vulkanDevice->flushCommandBuffer(layoutCmd, queue, true);
+
+		// Create sampler
+		VkSamplerCreateInfo sampler = vks::initializers::samplerCreateInfo();
+		sampler.magFilter = VK_FILTER_LINEAR;
+		sampler.minFilter = VK_FILTER_LINEAR;
+		sampler.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+		sampler.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
+		sampler.addressModeV = sampler.addressModeU;
+		sampler.addressModeW = sampler.addressModeU;
+		sampler.mipLodBias = 0.0f;
+		sampler.maxAnisotropy = 1.0f;
+		sampler.compareOp = VK_COMPARE_OP_NEVER;
+		sampler.minLod = 0.0f;
+		sampler.maxLod = tex->mipLevels;
+		sampler.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
+		VK_CHECK_RESULT(vkCreateSampler(device, &sampler, nullptr, &tex->sampler));
+
+		// Create image view
+		VkImageViewCreateInfo view = vks::initializers::imageViewCreateInfo();
+		view.image = VK_NULL_HANDLE;
+		view.viewType = VK_IMAGE_VIEW_TYPE_2D;
+		view.format = format;
+		view.components = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A };
+		view.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 };
+		view.image = tex->image;
+		VK_CHECK_RESULT(vkCreateImageView(device, &view, nullptr, &tex->view));
+
+		// Initialize a descriptor for later use
+		tex->descriptor.imageLayout = tex->imageLayout;
+		tex->descriptor.imageView = tex->view;
+		tex->descriptor.sampler = tex->sampler;
+		tex->device = vulkanDevice;
+	}
+
+	void loadAssets()
+	{
+		textureColorMap.loadFromFile(getAssetPath() + "textures/vulkan_11_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue, VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT, VK_IMAGE_LAYOUT_GENERAL);
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		clearValues[0].color = defaultClearColor;
+		clearValues[1].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.offset.x = 0;
+		renderPassBeginInfo.renderArea.offset.y = 0;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		renderPassBeginInfo.clearValueCount = 2;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			// Set target frame buffer
+			renderPassBeginInfo.framebuffer = frameBuffers[i];
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			// Image memory barrier to make sure that compute shader writes are finished before sampling from the texture
+			VkImageMemoryBarrier imageMemoryBarrier = {};
+			imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+			// We won't be changing the layout of the image
+			imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL;
+			imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_GENERAL;
+			imageMemoryBarrier.image = textureComputeTarget.image;
+			imageMemoryBarrier.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 };
+			imageMemoryBarrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
+			imageMemoryBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
+			imageMemoryBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+			imageMemoryBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+			vkCmdPipelineBarrier(
+				drawCmdBuffers[i],
+				VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
+				VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
+				VK_FLAGS_NONE,
+				0, nullptr,
+				0, nullptr,
+				1, &imageMemoryBarrier);
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			VkViewport viewport = vks::initializers::viewport((float)width * 0.5f, (float)height, 0.0f, 1.0f);
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+			VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+			VkDeviceSize offsets[1] = { 0 };
+			vkCmdBindVertexBuffers(drawCmdBuffers[i], VERTEX_BUFFER_BIND_ID, 1, &vertexBuffer.buffer, offsets);
+			vkCmdBindIndexBuffer(drawCmdBuffers[i], indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT32);
+
+			// Left (pre compute)
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphics.pipelineLayout, 0, 1, &graphics.descriptorSetPreCompute, 0, NULL);
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphics.pipeline);
+
+			vkCmdDrawIndexed(drawCmdBuffers[i], indexCount, 1, 0, 0, 0);
+
+			// Right (post compute)
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphics.pipelineLayout, 0, 1, &graphics.descriptorSetPostCompute, 0, NULL);
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphics.pipeline);
+
+			viewport.x = (float)width / 2.0f;
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+			vkCmdDrawIndexed(drawCmdBuffers[i], indexCount, 1, 0, 0, 0);
+
+			drawUI(drawCmdBuffers[i]);
+
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+
+	}
+
+	void buildComputeCommandBuffer()
+	{
+		// Flush the queue if we're rebuilding the command buffer after a pipeline change to ensure it's not currently in use
+		vkQueueWaitIdle(compute.queue);
+
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VK_CHECK_RESULT(vkBeginCommandBuffer(compute.commandBuffer, &cmdBufInfo));
+
+		vkCmdBindPipeline(compute.commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, compute.pipelines[compute.pipelineIndex]);
+		vkCmdBindDescriptorSets(compute.commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, compute.pipelineLayout, 0, 1, &compute.descriptorSet, 0, 0);
+
+		vkCmdDispatch(compute.commandBuffer, textureComputeTarget.width / 16, textureComputeTarget.height / 16, 1);
+
+		vkEndCommandBuffer(compute.commandBuffer);
+	}
+
+	// Setup vertices for a single uv-mapped quad
+	void generateQuad()
+	{
+		// Setup vertices for a single uv-mapped quad made from two triangles
+		std::vector<Vertex> vertices =
+		{
+			{ {  1.0f,  1.0f, 0.0f }, { 1.0f, 1.0f } },
+			{ { -1.0f,  1.0f, 0.0f }, { 0.0f, 1.0f } },
+			{ { -1.0f, -1.0f, 0.0f }, { 0.0f, 0.0f } },
+			{ {  1.0f, -1.0f, 0.0f }, { 1.0f, 0.0f } }
+		};
+
+		// Setup indices
+		std::vector<uint32_t> indices = { 0,1,2, 2,3,0 };
+		indexCount = static_cast<uint32_t>(indices.size());
+
+		// Create buffers
+		// For the sake of simplicity we won't stage the vertex data to the gpu memory
+		// Vertex buffer
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&vertexBuffer,
+			vertices.size() * sizeof(Vertex),
+			vertices.data()));
+		// Index buffer
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&indexBuffer,
+			indices.size() * sizeof(uint32_t),
+			indices.data()));
+	}
+
+	void setupVertexDescriptions()
+	{
+		// Binding description
+		vertices.bindingDescriptions = {
+			vks::initializers::vertexInputBindingDescription(VERTEX_BUFFER_BIND_ID, sizeof(Vertex), VK_VERTEX_INPUT_RATE_VERTEX)
+		};
+
+		// Attribute descriptions
+		// Describes memory layout and shader positions
+		vertices.attributeDescriptions = {
+			// Location 0: Position
+			vks::initializers::vertexInputAttributeDescription(VERTEX_BUFFER_BIND_ID, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, pos)),
+			// Location 1: Texture coordinates
+			vks::initializers::vertexInputAttributeDescription(VERTEX_BUFFER_BIND_ID, 1, VK_FORMAT_R32G32_SFLOAT, offsetof(Vertex, uv)),
+		};
+
+		// Assign to vertex buffer
+		vertices.inputState = vks::initializers::pipelineVertexInputStateCreateInfo();
+		vertices.inputState.vertexBindingDescriptionCount = vertices.bindingDescriptions.size();
+		vertices.inputState.pVertexBindingDescriptions = vertices.bindingDescriptions.data();
+		vertices.inputState.vertexAttributeDescriptionCount = vertices.attributeDescriptions.size();
+		vertices.inputState.pVertexAttributeDescriptions = vertices.attributeDescriptions.data();
+	}
+
+	void setupDescriptorPool()
+	{
+		std::vector<VkDescriptorPoolSize> poolSizes = {
+			// Graphics pipelines uniform buffers
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2),
+			// Graphics pipelines image samplers for displaying compute output image
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2),
+			// Compute pipelines uses a storage image for image reads and writes
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 2),
+		};
+		VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 3);
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+	}
+
+	void setupDescriptorSetLayout()
+	{
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			// Binding 0: Vertex shader uniform buffer
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0),
+			// Binding 1: Fragment shader input image
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1)
+		};
+
+		VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &graphics.descriptorSetLayout));
+
+		VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&graphics.descriptorSetLayout, 1);
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &graphics.pipelineLayout));
+	}
+
+	void setupDescriptorSet()
+	{
+		VkDescriptorSetAllocateInfo allocInfo =
+			vks::initializers::descriptorSetAllocateInfo(descriptorPool, &graphics.descriptorSetLayout, 1);
+
+		// Input image (before compute post processing)
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &graphics.descriptorSetPreCompute));
+		std::vector<VkWriteDescriptorSet> baseImageWriteDescriptorSets = {
+			vks::initializers::writeDescriptorSet(graphics.descriptorSetPreCompute, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBufferVS.descriptor),
+			vks::initializers::writeDescriptorSet(graphics.descriptorSetPreCompute, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &textureColorMap.descriptor)
+		};
+		vkUpdateDescriptorSets(device, baseImageWriteDescriptorSets.size(), baseImageWriteDescriptorSets.data(), 0, nullptr);
+
+		// Final image (after compute shader processing)
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &graphics.descriptorSetPostCompute));
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets = {
+			vks::initializers::writeDescriptorSet(graphics.descriptorSetPostCompute, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBufferVS.descriptor),
+			vks::initializers::writeDescriptorSet(graphics.descriptorSetPostCompute, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &textureComputeTarget.descriptor)
+		};
+		vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, nullptr);
+
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState =
+			vks::initializers::pipelineInputAssemblyStateCreateInfo(
+				VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
+				0,
+				VK_FALSE);
+
+		VkPipelineRasterizationStateCreateInfo rasterizationState =
+			vks::initializers::pipelineRasterizationStateCreateInfo(
+				VK_POLYGON_MODE_FILL,
+				VK_CULL_MODE_NONE,
+				VK_FRONT_FACE_COUNTER_CLOCKWISE,
+				0);
+
+		VkPipelineColorBlendAttachmentState blendAttachmentState =
+			vks::initializers::pipelineColorBlendAttachmentState(
+				0xf,
+				VK_FALSE);
+
+		VkPipelineColorBlendStateCreateInfo colorBlendState =
+			vks::initializers::pipelineColorBlendStateCreateInfo(
+				1,
+				&blendAttachmentState);
+
+		VkPipelineDepthStencilStateCreateInfo depthStencilState =
+			vks::initializers::pipelineDepthStencilStateCreateInfo(
+				VK_TRUE,
+				VK_TRUE,
+				VK_COMPARE_OP_LESS_OR_EQUAL);
+
+		VkPipelineViewportStateCreateInfo viewportState =
+			vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+
+		VkPipelineMultisampleStateCreateInfo multisampleState =
+			vks::initializers::pipelineMultisampleStateCreateInfo(
+				VK_SAMPLE_COUNT_1_BIT,
+				0);
+
+		std::vector<VkDynamicState> dynamicStateEnables = {
+			VK_DYNAMIC_STATE_VIEWPORT,
+			VK_DYNAMIC_STATE_SCISSOR
+		};
+		VkPipelineDynamicStateCreateInfo dynamicState =
+			vks::initializers::pipelineDynamicStateCreateInfo(
+				dynamicStateEnables.data(),
+				dynamicStateEnables.size(),
+				0);
+
+		// Rendering pipeline
+		// Load shaders
+		std::array<VkPipelineShaderStageCreateInfo,2> shaderStages;
+
+		shaderStages[0] = loadShader(getShadersPath() + "computeshader/texture.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "computeshader/texture.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+
+		VkGraphicsPipelineCreateInfo pipelineCreateInfo =
+			vks::initializers::pipelineCreateInfo(
+				graphics.pipelineLayout,
+				renderPass,
+				0);
+
+		pipelineCreateInfo.pVertexInputState = &vertices.inputState;
+		pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState;
+		pipelineCreateInfo.pRasterizationState = &rasterizationState;
+		pipelineCreateInfo.pColorBlendState = &colorBlendState;
+		pipelineCreateInfo.pMultisampleState = &multisampleState;
+		pipelineCreateInfo.pViewportState = &viewportState;
+		pipelineCreateInfo.pDepthStencilState = &depthStencilState;
+		pipelineCreateInfo.pDynamicState = &dynamicState;
+		pipelineCreateInfo.stageCount = shaderStages.size();
+		pipelineCreateInfo.pStages = shaderStages.data();
+		pipelineCreateInfo.renderPass = renderPass;
+
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &graphics.pipeline));
+	}
+
+	void prepareGraphics()
+	{
+		// Semaphore for compute & graphics sync
+		VkSemaphoreCreateInfo semaphoreCreateInfo = vks::initializers::semaphoreCreateInfo();
+		VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &graphics.semaphore));
+	
+		// Signal the semaphore
+		VkSubmitInfo submitInfo = vks::initializers::submitInfo();
+		submitInfo.signalSemaphoreCount = 1;
+		submitInfo.pSignalSemaphores = &graphics.semaphore;
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+		VK_CHECK_RESULT(vkQueueWaitIdle(queue));	
+	}
+
+	void prepareCompute()
+	{
+		// Get a compute queue from the device
+		vkGetDeviceQueue(device, vulkanDevice->queueFamilyIndices.compute, 0, &compute.queue);
+
+		// Create compute pipeline
+		// Compute pipelines are created separate from graphics pipelines even if they use the same queue
+
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			// Binding 0: Input image (read-only)
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_SHADER_STAGE_COMPUTE_BIT, 0),
+			// Binding 1: Output image (write)
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_SHADER_STAGE_COMPUTE_BIT, 1),
+		};
+
+		VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device,	&descriptorLayout, nullptr, &compute.descriptorSetLayout));
+
+		VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo =
+			vks::initializers::pipelineLayoutCreateInfo(&compute.descriptorSetLayout, 1);
+
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &compute.pipelineLayout));
+
+		VkDescriptorSetAllocateInfo allocInfo =
+			vks::initializers::descriptorSetAllocateInfo(descriptorPool, &compute.descriptorSetLayout, 1);
+
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &compute.descriptorSet));
+		std::vector<VkWriteDescriptorSet> computeWriteDescriptorSets = {
+			vks::initializers::writeDescriptorSet(compute.descriptorSet, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 0, &textureColorMap.descriptor),
+			vks::initializers::writeDescriptorSet(compute.descriptorSet, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, &textureComputeTarget.descriptor)
+		};
+		vkUpdateDescriptorSets(device, computeWriteDescriptorSets.size(), computeWriteDescriptorSets.data(), 0, NULL);
+
+		// Create compute shader pipelines
+		VkComputePipelineCreateInfo computePipelineCreateInfo =
+			vks::initializers::computePipelineCreateInfo(compute.pipelineLayout, 0);
+
+		// One pipeline for each effect
+		shaderNames = { "emboss", "edgedetect", "sharpen" };
+		for (auto& shaderName : shaderNames) {
+			std::string fileName = getShadersPath() + "computeshader/" + shaderName + ".comp.spv";
+			computePipelineCreateInfo.stage = loadShader(fileName, VK_SHADER_STAGE_COMPUTE_BIT);
+			VkPipeline pipeline;
+			VK_CHECK_RESULT(vkCreateComputePipelines(device, pipelineCache, 1, &computePipelineCreateInfo, nullptr, &pipeline));
+			compute.pipelines.push_back(pipeline);
+		}
+
+		// Separate command pool as queue family for compute may be different than graphics
+		VkCommandPoolCreateInfo cmdPoolInfo = {};
+		cmdPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
+		cmdPoolInfo.queueFamilyIndex = vulkanDevice->queueFamilyIndices.compute;
+		cmdPoolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
+		VK_CHECK_RESULT(vkCreateCommandPool(device, &cmdPoolInfo, nullptr, &compute.commandPool));
+
+		// Create a command buffer for compute operations
+		VkCommandBufferAllocateInfo cmdBufAllocateInfo =
+			vks::initializers::commandBufferAllocateInfo(
+				compute.commandPool,
+				VK_COMMAND_BUFFER_LEVEL_PRIMARY,
+				1);
+
+		VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, &compute.commandBuffer));
+
+		// Semaphore for compute & graphics sync
+		VkSemaphoreCreateInfo semaphoreCreateInfo = vks::initializers::semaphoreCreateInfo();
+		VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &compute.semaphore));
+
+		// Build a single command buffer containing the compute dispatch commands
+		buildComputeCommandBuffer();
+	}
+
+	// Prepare and initialize uniform buffer containing shader uniforms
+	void prepareUniformBuffers()
+	{
+		// Vertex shader uniform buffer block
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBufferVS,
+			sizeof(uboVS)));
+
+		// Map persistent
+		VK_CHECK_RESULT(uniformBufferVS.map());
+
+		updateUniformBuffers();
+	}
+
+	void updateUniformBuffers()
+	{
+		uboVS.projection = camera.matrices.perspective;
+		uboVS.modelView = camera.matrices.view;
+		memcpy(uniformBufferVS.mapped, &uboVS, sizeof(uboVS));
+	}
+
+	void draw()
+	{
+		// Wait for rendering finished
+		VkPipelineStageFlags waitStageMask = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
+
+		// Submit compute commands
+		VkSubmitInfo computeSubmitInfo = vks::initializers::submitInfo();
+		computeSubmitInfo.commandBufferCount = 1;
+		computeSubmitInfo.pCommandBuffers = &compute.commandBuffer;
+		computeSubmitInfo.waitSemaphoreCount = 1;
+		computeSubmitInfo.pWaitSemaphores = &graphics.semaphore;
+		computeSubmitInfo.pWaitDstStageMask = &waitStageMask;
+		computeSubmitInfo.signalSemaphoreCount = 1;
+		computeSubmitInfo.pSignalSemaphores = &compute.semaphore;
+		VK_CHECK_RESULT(vkQueueSubmit(compute.queue, 1, &computeSubmitInfo, VK_NULL_HANDLE));	
+		VulkanExampleBase::prepareFrame();
+
+		VkPipelineStageFlags graphicsWaitStageMasks[] = { VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
+		VkSemaphore graphicsWaitSemaphores[] = { compute.semaphore, semaphores.presentComplete };
+		VkSemaphore graphicsSignalSemaphores[] = { graphics.semaphore, semaphores.renderComplete };
+
+		// Submit graphics commands
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+		submitInfo.waitSemaphoreCount = 2;
+		submitInfo.pWaitSemaphores = graphicsWaitSemaphores;
+		submitInfo.pWaitDstStageMask = graphicsWaitStageMasks;
+		submitInfo.signalSemaphoreCount = 2;
+		submitInfo.pSignalSemaphores = graphicsSignalSemaphores;
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+
+		VulkanExampleBase::submitFrame();
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		loadAssets();
+		generateQuad();
+		setupVertexDescriptions();
+		prepareUniformBuffers();
+		prepareTextureTarget(&textureComputeTarget, textureColorMap.width, textureColorMap.height, VK_FORMAT_R8G8B8A8_UNORM);
+		setupDescriptorSetLayout();
+		preparePipelines();
+		setupDescriptorPool();
+		setupDescriptorSet();
+		prepareGraphics();
+		prepareCompute();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+		if (camera.updated) {
+			updateUniformBuffers();
+		}
+	}
+
+	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
+	{
+		if (overlay->header("Settings")) {
+			if (overlay->comboBox("Shader", &compute.pipelineIndex, shaderNames)) {
+				buildComputeCommandBuffer();
+			}
+		}
+	}
+};
+
+VULKAN_EXAMPLE_MAIN()
diff --git a/external/Vulkan/examples/conditionalrender/conditionalrender.cpp b/external/Vulkan/examples/conditionalrender/conditionalrender.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3c8195e9377d911d8d9aa393ec7ff00e19c59f3d
--- /dev/null
+++ b/external/Vulkan/examples/conditionalrender/conditionalrender.cpp
@@ -0,0 +1,368 @@
+/*
+* Vulkan Example - Conditional rendering
+*
+* Note: Requires a device that supports the VK_EXT_conditional_rendering extension
+*
+* With conditional rendering it's possible to execute certain rendering commands based on a buffer value instead of having to rebuild the command buffers.
+* This example sets up a conditional buffer with one value per glTF part, that is used to toggle visibility of single model parts.
+*
+* Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkanexamplebase.h"
+#include "VulkanglTFModel.h"
+
+#define ENABLE_VALIDATION false
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	PFN_vkCmdBeginConditionalRenderingEXT vkCmdBeginConditionalRenderingEXT;
+	PFN_vkCmdEndConditionalRenderingEXT vkCmdEndConditionalRenderingEXT;
+
+	vkglTF::Model scene;
+
+	struct {
+		glm::mat4 projection;
+		glm::mat4 view;
+		glm::mat4 model;
+	} uboVS;
+
+	vks::Buffer uniformBuffer;
+
+	std::vector<int32_t> conditionalVisibility;
+	vks::Buffer conditionalBuffer;
+
+	VkPipelineLayout pipelineLayout;
+	VkPipeline pipeline;
+	VkDescriptorSetLayout descriptorSetLayout;
+	VkDescriptorSet descriptorSet;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Conditional rendering";
+		camera.type = Camera::CameraType::lookat;
+		camera.setPerspective(45.0f, (float)width / (float)height, 0.1f, 512.0f);
+		camera.setRotation(glm::vec3(-2.25f, -52.0f, 0.0f));
+		camera.setTranslation(glm::vec3(1.9f, -2.05f, -18.0f));
+		camera.rotationSpeed *= 0.25f;
+
+		/*
+			[POI] Enable extension required for conditional rendering
+		*/
+		enabledDeviceExtensions.push_back(VK_EXT_CONDITIONAL_RENDERING_EXTENSION_NAME);
+	}
+
+	~VulkanExample()
+	{
+		vkDestroyPipeline(device, pipeline, nullptr);
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+		uniformBuffer.destroy();
+		conditionalBuffer.destroy();
+	}
+
+	void renderNode(vkglTF::Node *node, VkCommandBuffer commandBuffer) {
+		if (node->mesh) {
+			for (vkglTF::Primitive * primitive : node->mesh->primitives) {
+				const std::vector<VkDescriptorSet> descriptorsets = {
+					descriptorSet,
+					node->mesh->uniformBuffer.descriptorSet
+				};
+				vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, static_cast<uint32_t>(descriptorsets.size()), descriptorsets.data(), 0, NULL);
+
+				struct PushBlock {
+					glm::vec4 baseColorFactor;
+				} pushBlock;
+				pushBlock.baseColorFactor = primitive->material.baseColorFactor;
+
+				vkCmdPushConstants(commandBuffer, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(PushBlock), &pushBlock);
+
+				/*
+					[POI] Setup the conditional rendering
+				*/
+				VkConditionalRenderingBeginInfoEXT conditionalRenderingBeginInfo{};
+				conditionalRenderingBeginInfo.sType = VK_STRUCTURE_TYPE_CONDITIONAL_RENDERING_BEGIN_INFO_EXT;
+				conditionalRenderingBeginInfo.buffer = conditionalBuffer.buffer;
+				conditionalRenderingBeginInfo.offset = sizeof(int32_t) * node->index;
+
+				/*
+					[POI] Begin conditionally rendered section
+
+					If the value from the conditional rendering buffer at the given offset is != 0, the draw commands will be executed
+				*/
+				vkCmdBeginConditionalRenderingEXT(commandBuffer, &conditionalRenderingBeginInfo);
+
+				vkCmdDrawIndexed(commandBuffer, primitive->indexCount, 1, primitive->firstIndex, 0, 0);
+
+				vkCmdEndConditionalRenderingEXT(commandBuffer);
+			}
+
+		};
+		for (auto child : node->children) {
+			renderNode(child, commandBuffer);
+		}
+	}
+
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		clearValues[0].color = { { 1.0f, 1.0f, 1.0f, 1.0f } };
+		clearValues[1].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.offset.x = 0;
+		renderPassBeginInfo.renderArea.offset.y = 0;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		renderPassBeginInfo.clearValueCount = 2;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i) {
+			renderPassBeginInfo.framebuffer = frameBuffers[i];
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+			VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL);
+
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
+
+			const VkDeviceSize offsets[1] = { 0 };
+			vkCmdBindVertexBuffers(drawCmdBuffers[i], 0, 1, &scene.vertices.buffer, offsets);
+			vkCmdBindIndexBuffer(drawCmdBuffers[i], scene.indices.buffer, 0, VK_INDEX_TYPE_UINT32);
+			for (auto node : scene.nodes) {
+				renderNode(node, drawCmdBuffers[i]);
+			}
+
+			drawUI(drawCmdBuffers[i]);
+
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void loadAssets()
+	{
+		scene.loadFromFile(getAssetPath() + "models/gltf/glTF-Embedded/Buggy.gltf", vulkanDevice, queue);
+	}
+
+	void setupDescriptorSets()
+	{
+		std::vector<VkDescriptorPoolSize> poolSizes = {
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1),
+		};
+		VkDescriptorPoolCreateInfo descriptorPoolCI = vks::initializers::descriptorPoolCreateInfo(poolSizes.size(), poolSizes.data(), 1);
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolCI, nullptr, &descriptorPool));
+
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0),
+		};
+		VkDescriptorSetLayoutCreateInfo descriptorLayoutCI{};
+		descriptorLayoutCI.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
+		descriptorLayoutCI.bindingCount = static_cast<uint32_t>(setLayoutBindings.size());
+		descriptorLayoutCI.pBindings = setLayoutBindings.data();
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayoutCI, nullptr, &descriptorSetLayout));
+
+		std::array<VkDescriptorSetLayout, 2> setLayouts = {
+			descriptorSetLayout, vkglTF::descriptorSetLayoutUbo
+		};
+		VkPipelineLayoutCreateInfo pipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(setLayouts.data(), 2);
+		VkPushConstantRange pushConstantRange = vks::initializers::pushConstantRange(VK_SHADER_STAGE_VERTEX_BIT, sizeof(glm::vec4) * 2,	0);
+		pipelineLayoutCI.pushConstantRangeCount = 1;
+		pipelineLayoutCI.pPushConstantRanges = &pushConstantRange;
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelineLayout));
+
+		VkDescriptorSetAllocateInfo descriptorSetAllocateInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1);
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &descriptorSetAllocateInfo, &descriptorSet));
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets = {
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffer.descriptor)
+		};
+		vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, NULL);
+	}
+
+	void preparePipelines()
+	{
+		const std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
+
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyStateCI = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationStateCI = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendStateCI = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilStateCI = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportStateCI = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+		VkPipelineMultisampleStateCreateInfo multisampleStateCI = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
+		VkPipelineDynamicStateCreateInfo dynamicStateCI = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables.data(), static_cast<uint32_t>(dynamicStateEnables.size()), 0);
+
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass, 0);
+		pipelineCI.pInputAssemblyState = &inputAssemblyStateCI;
+		pipelineCI.pRasterizationState = &rasterizationStateCI;
+		pipelineCI.pColorBlendState = &colorBlendStateCI;
+		pipelineCI.pMultisampleState = &multisampleStateCI;
+		pipelineCI.pViewportState = &viewportStateCI;
+		pipelineCI.pDepthStencilState = &depthStencilStateCI;
+		pipelineCI.pDynamicState = &dynamicStateCI;
+		pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::UV });
+
+		const std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages = {
+			loadShader(getShadersPath() + "conditionalrender/model.vert.spv", VK_SHADER_STAGE_VERTEX_BIT),
+			loadShader(getShadersPath() + "conditionalrender/model.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT)
+		};
+
+		pipelineCI.stageCount = static_cast<uint32_t>(shaderStages.size());
+		pipelineCI.pStages = shaderStages.data();
+
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipeline));
+	}
+
+	void prepareUniformBuffers()
+	{
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffer,
+			sizeof(uboVS)));
+		VK_CHECK_RESULT(uniformBuffer.map());
+		updateUniformBuffers();
+	}
+
+	void updateUniformBuffers()
+	{
+		uboVS.projection = camera.matrices.perspective;
+		uboVS.view = glm::scale(camera.matrices.view, glm::vec3(0.1f , -0.1f, 0.1f));
+		uboVS.model = glm::translate(glm::mat4(1.0f), scene.dimensions.min);
+		memcpy(uniformBuffer.mapped, &uboVS, sizeof(uboVS));
+	}
+
+	void updateConditionalBuffer()
+	{
+		memcpy(conditionalBuffer.mapped, conditionalVisibility.data(), sizeof(int32_t) * conditionalVisibility.size());
+	}
+
+	/*
+		[POI] Extension specific setup
+
+		Gets the function pointers required for conditional rendering
+		Sets up a dedicated conditional buffer that is used to determine visibility at draw time
+	*/
+	void prepareConditionalRendering()
+	{
+		/*
+			The conditional rendering functions are part of an extension so they have to be loaded manually
+		*/
+		vkCmdBeginConditionalRenderingEXT = (PFN_vkCmdBeginConditionalRenderingEXT)vkGetDeviceProcAddr(device, "vkCmdBeginConditionalRenderingEXT");
+		if (!vkCmdBeginConditionalRenderingEXT) {
+			vks::tools::exitFatal("Could not get a valid function pointer for vkCmdBeginConditionalRenderingEXT", -1);
+		}
+
+		vkCmdEndConditionalRenderingEXT = (PFN_vkCmdEndConditionalRenderingEXT)vkGetDeviceProcAddr(device, "vkCmdEndConditionalRenderingEXT");
+		if (!vkCmdEndConditionalRenderingEXT) {
+			vks::tools::exitFatal("Could not get a valid function pointer for vkCmdEndConditionalRenderingEXT", -1);
+		}
+
+		/*
+			Create the buffer that contains the conditional rendering information
+
+			A single conditional value is 32 bits and if it's zero the rendering commands are discarded
+			This sample renders multiple rows of objects conditionally, so we setup a buffer with one value per row
+		*/
+		conditionalVisibility.resize(scene.linearNodes.size());
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_CONDITIONAL_RENDERING_BIT_EXT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&conditionalBuffer,
+			sizeof(int32_t) *conditionalVisibility.size(),
+			conditionalVisibility.data()));
+		VK_CHECK_RESULT(conditionalBuffer.map());
+
+		// By default, all parts of the glTF are visible
+		for (auto i = 0; i < conditionalVisibility.size(); i++) {
+			conditionalVisibility[i] = 1;
+		}
+
+		/*
+			Copy visibility data
+		*/
+		updateConditionalBuffer();
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+		VulkanExampleBase::submitFrame();
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		loadAssets();
+		prepareConditionalRendering();
+		prepareUniformBuffers();
+		setupDescriptorSets();
+		preparePipelines();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+		if (camera.updated) {
+			updateUniformBuffers();
+		}
+	}
+
+	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
+	{
+		if (overlay->header("Visibility")) {
+
+			if (overlay->button("All")) {
+				for (auto i = 0; i < conditionalVisibility.size(); i++) {
+					conditionalVisibility[i] = 1;
+				}
+				updateConditionalBuffer();
+			}
+			ImGui::SameLine();
+			if (overlay->button("None")) {
+				for (auto i = 0; i < conditionalVisibility.size(); i++) {
+					conditionalVisibility[i] = 0;
+				}
+				updateConditionalBuffer();
+			}
+			ImGui::NewLine();
+
+			ImGui::BeginChild("InnerRegion", ImVec2(200.0f, 400.0f), false);
+			for (auto node : scene.linearNodes) {
+				// Add visibility toggle checkboxes for all model nodes with a mesh
+				if (node->mesh) {
+					if (overlay->checkBox(("[" + std::to_string(node->index) + "] " + node->mesh->name).c_str(), &conditionalVisibility[node->index])) {
+						updateConditionalBuffer();
+					}
+				}
+			}
+			ImGui::EndChild();
+
+		}
+	}
+
+};
+
+VULKAN_EXAMPLE_MAIN()
\ No newline at end of file
diff --git a/external/Vulkan/examples/conservativeraster/conservativeraster.cpp b/external/Vulkan/examples/conservativeraster/conservativeraster.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..19cc838c9e62465e0a66b9bd78f29f86394fc338
--- /dev/null
+++ b/external/Vulkan/examples/conservativeraster/conservativeraster.cpp
@@ -0,0 +1,716 @@
+/*
+* Vulkan Example - Conservative rasterization
+*
+* Note: Requires a device that supports the VK_EXT_conservative_rasterization extension
+*
+* Uses an offscreen buffer with lower resolution to demonstrate the effect of conservative rasterization
+*
+* Copyright by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkanexamplebase.h"
+
+#define ENABLE_VALIDATION false
+
+#define FB_COLOR_FORMAT VK_FORMAT_R8G8B8A8_UNORM
+#define ZOOM_FACTOR 16
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	// Fetch and store conservative rasterization state props for display purposes
+	VkPhysicalDeviceConservativeRasterizationPropertiesEXT conservativeRasterProps{};
+
+	bool conservativeRasterEnabled = true;
+
+	struct Vertex {
+		float position[3];
+		float color[3];
+	};
+
+	struct Triangle {
+		vks::Buffer vertices;
+		vks::Buffer indices;
+		uint32_t indexCount;
+	} triangle;
+
+	vks::Buffer uniformBuffer;
+
+	struct UniformBuffers {
+		vks::Buffer scene;
+	} uniformBuffers;
+
+	struct UboScene {
+		glm::mat4 projection;
+		glm::mat4 model;
+	} uboScene;
+
+	struct PipelineLayouts {
+		VkPipelineLayout scene;
+		VkPipelineLayout fullscreen;
+	} pipelineLayouts;
+
+	struct Pipelines {
+		VkPipeline triangle;
+		VkPipeline triangleConservativeRaster;
+		VkPipeline triangleOverlay;
+		VkPipeline fullscreen;
+	} pipelines;
+
+	struct DescriptorSetLayouts {
+		VkDescriptorSetLayout scene;
+		VkDescriptorSetLayout fullscreen;
+	} descriptorSetLayouts;
+
+	struct DescriptorSets {
+		VkDescriptorSet scene;
+		VkDescriptorSet fullscreen;
+	} descriptorSets;
+
+	// Framebuffer for offscreen rendering
+	struct FrameBufferAttachment {
+		VkImage image;
+		VkDeviceMemory mem;
+		VkImageView view;
+	};
+	struct OffscreenPass {
+		int32_t width, height;
+		VkFramebuffer frameBuffer;
+		FrameBufferAttachment color, depth;
+		VkRenderPass renderPass;
+		VkSampler sampler;
+		VkDescriptorImageInfo descriptor;
+	} offscreenPass;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Conservative rasterization";
+
+		camera.type = Camera::CameraType::lookat;
+		camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 512.0f);
+		camera.setRotation(glm::vec3(0.0f));
+		camera.setTranslation(glm::vec3(0.0f, 0.0f, -2.0f));
+
+		// Enable extension required for conservative rasterization
+		enabledDeviceExtensions.push_back(VK_EXT_CONSERVATIVE_RASTERIZATION_EXTENSION_NAME);
+
+		// Reading device properties of conservative rasterization requires VK_KHR_get_physical_device_properties2 to be enabled
+		enabledInstanceExtensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
+	}
+
+	~VulkanExample()
+	{
+		// Frame buffer
+
+		vkDestroyImageView(device, offscreenPass.color.view, nullptr);
+		vkDestroyImage(device, offscreenPass.color.image, nullptr);
+		vkFreeMemory(device, offscreenPass.color.mem, nullptr);
+		vkDestroyImageView(device, offscreenPass.depth.view, nullptr);
+		vkDestroyImage(device, offscreenPass.depth.image, nullptr);
+		vkFreeMemory(device, offscreenPass.depth.mem, nullptr);
+
+		vkDestroyRenderPass(device, offscreenPass.renderPass, nullptr);
+		vkDestroySampler(device, offscreenPass.sampler, nullptr);
+		vkDestroyFramebuffer(device, offscreenPass.frameBuffer, nullptr);
+
+		vkDestroyPipeline(device, pipelines.triangle, nullptr);
+		vkDestroyPipeline(device, pipelines.triangleOverlay, nullptr);
+		vkDestroyPipeline(device, pipelines.triangleConservativeRaster, nullptr);
+		vkDestroyPipeline(device, pipelines.fullscreen, nullptr);
+
+		vkDestroyPipelineLayout(device, pipelineLayouts.fullscreen, nullptr);
+		vkDestroyPipelineLayout(device, pipelineLayouts.scene, nullptr);
+
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.scene, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.fullscreen, nullptr);
+
+		uniformBuffers.scene.destroy();
+		triangle.vertices.destroy();
+		triangle.indices.destroy();
+	}
+
+	void getEnabledFeatures()
+	{
+		enabledFeatures.fillModeNonSolid = deviceFeatures.fillModeNonSolid;
+		enabledFeatures.wideLines = deviceFeatures.wideLines;
+	}
+
+	/*
+		Setup offscreen framebuffer, attachments and render passes for lower resolution rendering of the scene
+	*/
+	void prepareOffscreen()
+	{
+		offscreenPass.width = width / ZOOM_FACTOR;
+		offscreenPass.height = height / ZOOM_FACTOR;
+
+		// Find a suitable depth format
+		VkFormat fbDepthFormat;
+		VkBool32 validDepthFormat = vks::tools::getSupportedDepthFormat(physicalDevice, &fbDepthFormat);
+		assert(validDepthFormat);
+
+		// Color attachment
+		VkImageCreateInfo image = vks::initializers::imageCreateInfo();
+		image.imageType = VK_IMAGE_TYPE_2D;
+		image.format = FB_COLOR_FORMAT;
+		image.extent.width = offscreenPass.width;
+		image.extent.height = offscreenPass.height;
+		image.extent.depth = 1;
+		image.mipLevels = 1;
+		image.arrayLayers = 1;
+		image.samples = VK_SAMPLE_COUNT_1_BIT;
+		image.tiling = VK_IMAGE_TILING_OPTIMAL;
+		// We will sample directly from the color attachment
+		image.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
+
+		VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo();
+		VkMemoryRequirements memReqs;
+
+		VK_CHECK_RESULT(vkCreateImage(device, &image, nullptr, &offscreenPass.color.image));
+		vkGetImageMemoryRequirements(device, offscreenPass.color.image, &memReqs);
+		memAlloc.allocationSize = memReqs.size;
+		memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+		VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &offscreenPass.color.mem));
+		VK_CHECK_RESULT(vkBindImageMemory(device, offscreenPass.color.image, offscreenPass.color.mem, 0));
+
+		VkImageViewCreateInfo colorImageView = vks::initializers::imageViewCreateInfo();
+		colorImageView.viewType = VK_IMAGE_VIEW_TYPE_2D;
+		colorImageView.format = FB_COLOR_FORMAT;
+		colorImageView.subresourceRange = {};
+		colorImageView.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+		colorImageView.subresourceRange.baseMipLevel = 0;
+		colorImageView.subresourceRange.levelCount = 1;
+		colorImageView.subresourceRange.baseArrayLayer = 0;
+		colorImageView.subresourceRange.layerCount = 1;
+		colorImageView.image = offscreenPass.color.image;
+		VK_CHECK_RESULT(vkCreateImageView(device, &colorImageView, nullptr, &offscreenPass.color.view));
+
+		// Create sampler to sample from the attachment in the fragment shader
+		VkSamplerCreateInfo samplerInfo = vks::initializers::samplerCreateInfo();
+		samplerInfo.magFilter = VK_FILTER_NEAREST;
+		samplerInfo.minFilter = VK_FILTER_NEAREST;
+		samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+		samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+		samplerInfo.addressModeV = samplerInfo.addressModeU;
+		samplerInfo.addressModeW = samplerInfo.addressModeU;
+		samplerInfo.mipLodBias = 0.0f;
+		samplerInfo.maxAnisotropy = 1.0f;
+		samplerInfo.minLod = 0.0f;
+		samplerInfo.maxLod = 1.0f;
+		samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
+		VK_CHECK_RESULT(vkCreateSampler(device, &samplerInfo, nullptr, &offscreenPass.sampler));
+
+		// Depth stencil attachment
+		image.format = fbDepthFormat;
+		image.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
+
+		VK_CHECK_RESULT(vkCreateImage(device, &image, nullptr, &offscreenPass.depth.image));
+		vkGetImageMemoryRequirements(device, offscreenPass.depth.image, &memReqs);
+		memAlloc.allocationSize = memReqs.size;
+		memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+		VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &offscreenPass.depth.mem));
+		VK_CHECK_RESULT(vkBindImageMemory(device, offscreenPass.depth.image, offscreenPass.depth.mem, 0));
+
+		VkImageViewCreateInfo depthStencilView = vks::initializers::imageViewCreateInfo();
+		depthStencilView.viewType = VK_IMAGE_VIEW_TYPE_2D;
+		depthStencilView.format = fbDepthFormat;
+		depthStencilView.flags = 0;
+		depthStencilView.subresourceRange = {};
+		depthStencilView.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
+		depthStencilView.subresourceRange.baseMipLevel = 0;
+		depthStencilView.subresourceRange.levelCount = 1;
+		depthStencilView.subresourceRange.baseArrayLayer = 0;
+		depthStencilView.subresourceRange.layerCount = 1;
+		depthStencilView.image = offscreenPass.depth.image;
+		VK_CHECK_RESULT(vkCreateImageView(device, &depthStencilView, nullptr, &offscreenPass.depth.view));
+
+		// Create a separate render pass for the offscreen rendering as it may differ from the one used for scene rendering
+
+		std::array<VkAttachmentDescription, 2> attchmentDescriptions = {};
+		// Color attachment
+		attchmentDescriptions[0].format = FB_COLOR_FORMAT;
+		attchmentDescriptions[0].samples = VK_SAMPLE_COUNT_1_BIT;
+		attchmentDescriptions[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+		attchmentDescriptions[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+		attchmentDescriptions[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+		attchmentDescriptions[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+		attchmentDescriptions[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+		attchmentDescriptions[0].finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+		// Depth attachment
+		attchmentDescriptions[1].format = fbDepthFormat;
+		attchmentDescriptions[1].samples = VK_SAMPLE_COUNT_1_BIT;
+		attchmentDescriptions[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+		attchmentDescriptions[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+		attchmentDescriptions[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+		attchmentDescriptions[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+		attchmentDescriptions[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+		attchmentDescriptions[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+
+		VkAttachmentReference colorReference = { 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
+		VkAttachmentReference depthReference = { 1, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL };
+
+		VkSubpassDescription subpassDescription = {};
+		subpassDescription.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+		subpassDescription.colorAttachmentCount = 1;
+		subpassDescription.pColorAttachments = &colorReference;
+		subpassDescription.pDepthStencilAttachment = &depthReference;
+
+		// Use subpass dependencies for layout transitions
+		std::array<VkSubpassDependency, 2> dependencies;
+
+		dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
+		dependencies[0].dstSubpass = 0;
+		dependencies[0].srcStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
+		dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+		dependencies[0].srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
+		dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+		dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+		dependencies[1].srcSubpass = 0;
+		dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
+		dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+		dependencies[1].dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
+		dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+		dependencies[1].dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
+		dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+		// Create the actual renderpass
+		VkRenderPassCreateInfo renderPassInfo = {};
+		renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
+		renderPassInfo.attachmentCount = static_cast<uint32_t>(attchmentDescriptions.size());
+		renderPassInfo.pAttachments = attchmentDescriptions.data();
+		renderPassInfo.subpassCount = 1;
+		renderPassInfo.pSubpasses = &subpassDescription;
+		renderPassInfo.dependencyCount = static_cast<uint32_t>(dependencies.size());
+		renderPassInfo.pDependencies = dependencies.data();
+
+		VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassInfo, nullptr, &offscreenPass.renderPass));
+
+		VkImageView attachments[2];
+		attachments[0] = offscreenPass.color.view;
+		attachments[1] = offscreenPass.depth.view;
+
+		VkFramebufferCreateInfo fbufCreateInfo = vks::initializers::framebufferCreateInfo();
+		fbufCreateInfo.renderPass = offscreenPass.renderPass;
+		fbufCreateInfo.attachmentCount = 2;
+		fbufCreateInfo.pAttachments = attachments;
+		fbufCreateInfo.width = offscreenPass.width;
+		fbufCreateInfo.height = offscreenPass.height;
+		fbufCreateInfo.layers = 1;
+
+		VK_CHECK_RESULT(vkCreateFramebuffer(device, &fbufCreateInfo, nullptr, &offscreenPass.frameBuffer));
+
+		// Fill a descriptor for later use in a descriptor set
+		offscreenPass.descriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+		offscreenPass.descriptor.imageView = offscreenPass.color.view;
+		offscreenPass.descriptor.sampler = offscreenPass.sampler;
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i) {
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			/*
+				First render pass: Render a low res triangle to an offscreen framebuffer to use for visualization in second pass
+			*/
+			{
+				VkClearValue clearValues[2];
+				clearValues[0].color = { { 0.25f, 0.25f, 0.25f, 0.0f } };
+				clearValues[1].depthStencil = { 1.0f, 0 };
+
+				VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+				renderPassBeginInfo.renderPass = offscreenPass.renderPass;
+				renderPassBeginInfo.framebuffer = offscreenPass.frameBuffer;
+				renderPassBeginInfo.renderArea.extent.width = offscreenPass.width;
+				renderPassBeginInfo.renderArea.extent.height = offscreenPass.height;
+				renderPassBeginInfo.clearValueCount = 2;
+				renderPassBeginInfo.pClearValues = clearValues;
+
+				VkViewport viewport = vks::initializers::viewport((float)offscreenPass.width, (float)offscreenPass.height, 0.0f, 1.0f);
+				vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+				VkRect2D scissor = vks::initializers::rect2D(offscreenPass.width, offscreenPass.height, 0, 0);
+				vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+				vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+				vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayouts.scene, 0, 1, &descriptorSets.scene, 0, nullptr);
+				vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, conservativeRasterEnabled ? pipelines.triangleConservativeRaster : pipelines.triangle);
+
+				VkDeviceSize offsets[1] = { 0 };
+				vkCmdBindVertexBuffers(drawCmdBuffers[i], 0, 1, &triangle.vertices.buffer, offsets);
+				vkCmdBindIndexBuffer(drawCmdBuffers[i], triangle.indices.buffer, 0, VK_INDEX_TYPE_UINT32);
+				vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+				vkCmdDrawIndexed(drawCmdBuffers[i], triangle.indexCount, 1, 0, 0, 0);
+
+				vkCmdEndRenderPass(drawCmdBuffers[i]);
+			}
+
+			/*
+				Note: Explicit synchronization is not required between the render pass, as this is done implicit via sub pass dependencies
+			*/
+
+			/*
+				Second render pass: Render scene with conservative rasterization
+			*/
+			{
+				VkClearValue clearValues[2];
+				clearValues[0].color = { { 0.25f, 0.25f, 0.25f, 0.25f } };
+				clearValues[1].depthStencil = { 1.0f, 0 };
+
+				VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+				renderPassBeginInfo.framebuffer = frameBuffers[i];
+				renderPassBeginInfo.renderPass = renderPass;
+				renderPassBeginInfo.renderArea.offset.x = 0;
+				renderPassBeginInfo.renderArea.offset.y = 0;
+				renderPassBeginInfo.renderArea.extent.width = width;
+				renderPassBeginInfo.renderArea.extent.height = height;
+				renderPassBeginInfo.clearValueCount = 2;
+				renderPassBeginInfo.pClearValues = clearValues;
+
+				vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+				VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+				vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+				VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
+				vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+				// Low-res triangle from offscreen framebuffer
+				vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.fullscreen);
+				vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayouts.fullscreen, 0, 1, &descriptorSets.fullscreen, 0, nullptr);
+				vkCmdDraw(drawCmdBuffers[i], 3, 1, 0, 0);
+
+				// Overlay actual triangle
+				VkDeviceSize offsets[1] = { 0 };
+				vkCmdBindVertexBuffers(drawCmdBuffers[i], 0, 1, &triangle.vertices.buffer, offsets);
+				vkCmdBindIndexBuffer(drawCmdBuffers[i], triangle.indices.buffer, 0, VK_INDEX_TYPE_UINT32);
+				vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.triangleOverlay);
+				vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayouts.scene, 0, 1, &descriptorSets.scene, 0, nullptr);
+				vkCmdDraw(drawCmdBuffers[i], 3, 1, 0, 0);
+
+				drawUI(drawCmdBuffers[i]);
+
+				vkCmdEndRenderPass(drawCmdBuffers[i]);
+			}
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void loadAssets()
+	{
+		// Create a single triangle
+		struct Vertex {
+			float position[3];
+			float color[3];
+		};
+
+		std::vector<Vertex> vertexBuffer = {
+			{ {  1.0f,  1.0f, 0.0f }, { 1.0f, 0.0f, 0.0f } },
+			{ { -1.0f,  1.0f, 0.0f }, { 0.0f, 1.0f, 0.0f } },
+			{ {  0.0f, -1.0f, 0.0f }, { 0.0f, 0.0f, 1.0f } }
+		};
+		uint32_t vertexBufferSize = static_cast<uint32_t>(vertexBuffer.size()) * sizeof(Vertex);
+		std::vector<uint32_t> indexBuffer = { 0, 1, 2 };
+		triangle.indexCount = static_cast<uint32_t>(indexBuffer.size());
+		uint32_t indexBufferSize = triangle.indexCount * sizeof(uint32_t);
+
+		struct StagingBuffers {
+			vks::Buffer vertices;
+			vks::Buffer indices;
+		} stagingBuffers;
+
+		// Host visible source buffers (staging)
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&stagingBuffers.vertices,
+			vertexBufferSize,
+			vertexBuffer.data()));
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&stagingBuffers.indices,
+			indexBufferSize,
+			indexBuffer.data()));
+
+		// Device local destination buffers
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
+			VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
+			&triangle.vertices,
+			vertexBufferSize));
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
+			VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
+			&triangle.indices,
+			indexBufferSize));
+
+		// Copy from host do device
+		vulkanDevice->copyBuffer(&stagingBuffers.vertices, &triangle.vertices, queue);
+		vulkanDevice->copyBuffer(&stagingBuffers.indices, &triangle.indices, queue);
+
+		// Clean up
+		stagingBuffers.vertices.destroy();
+		stagingBuffers.indices.destroy();
+	}
+
+	void setupDescriptorPool()
+	{
+		std::vector<VkDescriptorPoolSize> poolSizes = {
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 3),
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2)
+		};
+		VkDescriptorPoolCreateInfo descriptorPoolInfo =
+			vks::initializers::descriptorPoolCreateInfo(poolSizes,	2);
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+	}
+
+	void setupDescriptorSetLayout()
+	{
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings;
+		VkDescriptorSetLayoutCreateInfo descriptorLayout;
+		VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo;
+
+		// Scene rendering
+		setLayoutBindings = {
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0),			// Binding 0: Vertex shader uniform buffer
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1),	// Binding 1: Fragment shader image sampler
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_FRAGMENT_BIT, 2)			// Binding 2: Fragment shader uniform buffer
+		};
+		descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings.data(), static_cast<uint32_t>(setLayoutBindings.size()));
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayouts.scene));
+		pPipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayouts.scene, 1);
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayouts.scene));
+
+		// Fullscreen pass
+		setLayoutBindings = {
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0),			// Binding 0: Vertex shader uniform buffer
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1)	// Binding 1: Fragment shader image sampler
+		};
+		descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings.data(), static_cast<uint32_t>(setLayoutBindings.size()));
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayouts.fullscreen));
+		pPipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayouts.fullscreen, 1);
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayouts.fullscreen));
+	}
+
+	void setupDescriptorSet()
+	{
+		VkDescriptorSetAllocateInfo descriptorSetAllocInfo;
+
+		// Scene rendering
+		descriptorSetAllocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.scene, 1);
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &descriptorSetAllocInfo, &descriptorSets.scene));
+		std::vector<VkWriteDescriptorSet> offScreenWriteDescriptorSets = {
+			vks::initializers::writeDescriptorSet(descriptorSets.scene, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.scene.descriptor),
+		};
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(offScreenWriteDescriptorSets.size()), offScreenWriteDescriptorSets.data(), 0, nullptr);
+
+		// Fullscreen pass
+		descriptorSetAllocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.fullscreen, 1);
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &descriptorSetAllocInfo, &descriptorSets.fullscreen));
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets = {
+			vks::initializers::writeDescriptorSet(descriptorSets.fullscreen, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &offscreenPass.descriptor),
+		};
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr);
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyStateCI =
+			vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+
+		VkPipelineColorBlendAttachmentState blendAttachmentState =
+			vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+
+		VkPipelineColorBlendStateCreateInfo colorBlendStateCI =
+			vks::initializers::pipelineColorBlendStateCreateInfo(1,	&blendAttachmentState);
+
+		VkPipelineDepthStencilStateCreateInfo depthStencilStateCI =
+			vks::initializers::pipelineDepthStencilStateCreateInfo(VK_FALSE, VK_FALSE, VK_COMPARE_OP_LESS_OR_EQUAL);
+
+		VkPipelineViewportStateCreateInfo viewportStateCI =
+			vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+
+		VkPipelineMultisampleStateCreateInfo multisampleStateCI =
+			vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
+
+		std::vector<VkDynamicState> dynamicStateEnables = {
+			VK_DYNAMIC_STATE_VIEWPORT,
+			VK_DYNAMIC_STATE_SCISSOR,
+		};
+		VkPipelineDynamicStateCreateInfo dynamicStateCI =
+			vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
+
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+
+		VkGraphicsPipelineCreateInfo pipelineCreateInfo =
+			vks::initializers::pipelineCreateInfo(pipelineLayouts.fullscreen, renderPass, 0);
+
+		VkPipelineRasterizationStateCreateInfo rasterizationStateCI =
+			vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_CLOCKWISE, 0);
+
+		/*
+			Conservative rasterization setup
+		*/
+
+		/*
+			Get device properties for conservative rasterization
+			Requires VK_KHR_get_physical_device_properties2 and manual function pointer creation
+		*/
+		PFN_vkGetPhysicalDeviceProperties2KHR vkGetPhysicalDeviceProperties2KHR = reinterpret_cast<PFN_vkGetPhysicalDeviceProperties2KHR>(vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceProperties2KHR"));
+		assert(vkGetPhysicalDeviceProperties2KHR);
+		VkPhysicalDeviceProperties2KHR deviceProps2{};
+		conservativeRasterProps.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CONSERVATIVE_RASTERIZATION_PROPERTIES_EXT;
+		deviceProps2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR;
+		deviceProps2.pNext = &conservativeRasterProps;
+		vkGetPhysicalDeviceProperties2KHR(physicalDevice, &deviceProps2);
+
+		// Vertex bindings and attributes
+		std::vector<VkVertexInputBindingDescription> vertexInputBindings = {
+			vks::initializers::vertexInputBindingDescription(0, sizeof(Vertex), VK_VERTEX_INPUT_RATE_VERTEX),
+		};
+		std::vector<VkVertexInputAttributeDescription> vertexInputAttributes = {
+			vks::initializers::vertexInputAttributeDescription(0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0),					// Location 0: Position
+			vks::initializers::vertexInputAttributeDescription(0, 1, VK_FORMAT_R32G32B32_SFLOAT, sizeof(float) * 3),	// Location 1: Color
+		};
+		VkPipelineVertexInputStateCreateInfo vertexInputState = vks::initializers::pipelineVertexInputStateCreateInfo();
+		vertexInputState.vertexBindingDescriptionCount = static_cast<uint32_t>(vertexInputBindings.size());
+		vertexInputState.pVertexBindingDescriptions = vertexInputBindings.data();
+		vertexInputState.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertexInputAttributes.size());
+		vertexInputState.pVertexAttributeDescriptions = vertexInputAttributes.data();
+
+		pipelineCreateInfo.pInputAssemblyState = &inputAssemblyStateCI;
+		pipelineCreateInfo.pRasterizationState = &rasterizationStateCI;
+		pipelineCreateInfo.pColorBlendState = &colorBlendStateCI;
+		pipelineCreateInfo.pMultisampleState = &multisampleStateCI;
+		pipelineCreateInfo.pViewportState = &viewportStateCI;
+		pipelineCreateInfo.pDepthStencilState = &depthStencilStateCI;
+		pipelineCreateInfo.pDynamicState = &dynamicStateCI;
+		pipelineCreateInfo.stageCount = static_cast<uint32_t>(shaderStages.size());
+		pipelineCreateInfo.pStages = shaderStages.data();
+
+		// Full screen pass
+		shaderStages[0] = loadShader(getShadersPath() + "conservativeraster/fullscreen.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "conservativeraster/fullscreen.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		// Empty vertex input state (full screen triangle generated in vertex shader)
+		VkPipelineVertexInputStateCreateInfo emptyInputState = vks::initializers::pipelineVertexInputStateCreateInfo();
+		pipelineCreateInfo.pVertexInputState = &emptyInputState;
+		pipelineCreateInfo.layout = pipelineLayouts.fullscreen;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipelines.fullscreen));
+
+		pipelineCreateInfo.pVertexInputState = &vertexInputState;
+		pipelineCreateInfo.layout = pipelineLayouts.scene;
+
+		// Original triangle outline
+		// TODO: Check support for lines
+		rasterizationStateCI.lineWidth = 2.0f;
+		rasterizationStateCI.polygonMode = VK_POLYGON_MODE_LINE;
+		shaderStages[0] = loadShader(getShadersPath() + "conservativeraster/triangle.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "conservativeraster/triangleoverlay.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipelines.triangleOverlay));
+
+		pipelineCreateInfo.renderPass = offscreenPass.renderPass;
+
+		/*
+			Triangle rendering
+		*/
+		rasterizationStateCI.polygonMode = VK_POLYGON_MODE_FILL;
+		shaderStages[0] = loadShader(getShadersPath() + "conservativeraster/triangle.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "conservativeraster/triangle.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+
+		/*
+			Basic pipeline
+		*/
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipelines.triangle));
+
+		/*
+			Pipeline with conservative rasterization enabled
+		*/
+		VkPipelineRasterizationConservativeStateCreateInfoEXT conservativeRasterStateCI{};
+		conservativeRasterStateCI.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_CONSERVATIVE_STATE_CREATE_INFO_EXT;
+		conservativeRasterStateCI.conservativeRasterizationMode = VK_CONSERVATIVE_RASTERIZATION_MODE_OVERESTIMATE_EXT;
+		conservativeRasterStateCI.extraPrimitiveOverestimationSize = conservativeRasterProps.maxExtraPrimitiveOverestimationSize;
+
+		// Conservative rasterization state has to be chained into the pipeline rasterization state create info structure
+		rasterizationStateCI.pNext = &conservativeRasterStateCI;
+
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipelines.triangleConservativeRaster));
+	}
+
+	// Prepare and initialize uniform buffer containing shader uniforms
+	void prepareUniformBuffers()
+	{
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.scene,
+			sizeof(uboScene)));
+		VK_CHECK_RESULT(uniformBuffers.scene.map());
+		updateUniformBuffersScene();
+	}
+
+	void updateUniformBuffersScene()
+	{
+		uboScene.projection = camera.matrices.perspective;
+		uboScene.model = camera.matrices.view;
+		memcpy(uniformBuffers.scene.mapped, &uboScene, sizeof(uboScene));
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+		VulkanExampleBase::submitFrame();
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		loadAssets();
+		prepareOffscreen();
+		prepareUniformBuffers();
+		setupDescriptorSetLayout();
+		preparePipelines();
+		setupDescriptorPool();
+		setupDescriptorSet();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+		if (camera.updated)
+			updateUniformBuffersScene();
+	}
+
+	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
+	{
+		if (overlay->header("Settings")) {
+			if (overlay->checkBox("Conservative rasterization", &conservativeRasterEnabled)) {
+				buildCommandBuffers();
+			}
+		}
+		if (overlay->header("Device properties")) {
+			overlay->text("maxExtraPrimitiveOverestimationSize: %f", conservativeRasterProps.maxExtraPrimitiveOverestimationSize);
+			overlay->text("extraPrimitiveOverestimationSizeGranularity: %f", conservativeRasterProps.extraPrimitiveOverestimationSizeGranularity);
+			overlay->text("primitiveUnderestimation:  %s", conservativeRasterProps.primitiveUnderestimation ? "yes" : "no");
+			overlay->text("conservativePointAndLineRasterization:  %s", conservativeRasterProps.conservativePointAndLineRasterization ? "yes" : "no");
+			overlay->text("degenerateTrianglesRasterized: %s", conservativeRasterProps.degenerateTrianglesRasterized ? "yes" : "no");
+			overlay->text("degenerateLinesRasterized: %s", conservativeRasterProps.degenerateLinesRasterized ? "yes" : "no");
+			overlay->text("fullyCoveredFragmentShaderInputVariable: %s", conservativeRasterProps.fullyCoveredFragmentShaderInputVariable ? "yes" : "no");
+			overlay->text("conservativeRasterizationPostDepthCoverage: %s", conservativeRasterProps.conservativeRasterizationPostDepthCoverage ? "yes" : "no");
+		}
+
+	}
+};
+
+VULKAN_EXAMPLE_MAIN()
diff --git a/external/Vulkan/examples/debugmarker/debugmarker.cpp b/external/Vulkan/examples/debugmarker/debugmarker.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..dc5e42a88f527ceedccb52518099df0c5dcf29a2
--- /dev/null
+++ b/external/Vulkan/examples/debugmarker/debugmarker.cpp
@@ -0,0 +1,799 @@
+/*
+* Vulkan Example - Example for VK_EXT_debug_marker extension. To be used in conjunction with a debugging app like RenderDoc (https://renderdoc.org)
+*
+* Copyright (C) by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+/*
+ * Note: This sample is deprecated!
+ * An updated version using VK_EXT_debug_utils along with an in-depth tutorial is available in the official Khronos Vulkan Samples repository at
+ * https://github.com/KhronosGroup/Vulkan-Samples/blob/master/samples/extensions/debug_utils.
+ */
+
+#include "vulkanexamplebase.h"
+#include "VulkanglTFModel.h"
+
+#define ENABLE_VALIDATION false
+
+// Offscreen properties
+#define OFFSCREEN_DIM 256
+#define OFFSCREEN_FORMAT VK_FORMAT_R8G8B8A8_UNORM
+#define OFFSCREEN_FILTER VK_FILTER_LINEAR;
+
+// Setup and functions for the VK_EXT_debug_marker_extension
+// Extension spec can be found at https://github.com/KhronosGroup/Vulkan-Docs/blob/1.0-VK_EXT_debug_marker/doc/specs/vulkan/appendices/VK_EXT_debug_marker.txt
+// Note that the extension will only be present if run from an offline debugging application
+namespace DebugMarker
+{
+	bool active = false;
+	bool extensionPresent = false;
+
+	PFN_vkDebugMarkerSetObjectTagEXT vkDebugMarkerSetObjectTag = VK_NULL_HANDLE;
+	PFN_vkDebugMarkerSetObjectNameEXT vkDebugMarkerSetObjectName = VK_NULL_HANDLE;
+	PFN_vkCmdDebugMarkerBeginEXT vkCmdDebugMarkerBegin = VK_NULL_HANDLE;
+	PFN_vkCmdDebugMarkerEndEXT vkCmdDebugMarkerEnd = VK_NULL_HANDLE;
+	PFN_vkCmdDebugMarkerInsertEXT vkCmdDebugMarkerInsert = VK_NULL_HANDLE;
+
+	// Get function pointers for the debug report extensions from the device
+	void setup(VkDevice device, VkPhysicalDevice physicalDevice)
+	{
+		// Check if the debug marker extension is present (which is the case if run from a graphics debugger)
+		uint32_t extensionCount;
+		vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &extensionCount, nullptr);
+		std::vector<VkExtensionProperties> extensions(extensionCount);
+		vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &extensionCount, extensions.data());
+		for (auto extension : extensions) {
+			if (strcmp(extension.extensionName, VK_EXT_DEBUG_MARKER_EXTENSION_NAME) == 0) {
+				extensionPresent = true;
+				break;
+			}
+		}
+
+		if (extensionPresent) {
+			// The debug marker extension is not part of the core, so function pointers need to be loaded manually
+			vkDebugMarkerSetObjectTag = (PFN_vkDebugMarkerSetObjectTagEXT)vkGetDeviceProcAddr(device, "vkDebugMarkerSetObjectTagEXT");
+			vkDebugMarkerSetObjectName = (PFN_vkDebugMarkerSetObjectNameEXT)vkGetDeviceProcAddr(device, "vkDebugMarkerSetObjectNameEXT");
+			vkCmdDebugMarkerBegin = (PFN_vkCmdDebugMarkerBeginEXT)vkGetDeviceProcAddr(device, "vkCmdDebugMarkerBeginEXT");
+			vkCmdDebugMarkerEnd = (PFN_vkCmdDebugMarkerEndEXT)vkGetDeviceProcAddr(device, "vkCmdDebugMarkerEndEXT");
+			vkCmdDebugMarkerInsert = (PFN_vkCmdDebugMarkerInsertEXT)vkGetDeviceProcAddr(device, "vkCmdDebugMarkerInsertEXT");
+			// Set flag if at least one function pointer is present
+			active = (vkDebugMarkerSetObjectName != VK_NULL_HANDLE);
+		}
+		else {
+			std::cout << "Warning: " << VK_EXT_DEBUG_MARKER_EXTENSION_NAME << " not present, debug markers are disabled.";
+			std::cout << "Try running from inside a Vulkan graphics debugger (e.g. RenderDoc)" << std::endl;
+		}
+	}
+
+	// Sets the debug name of an object
+	// All Objects in Vulkan are represented by their 64-bit handles which are passed into this function
+	// along with the object type
+	void setObjectName(VkDevice device, uint64_t object, VkDebugReportObjectTypeEXT objectType, const char *name)
+	{
+		// Check for valid function pointer (may not be present if not running in a debugging application)
+		if (active)
+		{
+			VkDebugMarkerObjectNameInfoEXT nameInfo = {};
+			nameInfo.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_OBJECT_NAME_INFO_EXT;
+			nameInfo.objectType = objectType;
+			nameInfo.object = object;
+			nameInfo.pObjectName = name;
+			vkDebugMarkerSetObjectName(device, &nameInfo);
+		}
+	}
+
+	// Set the tag for an object
+	void setObjectTag(VkDevice device, uint64_t object, VkDebugReportObjectTypeEXT objectType, uint64_t name, size_t tagSize, const void* tag)
+	{
+		// Check for valid function pointer (may not be present if not running in a debugging application)
+		if (active)
+		{
+			VkDebugMarkerObjectTagInfoEXT tagInfo = {};
+			tagInfo.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_OBJECT_TAG_INFO_EXT;
+			tagInfo.objectType = objectType;
+			tagInfo.object = object;
+			tagInfo.tagName = name;
+			tagInfo.tagSize = tagSize;
+			tagInfo.pTag = tag;
+			vkDebugMarkerSetObjectTag(device, &tagInfo);
+		}
+	}
+
+	// Start a new debug marker region
+	void beginRegion(VkCommandBuffer cmdbuffer, const char* pMarkerName, glm::vec4 color)
+	{
+		// Check for valid function pointer (may not be present if not running in a debugging application)
+		if (active)
+		{
+			VkDebugMarkerMarkerInfoEXT markerInfo = {};
+			markerInfo.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT;
+			memcpy(markerInfo.color, &color[0], sizeof(float) * 4);
+			markerInfo.pMarkerName = pMarkerName;
+			vkCmdDebugMarkerBegin(cmdbuffer, &markerInfo);
+		}
+	}
+
+	// Insert a new debug marker into the command buffer
+	void insert(VkCommandBuffer cmdbuffer, std::string markerName, glm::vec4 color)
+	{
+		// Check for valid function pointer (may not be present if not running in a debugging application)
+		if (active)
+		{
+			VkDebugMarkerMarkerInfoEXT markerInfo = {};
+			markerInfo.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT;
+			memcpy(markerInfo.color, &color[0], sizeof(float) * 4);
+			markerInfo.pMarkerName = markerName.c_str();
+			vkCmdDebugMarkerInsert(cmdbuffer, &markerInfo);
+		}
+	}
+
+	// End the current debug marker region
+	void endRegion(VkCommandBuffer cmdBuffer)
+	{
+		// Check for valid function (may not be present if not running in a debugging application)
+		if (vkCmdDebugMarkerEnd)
+		{
+			vkCmdDebugMarkerEnd(cmdBuffer);
+		}
+	}
+};
+
+struct Scene {
+
+	vkglTF::Model model;
+	std::vector<std::string> modelPartNames;
+
+	void draw(VkCommandBuffer cmdBuffer)
+	{
+		VkDeviceSize offsets[1] = { 0 };
+		model.bindBuffers(cmdBuffer);
+		for (auto i = 0; i < model.nodes.size(); i++)
+		{
+			// Add debug marker the name of this glTF node
+			DebugMarker::insert(cmdBuffer, "Draw \"" + model.nodes[i]->name + "\"", glm::vec4(0.0f));
+			model.drawNode(model.nodes[i], cmdBuffer);
+		}
+	}
+
+	void loadFromFile(std::string filename, vks::VulkanDevice* vulkanDevice, VkQueue queue)
+	{
+		const uint32_t glTFLoadingFlags = vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::PreMultiplyVertexColors | vkglTF::FileLoadingFlags::FlipY;
+		model.loadFromFile(filename, vulkanDevice, queue, glTFLoadingFlags);
+	}
+};
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	bool wireframe = true;
+	bool glow = true;
+
+	Scene scene, sceneGlow;
+
+	vks::Buffer uniformBuffer;
+
+	struct UBOVS {
+		glm::mat4 projection;
+		glm::mat4 model;
+		glm::vec4 lightPos = glm::vec4(0.0f, 5.0f, 15.0f, 1.0f);
+	} uboVS;
+
+	struct Pipelines {
+		VkPipeline toonshading;
+		VkPipeline color;
+		VkPipeline wireframe = VK_NULL_HANDLE;
+		VkPipeline postprocess;
+	} pipelines;
+
+	VkPipelineLayout pipelineLayout;
+	VkDescriptorSetLayout descriptorSetLayout;
+
+	struct {
+		VkDescriptorSet scene;
+		VkDescriptorSet fullscreen;
+	} descriptorSets;
+
+	// Framebuffer for offscreen rendering
+	struct FrameBufferAttachment {
+		VkImage image;
+		VkDeviceMemory mem;
+		VkImageView view;
+	};
+	struct OffscreenPass {
+		int32_t width, height;
+		VkFramebuffer frameBuffer;
+		FrameBufferAttachment color, depth;
+		VkRenderPass renderPass;
+		VkSampler sampler;
+		VkDescriptorImageInfo descriptor;
+	} offscreenPass;
+
+	// Random tag data
+	struct DemoTag {
+		const char name[17] = "debug marker tag";
+	} demoTag;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Debugging with VK_EXT_debug_marker";
+		camera.setRotation(glm::vec3(-4.35f, 16.25f, 0.0f));
+		camera.setRotationSpeed(0.5f);
+		camera.setPosition(glm::vec3(0.1f, 1.1f, -8.5f));
+		camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 256.0f);
+	}
+
+	// Enable physical device features required for this example
+	virtual void getEnabledFeatures()
+	{
+		// Fill mode non solid is required for wireframe display
+		if (deviceFeatures.fillModeNonSolid) {
+			enabledFeatures.fillModeNonSolid = VK_TRUE;
+		};
+		wireframe = deviceFeatures.fillModeNonSolid;
+	}
+
+	~VulkanExample()
+	{
+		// Clean up used Vulkan resources
+		// Note : Inherited destructor cleans up resources stored in base class
+		vkDestroyPipeline(device, pipelines.toonshading, nullptr);
+		vkDestroyPipeline(device, pipelines.color, nullptr);
+		vkDestroyPipeline(device, pipelines.postprocess, nullptr);
+		if (pipelines.wireframe != VK_NULL_HANDLE) {
+			vkDestroyPipeline(device, pipelines.wireframe, nullptr);
+		}
+
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+
+		uniformBuffer.destroy();
+
+		// Offscreen
+		// Color attachment
+		vkDestroyImageView(device, offscreenPass.color.view, nullptr);
+		vkDestroyImage(device, offscreenPass.color.image, nullptr);
+		vkFreeMemory(device, offscreenPass.color.mem, nullptr);
+
+		// Depth attachment
+		vkDestroyImageView(device, offscreenPass.depth.view, nullptr);
+		vkDestroyImage(device, offscreenPass.depth.image, nullptr);
+		vkFreeMemory(device, offscreenPass.depth.mem, nullptr);
+
+		vkDestroyRenderPass(device, offscreenPass.renderPass, nullptr);
+		vkDestroySampler(device, offscreenPass.sampler, nullptr);
+		vkDestroyFramebuffer(device, offscreenPass.frameBuffer, nullptr);
+	}
+
+	// Prepare a texture target and framebuffer for offscreen rendering
+	void prepareOffscreen()
+	{
+		offscreenPass.width = OFFSCREEN_DIM;
+		offscreenPass.height = OFFSCREEN_DIM;
+
+		// Find a suitable depth format
+		VkFormat fbDepthFormat;
+		VkBool32 validDepthFormat = vks::tools::getSupportedDepthFormat(physicalDevice, &fbDepthFormat);
+		assert(validDepthFormat);
+
+		// Color attachment
+		VkImageCreateInfo image = vks::initializers::imageCreateInfo();
+		image.imageType = VK_IMAGE_TYPE_2D;
+		image.format = OFFSCREEN_FORMAT;
+		image.extent.width = offscreenPass.width;
+		image.extent.height = offscreenPass.height;
+		image.extent.depth = 1;
+		image.mipLevels = 1;
+		image.arrayLayers = 1;
+		image.samples = VK_SAMPLE_COUNT_1_BIT;
+		image.tiling = VK_IMAGE_TILING_OPTIMAL;
+		// We will sample directly from the color attachment
+		image.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
+
+		VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo();
+		VkMemoryRequirements memReqs;
+
+		VK_CHECK_RESULT(vkCreateImage(device, &image, nullptr, &offscreenPass.color.image));
+		vkGetImageMemoryRequirements(device, offscreenPass.color.image, &memReqs);
+		memAlloc.allocationSize = memReqs.size;
+		memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+		VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &offscreenPass.color.mem));
+		VK_CHECK_RESULT(vkBindImageMemory(device, offscreenPass.color.image, offscreenPass.color.mem, 0));
+
+		VkImageViewCreateInfo colorImageView = vks::initializers::imageViewCreateInfo();
+		colorImageView.viewType = VK_IMAGE_VIEW_TYPE_2D;
+		colorImageView.format = OFFSCREEN_FORMAT;
+		colorImageView.subresourceRange = {};
+		colorImageView.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+		colorImageView.subresourceRange.baseMipLevel = 0;
+		colorImageView.subresourceRange.levelCount = 1;
+		colorImageView.subresourceRange.baseArrayLayer = 0;
+		colorImageView.subresourceRange.layerCount = 1;
+		colorImageView.image = offscreenPass.color.image;
+		VK_CHECK_RESULT(vkCreateImageView(device, &colorImageView, nullptr, &offscreenPass.color.view));
+
+		// Create sampler to sample from the attachment in the fragment shader
+		VkSamplerCreateInfo samplerInfo = vks::initializers::samplerCreateInfo();
+		samplerInfo.magFilter = OFFSCREEN_FILTER;
+		samplerInfo.minFilter = OFFSCREEN_FILTER;
+		samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+		samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+		samplerInfo.addressModeV = samplerInfo.addressModeU;
+		samplerInfo.addressModeW = samplerInfo.addressModeU;
+		samplerInfo.mipLodBias = 0.0f;
+		samplerInfo.maxAnisotropy = 1.0f;
+		samplerInfo.minLod = 0.0f;
+		samplerInfo.maxLod = 1.0f;
+		samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
+		VK_CHECK_RESULT(vkCreateSampler(device, &samplerInfo, nullptr, &offscreenPass.sampler));
+
+		// Depth stencil attachment
+		image.format = fbDepthFormat;
+		image.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
+
+		VK_CHECK_RESULT(vkCreateImage(device, &image, nullptr, &offscreenPass.depth.image));
+		vkGetImageMemoryRequirements(device, offscreenPass.depth.image, &memReqs);
+		memAlloc.allocationSize = memReqs.size;
+		memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+		VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &offscreenPass.depth.mem));
+		VK_CHECK_RESULT(vkBindImageMemory(device, offscreenPass.depth.image, offscreenPass.depth.mem, 0));
+
+		VkImageViewCreateInfo depthStencilView = vks::initializers::imageViewCreateInfo();
+		depthStencilView.viewType = VK_IMAGE_VIEW_TYPE_2D;
+		depthStencilView.format = fbDepthFormat;
+		depthStencilView.flags = 0;
+		depthStencilView.subresourceRange = {};
+		depthStencilView.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
+		depthStencilView.subresourceRange.baseMipLevel = 0;
+		depthStencilView.subresourceRange.levelCount = 1;
+		depthStencilView.subresourceRange.baseArrayLayer = 0;
+		depthStencilView.subresourceRange.layerCount = 1;
+		depthStencilView.image = offscreenPass.depth.image;
+		VK_CHECK_RESULT(vkCreateImageView(device, &depthStencilView, nullptr, &offscreenPass.depth.view));
+
+		// Create a separate render pass for the offscreen rendering as it may differ from the one used for scene rendering
+
+		std::array<VkAttachmentDescription, 2> attchmentDescriptions = {};
+		// Color attachment
+		attchmentDescriptions[0].format = OFFSCREEN_FORMAT;
+		attchmentDescriptions[0].samples = VK_SAMPLE_COUNT_1_BIT;
+		attchmentDescriptions[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+		attchmentDescriptions[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+		attchmentDescriptions[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+		attchmentDescriptions[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+		attchmentDescriptions[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+		attchmentDescriptions[0].finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+		// Depth attachment
+		attchmentDescriptions[1].format = fbDepthFormat;
+		attchmentDescriptions[1].samples = VK_SAMPLE_COUNT_1_BIT;
+		attchmentDescriptions[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+		attchmentDescriptions[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+		attchmentDescriptions[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+		attchmentDescriptions[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+		attchmentDescriptions[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+		attchmentDescriptions[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+
+		VkAttachmentReference colorReference = { 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
+		VkAttachmentReference depthReference = { 1, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL };
+
+		VkSubpassDescription subpassDescription = {};
+		subpassDescription.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+		subpassDescription.colorAttachmentCount = 1;
+		subpassDescription.pColorAttachments = &colorReference;
+		subpassDescription.pDepthStencilAttachment = &depthReference;
+
+		// Use subpass dependencies for layout transitions
+		std::array<VkSubpassDependency, 2> dependencies;
+
+		dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
+		dependencies[0].dstSubpass = 0;
+		dependencies[0].srcStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
+		dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+		dependencies[0].srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
+		dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+		dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+		dependencies[1].srcSubpass = 0;
+		dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
+		dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+		dependencies[1].dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
+		dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+		dependencies[1].dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
+		dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+		// Create the actual renderpass
+		VkRenderPassCreateInfo renderPassInfo = {};
+		renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
+		renderPassInfo.attachmentCount = static_cast<uint32_t>(attchmentDescriptions.size());
+		renderPassInfo.pAttachments = attchmentDescriptions.data();
+		renderPassInfo.subpassCount = 1;
+		renderPassInfo.pSubpasses = &subpassDescription;
+		renderPassInfo.dependencyCount = static_cast<uint32_t>(dependencies.size());
+		renderPassInfo.pDependencies = dependencies.data();
+
+		VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassInfo, nullptr, &offscreenPass.renderPass));
+
+		VkImageView attachments[2];
+		attachments[0] = offscreenPass.color.view;
+		attachments[1] = offscreenPass.depth.view;
+
+		VkFramebufferCreateInfo fbufCreateInfo = vks::initializers::framebufferCreateInfo();
+		fbufCreateInfo.renderPass = offscreenPass.renderPass;
+		fbufCreateInfo.attachmentCount = 2;
+		fbufCreateInfo.pAttachments = attachments;
+		fbufCreateInfo.width = offscreenPass.width;
+		fbufCreateInfo.height = offscreenPass.height;
+		fbufCreateInfo.layers = 1;
+
+		VK_CHECK_RESULT(vkCreateFramebuffer(device, &fbufCreateInfo, nullptr, &offscreenPass.frameBuffer));
+
+		// Fill a descriptor for later use in a descriptor set
+		offscreenPass.descriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+		offscreenPass.descriptor.imageView = offscreenPass.color.view;
+		offscreenPass.descriptor.sampler = offscreenPass.sampler;
+
+		// Name some objects for debugging
+		DebugMarker::setObjectName(device, (uint64_t)offscreenPass.color.image, VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_EXT, "Off-screen color framebuffer");
+		DebugMarker::setObjectName(device, (uint64_t)offscreenPass.depth.image, VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_EXT, "Off-screen depth framebuffer");
+		DebugMarker::setObjectName(device, (uint64_t)offscreenPass.sampler, VK_DEBUG_REPORT_OBJECT_TYPE_SAMPLER_EXT, "Off-screen framebuffer default sampler");
+	}
+
+	void loadAssets()
+	{
+		scene.loadFromFile(getAssetPath() + "models/treasure_smooth.gltf", vulkanDevice, queue);
+		sceneGlow.loadFromFile(getAssetPath() + "models/treasure_glow.gltf", vulkanDevice, queue);
+		// Name the buffers for debugging
+		// Scene
+		DebugMarker::setObjectName(device, (uint64_t)scene.model.vertices.buffer, VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_EXT, "Scene vertex buffer");
+		DebugMarker::setObjectName(device, (uint64_t)scene.model.indices.buffer, VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_EXT, "Scene index buffer");
+		// Glow
+		DebugMarker::setObjectName(device, (uint64_t)sceneGlow.model.vertices.buffer, VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_EXT, "Glow vertex buffer");
+		DebugMarker::setObjectName(device, (uint64_t)sceneGlow.model.indices.buffer, VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_EXT, "Glow index buffer");
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+		VkClearValue clearValues[2];
+		VkViewport viewport;
+		VkRect2D scissor;
+		VkDeviceSize offsets[1] = { 0 };
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			/*
+				First render pass: Offscreen rendering
+			*/
+			if (glow)
+			{
+				VkClearValue clearValues[2];
+				clearValues[0].color = { { 0.0f, 0.0f, 0.0f, 0.0f } };
+				clearValues[1].depthStencil = { 1.0f, 0 };
+
+				VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+				renderPassBeginInfo.renderPass = offscreenPass.renderPass;
+				renderPassBeginInfo.framebuffer = offscreenPass.frameBuffer;
+				renderPassBeginInfo.renderArea.extent.width = offscreenPass.width;
+				renderPassBeginInfo.renderArea.extent.height = offscreenPass.height;
+				renderPassBeginInfo.clearValueCount = 2;
+				renderPassBeginInfo.pClearValues = clearValues;
+
+				// Start a new debug marker region
+				DebugMarker::beginRegion(drawCmdBuffers[i], "Off-screen scene rendering", glm::vec4(1.0f, 0.78f, 0.05f, 1.0f));
+
+				vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+				VkViewport viewport = vks::initializers::viewport((float)offscreenPass.width, (float)offscreenPass.height, 0.0f, 1.0f);
+				vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+				VkRect2D scissor = vks::initializers::rect2D(offscreenPass.width, offscreenPass.height, 0, 0);
+				vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+				vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets.scene, 0, NULL);
+				vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.color);
+
+				sceneGlow.draw(drawCmdBuffers[i]);
+
+				vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+				DebugMarker::endRegion(drawCmdBuffers[i]);
+			}
+
+			/*
+				Note: Explicit synchronization is not required between the render pass, as this is done implicit via sub pass dependencies
+			*/
+
+			/*
+				Second render pass: Scene rendering with applied bloom
+			*/
+			{
+				clearValues[0].color = defaultClearColor;
+				clearValues[1].depthStencil = { 1.0f, 0 };
+
+				VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+				renderPassBeginInfo.renderPass = renderPass;
+				renderPassBeginInfo.framebuffer = frameBuffers[i];
+				renderPassBeginInfo.renderArea.extent.width = width;
+				renderPassBeginInfo.renderArea.extent.height = height;
+				renderPassBeginInfo.clearValueCount = 2;
+				renderPassBeginInfo.pClearValues = clearValues;
+
+				// Start a new debug marker region
+				DebugMarker::beginRegion(drawCmdBuffers[i], "Render scene", glm::vec4(0.5f, 0.76f, 0.34f, 1.0f));
+
+				vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+				VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+				vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+				VkRect2D scissor = vks::initializers::rect2D(wireframe ? width / 2 : width, height, 0, 0);
+				vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+				vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets.scene, 0, NULL);
+
+				// Solid rendering
+
+				// Start a new debug marker region
+				DebugMarker::beginRegion(drawCmdBuffers[i], "Toon shading draw", glm::vec4(0.78f, 0.74f, 0.9f, 1.0f));
+
+				vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.toonshading);
+				scene.draw(drawCmdBuffers[i]);
+
+				DebugMarker::endRegion(drawCmdBuffers[i]);
+
+				// Wireframe rendering
+				if (wireframe)
+				{
+					// Insert debug marker
+					DebugMarker::beginRegion(drawCmdBuffers[i], "Wireframe draw", glm::vec4(0.53f, 0.78f, 0.91f, 1.0f));
+
+					scissor.offset.x = width / 2;
+					vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+					vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.wireframe);
+					scene.draw(drawCmdBuffers[i]);
+
+					DebugMarker::endRegion(drawCmdBuffers[i]);
+
+					scissor.offset.x = 0;
+					scissor.extent.width = width;
+					vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+				}
+
+				// Post processing
+				if (glow)
+				{
+					DebugMarker::beginRegion(drawCmdBuffers[i], "Apply post processing", glm::vec4(0.93f, 0.89f, 0.69f, 1.0f));
+
+					vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.postprocess);
+					// Full screen quad is generated by the vertex shaders, so we reuse four vertices (for four invocations) from current vertex buffer
+					vkCmdDraw(drawCmdBuffers[i], 4, 1, 0, 0);
+
+					DebugMarker::endRegion(drawCmdBuffers[i]);
+				}
+
+				drawUI(drawCmdBuffers[i]);
+
+				vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+				// End current debug marker region
+				DebugMarker::endRegion(drawCmdBuffers[i]);
+
+			}
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void setupDescriptorPool()
+	{
+		// Example uses one ubo and one combined image sampler
+		std::vector<VkDescriptorPoolSize> poolSizes = {
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1),
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1),
+		};
+		VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes.size(), poolSizes.data(), 1);
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+	}
+
+	void setupDescriptorSetLayout()
+	{
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			// Binding 0 : Vertex shader uniform buffer
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0),
+			// Binding 1 : Fragment shader combined sampler
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1),
+		};
+		VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings.data(), setLayoutBindings.size());
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout));
+
+		VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1);
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout));
+
+		// Name for debugging
+		DebugMarker::setObjectName(device, (uint64_t)pipelineLayout, VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_LAYOUT_EXT, "Shared pipeline layout");
+		DebugMarker::setObjectName(device, (uint64_t)descriptorSetLayout, VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT_EXT, "Shared descriptor set layout");
+	}
+
+	void setupDescriptorSet()
+	{
+		VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1);
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.scene));
+
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets = {
+			// Binding 0 : Vertex shader uniform buffer
+			vks::initializers::writeDescriptorSet(descriptorSets.scene, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffer.descriptor),
+			// Binding 1 : Color map
+			vks::initializers::writeDescriptorSet(descriptorSets.scene, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &offscreenPass.descriptor)
+		};
+		vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, NULL);
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyStateCI = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationStateCI = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendStateCI = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilStateCI = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportStateCI = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+		VkPipelineMultisampleStateCreateInfo multisampleStateCI = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
+		std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR	};
+		VkPipelineDynamicStateCreateInfo dynamicStateCI =	vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables.data(), dynamicStateEnables.size(), 0);
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass);
+		pipelineCI.pInputAssemblyState = &inputAssemblyStateCI;
+		pipelineCI.pRasterizationState = &rasterizationStateCI;
+		pipelineCI.pColorBlendState = &colorBlendStateCI;
+		pipelineCI.pMultisampleState = &multisampleStateCI;
+		pipelineCI.pViewportState = &viewportStateCI;
+		pipelineCI.pDepthStencilState = &depthStencilStateCI;
+		pipelineCI.pDynamicState = &dynamicStateCI;
+		pipelineCI.stageCount = shaderStages.size();
+		pipelineCI.pStages = shaderStages.data();
+		pipelineCI.pVertexInputState            = vkglTF::Vertex::getPipelineVertexInputState({vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::UV, vkglTF::VertexComponent::Color});
+
+		// Toon shading pipeline
+		shaderStages[0] = loadShader(getShadersPath() + "debugmarker/toon.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "debugmarker/toon.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.toonshading));
+
+		// Color only pipeline
+		shaderStages[0] = loadShader(getShadersPath() + "debugmarker/colorpass.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "debugmarker/colorpass.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		pipelineCI.renderPass = offscreenPass.renderPass;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.color));
+
+		// Wire frame rendering pipeline
+		if (deviceFeatures.fillModeNonSolid)
+		{
+			rasterizationStateCI.polygonMode = VK_POLYGON_MODE_LINE;
+			pipelineCI.renderPass = renderPass;
+			VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.wireframe));
+		}
+
+		// Post processing effect
+		shaderStages[0] = loadShader(getShadersPath() + "debugmarker/postprocess.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "debugmarker/postprocess.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		depthStencilStateCI.depthTestEnable = VK_FALSE;
+		depthStencilStateCI.depthWriteEnable = VK_FALSE;
+		rasterizationStateCI.polygonMode = VK_POLYGON_MODE_FILL;
+		rasterizationStateCI.cullMode = VK_CULL_MODE_NONE;
+		blendAttachmentState.colorWriteMask = 0xF;
+		blendAttachmentState.blendEnable =  VK_TRUE;
+		blendAttachmentState.colorBlendOp = VK_BLEND_OP_ADD;
+		blendAttachmentState.srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
+		blendAttachmentState.dstColorBlendFactor = VK_BLEND_FACTOR_ONE;
+		blendAttachmentState.alphaBlendOp = VK_BLEND_OP_ADD;
+		blendAttachmentState.srcAlphaBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
+		blendAttachmentState.dstAlphaBlendFactor = VK_BLEND_FACTOR_DST_ALPHA;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.postprocess));
+
+		// Name shader modules for debugging
+		// Shader module count starts at 2 when UI overlay in base class is enabled
+		uint32_t moduleIndex = settings.overlay ? 2 : 0;
+		DebugMarker::setObjectName(device, (uint64_t)shaderModules[moduleIndex + 0], VK_DEBUG_REPORT_OBJECT_TYPE_SHADER_MODULE_EXT, "Toon shading vertex shader");
+		DebugMarker::setObjectName(device, (uint64_t)shaderModules[moduleIndex + 1], VK_DEBUG_REPORT_OBJECT_TYPE_SHADER_MODULE_EXT, "Toon shading fragment shader");
+		DebugMarker::setObjectName(device, (uint64_t)shaderModules[moduleIndex + 2], VK_DEBUG_REPORT_OBJECT_TYPE_SHADER_MODULE_EXT, "Color-only vertex shader");
+		DebugMarker::setObjectName(device, (uint64_t)shaderModules[moduleIndex + 3], VK_DEBUG_REPORT_OBJECT_TYPE_SHADER_MODULE_EXT, "Color-only fragment shader");
+		DebugMarker::setObjectName(device, (uint64_t)shaderModules[moduleIndex + 4], VK_DEBUG_REPORT_OBJECT_TYPE_SHADER_MODULE_EXT, "Postprocess vertex shader");
+		DebugMarker::setObjectName(device, (uint64_t)shaderModules[moduleIndex + 5], VK_DEBUG_REPORT_OBJECT_TYPE_SHADER_MODULE_EXT, "Postprocess fragment shader");
+
+		// Name pipelines for debugging
+		DebugMarker::setObjectName(device, (uint64_t)pipelines.toonshading, VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_EXT, "Toon shading pipeline");
+		DebugMarker::setObjectName(device, (uint64_t)pipelines.color, VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_EXT, "Color only pipeline");
+
+		if (deviceFeatures.fillModeNonSolid)
+		{
+			DebugMarker::setObjectName(device, (uint64_t)pipelines.wireframe, VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_EXT, "Wireframe rendering pipeline");
+		}
+
+		DebugMarker::setObjectName(device, (uint64_t)pipelines.postprocess, VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_EXT, "Post processing pipeline");
+	}
+
+	// Prepare and initialize uniform buffer containing shader uniforms
+	void prepareUniformBuffers()
+	{
+		// Vertex shader uniform buffer block
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffer,
+			sizeof(uboVS)));
+
+		// Map persistent
+		VK_CHECK_RESULT(uniformBuffer.map());
+
+
+		// Name uniform buffer for debugging
+		DebugMarker::setObjectName(device, (uint64_t)uniformBuffer.buffer, VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_EXT, "Scene uniform buffer block");
+		// Add some random tag
+		DebugMarker::setObjectTag(device, (uint64_t)uniformBuffer.buffer, VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_EXT, 0, sizeof(demoTag), &demoTag);
+
+		updateUniformBuffers();
+	}
+
+	void updateUniformBuffers()
+	{
+		uboVS.projection = camera.matrices.perspective;
+		uboVS.model = camera.matrices.view;
+		memcpy(uniformBuffer.mapped, &uboVS, sizeof(uboVS));
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+		VulkanExampleBase::submitFrame();
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		DebugMarker::setup(device, physicalDevice);
+		loadAssets();
+		prepareOffscreen();
+		prepareUniformBuffers();
+		setupDescriptorSetLayout();
+		preparePipelines();
+		setupDescriptorPool();
+		setupDescriptorSet();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+		if (camera.updated)
+			updateUniformBuffers();
+	}
+
+
+	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
+	{
+		if (overlay->header("Info")) {
+			overlay->text("VK_EXT_debug_marker %s", (DebugMarker::active ? "active" : "not present"));
+		}
+		if (overlay->header("Settings")) {
+			if (overlay->checkBox("Glow", &glow)) {
+				buildCommandBuffers();
+			}
+			if (deviceFeatures.fillModeNonSolid) {
+				if (overlay->checkBox("Wireframe", &wireframe)) {
+					buildCommandBuffers();
+				}
+			}
+		}
+	}
+};
+
+VULKAN_EXAMPLE_MAIN()
\ No newline at end of file
diff --git a/external/Vulkan/examples/deferred/deferred.cpp b/external/Vulkan/examples/deferred/deferred.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..8c5db8d9914cbfb865abe317ec105b0aef0e973e
--- /dev/null
+++ b/external/Vulkan/examples/deferred/deferred.cpp
@@ -0,0 +1,823 @@
+/*
+* Vulkan Example - Deferred shading with multiple render targets (aka G-Buffer) example
+*
+* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkanexamplebase.h"
+#include "VulkanglTFModel.h"
+
+#define ENABLE_VALIDATION false
+
+// Texture properties
+#define TEX_DIM 2048
+#define TEX_FILTER VK_FILTER_LINEAR
+
+// Offscreen frame buffer properties
+#define FB_DIM TEX_DIM
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	int32_t debugDisplayTarget = 0;
+
+	struct {
+		struct {
+			vks::Texture2D colorMap;
+			vks::Texture2D normalMap;
+		} model;
+		struct {
+			vks::Texture2D colorMap;
+			vks::Texture2D normalMap;
+		} floor;
+	} textures;
+
+	struct {
+		vkglTF::Model model;
+		vkglTF::Model floor;
+	} models;
+
+	struct {
+		glm::mat4 projection;
+		glm::mat4 model;
+		glm::mat4 view;
+		glm::vec4 instancePos[3];
+	} uboOffscreenVS;
+
+	struct Light {
+		glm::vec4 position;
+		glm::vec3 color;
+		float radius;
+	};
+
+	struct {
+		Light lights[6];
+		glm::vec4 viewPos;
+		int debugDisplayTarget = 0;
+	} uboComposition;
+
+	struct {
+		vks::Buffer offscreen;
+		vks::Buffer composition;
+	} uniformBuffers;
+
+	struct {
+		VkPipeline offscreen;
+		VkPipeline composition;
+	} pipelines;
+	VkPipelineLayout pipelineLayout;
+
+	struct {
+		VkDescriptorSet model;
+		VkDescriptorSet floor;
+	} descriptorSets;
+
+	VkDescriptorSet descriptorSet;
+	VkDescriptorSetLayout descriptorSetLayout;
+
+	// Framebuffer for offscreen rendering
+	struct FrameBufferAttachment {
+		VkImage image;
+		VkDeviceMemory mem;
+		VkImageView view;
+		VkFormat format;
+	};
+	struct FrameBuffer {
+		int32_t width, height;
+		VkFramebuffer frameBuffer;
+		FrameBufferAttachment position, normal, albedo;
+		FrameBufferAttachment depth;
+		VkRenderPass renderPass;
+	} offScreenFrameBuf;
+
+	// One sampler for the frame buffer color attachments
+	VkSampler colorSampler;
+
+	VkCommandBuffer offScreenCmdBuffer = VK_NULL_HANDLE;
+
+	// Semaphore used to synchronize between offscreen and final scene rendering
+	VkSemaphore offscreenSemaphore = VK_NULL_HANDLE;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Deferred shading";
+		camera.type = Camera::CameraType::firstperson;
+		camera.movementSpeed = 5.0f;
+#ifndef __ANDROID__
+		camera.rotationSpeed = 0.25f;
+#endif
+		camera.position = { 2.15f, 0.3f, -8.75f };
+		camera.setRotation(glm::vec3(-0.75f, 12.5f, 0.0f));
+		camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 256.0f);
+	}
+
+	~VulkanExample()
+	{
+		// Clean up used Vulkan resources
+		// Note : Inherited destructor cleans up resources stored in base class
+
+		vkDestroySampler(device, colorSampler, nullptr);
+
+		// Frame buffer
+
+		// Color attachments
+		vkDestroyImageView(device, offScreenFrameBuf.position.view, nullptr);
+		vkDestroyImage(device, offScreenFrameBuf.position.image, nullptr);
+		vkFreeMemory(device, offScreenFrameBuf.position.mem, nullptr);
+
+		vkDestroyImageView(device, offScreenFrameBuf.normal.view, nullptr);
+		vkDestroyImage(device, offScreenFrameBuf.normal.image, nullptr);
+		vkFreeMemory(device, offScreenFrameBuf.normal.mem, nullptr);
+
+		vkDestroyImageView(device, offScreenFrameBuf.albedo.view, nullptr);
+		vkDestroyImage(device, offScreenFrameBuf.albedo.image, nullptr);
+		vkFreeMemory(device, offScreenFrameBuf.albedo.mem, nullptr);
+
+		// Depth attachment
+		vkDestroyImageView(device, offScreenFrameBuf.depth.view, nullptr);
+		vkDestroyImage(device, offScreenFrameBuf.depth.image, nullptr);
+		vkFreeMemory(device, offScreenFrameBuf.depth.mem, nullptr);
+
+		vkDestroyFramebuffer(device, offScreenFrameBuf.frameBuffer, nullptr);
+
+		vkDestroyPipeline(device, pipelines.composition, nullptr);
+		vkDestroyPipeline(device, pipelines.offscreen, nullptr);
+
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+
+		// Uniform buffers
+		uniformBuffers.offscreen.destroy();
+		uniformBuffers.composition.destroy();
+
+		vkDestroyRenderPass(device, offScreenFrameBuf.renderPass, nullptr);
+
+		textures.model.colorMap.destroy();
+		textures.model.normalMap.destroy();
+		textures.floor.colorMap.destroy();
+		textures.floor.normalMap.destroy();
+
+		vkDestroySemaphore(device, offscreenSemaphore, nullptr);
+	}
+
+	// Enable physical device features required for this example
+	virtual void getEnabledFeatures()
+	{
+		// Enable anisotropic filtering if supported
+		if (deviceFeatures.samplerAnisotropy) {
+			enabledFeatures.samplerAnisotropy = VK_TRUE;
+		}
+	};
+
+	// Create a frame buffer attachment
+	void createAttachment(
+		VkFormat format,
+		VkImageUsageFlagBits usage,
+		FrameBufferAttachment *attachment)
+	{
+		VkImageAspectFlags aspectMask = 0;
+		VkImageLayout imageLayout;
+
+		attachment->format = format;
+
+		if (usage & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT)
+		{
+			aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+			imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+		}
+		if (usage & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT)
+		{
+			aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
+			imageLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+		}
+
+		assert(aspectMask > 0);
+
+		VkImageCreateInfo image = vks::initializers::imageCreateInfo();
+		image.imageType = VK_IMAGE_TYPE_2D;
+		image.format = format;
+		image.extent.width = offScreenFrameBuf.width;
+		image.extent.height = offScreenFrameBuf.height;
+		image.extent.depth = 1;
+		image.mipLevels = 1;
+		image.arrayLayers = 1;
+		image.samples = VK_SAMPLE_COUNT_1_BIT;
+		image.tiling = VK_IMAGE_TILING_OPTIMAL;
+		image.usage = usage | VK_IMAGE_USAGE_SAMPLED_BIT;
+
+		VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo();
+		VkMemoryRequirements memReqs;
+
+		VK_CHECK_RESULT(vkCreateImage(device, &image, nullptr, &attachment->image));
+		vkGetImageMemoryRequirements(device, attachment->image, &memReqs);
+		memAlloc.allocationSize = memReqs.size;
+		memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+		VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &attachment->mem));
+		VK_CHECK_RESULT(vkBindImageMemory(device, attachment->image, attachment->mem, 0));
+
+		VkImageViewCreateInfo imageView = vks::initializers::imageViewCreateInfo();
+		imageView.viewType = VK_IMAGE_VIEW_TYPE_2D;
+		imageView.format = format;
+		imageView.subresourceRange = {};
+		imageView.subresourceRange.aspectMask = aspectMask;
+		imageView.subresourceRange.baseMipLevel = 0;
+		imageView.subresourceRange.levelCount = 1;
+		imageView.subresourceRange.baseArrayLayer = 0;
+		imageView.subresourceRange.layerCount = 1;
+		imageView.image = attachment->image;
+		VK_CHECK_RESULT(vkCreateImageView(device, &imageView, nullptr, &attachment->view));
+	}
+
+	// Prepare a new framebuffer and attachments for offscreen rendering (G-Buffer)
+	void prepareOffscreenFramebuffer()
+	{
+		offScreenFrameBuf.width = FB_DIM;
+		offScreenFrameBuf.height = FB_DIM;
+
+		// Color attachments
+
+		// (World space) Positions
+		createAttachment(
+			VK_FORMAT_R16G16B16A16_SFLOAT,
+			VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
+			&offScreenFrameBuf.position);
+
+		// (World space) Normals
+		createAttachment(
+			VK_FORMAT_R16G16B16A16_SFLOAT,
+			VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
+			&offScreenFrameBuf.normal);
+
+		// Albedo (color)
+		createAttachment(
+			VK_FORMAT_R8G8B8A8_UNORM,
+			VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
+			&offScreenFrameBuf.albedo);
+
+		// Depth attachment
+
+		// Find a suitable depth format
+		VkFormat attDepthFormat;
+		VkBool32 validDepthFormat = vks::tools::getSupportedDepthFormat(physicalDevice, &attDepthFormat);
+		assert(validDepthFormat);
+
+		createAttachment(
+			attDepthFormat,
+			VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT,
+			&offScreenFrameBuf.depth);
+
+		// Set up separate renderpass with references to the color and depth attachments
+		std::array<VkAttachmentDescription, 4> attachmentDescs = {};
+
+		// Init attachment properties
+		for (uint32_t i = 0; i < 4; ++i)
+		{
+			attachmentDescs[i].samples = VK_SAMPLE_COUNT_1_BIT;
+			attachmentDescs[i].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+			attachmentDescs[i].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+			attachmentDescs[i].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+			attachmentDescs[i].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+			if (i == 3)
+			{
+				attachmentDescs[i].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+				attachmentDescs[i].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+			}
+			else
+			{
+				attachmentDescs[i].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+				attachmentDescs[i].finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+			}
+		}
+
+		// Formats
+		attachmentDescs[0].format = offScreenFrameBuf.position.format;
+		attachmentDescs[1].format = offScreenFrameBuf.normal.format;
+		attachmentDescs[2].format = offScreenFrameBuf.albedo.format;
+		attachmentDescs[3].format = offScreenFrameBuf.depth.format;
+
+		std::vector<VkAttachmentReference> colorReferences;
+		colorReferences.push_back({ 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL });
+		colorReferences.push_back({ 1, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL });
+		colorReferences.push_back({ 2, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL });
+
+		VkAttachmentReference depthReference = {};
+		depthReference.attachment = 3;
+		depthReference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+
+		VkSubpassDescription subpass = {};
+		subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+		subpass.pColorAttachments = colorReferences.data();
+		subpass.colorAttachmentCount = static_cast<uint32_t>(colorReferences.size());
+		subpass.pDepthStencilAttachment = &depthReference;
+
+		// Use subpass dependencies for attachment layout transitions
+		std::array<VkSubpassDependency, 2> dependencies;
+
+		dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
+		dependencies[0].dstSubpass = 0;
+		dependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+		dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+		dependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
+		dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+		dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+		dependencies[1].srcSubpass = 0;
+		dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
+		dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+		dependencies[1].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+		dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+		dependencies[1].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
+		dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+		VkRenderPassCreateInfo renderPassInfo = {};
+		renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
+		renderPassInfo.pAttachments = attachmentDescs.data();
+		renderPassInfo.attachmentCount = static_cast<uint32_t>(attachmentDescs.size());
+		renderPassInfo.subpassCount = 1;
+		renderPassInfo.pSubpasses = &subpass;
+		renderPassInfo.dependencyCount = 2;
+		renderPassInfo.pDependencies = dependencies.data();
+
+		VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassInfo, nullptr, &offScreenFrameBuf.renderPass));
+
+		std::array<VkImageView,4> attachments;
+		attachments[0] = offScreenFrameBuf.position.view;
+		attachments[1] = offScreenFrameBuf.normal.view;
+		attachments[2] = offScreenFrameBuf.albedo.view;
+		attachments[3] = offScreenFrameBuf.depth.view;
+
+		VkFramebufferCreateInfo fbufCreateInfo = {};
+		fbufCreateInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
+		fbufCreateInfo.pNext = NULL;
+		fbufCreateInfo.renderPass = offScreenFrameBuf.renderPass;
+		fbufCreateInfo.pAttachments = attachments.data();
+		fbufCreateInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
+		fbufCreateInfo.width = offScreenFrameBuf.width;
+		fbufCreateInfo.height = offScreenFrameBuf.height;
+		fbufCreateInfo.layers = 1;
+		VK_CHECK_RESULT(vkCreateFramebuffer(device, &fbufCreateInfo, nullptr, &offScreenFrameBuf.frameBuffer));
+
+		// Create sampler to sample from the color attachments
+		VkSamplerCreateInfo sampler = vks::initializers::samplerCreateInfo();
+		sampler.magFilter = VK_FILTER_NEAREST;
+		sampler.minFilter = VK_FILTER_NEAREST;
+		sampler.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+		sampler.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+		sampler.addressModeV = sampler.addressModeU;
+		sampler.addressModeW = sampler.addressModeU;
+		sampler.mipLodBias = 0.0f;
+		sampler.maxAnisotropy = 1.0f;
+		sampler.minLod = 0.0f;
+		sampler.maxLod = 1.0f;
+		sampler.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
+		VK_CHECK_RESULT(vkCreateSampler(device, &sampler, nullptr, &colorSampler));
+	}
+
+	// Build command buffer for rendering the scene to the offscreen frame buffer attachments
+	void buildDeferredCommandBuffer()
+	{
+		if (offScreenCmdBuffer == VK_NULL_HANDLE)
+		{
+			offScreenCmdBuffer = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, false);
+		}
+
+		// Create a semaphore used to synchronize offscreen rendering and usage
+		VkSemaphoreCreateInfo semaphoreCreateInfo = vks::initializers::semaphoreCreateInfo();
+		VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &offscreenSemaphore));
+
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		// Clear values for all attachments written in the fragment shader
+		std::array<VkClearValue,4> clearValues;
+		clearValues[0].color = { { 0.0f, 0.0f, 0.0f, 0.0f } };
+		clearValues[1].color = { { 0.0f, 0.0f, 0.0f, 0.0f } };
+		clearValues[2].color = { { 0.0f, 0.0f, 0.0f, 0.0f } };
+		clearValues[3].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass =  offScreenFrameBuf.renderPass;
+		renderPassBeginInfo.framebuffer = offScreenFrameBuf.frameBuffer;
+		renderPassBeginInfo.renderArea.extent.width = offScreenFrameBuf.width;
+		renderPassBeginInfo.renderArea.extent.height = offScreenFrameBuf.height;
+		renderPassBeginInfo.clearValueCount = static_cast<uint32_t>(clearValues.size());
+		renderPassBeginInfo.pClearValues = clearValues.data();
+
+		VK_CHECK_RESULT(vkBeginCommandBuffer(offScreenCmdBuffer, &cmdBufInfo));
+
+		vkCmdBeginRenderPass(offScreenCmdBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+		VkViewport viewport = vks::initializers::viewport((float)offScreenFrameBuf.width, (float)offScreenFrameBuf.height, 0.0f, 1.0f);
+		vkCmdSetViewport(offScreenCmdBuffer, 0, 1, &viewport);
+
+		VkRect2D scissor = vks::initializers::rect2D(offScreenFrameBuf.width, offScreenFrameBuf.height, 0, 0);
+		vkCmdSetScissor(offScreenCmdBuffer, 0, 1, &scissor);
+
+		vkCmdBindPipeline(offScreenCmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.offscreen);
+
+		// Background
+		vkCmdBindDescriptorSets(offScreenCmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets.floor, 0, nullptr);
+		models.floor.draw(offScreenCmdBuffer);
+
+		// Instanced object
+		vkCmdBindDescriptorSets(offScreenCmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets.model, 0, nullptr);
+		models.model.bindBuffers(offScreenCmdBuffer);
+		vkCmdDrawIndexed(offScreenCmdBuffer, models.model.indices.count, 3, 0, 0, 0);
+
+		vkCmdEndRenderPass(offScreenCmdBuffer);
+
+		VK_CHECK_RESULT(vkEndCommandBuffer(offScreenCmdBuffer));
+	}
+
+	void loadAssets()
+	{
+		const uint32_t glTFLoadingFlags = vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::PreMultiplyVertexColors | vkglTF::FileLoadingFlags::FlipY;
+		models.model.loadFromFile(getAssetPath() + "models/armor/armor.gltf", vulkanDevice, queue, glTFLoadingFlags);
+		models.floor.loadFromFile(getAssetPath() + "models/deferred_floor.gltf", vulkanDevice, queue, glTFLoadingFlags);
+		textures.model.colorMap.loadFromFile(getAssetPath() + "models/armor/colormap_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue);
+		textures.model.normalMap.loadFromFile(getAssetPath() + "models/armor/normalmap_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue);
+		textures.floor.colorMap.loadFromFile(getAssetPath() + "textures/stonefloor01_color_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue);
+		textures.floor.normalMap.loadFromFile(getAssetPath() + "textures/stonefloor01_normal_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue);
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		clearValues[0].color = { { 0.0f, 0.0f, 0.2f, 0.0f } };
+		clearValues[1].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.offset.x = 0;
+		renderPassBeginInfo.renderArea.offset.y = 0;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		renderPassBeginInfo.clearValueCount = 2;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			renderPassBeginInfo.framebuffer = frameBuffers[i];
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+			VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr);
+
+   			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.composition);
+			// Final composition as full screen quad
+			// Note: Also used for debug display if debugDisplayTarget > 0
+			vkCmdDraw(drawCmdBuffers[i], 3, 1, 0, 0);
+
+			drawUI(drawCmdBuffers[i]);
+
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void setupDescriptorPool()
+	{
+		std::vector<VkDescriptorPoolSize> poolSizes = {
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 8),
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 9)
+		};
+
+		VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 3);
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+	}
+
+	void setupDescriptorSetLayout()
+	{
+		// Deferred shading layout
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			// Binding 0 : Vertex shader uniform buffer
+			vks::initializers::descriptorSetLayoutBinding( VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0),
+			// Binding 1 : Position texture target / Scene colormap
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1),
+			// Binding 2 : Normals texture target
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 2),
+			// Binding 3 : Albedo texture target
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 3),
+			// Binding 4 : Fragment shader uniform buffer
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_FRAGMENT_BIT, 4),
+		};
+
+		VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout));
+
+		// Shared pipeline layout used by all pipelines
+		VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1);
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayout));
+	}
+
+	void setupDescriptorSet()
+	{
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets;
+		VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1);
+
+		// Image descriptors for the offscreen color attachments
+		VkDescriptorImageInfo texDescriptorPosition =
+			vks::initializers::descriptorImageInfo(
+				colorSampler,
+				offScreenFrameBuf.position.view,
+				VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+
+		VkDescriptorImageInfo texDescriptorNormal =
+			vks::initializers::descriptorImageInfo(
+				colorSampler,
+				offScreenFrameBuf.normal.view,
+				VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+
+		VkDescriptorImageInfo texDescriptorAlbedo =
+			vks::initializers::descriptorImageInfo(
+				colorSampler,
+				offScreenFrameBuf.albedo.view,
+				VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+
+		// Deferred composition
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet));
+		writeDescriptorSets = {
+			// Binding 1 : Position texture target
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &texDescriptorPosition),
+			// Binding 2 : Normals texture target
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2, &texDescriptorNormal),
+			// Binding 3 : Albedo texture target
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 3, &texDescriptorAlbedo),
+			// Binding 4 : Fragment shader uniform buffer
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 4, &uniformBuffers.composition.descriptor),
+		};
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr);
+
+		// Offscreen (scene)
+
+		// Model
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.model));
+		writeDescriptorSets = {
+			// Binding 0: Vertex shader uniform buffer
+		    vks::initializers::writeDescriptorSet(descriptorSets.model, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.offscreen.descriptor),
+			// Binding 1: Color map
+			vks::initializers::writeDescriptorSet(descriptorSets.model, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &textures.model.colorMap.descriptor),
+			// Binding 2: Normal map
+			vks::initializers::writeDescriptorSet(descriptorSets.model, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2, &textures.model.normalMap.descriptor)
+		};
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr);
+
+		// Background
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.floor));
+		writeDescriptorSets = {
+			// Binding 0: Vertex shader uniform buffer
+		    vks::initializers::writeDescriptorSet(descriptorSets.floor, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.offscreen.descriptor),
+			// Binding 1: Color map
+			vks::initializers::writeDescriptorSet(descriptorSets.floor, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &textures.floor.colorMap.descriptor),
+			// Binding 2: Normal map
+			vks::initializers::writeDescriptorSet(descriptorSets.floor, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2, &textures.floor.normalMap.descriptor)
+		};
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr);
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+		VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
+		std::vector<VkDynamicState> dynamicStateEnables = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
+		VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass);
+		pipelineCI.pInputAssemblyState = &inputAssemblyState;
+		pipelineCI.pRasterizationState = &rasterizationState;
+		pipelineCI.pColorBlendState = &colorBlendState;
+		pipelineCI.pMultisampleState = &multisampleState;
+		pipelineCI.pViewportState = &viewportState;
+		pipelineCI.pDepthStencilState = &depthStencilState;
+		pipelineCI.pDynamicState = &dynamicState;
+		pipelineCI.stageCount = static_cast<uint32_t>(shaderStages.size());
+		pipelineCI.pStages = shaderStages.data();
+
+		// Final fullscreen composition pass pipeline
+		rasterizationState.cullMode = VK_CULL_MODE_FRONT_BIT;
+		shaderStages[0] = loadShader(getShadersPath() + "deferred/deferred.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "deferred/deferred.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		// Empty vertex input state, vertices are generated by the vertex shader
+		VkPipelineVertexInputStateCreateInfo emptyInputState = vks::initializers::pipelineVertexInputStateCreateInfo();
+		pipelineCI.pVertexInputState = &emptyInputState;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.composition));
+
+		// Vertex input state from glTF model for pipeline rendering models
+		pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({vkglTF::VertexComponent::Position, vkglTF::VertexComponent::UV, vkglTF::VertexComponent::Color, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::Tangent});
+		rasterizationState.cullMode = VK_CULL_MODE_BACK_BIT;
+
+		// Offscreen pipeline
+		shaderStages[0] = loadShader(getShadersPath() + "deferred/mrt.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "deferred/mrt.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+
+		// Separate render pass
+		pipelineCI.renderPass = offScreenFrameBuf.renderPass;
+
+		// Blend attachment states required for all color attachments
+		// This is important, as color write mask will otherwise be 0x0 and you
+		// won't see anything rendered to the attachment
+		std::array<VkPipelineColorBlendAttachmentState, 3> blendAttachmentStates = {
+			vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE),
+			vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE),
+			vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE)
+		};
+
+		colorBlendState.attachmentCount = static_cast<uint32_t>(blendAttachmentStates.size());
+		colorBlendState.pAttachments = blendAttachmentStates.data();
+
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.offscreen));
+	}
+
+	// Prepare and initialize uniform buffer containing shader uniforms
+	void prepareUniformBuffers()
+	{
+		// Offscreen vertex shader
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+		    &uniformBuffers.offscreen,
+			sizeof(uboOffscreenVS)));
+
+		// Deferred fragment shader
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+		    &uniformBuffers.composition,
+			sizeof(uboComposition)));
+
+		// Map persistent
+		VK_CHECK_RESULT(uniformBuffers.offscreen.map());
+		VK_CHECK_RESULT(uniformBuffers.composition.map());
+
+		// Setup instanced model positions
+		uboOffscreenVS.instancePos[0] = glm::vec4(0.0f);
+		uboOffscreenVS.instancePos[1] = glm::vec4(-4.0f, 0.0, -4.0f, 0.0f);
+		uboOffscreenVS.instancePos[2] = glm::vec4(4.0f, 0.0, -4.0f, 0.0f);
+
+		// Update
+		updateUniformBufferOffscreen();
+		updateUniformBufferComposition();
+	}
+
+	// Update matrices used for the offscreen rendering of the scene
+	void updateUniformBufferOffscreen()
+	{
+		uboOffscreenVS.projection = camera.matrices.perspective;
+		uboOffscreenVS.view = camera.matrices.view;
+		uboOffscreenVS.model = glm::mat4(1.0f);
+		memcpy(uniformBuffers.offscreen.mapped, &uboOffscreenVS, sizeof(uboOffscreenVS));
+	}
+
+	// Update lights and parameters passed to the composition shaders
+	void updateUniformBufferComposition()
+	{
+		// White
+		uboComposition.lights[0].position = glm::vec4(0.0f, 0.0f, 1.0f, 0.0f);
+		uboComposition.lights[0].color = glm::vec3(1.5f);
+		uboComposition.lights[0].radius = 15.0f * 0.25f;
+		// Red
+		uboComposition.lights[1].position = glm::vec4(-2.0f, 0.0f, 0.0f, 0.0f);
+		uboComposition.lights[1].color = glm::vec3(1.0f, 0.0f, 0.0f);
+		uboComposition.lights[1].radius = 15.0f;
+		// Blue
+		uboComposition.lights[2].position = glm::vec4(2.0f, -1.0f, 0.0f, 0.0f);
+		uboComposition.lights[2].color = glm::vec3(0.0f, 0.0f, 2.5f);
+		uboComposition.lights[2].radius = 5.0f;
+		// Yellow
+		uboComposition.lights[3].position = glm::vec4(0.0f, -0.9f, 0.5f, 0.0f);
+		uboComposition.lights[3].color = glm::vec3(1.0f, 1.0f, 0.0f);
+		uboComposition.lights[3].radius = 2.0f;
+		// Green
+		uboComposition.lights[4].position = glm::vec4(0.0f, -0.5f, 0.0f, 0.0f);
+		uboComposition.lights[4].color = glm::vec3(0.0f, 1.0f, 0.2f);
+		uboComposition.lights[4].radius = 5.0f;
+		// Yellow
+		uboComposition.lights[5].position = glm::vec4(0.0f, -1.0f, 0.0f, 0.0f);
+		uboComposition.lights[5].color = glm::vec3(1.0f, 0.7f, 0.3f);
+		uboComposition.lights[5].radius = 25.0f;
+
+		uboComposition.lights[0].position.x = sin(glm::radians(360.0f * timer)) * 5.0f;
+		uboComposition.lights[0].position.z = cos(glm::radians(360.0f * timer)) * 5.0f;
+
+		uboComposition.lights[1].position.x = -4.0f + sin(glm::radians(360.0f * timer) + 45.0f) * 2.0f;
+		uboComposition.lights[1].position.z =  0.0f + cos(glm::radians(360.0f * timer) + 45.0f) * 2.0f;
+
+		uboComposition.lights[2].position.x = 4.0f + sin(glm::radians(360.0f * timer)) * 2.0f;
+		uboComposition.lights[2].position.z = 0.0f + cos(glm::radians(360.0f * timer)) * 2.0f;
+
+		uboComposition.lights[4].position.x = 0.0f + sin(glm::radians(360.0f * timer + 90.0f)) * 5.0f;
+		uboComposition.lights[4].position.z = 0.0f - cos(glm::radians(360.0f * timer + 45.0f)) * 5.0f;
+
+		uboComposition.lights[5].position.x = 0.0f + sin(glm::radians(-360.0f * timer + 135.0f)) * 10.0f;
+		uboComposition.lights[5].position.z = 0.0f - cos(glm::radians(-360.0f * timer - 45.0f)) * 10.0f;
+
+		// Current view position
+		uboComposition.viewPos = glm::vec4(camera.position, 0.0f) * glm::vec4(-1.0f, 1.0f, -1.0f, 1.0f);
+
+		uboComposition.debugDisplayTarget = debugDisplayTarget;
+
+		memcpy(uniformBuffers.composition.mapped, &uboComposition, sizeof(uboComposition));
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+
+		// The scene render command buffer has to wait for the offscreen
+		// rendering to be finished before we can use the framebuffer
+		// color image for sampling during final rendering
+		// To ensure this we use a dedicated offscreen synchronization
+		// semaphore that will be signaled when offscreen renderin
+		// has been finished
+		// This is necessary as an implementation may start both
+		// command buffers at the same time, there is no guarantee
+		// that command buffers will be executed in the order they
+		// have been submitted by the application
+
+		// Offscreen rendering
+
+		// Wait for swap chain presentation to finish
+		submitInfo.pWaitSemaphores = &semaphores.presentComplete;
+		// Signal ready with offscreen semaphore
+		submitInfo.pSignalSemaphores = &offscreenSemaphore;
+
+		// Submit work
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &offScreenCmdBuffer;
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+
+		// Scene rendering
+
+		// Wait for offscreen semaphore
+		submitInfo.pWaitSemaphores = &offscreenSemaphore;
+		// Signal ready with render complete semaphore
+		submitInfo.pSignalSemaphores = &semaphores.renderComplete;
+
+		// Submit work
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+
+		VulkanExampleBase::submitFrame();
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		loadAssets();
+		prepareOffscreenFramebuffer();
+		prepareUniformBuffers();
+		setupDescriptorSetLayout();
+		preparePipelines();
+		setupDescriptorPool();
+		setupDescriptorSet();
+		buildCommandBuffers();
+		buildDeferredCommandBuffer();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+		if (!paused)
+		{
+			updateUniformBufferComposition();
+		}
+		if (camera.updated)
+		{
+			updateUniformBufferOffscreen();	
+		}
+	}
+
+	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
+	{
+		if (overlay->header("Settings")) {
+			if (overlay->comboBox("Display", &debugDisplayTarget, {"Final composition", "Position", "Normals", "Albedo", "Specular" }))
+			{
+				updateUniformBufferComposition();
+			}
+		}
+	}
+};
+
+VULKAN_EXAMPLE_MAIN()
diff --git a/external/Vulkan/examples/deferredmultisampling/deferredmultisampling.cpp b/external/Vulkan/examples/deferredmultisampling/deferredmultisampling.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3e95ae45e196acddaab8a7eff08510f809471732
--- /dev/null
+++ b/external/Vulkan/examples/deferredmultisampling/deferredmultisampling.cpp
@@ -0,0 +1,682 @@
+/*
+* Vulkan Example - Multi sampling with explicit resolve for deferred shading example
+*
+* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkanexamplebase.h"
+#include "VulkanFrameBuffer.hpp"
+#include "VulkanglTFModel.h"
+
+#define ENABLE_VALIDATION false
+
+#if defined(__ANDROID__)
+// Use max. screen dimension as deferred framebuffer size
+#define FB_DIM std::max(width,height)
+#else
+#define FB_DIM 2048
+#endif
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	int32_t debugDisplayTarget = 0;
+	bool useMSAA = true;
+	bool useSampleShading = true;
+	VkSampleCountFlagBits sampleCount = VK_SAMPLE_COUNT_1_BIT;
+
+	struct {
+		struct {
+			vks::Texture2D colorMap;
+			vks::Texture2D normalMap;
+		} model;
+		struct {
+			vks::Texture2D colorMap;
+			vks::Texture2D normalMap;
+		} background;
+	} textures;
+
+	struct {
+		vkglTF::Model model;
+		vkglTF::Model background;
+	} models;
+
+	struct {
+		glm::mat4 projection;
+		glm::mat4 model;
+		glm::mat4 view;
+		glm::vec4 instancePos[3];
+	} uboOffscreenVS;
+
+	struct Light {
+		glm::vec4 position;
+		glm::vec3 color;
+		float radius;
+	};
+
+	struct {
+		Light lights[6];
+		glm::vec4 viewPos;
+		int32_t debugDisplayTarget = 0;
+	} uboComposition;
+
+	struct {
+		vks::Buffer offscreen;
+		vks::Buffer composition;
+	} uniformBuffers;
+
+	struct {
+		VkPipeline deferred;				// Deferred lighting calculation
+		VkPipeline deferredNoMSAA;			// Deferred lighting calculation with explicit MSAA resolve
+		VkPipeline offscreen;				// (Offscreen) scene rendering (fill G-Buffers)
+		VkPipeline offscreenSampleShading;	// (Offscreen) scene rendering (fill G-Buffers) with sample shading rate enabled
+	} pipelines;
+	VkPipelineLayout pipelineLayout;
+
+	struct {
+		VkDescriptorSet model;
+		VkDescriptorSet background;
+	} descriptorSets;
+
+	VkDescriptorSet descriptorSet;
+	VkDescriptorSetLayout descriptorSetLayout;
+
+	vks::Framebuffer* offscreenframeBuffers;
+
+	VkCommandBuffer offScreenCmdBuffer = VK_NULL_HANDLE;
+
+	// Semaphore used to synchronize between offscreen and final scene rendering
+	VkSemaphore offscreenSemaphore = VK_NULL_HANDLE;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Multi sampled deferred shading";
+		camera.type = Camera::CameraType::firstperson;
+		camera.movementSpeed = 5.0f;
+#ifndef __ANDROID__
+		camera.rotationSpeed = 0.25f;
+#endif
+		camera.position = { 2.15f, 0.3f, -8.75f };
+		camera.setRotation(glm::vec3(-0.75f, 12.5f, 0.0f));
+		camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 256.0f);
+		paused = true;
+	}
+
+	~VulkanExample()
+	{
+		// Clean up used Vulkan resources
+		// Note : Inherited destructor cleans up resources stored in base class
+
+		// Frame buffers
+		if (offscreenframeBuffers)
+		{
+			delete offscreenframeBuffers;
+		}
+
+		vkDestroyPipeline(device, pipelines.deferred, nullptr);
+		vkDestroyPipeline(device, pipelines.deferredNoMSAA, nullptr);
+		vkDestroyPipeline(device, pipelines.offscreen, nullptr);
+		vkDestroyPipeline(device, pipelines.offscreenSampleShading, nullptr);
+
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+
+		// Uniform buffers
+		uniformBuffers.offscreen.destroy();
+		uniformBuffers.composition.destroy();
+
+		textures.model.colorMap.destroy();
+		textures.model.normalMap.destroy();
+		textures.background.colorMap.destroy();
+		textures.background.normalMap.destroy();
+
+		vkDestroySemaphore(device, offscreenSemaphore, nullptr);
+	}
+
+	// Enable physical device features required for this example
+	virtual void getEnabledFeatures()
+	{
+		// Enable sample rate shading filtering if supported
+		if (deviceFeatures.sampleRateShading) {
+			enabledFeatures.sampleRateShading = VK_TRUE;
+		}
+		// Enable anisotropic filtering if supported
+		if (deviceFeatures.samplerAnisotropy) {
+			enabledFeatures.samplerAnisotropy = VK_TRUE;
+		}
+	};
+
+	// Prepare the framebuffer for offscreen rendering with multiple attachments used as render targets inside the fragment shaders
+	void deferredSetup()
+	{
+		offscreenframeBuffers = new vks::Framebuffer(vulkanDevice);
+
+		offscreenframeBuffers->width = FB_DIM;
+		offscreenframeBuffers->height = FB_DIM;
+
+		// Four attachments (3 color, 1 depth)
+		vks::AttachmentCreateInfo attachmentInfo = {};
+		attachmentInfo.width = FB_DIM;
+		attachmentInfo.height = FB_DIM;
+		attachmentInfo.layerCount = 1;
+		attachmentInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
+		attachmentInfo.imageSampleCount = sampleCount;
+
+		// Color attachments
+		// Attachment 0: (World space) Positions
+		attachmentInfo.format = VK_FORMAT_R16G16B16A16_SFLOAT;
+		offscreenframeBuffers->addAttachment(attachmentInfo);
+
+		// Attachment 1: (World space) Normals
+		attachmentInfo.format = VK_FORMAT_R16G16B16A16_SFLOAT;
+		offscreenframeBuffers->addAttachment(attachmentInfo);
+
+		// Attachment 2: Albedo (color)
+		attachmentInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
+		offscreenframeBuffers->addAttachment(attachmentInfo);
+
+		// Depth attachment
+		// Find a suitable depth format
+		VkFormat attDepthFormat;
+		VkBool32 validDepthFormat = vks::tools::getSupportedDepthFormat(physicalDevice, &attDepthFormat);
+		assert(validDepthFormat);
+
+		attachmentInfo.format = attDepthFormat;
+		attachmentInfo.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
+		offscreenframeBuffers->addAttachment(attachmentInfo);
+
+		// Create sampler to sample from the color attachments
+		VK_CHECK_RESULT(offscreenframeBuffers->createSampler(VK_FILTER_NEAREST, VK_FILTER_NEAREST, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE));
+
+		// Create default renderpass for the framebuffer
+		VK_CHECK_RESULT(offscreenframeBuffers->createRenderPass());
+	}
+
+	// Build command buffer for rendering the scene to the offscreen frame buffer attachments
+	void buildDeferredCommandBuffer()
+	{
+		if (offScreenCmdBuffer == VK_NULL_HANDLE) {
+			offScreenCmdBuffer = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, false);
+		}
+
+		// Create a semaphore used to synchronize offscreen rendering and usage
+		if (offscreenSemaphore == VK_NULL_HANDLE) {
+			VkSemaphoreCreateInfo semaphoreCreateInfo = vks::initializers::semaphoreCreateInfo();
+			VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &offscreenSemaphore));
+		}
+
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		// Clear values for all attachments written in the fragment shader
+		std::array<VkClearValue,4> clearValues;
+		clearValues[0].color = clearValues[1].color = { { 0.0f, 0.0f, 0.0f, 0.0f } };
+		clearValues[2].color = { { 0.0f, 0.0f, 0.0f, 0.0f } };
+		clearValues[3].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = offscreenframeBuffers->renderPass;
+		renderPassBeginInfo.framebuffer = offscreenframeBuffers->framebuffer;
+		renderPassBeginInfo.renderArea.extent.width = offscreenframeBuffers->width;
+		renderPassBeginInfo.renderArea.extent.height = offscreenframeBuffers->height;
+		renderPassBeginInfo.clearValueCount = static_cast<uint32_t>(clearValues.size());
+		renderPassBeginInfo.pClearValues = clearValues.data();
+
+		VK_CHECK_RESULT(vkBeginCommandBuffer(offScreenCmdBuffer, &cmdBufInfo));
+
+		vkCmdBeginRenderPass(offScreenCmdBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+		VkViewport viewport = vks::initializers::viewport((float)offscreenframeBuffers->width, (float)offscreenframeBuffers->height, 0.0f, 1.0f);
+		vkCmdSetViewport(offScreenCmdBuffer, 0, 1, &viewport);
+
+		VkRect2D scissor = vks::initializers::rect2D(offscreenframeBuffers->width, offscreenframeBuffers->height, 0, 0);
+		vkCmdSetScissor(offScreenCmdBuffer, 0, 1, &scissor);
+
+		vkCmdBindPipeline(offScreenCmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, useSampleShading ? pipelines.offscreenSampleShading : pipelines.offscreen);
+
+		VkDeviceSize offsets[1] = { 0 };
+
+		// Background
+		vkCmdBindDescriptorSets(offScreenCmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets.background, 0, nullptr);
+		models.background.draw(offScreenCmdBuffer);
+
+		// Object
+		vkCmdBindDescriptorSets(offScreenCmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets.model, 0, nullptr);
+		models.model.bindBuffers(offScreenCmdBuffer);
+		vkCmdDrawIndexed(offScreenCmdBuffer, models.model.indices.count, 3, 0, 0, 0);
+
+		vkCmdEndRenderPass(offScreenCmdBuffer);
+
+		VK_CHECK_RESULT(vkEndCommandBuffer(offScreenCmdBuffer));
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		clearValues[0].color = { { 0.0f, 0.0f, 0.2f, 0.0f } };
+		clearValues[1].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.offset.x = 0;
+		renderPassBeginInfo.renderArea.offset.y = 0;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		renderPassBeginInfo.clearValueCount = 2;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			// Set target frame buffer
+			renderPassBeginInfo.framebuffer = VulkanExampleBase::frameBuffers[i];
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+			VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL);
+
+			// Final composition as full screen quad
+			// Note: Also used for debug display if debugDisplayTarget > 0
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, useMSAA ? pipelines.deferred : pipelines.deferredNoMSAA);
+			vkCmdDraw(drawCmdBuffers[i], 3, 1, 0, 0);
+
+			drawUI(drawCmdBuffers[i]);
+
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void loadAssets()
+	{
+		const uint32_t glTFLoadingFlags = vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::PreMultiplyVertexColors | vkglTF::FileLoadingFlags::FlipY;
+		models.model.loadFromFile(getAssetPath() + "models/armor/armor.gltf", vulkanDevice, queue, glTFLoadingFlags);
+		models.background.loadFromFile(getAssetPath() + "models/deferred_box.gltf", vulkanDevice, queue, glTFLoadingFlags);
+		textures.model.colorMap.loadFromFile(getAssetPath() + "models/armor/colormap_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue);
+		textures.model.normalMap.loadFromFile(getAssetPath() + "models/armor/normalmap_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue);
+		textures.background.colorMap.loadFromFile(getAssetPath() + "textures/stonefloor02_color_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue);
+		textures.background.normalMap.loadFromFile(getAssetPath() + "textures/stonefloor02_normal_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue);
+	}
+
+	void setupDescriptorPool()
+	{
+		std::vector<VkDescriptorPoolSize> poolSizes = {
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 8),
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 9)
+		};
+
+		VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 3);
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+	}
+
+	void setupDescriptorSetLayout()
+	{
+		// Deferred shading layout
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			// Binding 0 : Vertex shader uniform buffer
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0),
+			// Binding 1 : Position texture target / Scene colormap
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1),
+			// Binding 2 : Normals texture target
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 2),
+			// Binding 3 : Albedo texture target
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 3),
+			// Binding 4 : Fragment shader uniform buffer
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_FRAGMENT_BIT, 4),
+		};
+
+		VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout));
+
+		// Shared pipeline layout used by all pipelines
+		VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1);
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayout));
+	}
+
+	void setupDescriptorSet()
+	{
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets;
+		VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1);
+
+		// Image descriptors for the offscreen color attachments
+		VkDescriptorImageInfo texDescriptorPosition =
+			vks::initializers::descriptorImageInfo(
+				offscreenframeBuffers->sampler,
+				offscreenframeBuffers->attachments[0].view,
+				VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+
+		VkDescriptorImageInfo texDescriptorNormal =
+			vks::initializers::descriptorImageInfo(
+				offscreenframeBuffers->sampler,
+				offscreenframeBuffers->attachments[1].view,
+				VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+
+		VkDescriptorImageInfo texDescriptorAlbedo =
+			vks::initializers::descriptorImageInfo(
+				offscreenframeBuffers->sampler,
+				offscreenframeBuffers->attachments[2].view,
+				VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+
+		// Deferred composition
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet));
+		writeDescriptorSets = {
+			// Binding 1: World space position texture
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &texDescriptorPosition),
+			// Binding 2: World space normals texture
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2, &texDescriptorNormal),
+			// Binding 3: Albedo texture
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 3, &texDescriptorAlbedo),
+			// Binding 4: Fragment shader uniform buffer
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 4, &uniformBuffers.composition.descriptor),
+		};
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL);
+
+		// Offscreen (scene)
+
+		// Model
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.model));
+		writeDescriptorSets = {
+			// Binding 0: Vertex shader uniform buffer
+			vks::initializers::writeDescriptorSet(descriptorSets.model, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.offscreen.descriptor),
+			// Binding 1: Color map
+			vks::initializers::writeDescriptorSet(descriptorSets.model, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &textures.model.colorMap.descriptor),
+			// Binding 2: Normal map
+			vks::initializers::writeDescriptorSet(descriptorSets.model, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2, &textures.model.normalMap.descriptor)
+		};
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr);
+
+		// Background
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.background));
+		writeDescriptorSets = {
+			// Binding 0: Vertex shader uniform buffer
+			vks::initializers::writeDescriptorSet(descriptorSets.background, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.offscreen.descriptor),
+			// Binding 1: Color map
+			vks::initializers::writeDescriptorSet(descriptorSets.background, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &textures.background.colorMap.descriptor),
+			// Binding 2: Normal map
+			vks::initializers::writeDescriptorSet(descriptorSets.background, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2, &textures.background.normalMap.descriptor)
+		};
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr);
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+		VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
+		std::vector<VkDynamicState> dynamicStateEnables = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
+		VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass);
+		pipelineCI.pInputAssemblyState = &inputAssemblyState;
+		pipelineCI.pRasterizationState = &rasterizationState;
+		pipelineCI.pColorBlendState = &colorBlendState;
+		pipelineCI.pMultisampleState = &multisampleState;
+		pipelineCI.pViewportState = &viewportState;
+		pipelineCI.pDepthStencilState = &depthStencilState;
+		pipelineCI.pDynamicState = &dynamicState;
+		pipelineCI.stageCount = static_cast<uint32_t>(shaderStages.size());
+		pipelineCI.pStages = shaderStages.data();
+
+		// Fullscreen composition pass
+
+		// Empty vertex input state, vertices are generated by the vertex shader
+		VkPipelineVertexInputStateCreateInfo emptyInputState = vks::initializers::pipelineVertexInputStateCreateInfo();
+		pipelineCI.pVertexInputState = &emptyInputState;
+
+		// Use specialization constants to pass number of samples to the shader (used for MSAA resolve)
+		VkSpecializationMapEntry specializationEntry{};
+		specializationEntry.constantID = 0;
+		specializationEntry.offset = 0;
+		specializationEntry.size = sizeof(uint32_t);
+
+		uint32_t specializationData = sampleCount;
+
+		VkSpecializationInfo specializationInfo;
+		specializationInfo.mapEntryCount = 1;
+		specializationInfo.pMapEntries = &specializationEntry;
+		specializationInfo.dataSize = sizeof(specializationData);
+		specializationInfo.pData = &specializationData;
+
+		rasterizationState.cullMode = VK_CULL_MODE_FRONT_BIT;
+
+		// With MSAA
+		shaderStages[0] = loadShader(getShadersPath() + "deferredmultisampling/deferred.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "deferredmultisampling/deferred.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		shaderStages[1].pSpecializationInfo = &specializationInfo;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.deferred));
+
+		// No MSAA (1 sample)
+		specializationData = 1;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.deferredNoMSAA));
+
+		// Vertex input state from glTF model for pipeline rendering models
+		pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position, vkglTF::VertexComponent::UV, vkglTF::VertexComponent::Color, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::Tangent });
+		rasterizationState.cullMode = VK_CULL_MODE_BACK_BIT;
+
+		// Offscreen scene rendering pipeline
+		// Separate render pass
+		pipelineCI.renderPass = offscreenframeBuffers->renderPass;
+
+		shaderStages[0] = loadShader(getShadersPath() + "deferredmultisampling/mrt.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "deferredmultisampling/mrt.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+
+		//rasterizationState.polygonMode = VK_POLYGON_MODE_LINE;
+		//rasterizationState.lineWidth = 2.0f;
+		multisampleState.rasterizationSamples = sampleCount;
+		multisampleState.alphaToCoverageEnable = VK_TRUE;
+
+		// Blend attachment states required for all color attachments
+		// This is important, as color write mask will otherwise be 0x0 and you
+		// won't see anything rendered to the attachment
+		std::array<VkPipelineColorBlendAttachmentState, 3> blendAttachmentStates = {
+			vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE),
+			vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE),
+			vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE)
+		};
+
+		colorBlendState.attachmentCount = static_cast<uint32_t>(blendAttachmentStates.size());
+		colorBlendState.pAttachments = blendAttachmentStates.data();
+
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.offscreen));
+
+		multisampleState.sampleShadingEnable = VK_TRUE;
+		multisampleState.minSampleShading = 0.25f;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.offscreenSampleShading));
+	}
+
+	// Prepare and initialize uniform buffer containing shader uniforms
+	void prepareUniformBuffers()
+	{
+		// Offscreen vertex shader
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.offscreen,
+			sizeof(uboOffscreenVS)));
+
+		// Deferred fragment shader
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.composition,
+			sizeof(uboComposition)));;
+
+		// Map persistent
+		VK_CHECK_RESULT(uniformBuffers.offscreen.map());
+		VK_CHECK_RESULT(uniformBuffers.composition.map());
+
+		// Init some values
+		uboOffscreenVS.instancePos[0] = glm::vec4(0.0f);
+		uboOffscreenVS.instancePos[1] = glm::vec4(-4.0f, 0.0, -4.0f, 0.0f);
+		uboOffscreenVS.instancePos[2] = glm::vec4(4.0f, 0.0, -4.0f, 0.0f);
+
+		// Update
+		updateUniformBufferOffscreen();
+		updateUniformBufferDeferredLights();
+	}
+
+	void updateUniformBufferOffscreen()
+	{
+		uboOffscreenVS.projection = camera.matrices.perspective;
+		uboOffscreenVS.view = camera.matrices.view;
+		uboOffscreenVS.model = glm::mat4(1.0f);
+		memcpy(uniformBuffers.offscreen.mapped, &uboOffscreenVS, sizeof(uboOffscreenVS));
+	}
+
+	// Update fragment shader light position uniform block
+	void updateUniformBufferDeferredLights()
+	{
+		// White
+		uboComposition.lights[0].position = glm::vec4(0.0f, 0.0f, 1.0f, 0.0f);
+		uboComposition.lights[0].color = glm::vec3(1.5f);
+		uboComposition.lights[0].radius = 15.0f * 0.25f;
+		// Red
+		uboComposition.lights[1].position = glm::vec4(-2.0f, 0.0f, 0.0f, 0.0f);
+		uboComposition.lights[1].color = glm::vec3(1.0f, 0.0f, 0.0f);
+		uboComposition.lights[1].radius = 15.0f;
+		// Blue
+		uboComposition.lights[2].position = glm::vec4(2.0f, -1.0f, 0.0f, 0.0f);
+		uboComposition.lights[2].color = glm::vec3(0.0f, 0.0f, 2.5f);
+		uboComposition.lights[2].radius = 5.0f;
+		// Yellow
+		uboComposition.lights[3].position = glm::vec4(0.0f, -0.9f, 0.5f, 0.0f);
+		uboComposition.lights[3].color = glm::vec3(1.0f, 1.0f, 0.0f);
+		uboComposition.lights[3].radius = 2.0f;
+		// Green
+		uboComposition.lights[4].position = glm::vec4(0.0f, -0.5f, 0.0f, 0.0f);
+		uboComposition.lights[4].color = glm::vec3(0.0f, 1.0f, 0.2f);
+		uboComposition.lights[4].radius = 5.0f;
+		// Yellow
+		uboComposition.lights[5].position = glm::vec4(0.0f, -1.0f, 0.0f, 0.0f);
+		uboComposition.lights[5].color = glm::vec3(1.0f, 0.7f, 0.3f);
+		uboComposition.lights[5].radius = 25.0f;
+
+		uboComposition.lights[0].position.x = sin(glm::radians(360.0f * timer)) * 5.0f;
+		uboComposition.lights[0].position.z = cos(glm::radians(360.0f * timer)) * 5.0f;
+
+		uboComposition.lights[1].position.x = -4.0f + sin(glm::radians(360.0f * timer) + 45.0f) * 2.0f;
+		uboComposition.lights[1].position.z =  0.0f + cos(glm::radians(360.0f * timer) + 45.0f) * 2.0f;
+
+		uboComposition.lights[2].position.x = 4.0f + sin(glm::radians(360.0f * timer)) * 2.0f;
+		uboComposition.lights[2].position.z = 0.0f + cos(glm::radians(360.0f * timer)) * 2.0f;
+
+		uboComposition.lights[4].position.x = 0.0f + sin(glm::radians(360.0f * timer + 90.0f)) * 5.0f;
+		uboComposition.lights[4].position.z = 0.0f - cos(glm::radians(360.0f * timer + 45.0f)) * 5.0f;
+
+		uboComposition.lights[5].position.x = 0.0f + sin(glm::radians(-360.0f * timer + 135.0f)) * 10.0f;
+		uboComposition.lights[5].position.z = 0.0f - cos(glm::radians(-360.0f * timer - 45.0f)) * 10.0f;
+
+		// Current view position
+		uboComposition.viewPos = glm::vec4(camera.position, 0.0f) * glm::vec4(-1.0f, 1.0f, -1.0f, 1.0f);
+		uboComposition.debugDisplayTarget = debugDisplayTarget;
+
+		memcpy(uniformBuffers.composition.mapped, &uboComposition, sizeof(uboComposition));
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+
+		// Offscreen rendering
+
+		// Wait for swap chain presentation to finish
+		submitInfo.pWaitSemaphores = &semaphores.presentComplete;
+		// Signal ready with offscreen semaphore
+		submitInfo.pSignalSemaphores = &offscreenSemaphore;
+
+		// Submit work
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &offScreenCmdBuffer;
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+
+		// Scene rendering
+
+		// Wait for offscreen semaphore
+		submitInfo.pWaitSemaphores = &offscreenSemaphore;
+		// Signal ready with render complete semaphore
+		submitInfo.pSignalSemaphores = &semaphores.renderComplete;
+
+		// Submit work
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+
+		VulkanExampleBase::submitFrame();
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		sampleCount = getMaxUsableSampleCount();
+		loadAssets();
+		deferredSetup();
+		prepareUniformBuffers();
+		setupDescriptorSetLayout();
+		preparePipelines();
+		setupDescriptorPool();
+		setupDescriptorSet();
+		buildCommandBuffers();
+		buildDeferredCommandBuffer();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+		if (camera.updated) 
+		{
+			updateUniformBufferOffscreen();
+		}
+	}
+
+	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
+	{
+		if (overlay->header("Settings")) {
+			if (overlay->comboBox("Display", &debugDisplayTarget, { "Final composition", "Position", "Normals", "Albedo", "Specular" }))
+			{
+				updateUniformBufferDeferredLights();
+			}
+			if (overlay->checkBox("MSAA", &useMSAA)) {
+				buildCommandBuffers();
+			}
+			if (vulkanDevice->features.sampleRateShading) {
+				if (overlay->checkBox("Sample rate shading", &useSampleShading)) {
+					buildDeferredCommandBuffer();
+				}
+			}
+		}
+	}
+
+	// Returns the maximum sample count usable by the platform
+	VkSampleCountFlagBits getMaxUsableSampleCount()
+	{
+		VkSampleCountFlags counts = std::min(deviceProperties.limits.framebufferColorSampleCounts, deviceProperties.limits.framebufferDepthSampleCounts);
+		if (counts & VK_SAMPLE_COUNT_64_BIT) { return VK_SAMPLE_COUNT_64_BIT; }
+		if (counts & VK_SAMPLE_COUNT_32_BIT) { return VK_SAMPLE_COUNT_32_BIT; }
+		if (counts & VK_SAMPLE_COUNT_16_BIT) { return VK_SAMPLE_COUNT_16_BIT; }
+		if (counts & VK_SAMPLE_COUNT_8_BIT) { return VK_SAMPLE_COUNT_8_BIT; }
+		if (counts & VK_SAMPLE_COUNT_4_BIT) { return VK_SAMPLE_COUNT_4_BIT; }
+		if (counts & VK_SAMPLE_COUNT_2_BIT) { return VK_SAMPLE_COUNT_2_BIT; }
+		return VK_SAMPLE_COUNT_1_BIT;
+	}
+};
+
+VULKAN_EXAMPLE_MAIN()
diff --git a/external/Vulkan/examples/deferredshadows/deferredshadows.cpp b/external/Vulkan/examples/deferredshadows/deferredshadows.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..73c209883493113b369164590813a5b8d605aa3b
--- /dev/null
+++ b/external/Vulkan/examples/deferredshadows/deferredshadows.cpp
@@ -0,0 +1,824 @@
+/*
+* Vulkan Example - Deferred shading with shadows from multiple light sources using geometry shader instancing
+*
+* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkanexamplebase.h"
+#include "VulkanFrameBuffer.hpp"
+#include "VulkanglTFModel.h"
+
+#define VERTEX_BUFFER_BIND_ID 0
+#define ENABLE_VALIDATION false
+
+// Shadowmap properties
+#if defined(__ANDROID__)
+#define SHADOWMAP_DIM 1024
+#else
+#define SHADOWMAP_DIM 2048
+#endif
+// 16 bits of depth is enough for such a small scene
+#define SHADOWMAP_FORMAT VK_FORMAT_D32_SFLOAT_S8_UINT
+
+#if defined(__ANDROID__)
+// Use max. screen dimension as deferred framebuffer size
+#define FB_DIM std::max(width,height)
+#else
+#define FB_DIM 2048
+#endif
+
+// Must match the LIGHT_COUNT define in the shadow and deferred shaders
+#define LIGHT_COUNT 3
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	int32_t debugDisplayTarget = 0;
+	bool enableShadows = true;
+
+	// Keep depth range as small as possible
+	// for better shadow map precision
+	float zNear = 0.1f;
+	float zFar = 64.0f;
+	float lightFOV = 100.0f;
+
+	// Depth bias (and slope) are used to avoid shadowing artifacts
+	float depthBiasConstant = 1.25f;
+	float depthBiasSlope = 1.75f;
+
+	struct {
+		struct {
+			vks::Texture2D colorMap;
+			vks::Texture2D normalMap;
+		} model;
+		struct {
+			vks::Texture2D colorMap;
+			vks::Texture2D normalMap;
+		} background;
+	} textures;
+
+	struct {
+		vkglTF::Model model;
+		vkglTF::Model background;
+	} models;
+
+	struct {
+		glm::mat4 projection;
+		glm::mat4 model;
+		glm::mat4 view;
+		glm::vec4 instancePos[3];
+		int layer;
+	} uboOffscreenVS;
+
+	// This UBO stores the shadow matrices for all of the light sources
+	// The matrices are indexed using geometry shader instancing
+	// The instancePos is used to place the models using instanced draws
+	struct {
+		glm::mat4 mvp[LIGHT_COUNT];
+		glm::vec4 instancePos[3];
+	} uboShadowGeometryShader;
+
+	struct Light {
+		glm::vec4 position;
+		glm::vec4 target;
+		glm::vec4 color;
+		glm::mat4 viewMatrix;
+	};
+
+	struct {
+		glm::vec4 viewPos;
+		Light lights[LIGHT_COUNT];
+		uint32_t useShadows = 1;
+		int32_t debugDisplayTarget = 0;
+	} uboComposition;
+
+	struct {
+		vks::Buffer offscreen;
+		vks::Buffer composition;
+		vks::Buffer shadowGeometryShader;
+	} uniformBuffers;
+
+	struct {
+		VkPipeline deferred;
+		VkPipeline offscreen;
+		VkPipeline shadowpass;
+	} pipelines;
+	VkPipelineLayout pipelineLayout;
+
+	struct {
+		VkDescriptorSet model;
+		VkDescriptorSet background;
+		VkDescriptorSet shadow;
+	} descriptorSets;
+
+	VkDescriptorSet descriptorSet;
+	VkDescriptorSetLayout descriptorSetLayout;
+
+	struct
+	{
+		// Framebuffer resources for the deferred pass
+		vks::Framebuffer *deferred;
+		// Framebuffer resources for the shadow pass
+		vks::Framebuffer *shadow;
+	} frameBuffers;
+
+	struct {
+		VkCommandBuffer deferred = VK_NULL_HANDLE;
+	} commandBuffers;
+
+	// Semaphore used to synchronize between offscreen and final scene rendering
+	VkSemaphore offscreenSemaphore = VK_NULL_HANDLE;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Deferred shading with shadows";
+		camera.type = Camera::CameraType::firstperson;
+#if defined(__ANDROID__)
+		camera.movementSpeed = 2.5f;
+#else
+		camera.movementSpeed = 5.0f;
+		camera.rotationSpeed = 0.25f;
+#endif
+		camera.position = { 2.15f, 0.3f, -8.75f };
+		camera.setRotation(glm::vec3(-0.75f, 12.5f, 0.0f));
+		camera.setPerspective(60.0f, (float)width / (float)height, zNear, zFar);
+		timerSpeed *= 0.25f;
+		paused = true;
+	}
+
+	~VulkanExample()
+	{
+		// Frame buffers
+		if (frameBuffers.deferred)
+		{
+			delete frameBuffers.deferred;
+		}
+		if (frameBuffers.shadow)
+		{
+			delete frameBuffers.shadow;
+		}
+
+		vkDestroyPipeline(device, pipelines.deferred, nullptr);
+		vkDestroyPipeline(device, pipelines.offscreen, nullptr);
+		vkDestroyPipeline(device, pipelines.shadowpass, nullptr);
+
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+
+		// Uniform buffers
+		uniformBuffers.composition.destroy();
+		uniformBuffers.offscreen.destroy();
+		uniformBuffers.shadowGeometryShader.destroy();
+
+		// Textures
+		textures.model.colorMap.destroy();
+		textures.model.normalMap.destroy();
+		textures.background.colorMap.destroy();
+		textures.background.normalMap.destroy();
+
+		vkDestroySemaphore(device, offscreenSemaphore, nullptr);
+	}
+
+	// Enable physical device features required for this example
+	virtual void getEnabledFeatures()
+	{
+		// Geometry shader support is required for writing to multiple shadow map layers in one single pass
+		if (deviceFeatures.geometryShader) {
+			enabledFeatures.geometryShader = VK_TRUE;
+		}
+		else {
+			vks::tools::exitFatal("Selected GPU does not support geometry shaders!", VK_ERROR_FEATURE_NOT_PRESENT);
+		}
+		// Enable anisotropic filtering if supported
+		if (deviceFeatures.samplerAnisotropy) {
+			enabledFeatures.samplerAnisotropy = VK_TRUE;
+		}
+		// Enable texture compression
+		if (deviceFeatures.textureCompressionBC) {
+			enabledFeatures.textureCompressionBC = VK_TRUE;
+		}
+		else if (deviceFeatures.textureCompressionASTC_LDR) {
+			enabledFeatures.textureCompressionASTC_LDR = VK_TRUE;
+		}
+		else if (deviceFeatures.textureCompressionETC2) {
+			enabledFeatures.textureCompressionETC2 = VK_TRUE;
+		}
+	}
+
+	// Prepare a layered shadow map with each layer containing depth from a light's point of view
+	// The shadow mapping pass uses geometry shader instancing to output the scene from the different
+	// light sources' point of view to the layers of the depth attachment in one single pass
+	void shadowSetup()
+	{
+		frameBuffers.shadow = new vks::Framebuffer(vulkanDevice);
+
+		frameBuffers.shadow->width = SHADOWMAP_DIM;
+		frameBuffers.shadow->height = SHADOWMAP_DIM;
+
+		// Create a layered depth attachment for rendering the depth maps from the lights' point of view
+		// Each layer corresponds to one of the lights
+		// The actual output to the separate layers is done in the geometry shader using shader instancing
+		// We will pass the matrices of the lights to the GS that selects the layer by the current invocation
+		vks::AttachmentCreateInfo attachmentInfo = {};
+		attachmentInfo.format = SHADOWMAP_FORMAT;
+		attachmentInfo.width = SHADOWMAP_DIM;
+		attachmentInfo.height = SHADOWMAP_DIM;
+		attachmentInfo.layerCount = LIGHT_COUNT;
+		attachmentInfo.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
+		frameBuffers.shadow->addAttachment(attachmentInfo);
+
+		// Create sampler to sample from to depth attachment
+		// Used to sample in the fragment shader for shadowed rendering
+		VK_CHECK_RESULT(frameBuffers.shadow->createSampler(VK_FILTER_LINEAR, VK_FILTER_LINEAR, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE));
+
+		// Create default renderpass for the framebuffer
+		VK_CHECK_RESULT(frameBuffers.shadow->createRenderPass());
+	}
+
+	// Prepare the framebuffer for offscreen rendering with multiple attachments used as render targets inside the fragment shaders
+	void deferredSetup()
+	{
+		frameBuffers.deferred = new vks::Framebuffer(vulkanDevice);
+
+		frameBuffers.deferred->width = FB_DIM;
+		frameBuffers.deferred->height = FB_DIM;
+
+		// Four attachments (3 color, 1 depth)
+		vks::AttachmentCreateInfo attachmentInfo = {};
+		attachmentInfo.width = FB_DIM;
+		attachmentInfo.height = FB_DIM;
+		attachmentInfo.layerCount = 1;
+		attachmentInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
+
+		// Color attachments
+		// Attachment 0: (World space) Positions
+		attachmentInfo.format = VK_FORMAT_R16G16B16A16_SFLOAT;
+		frameBuffers.deferred->addAttachment(attachmentInfo);
+
+		// Attachment 1: (World space) Normals
+		attachmentInfo.format = VK_FORMAT_R16G16B16A16_SFLOAT;
+		frameBuffers.deferred->addAttachment(attachmentInfo);
+
+		// Attachment 2: Albedo (color)
+		attachmentInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
+		frameBuffers.deferred->addAttachment(attachmentInfo);
+
+		// Depth attachment
+		// Find a suitable depth format
+		VkFormat attDepthFormat;
+		VkBool32 validDepthFormat = vks::tools::getSupportedDepthFormat(physicalDevice, &attDepthFormat);
+		assert(validDepthFormat);
+
+		attachmentInfo.format = attDepthFormat;
+		attachmentInfo.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
+		frameBuffers.deferred->addAttachment(attachmentInfo);
+
+		// Create sampler to sample from the color attachments
+		VK_CHECK_RESULT(frameBuffers.deferred->createSampler(VK_FILTER_NEAREST, VK_FILTER_NEAREST, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE));
+
+		// Create default renderpass for the framebuffer
+		VK_CHECK_RESULT(frameBuffers.deferred->createRenderPass());
+	}
+
+	// Put render commands for the scene into the given command buffer
+	void renderScene(VkCommandBuffer cmdBuffer, bool shadow)
+	{
+		VkDeviceSize offsets[1] = { 0 };
+
+		// Background
+		vkCmdBindDescriptorSets(cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, shadow ? &descriptorSets.shadow : &descriptorSets.background, 0, NULL);
+		models.background.draw(cmdBuffer);
+
+		// Objects
+		vkCmdBindDescriptorSets(cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, shadow ? &descriptorSets.shadow : &descriptorSets.model, 0, NULL);
+		models.model.bindBuffers(cmdBuffer);
+		vkCmdDrawIndexed(cmdBuffer, models.model.indices.count, 3, 0, 0, 0);
+	}
+
+	// Build a secondary command buffer for rendering the scene values to the offscreen frame buffer attachments
+	void buildDeferredCommandBuffer()
+	{
+		if (commandBuffers.deferred == VK_NULL_HANDLE)
+		{
+			commandBuffers.deferred = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, false);
+		}
+
+		// Create a semaphore used to synchronize offscreen rendering and usage
+		VkSemaphoreCreateInfo semaphoreCreateInfo = vks::initializers::semaphoreCreateInfo();
+		VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &offscreenSemaphore));
+
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		std::array<VkClearValue, 4> clearValues = {};
+		VkViewport viewport;
+		VkRect2D scissor;
+
+		// First pass: Shadow map generation
+		// -------------------------------------------------------------------------------------------------------
+
+		clearValues[0].depthStencil = { 1.0f, 0 };
+
+		renderPassBeginInfo.renderPass = frameBuffers.shadow->renderPass;
+		renderPassBeginInfo.framebuffer = frameBuffers.shadow->framebuffer;
+		renderPassBeginInfo.renderArea.extent.width = frameBuffers.shadow->width;
+		renderPassBeginInfo.renderArea.extent.height = frameBuffers.shadow->height;
+		renderPassBeginInfo.clearValueCount = 1;
+		renderPassBeginInfo.pClearValues = clearValues.data();
+
+		VK_CHECK_RESULT(vkBeginCommandBuffer(commandBuffers.deferred, &cmdBufInfo));
+
+		viewport = vks::initializers::viewport((float)frameBuffers.shadow->width, (float)frameBuffers.shadow->height, 0.0f, 1.0f);
+		vkCmdSetViewport(commandBuffers.deferred, 0, 1, &viewport);
+
+		scissor = vks::initializers::rect2D(frameBuffers.shadow->width, frameBuffers.shadow->height, 0, 0);
+		vkCmdSetScissor(commandBuffers.deferred, 0, 1, &scissor);
+
+		// Set depth bias (aka "Polygon offset")
+		vkCmdSetDepthBias(
+			commandBuffers.deferred,
+			depthBiasConstant,
+			0.0f,
+			depthBiasSlope);
+
+		vkCmdBeginRenderPass(commandBuffers.deferred, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+		vkCmdBindPipeline(commandBuffers.deferred, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.shadowpass);
+		renderScene(commandBuffers.deferred, true);
+		vkCmdEndRenderPass(commandBuffers.deferred);
+
+		// Second pass: Deferred calculations
+		// -------------------------------------------------------------------------------------------------------
+
+		// Clear values for all attachments written in the fragment shader
+		clearValues[0].color = { { 0.0f, 0.0f, 0.0f, 0.0f } };
+		clearValues[1].color = { { 0.0f, 0.0f, 0.0f, 0.0f } };
+		clearValues[2].color = { { 0.0f, 0.0f, 0.0f, 0.0f } };
+		clearValues[3].depthStencil = { 1.0f, 0 };
+
+		renderPassBeginInfo.renderPass = frameBuffers.deferred->renderPass;
+		renderPassBeginInfo.framebuffer = frameBuffers.deferred->framebuffer;
+		renderPassBeginInfo.renderArea.extent.width = frameBuffers.deferred->width;
+		renderPassBeginInfo.renderArea.extent.height = frameBuffers.deferred->height;
+		renderPassBeginInfo.clearValueCount = static_cast<uint32_t>(clearValues.size());
+		renderPassBeginInfo.pClearValues = clearValues.data();
+
+		vkCmdBeginRenderPass(commandBuffers.deferred, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+		viewport = vks::initializers::viewport((float)frameBuffers.deferred->width, (float)frameBuffers.deferred->height, 0.0f, 1.0f);
+		vkCmdSetViewport(commandBuffers.deferred, 0, 1, &viewport);
+
+		scissor = vks::initializers::rect2D(frameBuffers.deferred->width, frameBuffers.deferred->height, 0, 0);
+		vkCmdSetScissor(commandBuffers.deferred, 0, 1, &scissor);
+
+		vkCmdBindPipeline(commandBuffers.deferred, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.offscreen);
+		renderScene(commandBuffers.deferred, false);
+		vkCmdEndRenderPass(commandBuffers.deferred);
+
+		VK_CHECK_RESULT(vkEndCommandBuffer(commandBuffers.deferred));
+	}
+
+	void loadAssets()
+	{
+		const uint32_t glTFLoadingFlags = vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::PreMultiplyVertexColors | vkglTF::FileLoadingFlags::FlipY;
+		models.model.loadFromFile(getAssetPath() + "models/armor/armor.gltf", vulkanDevice, queue, glTFLoadingFlags);
+		models.background.loadFromFile(getAssetPath() + "models/deferred_box.gltf", vulkanDevice, queue, glTFLoadingFlags);
+		textures.model.colorMap.loadFromFile(getAssetPath() + "models/armor/colormap_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue);
+		textures.model.normalMap.loadFromFile(getAssetPath() + "models/armor/normalmap_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue);
+		textures.background.colorMap.loadFromFile(getAssetPath() + "textures/stonefloor02_color_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue);
+		textures.background.normalMap.loadFromFile(getAssetPath() + "textures/stonefloor02_normal_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue);
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		clearValues[0].color = { { 0.0f, 0.0f, 0.2f, 0.0f } };
+		clearValues[1].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.offset.x = 0;
+		renderPassBeginInfo.renderArea.offset.y = 0;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		renderPassBeginInfo.clearValueCount = 2;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			// Set target frame buffer
+			renderPassBeginInfo.framebuffer = VulkanExampleBase::frameBuffers[i];
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+			VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+			VkDeviceSize offsets[1] = { 0 };
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr);
+
+			// Final composition as full screen quad
+			// Note: Also used for debug display if debugDisplayTarget > 0
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.deferred);
+			vkCmdDraw(drawCmdBuffers[i], 3, 1, 0, 0);
+
+			drawUI(drawCmdBuffers[i]);
+
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void setupDescriptorPool()
+	{
+		std::vector<VkDescriptorPoolSize> poolSizes =
+		{
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 12), //todo: separate set layouts
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 16)
+		};
+
+		VkDescriptorPoolCreateInfo descriptorPoolInfo =
+			vks::initializers::descriptorPoolCreateInfo(
+				static_cast<uint32_t>(poolSizes.size()),
+				poolSizes.data(),
+				4);
+
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+	}
+
+	void setupDescriptorSetLayout()
+	{
+		// // Deferred shading layout
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			// Binding 0: Vertex shader uniform buffer
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_GEOMETRY_BIT, 0),
+			// Binding 1: Position texture
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1),
+			// Binding 2: Normals texture
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 2),
+			// Binding 3: Albedo texture
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 3),
+			// Binding 4: Fragment shader uniform buffer
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_FRAGMENT_BIT, 4),
+			// Binding 5: Shadow map
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 5),
+		};
+		VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout));
+
+		// Shared pipeline layout used by all pipelines
+		VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1);
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayout));
+	}
+
+	void setupDescriptorSet()
+	{
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets;
+		VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1);
+
+		// Image descriptors for the offscreen color attachments
+		VkDescriptorImageInfo texDescriptorPosition =
+			vks::initializers::descriptorImageInfo(
+				frameBuffers.deferred->sampler,
+				frameBuffers.deferred->attachments[0].view,
+				VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+
+		VkDescriptorImageInfo texDescriptorNormal =
+			vks::initializers::descriptorImageInfo(
+				frameBuffers.deferred->sampler,
+				frameBuffers.deferred->attachments[1].view,
+				VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+
+		VkDescriptorImageInfo texDescriptorAlbedo =
+			vks::initializers::descriptorImageInfo(
+				frameBuffers.deferred->sampler,
+				frameBuffers.deferred->attachments[2].view,
+				VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+
+		VkDescriptorImageInfo texDescriptorShadowMap =
+			vks::initializers::descriptorImageInfo(
+				frameBuffers.shadow->sampler,
+				frameBuffers.shadow->attachments[0].view,
+				VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL);
+
+		// Deferred composition
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet));
+		writeDescriptorSets = {
+			// Binding 1: World space position texture
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &texDescriptorPosition),
+			// Binding 2: World space normals texture
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2, &texDescriptorNormal),
+			// Binding 3: Albedo texture
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 3, &texDescriptorAlbedo),
+			// Binding 4: Fragment shader uniform buffer
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 4, &uniformBuffers.composition.descriptor),
+			// Binding 5: Shadow map
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 5, &texDescriptorShadowMap),
+		};
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL);
+
+		// Offscreen (scene)
+
+		// Model
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.model));
+		writeDescriptorSets = {
+			// Binding 0: Vertex shader uniform buffer
+			vks::initializers::writeDescriptorSet(descriptorSets.model, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.offscreen.descriptor),
+			// Binding 1: Color map
+			vks::initializers::writeDescriptorSet(descriptorSets.model, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &textures.model.colorMap.descriptor),
+			// Binding 2: Normal map
+			vks::initializers::writeDescriptorSet(descriptorSets.model, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2, &textures.model.normalMap.descriptor)
+		};
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr);
+
+		// Background
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.background));
+		writeDescriptorSets = {
+			// Binding 0: Vertex shader uniform buffer
+			vks::initializers::writeDescriptorSet(descriptorSets.background, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.offscreen.descriptor),
+			// Binding 1: Color map
+			vks::initializers::writeDescriptorSet(descriptorSets.background, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &textures.background.colorMap.descriptor),
+			// Binding 2: Normal map
+			vks::initializers::writeDescriptorSet(descriptorSets.background, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2, &textures.background.normalMap.descriptor)
+		};
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr);
+
+		// Shadow mapping
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.shadow));
+		writeDescriptorSets = {
+			// Binding 0: Vertex shader uniform buffer
+			vks::initializers::writeDescriptorSet(descriptorSets.shadow, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.shadowGeometryShader.descriptor),
+		};
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr);
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+		VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
+		std::vector<VkDynamicState> dynamicStateEnables = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
+		VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass);
+		pipelineCI.pInputAssemblyState = &inputAssemblyState;
+		pipelineCI.pRasterizationState = &rasterizationState;
+		pipelineCI.pColorBlendState = &colorBlendState;
+		pipelineCI.pMultisampleState = &multisampleState;
+		pipelineCI.pViewportState = &viewportState;
+		pipelineCI.pDepthStencilState = &depthStencilState;
+		pipelineCI.pDynamicState = &dynamicState;
+		pipelineCI.stageCount = static_cast<uint32_t>(shaderStages.size());
+		pipelineCI.pStages = shaderStages.data();
+
+		// Final fullscreen composition pass pipeline
+		rasterizationState.cullMode = VK_CULL_MODE_FRONT_BIT;
+		shaderStages[0] = loadShader(getShadersPath() + "deferredshadows/deferred.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "deferredshadows/deferred.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		// Empty vertex input state, vertices are generated by the vertex shader
+		VkPipelineVertexInputStateCreateInfo emptyInputState = vks::initializers::pipelineVertexInputStateCreateInfo();
+		pipelineCI.pVertexInputState = &emptyInputState;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.deferred));
+
+		// Vertex input state from glTF model for pipeline rendering models
+		pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position, vkglTF::VertexComponent::UV, vkglTF::VertexComponent::Color, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::Tangent });
+		rasterizationState.cullMode = VK_CULL_MODE_BACK_BIT;
+
+		// Offscreen pipeline
+		// Separate render pass
+		pipelineCI.renderPass = frameBuffers.deferred->renderPass;
+
+		// Blend attachment states required for all color attachments
+		// This is important, as color write mask will otherwise be 0x0 and you
+		// won't see anything rendered to the attachment
+		std::array<VkPipelineColorBlendAttachmentState, 3> blendAttachmentStates =
+		{
+			vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE),
+			vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE),
+			vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE)
+		};
+		colorBlendState.attachmentCount = static_cast<uint32_t>(blendAttachmentStates.size());
+		colorBlendState.pAttachments = blendAttachmentStates.data();
+
+		shaderStages[0] = loadShader(getShadersPath() + "deferredshadows/mrt.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "deferredshadows/mrt.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.offscreen));
+
+		// Shadow mapping pipeline
+		// The shadow mapping pipeline uses geometry shader instancing (invocations layout modifier) to output
+		// shadow maps for multiple lights sources into the different shadow map layers in one single render pass
+		std::array<VkPipelineShaderStageCreateInfo, 2> shadowStages;
+		shadowStages[0] = loadShader(getShadersPath() + "deferredshadows/shadow.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shadowStages[1] = loadShader(getShadersPath() + "deferredshadows/shadow.geom.spv", VK_SHADER_STAGE_GEOMETRY_BIT);
+
+		pipelineCI.pStages = shadowStages.data();
+		pipelineCI.stageCount = static_cast<uint32_t>(shadowStages.size());
+
+		// Shadow pass doesn't use any color attachments
+		colorBlendState.attachmentCount = 0;
+		colorBlendState.pAttachments = nullptr;
+		// Cull front faces
+		rasterizationState.cullMode = VK_CULL_MODE_FRONT_BIT;
+		depthStencilState.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL;
+		// Enable depth bias
+		rasterizationState.depthBiasEnable = VK_TRUE;
+		// Add depth bias to dynamic state, so we can change it at runtime
+		dynamicStateEnables.push_back(VK_DYNAMIC_STATE_DEPTH_BIAS);
+		dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
+		// Reset blend attachment state
+		pipelineCI.renderPass = frameBuffers.shadow->renderPass;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.shadowpass));
+	}
+
+	// Prepare and initialize uniform buffer containing shader uniforms
+	void prepareUniformBuffers()
+	{
+		// Offscreen vertex shader
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.offscreen,
+			sizeof(uboOffscreenVS)));
+
+		// Deferred fragment shader
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.composition,
+			sizeof(uboComposition)));;
+
+		// Shadow map vertex shader (matrices from shadow's pov)
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.shadowGeometryShader,
+			sizeof(uboShadowGeometryShader)));
+
+		// Map persistent
+		VK_CHECK_RESULT(uniformBuffers.offscreen.map());
+		VK_CHECK_RESULT(uniformBuffers.composition.map());
+		VK_CHECK_RESULT(uniformBuffers.shadowGeometryShader.map());
+
+		// Init some values
+		uboOffscreenVS.instancePos[0] = glm::vec4(0.0f);
+		uboOffscreenVS.instancePos[1] = glm::vec4(-4.0f, 0.0, -4.0f, 0.0f);
+		uboOffscreenVS.instancePos[2] = glm::vec4(4.0f, 0.0, -4.0f, 0.0f);
+
+		uboOffscreenVS.instancePos[1] = glm::vec4(-7.0f, 0.0, -4.0f, 0.0f);
+		uboOffscreenVS.instancePos[2] = glm::vec4(4.0f, 0.0, -6.0f, 0.0f);
+
+		// Update
+		updateUniformBufferOffscreen();
+		updateUniformBufferDeferredLights();
+	}
+
+	void updateUniformBufferOffscreen()
+	{
+		uboOffscreenVS.projection = camera.matrices.perspective;
+		uboOffscreenVS.view = camera.matrices.view;
+		uboOffscreenVS.model = glm::mat4(1.0f);
+		memcpy(uniformBuffers.offscreen.mapped, &uboOffscreenVS, sizeof(uboOffscreenVS));
+	}
+
+	Light initLight(glm::vec3 pos, glm::vec3 target, glm::vec3 color)
+	{
+		Light light;
+		light.position = glm::vec4(pos, 1.0f);
+		light.target = glm::vec4(target, 0.0f);
+		light.color = glm::vec4(color, 0.0f);
+		return light;
+	}
+
+	void initLights()
+	{
+		uboComposition.lights[0] = initLight(glm::vec3(-14.0f, -0.5f, 15.0f), glm::vec3(-2.0f, 0.0f, 0.0f), glm::vec3(1.0f, 0.5f, 0.5f));
+		uboComposition.lights[1] = initLight(glm::vec3(14.0f, -4.0f, 12.0f), glm::vec3(2.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f));
+		uboComposition.lights[2] = initLight(glm::vec3(0.0f, -10.0f, 4.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(1.0f, 1.0f, 1.0f));
+	}
+
+	// Update fragment shader light position uniform block
+	void updateUniformBufferDeferredLights()
+	{
+		// Animate
+		uboComposition.lights[0].position.x = -14.0f + std::abs(sin(glm::radians(timer * 360.0f)) * 20.0f);
+		uboComposition.lights[0].position.z = 15.0f + cos(glm::radians(timer *360.0f)) * 1.0f;
+
+		uboComposition.lights[1].position.x = 14.0f - std::abs(sin(glm::radians(timer * 360.0f)) * 2.5f);
+		uboComposition.lights[1].position.z = 13.0f + cos(glm::radians(timer *360.0f)) * 4.0f;
+
+		uboComposition.lights[2].position.x = 0.0f + sin(glm::radians(timer *360.0f)) * 4.0f;
+		uboComposition.lights[2].position.z = 4.0f + cos(glm::radians(timer *360.0f)) * 2.0f;
+
+		for (uint32_t i = 0; i < LIGHT_COUNT; i++)
+		{
+			// mvp from light's pov (for shadows)
+			glm::mat4 shadowProj = glm::perspective(glm::radians(lightFOV), 1.0f, zNear, zFar);
+			glm::mat4 shadowView = glm::lookAt(glm::vec3(uboComposition.lights[i].position), glm::vec3(uboComposition.lights[i].target), glm::vec3(0.0f, 1.0f, 0.0f));
+			glm::mat4 shadowModel = glm::mat4(1.0f);
+
+			uboShadowGeometryShader.mvp[i] = shadowProj * shadowView * shadowModel;
+			uboComposition.lights[i].viewMatrix = uboShadowGeometryShader.mvp[i];
+		}
+
+		memcpy(uboShadowGeometryShader.instancePos, uboOffscreenVS.instancePos, sizeof(uboOffscreenVS.instancePos));
+		memcpy(uniformBuffers.shadowGeometryShader.mapped, &uboShadowGeometryShader, sizeof(uboShadowGeometryShader));
+
+		uboComposition.viewPos = glm::vec4(camera.position, 0.0f) * glm::vec4(-1.0f, 1.0f, -1.0f, 1.0f);;
+		uboComposition.debugDisplayTarget = debugDisplayTarget;
+
+		memcpy(uniformBuffers.composition.mapped, &uboComposition, sizeof(uboComposition));
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+
+		// Offscreen rendering
+
+		// Wait for swap chain presentation to finish
+		submitInfo.pWaitSemaphores = &semaphores.presentComplete;
+		// Signal ready with offscreen semaphore
+		submitInfo.pSignalSemaphores = &offscreenSemaphore;
+
+		// Submit work
+
+		// Shadow map pass
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &commandBuffers.deferred;
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+
+		// Scene rendering
+
+		// Wait for offscreen semaphore
+		submitInfo.pWaitSemaphores = &offscreenSemaphore;
+		// Signal ready with render complete semaphore
+		submitInfo.pSignalSemaphores = &semaphores.renderComplete;
+
+		// Submit work
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+
+		VulkanExampleBase::submitFrame();
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		loadAssets();
+		deferredSetup();
+		shadowSetup();
+		initLights();
+		prepareUniformBuffers();
+		setupDescriptorSetLayout();
+		preparePipelines();
+		setupDescriptorPool();
+		setupDescriptorSet();
+		buildCommandBuffers();
+		buildDeferredCommandBuffer();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+		updateUniformBufferDeferredLights();
+		if (camera.updated) 
+		{
+			updateUniformBufferOffscreen();
+		}
+	}
+
+	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
+	{
+		if (overlay->header("Settings")) {
+			if (overlay->comboBox("Display", &debugDisplayTarget, { "Final composition", "Shadows", "Position", "Normals", "Albedo", "Specular" }))
+			{
+				updateUniformBufferDeferredLights();
+			}
+			bool shadows = (uboComposition.useShadows == 1);
+			if (overlay->checkBox("Shadows", &shadows)) {
+				uboComposition.useShadows = shadows;
+				updateUniformBufferDeferredLights();
+			}
+		}
+	}
+};
+
+VULKAN_EXAMPLE_MAIN()
diff --git a/external/Vulkan/examples/descriptorindexing/descriptorindexing.cpp b/external/Vulkan/examples/descriptorindexing/descriptorindexing.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..354d3b55511f2734c6a37276c2324009f38b6cdf
--- /dev/null
+++ b/external/Vulkan/examples/descriptorindexing/descriptorindexing.cpp
@@ -0,0 +1,414 @@
+/*
+* Vulkan Example - Descriptor indexing (VK_EXT_descriptor_indexing)
+*
+* Demonstrates use of descriptor indexing to dynamically index into a variable sized array of samples
+* 
+* Relevant code parts are marked with [POI]
+*
+* Copyright (C) 2021 Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+
+#include "vulkanexamplebase.h"
+
+#define ENABLE_VALIDATION false
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	// We will be dynamically indexing into an array of samplers
+	std::vector<vks::Texture2D> textures;
+
+	vks::Buffer vertexBuffer;
+	vks::Buffer indexBuffer;
+	uint32_t indexCount;
+
+	vks::Buffer uniformBufferVS;
+	struct {
+		glm::mat4 projection;
+		glm::mat4 view;
+		glm::mat4 model;
+	} uboVS;
+
+	VkPipeline pipeline;
+	VkPipelineLayout pipelineLayout;
+	VkDescriptorSet descriptorSet;
+	VkDescriptorSetLayout descriptorSetLayout;
+
+	VkPhysicalDeviceDescriptorIndexingFeaturesEXT physicalDeviceDescriptorIndexingFeatures{};
+
+	struct Vertex {
+		float pos[3];
+		float uv[2];
+		int32_t textureIndex;
+	};
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Descriptor indexing";
+		camera.type = Camera::CameraType::lookat;
+		camera.setPosition(glm::vec3(0.0f, 0.0f, -10.0f));
+		camera.setRotation(glm::vec3(-35.0f, 0.0f, 0.0f));
+		camera.setPerspective(45.0f, (float)width / (float)height, 0.1f, 256.0f);
+
+		// [POI] Enable required extensions
+		enabledInstanceExtensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
+		enabledDeviceExtensions.push_back(VK_KHR_MAINTENANCE3_EXTENSION_NAME);
+		enabledDeviceExtensions.push_back(VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME);
+
+		// [POI] Enable required extension features
+		physicalDeviceDescriptorIndexingFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES_EXT;
+		physicalDeviceDescriptorIndexingFeatures.shaderSampledImageArrayNonUniformIndexing = VK_TRUE;
+		physicalDeviceDescriptorIndexingFeatures.runtimeDescriptorArray = VK_TRUE;
+		physicalDeviceDescriptorIndexingFeatures.descriptorBindingVariableDescriptorCount = VK_TRUE;
+
+		deviceCreatepNextChain = &physicalDeviceDescriptorIndexingFeatures;
+	}
+
+	~VulkanExample()
+	{
+		for (auto &texture : textures) {
+			texture.destroy();
+		}
+		vkDestroyPipeline(device, pipeline, nullptr);
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+		vertexBuffer.destroy();
+		indexBuffer.destroy();
+		uniformBufferVS.destroy();
+	}
+
+	struct V {
+		uint8_t r;
+		uint8_t g;
+		uint8_t b;
+		uint8_t a;
+	};
+
+	// Generate some random textures
+	void generateTextures()
+	{
+		textures.resize(32);
+		for (size_t i = 0; i < textures.size(); i++) {
+			std::random_device rndDevice;
+			std::default_random_engine rndEngine(rndDevice());
+			std::uniform_int_distribution<short> rndDist(50, 255);
+			const int32_t dim = 3;
+			const size_t bufferSize = dim * dim * 4;
+			std::vector<uint8_t> texture(bufferSize);
+			for (size_t i = 0; i < dim * dim; i++) {
+				texture[i * 4]     = rndDist(rndEngine);
+				texture[i * 4 + 1] = rndDist(rndEngine);
+				texture[i * 4 + 2] = rndDist(rndEngine);
+				texture[i * 4 + 3] = 255;
+			}
+			textures[i].fromBuffer(texture.data(), bufferSize, VK_FORMAT_R8G8B8A8_UNORM, dim, dim, vulkanDevice, queue, VK_FILTER_NEAREST);
+		}
+	}
+
+	// Generates a line of cubes with randomized per-face texture indices
+	void generateCubes()
+	{
+		std::vector<Vertex> vertices;
+		std::vector<uint32_t> indices;
+
+		// Generate random per-face texture indices
+		std::random_device rndDevice;
+		std::default_random_engine rndEngine(rndDevice());
+		std::uniform_int_distribution<int32_t> rndDist(0, static_cast<uint32_t>(textures.size()) - 1);
+
+		// Generate cubes with random per-face texture indices
+		const uint32_t count = 6;
+		for (uint32_t i = 0; i < count; i++) {
+			// Get random per-Face texture indices that the shader will sample from
+			int32_t textureIndices[6];
+			for (uint32_t j = 0; j < 6; j++) {
+				textureIndices[j] = rndDist(rndEngine);
+			}
+			// Push vertices to buffer
+			float pos = 2.5f * i - (count * 2.5f / 2.0f);
+			const std::vector<Vertex> cube = {
+				{ { -1.0f + pos, -1.0f,  1.0f }, { 0.0f, 0.0f }, textureIndices[0] },
+				{ {  1.0f + pos, -1.0f,  1.0f }, { 1.0f, 0.0f }, textureIndices[0] },
+				{ {  1.0f + pos,  1.0f,  1.0f }, { 1.0f, 1.0f }, textureIndices[0] },
+				{ { -1.0f + pos,  1.0f,  1.0f }, { 0.0f, 1.0f }, textureIndices[0] },
+
+				{ {  1.0f + pos,  1.0f,  1.0f }, { 0.0f, 0.0f }, textureIndices[1] },
+				{ {  1.0f + pos,  1.0f, -1.0f }, { 1.0f, 0.0f }, textureIndices[1] },
+				{ {  1.0f + pos, -1.0f, -1.0f }, { 1.0f, 1.0f }, textureIndices[1] },
+				{ {  1.0f + pos, -1.0f,  1.0f }, { 0.0f, 1.0f }, textureIndices[1] },
+
+				{ { -1.0f + pos, -1.0f, -1.0f }, { 0.0f, 0.0f }, textureIndices[2] },
+				{ {  1.0f + pos, -1.0f, -1.0f }, { 1.0f, 0.0f }, textureIndices[2] },
+				{ {  1.0f + pos,  1.0f, -1.0f }, { 1.0f, 1.0f }, textureIndices[2] },
+				{ { -1.0f + pos,  1.0f, -1.0f }, { 0.0f, 1.0f }, textureIndices[2] },
+
+				{ { -1.0f + pos, -1.0f, -1.0f }, { 0.0f, 0.0f }, textureIndices[3] },
+				{ { -1.0f + pos, -1.0f,  1.0f }, { 1.0f, 0.0f }, textureIndices[3] },
+				{ { -1.0f + pos,  1.0f,  1.0f }, { 1.0f, 1.0f }, textureIndices[3] },
+				{ { -1.0f + pos,  1.0f, -1.0f }, { 0.0f, 1.0f }, textureIndices[3] },
+
+				{ {  1.0f + pos,  1.0f,  1.0f }, { 0.0f, 0.0f }, textureIndices[4] },
+				{ { -1.0f + pos,  1.0f,  1.0f }, { 1.0f, 0.0f }, textureIndices[4] },
+				{ { -1.0f + pos,  1.0f, -1.0f }, { 1.0f, 1.0f }, textureIndices[4] },
+				{ {  1.0f + pos,  1.0f, -1.0f }, { 0.0f, 1.0f }, textureIndices[4] },
+
+				{ { -1.0f + pos, -1.0f, -1.0f }, { 0.0f, 0.0f }, textureIndices[5] },
+				{ {  1.0f + pos, -1.0f, -1.0f }, { 1.0f, 0.0f }, textureIndices[5] },
+				{ {  1.0f + pos, -1.0f,  1.0f }, { 1.0f, 1.0f }, textureIndices[5] },
+				{ { -1.0f + pos, -1.0f,  1.0f }, { 0.0f, 1.0f }, textureIndices[5] },
+			};
+			for (auto& vertex : cube) {
+				vertices.push_back(vertex);
+			}
+			// Push indices to buffer
+			const std::vector<uint32_t> cubeIndices = {
+				0,1,2,0,2,3,
+				4,5,6,4,6,7,
+				8,9,10,8,10,11,
+				12,13,14,12,14,15,
+				16,17,18,16,18,19,
+				20,21,22,20,22,23
+			};
+			for (auto& index : cubeIndices) {
+				indices.push_back(index + static_cast<uint32_t>(vertices.size()));
+			}
+		}
+
+		indexCount = static_cast<uint32_t>(indices.size());
+
+		// For the sake of simplicity we won't stage the vertex data to the gpu memory
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&vertexBuffer,
+			vertices.size() * sizeof(Vertex),
+			vertices.data()));
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&indexBuffer,
+			indices.size() * sizeof(uint32_t),
+			indices.data()));
+	}
+
+	// [POI] Set up descriptor sets and set layout
+	void setupDescriptorSets()
+	{
+		// Descriptor pool
+		std::vector<VkDescriptorPoolSize> poolSizes = {
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1),
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, static_cast<uint32_t>(textures.size()))
+		};
+		VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 2);
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+
+		// Descriptor set layout
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			// Binding 0 : Vertex shader uniform buffer
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0),
+			// [POI] Binding 1 contains a texture array that is dynamically non-uniform sampled from
+			// In the fragment shader:
+			//	outFragColor = texture(textures[nonuniformEXT(inTexIndex)], inUV);
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1, static_cast<uint32_t>(textures.size()))
+		};
+
+		// [POI] The fragment shader will be using an unsized array of samplers, which has to be marked with the VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT_EXT
+		// In the fragment shader:
+		//	layout (set = 0, binding = 1) uniform sampler2D textures[];
+		VkDescriptorSetLayoutBindingFlagsCreateInfoEXT setLayoutBindingFlags{};
+		setLayoutBindingFlags.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO_EXT;
+		setLayoutBindingFlags.bindingCount = 2;
+		std::vector<VkDescriptorBindingFlagsEXT> descriptorBindingFlags = {
+			0,
+			VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT_EXT
+		};
+		setLayoutBindingFlags.pBindingFlags = descriptorBindingFlags.data();
+
+		VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCI = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
+		descriptorSetLayoutCI.pNext = &setLayoutBindingFlags;
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorSetLayoutCI, nullptr, &descriptorSetLayout));
+
+		// Descriptor sets
+		VkDescriptorSetVariableDescriptorCountAllocateInfoEXT variableDescriptorCountAllocInfo = {};
+
+		uint32_t variableDescCounts[] = { static_cast<uint32_t>(textures.size())};
+
+		variableDescriptorCountAllocInfo.sType              = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_VARIABLE_DESCRIPTOR_COUNT_ALLOCATE_INFO_EXT;
+		variableDescriptorCountAllocInfo.descriptorSetCount = 1;
+		variableDescriptorCountAllocInfo.pDescriptorCounts  = variableDescCounts;
+
+		VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1);
+		allocInfo.pNext = &variableDescriptorCountAllocInfo;
+		
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet));
+
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets(2);
+
+		writeDescriptorSets[0] = vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBufferVS.descriptor);
+
+		// Image descriptors for the texture array
+		std::vector<VkDescriptorImageInfo> textureDescriptors(textures.size());
+		for (size_t i = 0; i < textures.size(); i++) {
+			textureDescriptors[i].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+			textureDescriptors[i].sampler = textures[i].sampler;;
+			textureDescriptors[i].imageView = textures[i].view;
+		}
+
+		// [POI] Second and final descriptor is a texture array
+		// Unlike an array texture, these are adressed like typical arrays
+		writeDescriptorSets[1] = {};
+		writeDescriptorSets[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+		writeDescriptorSets[1].dstBinding = 1;
+		writeDescriptorSets[1].dstArrayElement = 0;
+		writeDescriptorSets[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+		writeDescriptorSets[1].descriptorCount = static_cast<uint32_t>(textures.size());
+		writeDescriptorSets[1].pBufferInfo = 0;
+		writeDescriptorSets[1].dstSet = descriptorSet;
+		writeDescriptorSets[1].pImageInfo = textureDescriptors.data();
+
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr);
+	}
+
+	void preparePipelines()
+	{
+
+		VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1);
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout));
+
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyStateCI = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationStateCI = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendStateCI = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilStateCI = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportStateCI = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+		VkPipelineMultisampleStateCreateInfo multisampleStateCI = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
+		std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
+		VkPipelineDynamicStateCreateInfo dynamicStateCI = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
+
+		// Vertex bindings and attributes
+		VkVertexInputBindingDescription vertexInputBinding = { 0, sizeof(Vertex), VK_VERTEX_INPUT_RATE_VERTEX };
+		std::vector<VkVertexInputAttributeDescription> vertexInputAttributes = {
+			{ 0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, pos) },
+			{ 1, 0, VK_FORMAT_R32G32_SFLOAT, offsetof(Vertex, uv) },
+			{ 2, 0, VK_FORMAT_R32_SINT, offsetof(Vertex, textureIndex) }
+		};
+		VkPipelineVertexInputStateCreateInfo vertexInputStateCI = vks::initializers::pipelineVertexInputStateCreateInfo();
+		vertexInputStateCI.vertexBindingDescriptionCount = 1;
+		vertexInputStateCI.pVertexBindingDescriptions = &vertexInputBinding;
+		vertexInputStateCI.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertexInputAttributes.size());
+		vertexInputStateCI.pVertexAttributeDescriptions = vertexInputAttributes.data();
+
+		// Instacing pipeline
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+
+		shaderStages[0] = loadShader(getShadersPath() + "descriptorindexing/descriptorindexing.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		// [POI] The fragment shader does non-uniform access into our sampler array, so we need to use nonuniformEXT: texture(textures[nonuniformEXT(inTexIndex)], inUV)
+		shaderStages[1] = loadShader(getShadersPath() + "descriptorindexing/descriptorindexing.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass, 0);
+		pipelineCI.pVertexInputState = &vertexInputStateCI;
+		pipelineCI.pInputAssemblyState = &inputAssemblyStateCI;
+		pipelineCI.pRasterizationState = &rasterizationStateCI;
+		pipelineCI.pColorBlendState = &colorBlendStateCI;
+		pipelineCI.pMultisampleState = &multisampleStateCI;
+		pipelineCI.pViewportState = &viewportStateCI;
+		pipelineCI.pDepthStencilState = &depthStencilStateCI;
+		pipelineCI.pDynamicState = &dynamicStateCI;
+		pipelineCI.stageCount = static_cast<uint32_t>(shaderStages.size());
+		pipelineCI.pStages = shaderStages.data();
+
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipeline));
+	}
+
+	void prepareUniformBuffers()
+	{
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBufferVS,
+			sizeof(uboVS)));
+		VK_CHECK_RESULT(uniformBufferVS.map());
+		updateUniformBuffersCamera();
+	}
+
+	void updateUniformBuffersCamera()
+	{
+		uboVS.projection = camera.matrices.perspective;
+		uboVS.view = camera.matrices.view;
+		uboVS.model = glm::mat4(1.0f);
+		memcpy(uniformBufferVS.mapped, &uboVS, sizeof(uboVS));
+	}
+
+	void buildCommandBuffers()
+	{
+		VkClearValue clearValues[2];
+		clearValues[0].color = defaultClearColor;
+		clearValues[1].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.offset.x = 0;
+		renderPassBeginInfo.renderArea.offset.y = 0;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		renderPassBeginInfo.clearValueCount = 2;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			renderPassBeginInfo.framebuffer = frameBuffers[i];
+			VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+			VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+			VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL);
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
+			VkDeviceSize offsets[1] = { 0 };
+			vkCmdBindVertexBuffers(drawCmdBuffers[i], 0, 1, &vertexBuffer.buffer, offsets);
+			vkCmdBindIndexBuffer(drawCmdBuffers[i], indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT32);
+			vkCmdDrawIndexed(drawCmdBuffers[i], indexCount, 1, 0, 0, 0);
+			drawUI(drawCmdBuffers[i]);
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+		VulkanExampleBase::submitFrame();
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		generateTextures();
+		generateCubes();
+		prepareUniformBuffers();
+		setupDescriptorSets();
+		preparePipelines();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+		if (camera.updated)
+			updateUniformBuffersCamera();
+	}
+
+};
+
+VULKAN_EXAMPLE_MAIN()
\ No newline at end of file
diff --git a/external/Vulkan/examples/descriptorsets/descriptorsets.cpp b/external/Vulkan/examples/descriptorsets/descriptorsets.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5afbf6aac179f7cc85deb6c7d1dcf8da22d15185
--- /dev/null
+++ b/external/Vulkan/examples/descriptorsets/descriptorsets.cpp
@@ -0,0 +1,386 @@
+/*
+* Vulkan Example - Using descriptor sets for passing data to shader stages
+*
+* Relevant code parts are marked with [POI]
+*
+* Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkanexamplebase.h"
+#include "VulkanglTFModel.h"
+
+#define ENABLE_VALIDATION false
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	bool animate = true;
+
+	struct Cube {
+		struct Matrices {
+			glm::mat4 projection;
+			glm::mat4 view;
+			glm::mat4 model;
+		} matrices;
+		VkDescriptorSet descriptorSet;
+		vks::Texture2D texture;
+		vks::Buffer uniformBuffer;
+		glm::vec3 rotation;
+	};
+	std::array<Cube, 2> cubes;
+
+	vkglTF::Model model;
+
+	VkPipeline pipeline;
+	VkPipelineLayout pipelineLayout;
+
+	VkDescriptorSetLayout descriptorSetLayout;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Using descriptor Sets";
+		camera.type = Camera::CameraType::lookat;
+		camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 512.0f);
+		camera.setRotation(glm::vec3(0.0f, 0.0f, 0.0f));
+		camera.setTranslation(glm::vec3(0.0f, 0.0f, -5.0f));
+	}
+
+	~VulkanExample()
+	{
+		vkDestroyPipeline(device, pipeline, nullptr);
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+		for (auto cube : cubes) {
+			cube.uniformBuffer.destroy();
+			cube.texture.destroy();
+		}
+	}
+
+	virtual void getEnabledFeatures()
+	{
+		if (deviceFeatures.samplerAnisotropy) {
+			enabledFeatures.samplerAnisotropy = VK_TRUE;
+		};
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		clearValues[0].color = defaultClearColor;
+		clearValues[1].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.offset.x = 0;
+		renderPassBeginInfo.renderArea.offset.y = 0;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		renderPassBeginInfo.clearValueCount = 2;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i) {
+			renderPassBeginInfo.framebuffer = frameBuffers[i];
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
+
+			VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+			VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+			VkDeviceSize offsets[1] = { 0 };
+			model.bindBuffers(drawCmdBuffers[i]);
+
+			/*
+				[POI] Render cubes with separate descriptor sets
+			*/
+			for (auto cube : cubes) {
+				// Bind the cube's descriptor set. This tells the command buffer to use the uniform buffer and image set for this cube
+				vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &cube.descriptorSet, 0, nullptr);
+				model.draw(drawCmdBuffers[i]);
+			}
+
+			drawUI(drawCmdBuffers[i]);
+
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void loadAssets()
+	{
+		const uint32_t glTFLoadingFlags = vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::PreMultiplyVertexColors | vkglTF::FileLoadingFlags::FlipY;
+		model.loadFromFile(getAssetPath() + "models/cube.gltf", vulkanDevice, queue, glTFLoadingFlags);
+		cubes[0].texture.loadFromFile(getAssetPath() + "textures/crate01_color_height_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue);
+		cubes[1].texture.loadFromFile(getAssetPath() + "textures/crate02_color_height_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue);
+	}
+
+	/*
+		[POI] Set up descriptor sets and set layout
+	*/
+	void setupDescriptors()
+	{
+		/*
+
+			Descriptor set layout
+
+			The layout describes the shader bindings and types used for a certain descriptor layout and as such must match the shader bindings
+
+			Shader bindings used in this example:
+
+			VS:
+				layout (set = 0, binding = 0) uniform UBOMatrices ...
+
+			FS :
+				layout (set = 0, binding = 1) uniform sampler2D ...;
+
+		*/
+
+		std::array<VkDescriptorSetLayoutBinding,2> setLayoutBindings{};
+
+		/*
+			Binding 0: Uniform buffers (used to pass matrices)
+		*/
+		setLayoutBindings[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+		// Shader binding point
+		setLayoutBindings[0].binding = 0;
+		// Accessible from the vertex shader only (flags can be combined to make it accessible to multiple shader stages)
+		setLayoutBindings[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
+		// Binding contains one element (can be used for array bindings)
+		setLayoutBindings[0].descriptorCount = 1;
+
+		/*
+			Binding 1: Combined image sampler (used to pass per object texture information)
+		*/
+		setLayoutBindings[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+		setLayoutBindings[1].binding = 1;
+		// Accessible from the fragment shader only
+		setLayoutBindings[1].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
+		setLayoutBindings[1].descriptorCount = 1;
+
+		// Create the descriptor set layout
+		VkDescriptorSetLayoutCreateInfo descriptorLayoutCI{};
+		descriptorLayoutCI.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
+		descriptorLayoutCI.bindingCount = static_cast<uint32_t>(setLayoutBindings.size());
+		descriptorLayoutCI.pBindings = setLayoutBindings.data();
+
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayoutCI, nullptr, &descriptorSetLayout));
+
+		/*
+
+			Descriptor pool
+
+			Actual descriptors are allocated from a descriptor pool telling the driver what types and how many
+			descriptors this application will use
+
+			An application can have multiple pools (e.g. for multiple threads) with any number of descriptor types
+			as long as device limits are not surpassed
+
+			It's good practice to allocate pools with actually required descriptor types and counts
+
+		*/
+
+		std::array<VkDescriptorPoolSize, 2> descriptorPoolSizes{};
+
+		// Uniform buffers : 1 for scene and 1 per object (scene and local matrices)
+		descriptorPoolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+		descriptorPoolSizes[0].descriptorCount = 1 +  static_cast<uint32_t>(cubes.size());
+
+		// Combined image samples : 1 per mesh texture
+		descriptorPoolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+		descriptorPoolSizes[1].descriptorCount = static_cast<uint32_t>(cubes.size());
+
+		// Create the global descriptor pool
+		VkDescriptorPoolCreateInfo descriptorPoolCI = {};
+		descriptorPoolCI.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
+		descriptorPoolCI.poolSizeCount = static_cast<uint32_t>(descriptorPoolSizes.size());
+		descriptorPoolCI.pPoolSizes = descriptorPoolSizes.data();
+		// Max. number of descriptor sets that can be allocated from this pool (one per object)
+		descriptorPoolCI.maxSets = static_cast<uint32_t>(cubes.size());
+
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolCI, nullptr, &descriptorPool));
+
+		/*
+
+			Descriptor sets
+
+			Using the shared descriptor set layout and the descriptor pool we will now allocate the descriptor sets.
+
+			Descriptor sets contain the actual descriptor for the objects (buffers, images) used at render time.
+
+		*/
+
+		for (auto &cube: cubes) {
+
+			// Allocates an empty descriptor set without actual descriptors from the pool using the set layout
+			VkDescriptorSetAllocateInfo allocateInfo{};
+			allocateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
+			allocateInfo.descriptorPool = descriptorPool;
+			allocateInfo.descriptorSetCount = 1;
+			allocateInfo.pSetLayouts = &descriptorSetLayout;
+			VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocateInfo, &cube.descriptorSet));
+
+			// Update the descriptor set with the actual descriptors matching shader bindings set in the layout
+
+			std::array<VkWriteDescriptorSet, 2> writeDescriptorSets{};
+
+			/*
+				Binding 0: Object matrices Uniform buffer
+			*/
+			writeDescriptorSets[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+			writeDescriptorSets[0].dstSet = cube.descriptorSet;
+			writeDescriptorSets[0].dstBinding = 0;
+			writeDescriptorSets[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+			writeDescriptorSets[0].pBufferInfo = &cube.uniformBuffer.descriptor;
+			writeDescriptorSets[0].descriptorCount = 1;
+
+			/*
+				Binding 1: Object texture
+			*/
+			writeDescriptorSets[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+			writeDescriptorSets[1].dstSet = cube.descriptorSet;
+			writeDescriptorSets[1].dstBinding = 1;
+			writeDescriptorSets[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+			// Images use a different descriptor structure, so we use pImageInfo instead of pBufferInfo
+			writeDescriptorSets[1].pImageInfo = &cube.texture.descriptor;
+			writeDescriptorSets[1].descriptorCount = 1;
+
+			// Execute the writes to update descriptors for this set
+			// Note that it's also possible to gather all writes and only run updates once, even for multiple sets
+			// This is possible because each VkWriteDescriptorSet also contains the destination set to be updated
+			// For simplicity we will update once per set instead
+
+			vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr);
+		}
+
+	}
+
+	void preparePipelines()
+	{
+		/*
+			[POI] Create a pipeline layout used for our graphics pipeline
+		*/
+		VkPipelineLayoutCreateInfo pipelineLayoutCI{};
+		pipelineLayoutCI.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
+		// The pipeline layout is based on the descriptor set layout we created above
+		pipelineLayoutCI.setLayoutCount = 1;
+		pipelineLayoutCI.pSetLayouts = &descriptorSetLayout;
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelineLayout));
+
+		const std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
+
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyStateCI = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationStateCI = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendStateCI = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilStateCI = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportStateCI = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+		VkPipelineMultisampleStateCreateInfo multisampleStateCI = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
+		VkPipelineDynamicStateCreateInfo dynamicStateCI = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables.data(), static_cast<uint32_t>(dynamicStateEnables.size()),0);
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass, 0);
+		pipelineCI.pInputAssemblyState = &inputAssemblyStateCI;
+		pipelineCI.pRasterizationState = &rasterizationStateCI;
+		pipelineCI.pColorBlendState = &colorBlendStateCI;
+		pipelineCI.pMultisampleState = &multisampleStateCI;
+		pipelineCI.pViewportState = &viewportStateCI;
+		pipelineCI.pDepthStencilState = &depthStencilStateCI;
+		pipelineCI.pDynamicState = &dynamicStateCI;
+		pipelineCI.stageCount = static_cast<uint32_t>(shaderStages.size());
+		pipelineCI.pStages = shaderStages.data();
+		pipelineCI.pVertexInputState  = vkglTF::Vertex::getPipelineVertexInputState({vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::UV, vkglTF::VertexComponent::Color});
+
+	    shaderStages[0] = loadShader(getShadersPath() + "descriptorsets/cube.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "descriptorsets/cube.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipeline));
+	}
+
+	void prepareUniformBuffers()
+	{
+		// Vertex shader matrix uniform buffer block
+		for (auto& cube : cubes) {
+			VK_CHECK_RESULT(vulkanDevice->createBuffer(
+				VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+				VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+				&cube.uniformBuffer,
+				sizeof(Cube::Matrices)));
+			VK_CHECK_RESULT(cube.uniformBuffer.map());
+		}
+
+		updateUniformBuffers();
+	}
+
+	void updateUniformBuffers()
+	{
+		cubes[0].matrices.model = glm::translate(glm::mat4(1.0f), glm::vec3(-2.0f, 0.0f, 0.0f));
+		cubes[1].matrices.model = glm::translate(glm::mat4(1.0f), glm::vec3( 1.5f, 0.5f, 0.0f));
+
+		for (auto& cube : cubes) {
+			cube.matrices.projection = camera.matrices.perspective;
+			cube.matrices.view = camera.matrices.view;
+			cube.matrices.model = glm::rotate(cube.matrices.model, glm::radians(cube.rotation.x), glm::vec3(1.0f, 0.0f, 0.0f));
+			cube.matrices.model = glm::rotate(cube.matrices.model, glm::radians(cube.rotation.y), glm::vec3(0.0f, 1.0f, 0.0f));
+			cube.matrices.model = glm::rotate(cube.matrices.model, glm::radians(cube.rotation.z), glm::vec3(0.0f, 0.0f, 1.0f));
+			cube.matrices.model = glm::scale(cube.matrices.model, glm::vec3(0.25f));
+			memcpy(cube.uniformBuffer.mapped, &cube.matrices, sizeof(cube.matrices));
+		}
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+		VulkanExampleBase::submitFrame();
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		loadAssets();
+		prepareUniformBuffers();
+		setupDescriptors();
+		preparePipelines();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+		if (animate) {
+			cubes[0].rotation.x += 2.5f * frameTimer;
+			if (cubes[0].rotation.x > 360.0f)
+				cubes[0].rotation.x -= 360.0f;
+			cubes[1].rotation.y += 2.0f * frameTimer;
+			if (cubes[1].rotation.x > 360.0f)
+				cubes[1].rotation.x -= 360.0f;
+		}
+		if ((camera.updated) || (animate)) {
+			updateUniformBuffers();
+		}
+	}
+
+	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
+	{
+		if (overlay->header("Settings")) {
+			overlay->checkBox("Animate", &animate);
+		}
+	}
+};
+
+VULKAN_EXAMPLE_MAIN()
\ No newline at end of file
diff --git a/external/Vulkan/examples/displacement/displacement.cpp b/external/Vulkan/examples/displacement/displacement.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..125ff200022dd4755f7cb078bab06104f252fed4
--- /dev/null
+++ b/external/Vulkan/examples/displacement/displacement.cpp
@@ -0,0 +1,411 @@
+/*
+* Vulkan Example - Displacement mapping with tessellation shaders
+*
+* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkanexamplebase.h"
+#include "VulkanglTFModel.h"
+
+#define ENABLE_VALIDATION false
+
+class VulkanExample : public VulkanExampleBase
+{
+private:
+	struct {
+		vks::Texture2D colorHeightMap;
+	} textures;
+public:
+	bool splitScreen = true;
+	bool displacement = true;
+
+	vkglTF::Model plane;
+
+	struct {
+		vks::Buffer tessControl, tessEval;
+	} uniformBuffers;
+
+	struct UBOTessControl {
+		float tessLevel = 64.0f;
+	} uboTessControl;
+
+	struct UBOTessEval {
+		glm::mat4 projection;
+		glm::mat4 modelView;
+		glm::vec4 lightPos = glm::vec4(0.0f, -1.0f, 0.0f, 0.0f);
+		float tessAlpha = 1.0f;
+		float tessStrength = 0.1f;
+	} uboTessEval;
+
+	struct Pipelines {
+		VkPipeline solid;
+		VkPipeline wireframe = VK_NULL_HANDLE;
+	} pipelines;
+
+	VkPipelineLayout pipelineLayout;
+	VkDescriptorSet descriptorSet;
+	VkDescriptorSetLayout descriptorSetLayout;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Tessellation shader displacement";
+		camera.type = Camera::CameraType::lookat;
+		camera.setPosition(glm::vec3(0.0f, 0.0f, -1.25f));
+		camera.setRotation(glm::vec3(-20.0f, 45.0f, 0.0f));
+		camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 256.0f);
+	}
+
+	~VulkanExample()
+	{
+		// Clean up used Vulkan resources
+		// Note : Inherited destructor cleans up resources stored in base class
+		vkDestroyPipeline(device, pipelines.solid, nullptr);
+		if (pipelines.wireframe != VK_NULL_HANDLE) {
+			vkDestroyPipeline(device, pipelines.wireframe, nullptr);
+		};
+
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+
+		uniformBuffers.tessControl.destroy();
+		uniformBuffers.tessEval.destroy();
+		textures.colorHeightMap.destroy();
+	}
+
+	// Enable physical device features required for this example
+	virtual void getEnabledFeatures()
+	{
+		// Tessellation shader support is required for this example
+		if (deviceFeatures.tessellationShader) {
+			enabledFeatures.tessellationShader = VK_TRUE;
+		}
+		else {
+			vks::tools::exitFatal("Selected GPU does not support tessellation shaders!", VK_ERROR_FEATURE_NOT_PRESENT);
+		}
+		// Fill mode non solid is required for wireframe display
+		if (deviceFeatures.fillModeNonSolid) {
+			enabledFeatures.fillModeNonSolid = VK_TRUE;
+		}
+		else {
+			splitScreen = false;
+		}
+	}
+
+	void loadAssets()
+	{
+		const uint32_t glTFLoadingFlags = vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::PreMultiplyVertexColors | vkglTF::FileLoadingFlags::FlipY;
+		plane.loadFromFile(getAssetPath() + "models/displacement_plane.gltf", vulkanDevice, queue, glTFLoadingFlags);
+		textures.colorHeightMap.loadFromFile(getAssetPath() + "textures/stonefloor03_color_height_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue);
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		clearValues[0].color = defaultClearColor;
+		clearValues[1].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.offset.x = 0;
+		renderPassBeginInfo.renderArea.offset.y = 0;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		renderPassBeginInfo.clearValueCount = 2;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			// Set target frame buffer
+			renderPassBeginInfo.framebuffer = frameBuffers[i];
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+			VkRect2D scissor = vks::initializers::rect2D(splitScreen ? width / 2 : width, height, 0, 0);
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+			vkCmdSetLineWidth(drawCmdBuffers[i], 1.0f);
+
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL);
+
+			plane.bindBuffers(drawCmdBuffers[i]);
+
+			if (splitScreen)
+			{
+				vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.wireframe);
+				plane.draw(drawCmdBuffers[i]);
+				scissor.offset.x = width / 2;
+				vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+			}
+
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.solid);
+			plane.draw(drawCmdBuffers[i]);
+
+			drawUI(drawCmdBuffers[i]);
+
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void setupDescriptorPool()
+	{
+		std::vector<VkDescriptorPoolSize> poolSizes = {
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2),
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1)
+		};
+		VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 2);
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+	}
+
+	void setupDescriptorSetLayout()
+	{
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings =
+		{
+			// Binding 0 : Tessellation control shader ubo
+			vks::initializers::descriptorSetLayoutBinding(
+				VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+				VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT,
+				0),
+			// Binding 1 : Tessellation evaluation shader ubo
+			vks::initializers::descriptorSetLayoutBinding(
+				VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+				VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT,
+				1),
+			// Binding 2 : Combined color (rgb) and height (alpha) map
+			vks::initializers::descriptorSetLayoutBinding(
+				VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+				VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
+				2),
+		};
+
+		VkDescriptorSetLayoutCreateInfo descriptorLayout =
+			vks::initializers::descriptorSetLayoutCreateInfo(
+				setLayoutBindings.data(),
+				static_cast<uint32_t>(setLayoutBindings.size()));
+
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout));
+
+		VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo =
+			vks::initializers::pipelineLayoutCreateInfo(
+				&descriptorSetLayout,
+				1);
+
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayout));
+	}
+
+	void setupDescriptorSet()
+	{
+		VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1);
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet));
+
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets = {
+			// Binding 0 : Tessellation control shader ubo
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.tessControl.descriptor),
+			// Binding 1 : Tessellation evaluation shader ubo
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, &uniformBuffers.tessEval.descriptor),
+			// Binding 2 : Color and displacement map (alpha channel)
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2, &textures.colorHeightMap.descriptor),
+		};
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL);
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState =
+			vks::initializers::pipelineInputAssemblyStateCreateInfo(
+				VK_PRIMITIVE_TOPOLOGY_PATCH_LIST,
+				0,
+				VK_FALSE);
+
+		VkPipelineRasterizationStateCreateInfo rasterizationState =
+			vks::initializers::pipelineRasterizationStateCreateInfo(
+				VK_POLYGON_MODE_FILL,
+				VK_CULL_MODE_BACK_BIT,
+				VK_FRONT_FACE_COUNTER_CLOCKWISE,
+				0);
+
+		VkPipelineColorBlendAttachmentState blendAttachmentState =
+			vks::initializers::pipelineColorBlendAttachmentState(
+				0xf,
+				VK_FALSE);
+
+		VkPipelineColorBlendStateCreateInfo colorBlendState =
+			vks::initializers::pipelineColorBlendStateCreateInfo(
+				1,
+				&blendAttachmentState);
+
+		VkPipelineDepthStencilStateCreateInfo depthStencilState =
+			vks::initializers::pipelineDepthStencilStateCreateInfo(
+				VK_TRUE,
+				VK_TRUE,
+				VK_COMPARE_OP_LESS_OR_EQUAL);
+
+		VkPipelineViewportStateCreateInfo viewportState =
+			vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+
+		VkPipelineMultisampleStateCreateInfo multisampleState =
+			vks::initializers::pipelineMultisampleStateCreateInfo(
+				VK_SAMPLE_COUNT_1_BIT,
+				0);
+
+		std::vector<VkDynamicState> dynamicStateEnables = {
+			VK_DYNAMIC_STATE_VIEWPORT,
+			VK_DYNAMIC_STATE_SCISSOR,
+			VK_DYNAMIC_STATE_LINE_WIDTH
+		};
+		VkPipelineDynamicStateCreateInfo dynamicState =
+			vks::initializers::pipelineDynamicStateCreateInfo(
+				dynamicStateEnables.data(),
+				static_cast<uint32_t>(dynamicStateEnables.size()),
+				0);
+
+		VkPipelineTessellationStateCreateInfo tessellationState =
+			vks::initializers::pipelineTessellationStateCreateInfo(3);
+
+		// Tessellation pipeline
+		// Load shaders
+		std::array<VkPipelineShaderStageCreateInfo, 4> shaderStages;
+		shaderStages[0] = loadShader(getShadersPath() + "displacement/base.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "displacement/base.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		shaderStages[2] = loadShader(getShadersPath() + "displacement/displacement.tesc.spv", VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT);
+		shaderStages[3] = loadShader(getShadersPath() + "displacement/displacement.tese.spv", VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT);
+
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass);
+		pipelineCI.pInputAssemblyState = &inputAssemblyState;
+		pipelineCI.pRasterizationState = &rasterizationState;
+		pipelineCI.pColorBlendState = &colorBlendState;
+		pipelineCI.pMultisampleState = &multisampleState;
+		pipelineCI.pViewportState = &viewportState;
+		pipelineCI.pDepthStencilState = &depthStencilState;
+		pipelineCI.pDynamicState = &dynamicState;
+		pipelineCI.pTessellationState = &tessellationState;
+		pipelineCI.stageCount = static_cast<uint32_t>(shaderStages.size());
+		pipelineCI.pStages = shaderStages.data();
+		pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::UV });
+
+		// Solid pipeline
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.solid));
+		if (deviceFeatures.fillModeNonSolid) {
+			// Wireframe pipeline
+			rasterizationState.polygonMode = VK_POLYGON_MODE_LINE;
+			rasterizationState.cullMode = VK_CULL_MODE_NONE;
+			VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.wireframe));
+		}
+	}
+
+	// Prepare and initialize uniform buffer containing shader uniforms
+	void prepareUniformBuffers()
+	{
+		// Tessellation evaluation shader uniform buffer
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.tessEval,
+			sizeof(uboTessEval)));
+
+		// Tessellation control shader uniform buffer
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.tessControl,
+			sizeof(uboTessControl)));
+
+		// Map persistent
+		VK_CHECK_RESULT(uniformBuffers.tessControl.map());
+		VK_CHECK_RESULT(uniformBuffers.tessEval.map());
+
+		updateUniformBuffers();
+	}
+
+	void updateUniformBuffers()
+	{
+		uboTessEval.projection = camera.matrices.perspective;
+		uboTessEval.modelView = camera.matrices.view;
+		uboTessEval.lightPos.y = -0.5f - uboTessEval.tessStrength;
+		memcpy(uniformBuffers.tessEval.mapped, &uboTessEval, sizeof(uboTessEval));
+
+		// Tessellation control
+		float savedLevel = uboTessControl.tessLevel;
+		if (!displacement)
+		{
+			uboTessControl.tessLevel = 1.0f;
+		}
+
+		memcpy(uniformBuffers.tessControl.mapped, &uboTessControl, sizeof(uboTessControl));
+
+		if (!displacement)
+		{
+			uboTessControl.tessLevel = savedLevel;
+		}
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+
+		// Command buffer to be submitted to the queue
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+
+		// Submit to queue
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+
+		VulkanExampleBase::submitFrame();
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		loadAssets();
+		prepareUniformBuffers();
+		setupDescriptorSetLayout();
+		preparePipelines();
+		setupDescriptorPool();
+		setupDescriptorSet();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+		if (camera.updated) {
+			updateUniformBuffers();
+		}
+	}
+	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
+	{
+		if (overlay->header("Settings")) {
+			if (overlay->checkBox("Tessellation displacement", &displacement)) {
+				updateUniformBuffers();
+			}
+			if (overlay->inputFloat("Strength", &uboTessEval.tessStrength, 0.025f, 3)) {
+				updateUniformBuffers();
+			}
+			if (overlay->inputFloat("Level", &uboTessControl.tessLevel, 0.5f, 2)) {
+				updateUniformBuffers();
+			}
+			if (deviceFeatures.fillModeNonSolid) {
+				if (overlay->checkBox("Splitscreen", &splitScreen)) {
+					buildCommandBuffers();
+					updateUniformBuffers();
+				}
+			}
+
+		}
+	}
+};
+
+VULKAN_EXAMPLE_MAIN()
\ No newline at end of file
diff --git a/external/Vulkan/examples/distancefieldfonts/distancefieldfonts.cpp b/external/Vulkan/examples/distancefieldfonts/distancefieldfonts.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..30cafc751581f1346d50bf43f75cbe9b135e0955
--- /dev/null
+++ b/external/Vulkan/examples/distancefieldfonts/distancefieldfonts.cpp
@@ -0,0 +1,666 @@
+/*
+* Vulkan Example - Font rendering using signed distance fields
+*
+* Font generated using https://github.com/libgdx/libgdx/wiki/Hiero
+*
+* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkanexamplebase.h"
+
+#define VERTEX_BUFFER_BIND_ID 0
+#define ENABLE_VALIDATION false
+
+// Vertex layout for this example
+struct Vertex {
+	float pos[3];
+	float uv[2];
+};
+
+// AngelCode .fnt format structs and classes
+struct bmchar {
+	uint32_t x, y;
+	uint32_t width;
+	uint32_t height;
+	int32_t xoffset;
+	int32_t yoffset;
+	int32_t xadvance;
+	uint32_t page;
+};
+
+// Quick and dirty : complete ASCII table
+// Only chars present in the .fnt are filled with data!
+std::array<bmchar, 255> fontChars;
+
+int32_t nextValuePair(std::stringstream *stream)
+{
+	std::string pair;
+	*stream >> pair;
+	uint32_t spos = pair.find("=");
+	std::string value = pair.substr(spos + 1);
+	int32_t val = std::stoi(value);
+	return val;
+}
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	bool splitScreen = true;
+
+	struct {
+		vks::Texture2D fontSDF;
+		vks::Texture2D fontBitmap;
+	} textures;
+
+	struct {
+		VkPipelineVertexInputStateCreateInfo inputState;
+		std::vector<VkVertexInputBindingDescription> bindingDescriptions;
+		std::vector<VkVertexInputAttributeDescription> attributeDescriptions;
+	} vertices;
+
+	vks::Buffer vertexBuffer;
+	vks::Buffer indexBuffer;
+	uint32_t indexCount;
+
+	struct {
+		vks::Buffer vs;
+		vks::Buffer fs;
+	} uniformBuffers;
+
+	struct UBOVS {
+		glm::mat4 projection;
+		glm::mat4 modelView;
+	} uboVS;
+
+	struct UBOFS {
+		glm::vec4 outlineColor = glm::vec4(1.0f, 0.0f, 0.0f, 0.0f);
+		float outlineWidth = 0.6f;
+		float outline = true;
+	} uboFS;
+
+	struct {
+		VkPipeline sdf;
+		VkPipeline bitmap;
+	} pipelines;
+
+	struct {
+		VkDescriptorSet sdf;
+		VkDescriptorSet bitmap;
+	} descriptorSets;
+
+	VkPipelineLayout pipelineLayout;
+	VkDescriptorSetLayout descriptorSetLayout;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Distance field font rendering";
+		camera.type = Camera::CameraType::lookat;
+		camera.setPosition(glm::vec3(0.0f, 0.0f, -2.0f));
+		camera.setRotation(glm::vec3(0.0f));
+		camera.setPerspective(splitScreen ? 30.0f : 45.0f, (float)width / (float)(height * ((splitScreen) ? 0.5f : 1.0f)), 1.0f, 256.0f);
+	}
+
+	~VulkanExample()
+	{
+		// Clean up used Vulkan resources
+		// Note : Inherited destructor cleans up resources stored in base class
+
+		// Clean up texture resources
+		textures.fontSDF.destroy();
+		textures.fontBitmap.destroy();
+
+		vkDestroyPipeline(device, pipelines.sdf, nullptr);
+		vkDestroyPipeline(device, pipelines.bitmap, nullptr);
+
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+
+		vertexBuffer.destroy();
+		indexBuffer.destroy();
+
+		uniformBuffers.vs.destroy();
+		uniformBuffers.fs.destroy();
+	}
+
+	// Basic parser for AngelCode bitmap font format files
+	// See http://www.angelcode.com/products/bmfont/doc/file_format.html for details
+	void parsebmFont()
+	{
+		std::string fileName = getAssetPath() + "font.fnt";
+
+#if defined(__ANDROID__)
+		// Font description file is stored inside the apk
+		// So we need to load it using the asset manager
+		AAsset* asset = AAssetManager_open(androidApp->activity->assetManager, fileName.c_str(), AASSET_MODE_STREAMING);
+		assert(asset);
+		size_t size = AAsset_getLength(asset);
+
+		assert(size > 0);
+
+		void *fileData = malloc(size);
+		AAsset_read(asset, fileData, size);
+		AAsset_close(asset);
+
+		std::stringbuf sbuf((const char*)fileData);
+		std::istream istream(&sbuf);
+#else
+		std::filebuf fileBuffer;
+		fileBuffer.open(fileName, std::ios::in);
+		std::istream istream(&fileBuffer);
+#endif
+
+		assert(istream.good());
+
+		while (!istream.eof())
+		{
+			std::string line;
+			std::stringstream lineStream;
+			std::getline(istream, line);
+			lineStream << line;
+
+			std::string info;
+			lineStream >> info;
+
+			if (info == "char")
+			{
+				// char id
+				uint32_t charid = nextValuePair(&lineStream);
+				// Char properties
+				fontChars[charid].x = nextValuePair(&lineStream);
+				fontChars[charid].y = nextValuePair(&lineStream);
+				fontChars[charid].width = nextValuePair(&lineStream);
+				fontChars[charid].height = nextValuePair(&lineStream);
+				fontChars[charid].xoffset = nextValuePair(&lineStream);
+				fontChars[charid].yoffset = nextValuePair(&lineStream);
+				fontChars[charid].xadvance = nextValuePair(&lineStream);
+				fontChars[charid].page = nextValuePair(&lineStream);
+			}
+		}
+
+	}
+
+	void loadAssets()
+	{
+		textures.fontSDF.loadFromFile(getAssetPath() + "textures/font_sdf_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue);
+		textures.fontBitmap.loadFromFile(getAssetPath() + "textures/font_bitmap_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue);
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		clearValues[0].color = defaultClearColor;
+		clearValues[1].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.offset.x = 0;
+		renderPassBeginInfo.renderArea.offset.y = 0;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		renderPassBeginInfo.clearValueCount = 2;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			renderPassBeginInfo.framebuffer = frameBuffers[i];
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			VkViewport viewport = vks::initializers::viewport((float)width, (splitScreen) ? (float)height / 2.0f : (float)height, 0.0f, 1.0f);
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+			VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+			VkDeviceSize offsets[1] = { 0 };
+
+			// Signed distance field font
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets.sdf, 0, NULL);
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.sdf);
+			vkCmdBindVertexBuffers(drawCmdBuffers[i], VERTEX_BUFFER_BIND_ID, 1, &vertexBuffer.buffer, offsets);
+			vkCmdBindIndexBuffer(drawCmdBuffers[i], indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT32);
+			vkCmdDrawIndexed(drawCmdBuffers[i], indexCount, 1, 0, 0, 0);
+
+			// Linear filtered bitmap font
+			if (splitScreen)
+			{
+				viewport.y = (float)height / 2.0f;
+				vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+				vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets.bitmap, 0, NULL);
+				vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.bitmap);
+				vkCmdDrawIndexed(drawCmdBuffers[i], indexCount, 1, 0, 0, 0);
+			}
+
+			drawUI(drawCmdBuffers[i]);
+
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	// Creates a vertex buffer containing quads for the passed text
+	void generateText(std:: string text)
+	{
+		std::vector<Vertex> vertices;
+		std::vector<uint32_t> indices;
+		uint32_t indexOffset = 0;
+
+		float w = textures.fontSDF.width;
+
+		float posx = 0.0f;
+		float posy = 0.0f;
+
+		for (uint32_t i = 0; i < text.size(); i++)
+		{
+			bmchar *charInfo = &fontChars[(int)text[i]];
+
+			if (charInfo->width == 0)
+				charInfo->width = 36;
+
+			float charw = ((float)(charInfo->width) / 36.0f);
+			float dimx = 1.0f * charw;
+			float charh = ((float)(charInfo->height) / 36.0f);
+			float dimy = 1.0f * charh;
+
+			float us = charInfo->x / w;
+			float ue = (charInfo->x + charInfo->width) / w;
+			float ts = charInfo->y / w;
+			float te = (charInfo->y + charInfo->height) / w;
+
+			float xo = charInfo->xoffset / 36.0f;
+			float yo = charInfo->yoffset / 36.0f;
+
+			posy = yo;
+
+			vertices.push_back({ { posx + dimx + xo,  posy + dimy, 0.0f }, { ue, te } });
+			vertices.push_back({ { posx + xo,         posy + dimy, 0.0f }, { us, te } });
+			vertices.push_back({ { posx + xo,         posy,        0.0f }, { us, ts } });
+			vertices.push_back({ { posx + dimx + xo,  posy,        0.0f }, { ue, ts } });
+
+			std::array<uint32_t, 6> letterIndices = { 0,1,2, 2,3,0 };
+			for (auto& index : letterIndices)
+			{
+				indices.push_back(indexOffset + index);
+			}
+			indexOffset += 4;
+
+			float advance = ((float)(charInfo->xadvance) / 36.0f);
+			posx += advance;
+		}
+		indexCount = indices.size();
+
+		// Center
+		for (auto& v : vertices)
+		{
+			v.pos[0] -= posx / 2.0f;
+			v.pos[1] -= 0.5f;
+		}
+
+		// Generate host accessible buffers for the text vertices and indices and upload the data
+
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&vertexBuffer,
+			vertices.size() * sizeof(Vertex),
+			vertices.data()));
+
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&indexBuffer,
+			indices.size() * sizeof(uint32_t),
+			indices.data()));
+	}
+
+	void setupVertexDescriptions()
+	{
+		// Binding description
+		vertices.bindingDescriptions.resize(1);
+		vertices.bindingDescriptions[0] =
+			vks::initializers::vertexInputBindingDescription(
+				VERTEX_BUFFER_BIND_ID,
+				sizeof(Vertex),
+				VK_VERTEX_INPUT_RATE_VERTEX);
+
+		// Attribute descriptions
+		// Describes memory layout and shader positions
+		vertices.attributeDescriptions.resize(2);
+		// Location 0 : Position
+		vertices.attributeDescriptions[0] =
+			vks::initializers::vertexInputAttributeDescription(
+				VERTEX_BUFFER_BIND_ID,
+				0,
+				VK_FORMAT_R32G32B32_SFLOAT,
+				0);
+		// Location 1 : Texture coordinates
+		vertices.attributeDescriptions[1] =
+			vks::initializers::vertexInputAttributeDescription(
+				VERTEX_BUFFER_BIND_ID,
+				1,
+				VK_FORMAT_R32G32_SFLOAT,
+				sizeof(float) * 3);
+
+		vertices.inputState = vks::initializers::pipelineVertexInputStateCreateInfo();
+		vertices.inputState.vertexBindingDescriptionCount = vertices.bindingDescriptions.size();
+		vertices.inputState.pVertexBindingDescriptions = vertices.bindingDescriptions.data();
+		vertices.inputState.vertexAttributeDescriptionCount = vertices.attributeDescriptions.size();
+		vertices.inputState.pVertexAttributeDescriptions = vertices.attributeDescriptions.data();
+	}
+
+	void setupDescriptorPool()
+	{
+		std::vector<VkDescriptorPoolSize> poolSizes =
+		{
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 4),
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2)
+		};
+
+		VkDescriptorPoolCreateInfo descriptorPoolInfo =
+			vks::initializers::descriptorPoolCreateInfo(
+				poolSizes.size(),
+				poolSizes.data(),
+				2);
+
+		VkResult vkRes = vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool);
+		assert(!vkRes);
+	}
+
+	void setupDescriptorSetLayout()
+	{
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings =
+		{
+			// Binding 0 : Vertex shader uniform buffer
+			vks::initializers::descriptorSetLayoutBinding(
+				VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+				VK_SHADER_STAGE_VERTEX_BIT,
+				0),
+			// Binding 1 : Fragment shader image sampler
+			vks::initializers::descriptorSetLayoutBinding(
+				VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+				VK_SHADER_STAGE_FRAGMENT_BIT,
+				1),
+			// Binding 2 : Fragment shader uniform buffer
+			vks::initializers::descriptorSetLayoutBinding(
+				VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+				VK_SHADER_STAGE_FRAGMENT_BIT,
+				2)
+		};
+
+		VkDescriptorSetLayoutCreateInfo descriptorLayout =
+			vks::initializers::descriptorSetLayoutCreateInfo(
+				setLayoutBindings.data(),
+				setLayoutBindings.size());
+
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout));
+
+		VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo =
+			vks::initializers::pipelineLayoutCreateInfo(
+				&descriptorSetLayout,
+				1);
+
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayout));
+	}
+
+	void setupDescriptorSet()
+	{
+		VkDescriptorSetAllocateInfo allocInfo =
+			vks::initializers::descriptorSetAllocateInfo(
+				descriptorPool,
+				&descriptorSetLayout,
+				1);
+
+		// Signed distance front descriptor set
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.sdf));
+
+		// Image descriptor for the color map texture
+		VkDescriptorImageInfo texDescriptor =
+			vks::initializers::descriptorImageInfo(
+				textures.fontSDF.sampler,
+				textures.fontSDF.view,
+				VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets =
+		{
+			// Binding 0 : Vertex shader uniform buffer
+			vks::initializers::writeDescriptorSet(
+			descriptorSets.sdf,
+				VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+				0,
+				&uniformBuffers.vs.descriptor),
+			// Binding 1 : Fragment shader texture sampler
+			vks::initializers::writeDescriptorSet(
+				descriptorSets.sdf,
+				VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+				1,
+				&texDescriptor),
+			// Binding 2 : Fragment shader uniform buffer
+			vks::initializers::writeDescriptorSet(
+				descriptorSets.sdf,
+				VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+				2,
+				&uniformBuffers.fs.descriptor)
+		};
+
+		vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, NULL);
+
+		// Default font rendering descriptor set
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.bitmap));
+
+		// Image descriptor for the color map texture
+		texDescriptor.sampler = textures.fontBitmap.sampler;
+		texDescriptor.imageView = textures.fontBitmap.view;
+
+		writeDescriptorSets =
+		{
+			// Binding 0 : Vertex shader uniform buffer
+			vks::initializers::writeDescriptorSet(
+				descriptorSets.bitmap,
+				VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+				0,
+				&uniformBuffers.vs.descriptor),
+			// Binding 1 : Fragment shader texture sampler
+			vks::initializers::writeDescriptorSet(
+				descriptorSets.bitmap,
+				VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+				1,
+				&texDescriptor)
+		};
+
+		vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, NULL);
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState =
+			vks::initializers::pipelineInputAssemblyStateCreateInfo(
+				VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
+				0,
+				VK_FALSE);
+
+		VkPipelineRasterizationStateCreateInfo rasterizationState =
+			vks::initializers::pipelineRasterizationStateCreateInfo(
+				VK_POLYGON_MODE_FILL,
+				VK_CULL_MODE_NONE,
+				VK_FRONT_FACE_COUNTER_CLOCKWISE,
+				0);
+
+		VkPipelineColorBlendAttachmentState blendAttachmentState =
+			vks::initializers::pipelineColorBlendAttachmentState(
+				0xf,
+				VK_TRUE);
+
+		blendAttachmentState.blendEnable = VK_TRUE;
+		blendAttachmentState.srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
+		blendAttachmentState.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
+		blendAttachmentState.colorBlendOp = VK_BLEND_OP_ADD;
+		blendAttachmentState.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
+		blendAttachmentState.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
+		blendAttachmentState.alphaBlendOp = VK_BLEND_OP_ADD;
+		blendAttachmentState.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
+
+		VkPipelineColorBlendStateCreateInfo colorBlendState =
+			vks::initializers::pipelineColorBlendStateCreateInfo(
+				1,
+				&blendAttachmentState);
+
+		VkPipelineDepthStencilStateCreateInfo depthStencilState =
+			vks::initializers::pipelineDepthStencilStateCreateInfo(
+				VK_FALSE,
+				VK_TRUE,
+				VK_COMPARE_OP_LESS_OR_EQUAL);
+
+		VkPipelineViewportStateCreateInfo viewportState =
+			vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+
+		VkPipelineMultisampleStateCreateInfo multisampleState =
+			vks::initializers::pipelineMultisampleStateCreateInfo(
+				VK_SAMPLE_COUNT_1_BIT,
+				0);
+
+		std::vector<VkDynamicState> dynamicStateEnables = {
+			VK_DYNAMIC_STATE_VIEWPORT,
+			VK_DYNAMIC_STATE_SCISSOR
+		};
+		VkPipelineDynamicStateCreateInfo dynamicState =
+			vks::initializers::pipelineDynamicStateCreateInfo(
+				dynamicStateEnables.data(),
+				dynamicStateEnables.size(),
+				0);
+
+		// Load shaders
+		std::array<VkPipelineShaderStageCreateInfo,2> shaderStages;
+
+		shaderStages[0] = loadShader(getShadersPath() + "distancefieldfonts/sdf.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "distancefieldfonts/sdf.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+
+		VkGraphicsPipelineCreateInfo pipelineCreateInfo =
+			vks::initializers::pipelineCreateInfo(
+				pipelineLayout,
+				renderPass,
+				0);
+
+		pipelineCreateInfo.pVertexInputState = &vertices.inputState;
+		pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState;
+		pipelineCreateInfo.pRasterizationState = &rasterizationState;
+		pipelineCreateInfo.pColorBlendState = &colorBlendState;
+		pipelineCreateInfo.pMultisampleState = &multisampleState;
+		pipelineCreateInfo.pViewportState = &viewportState;
+		pipelineCreateInfo.pDepthStencilState = &depthStencilState;
+		pipelineCreateInfo.pDynamicState = &dynamicState;
+		pipelineCreateInfo.stageCount = shaderStages.size();
+		pipelineCreateInfo.pStages = shaderStages.data();
+
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipelines.sdf));
+
+		// Default bitmap font rendering pipeline
+		shaderStages[0] = loadShader(getShadersPath() + "distancefieldfonts/bitmap.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "distancefieldfonts/bitmap.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipelines.bitmap));
+	}
+
+	// Prepare and initialize uniform buffer containing shader uniforms
+	void prepareUniformBuffers()
+	{
+		// Vertex shader uniform buffer block
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.vs,
+			sizeof(uboVS)));
+
+		// Fragment shader uniform buffer block (Contains font rendering parameters)
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.fs,
+			sizeof(uboFS)));
+
+		// Map persistent
+		VK_CHECK_RESULT(uniformBuffers.vs.map());
+		VK_CHECK_RESULT(uniformBuffers.fs.map());
+
+		updateUniformBuffers();
+		updateFontSettings();
+	}
+
+	void updateUniformBuffers()
+	{
+		uboVS.projection = camera.matrices.perspective;
+		uboVS.modelView = camera.matrices.view;
+		memcpy(uniformBuffers.vs.mapped, &uboVS, sizeof(uboVS));
+	}
+
+	void updateFontSettings()
+	{
+		// Fragment shader
+		memcpy(uniformBuffers.fs.mapped, &uboFS, sizeof(uboFS));
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+
+		// Command buffer to be submitted to the queue
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+
+		// Submit to queue
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+
+		VulkanExampleBase::submitFrame();
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		parsebmFont();
+		loadAssets();
+		generateText("Vulkan");
+		setupVertexDescriptions();
+		prepareUniformBuffers();
+		setupDescriptorSetLayout();
+		preparePipelines();
+		setupDescriptorPool();
+		setupDescriptorSet();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+	}
+
+	virtual void viewChanged()
+	{
+		updateUniformBuffers();
+	}
+
+	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
+	{
+		if (overlay->header("Settings")) {
+			bool outline = (uboFS.outline == 1.0f);
+			if (overlay->checkBox("Outline", &outline)) {
+				uboFS.outline = outline ? 1.0f : 0.0f;
+				updateFontSettings();
+			}
+			if (overlay->checkBox("Splitscreen", &splitScreen)) {
+				camera.setPerspective(splitScreen ? 30.0f : 45.0f, (float)width / (float)(height * ((splitScreen) ? 0.5f : 1.0f)), 1.0f, 256.0f);
+				buildCommandBuffers();
+				updateUniformBuffers();
+			}
+		}
+	}
+};
+
+VULKAN_EXAMPLE_MAIN()
\ No newline at end of file
diff --git a/external/Vulkan/examples/dynamicrendering/dynamicrendering.cpp b/external/Vulkan/examples/dynamicrendering/dynamicrendering.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..4143293199571fb814fc5b3ff5e2d55b6a09c263
--- /dev/null
+++ b/external/Vulkan/examples/dynamicrendering/dynamicrendering.cpp
@@ -0,0 +1,319 @@
+/*
+ * Vulkan Example - Using VK_KHR_dynamic_rendering for rendering without framebuffers and render passes (wip)
+ *
+ * Copyright (C) 2021 by Sascha Willems - www.saschawillems.de
+ *
+ * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+
+#include "vulkanexamplebase.h"
+#include "VulkanglTFModel.h"
+
+#define ENABLE_VALIDATION false
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	PFN_vkCmdBeginRenderingKHR vkCmdBeginRenderingKHR;
+	PFN_vkCmdEndRenderingKHR vkCmdEndRenderingKHR;
+
+	VkPhysicalDeviceDynamicRenderingFeaturesKHR dynamicRenderingFeaturesKHR{};
+
+	vkglTF::Model model;
+
+	struct UniformData {
+		glm::mat4 projection;
+		glm::mat4 modelView;
+		glm::vec4 viewPos;
+	} uniformData;
+	vks::Buffer uniformBuffer;
+
+	VkPipeline pipeline;
+	VkPipelineLayout pipelineLayout;
+	VkDescriptorSet descriptorSet;
+	VkDescriptorSetLayout descriptorSetLayout;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Dynamic rendering";
+		camera.type = Camera::CameraType::lookat;
+		camera.setPosition(glm::vec3(0.0f, 0.0f, -10.0f));
+		camera.setRotation(glm::vec3(-7.5f, 72.0f, 0.0f));
+		camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 256.0f);
+
+		enabledInstanceExtensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
+		enabledDeviceExtensions.push_back(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME);
+	}
+
+	~VulkanExample()
+	{
+		if (device) {
+			vkDestroyPipeline(device, pipeline, nullptr);
+			vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+			vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+			uniformBuffer.destroy();
+		}
+	}
+
+	void setupRenderPass()
+	{
+		// With VK_KHR_dynamic_rendering we no longer need a render pass, so skip the sample base render pass setup
+		renderPass = VK_NULL_HANDLE;
+	}
+
+	void setupFrameBuffer()
+	{
+		// With VK_KHR_dynamic_rendering we no longer need a frame buffer, so skip the sample base framebuffer setup
+	}
+
+	// Enable physical device features required for this example
+	virtual void getEnabledFeatures()
+	{
+		// Enable anisotropic filtering if supported
+		if (deviceFeatures.samplerAnisotropy) {
+			enabledFeatures.samplerAnisotropy = VK_TRUE;
+		};
+
+		dynamicRenderingFeaturesKHR.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES_KHR;
+		dynamicRenderingFeaturesKHR.dynamicRendering = VK_TRUE;
+
+		deviceCreatepNextChain = &dynamicRenderingFeaturesKHR;
+	}
+
+	void loadAssets()
+	{
+		const uint32_t glTFLoadingFlags = vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::PreMultiplyVertexColors | vkglTF::FileLoadingFlags::FlipY;
+		model.loadFromFile(getAssetPath() + "models/voyager.gltf", vulkanDevice, queue, glTFLoadingFlags);
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			// Transition color and depth images for drawing
+			vks::tools::insertImageMemoryBarrier(
+				drawCmdBuffers[i],
+				swapChain.buffers[i].image,
+				0,
+				VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
+				VK_IMAGE_LAYOUT_UNDEFINED,
+				VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+				VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
+				VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
+				VkImageSubresourceRange{ VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 });
+			vks::tools::insertImageMemoryBarrier(
+				drawCmdBuffers[i],
+				depthStencil.image,
+				0,
+				VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
+				VK_IMAGE_LAYOUT_UNDEFINED,
+				VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL,
+				VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT,
+				VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT,
+				VkImageSubresourceRange{ VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT, 0, 1, 0, 1 });
+
+			// New structures are used to define the attachments used in dynamic rendering
+			VkRenderingAttachmentInfoKHR colorAttachment{};
+			colorAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR;
+			colorAttachment.imageView = swapChain.buffers[i].view;
+			colorAttachment.imageLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL_KHR;
+			colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+			colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+			colorAttachment.clearValue.color = { 0.0f,0.0f,0.0f,0.0f };
+
+			// A single depth stencil attachment info can be used, but they can also be specified separately.
+			// When both are specified separately, the only requirement is that the image view is identical.			
+			VkRenderingAttachmentInfoKHR depthStencilAttachment{};
+			depthStencilAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR;
+			depthStencilAttachment.imageView = depthStencil.view;
+			depthStencilAttachment.imageLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL_KHR;
+			depthStencilAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+			depthStencilAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+			depthStencilAttachment.clearValue.depthStencil = { 1.0f,  0 };
+
+			VkRenderingInfoKHR renderingInfo{};
+			renderingInfo.sType = VK_STRUCTURE_TYPE_RENDERING_INFO_KHR;
+			renderingInfo.renderArea = { 0, 0, width, height };
+			renderingInfo.layerCount = 1;
+			renderingInfo.colorAttachmentCount = 1;
+			renderingInfo.pColorAttachments = &colorAttachment;
+			renderingInfo.pDepthAttachment = &depthStencilAttachment;
+			renderingInfo.pStencilAttachment = &depthStencilAttachment;
+
+			// Begin dynamic rendering
+			vkCmdBeginRenderingKHR(drawCmdBuffers[i], &renderingInfo);
+
+			VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+			VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr);
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
+
+			model.draw(drawCmdBuffers[i], vkglTF::RenderFlags::BindImages, pipelineLayout);
+
+			// End dynamic rendering
+			vkCmdEndRenderingKHR(drawCmdBuffers[i]);
+
+			// Transition color image for presentation
+			vks::tools::insertImageMemoryBarrier(
+				drawCmdBuffers[i],
+				swapChain.buffers[i].image,
+				VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
+				0,
+				VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+				VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
+				VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
+				VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
+				VkImageSubresourceRange{ VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 });
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+		VulkanExampleBase::submitFrame();
+	}
+
+	void setupDescriptorPool()
+	{
+		// Example uses one ubo and one image sampler
+		std::vector<VkDescriptorPoolSize> poolSizes = {
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1),
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1)
+		};
+		VkDescriptorPoolCreateInfo descriptorPoolInfo =
+			vks::initializers::descriptorPoolCreateInfo(poolSizes, 2);
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+	}
+
+	void setupDescriptorSetLayout()
+	{
+		const std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			// Binding 0 : Vertex shader uniform buffer
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0),
+		};
+		VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout));
+
+		// Layout uses set 0 for passing vertex shader ubo and set 1 for fragment shader images (taken from glTF model)
+		const std::vector<VkDescriptorSetLayout> setLayouts = {
+			descriptorSetLayout,
+			vkglTF::descriptorSetLayoutImage,
+		};
+		VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(setLayouts.data(), 2);
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayout));
+	}
+
+	void setupDescriptorSet()
+	{
+		VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1);
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet));
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets = {
+			// Binding 0 : Vertex shader uniform buffer
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffer.descriptor),
+		};
+		vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, nullptr);
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+		VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
+		std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
+		VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages{};
+
+		// We no longer need to set a renderpass for the pipeline create info
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo();
+		pipelineCI.layout = pipelineLayout;
+		pipelineCI.pInputAssemblyState = &inputAssemblyState;
+		pipelineCI.pRasterizationState = &rasterizationState;
+		pipelineCI.pColorBlendState = &colorBlendState;
+		pipelineCI.pMultisampleState = &multisampleState;
+		pipelineCI.pViewportState = &viewportState;
+		pipelineCI.pDepthStencilState = &depthStencilState;
+		pipelineCI.pDynamicState = &dynamicState;
+		pipelineCI.stageCount = static_cast<uint32_t>(shaderStages.size());
+		pipelineCI.pStages = shaderStages.data();
+		pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::UV });
+
+		// New create info to define color, depth and stencil attachments at pipeline create time
+		VkPipelineRenderingCreateInfoKHR pipelineRenderingCreateInfo{};
+		pipelineRenderingCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR;
+		pipelineRenderingCreateInfo.colorAttachmentCount = 1;
+		pipelineRenderingCreateInfo.pColorAttachmentFormats = &swapChain.colorFormat;
+		pipelineRenderingCreateInfo.depthAttachmentFormat = depthFormat;
+		pipelineRenderingCreateInfo.stencilAttachmentFormat = depthFormat;
+		// Chain into the pipeline creat einfo
+		pipelineCI.pNext = &pipelineRenderingCreateInfo;
+
+		shaderStages[0] = loadShader(getShadersPath() + "dynamicrendering/texture.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "dynamicrendering/texture.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipeline));
+	}
+
+	// Prepare and initialize uniform buffer containing shader uniforms
+	void prepareUniformBuffers()
+	{
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &uniformBuffer, sizeof(uniformData), &uniformData));
+		VK_CHECK_RESULT(uniformBuffer.map());
+
+		updateUniformBuffers();
+	}
+
+	void updateUniformBuffers()
+	{
+		uniformData.projection = camera.matrices.perspective;
+		uniformData.modelView = camera.matrices.view;
+		uniformData.viewPos = camera.viewPos;
+		memcpy(uniformBuffer.mapped, &uniformData, sizeof(uniformData));
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+
+		vkCmdBeginRenderingKHR = reinterpret_cast<PFN_vkCmdBeginRenderingKHR>(vkGetDeviceProcAddr(device, "vkCmdBeginRenderingKHR"));
+		vkCmdEndRenderingKHR = reinterpret_cast<PFN_vkCmdEndRenderingKHR>(vkGetDeviceProcAddr(device, "vkCmdEndRenderingKHR"));
+
+		loadAssets();
+		prepareUniformBuffers();
+		setupDescriptorSetLayout();
+		preparePipelines();
+		setupDescriptorPool();
+		setupDescriptorSet();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+	}
+
+	virtual void viewChanged()
+	{
+		updateUniformBuffers();
+	}
+};
+
+VULKAN_EXAMPLE_MAIN()
diff --git a/external/Vulkan/examples/dynamicuniformbuffer/README.md b/external/Vulkan/examples/dynamicuniformbuffer/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..af96cb0a569b4abe4571a98c765d2686c7eb9c9a
--- /dev/null
+++ b/external/Vulkan/examples/dynamicuniformbuffer/README.md
@@ -0,0 +1,160 @@
+# Dynamic uniform buffers
+
+<img src="../../screenshots/dynamicuniformbuffer.jpg" height="256px">
+
+## Synopsis
+
+Use a single uniform buffer object as a dynamic uniform buffer to draw multiple objects with different matrices from one big uniform buffer object.
+
+## Requirements
+
+The max. number of dynamic uniform buffers supported by the device should be checked with the [maxDescriptorSetUniformBuffersDynamic](http://vulkan.gpuinfo.org/listreports.php?limit=maxDescriptorSetUniformBuffersDynamic) device limit if you need more than the 8 dynamic uniform buffers required by the Vulkan specification.
+
+## Description
+
+This example demonstrates the use of dynamic uniform buffers (```VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC```) for offsetting into one or more uniform block objects when binding the descriptor set using [vkCmdBindDescriptorSets](https://www.khronos.org/registry/vulkan/specs/1.0/man/html/vkCmdBindDescriptorSets.html]).
+
+Instead of having single uniform buffers for each descriptor, dynamic uniform buffers can be used to store data for multiple descriptors in one single uniform buffer.
+
+This minimizes the number of descriptor sets required and may help in optimizing memory writes by e.g. only doing partial updates to that memory.
+
+For this example we will store the model matrices for multiple objects in one dynamic uniform buffer object and offset into this for each object draw.
+
+## Points of interest
+
+### Shader binding
+
+The shader binding itself does not have to be changed compared to a static (single) uniform buffer, as the offset is done in the actual application code.
+
+```glsl
+layout (binding = 1) uniform UboInstance {
+	mat4 model; 
+} uboInstance;
+```
+
+### Preparing the uniform buffer (and memory)
+
+***Note:*** When preparing the (host) memory to back up the dynamic uniform buffer object it's crucial to take the [minUniformBufferOffsetAlignment](http://vulkan.gpuinfo.org/listreports.php?limit=minUniformBufferOffsetAlignment) limit of the implementation into account. 
+
+Due to the implementation dependent alignment (different from our actual data size) we can't just use a vector and work with pointers instead:
+
+```cpp
+struct UboDataDynamic {
+  glm::mat4 *model = nullptr;
+} uboDataDynamic;
+```
+First step is to calculate the alignment required for the data we want to store compared to the min. uniform buffer offset alignment reported by the GPU:
+
+```cpp
+void prepareUniformBuffers()
+{
+	// Calculate required alignment based on minimum device offset alignment
+	size_t minUboAlignment = vulkanDevice->properties.limits.minUniformBufferOffsetAlignment;
+	dynamicAlignment = sizeof(glm::mat4);
+	if (minUboAlignment > 0) {
+		dynamicAlignment = (dynamicAlignment + minUboAlignment - 1) & ~(minUboAlignment - 1);
+	}
+```
+
+The max. allowed alignment (as per spec) is 256 bytes which may be much higher than the data size we actually need for each entry (one 4x4 matrix = 64 bytes). 
+
+Now that we know the actual alignment required we can create our host memory for the dynamic uniform buffer:
+
+```cpp
+  size_t bufferSize = OBJECT_INSTANCES * dynamicAlignment;
+  uboDataDynamic.model = (glm::mat4*)alignedAlloc(bufferSize, dynamicAlignment);
+```
+*(The ```alignedAlloc``` function is a small wrapper doing aligned memory allocation depending on the OS/Compiler)*
+
+Creating the buffer is the same as creating any other uniform buffer object:
+
+```cpp
+vulkanDevice->createBuffer(
+  VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, &uniformBuffers.dynamic, bufferSize);
+```      
+
+*(Updates will be flushed manually, the ```VK_MEMORY_PROPERTY_HOST_COHERENT_BIT``` flag will isn't used)*
+
+### Setting up the descriptors
+
+This is the same as for regular uniform buffers but with descriptor type ```VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC``` instead.
+
+#### Descriptor pool
+
+The example uses one dynamic uniform buffer, so we need to request at least one such descriptor type from the descriptor pool:
+
+```cpp
+void setupDescriptorPool()
+{
+  ...
+  std::vector<VkDescriptorPoolSize> poolSizes = {
+    ...
+    vkTools::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1),
+    ...
+  };
+```
+
+#### Descriptor set layout
+
+The vertex shader interface defines the uniform with the model matrices (sampled from the dynamic buffer) at binding 1, so we need to setup a matching descriptor set layout:
+
+```cpp
+void setupDescriptorSetLayout()
+{
+  std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings =  {
+    ...
+    vkTools::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, VK_SHADER_STAGE_VERTEX_BIT, 1),
+    ...
+  };
+```
+
+#### Descriptor set
+
+The example uses the same descriptor set based on the set layout above for all objects in the scene. As with the layout we bind the dynamic uniform buffer to binding point 1 using the descriptor set up while creating the buffer earlier on.
+
+```cpp
+void setupDescriptorSet()
+{
+  std::vector<VkWriteDescriptorSet> writeDescriptorSets = {    
+    // Binding 1 : Instance matrix as dynamic uniform buffer
+    vkTools::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1, &uniformBuffers.dynamic.descriptor),
+  };
+```
+
+### Using the dynamic uniform buffer
+
+Now that everything is set up, it's time to render the objects using the different matrices stored in the dynamic uniform buffer.
+
+```cpp
+for (uint32_t j = 0; j < OBJECT_INSTANCES; j++)
+{
+  // One dynamic offset per dynamic descriptor to offset into the ubo containing all model matrices
+  uint32_t dynamicOffset = j * static_cast<uint32_t>(dynamicAlignment);
+  // Bind the descriptor set for rendering a mesh using the dynamic offset
+  vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 1, &dynamicOffset);
+
+  vkCmdDrawIndexed(drawCmdBuffers[i], indexCount, 1, 0, 0, 0);
+}
+```      
+For each object to be drawn the offset into the dynamic uniform buffer object's memory is calculated using the dynamic alignment set before buffer creation.
+
+The dynamic offset is then passed at descriptor set binding time using the ```dynamicOffsetCount``` and ```pDynamicOffsets``` parameters of ```vkCmdBindDescriptorSets```.
+
+For each dynamic uniform buffer in the descriptor set currently bound one pointer to an ```uint32_t``` has to be passed in the order of the dynamic buffers' binding indices.
+
+The data starting at the given offset is then passed to the shader for which the dynamic binding applies upon drawing with ```vkCmdDrawIndexed```.
+
+### Updating the buffer
+
+While creating the buffer we did not specify the ```VK_MEMORY_PROPERTY_HOST_COHERENT_BIT``` flag. While this is possible, in a real-world application you would usually only update the parts of the dynamic buffer that actually changed (e.g. only objects that moved since the last frame) and do a manual flush of the updated buffer memory part for better performance. 
+
+This would be done using e.g. [vkFlushMappedMemoryRanges](https://www.khronos.org/registry/vulkan/specs/1.0/man/html/vkFlushMappedMemoryRanges.html).
+
+```cpp
+VkMappedMemoryRange memoryRange = vkTools::initializers::mappedMemoryRange();
+memoryRange.memory = uniformBuffers.dynamic.memory;
+memoryRange.size = sizeof(uboDataDynamic);
+vkFlushMappedMemoryRanges(device, 1, &memoryRange);
+```
+*(The example always updates the whole dynamic buffer's range)*
+
diff --git a/external/Vulkan/examples/dynamicuniformbuffer/dynamicuniformbuffer.cpp b/external/Vulkan/examples/dynamicuniformbuffer/dynamicuniformbuffer.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5e450838f78b9e5be6eae9290927f998ea63a18d
--- /dev/null
+++ b/external/Vulkan/examples/dynamicuniformbuffer/dynamicuniformbuffer.cpp
@@ -0,0 +1,530 @@
+/*
+* Vulkan Example - Dynamic uniform buffers
+*
+* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*
+* Summary:
+* Demonstrates the use of dynamic uniform buffers.
+*
+* Instead of using one uniform buffer per-object, this example allocates one big uniform buffer
+* with respect to the alignment reported by the device via minUniformBufferOffsetAlignment that
+* contains all matrices for the objects in the scene.
+*
+* The used descriptor type VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC then allows to set a dynamic
+* offset used to pass data from the single uniform buffer to the connected shader binding point.
+*/
+
+#include "vulkanexamplebase.h"
+
+#define VERTEX_BUFFER_BIND_ID 0
+#define ENABLE_VALIDATION false
+#define OBJECT_INSTANCES 125
+
+// Vertex layout for this example
+struct Vertex {
+	float pos[3];
+	float color[3];
+};
+
+// Wrapper functions for aligned memory allocation
+// There is currently no standard for this in C++ that works across all platforms and vendors, so we abstract this
+void* alignedAlloc(size_t size, size_t alignment)
+{
+	void *data = nullptr;
+#if defined(_MSC_VER) || defined(__MINGW32__)
+	data = _aligned_malloc(size, alignment);
+#else
+	int res = posix_memalign(&data, alignment, size);
+	if (res != 0)
+		data = nullptr;
+#endif
+	return data;
+}
+
+void alignedFree(void* data)
+{
+#if	defined(_MSC_VER) || defined(__MINGW32__)
+	_aligned_free(data);
+#else
+	free(data);
+#endif
+}
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	struct {
+		VkPipelineVertexInputStateCreateInfo inputState;
+		std::vector<VkVertexInputBindingDescription> bindingDescriptions;
+		std::vector<VkVertexInputAttributeDescription> attributeDescriptions;
+	} vertices;
+
+	vks::Buffer vertexBuffer;
+	vks::Buffer indexBuffer;
+	uint32_t indexCount;
+
+	struct {
+		vks::Buffer view;
+		vks::Buffer dynamic;
+	} uniformBuffers;
+
+	struct {
+		glm::mat4 projection;
+		glm::mat4 view;
+	} uboVS;
+
+	// Store random per-object rotations
+	glm::vec3 rotations[OBJECT_INSTANCES];
+	glm::vec3 rotationSpeeds[OBJECT_INSTANCES];
+
+	// One big uniform buffer that contains all matrices
+	// Note that we need to manually allocate the data to cope for GPU-specific uniform buffer offset alignments
+	struct UboDataDynamic {
+		glm::mat4 *model = nullptr;
+	} uboDataDynamic;
+
+	VkPipeline pipeline;
+	VkPipelineLayout pipelineLayout;
+	VkDescriptorSet descriptorSet;
+	VkDescriptorSetLayout descriptorSetLayout;
+
+	float animationTimer = 0.0f;
+
+	size_t dynamicAlignment;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Dynamic uniform buffers";
+		camera.type = Camera::CameraType::lookat;
+		camera.setPosition(glm::vec3(0.0f, 0.0f, -30.0f));
+		camera.setRotation(glm::vec3(0.0f));
+		camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 256.0f);
+	}
+
+	~VulkanExample()
+	{
+		if (uboDataDynamic.model) {
+			alignedFree(uboDataDynamic.model);
+		}
+
+		// Clean up used Vulkan resources
+		// Note : Inherited destructor cleans up resources stored in base class
+		vkDestroyPipeline(device, pipeline, nullptr);
+
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+
+		vertexBuffer.destroy();
+		indexBuffer.destroy();
+
+		uniformBuffers.view.destroy();
+		uniformBuffers.dynamic.destroy();
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		clearValues[0].color = defaultClearColor;
+		clearValues[1].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.offset.x = 0;
+		renderPassBeginInfo.renderArea.offset.y = 0;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		renderPassBeginInfo.clearValueCount = 2;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			renderPassBeginInfo.framebuffer = frameBuffers[i];
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+			VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
+
+			VkDeviceSize offsets[1] = { 0 };
+			vkCmdBindVertexBuffers(drawCmdBuffers[i], VERTEX_BUFFER_BIND_ID, 1, &vertexBuffer.buffer, offsets);
+			vkCmdBindIndexBuffer(drawCmdBuffers[i], indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT32);
+
+			// Render multiple objects using different model matrices by dynamically offsetting into one uniform buffer
+			for (uint32_t j = 0; j < OBJECT_INSTANCES; j++)
+			{
+				// One dynamic offset per dynamic descriptor to offset into the ubo containing all model matrices
+				uint32_t dynamicOffset = j * static_cast<uint32_t>(dynamicAlignment);
+				// Bind the descriptor set for rendering a mesh using the dynamic offset
+				vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 1, &dynamicOffset);
+
+				vkCmdDrawIndexed(drawCmdBuffers[i], indexCount, 1, 0, 0, 0);
+			}
+
+			drawUI(drawCmdBuffers[i]);
+
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+
+		// Command buffer to be submitted to the queue
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+
+		// Submit to queue
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+
+		VulkanExampleBase::submitFrame();
+	}
+
+	void generateCube()
+	{
+		// Setup vertices indices for a colored cube
+		std::vector<Vertex> vertices = {
+			{ { -1.0f, -1.0f,  1.0f },{ 1.0f, 0.0f, 0.0f } },
+			{ {  1.0f, -1.0f,  1.0f },{ 0.0f, 1.0f, 0.0f } },
+			{ {  1.0f,  1.0f,  1.0f },{ 0.0f, 0.0f, 1.0f } },
+			{ { -1.0f,  1.0f,  1.0f },{ 0.0f, 0.0f, 0.0f } },
+			{ { -1.0f, -1.0f, -1.0f },{ 1.0f, 0.0f, 0.0f } },
+			{ {  1.0f, -1.0f, -1.0f },{ 0.0f, 1.0f, 0.0f } },
+			{ {  1.0f,  1.0f, -1.0f },{ 0.0f, 0.0f, 1.0f } },
+			{ { -1.0f,  1.0f, -1.0f },{ 0.0f, 0.0f, 0.0f } },
+		};
+
+		std::vector<uint32_t> indices = {
+			0,1,2, 2,3,0, 1,5,6, 6,2,1, 7,6,5, 5,4,7, 4,0,3, 3,7,4, 4,5,1, 1,0,4, 3,2,6, 6,7,3,
+		};
+
+		indexCount = static_cast<uint32_t>(indices.size());
+
+		// Create buffers
+		// For the sake of simplicity we won't stage the vertex data to the gpu memory
+		// Vertex buffer
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&vertexBuffer,
+			vertices.size() * sizeof(Vertex),
+			vertices.data()));
+		// Index buffer
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&indexBuffer,
+			indices.size() * sizeof(uint32_t),
+			indices.data()));
+	}
+
+	void setupVertexDescriptions()
+	{
+		// Binding description
+		vertices.bindingDescriptions = {
+			vks::initializers::vertexInputBindingDescription(VERTEX_BUFFER_BIND_ID, sizeof(Vertex), VK_VERTEX_INPUT_RATE_VERTEX),
+		};
+
+		// Attribute descriptions
+		vertices.attributeDescriptions = {
+			vks::initializers::vertexInputAttributeDescription(VERTEX_BUFFER_BIND_ID, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, pos)),	// Location 0 : Position
+			vks::initializers::vertexInputAttributeDescription(VERTEX_BUFFER_BIND_ID, 1, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, color)),	// Location 1 : Color
+		};
+
+		vertices.inputState = vks::initializers::pipelineVertexInputStateCreateInfo();
+		vertices.inputState.vertexBindingDescriptionCount = static_cast<uint32_t>(vertices.bindingDescriptions.size());
+		vertices.inputState.pVertexBindingDescriptions = vertices.bindingDescriptions.data();
+		vertices.inputState.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertices.attributeDescriptions.size());
+		vertices.inputState.pVertexAttributeDescriptions = vertices.attributeDescriptions.data();
+	}
+
+	void setupDescriptorPool()
+	{
+		// Example uses one ubo and one image sampler
+		std::vector<VkDescriptorPoolSize> poolSizes =
+		{
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1),
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1)
+		};
+
+		VkDescriptorPoolCreateInfo descriptorPoolInfo =
+			vks::initializers::descriptorPoolCreateInfo(
+				static_cast<uint32_t>(poolSizes.size()),
+				poolSizes.data(),
+				2);
+
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+	}
+
+	void setupDescriptorSetLayout()
+	{
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings =
+		{
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0),
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, VK_SHADER_STAGE_VERTEX_BIT, 1)
+		};
+
+		VkDescriptorSetLayoutCreateInfo descriptorLayout =
+			vks::initializers::descriptorSetLayoutCreateInfo(
+				setLayoutBindings.data(),
+				static_cast<uint32_t>(setLayoutBindings.size()));
+
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout));
+
+		VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo =
+			vks::initializers::pipelineLayoutCreateInfo(
+				&descriptorSetLayout,
+				1);
+
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayout));
+	}
+
+	void setupDescriptorSet()
+	{
+		VkDescriptorSetAllocateInfo allocInfo =
+			vks::initializers::descriptorSetAllocateInfo(
+				descriptorPool,
+				&descriptorSetLayout,
+				1);
+
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet));
+
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets = {
+			// Binding 0 : Projection/View matrix uniform buffer
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.view.descriptor),
+			// Binding 1 : Instance matrix as dynamic uniform buffer
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1, &uniformBuffers.dynamic.descriptor),
+		};
+
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL);
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState =
+			vks::initializers::pipelineInputAssemblyStateCreateInfo(
+				VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
+				0,
+				VK_FALSE);
+
+		VkPipelineRasterizationStateCreateInfo rasterizationState =
+			vks::initializers::pipelineRasterizationStateCreateInfo(
+				VK_POLYGON_MODE_FILL,
+				VK_CULL_MODE_NONE,
+				VK_FRONT_FACE_COUNTER_CLOCKWISE,
+				0);
+
+		VkPipelineColorBlendAttachmentState blendAttachmentState =
+			vks::initializers::pipelineColorBlendAttachmentState(
+				0xf,
+				VK_FALSE);
+
+		VkPipelineColorBlendStateCreateInfo colorBlendState =
+			vks::initializers::pipelineColorBlendStateCreateInfo(
+				1,
+				&blendAttachmentState);
+
+		VkPipelineDepthStencilStateCreateInfo depthStencilState =
+			vks::initializers::pipelineDepthStencilStateCreateInfo(
+				VK_TRUE,
+				VK_TRUE,
+				VK_COMPARE_OP_LESS_OR_EQUAL);
+
+		VkPipelineViewportStateCreateInfo viewportState =
+			vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+
+		VkPipelineMultisampleStateCreateInfo multisampleState =
+			vks::initializers::pipelineMultisampleStateCreateInfo(
+				VK_SAMPLE_COUNT_1_BIT,
+				0);
+
+		std::vector<VkDynamicState> dynamicStateEnables = {
+			VK_DYNAMIC_STATE_VIEWPORT,
+			VK_DYNAMIC_STATE_SCISSOR
+		};
+		VkPipelineDynamicStateCreateInfo dynamicState =
+			vks::initializers::pipelineDynamicStateCreateInfo(
+				dynamicStateEnables.data(),
+				static_cast<uint32_t>(dynamicStateEnables.size()),
+				0);
+
+		// Load shaders
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+
+		shaderStages[0] = loadShader(getShadersPath() + "dynamicuniformbuffer/base.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "dynamicuniformbuffer/base.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+
+		VkGraphicsPipelineCreateInfo pipelineCreateInfo =
+			vks::initializers::pipelineCreateInfo(
+				pipelineLayout,
+				renderPass,
+				0);
+
+		pipelineCreateInfo.pVertexInputState = &vertices.inputState;
+		pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState;
+		pipelineCreateInfo.pRasterizationState = &rasterizationState;
+		pipelineCreateInfo.pColorBlendState = &colorBlendState;
+		pipelineCreateInfo.pMultisampleState = &multisampleState;
+		pipelineCreateInfo.pViewportState = &viewportState;
+		pipelineCreateInfo.pDepthStencilState = &depthStencilState;
+		pipelineCreateInfo.pDynamicState = &dynamicState;
+		pipelineCreateInfo.stageCount = static_cast<uint32_t>(shaderStages.size());
+		pipelineCreateInfo.pStages = shaderStages.data();
+
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipeline));
+	}
+
+	// Prepare and initialize uniform buffer containing shader uniforms
+	void prepareUniformBuffers()
+	{
+		// Allocate data for the dynamic uniform buffer object
+		// We allocate this manually as the alignment of the offset differs between GPUs
+
+		// Calculate required alignment based on minimum device offset alignment
+		size_t minUboAlignment = vulkanDevice->properties.limits.minUniformBufferOffsetAlignment;
+		dynamicAlignment = sizeof(glm::mat4);
+		if (minUboAlignment > 0) {
+			dynamicAlignment = (dynamicAlignment + minUboAlignment - 1) & ~(minUboAlignment - 1);
+		}
+
+		size_t bufferSize = OBJECT_INSTANCES * dynamicAlignment;
+
+		uboDataDynamic.model = (glm::mat4*)alignedAlloc(bufferSize, dynamicAlignment);
+		assert(uboDataDynamic.model);
+
+		std::cout << "minUniformBufferOffsetAlignment = " << minUboAlignment << std::endl;
+		std::cout << "dynamicAlignment = " << dynamicAlignment << std::endl;
+
+		// Vertex shader uniform buffer block
+
+		// Static shared uniform buffer object with projection and view matrix
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.view,
+			sizeof(uboVS)));
+
+		// Uniform buffer object with per-object matrices
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT,
+			&uniformBuffers.dynamic,
+			bufferSize));
+
+		// Override descriptor range to [base, base + dynamicAlignment]
+		uniformBuffers.dynamic.descriptor.range = dynamicAlignment;
+
+		// Map persistent
+		VK_CHECK_RESULT(uniformBuffers.view.map());
+		VK_CHECK_RESULT(uniformBuffers.dynamic.map());
+
+		// Prepare per-object matrices with offsets and random rotations
+		std::default_random_engine rndEngine(benchmark.active ? 0 : (unsigned)time(nullptr));
+		std::normal_distribution<float> rndDist(-1.0f, 1.0f);
+		for (uint32_t i = 0; i < OBJECT_INSTANCES; i++) {
+			rotations[i] = glm::vec3(rndDist(rndEngine), rndDist(rndEngine), rndDist(rndEngine)) * 2.0f * (float)M_PI;
+			rotationSpeeds[i] = glm::vec3(rndDist(rndEngine), rndDist(rndEngine), rndDist(rndEngine));
+		}
+
+		updateUniformBuffers();
+		updateDynamicUniformBuffer(true);
+	}
+
+	void updateUniformBuffers()
+	{
+		// Fixed ubo with projection and view matrices
+		uboVS.projection = camera.matrices.perspective;
+		uboVS.view = camera.matrices.view;
+
+		memcpy(uniformBuffers.view.mapped, &uboVS, sizeof(uboVS));
+	}
+
+	void updateDynamicUniformBuffer(bool force = false)
+	{
+		// Update at max. 60 fps
+		animationTimer += frameTimer;
+		if ((animationTimer <= 1.0f / 60.0f) && (!force)) {
+			return;
+		}
+
+		// Dynamic ubo with per-object model matrices indexed by offsets in the command buffer
+		uint32_t dim = static_cast<uint32_t>(pow(OBJECT_INSTANCES, (1.0f / 3.0f)));
+		glm::vec3 offset(5.0f);
+
+		for (uint32_t x = 0; x < dim; x++)
+		{
+			for (uint32_t y = 0; y < dim; y++)
+			{
+				for (uint32_t z = 0; z < dim; z++)
+				{
+					uint32_t index = x * dim * dim + y * dim + z;
+
+					// Aligned offset
+					glm::mat4* modelMat = (glm::mat4*)(((uint64_t)uboDataDynamic.model + (index * dynamicAlignment)));
+
+					// Update rotations
+					rotations[index] += animationTimer * rotationSpeeds[index];
+
+					// Update matrices
+					glm::vec3 pos = glm::vec3(-((dim * offset.x) / 2.0f) + offset.x / 2.0f + x * offset.x, -((dim * offset.y) / 2.0f) + offset.y / 2.0f + y * offset.y, -((dim * offset.z) / 2.0f) + offset.z / 2.0f + z * offset.z);
+					*modelMat = glm::translate(glm::mat4(1.0f), pos);
+					*modelMat = glm::rotate(*modelMat, rotations[index].x, glm::vec3(1.0f, 1.0f, 0.0f));
+					*modelMat = glm::rotate(*modelMat, rotations[index].y, glm::vec3(0.0f, 1.0f, 0.0f));
+					*modelMat = glm::rotate(*modelMat, rotations[index].z, glm::vec3(0.0f, 0.0f, 1.0f));
+				}
+			}
+		}
+
+		animationTimer = 0.0f;
+
+		memcpy(uniformBuffers.dynamic.mapped, uboDataDynamic.model, uniformBuffers.dynamic.size);
+		// Flush to make changes visible to the host
+		VkMappedMemoryRange memoryRange = vks::initializers::mappedMemoryRange();
+		memoryRange.memory = uniformBuffers.dynamic.memory;
+		memoryRange.size = uniformBuffers.dynamic.size;
+		vkFlushMappedMemoryRanges(device, 1, &memoryRange);
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		generateCube();
+		setupVertexDescriptions();
+		prepareUniformBuffers();
+		setupDescriptorSetLayout();
+		preparePipelines();
+		setupDescriptorPool();
+		setupDescriptorSet();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+		if (!paused)
+			updateDynamicUniformBuffer();
+	}
+
+	virtual void viewChanged()
+	{
+		updateUniformBuffers();
+	}
+};
+
+VULKAN_EXAMPLE_MAIN()
diff --git a/external/Vulkan/examples/gears/gears.cpp b/external/Vulkan/examples/gears/gears.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f3a059ea44f8100d88cf88c098d01c6f6ad3ebbd
--- /dev/null
+++ b/external/Vulkan/examples/gears/gears.cpp
@@ -0,0 +1,365 @@
+/*
+* Vulkan Example - Animated gears using multiple uniform buffers
+*
+* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkangear.h"
+#include "vulkanexamplebase.h"
+
+#define VERTEX_BUFFER_BIND_ID 0
+#define ENABLE_VALIDATION false
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	struct {
+		VkPipelineVertexInputStateCreateInfo inputState;
+		std::vector<VkVertexInputBindingDescription> bindingDescriptions;
+		std::vector<VkVertexInputAttributeDescription> attributeDescriptions;
+	} vertices;
+
+	struct {
+		VkPipeline solid;
+	} pipelines;
+
+	std::vector<VulkanGear*> gears;
+
+	VkPipelineLayout pipelineLayout;
+	VkDescriptorSetLayout descriptorSetLayout;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Vulkan gears";
+		camera.type = Camera::CameraType::lookat;
+		camera.setPosition(glm::vec3(0.0f, 2.5f, -16.0f));
+		camera.setRotation(glm::vec3(-23.75f, 41.25f, 21.0f));
+		camera.setPerspective(60.0f, (float)width / (float)height, 0.001f, 256.0f);
+		timerSpeed *= 0.25f;
+	}
+
+	~VulkanExample()
+	{
+		// Clean up used Vulkan resources
+		// Note : Inherited destructor cleans up resources stored in base class
+		vkDestroyPipeline(device, pipelines.solid, nullptr);
+
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+
+		for (auto& gear : gears)
+		{
+			delete(gear);
+		}
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		clearValues[0].color = defaultClearColor;
+		clearValues[1].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.offset.x = 0;
+		renderPassBeginInfo.renderArea.offset.y = 0;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		renderPassBeginInfo.clearValueCount = 2;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			renderPassBeginInfo.framebuffer = frameBuffers[i];
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+			VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.solid);
+
+			for (auto& gear : gears)
+			{
+				gear->draw(drawCmdBuffers[i], pipelineLayout);
+			}
+
+			drawUI(drawCmdBuffers[i]);
+
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void prepareVertices()
+	{
+		// Gear definitions
+		std::vector<float> innerRadiuses = { 1.0f, 0.5f, 1.3f };
+		std::vector<float> outerRadiuses = { 4.0f, 2.0f, 2.0f };
+		std::vector<float> widths = { 1.0f, 2.0f, 0.5f };
+		std::vector<int32_t> toothCount = { 20, 10, 10 };
+		std::vector<float> toothDepth = { 0.7f, 0.7f, 0.7f };
+		std::vector<glm::vec3> colors = {
+			glm::vec3(1.0f, 0.0f, 0.0f),
+			glm::vec3(0.0f, 1.0f, 0.2f),
+			glm::vec3(0.0f, 0.0f, 1.0f)
+		};
+		std::vector<glm::vec3> positions = {
+			glm::vec3(-3.0, 0.0, 0.0),
+			glm::vec3(3.1, 0.0, 0.0),
+			glm::vec3(-3.1, -6.2, 0.0)
+		};
+		std::vector<float> rotationSpeeds = { 1.0f, -2.0f, -2.0f };
+		std::vector<float> rotationOffsets = { 0.0f, -9.0f, -30.0f };
+
+		gears.resize(positions.size());
+		for (int32_t i = 0; i < gears.size(); ++i)
+		{
+			GearInfo gearInfo = {};
+			gearInfo.innerRadius = innerRadiuses[i];
+			gearInfo.outerRadius = outerRadiuses[i];
+			gearInfo.width = widths[i];
+			gearInfo.numTeeth = toothCount[i];
+			gearInfo.toothDepth = toothDepth[i];
+			gearInfo.color = colors[i];
+			gearInfo.pos = positions[i];
+			gearInfo.rotSpeed = rotationSpeeds[i];
+			gearInfo.rotOffset = rotationOffsets[i];
+
+			gears[i] = new VulkanGear(vulkanDevice);
+			gears[i]->generate(&gearInfo, queue);
+		}
+
+		// Binding and attribute descriptions are shared across all gears
+		vertices.bindingDescriptions.resize(1);
+		vertices.bindingDescriptions[0] =
+			vks::initializers::vertexInputBindingDescription(
+				VERTEX_BUFFER_BIND_ID,
+				sizeof(Vertex),
+				VK_VERTEX_INPUT_RATE_VERTEX);
+
+		// Attribute descriptions
+		// Describes memory layout and shader positions
+		vertices.attributeDescriptions.resize(3);
+		// Location 0 : Position
+		vertices.attributeDescriptions[0] =
+			vks::initializers::vertexInputAttributeDescription(
+				VERTEX_BUFFER_BIND_ID,
+				0,
+				VK_FORMAT_R32G32B32_SFLOAT,
+				0);
+		// Location 1 : Normal
+		vertices.attributeDescriptions[1] =
+			vks::initializers::vertexInputAttributeDescription(
+				VERTEX_BUFFER_BIND_ID,
+				1,
+				VK_FORMAT_R32G32B32_SFLOAT,
+				sizeof(float) * 3);
+		// Location 2 : Color
+		vertices.attributeDescriptions[2] =
+			vks::initializers::vertexInputAttributeDescription(
+				VERTEX_BUFFER_BIND_ID,
+				2,
+				VK_FORMAT_R32G32B32_SFLOAT,
+				sizeof(float) * 6);
+
+		vertices.inputState = vks::initializers::pipelineVertexInputStateCreateInfo();
+		vertices.inputState.vertexBindingDescriptionCount = static_cast<uint32_t>(vertices.bindingDescriptions.size());
+		vertices.inputState.pVertexBindingDescriptions = vertices.bindingDescriptions.data();
+		vertices.inputState.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertices.attributeDescriptions.size());
+		vertices.inputState.pVertexAttributeDescriptions = vertices.attributeDescriptions.data();
+	}
+
+	void setupDescriptorPool()
+	{
+		// One UBO for each gear
+		std::vector<VkDescriptorPoolSize> poolSizes =
+		{
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 3),
+		};
+
+		VkDescriptorPoolCreateInfo descriptorPoolInfo =
+			vks::initializers::descriptorPoolCreateInfo(
+				static_cast<uint32_t>(poolSizes.size()),
+				poolSizes.data(),
+				// Three descriptor sets (for each gear)
+				3);
+
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+	}
+
+	void setupDescriptorSetLayout()
+	{
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings =
+		{
+			// Binding 0 : Vertex shader uniform buffer
+			vks::initializers::descriptorSetLayoutBinding(
+				VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+				VK_SHADER_STAGE_VERTEX_BIT,
+				0)
+		};
+
+		VkDescriptorSetLayoutCreateInfo descriptorLayout =
+			vks::initializers::descriptorSetLayoutCreateInfo(
+				setLayoutBindings.data(),
+				static_cast<uint32_t>(setLayoutBindings.size()));
+
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout));
+
+		VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo =
+			vks::initializers::pipelineLayoutCreateInfo(
+				&descriptorSetLayout,
+				1);
+
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayout));
+	}
+
+	void setupDescriptorSets()
+	{
+		for (auto& gear : gears)
+		{
+			gear->setupDescriptorSet(descriptorPool, descriptorSetLayout);
+		}
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState =
+			vks::initializers::pipelineInputAssemblyStateCreateInfo(
+				VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
+				0,
+				VK_FALSE);
+
+		VkPipelineRasterizationStateCreateInfo rasterizationState =
+			vks::initializers::pipelineRasterizationStateCreateInfo(
+				VK_POLYGON_MODE_FILL,
+				VK_CULL_MODE_BACK_BIT,
+				VK_FRONT_FACE_CLOCKWISE,
+				0);
+
+		VkPipelineColorBlendAttachmentState blendAttachmentState =
+			vks::initializers::pipelineColorBlendAttachmentState(
+				0xf,
+				VK_FALSE);
+
+		VkPipelineColorBlendStateCreateInfo colorBlendState =
+			vks::initializers::pipelineColorBlendStateCreateInfo(
+				1,
+				&blendAttachmentState);
+
+		VkPipelineDepthStencilStateCreateInfo depthStencilState =
+			vks::initializers::pipelineDepthStencilStateCreateInfo(
+				VK_TRUE,
+				VK_TRUE,
+				VK_COMPARE_OP_LESS_OR_EQUAL);
+
+		VkPipelineViewportStateCreateInfo viewportState =
+			vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+
+		VkPipelineMultisampleStateCreateInfo multisampleState =
+			vks::initializers::pipelineMultisampleStateCreateInfo(
+				VK_SAMPLE_COUNT_1_BIT,
+				0);
+
+		std::vector<VkDynamicState> dynamicStateEnables = {
+			VK_DYNAMIC_STATE_VIEWPORT,
+			VK_DYNAMIC_STATE_SCISSOR
+		};
+		VkPipelineDynamicStateCreateInfo dynamicState =
+			vks::initializers::pipelineDynamicStateCreateInfo(
+				dynamicStateEnables.data(),
+				static_cast<uint32_t>(dynamicStateEnables.size()),
+				0);
+
+		// Solid rendering pipeline
+		// Load shaders
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+
+		shaderStages[0] = loadShader(getShadersPath() + "gears/gears.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "gears/gears.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+
+		VkGraphicsPipelineCreateInfo pipelineCreateInfo =
+			vks::initializers::pipelineCreateInfo(
+				pipelineLayout,
+				renderPass,
+				0);
+
+		pipelineCreateInfo.pVertexInputState = &vertices.inputState;
+		pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState;
+		pipelineCreateInfo.pRasterizationState = &rasterizationState;
+		pipelineCreateInfo.pColorBlendState = &colorBlendState;
+		pipelineCreateInfo.pMultisampleState = &multisampleState;
+		pipelineCreateInfo.pViewportState = &viewportState;
+		pipelineCreateInfo.pDepthStencilState = &depthStencilState;
+		pipelineCreateInfo.pDynamicState = &dynamicState;
+		pipelineCreateInfo.stageCount = static_cast<uint32_t>(shaderStages.size());
+		pipelineCreateInfo.pStages = shaderStages.data();
+
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipelines.solid));
+	}
+
+	void updateUniformBuffers()
+	{
+		for (auto& gear : gears)
+		{
+			gear->updateUniformBuffer(camera.matrices.perspective, camera.matrices.view, timer * 360.0f);
+		}
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+
+		// Command buffer to be submitted to the queue
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+
+		// Submit to queue
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+
+		VulkanExampleBase::submitFrame();
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		prepareVertices();
+		setupDescriptorSetLayout();
+		preparePipelines();
+		setupDescriptorPool();
+		setupDescriptorSets();
+		updateUniformBuffers();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		vkDeviceWaitIdle(device);
+		draw();
+		vkDeviceWaitIdle(device);
+		if (!paused)
+		{
+			updateUniformBuffers();
+		}
+	}
+
+	virtual void viewChanged()
+	{
+		updateUniformBuffers();
+	}
+};
+
+VULKAN_EXAMPLE_MAIN()
\ No newline at end of file
diff --git a/external/Vulkan/examples/gears/vulkangear.cpp b/external/Vulkan/examples/gears/vulkangear.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3ccc231c9814c0a97951ff0539ed738196546609
--- /dev/null
+++ b/external/Vulkan/examples/gears/vulkangear.cpp
@@ -0,0 +1,312 @@
+/*
+* Vulkan Example - Animated gears using multiple uniform buffers
+*
+* See readme.md for details
+*
+* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkangear.h"
+
+int32_t VulkanGear::newVertex(std::vector<Vertex> *vBuffer, float x, float y, float z, const glm::vec3& normal)
+{
+	Vertex v(glm::vec3(x, y, z), normal, color);
+	vBuffer->push_back(v);
+	return static_cast<int32_t>(vBuffer->size()) - 1;
+}
+
+void VulkanGear::newFace(std::vector<uint32_t> *iBuffer, int a, int b, int c)
+{
+	iBuffer->push_back(a);
+	iBuffer->push_back(b);
+	iBuffer->push_back(c);
+}
+
+VulkanGear::~VulkanGear()
+{
+	// Clean up vulkan resources
+	uniformBuffer.destroy();
+	vertexBuffer.destroy();
+	indexBuffer.destroy();
+}
+
+void VulkanGear::generate(GearInfo *gearinfo, VkQueue queue)
+{
+	this->color = gearinfo->color;
+	this->pos = gearinfo->pos;
+	this->rotOffset = gearinfo->rotOffset;
+	this->rotSpeed = gearinfo->rotSpeed;
+
+	std::vector<Vertex> vBuffer;
+	std::vector<uint32_t> iBuffer;
+
+	int i, j;
+	float r0, r1, r2;
+	float ta, da;
+	float u1, v1, u2, v2, len;
+	float cos_ta, cos_ta_1da, cos_ta_2da, cos_ta_3da, cos_ta_4da;
+	float sin_ta, sin_ta_1da, sin_ta_2da, sin_ta_3da, sin_ta_4da;
+	int32_t ix0, ix1, ix2, ix3, ix4, ix5;
+
+	r0 = gearinfo->innerRadius;
+	r1 = gearinfo->outerRadius - gearinfo->toothDepth / 2.0f;
+	r2 = gearinfo->outerRadius + gearinfo->toothDepth / 2.0f;
+	da = 2.0f * M_PI / gearinfo->numTeeth / 4.0f;
+
+	glm::vec3 normal;
+
+	for (i = 0; i < gearinfo->numTeeth; i++)
+	{
+		ta = i * 2.0f * M_PI / gearinfo->numTeeth;
+
+		cos_ta = cos(ta);
+		cos_ta_1da = cos(ta + da);
+		cos_ta_2da = cos(ta + 2.0f * da);
+		cos_ta_3da = cos(ta + 3.0f * da);
+		cos_ta_4da = cos(ta + 4.0f * da);
+		sin_ta = sin(ta);
+		sin_ta_1da = sin(ta + da);
+		sin_ta_2da = sin(ta + 2.0f * da);
+		sin_ta_3da = sin(ta + 3.0f * da);
+		sin_ta_4da = sin(ta + 4.0f * da);
+
+		u1 = r2 * cos_ta_1da - r1 * cos_ta;
+		v1 = r2 * sin_ta_1da - r1 * sin_ta;
+		len = sqrt(u1 * u1 + v1 * v1);
+		u1 /= len;
+		v1 /= len;
+		u2 = r1 * cos_ta_3da - r2 * cos_ta_2da;
+		v2 = r1 * sin_ta_3da - r2 * sin_ta_2da;
+
+		// front face
+		normal = glm::vec3(0.0f, 0.0f, 1.0f);
+		ix0 = newVertex(&vBuffer, r0 * cos_ta, r0 * sin_ta, gearinfo->width * 0.5f, normal);
+		ix1 = newVertex(&vBuffer, r1 * cos_ta, r1 * sin_ta, gearinfo->width * 0.5f, normal);
+		ix2 = newVertex(&vBuffer, r0 * cos_ta, r0 * sin_ta, gearinfo->width * 0.5f, normal);
+		ix3 = newVertex(&vBuffer, r1 * cos_ta_3da, r1 * sin_ta_3da, gearinfo->width * 0.5f, normal);
+		ix4 = newVertex(&vBuffer, r0 * cos_ta_4da, r0 * sin_ta_4da, gearinfo->width * 0.5f, normal);
+		ix5 = newVertex(&vBuffer, r1 * cos_ta_4da, r1 * sin_ta_4da, gearinfo->width * 0.5f, normal);
+		newFace(&iBuffer, ix0, ix1, ix2);
+		newFace(&iBuffer, ix1, ix3, ix2);
+		newFace(&iBuffer, ix2, ix3, ix4);
+		newFace(&iBuffer, ix3, ix5, ix4);
+
+		// front sides of teeth
+		normal = glm::vec3(0.0f, 0.0f, 1.0f);
+		ix0 = newVertex(&vBuffer, r1 * cos_ta, r1 * sin_ta, gearinfo->width * 0.5f, normal);
+		ix1 = newVertex(&vBuffer, r2 * cos_ta_1da, r2 * sin_ta_1da, gearinfo->width * 0.5f, normal);
+		ix2 = newVertex(&vBuffer, r1 * cos_ta_3da, r1 * sin_ta_3da, gearinfo->width * 0.5f, normal);
+		ix3 = newVertex(&vBuffer, r2 * cos_ta_2da, r2 * sin_ta_2da, gearinfo->width * 0.5f, normal);
+		newFace(&iBuffer, ix0, ix1, ix2);
+		newFace(&iBuffer, ix1, ix3, ix2);
+
+		// back face 
+		normal = glm::vec3(0.0f, 0.0f, -1.0f);
+		ix0 = newVertex(&vBuffer, r1 * cos_ta, r1 * sin_ta, -gearinfo->width * 0.5f, normal);
+		ix1 = newVertex(&vBuffer, r0 * cos_ta, r0 * sin_ta, -gearinfo->width * 0.5f, normal);
+		ix2 = newVertex(&vBuffer, r1 * cos_ta_3da, r1 * sin_ta_3da, -gearinfo->width * 0.5f, normal);
+		ix3 = newVertex(&vBuffer, r0 * cos_ta, r0 * sin_ta, -gearinfo->width * 0.5f, normal);
+		ix4 = newVertex(&vBuffer, r1 * cos_ta_4da, r1 * sin_ta_4da, -gearinfo->width * 0.5f, normal);
+		ix5 = newVertex(&vBuffer, r0 * cos_ta_4da, r0 * sin_ta_4da, -gearinfo->width * 0.5f, normal);
+		newFace(&iBuffer, ix0, ix1, ix2);
+		newFace(&iBuffer, ix1, ix3, ix2);
+		newFace(&iBuffer, ix2, ix3, ix4);
+		newFace(&iBuffer, ix3, ix5, ix4);
+
+		// back sides of teeth 
+		normal = glm::vec3(0.0f, 0.0f, -1.0f);
+		ix0 = newVertex(&vBuffer, r1 * cos_ta_3da, r1 * sin_ta_3da, -gearinfo->width * 0.5f, normal);
+		ix1 = newVertex(&vBuffer, r2 * cos_ta_2da, r2 * sin_ta_2da, -gearinfo->width * 0.5f, normal);
+		ix2 = newVertex(&vBuffer, r1 * cos_ta, r1 * sin_ta, -gearinfo->width * 0.5f, normal);
+		ix3 = newVertex(&vBuffer, r2 * cos_ta_1da, r2 * sin_ta_1da, -gearinfo->width * 0.5f, normal);
+		newFace(&iBuffer, ix0, ix1, ix2);
+		newFace(&iBuffer, ix1, ix3, ix2);
+
+		// draw outward faces of teeth 
+		normal = glm::vec3(v1, -u1, 0.0f);
+		ix0 = newVertex(&vBuffer, r1 * cos_ta, r1 * sin_ta, gearinfo->width * 0.5f, normal);
+		ix1 = newVertex(&vBuffer, r1 * cos_ta, r1 * sin_ta, -gearinfo->width * 0.5f, normal);
+		ix2 = newVertex(&vBuffer, r2 * cos_ta_1da, r2 * sin_ta_1da, gearinfo->width * 0.5f, normal);
+		ix3 = newVertex(&vBuffer, r2 * cos_ta_1da, r2 * sin_ta_1da, -gearinfo->width * 0.5f, normal);
+		newFace(&iBuffer, ix0, ix1, ix2);
+		newFace(&iBuffer, ix1, ix3, ix2);
+
+		normal = glm::vec3(cos_ta, sin_ta, 0.0f);
+		ix0 = newVertex(&vBuffer, r2 * cos_ta_1da, r2 * sin_ta_1da, gearinfo->width * 0.5f, normal);
+		ix1 = newVertex(&vBuffer, r2 * cos_ta_1da, r2 * sin_ta_1da, -gearinfo->width * 0.5f, normal);
+		ix2 = newVertex(&vBuffer, r2 * cos_ta_2da, r2 * sin_ta_2da, gearinfo->width * 0.5f, normal);
+		ix3 = newVertex(&vBuffer, r2 * cos_ta_2da, r2 * sin_ta_2da, -gearinfo->width * 0.5f, normal);
+		newFace(&iBuffer, ix0, ix1, ix2);
+		newFace(&iBuffer, ix1, ix3, ix2);
+
+		normal = glm::vec3(v2, -u2, 0.0f);
+		ix0 = newVertex(&vBuffer, r2 * cos_ta_2da, r2 * sin_ta_2da, gearinfo->width * 0.5f, normal);
+		ix1 = newVertex(&vBuffer, r2 * cos_ta_2da, r2 * sin_ta_2da, -gearinfo->width * 0.5f, normal);
+		ix2 = newVertex(&vBuffer, r1 * cos_ta_3da, r1 * sin_ta_3da, gearinfo->width * 0.5f, normal);
+		ix3 = newVertex(&vBuffer, r1 * cos_ta_3da, r1 * sin_ta_3da, -gearinfo->width * 0.5f, normal);
+		newFace(&iBuffer, ix0, ix1, ix2);
+		newFace(&iBuffer, ix1, ix3, ix2);
+
+		normal = glm::vec3(cos_ta, sin_ta, 0.0f);
+		ix0 = newVertex(&vBuffer, r1 * cos_ta_3da, r1 * sin_ta_3da, gearinfo->width * 0.5f, normal);
+		ix1 = newVertex(&vBuffer, r1 * cos_ta_3da, r1 * sin_ta_3da, -gearinfo->width * 0.5f, normal);
+		ix2 = newVertex(&vBuffer, r1 * cos_ta_4da, r1 * sin_ta_4da, gearinfo->width * 0.5f, normal);
+		ix3 = newVertex(&vBuffer, r1 * cos_ta_4da, r1 * sin_ta_4da, -gearinfo->width * 0.5f, normal);
+		newFace(&iBuffer, ix0, ix1, ix2);
+		newFace(&iBuffer, ix1, ix3, ix2);
+
+		// draw inside radius cylinder 
+		ix0 = newVertex(&vBuffer, r0 * cos_ta, r0 * sin_ta, -gearinfo->width * 0.5f, glm::vec3(-cos_ta, -sin_ta, 0.0f));
+		ix1 = newVertex(&vBuffer, r0 * cos_ta, r0 * sin_ta, gearinfo->width * 0.5f, glm::vec3(-cos_ta, -sin_ta, 0.0f));
+		ix2 = newVertex(&vBuffer, r0 * cos_ta_4da, r0 * sin_ta_4da, -gearinfo->width * 0.5f, glm::vec3(-cos_ta_4da, -sin_ta_4da, 0.0f));
+		ix3 = newVertex(&vBuffer, r0 * cos_ta_4da, r0 * sin_ta_4da, gearinfo->width * 0.5f, glm::vec3(-cos_ta_4da, -sin_ta_4da, 0.0f));
+		newFace(&iBuffer, ix0, ix1, ix2);
+		newFace(&iBuffer, ix1, ix3, ix2);
+	}
+
+	size_t vertexBufferSize = vBuffer.size() * sizeof(Vertex);
+	size_t indexBufferSize = iBuffer.size() * sizeof(uint32_t);
+
+	bool useStaging = true;
+
+	if (useStaging)
+	{
+		vks::Buffer vertexStaging, indexStaging;
+
+		// Create staging buffers
+		// Vertex data
+		vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT,
+			&vertexStaging,
+			vertexBufferSize,
+			vBuffer.data());
+		// Index data
+		vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT,
+			&indexStaging,
+			indexBufferSize,
+			iBuffer.data());
+
+		// Create device local buffers
+		// Vertex buffer
+		vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
+			VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
+			&vertexBuffer,
+			vertexBufferSize);
+		// Index buffer
+		vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
+			VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
+			&indexBuffer,
+			indexBufferSize);
+
+		// Copy from staging buffers
+		VkCommandBuffer copyCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+
+		VkBufferCopy copyRegion = {};
+
+		copyRegion.size = vertexBufferSize;
+		vkCmdCopyBuffer(
+			copyCmd,
+			vertexStaging.buffer,
+			vertexBuffer.buffer,
+			1,
+			&copyRegion);
+
+		copyRegion.size = indexBufferSize;
+		vkCmdCopyBuffer(
+			copyCmd,
+			indexStaging.buffer,
+			indexBuffer.buffer,
+			1,
+			&copyRegion);
+
+		vulkanDevice->flushCommandBuffer(copyCmd, queue, true);
+
+		vkDestroyBuffer(vulkanDevice->logicalDevice, vertexStaging.buffer, nullptr);
+		vkFreeMemory(vulkanDevice->logicalDevice, vertexStaging.memory, nullptr);
+		vkDestroyBuffer(vulkanDevice->logicalDevice, indexStaging.buffer, nullptr);
+		vkFreeMemory(vulkanDevice->logicalDevice, indexStaging.memory, nullptr);
+	}
+	else
+	{
+		// Vertex buffer
+		vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT,
+			&vertexBuffer,
+			vertexBufferSize,
+			vBuffer.data());
+		// Index buffer
+		vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT,
+			&indexBuffer,
+			indexBufferSize,
+			iBuffer.data());
+	}
+
+	indexCount = iBuffer.size();
+
+	prepareUniformBuffer();
+}
+
+void VulkanGear::draw(VkCommandBuffer cmdbuffer, VkPipelineLayout pipelineLayout)
+{
+	VkDeviceSize offsets[1] = { 0 };
+	vkCmdBindDescriptorSets(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL);
+	vkCmdBindVertexBuffers(cmdbuffer, 0, 1, &vertexBuffer.buffer, offsets);
+	vkCmdBindIndexBuffer(cmdbuffer, indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT32);
+	vkCmdDrawIndexed(cmdbuffer, indexCount, 1, 0, 0, 1);
+}
+
+void VulkanGear::updateUniformBuffer(glm::mat4 perspective, glm::mat4 view, float timer)
+{
+	ubo.projection = perspective;
+	ubo.view = view;
+	ubo.model = glm::mat4(1.0f);
+	ubo.model = glm::translate(ubo.model, pos);
+	ubo.model = glm::rotate(ubo.model, glm::radians((rotSpeed * timer) + rotOffset), glm::vec3(0.0f, 0.0f, 1.0f));
+	ubo.normal = glm::inverseTranspose(ubo.view * ubo.model);
+	ubo.lightPos = glm::vec3(0.0f, 0.0f, 2.5f);
+	ubo.lightPos.x = sin(glm::radians(timer)) * 8.0f;
+	ubo.lightPos.z = cos(glm::radians(timer)) * 8.0f;
+	memcpy(uniformBuffer.mapped, &ubo, sizeof(ubo));
+}
+
+void VulkanGear::setupDescriptorSet(VkDescriptorPool pool, VkDescriptorSetLayout descriptorSetLayout)
+{
+	VkDescriptorSetAllocateInfo allocInfo =
+		vks::initializers::descriptorSetAllocateInfo(
+			pool,
+			&descriptorSetLayout,
+			1);
+
+	VK_CHECK_RESULT(vkAllocateDescriptorSets(vulkanDevice->logicalDevice, &allocInfo, &descriptorSet));
+
+	// Binding 0 : Vertex shader uniform buffer
+	VkWriteDescriptorSet writeDescriptorSet =
+		vks::initializers::writeDescriptorSet(
+			descriptorSet,
+			VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+			0,
+			&uniformBuffer.descriptor);
+
+	vkUpdateDescriptorSets(vulkanDevice->logicalDevice, 1, &writeDescriptorSet, 0, NULL);
+}
+
+void VulkanGear::prepareUniformBuffer()
+{
+	VK_CHECK_RESULT(vulkanDevice->createBuffer(
+		VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+		VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+		&uniformBuffer,
+		sizeof(ubo)));
+	// Map persistent
+	VK_CHECK_RESULT(uniformBuffer.map());
+}
diff --git a/external/Vulkan/examples/gears/vulkangear.h b/external/Vulkan/examples/gears/vulkangear.h
new file mode 100644
index 0000000000000000000000000000000000000000..cedad89533b80bf436dd0db4ccb7030ee52228ed
--- /dev/null
+++ b/external/Vulkan/examples/gears/vulkangear.h
@@ -0,0 +1,104 @@
+/*
+* Vulkan Example - Animated gears using multiple uniform buffers
+*
+* See readme.md for details
+*
+* Copyright (C) 2015 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#pragma once
+
+#include <math.h>
+#include <vector>
+
+#define GLM_FORCE_RADIANS
+#define GLM_FORCE_DEPTH_ZERO_TO_ONE
+#include <glm/glm.hpp>
+#include <glm/gtc/matrix_transform.hpp>
+#include <glm/gtc/matrix_inverse.hpp>
+
+#include "vulkan/vulkan.h"
+#include "VulkanTools.h"
+#include "VulkanBuffer.h"
+#include "VulkanDevice.h"
+
+struct Vertex
+{
+	float pos[3];
+	float normal[3];
+	float color[3];
+
+	Vertex(const glm::vec3& p, const glm::vec3& n, const glm::vec3& c)
+	{
+		pos[0] = p.x;
+		pos[1] = p.y;
+		pos[2] = p.z;
+		color[0] = c.x;
+		color[1] = c.y;
+		color[2] = c.z;
+		normal[0] = n.x;
+		normal[1] = n.y;
+		normal[2] = n.z;
+	}
+};
+
+struct GearInfo
+{
+	float innerRadius;
+	float outerRadius;
+	float width;
+	int numTeeth;
+	float toothDepth;
+	glm::vec3 color;
+	glm::vec3 pos;
+	float rotSpeed;
+	float rotOffset;
+};
+
+class VulkanGear
+{
+private:
+	struct UBO
+	{
+		glm::mat4 projection;
+		glm::mat4 model;
+		glm::mat4 normal;
+		glm::mat4 view;
+		glm::vec3 lightPos;
+	};
+
+	vks::VulkanDevice *vulkanDevice;
+
+	glm::vec3 color;
+	glm::vec3 pos;
+	float rotSpeed;
+	float rotOffset;
+
+	vks::Buffer vertexBuffer;
+	vks::Buffer indexBuffer;
+	uint32_t indexCount;
+
+	UBO ubo;
+	vks::Buffer uniformBuffer;
+
+	int32_t newVertex(std::vector<Vertex> *vBuffer, float x, float y, float z, const glm::vec3& normal);
+	void newFace(std::vector<uint32_t> *iBuffer, int a, int b, int c);
+
+	void prepareUniformBuffer();
+public:
+	VkDescriptorSet descriptorSet;
+
+	void draw(VkCommandBuffer cmdbuffer, VkPipelineLayout pipelineLayout);
+	void updateUniformBuffer(glm::mat4 perspective, glm::mat4 view, float timer);
+
+	void setupDescriptorSet(VkDescriptorPool pool, VkDescriptorSetLayout descriptorSetLayout);
+
+	VulkanGear(vks::VulkanDevice *vulkanDevice) : vulkanDevice(vulkanDevice) {};
+	~VulkanGear();
+
+	void generate(GearInfo *gearinfo, VkQueue queue);
+
+};
+
diff --git a/external/Vulkan/examples/geometryshader/geometryshader.cpp b/external/Vulkan/examples/geometryshader/geometryshader.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5fe9e4a3dfd11229483639b87b58a030a92d3419
--- /dev/null
+++ b/external/Vulkan/examples/geometryshader/geometryshader.cpp
@@ -0,0 +1,350 @@
+/*
+* Vulkan Example - Geometry shader (vertex normal debugging)
+*
+* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkanexamplebase.h"
+#include "VulkanglTFModel.h"
+
+#define VERTEX_BUFFER_BIND_ID 0
+#define ENABLE_VALIDATION false
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	bool displayNormals = true;
+
+	vkglTF::Model scene;
+
+	struct {
+		glm::mat4 projection;
+		glm::mat4 modelView;
+	} uboVS;
+
+	struct {
+		glm::mat4 projection;
+		glm::mat4 modelView;
+		glm::vec2 viewportDim;
+	} uboGS;
+
+	struct {
+		vks::Buffer VS;
+		vks::Buffer GS;
+	} uniformBuffers;
+
+	struct {
+		VkPipeline solid;
+		VkPipeline normals;
+	} pipelines;
+
+	VkPipelineLayout pipelineLayout;
+	VkDescriptorSet descriptorSet;
+	VkDescriptorSetLayout descriptorSetLayout;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Geometry shader normal debugging";
+		camera.type = Camera::CameraType::lookat;
+		camera.setPosition(glm::vec3(0.0f, 0.0f, -1.0f));
+		camera.setRotation(glm::vec3(0.0f, -25.0f, 0.0f));
+		camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 128.0f);
+	}
+
+	~VulkanExample()
+	{
+		// Clean up used Vulkan resources
+		// Note : Inherited destructor cleans up resources stored in base class
+		vkDestroyPipeline(device, pipelines.solid, nullptr);
+		vkDestroyPipeline(device, pipelines.normals, nullptr);
+
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+
+		uniformBuffers.GS.destroy();
+		uniformBuffers.VS.destroy();
+	}
+
+	// Enable physical device features required for this example
+	virtual void getEnabledFeatures()
+	{
+		// Geometry shader support is required for this example
+		if (deviceFeatures.geometryShader) {
+			enabledFeatures.geometryShader = VK_TRUE;
+		}
+		else {
+			vks::tools::exitFatal("Selected GPU does not support geometry shaders!", VK_ERROR_FEATURE_NOT_PRESENT);
+		}
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		clearValues[0].color = { { 0.0f, 0.0f, 0.0f, 0.0f } };
+		clearValues[1].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.offset.x = 0;
+		renderPassBeginInfo.renderArea.offset.y = 0;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		renderPassBeginInfo.clearValueCount = 2;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			// Set target frame buffer
+			renderPassBeginInfo.framebuffer = frameBuffers[i];
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f
+			);
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+			VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+			vkCmdSetLineWidth(drawCmdBuffers[i], 1.0f);
+
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL);
+
+			VkDeviceSize offsets[1] = { 0 };
+
+			// Solid shading
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.solid);
+			scene.draw(drawCmdBuffers[i]);
+
+			// Normal debugging
+			if (displayNormals)
+			{
+				vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.normals);
+				scene.draw(drawCmdBuffers[i]);
+			}
+
+			drawUI(drawCmdBuffers[i]);
+
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void loadAssets()
+	{
+		scene.loadFromFile(getAssetPath() + "models/suzanne.gltf", vulkanDevice, queue, vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::PreMultiplyVertexColors | vkglTF::FileLoadingFlags::FlipY);
+	}
+
+	void setupDescriptorPool()
+	{
+		// Example uses two ubos
+		std::vector<VkDescriptorPoolSize> poolSizes =
+		{
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2),
+		};
+
+		VkDescriptorPoolCreateInfo descriptorPoolInfo =
+			vks::initializers::descriptorPoolCreateInfo(
+				poolSizes.size(),
+				poolSizes.data(),
+				2);
+
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+	}
+
+	void setupDescriptorSetLayout()
+	{
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings =
+		{
+			// Binding 0 : Vertex shader ubo
+			vks::initializers::descriptorSetLayoutBinding(
+				VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+				VK_SHADER_STAGE_VERTEX_BIT,
+				0),
+			// Binding 1 : Geometry shader ubo
+			vks::initializers::descriptorSetLayoutBinding(
+				VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+				VK_SHADER_STAGE_GEOMETRY_BIT,
+				1)
+		};
+
+		VkDescriptorSetLayoutCreateInfo descriptorLayout =
+			vks::initializers::descriptorSetLayoutCreateInfo(
+				setLayoutBindings.data(),
+				setLayoutBindings.size());
+
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout));
+
+		VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo =
+			vks::initializers::pipelineLayoutCreateInfo(
+				&descriptorSetLayout,
+				1);
+
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayout));
+	}
+
+	void setupDescriptorSet()
+	{
+		VkDescriptorSetAllocateInfo allocInfo =
+			vks::initializers::descriptorSetAllocateInfo(
+				descriptorPool,
+				&descriptorSetLayout,
+				1);
+
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet));
+
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets =
+		{
+			// Binding 0 : Vertex shader ubo
+			vks::initializers::writeDescriptorSet(
+				descriptorSet,
+				VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+				0,
+				&uniformBuffers.VS.descriptor),
+			// Binding 1 : Geometry shader ubo
+			vks::initializers::writeDescriptorSet(
+				descriptorSet,
+				VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+				1,
+				&uniformBuffers.GS.descriptor)
+		};
+
+		vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, NULL);
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE, VK_FRONT_FACE_CLOCKWISE, 0);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+		VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
+		std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR, VK_DYNAMIC_STATE_LINE_WIDTH };
+		VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables, 0);
+
+		// Tessellation pipeline
+		std::array<VkPipelineShaderStageCreateInfo, 3> shaderStages;
+		shaderStages[0] = loadShader(getShadersPath() + "geometryshader/base.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "geometryshader/base.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		shaderStages[2] = loadShader(getShadersPath() + "geometryshader/normaldebug.geom.spv", VK_SHADER_STAGE_GEOMETRY_BIT);
+
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass, 0);
+		pipelineCI.pInputAssemblyState = &inputAssemblyState;
+		pipelineCI.pRasterizationState = &rasterizationState;
+		pipelineCI.pColorBlendState = &colorBlendState;
+		pipelineCI.pMultisampleState = &multisampleState;
+		pipelineCI.pViewportState = &viewportState;
+		pipelineCI.pDepthStencilState = &depthStencilState;
+		pipelineCI.pDynamicState = &dynamicState;
+		pipelineCI.stageCount = shaderStages.size();
+		pipelineCI.pStages = shaderStages.data();
+		pipelineCI.renderPass = renderPass;
+		pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::Color });
+
+		// Normal debugging pipeline
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.normals));
+
+		// Solid rendering pipeline
+		shaderStages[0] = loadShader(getShadersPath() + "geometryshader/mesh.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "geometryshader/mesh.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		pipelineCI.stageCount = 2;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.solid));
+	}
+
+	// Prepare and initialize uniform buffer containing shader uniforms
+	void prepareUniformBuffers()
+	{
+		// Vertex shader uniform buffer block
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.VS,
+			sizeof(uboVS)));
+
+		// Geometry shader uniform buffer block
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.GS,
+			sizeof(uboGS)));
+
+		// Map persistent
+		VK_CHECK_RESULT(uniformBuffers.VS.map());
+		VK_CHECK_RESULT(uniformBuffers.GS.map());
+
+		updateUniformBuffers();
+	}
+
+	void updateUniformBuffers()
+	{
+		// Vertex shader
+		uboVS.projection = camera.matrices.perspective;
+		uboVS.modelView = camera.matrices.view;
+		memcpy(uniformBuffers.VS.mapped, &uboVS, sizeof(uboVS));
+		// Geometry shader
+		uboGS.projection = camera.matrices.perspective;
+		uboGS.modelView = camera.matrices.view;
+		uboGS.viewportDim = glm::vec2(width, height);
+		memcpy(uniformBuffers.GS.mapped, &uboGS, sizeof(uboGS));
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+
+		// Command buffer to be submitted to the queue
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+
+		// Submit to queue
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+
+		VulkanExampleBase::submitFrame();
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		loadAssets();
+		prepareUniformBuffers();
+		setupDescriptorSetLayout();
+		preparePipelines();
+		setupDescriptorPool();
+		setupDescriptorSet();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+	}
+
+	virtual void viewChanged()
+	{
+		updateUniformBuffers();
+	}
+
+	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
+	{
+		if (overlay->header("Settings")) {
+			if (overlay->checkBox("Display normals", &displayNormals)) {
+				buildCommandBuffers();
+			}
+		}
+	}
+
+};
+
+VULKAN_EXAMPLE_MAIN()
\ No newline at end of file
diff --git a/external/Vulkan/examples/gltfloading/gltfloading.cpp b/external/Vulkan/examples/gltfloading/gltfloading.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..eae2e3da354421babae01493392f0938644b2ef0
--- /dev/null
+++ b/external/Vulkan/examples/gltfloading/gltfloading.cpp
@@ -0,0 +1,742 @@
+/*
+* Vulkan Example - glTF scene loading and rendering
+*
+* Copyright (C) 2020-2021 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+/*
+ * Shows how to load and display a simple scene from a glTF file
+ * Note that this isn't a complete glTF loader and only basic functions are shown here
+ * This means no complex materials, no animations, no skins, etc.
+ * For details on how glTF 2.0 works, see the official spec at https://github.com/KhronosGroup/glTF/tree/master/specification/2.0
+ *
+ * Other samples will load models using a dedicated model loader with more features (see base/VulkanglTFModel.hpp)
+ *
+ * If you are looking for a complete glTF implementation, check out https://github.com/SaschaWillems/Vulkan-glTF-PBR/
+ */
+
+#define TINYGLTF_IMPLEMENTATION
+#define STB_IMAGE_IMPLEMENTATION
+#define TINYGLTF_NO_STB_IMAGE_WRITE
+#ifdef VK_USE_PLATFORM_ANDROID_KHR
+#define TINYGLTF_ANDROID_LOAD_FROM_ASSETS
+#endif
+#include "tiny_gltf.h"
+
+#include "vulkanexamplebase.h"
+
+#define ENABLE_VALIDATION false
+
+// Contains everything required to render a glTF model in Vulkan
+// This class is heavily simplified (compared to glTF's feature set) but retains the basic glTF structure
+class VulkanglTFModel
+{
+public:
+	// The class requires some Vulkan objects so it can create it's own resources
+	vks::VulkanDevice* vulkanDevice;
+	VkQueue copyQueue;
+
+	// The vertex layout for the samples' model
+	struct Vertex {
+		glm::vec3 pos;
+		glm::vec3 normal;
+		glm::vec2 uv;
+		glm::vec3 color;
+	};
+
+	// Single vertex buffer for all primitives
+	struct {
+		VkBuffer buffer;
+		VkDeviceMemory memory;
+	} vertices;
+
+	// Single index buffer for all primitives
+	struct {
+		int count;
+		VkBuffer buffer;
+		VkDeviceMemory memory;
+	} indices;
+
+	// The following structures roughly represent the glTF scene structure
+	// To keep things simple, they only contain those properties that are required for this sample
+	struct Node;
+
+	// A primitive contains the data for a single draw call
+	struct Primitive {
+		uint32_t firstIndex;
+		uint32_t indexCount;
+		int32_t materialIndex;
+	};
+
+	// Contains the node's (optional) geometry and can be made up of an arbitrary number of primitives
+	struct Mesh {
+		std::vector<Primitive> primitives;
+	};
+
+	// A node represents an object in the glTF scene graph
+	struct Node {
+		Node* parent;
+		std::vector<Node> children;
+		Mesh mesh;
+		glm::mat4 matrix;
+	};
+
+	// A glTF material stores information in e.g. the texture that is attached to it and colors
+	struct Material {
+		glm::vec4 baseColorFactor = glm::vec4(1.0f);
+		uint32_t baseColorTextureIndex;
+	};
+
+	// Contains the texture for a single glTF image
+	// Images may be reused by texture objects and are as such separated
+	struct Image {
+		vks::Texture2D texture;
+		// We also store (and create) a descriptor set that's used to access this texture from the fragment shader
+		VkDescriptorSet descriptorSet;
+	};
+
+	// A glTF texture stores a reference to the image and a sampler
+	// In this sample, we are only interested in the image
+	struct Texture {
+		int32_t imageIndex;
+	};
+
+	/*
+		Model data
+	*/
+	std::vector<Image> images;
+	std::vector<Texture> textures;
+	std::vector<Material> materials;
+	std::vector<Node> nodes;
+
+	~VulkanglTFModel()
+	{
+		// Release all Vulkan resources allocated for the model
+		vkDestroyBuffer(vulkanDevice->logicalDevice, vertices.buffer, nullptr);
+		vkFreeMemory(vulkanDevice->logicalDevice, vertices.memory, nullptr);
+		vkDestroyBuffer(vulkanDevice->logicalDevice, indices.buffer, nullptr);
+		vkFreeMemory(vulkanDevice->logicalDevice, indices.memory, nullptr);
+		for (Image image : images) {
+			vkDestroyImageView(vulkanDevice->logicalDevice, image.texture.view, nullptr);
+			vkDestroyImage(vulkanDevice->logicalDevice, image.texture.image, nullptr);
+			vkDestroySampler(vulkanDevice->logicalDevice, image.texture.sampler, nullptr);
+			vkFreeMemory(vulkanDevice->logicalDevice, image.texture.deviceMemory, nullptr);
+		}
+	}
+
+	/*
+		glTF loading functions
+
+		The following functions take a glTF input model loaded via tinyglTF and convert all required data into our own structure
+	*/
+
+	void loadImages(tinygltf::Model& input)
+	{
+		// Images can be stored inside the glTF (which is the case for the sample model), so instead of directly
+		// loading them from disk, we fetch them from the glTF loader and upload the buffers
+		images.resize(input.images.size());
+		for (size_t i = 0; i < input.images.size(); i++) {
+			tinygltf::Image& glTFImage = input.images[i];
+			// Get the image data from the glTF loader
+			unsigned char* buffer = nullptr;
+			VkDeviceSize bufferSize = 0;
+			bool deleteBuffer = false;
+			// We convert RGB-only images to RGBA, as most devices don't support RGB-formats in Vulkan
+			if (glTFImage.component == 3) {
+				bufferSize = glTFImage.width * glTFImage.height * 4;
+				buffer = new unsigned char[bufferSize];
+				unsigned char* rgba = buffer;
+				unsigned char* rgb = &glTFImage.image[0];
+				for (size_t i = 0; i < glTFImage.width * glTFImage.height; ++i) {
+					memcpy(rgba, rgb, sizeof(unsigned char) * 3);
+					rgba += 4;
+					rgb += 3;
+				}
+				deleteBuffer = true;
+			}
+			else {
+				buffer = &glTFImage.image[0];
+				bufferSize = glTFImage.image.size();
+			}
+			// Load texture from image buffer
+			images[i].texture.fromBuffer(buffer, bufferSize, VK_FORMAT_R8G8B8A8_UNORM, glTFImage.width, glTFImage.height, vulkanDevice, copyQueue);
+			if (deleteBuffer) {
+				delete[] buffer;
+			}
+		}
+	}
+
+	void loadTextures(tinygltf::Model& input)
+	{
+		textures.resize(input.textures.size());
+		for (size_t i = 0; i < input.textures.size(); i++) {
+			textures[i].imageIndex = input.textures[i].source;
+		}
+	}
+
+	void loadMaterials(tinygltf::Model& input)
+	{
+		materials.resize(input.materials.size());
+		for (size_t i = 0; i < input.materials.size(); i++) {
+			// We only read the most basic properties required for our sample
+			tinygltf::Material glTFMaterial = input.materials[i];
+			// Get the base color factor
+			if (glTFMaterial.values.find("baseColorFactor") != glTFMaterial.values.end()) {
+				materials[i].baseColorFactor = glm::make_vec4(glTFMaterial.values["baseColorFactor"].ColorFactor().data());
+			}
+			// Get base color texture index
+			if (glTFMaterial.values.find("baseColorTexture") != glTFMaterial.values.end()) {
+				materials[i].baseColorTextureIndex = glTFMaterial.values["baseColorTexture"].TextureIndex();
+			}
+		}
+	}
+
+	void loadNode(const tinygltf::Node& inputNode, const tinygltf::Model& input, VulkanglTFModel::Node* parent, std::vector<uint32_t>& indexBuffer, std::vector<VulkanglTFModel::Vertex>& vertexBuffer)
+	{
+		VulkanglTFModel::Node node{};
+		node.matrix = glm::mat4(1.0f);
+
+		// Get the local node matrix
+		// It's either made up from translation, rotation, scale or a 4x4 matrix
+		if (inputNode.translation.size() == 3) {
+			node.matrix = glm::translate(node.matrix, glm::vec3(glm::make_vec3(inputNode.translation.data())));
+		}
+		if (inputNode.rotation.size() == 4) {
+			glm::quat q = glm::make_quat(inputNode.rotation.data());
+			node.matrix *= glm::mat4(q);
+		}
+		if (inputNode.scale.size() == 3) {
+			node.matrix = glm::scale(node.matrix, glm::vec3(glm::make_vec3(inputNode.scale.data())));
+		}
+		if (inputNode.matrix.size() == 16) {
+			node.matrix = glm::make_mat4x4(inputNode.matrix.data());
+		};
+
+		// Load node's children
+		if (inputNode.children.size() > 0) {
+			for (size_t i = 0; i < inputNode.children.size(); i++) {
+				loadNode(input.nodes[inputNode.children[i]], input , &node, indexBuffer, vertexBuffer);
+			}
+		}
+
+		// If the node contains mesh data, we load vertices and indices from the buffers
+		// In glTF this is done via accessors and buffer views
+		if (inputNode.mesh > -1) {
+			const tinygltf::Mesh mesh = input.meshes[inputNode.mesh];
+			// Iterate through all primitives of this node's mesh
+			for (size_t i = 0; i < mesh.primitives.size(); i++) {
+				const tinygltf::Primitive& glTFPrimitive = mesh.primitives[i];
+				uint32_t firstIndex = static_cast<uint32_t>(indexBuffer.size());
+				uint32_t vertexStart = static_cast<uint32_t>(vertexBuffer.size());
+				uint32_t indexCount = 0;
+				// Vertices
+				{
+					const float* positionBuffer = nullptr;
+					const float* normalsBuffer = nullptr;
+					const float* texCoordsBuffer = nullptr;
+					size_t vertexCount = 0;
+
+					// Get buffer data for vertex normals
+					if (glTFPrimitive.attributes.find("POSITION") != glTFPrimitive.attributes.end()) {
+						const tinygltf::Accessor& accessor = input.accessors[glTFPrimitive.attributes.find("POSITION")->second];
+						const tinygltf::BufferView& view = input.bufferViews[accessor.bufferView];
+						positionBuffer = reinterpret_cast<const float*>(&(input.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset]));
+						vertexCount = accessor.count;
+					}
+					// Get buffer data for vertex normals
+					if (glTFPrimitive.attributes.find("NORMAL") != glTFPrimitive.attributes.end()) {
+						const tinygltf::Accessor& accessor = input.accessors[glTFPrimitive.attributes.find("NORMAL")->second];
+						const tinygltf::BufferView& view = input.bufferViews[accessor.bufferView];
+						normalsBuffer = reinterpret_cast<const float*>(&(input.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset]));
+					}
+					// Get buffer data for vertex texture coordinates
+					// glTF supports multiple sets, we only load the first one
+					if (glTFPrimitive.attributes.find("TEXCOORD_0") != glTFPrimitive.attributes.end()) {
+						const tinygltf::Accessor& accessor = input.accessors[glTFPrimitive.attributes.find("TEXCOORD_0")->second];
+						const tinygltf::BufferView& view = input.bufferViews[accessor.bufferView];
+						texCoordsBuffer = reinterpret_cast<const float*>(&(input.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset]));
+					}
+
+					// Append data to model's vertex buffer
+					for (size_t v = 0; v < vertexCount; v++) {
+						Vertex vert{};
+						vert.pos = glm::vec4(glm::make_vec3(&positionBuffer[v * 3]), 1.0f);
+						vert.normal = glm::normalize(glm::vec3(normalsBuffer ? glm::make_vec3(&normalsBuffer[v * 3]) : glm::vec3(0.0f)));
+						vert.uv = texCoordsBuffer ? glm::make_vec2(&texCoordsBuffer[v * 2]) : glm::vec3(0.0f);
+						vert.color = glm::vec3(1.0f);
+						vertexBuffer.push_back(vert);
+					}
+				}
+				// Indices
+				{
+					const tinygltf::Accessor& accessor = input.accessors[glTFPrimitive.indices];
+					const tinygltf::BufferView& bufferView = input.bufferViews[accessor.bufferView];
+					const tinygltf::Buffer& buffer = input.buffers[bufferView.buffer];
+
+					indexCount += static_cast<uint32_t>(accessor.count);
+
+					// glTF supports different component types of indices
+					switch (accessor.componentType) {
+					case TINYGLTF_PARAMETER_TYPE_UNSIGNED_INT: {
+						const uint32_t* buf = reinterpret_cast<const uint32_t*>(&buffer.data[accessor.byteOffset + bufferView.byteOffset]);
+						for (size_t index = 0; index < accessor.count; index++) {
+							indexBuffer.push_back(buf[index] + vertexStart);
+						}
+						break;
+					}
+					case TINYGLTF_PARAMETER_TYPE_UNSIGNED_SHORT: {
+						const uint16_t* buf = reinterpret_cast<const uint16_t*>(&buffer.data[accessor.byteOffset + bufferView.byteOffset]);
+						for (size_t index = 0; index < accessor.count; index++) {
+							indexBuffer.push_back(buf[index] + vertexStart);
+						}
+						break;
+					}
+					case TINYGLTF_PARAMETER_TYPE_UNSIGNED_BYTE: {
+						const uint8_t* buf = reinterpret_cast<const uint8_t*>(&buffer.data[accessor.byteOffset + bufferView.byteOffset]);
+						for (size_t index = 0; index < accessor.count; index++) {
+							indexBuffer.push_back(buf[index] + vertexStart);
+						}
+						break;
+					}
+					default:
+						std::cerr << "Index component type " << accessor.componentType << " not supported!" << std::endl;
+						return;
+					}
+				}
+				Primitive primitive{};
+				primitive.firstIndex = firstIndex;
+				primitive.indexCount = indexCount;
+				primitive.materialIndex = glTFPrimitive.material;
+				node.mesh.primitives.push_back(primitive);
+			}
+		}
+
+		if (parent) {
+			parent->children.push_back(node);
+		}
+		else {
+			nodes.push_back(node);
+		}
+	}
+
+	/*
+		glTF rendering functions
+	*/
+
+	// Draw a single node including child nodes (if present)
+	void drawNode(VkCommandBuffer commandBuffer, VkPipelineLayout pipelineLayout, VulkanglTFModel::Node node)
+	{
+		if (node.mesh.primitives.size() > 0) {
+			// Pass the node's matrix via push constants
+			// Traverse the node hierarchy to the top-most parent to get the final matrix of the current node
+			glm::mat4 nodeMatrix = node.matrix;
+			VulkanglTFModel::Node* currentParent = node.parent;
+			while (currentParent) {
+				nodeMatrix = currentParent->matrix * nodeMatrix;
+				currentParent = currentParent->parent;
+			}
+			// Pass the final matrix to the vertex shader using push constants
+			vkCmdPushConstants(commandBuffer, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(glm::mat4), &nodeMatrix);
+			for (VulkanglTFModel::Primitive& primitive : node.mesh.primitives) {
+				if (primitive.indexCount > 0) {
+					// Get the texture index for this primitive
+					VulkanglTFModel::Texture texture = textures[materials[primitive.materialIndex].baseColorTextureIndex];
+					// Bind the descriptor for the current primitive's texture
+					vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 1, 1, &images[texture.imageIndex].descriptorSet, 0, nullptr);
+					vkCmdDrawIndexed(commandBuffer, primitive.indexCount, 1, primitive.firstIndex, 0, 0);
+				}
+			}
+		}
+		for (auto& child : node.children) {
+			drawNode(commandBuffer, pipelineLayout, child);
+		}
+	}
+
+	// Draw the glTF scene starting at the top-level-nodes
+	void draw(VkCommandBuffer commandBuffer, VkPipelineLayout pipelineLayout)
+	{
+		// All vertices and indices are stored in single buffers, so we only need to bind once
+		VkDeviceSize offsets[1] = { 0 };
+		vkCmdBindVertexBuffers(commandBuffer, 0, 1, &vertices.buffer, offsets);
+		vkCmdBindIndexBuffer(commandBuffer, indices.buffer, 0, VK_INDEX_TYPE_UINT32);
+		// Render all nodes at top-level
+		for (auto& node : nodes) {
+			drawNode(commandBuffer, pipelineLayout, node);
+		}
+	}
+
+};
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	bool wireframe = false;
+
+	VulkanglTFModel glTFModel;
+
+	struct ShaderData {
+		vks::Buffer buffer;
+		struct Values {
+			glm::mat4 projection;
+			glm::mat4 model;
+			glm::vec4 lightPos = glm::vec4(5.0f, 5.0f, -5.0f, 1.0f);
+		} values;
+	} shaderData;
+
+	struct Pipelines {
+		VkPipeline solid;
+		VkPipeline wireframe = VK_NULL_HANDLE;
+	} pipelines;
+
+	VkPipelineLayout pipelineLayout;
+	VkDescriptorSet descriptorSet;
+
+	struct DescriptorSetLayouts {
+		VkDescriptorSetLayout matrices;
+		VkDescriptorSetLayout textures;
+	} descriptorSetLayouts;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "glTF model rendering";
+		camera.type = Camera::CameraType::lookat;
+		camera.flipY = true;
+		camera.setPosition(glm::vec3(0.0f, -0.1f, -1.0f));
+		camera.setRotation(glm::vec3(0.0f, -135.0f, 0.0f));
+		camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 256.0f);
+	}
+
+	~VulkanExample()
+	{
+		// Clean up used Vulkan resources
+		// Note : Inherited destructor cleans up resources stored in base class
+		vkDestroyPipeline(device, pipelines.solid, nullptr);
+		if (pipelines.wireframe != VK_NULL_HANDLE) {
+			vkDestroyPipeline(device, pipelines.wireframe, nullptr);
+		}
+
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.matrices, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.textures, nullptr);
+
+		shaderData.buffer.destroy();
+	}
+
+	virtual void getEnabledFeatures()
+	{
+		// Fill mode non solid is required for wireframe display
+		if (deviceFeatures.fillModeNonSolid) {
+			enabledFeatures.fillModeNonSolid = VK_TRUE;
+		};
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		clearValues[0].color = defaultClearColor;
+		clearValues[0].color = { { 0.25f, 0.25f, 0.25f, 1.0f } };;
+		clearValues[1].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.offset.x = 0;
+		renderPassBeginInfo.renderArea.offset.y = 0;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		renderPassBeginInfo.clearValueCount = 2;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		const VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+		const VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			renderPassBeginInfo.framebuffer = frameBuffers[i];
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+			// Bind scene matrices descriptor to set 0
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr);
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, wireframe ? pipelines.wireframe : pipelines.solid);
+			glTFModel.draw(drawCmdBuffers[i], pipelineLayout);
+			drawUI(drawCmdBuffers[i]);
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void loadglTFFile(std::string filename)
+	{
+		tinygltf::Model glTFInput;
+		tinygltf::TinyGLTF gltfContext;
+		std::string error, warning;
+
+		this->device = device;
+
+#if defined(__ANDROID__)
+		// On Android all assets are packed with the apk in a compressed form, so we need to open them using the asset manager
+		// We let tinygltf handle this, by passing the asset manager of our app
+		tinygltf::asset_manager = androidApp->activity->assetManager;
+#endif
+		bool fileLoaded = gltfContext.LoadASCIIFromFile(&glTFInput, &error, &warning, filename);
+
+		// Pass some Vulkan resources required for setup and rendering to the glTF model loading class
+		glTFModel.vulkanDevice = vulkanDevice;
+		glTFModel.copyQueue = queue;
+
+		std::vector<uint32_t> indexBuffer;
+		std::vector<VulkanglTFModel::Vertex> vertexBuffer;
+
+		if (fileLoaded) {
+			glTFModel.loadImages(glTFInput);
+			glTFModel.loadMaterials(glTFInput);
+			glTFModel.loadTextures(glTFInput);
+			const tinygltf::Scene& scene = glTFInput.scenes[0];
+			for (size_t i = 0; i < scene.nodes.size(); i++) {
+				const tinygltf::Node node = glTFInput.nodes[scene.nodes[i]];
+				glTFModel.loadNode(node, glTFInput, nullptr, indexBuffer, vertexBuffer);
+			}
+		}
+		else {
+			vks::tools::exitFatal("Could not open the glTF file.\n\nThe file is part of the additional asset pack.\n\nRun \"download_assets.py\" in the repository root to download the latest version.", -1);
+			return;
+		}
+
+		// Create and upload vertex and index buffer
+		// We will be using one single vertex buffer and one single index buffer for the whole glTF scene
+		// Primitives (of the glTF model) will then index into these using index offsets
+
+		size_t vertexBufferSize = vertexBuffer.size() * sizeof(VulkanglTFModel::Vertex);
+		size_t indexBufferSize = indexBuffer.size() * sizeof(uint32_t);
+		glTFModel.indices.count = static_cast<uint32_t>(indexBuffer.size());
+
+		struct StagingBuffer {
+			VkBuffer buffer;
+			VkDeviceMemory memory;
+		} vertexStaging, indexStaging;
+
+		// Create host visible staging buffers (source)
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			vertexBufferSize,
+			&vertexStaging.buffer,
+			&vertexStaging.memory,
+			vertexBuffer.data()));
+		// Index data
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			indexBufferSize,
+			&indexStaging.buffer,
+			&indexStaging.memory,
+			indexBuffer.data()));
+
+		// Create device local buffers (target)
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
+			VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
+			vertexBufferSize,
+			&glTFModel.vertices.buffer,
+			&glTFModel.vertices.memory));
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
+			VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
+			indexBufferSize,
+			&glTFModel.indices.buffer,
+			&glTFModel.indices.memory));
+
+		// Copy data from staging buffers (host) do device local buffer (gpu)
+		VkCommandBuffer copyCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+		VkBufferCopy copyRegion = {};
+
+		copyRegion.size = vertexBufferSize;
+		vkCmdCopyBuffer(
+			copyCmd,
+			vertexStaging.buffer,
+			glTFModel.vertices.buffer,
+			1,
+			&copyRegion);
+
+		copyRegion.size = indexBufferSize;
+		vkCmdCopyBuffer(
+			copyCmd,
+			indexStaging.buffer,
+			glTFModel.indices.buffer,
+			1,
+			&copyRegion);
+
+		vulkanDevice->flushCommandBuffer(copyCmd, queue, true);
+
+		// Free staging resources
+		vkDestroyBuffer(device, vertexStaging.buffer, nullptr);
+		vkFreeMemory(device, vertexStaging.memory, nullptr);
+		vkDestroyBuffer(device, indexStaging.buffer, nullptr);
+		vkFreeMemory(device, indexStaging.memory, nullptr);
+	}
+
+	void loadAssets()
+	{
+		loadglTFFile(getAssetPath() + "models/FlightHelmet/glTF/FlightHelmet.gltf");
+	}
+
+	void setupDescriptors()
+	{
+		/*
+			This sample uses separate descriptor sets (and layouts) for the matrices and materials (textures)
+		*/
+
+		std::vector<VkDescriptorPoolSize> poolSizes = {
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1),
+			// One combined image sampler per model image/texture
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, static_cast<uint32_t>(glTFModel.images.size())),
+		};
+		// One set for matrices and one per model image/texture
+		const uint32_t maxSetCount = static_cast<uint32_t>(glTFModel.images.size()) + 1;
+		VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, maxSetCount);
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+
+		// Descriptor set layout for passing matrices
+		VkDescriptorSetLayoutBinding setLayoutBinding = vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0);
+		VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCI = vks::initializers::descriptorSetLayoutCreateInfo(&setLayoutBinding, 1);
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorSetLayoutCI, nullptr, &descriptorSetLayouts.matrices));
+		// Descriptor set layout for passing material textures
+		setLayoutBinding = vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0);
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorSetLayoutCI, nullptr, &descriptorSetLayouts.textures));
+		// Pipeline layout using both descriptor sets (set 0 = matrices, set 1 = material)
+		std::array<VkDescriptorSetLayout, 2> setLayouts = { descriptorSetLayouts.matrices, descriptorSetLayouts.textures };
+		VkPipelineLayoutCreateInfo pipelineLayoutCI= vks::initializers::pipelineLayoutCreateInfo(setLayouts.data(), static_cast<uint32_t>(setLayouts.size()));
+		// We will use push constants to push the local matrices of a primitive to the vertex shader
+		VkPushConstantRange pushConstantRange = vks::initializers::pushConstantRange(VK_SHADER_STAGE_VERTEX_BIT, sizeof(glm::mat4), 0);
+		// Push constant ranges are part of the pipeline layout
+		pipelineLayoutCI.pushConstantRangeCount = 1;
+		pipelineLayoutCI.pPushConstantRanges = &pushConstantRange;
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelineLayout));
+
+		// Descriptor set for scene matrices
+		VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.matrices, 1);
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet));
+		VkWriteDescriptorSet writeDescriptorSet = vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &shaderData.buffer.descriptor);
+		vkUpdateDescriptorSets(device, 1, &writeDescriptorSet, 0, nullptr);
+		// Descriptor sets for materials
+		for (auto& image : glTFModel.images) {
+			const VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.textures, 1);
+			VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &image.descriptorSet));
+			VkWriteDescriptorSet writeDescriptorSet = vks::initializers::writeDescriptorSet(image.descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &image.texture.descriptor);
+			vkUpdateDescriptorSets(device, 1, &writeDescriptorSet, 0, nullptr);
+		}
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyStateCI = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationStateCI = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
+		VkPipelineColorBlendAttachmentState blendAttachmentStateCI = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendStateCI = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentStateCI);
+		VkPipelineDepthStencilStateCreateInfo depthStencilStateCI = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportStateCI = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+		VkPipelineMultisampleStateCreateInfo multisampleStateCI = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
+		const std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
+		VkPipelineDynamicStateCreateInfo dynamicStateCI = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables.data(), static_cast<uint32_t>(dynamicStateEnables.size()), 0);
+		// Vertex input bindings and attributes
+		const std::vector<VkVertexInputBindingDescription> vertexInputBindings = {
+			vks::initializers::vertexInputBindingDescription(0, sizeof(VulkanglTFModel::Vertex), VK_VERTEX_INPUT_RATE_VERTEX),
+		};
+		const std::vector<VkVertexInputAttributeDescription> vertexInputAttributes = {
+			vks::initializers::vertexInputAttributeDescription(0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(VulkanglTFModel::Vertex, pos)),	// Location 0: Position
+			vks::initializers::vertexInputAttributeDescription(0, 1, VK_FORMAT_R32G32B32_SFLOAT, offsetof(VulkanglTFModel::Vertex, normal)),// Location 1: Normal
+			vks::initializers::vertexInputAttributeDescription(0, 2, VK_FORMAT_R32G32B32_SFLOAT, offsetof(VulkanglTFModel::Vertex, uv)),	// Location 2: Texture coordinates
+			vks::initializers::vertexInputAttributeDescription(0, 3, VK_FORMAT_R32G32B32_SFLOAT, offsetof(VulkanglTFModel::Vertex, color)),	// Location 3: Color
+		};
+		VkPipelineVertexInputStateCreateInfo vertexInputStateCI = vks::initializers::pipelineVertexInputStateCreateInfo();
+		vertexInputStateCI.vertexBindingDescriptionCount = static_cast<uint32_t>(vertexInputBindings.size());
+		vertexInputStateCI.pVertexBindingDescriptions = vertexInputBindings.data();
+		vertexInputStateCI.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertexInputAttributes.size());
+		vertexInputStateCI.pVertexAttributeDescriptions = vertexInputAttributes.data();
+
+		const std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages = {
+			loadShader(getShadersPath() + "gltfloading/mesh.vert.spv", VK_SHADER_STAGE_VERTEX_BIT),
+			loadShader(getShadersPath() + "gltfloading/mesh.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT)
+		};
+
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass, 0);
+		pipelineCI.pVertexInputState = &vertexInputStateCI;
+		pipelineCI.pInputAssemblyState = &inputAssemblyStateCI;
+		pipelineCI.pRasterizationState = &rasterizationStateCI;
+		pipelineCI.pColorBlendState = &colorBlendStateCI;
+		pipelineCI.pMultisampleState = &multisampleStateCI;
+		pipelineCI.pViewportState = &viewportStateCI;
+		pipelineCI.pDepthStencilState = &depthStencilStateCI;
+		pipelineCI.pDynamicState = &dynamicStateCI;
+		pipelineCI.stageCount = static_cast<uint32_t>(shaderStages.size());
+		pipelineCI.pStages = shaderStages.data();
+
+		// Solid rendering pipeline
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.solid));
+
+		// Wire frame rendering pipeline
+		if (deviceFeatures.fillModeNonSolid) {
+			rasterizationStateCI.polygonMode = VK_POLYGON_MODE_LINE;
+			rasterizationStateCI.lineWidth = 1.0f;
+			VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.wireframe));
+		}
+	}
+
+	// Prepare and initialize uniform buffer containing shader uniforms
+	void prepareUniformBuffers()
+	{
+		// Vertex shader uniform buffer block
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&shaderData.buffer,
+			sizeof(shaderData.values)));
+
+		// Map persistent
+		VK_CHECK_RESULT(shaderData.buffer.map());
+
+		updateUniformBuffers();
+	}
+
+	void updateUniformBuffers()
+	{
+		shaderData.values.projection = camera.matrices.perspective;
+		shaderData.values.model = camera.matrices.view;
+		memcpy(shaderData.buffer.mapped, &shaderData.values, sizeof(shaderData.values));
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		loadAssets();
+		prepareUniformBuffers();
+		setupDescriptors();
+		preparePipelines();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		renderFrame();
+		if (camera.updated) {
+			updateUniformBuffers();
+		}
+	}
+
+	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
+	{
+		if (overlay->header("Settings")) {
+			if (overlay->checkBox("Wireframe", &wireframe)) {
+				buildCommandBuffers();
+			}
+		}
+	}
+};
+
+VULKAN_EXAMPLE_MAIN()
\ No newline at end of file
diff --git a/external/Vulkan/examples/gltfscenerendering/README.md b/external/Vulkan/examples/gltfscenerendering/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..49016879ed46bbce13f945280f88a798cfca7e7f
--- /dev/null
+++ b/external/Vulkan/examples/gltfscenerendering/README.md
@@ -0,0 +1,323 @@
+# glTF scene rendering
+
+<img src="../../screenshots/gltf_scene.jpg" height="256px">
+
+## Synopsis
+
+Render a complete scene loaded from an glTF file. The sample is based on the [glTF scene](../gltfscene) sample, and adds data structures, functions and shaders required to render a more complex scene using Crytek's Sponza model.
+
+## Description
+
+This example demonstrates how to render a more complex scene loaded from a glTF model.
+
+It builds on the basic glTF scene sample but instead of using global pipelines, it adds per-material pipelines that are dynamically created from the material definitions of the glTF model.
+
+Those pipelines pass per-material parameters to the shader so different materials for e.g. displaying opaque and transparent objects can be built from a single shader.
+
+It also adds data structures, loading functions and shaders to do normal mapping and an easy way of toggling visibility for the scene nodes.
+
+Note that this is not a full glTF implementation as this would be beyond the scope of a simple example. For a complete glTF Vulkan implementation see [my Vulkan glTF PBR renderer](https://github.com/SaschaWillems/Vulkan-glTF-PBR/).
+
+For details on glTF refer to the [official glTF 2.0 specification](https://github.com/KhronosGroup/glTF/tree/master/specification/2.0).
+
+## Points of interest
+
+**Note:** Points of interest are marked with a **POI** in the code comments:
+
+```cpp
+// POI: This sample uses normal mapping, so we also need to load the tangents from the glTF file
+```
+
+For this sample, those points of interest mark additions and changes compared to the basic glTF sample.
+
+### Loading external images
+
+Unlike the other samples, the glTF scene used for this example doesn't embed the images but uses external ktx images instead. This makes loading a lot faster as the ktx image format natively maps to the GPU and no longer requires us to convert RGB to RGBA, but ktx also allows us to store the mip-chain in the image file itself.
+
+So instead of creating the textures from a buffer that has been converted from the embedded RGB images, we just load the ktx files from disk:
+
+```cpp
+void VulkanglTFScene::loadImages(tinygltf::Model& input)
+{
+	images.resize(input.images.size());
+	for (size_t i = 0; i < input.images.size(); i++) {
+		tinygltf::Image& glTFImage = input.images[i];
+		images[i].texture.loadFromFile(path + "/" + glTFImage.uri, VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, copyQueue);
+	}
+}
+```
+
+### Materials 
+
+#### New Material properties
+
+```cpp
+struct Material 
+{
+	glm::vec4 baseColorFactor = glm::vec4(1.0f);
+	uint32_t baseColorTextureIndex;
+	uint32_t normalTextureIndex;
+	std::string alphaMode = "OPAQUE";
+	float alphaCutOff;
+	bool doubleSided = false;
+	VkDescriptorSet descriptorSet;
+	VkPipeline pipeline;
+};
+```
+
+Several new properties have been added to the material class for this example that are taken from the glTF source.
+
+Along with the base color we now also get the index of the normal map for that material in ```normalTextureIndex```, and store several material properties required to render the different materials in this scene:
+
+- ```alphaMode```<br/>
+The alpha mode defines how the alpha value for this material is determined. For opaque materials it's ignored, for masked materials the shader will discard fragments based on the alpha cutoff.
+- ```alphaCutOff```<br/>
+For masked materials, this value specifies the threshold between fully opaque and fully transparent. This is used to discard fragments in the fragment shader.
+- ```doubleSided```<br/>
+This property is used to select the appropriate culling mode for this material. For double-sided materials, culling will be disabled.
+
+Retrieving these additional values is done here:
+
+```cpp
+void VulkanglTFScene::loadMaterials(tinygltf::Model& input)
+{
+	materials.resize(input.materials.size());
+	for (size_t i = 0; i < input.materials.size(); i++) {
+		tinygltf::Material glTFMaterial = input.materials[i];
+		...
+		materials[i].alphaMode = glTFMaterial.alphaMode;
+		materials[i].alphaCutOff = (float)glTFMaterial.alphaCutoff;
+		materials[i].doubleSided = glTFMaterial.doubleSided;
+	}
+}
+```
+**Note:** We only read the glTF material properties we use in this sample. There are many more, details on those can be found [here](https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#materials).
+
+#### Per-Material pipelines
+
+Unlike most of the other samples that use a few pre-defined pipelines, this sample will dynamically generate per-material pipelines based on material properties in the ```VulkanExample::preparePipelines()``` function
+
+We first setup pipeline state that's common for all materials:
+
+```cpp
+// Setup common pipeline state properties...
+VkPipelineInputAssemblyStateCreateInfo inputAssemblyStateCI = ...
+VkPipelineRasterizationStateCreateInfo rasterizationStateCI = ...
+VkPipelineColorBlendAttachmentState blendAttachmentStateCI = ...
+...
+
+for (auto &material : glTFScene.materials) 
+{
+	...
+```
+
+For each material we then set constant properties for the fragment shader using specialization constants:
+
+```cpp
+	struct MaterialSpecializationData {
+		bool alphaMask;
+		float alphaMaskCutoff;
+	} materialSpecializationData;
+
+	materialSpecializationData.alphaMask = material.alphaMode == "MASK";
+	materialSpecializationData.alphaMaskCutoff = material.alphaCutOff;
+
+	std::vector<VkSpecializationMapEntry> specializationMapEntries = {
+		vks::initializers::specializationMapEntry(0, offsetof(MaterialSpecializationData, alphaMask), sizeof(MaterialSpecializationData::alphaMask)),
+		vks::initializers::specializationMapEntry(1, offsetof(MaterialSpecializationData, alphaMaskCutoff), sizeof(MaterialSpecializationData::alphaMaskCutoff)),
+	};
+	VkSpecializationInfo specializationInfo = vks::initializers::specializationInfo(specializationMapEntries, sizeof(materialSpecializationData), &materialSpecializationData);
+	shaderStages[1].pSpecializationInfo = &specializationInfo;
+	...
+```
+
+We also set the culling mode depending on whether this material is double-sided:
+
+```cpp
+	// For double sided materials, culling will be disabled
+	rasterizationStateCI.cullMode = material.doubleSided ? VK_CULL_MODE_NONE : VK_CULL_MODE_BACK_BIT;
+```
+
+With those setup we create a pipeline for the current material and store it as a property of the material class:
+
+```cpp
+	VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &material.pipeline));
+}
+```
+
+The material now also get's it's own ```pipeline```.
+
+The alpha mask properties are used in the fragment shader to distinguish between opaque and transparent materials (```scene.frag```).
+
+Specialization constant declaration in the shaders's header:
+
+```glsl
+layout (constant_id = 0) const bool ALPHA_MASK = false;
+layout (constant_id = 1) const float ALPHA_MASK_CUTOFF = 0.0;
+```
+*Note:* The default values provided in the shader are overwritten by the values passed at pipeline creation time.
+
+For alpha masked materials, fragments below the cutoff threshold are discarded:
+
+```glsl
+	vec4 color = texture(samplerColorMap, inUV) * vec4(inColor, 1.0);
+
+	if (ALPHA_MASK) {
+		if (color.a < ALPHA_MASK_CUTOFF) {
+			discard;
+		}
+	}
+```
+
+### Normal mapping
+
+This sample also adds tangent space normal mapping to the rendering equation to add additional detail to the scene, which requires loading additional data.
+
+#### Normal maps
+
+Along with the color maps, we now also load all normal maps. From the glTF POV those are just images like all other texture maps, and are stored in the image vector. So as for loading normal maps no code changes are required. The normal map images are then referenced by the index of the normal map of the material, which is now read in addition to the other material properties:
+
+```cpp
+void VulkanglTFScene::loadMaterials(tinygltf::Model& input)
+{
+	materials.resize(input.materials.size());
+	for (size_t i = 0; i < input.materials.size(); i++) {
+		tinygltf::Material glTFMaterial = input.materials[i];
+		...
+		// Get the normal map texture index
+		if (glTFMaterial.additionalValues.find("normalTexture") != glTFMaterial.additionalValues.end()) {
+			materials[i].normalTextureIndex = glTFMaterial.additionalValues["normalTexture"].TextureIndex();
+		}
+		...
+	}
+}
+```
+**Note:* Unlike the color map index, the normal map index is stored in the ```additionalValues``` of the material.
+
+The normal maps are then bound to binding 1 via the material's descriptor set in ```VulkanExample::setupDescriptors```:
+
+```cpp
+for (auto& material : glTFScene.materials) {
+	...
+	VkDescriptorImageInfo colorMap = glTFScene.getTextureDescriptor(material.baseColorTextureIndex);
+	VkDescriptorImageInfo normalMap = glTFScene.getTextureDescriptor(material.normalTextureIndex);
+	std::vector<VkWriteDescriptorSet> writeDescriptorSets = {
+		vks::initializers::writeDescriptorSet(material.descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &colorMap),
+		vks::initializers::writeDescriptorSet(material.descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &normalMap),
+	};
+	vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr);
+}
+```
+
+The descriptor set itself is then bound to set 1 at draw time in ```VulkanglTFScene::drawNode```:
+
+```cpp
+if (node.mesh.primitives.size() > 0) {
+	...
+	for (VulkanglTFScene::Primitive& primitive : node.mesh.primitives) {
+		if (primitive.indexCount > 0) {
+			VulkanglTFScene::Material& material = materials[primitive.materialIndex];
+			...
+			vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 1, 1, &material.descriptorSet, 0, nullptr);
+			...
+		}
+	}
+}
+```
+
+Fragment shader interface in ```scene.frag```:
+
+```glsl
+layout (set = 1, binding = 0) uniform sampler2D samplerColorMap;
+layout (set = 1, binding = 1) uniform sampler2D samplerNormalMap;
+```
+
+#### Per-Vertex tangents
+
+Along with the normals we also need per-vertex tangents and bitangents for normal mapping. As the bitangent can easily be calculated using the normal and tangent, glTF only stores those two. 
+
+So just like with other vertex data already loaded we need to check if there are tangents for a node and load them from the appropriate buffer using a glTF accessor:
+
+```cpp
+void VulkanglTFScene::loadNode(const tinygltf::Node& inputNode, const tinygltf::Model& input, VulkanglTFScene::Node* parent, std::vector<uint32_t>& indexBuffer, std::vector<VulkanglTFScene::Vertex>& vertexBuffer)
+{
+	VulkanglTFScene::Node node{};
+	...
+
+	if (inputNode.mesh > -1) {
+		const tinygltf::Mesh mesh = input.meshes[inputNode.mesh];
+		for (size_t i = 0; i < mesh.primitives.size(); i++) {
+			const tinygltf::Primitive& glTFPrimitive = mesh.primitives[i];
+			// Vertices
+			{
+				...
+				const float* tangentsBuffer = nullptr;
+
+				if (glTFPrimitive.attributes.find("TANGENT") != glTFPrimitive.attributes.end()) {
+					const tinygltf::Accessor& accessor = input.accessors[glTFPrimitive.attributes.find("TANGENT")->second];
+					const tinygltf::BufferView& view = input.bufferViews[accessor.bufferView];
+					tangentsBuffer = reinterpret_cast<const float*>(&(input.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset]));
+				}
+
+				for (size_t v = 0; v < vertexCount; v++) {
+					Vertex vert{};
+					...
+					vert.tangent = tangentsBuffer ? glm::make_vec4(&tangentsBuffer[v * 4]) : glm::vec4(0.0f);
+ 					vertexBuffer.push_back(vert);
+				}
+			}
+			...
+```
+
+**Note:** The tangent is a four-component vector, with the w-component storing the handedness of the tangent basis. This will be used later on in the shader.
+
+#### Shaders
+
+Normal mapping is applied in the ```scene.frag``` fragment shader and boils down to calculating a new world-space normal from the already provided per-vertex normal and the per-fragment tangent space normals provided via the materials' normal map.
+
+With the per-vertex normal and tangent values passed to the fragment shader, we simply change the way the per-fragment normal is calculated:
+
+```glsl
+vec3 normal      = normalize(inNormal);
+vec3 tangent     = normalize(inTangent.xyz);
+vec3 bitangent   = cross(inNormal, inTangent.xyz) * inTangent.w;
+mat3 TBN         = mat3(tangent, bitangent, normal);
+vec3 localNormal = texture(samplerNormalMap, inUV).xyz * 2.0 - 1.0;
+normal           = normalize(TBN * localNormal);
+```
+
+As noted earlier, glTF does not store bitangents, but we can easily calculate them using the cross product of the normal and tangent. We also multiply this with the tangent's w-component which stores the handedness of the tangent. This is important, as this may differ between nodes in a glTF file.
+
+After that we calculate the tangent to world-space transformation matrix that is then applied to the per-fragment normal read from the normal map.
+
+This is then our new normal that is used for the lighting calculations to follow.
+
+### Rendering the scene
+
+Just like in the basic glTF sample, the scene hierarchy is added to the command buffer in ```VulkanglTFModel::draw```. Since glTF has a hierarchical node structure this function recursively calls ```VulkanglTFModel::drawNode``` for rendering a give node with it's children.
+
+The only real change in this sample is binding the per-material pipeline for a node's mesh:
+
+```cpp
+void VulkanglTFScene::drawNode(VkCommandBuffer commandBuffer, VkPipelineLayout pipelineLayout, VulkanglTFScene::Node node)
+{
+	if (!node.visible) {
+		return;
+	}
+	if (node.mesh.primitives.size() > 0) {
+		...
+		vkCmdPushConstants(commandBuffer, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(glm::mat4), &nodeMatrix);
+		for (VulkanglTFScene::Primitive& primitive : node.mesh.primitives) {
+			if (primitive.indexCount > 0) {
+				VulkanglTFScene::Material& material = materials[primitive.materialIndex];
+				vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, material.pipeline);
+				vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 1, 1, &material.descriptorSet, 0, nullptr);
+				vkCmdDrawIndexed(commandBuffer, primitive.indexCount, 1, primitive.firstIndex, 0, 0);
+			}
+		}
+	}
+	for (auto& child : node.children) {
+		drawNode(commandBuffer, pipelineLayout, child);
+	}
+}
+```
\ No newline at end of file
diff --git a/external/Vulkan/examples/gltfscenerendering/gltfscenerendering.cpp b/external/Vulkan/examples/gltfscenerendering/gltfscenerendering.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..64f062f13606b4299f76533bf4d07c3f6aad3991
--- /dev/null
+++ b/external/Vulkan/examples/gltfscenerendering/gltfscenerendering.cpp
@@ -0,0 +1,662 @@
+/*
+* Vulkan Example - Scene rendering
+*
+* Copyright (C) 2020-2021 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*
+* Summary:
+* Render a complete scene loaded from an glTF file. The sample is based on the glTF model loading sample, 
+* and adds data structures, functions and shaders required to render a more complex scene using Crytek's Sponza model.
+*
+* This sample comes with a tutorial, see the README.md in this folder
+*/
+
+#include "gltfscenerendering.h"
+
+/*
+	Vulkan glTF scene class
+*/
+
+VulkanglTFScene::~VulkanglTFScene()
+{
+	// Release all Vulkan resources allocated for the model
+	vkDestroyBuffer(vulkanDevice->logicalDevice, vertices.buffer, nullptr);
+	vkFreeMemory(vulkanDevice->logicalDevice, vertices.memory, nullptr);
+	vkDestroyBuffer(vulkanDevice->logicalDevice, indices.buffer, nullptr);
+	vkFreeMemory(vulkanDevice->logicalDevice, indices.memory, nullptr);
+	for (Image image : images) {
+		vkDestroyImageView(vulkanDevice->logicalDevice, image.texture.view, nullptr);
+		vkDestroyImage(vulkanDevice->logicalDevice, image.texture.image, nullptr);
+		vkDestroySampler(vulkanDevice->logicalDevice, image.texture.sampler, nullptr);
+		vkFreeMemory(vulkanDevice->logicalDevice, image.texture.deviceMemory, nullptr);
+	}
+	for (Material material : materials) {
+		vkDestroyPipeline(vulkanDevice->logicalDevice, material.pipeline, nullptr);
+	}
+}
+
+/*
+	glTF loading functions
+
+	The following functions take a glTF input model loaded via tinyglTF and convert all required data into our own structure
+*/
+
+void VulkanglTFScene::loadImages(tinygltf::Model& input)
+{
+	// POI: The textures for the glTF file used in this sample are stored as external ktx files, so we can directly load them from disk without the need for conversion
+	images.resize(input.images.size());
+	for (size_t i = 0; i < input.images.size(); i++) {
+		tinygltf::Image& glTFImage = input.images[i];
+		images[i].texture.loadFromFile(path + "/" + glTFImage.uri, VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, copyQueue);
+	}
+}
+
+void VulkanglTFScene::loadTextures(tinygltf::Model& input)
+{
+	textures.resize(input.textures.size());
+	for (size_t i = 0; i < input.textures.size(); i++) {
+		textures[i].imageIndex = input.textures[i].source;
+	}
+}
+
+void VulkanglTFScene::loadMaterials(tinygltf::Model& input)
+{
+	materials.resize(input.materials.size());
+	for (size_t i = 0; i < input.materials.size(); i++) {
+		// We only read the most basic properties required for our sample
+		tinygltf::Material glTFMaterial = input.materials[i];
+		// Get the base color factor
+		if (glTFMaterial.values.find("baseColorFactor") != glTFMaterial.values.end()) {
+			materials[i].baseColorFactor = glm::make_vec4(glTFMaterial.values["baseColorFactor"].ColorFactor().data());
+		}
+		// Get base color texture index
+		if (glTFMaterial.values.find("baseColorTexture") != glTFMaterial.values.end()) {
+			materials[i].baseColorTextureIndex = glTFMaterial.values["baseColorTexture"].TextureIndex();
+		}
+		// Get the normal map texture index
+		if (glTFMaterial.additionalValues.find("normalTexture") != glTFMaterial.additionalValues.end()) {
+			materials[i].normalTextureIndex = glTFMaterial.additionalValues["normalTexture"].TextureIndex();
+		}
+		// Get some additional material parameters that are used in this sample
+		materials[i].alphaMode = glTFMaterial.alphaMode;
+		materials[i].alphaCutOff = (float)glTFMaterial.alphaCutoff;
+		materials[i].doubleSided = glTFMaterial.doubleSided;
+	}
+}
+
+void VulkanglTFScene::loadNode(const tinygltf::Node& inputNode, const tinygltf::Model& input, VulkanglTFScene::Node* parent, std::vector<uint32_t>& indexBuffer, std::vector<VulkanglTFScene::Vertex>& vertexBuffer)
+{
+	VulkanglTFScene::Node node{};
+	node.name = inputNode.name;
+	
+	// Get the local node matrix
+	// It's either made up from translation, rotation, scale or a 4x4 matrix
+	node.matrix = glm::mat4(1.0f);
+	if (inputNode.translation.size() == 3) {
+		node.matrix = glm::translate(node.matrix, glm::vec3(glm::make_vec3(inputNode.translation.data())));
+	}
+	if (inputNode.rotation.size() == 4) {
+		glm::quat q = glm::make_quat(inputNode.rotation.data());
+		node.matrix *= glm::mat4(q);
+	}
+	if (inputNode.scale.size() == 3) {
+		node.matrix = glm::scale(node.matrix, glm::vec3(glm::make_vec3(inputNode.scale.data())));
+	}
+	if (inputNode.matrix.size() == 16) {
+		node.matrix = glm::make_mat4x4(inputNode.matrix.data());
+	};
+
+	// Load node's children
+	if (inputNode.children.size() > 0) {
+		for (size_t i = 0; i < inputNode.children.size(); i++) {
+			loadNode(input.nodes[inputNode.children[i]], input, &node, indexBuffer, vertexBuffer);
+		}
+	}
+
+	// If the node contains mesh data, we load vertices and indices from the buffers
+	// In glTF this is done via accessors and buffer views
+	if (inputNode.mesh > -1) {
+		const tinygltf::Mesh mesh = input.meshes[inputNode.mesh];
+		// Iterate through all primitives of this node's mesh
+		for (size_t i = 0; i < mesh.primitives.size(); i++) {
+			const tinygltf::Primitive& glTFPrimitive = mesh.primitives[i];
+			uint32_t firstIndex = static_cast<uint32_t>(indexBuffer.size());
+			uint32_t vertexStart = static_cast<uint32_t>(vertexBuffer.size());
+			uint32_t indexCount = 0;
+			// Vertices
+			{
+				const float* positionBuffer = nullptr;
+				const float* normalsBuffer = nullptr;
+				const float* texCoordsBuffer = nullptr;
+				const float* tangentsBuffer = nullptr;
+				size_t vertexCount = 0;
+
+				// Get buffer data for vertex normals
+				if (glTFPrimitive.attributes.find("POSITION") != glTFPrimitive.attributes.end()) {
+					const tinygltf::Accessor& accessor = input.accessors[glTFPrimitive.attributes.find("POSITION")->second];
+					const tinygltf::BufferView& view = input.bufferViews[accessor.bufferView];
+					positionBuffer = reinterpret_cast<const float*>(&(input.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset]));
+					vertexCount = accessor.count;
+				}
+				// Get buffer data for vertex normals
+				if (glTFPrimitive.attributes.find("NORMAL") != glTFPrimitive.attributes.end()) {
+					const tinygltf::Accessor& accessor = input.accessors[glTFPrimitive.attributes.find("NORMAL")->second];
+					const tinygltf::BufferView& view = input.bufferViews[accessor.bufferView];
+					normalsBuffer = reinterpret_cast<const float*>(&(input.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset]));
+				}
+				// Get buffer data for vertex texture coordinates
+				// glTF supports multiple sets, we only load the first one
+				if (glTFPrimitive.attributes.find("TEXCOORD_0") != glTFPrimitive.attributes.end()) {
+					const tinygltf::Accessor& accessor = input.accessors[glTFPrimitive.attributes.find("TEXCOORD_0")->second];
+					const tinygltf::BufferView& view = input.bufferViews[accessor.bufferView];
+					texCoordsBuffer = reinterpret_cast<const float*>(&(input.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset]));
+				}
+				// POI: This sample uses normal mapping, so we also need to load the tangents from the glTF file
+				if (glTFPrimitive.attributes.find("TANGENT") != glTFPrimitive.attributes.end()) {
+					const tinygltf::Accessor& accessor = input.accessors[glTFPrimitive.attributes.find("TANGENT")->second];
+					const tinygltf::BufferView& view = input.bufferViews[accessor.bufferView];
+					tangentsBuffer = reinterpret_cast<const float*>(&(input.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset]));
+				}
+
+				// Append data to model's vertex buffer
+				for (size_t v = 0; v < vertexCount; v++) {
+					Vertex vert{};
+					vert.pos = glm::vec4(glm::make_vec3(&positionBuffer[v * 3]), 1.0f);
+					vert.normal = glm::normalize(glm::vec3(normalsBuffer ? glm::make_vec3(&normalsBuffer[v * 3]) : glm::vec3(0.0f)));
+					vert.uv = texCoordsBuffer ? glm::make_vec2(&texCoordsBuffer[v * 2]) : glm::vec3(0.0f);
+					vert.color = glm::vec3(1.0f);
+					vert.tangent = tangentsBuffer ? glm::make_vec4(&tangentsBuffer[v * 4]) : glm::vec4(0.0f);
+ 					vertexBuffer.push_back(vert);
+				}
+			}
+			// Indices
+			{
+				const tinygltf::Accessor& accessor = input.accessors[glTFPrimitive.indices];
+				const tinygltf::BufferView& bufferView = input.bufferViews[accessor.bufferView];
+				const tinygltf::Buffer& buffer = input.buffers[bufferView.buffer];
+
+				indexCount += static_cast<uint32_t>(accessor.count);
+
+				// glTF supports different component types of indices
+				switch (accessor.componentType) {
+				case TINYGLTF_PARAMETER_TYPE_UNSIGNED_INT: {
+					const uint32_t* buf = reinterpret_cast<const uint32_t*>(&buffer.data[accessor.byteOffset + bufferView.byteOffset]);
+					for (size_t index = 0; index < accessor.count; index++) {
+						indexBuffer.push_back(buf[index] + vertexStart);
+					}
+					break;
+				}
+				case TINYGLTF_PARAMETER_TYPE_UNSIGNED_SHORT: {
+					const uint16_t* buf = reinterpret_cast<const uint16_t*>(&buffer.data[accessor.byteOffset + bufferView.byteOffset]);
+					for (size_t index = 0; index < accessor.count; index++) {
+						indexBuffer.push_back(buf[index] + vertexStart);
+					}
+					break;
+				}
+				case TINYGLTF_PARAMETER_TYPE_UNSIGNED_BYTE: {
+					const uint8_t* buf = reinterpret_cast<const uint8_t*>(&buffer.data[accessor.byteOffset + bufferView.byteOffset]);
+					for (size_t index = 0; index < accessor.count; index++) {
+						indexBuffer.push_back(buf[index] + vertexStart);
+					}
+					break;
+				}
+				default:
+					std::cerr << "Index component type " << accessor.componentType << " not supported!" << std::endl;
+					return;
+				}
+			}
+			Primitive primitive{};
+			primitive.firstIndex = firstIndex;
+			primitive.indexCount = indexCount;
+			primitive.materialIndex = glTFPrimitive.material;
+			node.mesh.primitives.push_back(primitive);
+		}
+	}
+
+	if (parent) {
+		parent->children.push_back(node);
+	}
+	else {
+		nodes.push_back(node);
+	}
+}
+
+VkDescriptorImageInfo VulkanglTFScene::getTextureDescriptor(const size_t index)
+{
+	return images[index].texture.descriptor;
+}
+
+/*
+	glTF rendering functions
+*/
+
+// Draw a single node including child nodes (if present)
+void VulkanglTFScene::drawNode(VkCommandBuffer commandBuffer, VkPipelineLayout pipelineLayout, VulkanglTFScene::Node node)
+{
+	if (!node.visible) {
+		return;
+	}
+	if (node.mesh.primitives.size() > 0) {
+		// Pass the node's matrix via push constants
+		// Traverse the node hierarchy to the top-most parent to get the final matrix of the current node
+		glm::mat4 nodeMatrix = node.matrix;
+		VulkanglTFScene::Node* currentParent = node.parent;
+		while (currentParent) {
+			nodeMatrix = currentParent->matrix * nodeMatrix;
+			currentParent = currentParent->parent;
+		}
+		// Pass the final matrix to the vertex shader using push constants
+		vkCmdPushConstants(commandBuffer, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(glm::mat4), &nodeMatrix);
+		for (VulkanglTFScene::Primitive& primitive : node.mesh.primitives) {
+			if (primitive.indexCount > 0) {
+				VulkanglTFScene::Material& material = materials[primitive.materialIndex];
+				// POI: Bind the pipeline for the node's material
+				vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, material.pipeline);
+				vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 1, 1, &material.descriptorSet, 0, nullptr);
+				vkCmdDrawIndexed(commandBuffer, primitive.indexCount, 1, primitive.firstIndex, 0, 0);
+			}
+		}
+	}
+	for (auto& child : node.children) {
+		drawNode(commandBuffer, pipelineLayout, child);
+	}
+}
+
+// Draw the glTF scene starting at the top-level-nodes
+void VulkanglTFScene::draw(VkCommandBuffer commandBuffer, VkPipelineLayout pipelineLayout)
+{
+	// All vertices and indices are stored in single buffers, so we only need to bind once
+	VkDeviceSize offsets[1] = { 0 };
+	vkCmdBindVertexBuffers(commandBuffer, 0, 1, &vertices.buffer, offsets);
+	vkCmdBindIndexBuffer(commandBuffer, indices.buffer, 0, VK_INDEX_TYPE_UINT32);
+	// Render all nodes at top-level
+	for (auto& node : nodes) {
+		drawNode(commandBuffer, pipelineLayout, node);
+	}
+}
+
+/*
+	Vulkan Example class
+*/
+
+VulkanExample::VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+{
+	title = "glTF scene rendering";
+	camera.type = Camera::CameraType::firstperson;
+	camera.flipY = true;
+	camera.setPosition(glm::vec3(0.0f, 1.0f, 0.0f));
+	camera.setRotation(glm::vec3(0.0f, -90.0f, 0.0f));
+	camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 256.0f);
+}
+
+VulkanExample::~VulkanExample()
+{
+	vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+	vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.matrices, nullptr);
+	vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.textures, nullptr);
+	shaderData.buffer.destroy();
+}
+
+void VulkanExample::getEnabledFeatures()
+{
+	enabledFeatures.samplerAnisotropy = deviceFeatures.samplerAnisotropy;
+}
+
+void VulkanExample::buildCommandBuffers()
+{
+	VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+	VkClearValue clearValues[2];
+	clearValues[0].color = defaultClearColor;
+	clearValues[0].color = { { 0.25f, 0.25f, 0.25f, 1.0f } };;
+	clearValues[1].depthStencil = { 1.0f, 0 };
+
+	VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+	renderPassBeginInfo.renderPass = renderPass;
+	renderPassBeginInfo.renderArea.offset.x = 0;
+	renderPassBeginInfo.renderArea.offset.y = 0;
+	renderPassBeginInfo.renderArea.extent.width = width;
+	renderPassBeginInfo.renderArea.extent.height = height;
+	renderPassBeginInfo.clearValueCount = 2;
+	renderPassBeginInfo.pClearValues = clearValues;
+
+	const VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+	const VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
+
+	for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+	{
+		renderPassBeginInfo.framebuffer = frameBuffers[i];
+		VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+		vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+		vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+		vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+		// Bind scene matrices descriptor to set 0
+		vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr);
+
+		// POI: Draw the glTF scene
+		glTFScene.draw(drawCmdBuffers[i], pipelineLayout);
+
+		drawUI(drawCmdBuffers[i]);
+		vkCmdEndRenderPass(drawCmdBuffers[i]);
+		VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+	}
+}
+
+void VulkanExample::loadglTFFile(std::string filename)
+{
+	tinygltf::Model glTFInput;
+	tinygltf::TinyGLTF gltfContext;
+	std::string error, warning;
+
+	this->device = device;
+
+#if defined(__ANDROID__)
+	// On Android all assets are packed with the apk in a compressed form, so we need to open them using the asset manager
+	// We let tinygltf handle this, by passing the asset manager of our app
+	tinygltf::asset_manager = androidApp->activity->assetManager;
+#endif
+	bool fileLoaded = gltfContext.LoadASCIIFromFile(&glTFInput, &error, &warning, filename);
+
+	// Pass some Vulkan resources required for setup and rendering to the glTF model loading class
+	glTFScene.vulkanDevice = vulkanDevice;
+	glTFScene.copyQueue    = queue;
+
+	size_t pos = filename.find_last_of('/');
+	glTFScene.path = filename.substr(0, pos);
+
+	std::vector<uint32_t> indexBuffer;
+	std::vector<VulkanglTFScene::Vertex> vertexBuffer;
+
+	if (fileLoaded) {
+		glTFScene.loadImages(glTFInput);
+		glTFScene.loadMaterials(glTFInput);
+		glTFScene.loadTextures(glTFInput);
+		const tinygltf::Scene& scene = glTFInput.scenes[0];
+		for (size_t i = 0; i < scene.nodes.size(); i++) {
+			const tinygltf::Node node = glTFInput.nodes[scene.nodes[i]];
+			glTFScene.loadNode(node, glTFInput, nullptr, indexBuffer, vertexBuffer);
+		}
+	}
+	else {
+		vks::tools::exitFatal("Could not open the glTF file.\n\nThe file is part of the additional asset pack.\n\nRun \"download_assets.py\" in the repository root to download the latest version.", -1);
+		return;
+	}
+
+	// Create and upload vertex and index buffer
+	// We will be using one single vertex buffer and one single index buffer for the whole glTF scene
+	// Primitives (of the glTF model) will then index into these using index offsets
+
+	size_t vertexBufferSize = vertexBuffer.size() * sizeof(VulkanglTFScene::Vertex);
+	size_t indexBufferSize = indexBuffer.size() * sizeof(uint32_t);
+	glTFScene.indices.count = static_cast<uint32_t>(indexBuffer.size());
+
+	struct StagingBuffer {
+		VkBuffer buffer;
+		VkDeviceMemory memory;
+	} vertexStaging, indexStaging;
+
+	// Create host visible staging buffers (source)
+	VK_CHECK_RESULT(vulkanDevice->createBuffer(
+		VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
+		VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+		vertexBufferSize,
+		&vertexStaging.buffer,
+		&vertexStaging.memory,
+		vertexBuffer.data()));
+	// Index data
+	VK_CHECK_RESULT(vulkanDevice->createBuffer(
+		VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
+		VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+		indexBufferSize,
+		&indexStaging.buffer,
+		&indexStaging.memory,
+		indexBuffer.data()));
+
+	// Create device local buffers (target)
+	VK_CHECK_RESULT(vulkanDevice->createBuffer(
+		VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
+		VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
+		vertexBufferSize,
+		&glTFScene.vertices.buffer,
+		&glTFScene.vertices.memory));
+	VK_CHECK_RESULT(vulkanDevice->createBuffer(
+		VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
+		VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
+		indexBufferSize,
+		&glTFScene.indices.buffer,
+		&glTFScene.indices.memory));
+
+	// Copy data from staging buffers (host) do device local buffer (gpu)
+	VkCommandBuffer copyCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+	VkBufferCopy copyRegion = {};
+
+	copyRegion.size = vertexBufferSize;
+	vkCmdCopyBuffer(
+		copyCmd,
+		vertexStaging.buffer,
+		glTFScene.vertices.buffer,
+		1,
+		&copyRegion);
+
+	copyRegion.size = indexBufferSize;
+	vkCmdCopyBuffer(
+		copyCmd,
+		indexStaging.buffer,
+		glTFScene.indices.buffer,
+		1,
+		&copyRegion);
+
+	vulkanDevice->flushCommandBuffer(copyCmd, queue, true);
+
+	// Free staging resources
+	vkDestroyBuffer(device, vertexStaging.buffer, nullptr);
+	vkFreeMemory(device, vertexStaging.memory, nullptr);
+	vkDestroyBuffer(device, indexStaging.buffer, nullptr);
+	vkFreeMemory(device, indexStaging.memory, nullptr);
+}
+
+void VulkanExample::loadAssets()
+{
+	loadglTFFile(getAssetPath() + "models/sponza/sponza.gltf");
+}
+
+void VulkanExample::setupDescriptors()
+{
+	/*
+		This sample uses separate descriptor sets (and layouts) for the matrices and materials (textures)
+	*/
+
+	// One ubo to pass dynamic data to the shader
+	// Two combined image samplers per material as each material uses color and normal maps
+	std::vector<VkDescriptorPoolSize> poolSizes = {
+		vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1),
+		vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, static_cast<uint32_t>(glTFScene.materials.size()) * 2),
+	};
+	// One set for matrices and one per model image/texture
+	const uint32_t maxSetCount = static_cast<uint32_t>(glTFScene.images.size()) + 1;
+	VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, maxSetCount);
+	VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+
+	// Descriptor set layout for passing matrices
+	std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+		vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0)
+	};
+	VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCI = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings.data(), static_cast<uint32_t>(setLayoutBindings.size()));
+
+	VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorSetLayoutCI, nullptr, &descriptorSetLayouts.matrices));
+
+	// Descriptor set layout for passing material textures
+	setLayoutBindings = {
+		// Color map
+		vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0),
+		// Normal map
+		vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1),
+	};
+	descriptorSetLayoutCI.pBindings = setLayoutBindings.data();
+	descriptorSetLayoutCI.bindingCount = 2;
+	VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorSetLayoutCI, nullptr, &descriptorSetLayouts.textures));
+
+	// Pipeline layout using both descriptor sets (set 0 = matrices, set 1 = material)
+	std::array<VkDescriptorSetLayout, 2> setLayouts = { descriptorSetLayouts.matrices, descriptorSetLayouts.textures };
+	VkPipelineLayoutCreateInfo pipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(setLayouts.data(), static_cast<uint32_t>(setLayouts.size()));
+	// We will use push constants to push the local matrices of a primitive to the vertex shader
+	VkPushConstantRange pushConstantRange = vks::initializers::pushConstantRange(VK_SHADER_STAGE_VERTEX_BIT, sizeof(glm::mat4), 0);
+	// Push constant ranges are part of the pipeline layout
+	pipelineLayoutCI.pushConstantRangeCount = 1;
+	pipelineLayoutCI.pPushConstantRanges = &pushConstantRange;
+	VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelineLayout));
+
+	// Descriptor set for scene matrices
+	VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.matrices, 1);
+	VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet));
+	VkWriteDescriptorSet writeDescriptorSet = vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &shaderData.buffer.descriptor);
+	vkUpdateDescriptorSets(device, 1, &writeDescriptorSet, 0, nullptr);
+
+	// Descriptor sets for materials
+	for (auto& material : glTFScene.materials) {
+		const VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.textures, 1);
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &material.descriptorSet));
+		VkDescriptorImageInfo colorMap = glTFScene.getTextureDescriptor(material.baseColorTextureIndex);
+		VkDescriptorImageInfo normalMap = glTFScene.getTextureDescriptor(material.normalTextureIndex);
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets = {
+			vks::initializers::writeDescriptorSet(material.descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &colorMap),
+			vks::initializers::writeDescriptorSet(material.descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &normalMap),
+		};
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr);
+	}
+}
+
+void VulkanExample::preparePipelines()
+{
+	VkPipelineInputAssemblyStateCreateInfo inputAssemblyStateCI = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+	VkPipelineRasterizationStateCreateInfo rasterizationStateCI = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
+	VkPipelineColorBlendAttachmentState blendAttachmentStateCI = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+	VkPipelineColorBlendStateCreateInfo colorBlendStateCI = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentStateCI);
+	VkPipelineDepthStencilStateCreateInfo depthStencilStateCI = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+	VkPipelineViewportStateCreateInfo viewportStateCI = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+	VkPipelineMultisampleStateCreateInfo multisampleStateCI = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
+	const std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
+	VkPipelineDynamicStateCreateInfo dynamicStateCI = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables.data(), static_cast<uint32_t>(dynamicStateEnables.size()), 0);
+	std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+
+	const std::vector<VkVertexInputBindingDescription> vertexInputBindings = {
+		vks::initializers::vertexInputBindingDescription(0, sizeof(VulkanglTFScene::Vertex), VK_VERTEX_INPUT_RATE_VERTEX),
+	};
+	const std::vector<VkVertexInputAttributeDescription> vertexInputAttributes = {
+		vks::initializers::vertexInputAttributeDescription(0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(VulkanglTFScene::Vertex, pos)),
+		vks::initializers::vertexInputAttributeDescription(0, 1, VK_FORMAT_R32G32B32_SFLOAT, offsetof(VulkanglTFScene::Vertex, normal)),
+		vks::initializers::vertexInputAttributeDescription(0, 2, VK_FORMAT_R32G32B32_SFLOAT, offsetof(VulkanglTFScene::Vertex, uv)),
+		vks::initializers::vertexInputAttributeDescription(0, 3, VK_FORMAT_R32G32B32_SFLOAT, offsetof(VulkanglTFScene::Vertex, color)),
+		vks::initializers::vertexInputAttributeDescription(0, 4, VK_FORMAT_R32G32B32_SFLOAT, offsetof(VulkanglTFScene::Vertex, tangent)),
+	};
+	VkPipelineVertexInputStateCreateInfo vertexInputStateCI = vks::initializers::pipelineVertexInputStateCreateInfo(vertexInputBindings, vertexInputAttributes);
+
+	VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass, 0);
+	pipelineCI.pVertexInputState = &vertexInputStateCI;
+	pipelineCI.pInputAssemblyState = &inputAssemblyStateCI;
+	pipelineCI.pRasterizationState = &rasterizationStateCI;
+	pipelineCI.pColorBlendState = &colorBlendStateCI;
+	pipelineCI.pMultisampleState = &multisampleStateCI;
+	pipelineCI.pViewportState = &viewportStateCI;
+	pipelineCI.pDepthStencilState = &depthStencilStateCI;
+	pipelineCI.pDynamicState = &dynamicStateCI;
+	pipelineCI.stageCount = static_cast<uint32_t>(shaderStages.size());
+	pipelineCI.pStages = shaderStages.data();
+
+	shaderStages[0] = loadShader(getShadersPath() + "gltfscenerendering/scene.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+	shaderStages[1] = loadShader(getShadersPath() + "gltfscenerendering/scene.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+
+	// POI: Instead if using a few fixed pipelines, we create one pipeline for each material using the properties of that material
+	for (auto &material : glTFScene.materials) {
+
+		struct MaterialSpecializationData {
+			VkBool32 alphaMask;
+			float alphaMaskCutoff;
+		} materialSpecializationData;
+
+		materialSpecializationData.alphaMask = material.alphaMode == "MASK";
+		materialSpecializationData.alphaMaskCutoff = material.alphaCutOff;
+
+		// POI: Constant fragment shader material parameters will be set using specialization constants
+		std::vector<VkSpecializationMapEntry> specializationMapEntries = {
+			vks::initializers::specializationMapEntry(0, offsetof(MaterialSpecializationData, alphaMask), sizeof(MaterialSpecializationData::alphaMask)),
+			vks::initializers::specializationMapEntry(1, offsetof(MaterialSpecializationData, alphaMaskCutoff), sizeof(MaterialSpecializationData::alphaMaskCutoff)),
+		};
+		VkSpecializationInfo specializationInfo = vks::initializers::specializationInfo(specializationMapEntries, sizeof(materialSpecializationData), &materialSpecializationData);
+		shaderStages[1].pSpecializationInfo = &specializationInfo;
+
+		// For double sided materials, culling will be disabled
+		rasterizationStateCI.cullMode = material.doubleSided ? VK_CULL_MODE_NONE : VK_CULL_MODE_BACK_BIT;
+
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &material.pipeline));
+	}
+}
+
+void VulkanExample::prepareUniformBuffers()
+{
+	VK_CHECK_RESULT(vulkanDevice->createBuffer(
+		VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+		VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+		&shaderData.buffer,
+		sizeof(shaderData.values)));
+	VK_CHECK_RESULT(shaderData.buffer.map());
+	updateUniformBuffers();
+}
+
+void VulkanExample::updateUniformBuffers()
+{
+	shaderData.values.projection = camera.matrices.perspective;
+	shaderData.values.view = camera.matrices.view;
+	shaderData.values.viewPos = camera.viewPos;
+	memcpy(shaderData.buffer.mapped, &shaderData.values, sizeof(shaderData.values));
+}
+
+void VulkanExample::prepare()
+{
+	VulkanExampleBase::prepare();
+	loadAssets();
+	prepareUniformBuffers();
+	setupDescriptors();
+	preparePipelines();
+	buildCommandBuffers();
+	prepared = true;
+}
+
+void VulkanExample::render()
+{
+	renderFrame();
+	if (camera.updated) {
+		updateUniformBuffers();
+	}
+}
+
+void VulkanExample::OnUpdateUIOverlay(vks::UIOverlay* overlay)
+{
+	if (overlay->header("Visibility")) {
+
+		if (overlay->button("All")) {
+			std::for_each(glTFScene.nodes.begin(), glTFScene.nodes.end(), [](VulkanglTFScene::Node &node) { node.visible = true; });
+			buildCommandBuffers();
+		}
+		ImGui::SameLine();
+		if (overlay->button("None")) {
+			std::for_each(glTFScene.nodes.begin(), glTFScene.nodes.end(), [](VulkanglTFScene::Node &node) { node.visible = false; });
+			buildCommandBuffers();
+		}
+		ImGui::NewLine();
+
+		// POI: Create a list of glTF nodes for visibility toggle
+		ImGui::BeginChild("#nodelist", ImVec2(200.0f, 340.0f), false);
+		for (auto &node : glTFScene.nodes)
+		{		
+			if (overlay->checkBox(node.name.c_str(), &node.visible))
+			{
+				buildCommandBuffers();
+			}
+		}
+		ImGui::EndChild();
+	}
+}
+
+VULKAN_EXAMPLE_MAIN()
\ No newline at end of file
diff --git a/external/Vulkan/examples/gltfscenerendering/gltfscenerendering.h b/external/Vulkan/examples/gltfscenerendering/gltfscenerendering.h
new file mode 100644
index 0000000000000000000000000000000000000000..8e6b6355d42eb0028a324cb29e3c1116316fa2c0
--- /dev/null
+++ b/external/Vulkan/examples/gltfscenerendering/gltfscenerendering.h
@@ -0,0 +1,166 @@
+/*
+* Vulkan Example - Scene rendering
+*
+* Copyright (C) 2020 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*
+* Summary:
+* Render a complete scene loaded from an glTF file. The sample is based on the glTF model loading sample,
+* and adds data structures, functions and shaders required to render a more complex scene using Crytek's Sponza model.
+*
+* This sample comes with a tutorial, see the README.md in this folder
+*/
+
+#define TINYGLTF_IMPLEMENTATION
+#define STB_IMAGE_IMPLEMENTATION
+#define TINYGLTF_NO_STB_IMAGE_WRITE
+#define TINYGLTF_NO_STB_IMAGE
+#define TINYGLTF_NO_EXTERNAL_IMAGE
+#ifdef VK_USE_PLATFORM_ANDROID_KHR
+#define TINYGLTF_ANDROID_LOAD_FROM_ASSETS
+#endif
+#include "tiny_gltf.h"
+
+#include "vulkanexamplebase.h"
+
+#define ENABLE_VALIDATION false
+
+ // Contains everything required to render a basic glTF scene in Vulkan
+ // This class is heavily simplified (compared to glTF's feature set) but retains the basic glTF structure
+class VulkanglTFScene
+{
+public:
+	// The class requires some Vulkan objects so it can create it's own resources
+	vks::VulkanDevice* vulkanDevice;
+	VkQueue copyQueue;
+
+	// The vertex layout for the samples' model
+	struct Vertex {
+		glm::vec3 pos;
+		glm::vec3 normal;
+		glm::vec2 uv;
+		glm::vec3 color;
+		glm::vec4 tangent;
+	};
+
+	// Single vertex buffer for all primitives
+	struct {
+		VkBuffer buffer;
+		VkDeviceMemory memory;
+	} vertices;
+
+	// Single index buffer for all primitives
+	struct {
+		int count;
+		VkBuffer buffer;
+		VkDeviceMemory memory;
+	} indices;
+
+	// The following structures roughly represent the glTF scene structure
+	// To keep things simple, they only contain those properties that are required for this sample
+	struct Node;
+
+	// A primitive contains the data for a single draw call
+	struct Primitive {
+		uint32_t firstIndex;
+		uint32_t indexCount;
+		int32_t materialIndex;
+	};
+
+	// Contains the node's (optional) geometry and can be made up of an arbitrary number of primitives
+	struct Mesh {
+		std::vector<Primitive> primitives;
+	};
+
+	// A node represents an object in the glTF scene graph
+	struct Node {
+		Node* parent;
+		std::vector<Node> children;
+		Mesh mesh;
+		glm::mat4 matrix;
+		std::string name;
+		bool visible = true;
+	};
+
+	// A glTF material stores information in e.g. the texture that is attached to it and colors
+	struct Material {
+		glm::vec4 baseColorFactor = glm::vec4(1.0f);
+		uint32_t baseColorTextureIndex;
+		uint32_t normalTextureIndex;
+		std::string alphaMode = "OPAQUE";
+		float alphaCutOff;
+		bool doubleSided = false;
+		VkDescriptorSet descriptorSet;
+		VkPipeline pipeline;
+	};
+
+	// Contains the texture for a single glTF image
+	// Images may be reused by texture objects and are as such separated
+	struct Image {
+		vks::Texture2D texture;
+	};
+
+	// A glTF texture stores a reference to the image and a sampler
+	// In this sample, we are only interested in the image
+	struct Texture {
+		int32_t imageIndex;
+	};
+
+	/*
+		Model data
+	*/
+	std::vector<Image> images;
+	std::vector<Texture> textures;
+	std::vector<Material> materials;
+	std::vector<Node> nodes;
+
+	std::string path;
+
+	~VulkanglTFScene();
+	VkDescriptorImageInfo getTextureDescriptor(const size_t index);
+	void loadImages(tinygltf::Model& input);
+	void loadTextures(tinygltf::Model& input);
+	void loadMaterials(tinygltf::Model& input);
+	void loadNode(const tinygltf::Node& inputNode, const tinygltf::Model& input, VulkanglTFScene::Node* parent, std::vector<uint32_t>& indexBuffer, std::vector<VulkanglTFScene::Vertex>& vertexBuffer);
+	void drawNode(VkCommandBuffer commandBuffer, VkPipelineLayout pipelineLayout, VulkanglTFScene::Node node);
+	void draw(VkCommandBuffer commandBuffer, VkPipelineLayout pipelineLayout);
+};
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	VulkanglTFScene glTFScene;
+
+	struct ShaderData {
+		vks::Buffer buffer;
+		struct Values {
+			glm::mat4 projection;
+			glm::mat4 view;
+			glm::vec4 lightPos = glm::vec4(0.0f, 2.5f, 0.0f, 1.0f);
+			glm::vec4 viewPos;
+		} values;
+	} shaderData;
+
+	VkPipelineLayout pipelineLayout;
+	VkDescriptorSet descriptorSet;
+
+	struct DescriptorSetLayouts {
+		VkDescriptorSetLayout matrices;
+		VkDescriptorSetLayout textures;
+	} descriptorSetLayouts;
+
+	VulkanExample();
+	~VulkanExample();
+	virtual void getEnabledFeatures();
+	void buildCommandBuffers();
+	void loadglTFFile(std::string filename);
+	void loadAssets();
+	void setupDescriptors();
+	void preparePipelines();
+	void prepareUniformBuffers();
+	void updateUniformBuffers();
+	void prepare();
+	virtual void render();
+	virtual void OnUpdateUIOverlay(vks::UIOverlay* overlay);
+};
\ No newline at end of file
diff --git a/external/Vulkan/examples/gltfskinning/README.md b/external/Vulkan/examples/gltfskinning/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..88e199b3afc75244f8da1c91bf9742d63ff23ec0
--- /dev/null
+++ b/external/Vulkan/examples/gltfskinning/README.md
@@ -0,0 +1,550 @@
+# glTF vertex skinning
+
+<img src="../../screenshots/gltf_skinning.jpg" height="256px">
+
+## Synopsis
+
+Renders an animated glTF model with vertex skinning. The sample is based on the [glTF scene](../gltfscene) sample, and adds data structures, functions and shaders required to apply vertex skinning to a mesh.
+
+## Description
+
+This example demonstrates how to load and use the data structures required for animating a mesh with vertex skinning.
+
+Vertex skinning is a technique that uses per-vertex weights based on the current pose of a skeleton made up of bones.
+
+Animations then are applied to those bones instead and during rendering the matrices of those bones along with the vertex weights are used to calculate the final vertex positions.
+
+A good glTF skinning tutorial can be found [here](https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_020_Skins.md), so this readme only gives a coarse overview on the actual implementation instead of going into full detail on how vertex skinning works.
+
+Note that this is not a full glTF implementation as this would be beyond the scope of a simple example. For a complete glTF Vulkan implementation see https://github.com/SaschaWillems/Vulkan-glTF-PBR/.
+
+## Points of interest
+
+**Note:** Points of interest are marked with a **POI** in the code comments:
+
+```cpp
+// POI: Get buffer data required for vertex skinning
+```
+
+### Data structures
+
+Several new data structures are required for doing animations with vertex skinning. The [official glTF spec](https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#skinned-mesh-attributes) has the details on those.
+
+#### Node additions
+
+```cpp
+struct Node
+{
+  Node *              parent;
+  uint32_t            index;
+  std::vector<Node *> children;
+  Mesh                mesh;
+  glm::vec3           translation{};
+  glm::vec3           scale{1.0f};
+  glm::quat           rotation{};
+  int32_t             skin = -1;
+  glm::mat4           matrix;
+  glm::mat4           getLocalMatrix();
+};
+```
+
+The node now also stores the matrix components (```translation```, ```rotation``` ,```scale```) as they can be independently influenced.
+
+The ```skin``` member is the index of the skin (see below) that is applied to this node.
+
+A new function called ```getLocalMatrix``` is introduced that calculates the local matrix from the initial one and the current components.
+
+#### Vertex additions
+
+```cpp
+	struct Vertex
+	{
+    ...
+		glm::vec4 jointIndices;
+		glm::vec4 jointWeights;
+	};
+```
+
+To calculate the final matrix to be applied to the vertex we now pass the indices of the joints (see below) and the weights of those, which determines how strongly this vertex is influenced by the joint. glTF support at max. four indices and weights per joint, so we pass them as four-component vectors.
+
+#### Skin
+
+[glTF spec chapter on skins](https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#skins)
+
+```cpp
+struct Skin
+{
+  std::string            name;
+  Node *                 skeletonRoot = nullptr;
+  std::vector<glm::mat4> inverseBindMatrices;
+  std::vector<Node *>    joints;
+  vks::Buffer            ssbo;
+  VkDescriptorSet        descriptorSet;
+};
+```
+
+This struct stores all information required for applying a skin to a mesh. Most important are the ```inverseBindMatrices``` used to transform the geometry into the space of the accompanying joint node. The ```joints``` vector contains the nodes used as joints in this skin.
+
+We will pass the actual joint matrices for the current animation frame using a shader storage buffer object, so each skin also get's it's own ```ssboo``` along with a descriptor set to be bound at render time.
+
+#### Animations
+
+[glTF spec chapter on animations](https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#animations)
+
+##### Animation sampler
+```cpp
+struct AnimationSampler
+{
+  std::string            interpolation;
+  std::vector<float>     inputs;
+  std::vector<glm::vec4> outputsVec4;
+};
+```
+
+The animation sampler contains the key frame data read from a buffer using an accessor and the way the key frame is interpolated. This can be ```LINEAR```, which is just a simple linear interpolation over time, ```STEP```, which remains constant until the next key frame is reached, and ```CUBICSPLINE``` which uses a cubic spline with tangents for calculating the interpolated key frames. This is a bit more complex and separately documented in this [glTF spec chapter](https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#appendix-c-spline-interpolation).
+
+**Note:** For simplicity, this sample only implements ```LINEAR``` interpolation.
+
+##### Animation channel
+
+```cpp
+struct AnimationChannel
+{
+  std::string path;
+  Node *      node;
+  uint32_t    samplerIndex;
+};
+```
+
+The animation channel connects the node with a key frame specified by an animation sampler with the ```path``` member specifying the node property to animate, which is either ```translation```, ```rotation```, ```scale``` or ```weights```. The latter one refers to morph targets and not vertex weights (for skinning) and is not used in this sample.
+
+##### Animation
+```cpp
+struct Animation
+{
+  std::string                   name;
+  std::vector<AnimationSampler> samplers;
+  std::vector<AnimationChannel> channels;
+  float                         start       = std::numeric_limits<float>::max();
+  float                         end         = std::numeric_limits<float>::min();
+  float                         currentTime = 0.0f;
+};
+```
+
+The animation itself then contains the animation samplers and channels along with timing information.
+
+#### Loading and passing the data
+
+##### Vertex attributes
+
+This samples adds two new vertex attributes for passing per-vertex `joints` and `weights` information. As with other per-vertex attributes, these need to be loaded using glTF accessors in `VulkanglTFModel::loadNode`:
+
+```cpp
+// Get vertex joint indices
+if (glTFPrimitive.attributes.find("JOINTS_0") != glTFPrimitive.attributes.end())
+{
+  const tinygltf::Accessor &  accessor = input.accessors[glTFPrimitive.attributes.find("JOINTS_0")->second];
+  const tinygltf::BufferView &view     = input.bufferViews[accessor.bufferView];
+  jointIndicesBuffer                   = reinterpret_cast<const uint16_t *>(&(input.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset]));
+}
+// Get vertex joint weights
+if (glTFPrimitive.attributes.find("WEIGHTS_0") != glTFPrimitive.attributes.end())
+{
+  const tinygltf::Accessor &  accessor = input.accessors[glTFPrimitive.attributes.find("WEIGHTS_0")->second];
+  const tinygltf::BufferView &view     = input.bufferViews[accessor.bufferView];
+  jointWeightsBuffer                   = reinterpret_cast<const float *>(&(input.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset]));
+}
+```
+
+As usual we check if those attributes actually exist inside the glTF file, and if so we use the accessor and buffer view to get a pointer to the required vertex attributes.
+
+The new attributes are added to the vertex input state of our pipeline in `VulkanExample::preparePipelines` at locations 4 and 5:
+
+```cpp
+const std::vector<VkVertexInputAttributeDescription> vertexInputAttributes = {
+    ...
+    {4, 0, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(VulkanglTFModel::Vertex, jointIndices)},
+    {5, 0, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(VulkanglTFModel::Vertex, jointWeights)},
+};
+```
+
+Vertex shader interface in `skinnedmodel.vert`:
+
+```glsl
+layout (location = 4) in vec4 inJointIndices;
+layout (location = 5) in vec4 inJointWeights;
+```
+
+##### Skins
+
+Loading skins is done in `VulkanglTFModel::loadSkin` and aside from getting the required data from the glTF sources into our own structures, this method also creates a buffer for uploading the inverse bind matrices:
+
+```cpp
+void VulkanglTFModel::loadSkins(tinygltf::Model &input)
+{
+	skins.resize(input.skins.size());
+
+	for (size_t i = 0; i < input.skins.size(); i++)
+	{
+		tinygltf::Skin glTFSkin = input.skins[i];
+		...
+		if (glTFSkin.inverseBindMatrices > -1)
+		{
+			const tinygltf::Accessor &  accessor   = input.accessors[glTFSkin.inverseBindMatrices];
+			const tinygltf::BufferView &bufferView = input.bufferViews[accessor.bufferView];
+			const tinygltf::Buffer &    buffer     = input.buffers[bufferView.buffer];
+			skins[i].inverseBindMatrices.resize(accessor.count);
+			memcpy(skins[i].inverseBindMatrices.data(), &buffer.data[accessor.byteOffset + bufferView.byteOffset], accessor.count * sizeof(glm::mat4));
+
+			vulkanDevice->createBuffer(
+			    VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
+			    VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			    &skins[i].ssbo,
+			    sizeof(glm::mat4) * skins[i].inverseBindMatrices.size(),
+			    skins[i].inverseBindMatrices.data());
+			...
+		}
+	}
+```
+
+Vertex shader interface in `skinnedmodel.vert`:
+
+```glsl
+layout(std430, set = 1, binding = 0) readonly buffer JointMatrices {
+	mat4 jointMatrices[];
+};
+```
+
+As with e.g. vertex attributes we retrieve the inverse bind matrices from the glTF accessor and buffer view. These are used at a later point for generating the actual animation matrices.
+
+We then create a shader storage buffer object (for each skin) big enough to hold all animation matrices. See [Updating the animations](#UpdatingAnimation) for how these are calculated and updated.
+
+**Note**: For simplicity we create a host visible SSBO. This makes code easier to read. In a real-world application you'd use a device local SSBO instead.
+
+##### Animations
+
+Loading the animation data is done in ```VulkanglTFModel::loadAnimations``` and is also mostly about getting the required data from glTF into our own sources, including sampler and channel data.
+
+The most interesting part is getting the input and output pairs for the animation's samplers, which are loaded using glTF's accessors.
+
+We first load the **sampler's input values**, containing keyframe time values. These are also used to determine the start and end time of the animation:
+
+```cpp
+void VulkanglTFModel::loadAnimations(tinygltf::Model &input)
+	...
+	for (size_t i = 0; i < input.animations.size(); i++)
+	{
+		...
+		for (size_t j = 0; j < glTFAnimation.samplers.size(); j++)
+		{
+			...
+			{
+				const tinygltf::Accessor &  accessor   = input.accessors[glTFSampler.input];
+				const tinygltf::BufferView &bufferView = input.bufferViews[accessor.bufferView];
+				const tinygltf::Buffer &    buffer     = input.buffers[bufferView.buffer];
+				const void *                dataPtr    = &buffer.data[accessor.byteOffset + bufferView.byteOffset];
+				const float *               buf        = static_cast<const float *>(dataPtr);
+				for (size_t index = 0; index < accessor.count; index++)
+				{
+					dstSampler.inputs.push_back(buf[index]);
+				}
+				// Adjust animation's start and end times 
+				for (auto input : animations[i].samplers[j].inputs)
+				{
+					if (input < animations[i].start)
+					{
+						animations[i].start = input;
+					};
+					if (input > animations[i].end)
+					{
+						animations[i].end = input;
+					}
+				}
+			}
+```
+
+Next up are the **sampler's output values** containing the keyframe output values for the input time values read above:
+
+```cpp
+void VulkanglTFModel::loadAnimations(tinygltf::Model &input)
+	...
+	for (size_t i = 0; i < input.animations.size(); i++)
+	{
+		...
+		for (size_t j = 0; j < glTFAnimation.samplers.size(); j++)
+		{
+			...
+			// Read sampler keyframe output translate/rotate/scale values
+			{
+				const tinygltf::Accessor &  accessor   = input.accessors[glTFSampler.output];
+				const tinygltf::BufferView &bufferView = input.bufferViews[accessor.bufferView];
+				const tinygltf::Buffer &    buffer     = input.buffers[bufferView.buffer];
+				const void *                dataPtr    = &buffer.data[accessor.byteOffset + bufferView.byteOffset];
+				switch (accessor.type)
+				{
+					case TINYGLTF_TYPE_VEC3: {
+						const glm::vec3 *buf = static_cast<const glm::vec3 *>(dataPtr);
+						for (size_t index = 0; index < accessor.count; index++)
+						{
+							dstSampler.outputsVec4.push_back(glm::vec4(buf[index], 0.0f));
+						}
+						break;
+					}
+					case TINYGLTF_TYPE_VEC4: {
+						const glm::vec4 *buf = static_cast<const glm::vec4 *>(dataPtr);
+						for (size_t index = 0; index < accessor.count; index++)
+						{
+							dstSampler.outputsVec4.push_back(buf[index]);
+						}
+						break;
+					}
+					default: {
+						std::cout << "unknown type" << std::endl;
+						break;
+					}
+				}
+			}
+```
+
+As the comment notes these outputs apply to a certain animation target path, which is either a translation, rotation or scale.
+
+The path and node that the output keyframe values are applied to are part of the animation's channel:
+
+```cpp
+	...
+	for (size_t i = 0; i < input.animations.size(); i++)
+	{
+		...
+		for (size_t j = 0; j < glTFAnimation.channels.size(); j++)
+		{
+			tinygltf::AnimationChannel glTFChannel = glTFAnimation.channels[j];
+			AnimationChannel &         dstChannel  = animations[i].channels[j];
+			dstChannel.path                        = glTFChannel.target_path;
+			dstChannel.samplerIndex                = glTFChannel.sampler;
+			dstChannel.node                        = nodeFromIndex(glTFChannel.target_node);
+		}
+```
+
+So if the animation's channel's path is set to ```translation```, the keyframe output values contain translation values that are applied to the channel's node depending on the current animation time.
+
+
+#### <a name="UpdatingAnimation"></a>Updating the animation
+
+With all required structures loaded, the next step is updating the actual animation data. This is done inside the ```VulkanglTFModel::updateAnimation``` function, where the data from the animation's samplers and channels is applied to the animation targets of the destination node.
+
+We first update the active animation's current timestamp and also check if we need to restart it:
+
+```cpp
+Animation &animation = animations[activeAnimation];
+animation.currentTime += deltaTime;
+if (animation.currentTime > animation.end)
+{
+  animation.currentTime -= animation.end;
+}
+```
+
+Next we go through all the channels that are applied to this animation (translation, rotation, scale) and try to find the input keyframe values for the current timestamp:
+
+```cpp
+for (auto &channel : animation.channels)
+{
+  AnimationSampler &sampler = animation.samplers[channel.samplerIndex];
+  for (size_t i = 0; i < sampler.inputs.size() - 1; i++)
+  {
+    // Get the input keyframe values for the current time stamp
+    if ((animation.currentTime >= sampler.inputs[i]) && (animation.currentTime <= sampler.inputs[i + 1]))
+    {
+      // Calculate interpolation value based on timestamp, Update node, see next paragraph
+    }
+  }
+}
+```
+
+Inside the above loop we then calculate the interpolation value based on the animation's current time and the sampler's input keyframes for the current and next frame:
+
+```cpp
+float a = (animation.currentTime - sampler.inputs[i]) / (sampler.inputs[i + 1] - sampler.inputs[i]);
+```
+
+This interpolation value is then used to apply the keyframe output values to the appropriate channel:
+
+```cpp
+if (channel.path == "translation")
+{
+  channel.node->translation = glm::mix(sampler.outputsVec4[i], sampler.outputsVec4[i + 1], a);
+}
+if (channel.path == "rotation")
+{
+  glm::quat q1;
+  q1.x = sampler.outputsVec4[i].x;
+  q1.y = sampler.outputsVec4[i].y;
+  q1.z = sampler.outputsVec4[i].z;
+  q1.w = sampler.outputsVec4[i].w;
+
+  glm::quat q2;
+  q2.x = sampler.outputsVec4[i + 1].x;
+  q2.y = sampler.outputsVec4[i + 1].y;
+  q2.z = sampler.outputsVec4[i + 1].z;
+  q2.w = sampler.outputsVec4[i + 1].w;
+
+  channel.node->rotation = glm::normalize(glm::slerp(q1, q2, a));
+}
+if (channel.path == "scale")
+{
+  channel.node->scale = glm::mix(sampler.outputsVec4[i], sampler.outputsVec4[i + 1], a);
+}
+```
+
+**Note:** As mentioned earlier, this sample only supports linear interpolation types.
+
+Translation and scale is a simple linear interpolation between the output keyframe values of the current and next frame.
+
+Rotations use quaternions and as such are interpolated using spherical linear interpolation.
+
+After the node's animation components have been updated, we update all node joints:
+
+```cpp
+for (auto &node : nodes)
+{
+  updateJoints(node);
+}
+```
+
+The ```updateJoints``` function will calculate the actual joint matrices and update the shader storage buffer object:
+
+```cpp
+void VulkanglTFModel::updateJoints(VulkanglTFModel::Node *node)
+{
+	if (node->skin > -1)
+	{
+		// Update the joint matrices
+		glm::mat4              inverseTransform = glm::inverse(getNodeMatrix(node));
+		Skin                   skin             = skins[node->skin];
+		size_t                 numJoints        = (uint32_t) skin.joints.size();
+		std::vector<glm::mat4> jointMatrices(numJoints);
+		for (size_t i = 0; i < numJoints; i++)
+		{
+			jointMatrices[i] = getNodeMatrix(skin.joints[i]) * skin.inverseBindMatrices[i];
+			jointMatrices[i] = inverseTransform * jointMatrices[i];
+		}
+		// Update ssbo
+		skin.ssbo.copyTo(jointMatrices.data(), jointMatrices.size() * sizeof(glm::mat4));
+	}
+
+	for (auto &child : node->children)
+	{
+		updateJoints(child);
+	}
+}
+```
+
+Note the use of the ```getNodeMatrix``` function which will return the current matrix of a given node calculated from the node hierarchy and the node's current translate/rotate/scale values updated earlier. This is the actual matrix that's updated by the current animation state.
+
+After this we copy the new joint matrices to the shader storage buffer object of the current skin to make it available to the shader.
+
+#### Rendering the model
+
+With all the matrices calculated and made available to the shaders, we can now finally render our animated model using vertex skinning.
+
+Rendering the glTF model is done in ```VulkanglTFModel::draw``` which is called at command buffer creation. Since glTF has a hierarchical node structure this function recursively calls ```VulkanglTFModel::drawNode``` for rendering a give node with it's children:
+
+```cpp
+void VulkanglTFModel::drawNode(VkCommandBuffer commandBuffer, VkPipelineLayout pipelineLayout, VulkanglTFModel::Node node)
+{
+	if (node.mesh.primitives.size() > 0)
+	{
+		// Pass the node's matrix via push constants
+		// Traverse the node hierarchy to the top-most parent to get the final matrix of the current node
+		glm::mat4              nodeMatrix    = node.matrix;
+		VulkanglTFModel::Node *currentParent = node.parent;
+		while (currentParent)
+		{
+			nodeMatrix    = currentParent->matrix * nodeMatrix;
+			currentParent = currentParent->parent;
+		}
+		// Pass the final matrix to the vertex shader using push constants
+		vkCmdPushConstants(commandBuffer, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(glm::mat4), &nodeMatrix);
+		// Bind SSBO with skin data for this node to set 1
+		vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 1, 1, &skins[node.skin].descriptorSet, 0, nullptr);
+		for (VulkanglTFModel::Primitive &primitive : node.mesh.primitives)
+		{
+			if (primitive.indexCount > 0)
+			{
+				...
+				vkCmdDrawIndexed(commandBuffer, primitive.indexCount, 1, primitive.firstIndex, 0, 0);
+			}
+		}
+	}
+	for (auto &child : node.children)
+	{
+		drawNode(commandBuffer, pipelineLayout, *child);
+	}
+}
+```
+
+There are two points-of-interest in this code related to vertex skinning.
+
+First is passing the (fixed) model matrix via a push constant, which is not directly related to the animation itself but required later on on the shader:
+
+```cpp
+glm::mat4              nodeMatrix    = node.matrix;
+VulkanglTFModel::Node *currentParent = node.parent;
+while (currentParent)
+{
+  nodeMatrix    = currentParent->matrix * nodeMatrix;
+  currentParent = currentParent->parent;
+}
+// Pass the final matrix to the vertex shader using push constants
+vkCmdPushConstants(commandBuffer, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(glm::mat4), &nodeMatrix);
+```
+
+As this matrix won't change in our case, we pass this is a push constant to the vertex shader.
+
+And we also bind the shader storage buffer object of the skin so the vertex shader get's access to the current joint matrices for the skin to be applied to that particular node:
+
+```cpp
+vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 1, 1, &skins[node.skin].descriptorSet, 0, nullptr);
+```
+
+With the application side setup done, we can now take a look at the vertex shader that does the actual vertex skinning (```skinnedmodel.vert```).
+
+As mentioned earlier, the interface has been expanded with values and attributes related to vertex skinning:
+
+```glsl
+#version 450
+
+...
+layout (location = 4) in vec4 inJointIndices;
+layout (location = 5) in vec4 inJointWeights;
+
+...
+
+layout(push_constant) uniform PushConsts {
+	mat4 model;
+} primitive;
+
+layout(std430, set = 1, binding = 0) readonly buffer JointMatrices {
+	mat4 jointMatrices[];
+};
+```
+
+Those are then used to calculate the skin matrix that's applied to the vertex position:
+
+```glsl
+void main() 
+{
+	...
+
+	mat4 skinMat = 
+		inJointWeights.x * jointMatrices[int(inJointIndices.x)] +
+		inJointWeights.y * jointMatrices[int(inJointIndices.y)] +
+		inJointWeights.z * jointMatrices[int(inJointIndices.z)] +
+		inJointWeights.w * jointMatrices[int(inJointIndices.w)];
+
+	gl_Position = uboScene.projection * uboScene.view * primitive.model * skinMat * vec4(inPos.xyz, 1.0);
+
+	...
+}
+```
+
+The skin matrix is a linear combination of the joint matrices. The indices of the joint matrices to be applied are taken from the ```inJointIndices``` vertex attribute, with each component (xyzw) storing one index, and those matrices are then weighted by the ```inJointWeights``` vertex attribute to calculate the final skin matrix that is applied to this vertex.
\ No newline at end of file
diff --git a/external/Vulkan/examples/gltfskinning/gltfskinning.cpp b/external/Vulkan/examples/gltfskinning/gltfskinning.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d7988b83f04e78485f101d2106721d39ee748ef6
--- /dev/null
+++ b/external/Vulkan/examples/gltfskinning/gltfskinning.cpp
@@ -0,0 +1,1009 @@
+/*
+* Vulkan Example - glTF skinned animation
+*
+* Copyright (C) 2020-2021 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+/*
+ * Shows how to load and display an animated scene from a glTF file using vertex skinning
+ * See the accompanying README.md for a short tutorial on the data structures and functions required for vertex skinning
+ *
+ * For details on how glTF 2.0 works, see the official spec at https://github.com/KhronosGroup/glTF/tree/master/specification/2.0
+ *
+ * If you are looking for a complete glTF implementation, check out https://github.com/SaschaWillems/Vulkan-glTF-PBR/
+ */
+
+#include "gltfskinning.h"
+
+/*
+
+	 glTF model class
+
+	 Contains everything required to render a skinned glTF model in Vulkan
+	 This class is simplified compared to glTF's feature set but retains the basic glTF structure required for this sample
+
+ */
+
+/*
+	 Get a node's local matrix from the current translation, rotation and scale values
+	 These are calculated from the current animation an need to be calculated dynamically
+ */
+glm::mat4 VulkanglTFModel::Node::getLocalMatrix()
+{
+	return glm::translate(glm::mat4(1.0f), translation) * glm::mat4(rotation) * glm::scale(glm::mat4(1.0f), scale) * matrix;
+}
+
+/*
+	Release all Vulkan resources acquired for the model
+*/
+VulkanglTFModel::~VulkanglTFModel()
+{
+	vkDestroyBuffer(vulkanDevice->logicalDevice, vertices.buffer, nullptr);
+	vkFreeMemory(vulkanDevice->logicalDevice, vertices.memory, nullptr);
+	vkDestroyBuffer(vulkanDevice->logicalDevice, indices.buffer, nullptr);
+	vkFreeMemory(vulkanDevice->logicalDevice, indices.memory, nullptr);
+	for (Image image : images)
+	{
+		vkDestroyImageView(vulkanDevice->logicalDevice, image.texture.view, nullptr);
+		vkDestroyImage(vulkanDevice->logicalDevice, image.texture.image, nullptr);
+		vkDestroySampler(vulkanDevice->logicalDevice, image.texture.sampler, nullptr);
+		vkFreeMemory(vulkanDevice->logicalDevice, image.texture.deviceMemory, nullptr);
+	}
+	for (Skin skin : skins)
+	{
+		skin.ssbo.destroy();
+	}
+}
+
+/*
+	glTF loading functions
+
+	The following functions take a glTF input model loaded via tinyglTF and converts all required data into our own structures
+*/
+
+void VulkanglTFModel::loadImages(tinygltf::Model &input)
+{
+	// Images can be stored inside the glTF (which is the case for the sample model), so instead of directly
+	// loading them from disk, we fetch them from the glTF loader and upload the buffers
+	images.resize(input.images.size());
+	for (size_t i = 0; i < input.images.size(); i++)
+	{
+		tinygltf::Image &glTFImage = input.images[i];
+		// Get the image data from the glTF loader
+		unsigned char *buffer       = nullptr;
+		VkDeviceSize   bufferSize   = 0;
+		bool           deleteBuffer = false;
+		// We convert RGB-only images to RGBA, as most devices don't support RGB-formats in Vulkan
+		if (glTFImage.component == 3)
+		{
+			bufferSize          = glTFImage.width * glTFImage.height * 4;
+			buffer              = new unsigned char[bufferSize];
+			unsigned char *rgba = buffer;
+			unsigned char *rgb  = &glTFImage.image[0];
+			for (size_t i = 0; i < glTFImage.width * glTFImage.height; ++i)
+			{
+				memcpy(rgba, rgb, sizeof(unsigned char) * 3);
+				rgba += 4;
+				rgb += 3;
+			}
+			deleteBuffer = true;
+		}
+		else
+		{
+			buffer     = &glTFImage.image[0];
+			bufferSize = glTFImage.image.size();
+		}
+		// Load texture from image buffer
+		images[i].texture.fromBuffer(buffer, bufferSize, VK_FORMAT_R8G8B8A8_UNORM, glTFImage.width, glTFImage.height, vulkanDevice, copyQueue);
+		if (deleteBuffer)
+		{
+			delete[] buffer;
+		}
+	}
+}
+
+void VulkanglTFModel::loadTextures(tinygltf::Model &input)
+{
+	textures.resize(input.textures.size());
+	for (size_t i = 0; i < input.textures.size(); i++)
+	{
+		textures[i].imageIndex = input.textures[i].source;
+	}
+}
+
+void VulkanglTFModel::loadMaterials(tinygltf::Model &input)
+{
+	materials.resize(input.materials.size());
+	for (size_t i = 0; i < input.materials.size(); i++)
+	{
+		// We only read the most basic properties required for our sample
+		tinygltf::Material glTFMaterial = input.materials[i];
+		// Get the base color factor
+		if (glTFMaterial.values.find("baseColorFactor") != glTFMaterial.values.end())
+		{
+			materials[i].baseColorFactor = glm::make_vec4(glTFMaterial.values["baseColorFactor"].ColorFactor().data());
+		}
+		// Get base color texture index
+		if (glTFMaterial.values.find("baseColorTexture") != glTFMaterial.values.end())
+		{
+			materials[i].baseColorTextureIndex = glTFMaterial.values["baseColorTexture"].TextureIndex();
+		}
+	}
+}
+
+// Helper functions for locating glTF nodes
+
+VulkanglTFModel::Node *VulkanglTFModel::findNode(Node *parent, uint32_t index)
+{
+	Node *nodeFound = nullptr;
+	if (parent->index == index)
+	{
+		return parent;
+	}
+	for (auto &child : parent->children)
+	{
+		nodeFound = findNode(child, index);
+		if (nodeFound)
+		{
+			break;
+		}
+	}
+	return nodeFound;
+}
+
+VulkanglTFModel::Node *VulkanglTFModel::nodeFromIndex(uint32_t index)
+{
+	Node *nodeFound = nullptr;
+	for (auto &node : nodes)
+	{
+		nodeFound = findNode(node, index);
+		if (nodeFound)
+		{
+			break;
+		}
+	}
+	return nodeFound;
+}
+
+// POI: Load the skins from the glTF model
+void VulkanglTFModel::loadSkins(tinygltf::Model &input)
+{
+	skins.resize(input.skins.size());
+
+	for (size_t i = 0; i < input.skins.size(); i++)
+	{
+		tinygltf::Skin glTFSkin = input.skins[i];
+
+		skins[i].name = glTFSkin.name;
+		// Find the root node of the skeleton
+		skins[i].skeletonRoot = nodeFromIndex(glTFSkin.skeleton);
+
+		// Find joint nodes
+		for (int jointIndex : glTFSkin.joints)
+		{
+			Node *node = nodeFromIndex(jointIndex);
+			if (node)
+			{
+				skins[i].joints.push_back(node);
+			}
+		}
+
+		// Get the inverse bind matrices from the buffer associated to this skin
+		if (glTFSkin.inverseBindMatrices > -1)
+		{
+			const tinygltf::Accessor &  accessor   = input.accessors[glTFSkin.inverseBindMatrices];
+			const tinygltf::BufferView &bufferView = input.bufferViews[accessor.bufferView];
+			const tinygltf::Buffer &    buffer     = input.buffers[bufferView.buffer];
+			skins[i].inverseBindMatrices.resize(accessor.count);
+			memcpy(skins[i].inverseBindMatrices.data(), &buffer.data[accessor.byteOffset + bufferView.byteOffset], accessor.count * sizeof(glm::mat4));
+
+			// Store inverse bind matrices for this skin in a shader storage buffer object
+			// To keep this sample simple, we create a host visible shader storage buffer
+			VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			    VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
+			    VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			    &skins[i].ssbo,
+			    sizeof(glm::mat4) * skins[i].inverseBindMatrices.size(),
+			    skins[i].inverseBindMatrices.data()));
+			VK_CHECK_RESULT(skins[i].ssbo.map());
+		}
+	}
+}
+
+// POI: Load the animations from the glTF model
+void VulkanglTFModel::loadAnimations(tinygltf::Model &input)
+{
+	animations.resize(input.animations.size());
+
+	for (size_t i = 0; i < input.animations.size(); i++)
+	{
+		tinygltf::Animation glTFAnimation = input.animations[i];
+		animations[i].name                = glTFAnimation.name;
+
+		// Samplers
+		animations[i].samplers.resize(glTFAnimation.samplers.size());
+		for (size_t j = 0; j < glTFAnimation.samplers.size(); j++)
+		{
+			tinygltf::AnimationSampler glTFSampler = glTFAnimation.samplers[j];
+			AnimationSampler &         dstSampler  = animations[i].samplers[j];
+			dstSampler.interpolation               = glTFSampler.interpolation;
+
+			// Read sampler keyframe input time values
+			{
+				const tinygltf::Accessor &  accessor   = input.accessors[glTFSampler.input];
+				const tinygltf::BufferView &bufferView = input.bufferViews[accessor.bufferView];
+				const tinygltf::Buffer &    buffer     = input.buffers[bufferView.buffer];
+				const void *                dataPtr    = &buffer.data[accessor.byteOffset + bufferView.byteOffset];
+				const float *               buf        = static_cast<const float *>(dataPtr);
+				for (size_t index = 0; index < accessor.count; index++)
+				{
+					dstSampler.inputs.push_back(buf[index]);
+				}
+				// Adjust animation's start and end times
+				for (auto input : animations[i].samplers[j].inputs)
+				{
+					if (input < animations[i].start)
+					{
+						animations[i].start = input;
+					};
+					if (input > animations[i].end)
+					{
+						animations[i].end = input;
+					}
+				}
+			}
+
+			// Read sampler keyframe output translate/rotate/scale values
+			{
+				const tinygltf::Accessor &  accessor   = input.accessors[glTFSampler.output];
+				const tinygltf::BufferView &bufferView = input.bufferViews[accessor.bufferView];
+				const tinygltf::Buffer &    buffer     = input.buffers[bufferView.buffer];
+				const void *                dataPtr    = &buffer.data[accessor.byteOffset + bufferView.byteOffset];
+				switch (accessor.type)
+				{
+					case TINYGLTF_TYPE_VEC3: {
+						const glm::vec3 *buf = static_cast<const glm::vec3 *>(dataPtr);
+						for (size_t index = 0; index < accessor.count; index++)
+						{
+							dstSampler.outputsVec4.push_back(glm::vec4(buf[index], 0.0f));
+						}
+						break;
+					}
+					case TINYGLTF_TYPE_VEC4: {
+						const glm::vec4 *buf = static_cast<const glm::vec4 *>(dataPtr);
+						for (size_t index = 0; index < accessor.count; index++)
+						{
+							dstSampler.outputsVec4.push_back(buf[index]);
+						}
+						break;
+					}
+					default: {
+						std::cout << "unknown type" << std::endl;
+						break;
+					}
+				}
+			}
+		}
+
+		// Channels
+		animations[i].channels.resize(glTFAnimation.channels.size());
+		for (size_t j = 0; j < glTFAnimation.channels.size(); j++)
+		{
+			tinygltf::AnimationChannel glTFChannel = glTFAnimation.channels[j];
+			AnimationChannel &         dstChannel  = animations[i].channels[j];
+			dstChannel.path                        = glTFChannel.target_path;
+			dstChannel.samplerIndex                = glTFChannel.sampler;
+			dstChannel.node                        = nodeFromIndex(glTFChannel.target_node);
+		}
+	}
+}
+
+void VulkanglTFModel::loadNode(const tinygltf::Node &inputNode, const tinygltf::Model &input, VulkanglTFModel::Node *parent, uint32_t nodeIndex, std::vector<uint32_t> &indexBuffer, std::vector<VulkanglTFModel::Vertex> &vertexBuffer)
+{
+	VulkanglTFModel::Node *node = new VulkanglTFModel::Node{};
+	node->parent                = parent;
+	node->matrix                = glm::mat4(1.0f);
+	node->index                 = nodeIndex;
+	node->skin                  = inputNode.skin;
+
+	// Get the local node matrix
+	// It's either made up from translation, rotation, scale or a 4x4 matrix
+	if (inputNode.translation.size() == 3)
+	{
+		node->translation = glm::make_vec3(inputNode.translation.data());
+	}
+	if (inputNode.rotation.size() == 4)
+	{
+		glm::quat q    = glm::make_quat(inputNode.rotation.data());
+		node->rotation = glm::mat4(q);
+	}
+	if (inputNode.scale.size() == 3)
+	{
+		node->scale = glm::make_vec3(inputNode.scale.data());
+	}
+	if (inputNode.matrix.size() == 16)
+	{
+		node->matrix = glm::make_mat4x4(inputNode.matrix.data());
+	};
+
+	// Load node's children
+	if (inputNode.children.size() > 0)
+	{
+		for (size_t i = 0; i < inputNode.children.size(); i++)
+		{
+			loadNode(input.nodes[inputNode.children[i]], input, node, inputNode.children[i], indexBuffer, vertexBuffer);
+		}
+	}
+
+	// If the node contains mesh data, we load vertices and indices from the buffers
+	// In glTF this is done via accessors and buffer views
+	if (inputNode.mesh > -1)
+	{
+		const tinygltf::Mesh mesh = input.meshes[inputNode.mesh];
+		// Iterate through all primitives of this node's mesh
+		for (size_t i = 0; i < mesh.primitives.size(); i++)
+		{
+			const tinygltf::Primitive &glTFPrimitive = mesh.primitives[i];
+			uint32_t                   firstIndex    = static_cast<uint32_t>(indexBuffer.size());
+			uint32_t                   vertexStart   = static_cast<uint32_t>(vertexBuffer.size());
+			uint32_t                   indexCount    = 0;
+			bool                       hasSkin       = false;
+			// Vertices
+			{
+				const float *   positionBuffer     = nullptr;
+				const float *   normalsBuffer      = nullptr;
+				const float *   texCoordsBuffer    = nullptr;
+				const uint16_t *jointIndicesBuffer = nullptr;
+				const float *   jointWeightsBuffer = nullptr;
+				size_t          vertexCount        = 0;
+
+				// Get buffer data for vertex normals
+				if (glTFPrimitive.attributes.find("POSITION") != glTFPrimitive.attributes.end())
+				{
+					const tinygltf::Accessor &  accessor = input.accessors[glTFPrimitive.attributes.find("POSITION")->second];
+					const tinygltf::BufferView &view     = input.bufferViews[accessor.bufferView];
+					positionBuffer                       = reinterpret_cast<const float *>(&(input.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset]));
+					vertexCount                          = accessor.count;
+				}
+				// Get buffer data for vertex normals
+				if (glTFPrimitive.attributes.find("NORMAL") != glTFPrimitive.attributes.end())
+				{
+					const tinygltf::Accessor &  accessor = input.accessors[glTFPrimitive.attributes.find("NORMAL")->second];
+					const tinygltf::BufferView &view     = input.bufferViews[accessor.bufferView];
+					normalsBuffer                        = reinterpret_cast<const float *>(&(input.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset]));
+				}
+				// Get buffer data for vertex texture coordinates
+				// glTF supports multiple sets, we only load the first one
+				if (glTFPrimitive.attributes.find("TEXCOORD_0") != glTFPrimitive.attributes.end())
+				{
+					const tinygltf::Accessor &  accessor = input.accessors[glTFPrimitive.attributes.find("TEXCOORD_0")->second];
+					const tinygltf::BufferView &view     = input.bufferViews[accessor.bufferView];
+					texCoordsBuffer                      = reinterpret_cast<const float *>(&(input.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset]));
+				}
+
+				// POI: Get buffer data required for vertex skinning
+				// Get vertex joint indices
+				if (glTFPrimitive.attributes.find("JOINTS_0") != glTFPrimitive.attributes.end())
+				{
+					const tinygltf::Accessor &  accessor = input.accessors[glTFPrimitive.attributes.find("JOINTS_0")->second];
+					const tinygltf::BufferView &view     = input.bufferViews[accessor.bufferView];
+					jointIndicesBuffer                   = reinterpret_cast<const uint16_t *>(&(input.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset]));
+				}
+				// Get vertex joint weights
+				if (glTFPrimitive.attributes.find("WEIGHTS_0") != glTFPrimitive.attributes.end())
+				{
+					const tinygltf::Accessor &  accessor = input.accessors[glTFPrimitive.attributes.find("WEIGHTS_0")->second];
+					const tinygltf::BufferView &view     = input.bufferViews[accessor.bufferView];
+					jointWeightsBuffer                   = reinterpret_cast<const float *>(&(input.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset]));
+				}
+
+				hasSkin = (jointIndicesBuffer && jointWeightsBuffer);
+
+				// Append data to model's vertex buffer
+				for (size_t v = 0; v < vertexCount; v++)
+				{
+					Vertex vert{};
+					vert.pos          = glm::vec4(glm::make_vec3(&positionBuffer[v * 3]), 1.0f);
+					vert.normal       = glm::normalize(glm::vec3(normalsBuffer ? glm::make_vec3(&normalsBuffer[v * 3]) : glm::vec3(0.0f)));
+					vert.uv           = texCoordsBuffer ? glm::make_vec2(&texCoordsBuffer[v * 2]) : glm::vec3(0.0f);
+					vert.color        = glm::vec3(1.0f);
+					vert.jointIndices = hasSkin ? glm::vec4(glm::make_vec4(&jointIndicesBuffer[v * 4])) : glm::vec4(0.0f);
+					vert.jointWeights = hasSkin ? glm::make_vec4(&jointWeightsBuffer[v * 4]) : glm::vec4(0.0f);
+					vertexBuffer.push_back(vert);
+				}
+			}
+			// Indices
+			{
+				const tinygltf::Accessor &  accessor   = input.accessors[glTFPrimitive.indices];
+				const tinygltf::BufferView &bufferView = input.bufferViews[accessor.bufferView];
+				const tinygltf::Buffer &    buffer     = input.buffers[bufferView.buffer];
+
+				indexCount += static_cast<uint32_t>(accessor.count);
+
+				// glTF supports different component types of indices
+				switch (accessor.componentType)
+				{
+					case TINYGLTF_PARAMETER_TYPE_UNSIGNED_INT: {
+						const uint32_t* buf = reinterpret_cast<const uint32_t*>(&buffer.data[accessor.byteOffset + bufferView.byteOffset]);
+						for (size_t index = 0; index < accessor.count; index++)
+						{
+							indexBuffer.push_back(buf[index] + vertexStart);
+						}
+						break;
+					}
+					case TINYGLTF_PARAMETER_TYPE_UNSIGNED_SHORT: {
+						const uint16_t* buf = reinterpret_cast<const uint16_t*>(&buffer.data[accessor.byteOffset + bufferView.byteOffset]);
+						for (size_t index = 0; index < accessor.count; index++)
+						{
+							indexBuffer.push_back(buf[index] + vertexStart);
+						}
+						break;
+					}
+					case TINYGLTF_PARAMETER_TYPE_UNSIGNED_BYTE: {
+						const uint8_t* buf = reinterpret_cast<const uint8_t*>(&buffer.data[accessor.byteOffset + bufferView.byteOffset]);
+						for (size_t index = 0; index < accessor.count; index++)
+						{
+							indexBuffer.push_back(buf[index] + vertexStart);
+						}
+						break;
+					}
+					default:
+						std::cerr << "Index component type " << accessor.componentType << " not supported!" << std::endl;
+						return;
+				}
+			}
+			Primitive primitive{};
+			primitive.firstIndex    = firstIndex;
+			primitive.indexCount    = indexCount;
+			primitive.materialIndex = glTFPrimitive.material;
+			node->mesh.primitives.push_back(primitive);
+		}
+	}
+
+	if (parent)
+	{
+		parent->children.push_back(node);
+	}
+	else
+	{
+		nodes.push_back(node);
+	}
+}
+
+/*
+	glTF vertex skinning functions
+*/
+
+// POI: Traverse the node hierarchy to the top-most parent to get the local matrix of the given node
+glm::mat4 VulkanglTFModel::getNodeMatrix(VulkanglTFModel::Node *node)
+{
+	glm::mat4              nodeMatrix    = node->getLocalMatrix();
+	VulkanglTFModel::Node *currentParent = node->parent;
+	while (currentParent)
+	{
+		nodeMatrix    = currentParent->getLocalMatrix() * nodeMatrix;
+		currentParent = currentParent->parent;
+	}
+	return nodeMatrix;
+}
+
+// POI: Update the joint matrices from the current animation frame and pass them to the GPU
+void VulkanglTFModel::updateJoints(VulkanglTFModel::Node *node)
+{
+	if (node->skin > -1)
+	{
+		// Update the joint matrices
+		glm::mat4              inverseTransform = glm::inverse(getNodeMatrix(node));
+		Skin                   skin             = skins[node->skin];
+		size_t                 numJoints        = (uint32_t) skin.joints.size();
+		std::vector<glm::mat4> jointMatrices(numJoints);
+		for (size_t i = 0; i < numJoints; i++)
+		{
+			jointMatrices[i] = getNodeMatrix(skin.joints[i]) * skin.inverseBindMatrices[i];
+			jointMatrices[i] = inverseTransform * jointMatrices[i];
+		}
+		// Update ssbo
+		skin.ssbo.copyTo(jointMatrices.data(), jointMatrices.size() * sizeof(glm::mat4));
+	}
+
+	for (auto &child : node->children)
+	{
+		updateJoints(child);
+	}
+}
+
+// POI: Update the current animation
+void VulkanglTFModel::updateAnimation(float deltaTime)
+{
+	if (activeAnimation > static_cast<uint32_t>(animations.size()) - 1)
+	{
+		std::cout << "No animation with index " << activeAnimation << std::endl;
+		return;
+	}
+	Animation &animation = animations[activeAnimation];
+	animation.currentTime += deltaTime;
+	if (animation.currentTime > animation.end)
+	{
+		animation.currentTime -= animation.end;
+	}
+
+	for (auto &channel : animation.channels)
+	{
+		AnimationSampler &sampler = animation.samplers[channel.samplerIndex];
+		for (size_t i = 0; i < sampler.inputs.size() - 1; i++)
+		{
+			if (sampler.interpolation != "LINEAR")
+			{
+				std::cout << "This sample only supports linear interpolations\n";
+				continue;
+			}
+
+			// Get the input keyframe values for the current time stamp
+			if ((animation.currentTime >= sampler.inputs[i]) && (animation.currentTime <= sampler.inputs[i + 1]))
+			{
+				float a = (animation.currentTime - sampler.inputs[i]) / (sampler.inputs[i + 1] - sampler.inputs[i]);
+				if (channel.path == "translation")
+				{
+					channel.node->translation = glm::mix(sampler.outputsVec4[i], sampler.outputsVec4[i + 1], a);
+				}
+				if (channel.path == "rotation")
+				{
+					glm::quat q1;
+					q1.x = sampler.outputsVec4[i].x;
+					q1.y = sampler.outputsVec4[i].y;
+					q1.z = sampler.outputsVec4[i].z;
+					q1.w = sampler.outputsVec4[i].w;
+
+					glm::quat q2;
+					q2.x = sampler.outputsVec4[i + 1].x;
+					q2.y = sampler.outputsVec4[i + 1].y;
+					q2.z = sampler.outputsVec4[i + 1].z;
+					q2.w = sampler.outputsVec4[i + 1].w;
+
+					channel.node->rotation = glm::normalize(glm::slerp(q1, q2, a));
+				}
+				if (channel.path == "scale")
+				{
+					channel.node->scale = glm::mix(sampler.outputsVec4[i], sampler.outputsVec4[i + 1], a);
+				}
+			}
+		}
+	}
+	for (auto &node : nodes)
+	{
+		updateJoints(node);
+	}
+}
+
+/*
+	glTF rendering functions
+*/
+
+// Draw a single node including child nodes (if present)
+void VulkanglTFModel::drawNode(VkCommandBuffer commandBuffer, VkPipelineLayout pipelineLayout, VulkanglTFModel::Node node)
+{
+	if (node.mesh.primitives.size() > 0)
+	{
+		// Pass the node's matrix via push constants
+		// Traverse the node hierarchy to the top-most parent to get the final matrix of the current node
+		glm::mat4              nodeMatrix    = node.matrix;
+		VulkanglTFModel::Node *currentParent = node.parent;
+		while (currentParent)
+		{
+			nodeMatrix    = currentParent->matrix * nodeMatrix;
+			currentParent = currentParent->parent;
+		}
+		// Pass the final matrix to the vertex shader using push constants
+		vkCmdPushConstants(commandBuffer, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(glm::mat4), &nodeMatrix);
+		// Bind SSBO with skin data for this node to set 1
+		vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 1, 1, &skins[node.skin].descriptorSet, 0, nullptr);
+		for (VulkanglTFModel::Primitive &primitive : node.mesh.primitives)
+		{
+			if (primitive.indexCount > 0)
+			{
+				// Get the texture index for this primitive
+				VulkanglTFModel::Texture texture = textures[materials[primitive.materialIndex].baseColorTextureIndex];
+				// Bind the descriptor for the current primitive's texture to set 2
+				vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 2, 1, &images[texture.imageIndex].descriptorSet, 0, nullptr);
+				vkCmdDrawIndexed(commandBuffer, primitive.indexCount, 1, primitive.firstIndex, 0, 0);
+			}
+		}
+	}
+	for (auto &child : node.children)
+	{
+		drawNode(commandBuffer, pipelineLayout, *child);
+	}
+}
+
+// Draw the glTF scene starting at the top-level-nodes
+void VulkanglTFModel::draw(VkCommandBuffer commandBuffer, VkPipelineLayout pipelineLayout)
+{
+	// All vertices and indices are stored in single buffers, so we only need to bind once
+	VkDeviceSize offsets[1] = {0};
+	vkCmdBindVertexBuffers(commandBuffer, 0, 1, &vertices.buffer, offsets);
+	vkCmdBindIndexBuffer(commandBuffer, indices.buffer, 0, VK_INDEX_TYPE_UINT32);
+	// Render all nodes at top-level
+	for (auto &node : nodes)
+	{
+		drawNode(commandBuffer, pipelineLayout, *node);
+	}
+}
+
+/*
+
+	Vulkan Example class
+
+*/
+
+VulkanExample::VulkanExample() :
+    VulkanExampleBase(ENABLE_VALIDATION)
+{
+	title        = "glTF vertex skinning";
+	camera.type  = Camera::CameraType::lookat;
+	camera.flipY = true;
+	camera.setPosition(glm::vec3(0.0f, 0.75f, -2.0f));
+	camera.setRotation(glm::vec3(0.0f, 0.0f, 0.0f));
+	camera.setPerspective(60.0f, (float) width / (float) height, 0.1f, 256.0f);
+}
+
+VulkanExample::~VulkanExample()
+{
+	vkDestroyPipeline(device, pipelines.solid, nullptr);
+	if (pipelines.wireframe != VK_NULL_HANDLE)
+	{
+		vkDestroyPipeline(device, pipelines.wireframe, nullptr);
+	}
+
+	vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+	vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.matrices, nullptr);
+	vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.textures, nullptr);
+	vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.jointMatrices, nullptr);
+
+	shaderData.buffer.destroy();
+}
+
+void VulkanExample::getEnabledFeatures()
+{
+	// Fill mode non solid is required for wireframe display
+	if (deviceFeatures.fillModeNonSolid)
+	{
+		enabledFeatures.fillModeNonSolid = VK_TRUE;
+	};
+}
+
+void VulkanExample::buildCommandBuffers()
+{
+	VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+	VkClearValue clearValues[2];
+	clearValues[0].color = {{0.25f, 0.25f, 0.25f, 1.0f}};
+	;
+	clearValues[1].depthStencil = {1.0f, 0};
+
+	VkRenderPassBeginInfo renderPassBeginInfo    = vks::initializers::renderPassBeginInfo();
+	renderPassBeginInfo.renderPass               = renderPass;
+	renderPassBeginInfo.renderArea.offset.x      = 0;
+	renderPassBeginInfo.renderArea.offset.y      = 0;
+	renderPassBeginInfo.renderArea.extent.width  = width;
+	renderPassBeginInfo.renderArea.extent.height = height;
+	renderPassBeginInfo.clearValueCount          = 2;
+	renderPassBeginInfo.pClearValues             = clearValues;
+
+	const VkViewport viewport = vks::initializers::viewport((float) width, (float) height, 0.0f, 1.0f);
+	const VkRect2D   scissor  = vks::initializers::rect2D(width, height, 0, 0);
+
+	for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+	{
+		renderPassBeginInfo.framebuffer = frameBuffers[i];
+		VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+		vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+		vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+		vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+		// Bind scene matrices descriptor to set 0
+		vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr);
+		vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, wireframe ? pipelines.wireframe : pipelines.solid);
+		glTFModel.draw(drawCmdBuffers[i], pipelineLayout);
+		drawUI(drawCmdBuffers[i]);
+		vkCmdEndRenderPass(drawCmdBuffers[i]);
+		VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+	}
+}
+
+void VulkanExample::loadglTFFile(std::string filename)
+{
+	tinygltf::Model    glTFInput;
+	tinygltf::TinyGLTF gltfContext;
+	std::string        error, warning;
+
+	this->device = device;
+
+#if defined(__ANDROID__)
+	// On Android all assets are packed with the apk in a compressed form, so we need to open them using the asset manager
+	// We let tinygltf handle this, by passing the asset manager of our app
+	tinygltf::asset_manager = androidApp->activity->assetManager;
+#endif
+	bool fileLoaded = gltfContext.LoadASCIIFromFile(&glTFInput, &error, &warning, filename);
+
+	// Pass some Vulkan resources required for setup and rendering to the glTF model loading class
+	glTFModel.vulkanDevice = vulkanDevice;
+	glTFModel.copyQueue    = queue;
+
+	std::vector<uint32_t>                indexBuffer;
+	std::vector<VulkanglTFModel::Vertex> vertexBuffer;
+
+	if (fileLoaded)
+	{
+		glTFModel.loadImages(glTFInput);
+		glTFModel.loadMaterials(glTFInput);
+		glTFModel.loadTextures(glTFInput);
+		const tinygltf::Scene &scene = glTFInput.scenes[0];
+		for (size_t i = 0; i < scene.nodes.size(); i++)
+		{
+			const tinygltf::Node node = glTFInput.nodes[scene.nodes[i]];
+			glTFModel.loadNode(node, glTFInput, nullptr, scene.nodes[i], indexBuffer, vertexBuffer);
+		}
+		glTFModel.loadSkins(glTFInput);
+		glTFModel.loadAnimations(glTFInput);
+		// Calculate initial pose
+		for (auto node : glTFModel.nodes)
+		{
+			glTFModel.updateJoints(node);
+		}
+	}
+	else
+	{
+		vks::tools::exitFatal("Could not open the glTF file.\n\nThe file is part of the additional asset pack.\n\nRun \"download_assets.py\" in the repository root to download the latest version.", -1);
+		return;
+	}
+
+	// Create and upload vertex and index buffer
+	size_t vertexBufferSize = vertexBuffer.size() * sizeof(VulkanglTFModel::Vertex);
+	size_t indexBufferSize  = indexBuffer.size() * sizeof(uint32_t);
+	glTFModel.indices.count = static_cast<uint32_t>(indexBuffer.size());
+
+	struct StagingBuffer
+	{
+		VkBuffer       buffer;
+		VkDeviceMemory memory;
+	} vertexStaging, indexStaging;
+
+	// Create host visible staging buffers (source)
+	VK_CHECK_RESULT(vulkanDevice->createBuffer(
+	    VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
+	    VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+	    vertexBufferSize,
+	    &vertexStaging.buffer,
+	    &vertexStaging.memory,
+	    vertexBuffer.data()));
+	// Index data
+	VK_CHECK_RESULT(vulkanDevice->createBuffer(
+	    VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
+	    VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+	    indexBufferSize,
+	    &indexStaging.buffer,
+	    &indexStaging.memory,
+	    indexBuffer.data()));
+
+	// Create device local buffers (target)
+	VK_CHECK_RESULT(vulkanDevice->createBuffer(
+	    VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
+	    VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
+	    vertexBufferSize,
+	    &glTFModel.vertices.buffer,
+	    &glTFModel.vertices.memory));
+	VK_CHECK_RESULT(vulkanDevice->createBuffer(
+	    VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
+	    VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
+	    indexBufferSize,
+	    &glTFModel.indices.buffer,
+	    &glTFModel.indices.memory));
+
+	// Copy data from staging buffers (host) do device local buffer (gpu)
+	VkCommandBuffer copyCmd    = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+	VkBufferCopy    copyRegion = {};
+	copyRegion.size            = vertexBufferSize;
+	vkCmdCopyBuffer(copyCmd, vertexStaging.buffer, glTFModel.vertices.buffer, 1, &copyRegion);
+	copyRegion.size = indexBufferSize;
+	vkCmdCopyBuffer(copyCmd, indexStaging.buffer, glTFModel.indices.buffer, 1, &copyRegion);
+	vulkanDevice->flushCommandBuffer(copyCmd, queue, true);
+
+	// Free staging resources
+	vkDestroyBuffer(device, vertexStaging.buffer, nullptr);
+	vkFreeMemory(device, vertexStaging.memory, nullptr);
+	vkDestroyBuffer(device, indexStaging.buffer, nullptr);
+	vkFreeMemory(device, indexStaging.memory, nullptr);
+}
+
+void VulkanExample::setupDescriptors()
+{
+	/*
+		This sample uses separate descriptor sets (and layouts) for the matrices and materials (textures)
+	*/
+
+	std::vector<VkDescriptorPoolSize> poolSizes = {
+	    vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1),
+	    // One combined image sampler per material image/texture
+	    vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, static_cast<uint32_t>(glTFModel.images.size())),
+	    // One ssbo per skin
+	    vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, static_cast<uint32_t>(glTFModel.skins.size())),
+	};
+	// Number of descriptor sets = One for the scene ubo + one per image + one per skin
+	const uint32_t             maxSetCount        = static_cast<uint32_t>(glTFModel.images.size()) + static_cast<uint32_t>(glTFModel.skins.size()) + 1;
+	VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, maxSetCount);
+	VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+
+	// Descriptor set layouts
+	VkDescriptorSetLayoutBinding    setLayoutBinding{};
+	VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCI = vks::initializers::descriptorSetLayoutCreateInfo(&setLayoutBinding, 1);
+
+	// Descriptor set layout for passing matrices
+	setLayoutBinding = vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0);
+	VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorSetLayoutCI, nullptr, &descriptorSetLayouts.matrices));
+
+	// Descriptor set layout for passing material textures
+	setLayoutBinding = vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0);
+	VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorSetLayoutCI, nullptr, &descriptorSetLayouts.textures));
+
+	// Descriptor set layout for passing skin joint matrices
+	setLayoutBinding = vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0);
+	VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorSetLayoutCI, nullptr, &descriptorSetLayouts.jointMatrices));
+
+	// The pipeline layout uses three sets:
+	// Set 0 = Scene matrices (VS)
+	// Set 1 = Joint matrices (VS)
+	// Set 2 = Material texture (FS)
+	std::array<VkDescriptorSetLayout, 3> setLayouts = {
+	    descriptorSetLayouts.matrices,
+	    descriptorSetLayouts.jointMatrices,
+	    descriptorSetLayouts.textures};
+	VkPipelineLayoutCreateInfo pipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(setLayouts.data(), static_cast<uint32_t>(setLayouts.size()));
+
+	// We will use push constants to push the local matrices of a primitive to the vertex shader
+	VkPushConstantRange pushConstantRange = vks::initializers::pushConstantRange(VK_SHADER_STAGE_VERTEX_BIT, sizeof(glm::mat4), 0);
+	// Push constant ranges are part of the pipeline layout
+	pipelineLayoutCI.pushConstantRangeCount = 1;
+	pipelineLayoutCI.pPushConstantRanges    = &pushConstantRange;
+	VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelineLayout));
+
+	// Descriptor set for scene matrices
+	VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.matrices, 1);
+	VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet));
+	VkWriteDescriptorSet writeDescriptorSet = vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &shaderData.buffer.descriptor);
+	vkUpdateDescriptorSets(device, 1, &writeDescriptorSet, 0, nullptr);
+
+	// Descriptor set for glTF model skin joint matrices
+	for (auto &skin : glTFModel.skins)
+	{
+		const VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.jointMatrices, 1);
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &skin.descriptorSet));
+		VkWriteDescriptorSet writeDescriptorSet = vks::initializers::writeDescriptorSet(skin.descriptorSet, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 0, &skin.ssbo.descriptor);
+		vkUpdateDescriptorSets(device, 1, &writeDescriptorSet, 0, nullptr);
+	}
+
+	// Descriptor sets for glTF model materials
+	for (auto &image : glTFModel.images)
+	{
+		const VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.textures, 1);
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &image.descriptorSet));
+		VkWriteDescriptorSet writeDescriptorSet = vks::initializers::writeDescriptorSet(image.descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &image.texture.descriptor);
+		vkUpdateDescriptorSets(device, 1, &writeDescriptorSet, 0, nullptr);
+	}
+}
+
+void VulkanExample::preparePipelines()
+{
+	VkPipelineInputAssemblyStateCreateInfo inputAssemblyStateCI   = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+	VkPipelineRasterizationStateCreateInfo rasterizationStateCI   = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
+	VkPipelineColorBlendAttachmentState    blendAttachmentStateCI = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+	VkPipelineColorBlendStateCreateInfo    colorBlendStateCI      = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentStateCI);
+	VkPipelineDepthStencilStateCreateInfo  depthStencilStateCI    = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+	VkPipelineViewportStateCreateInfo      viewportStateCI        = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+	VkPipelineMultisampleStateCreateInfo   multisampleStateCI     = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
+	const std::vector<VkDynamicState>      dynamicStateEnables    = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
+	VkPipelineDynamicStateCreateInfo       dynamicStateCI         = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables.data(), static_cast<uint32_t>(dynamicStateEnables.size()), 0);
+	// Vertex input bindings and attributes
+	const std::vector<VkVertexInputBindingDescription> vertexInputBindings = {
+	    vks::initializers::vertexInputBindingDescription(0, sizeof(VulkanglTFModel::Vertex), VK_VERTEX_INPUT_RATE_VERTEX),
+	};
+	const std::vector<VkVertexInputAttributeDescription> vertexInputAttributes = {
+	    {0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(VulkanglTFModel::Vertex, pos)},
+	    {1, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(VulkanglTFModel::Vertex, normal)},
+	    {2, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(VulkanglTFModel::Vertex, uv)},
+	    {3, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(VulkanglTFModel::Vertex, color)},
+	    // POI: Per-Vertex Joint indices and weights are passed to the vertex shader
+	    {4, 0, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(VulkanglTFModel::Vertex, jointIndices)},
+	    {5, 0, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(VulkanglTFModel::Vertex, jointWeights)},
+	};
+
+	VkPipelineVertexInputStateCreateInfo vertexInputStateCI = vks::initializers::pipelineVertexInputStateCreateInfo();
+	vertexInputStateCI.vertexBindingDescriptionCount        = static_cast<uint32_t>(vertexInputBindings.size());
+	vertexInputStateCI.pVertexBindingDescriptions           = vertexInputBindings.data();
+	vertexInputStateCI.vertexAttributeDescriptionCount      = static_cast<uint32_t>(vertexInputAttributes.size());
+	vertexInputStateCI.pVertexAttributeDescriptions         = vertexInputAttributes.data();
+
+	const std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages = {
+	    loadShader(getShadersPath() + "gltfskinning/skinnedmodel.vert.spv", VK_SHADER_STAGE_VERTEX_BIT),
+	    loadShader(getShadersPath() + "gltfskinning/skinnedmodel.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT)};
+
+	VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass, 0);
+	pipelineCI.pVertexInputState            = &vertexInputStateCI;
+	pipelineCI.pInputAssemblyState          = &inputAssemblyStateCI;
+	pipelineCI.pRasterizationState          = &rasterizationStateCI;
+	pipelineCI.pColorBlendState             = &colorBlendStateCI;
+	pipelineCI.pMultisampleState            = &multisampleStateCI;
+	pipelineCI.pViewportState               = &viewportStateCI;
+	pipelineCI.pDepthStencilState           = &depthStencilStateCI;
+	pipelineCI.pDynamicState                = &dynamicStateCI;
+	pipelineCI.stageCount                   = static_cast<uint32_t>(shaderStages.size());
+	pipelineCI.pStages                      = shaderStages.data();
+
+	// Solid rendering pipeline
+	VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.solid));
+
+	// Wire frame rendering pipeline
+	if (deviceFeatures.fillModeNonSolid)
+	{
+		rasterizationStateCI.polygonMode = VK_POLYGON_MODE_LINE;
+		rasterizationStateCI.lineWidth   = 1.0f;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.wireframe));
+	}
+}
+
+void VulkanExample::prepareUniformBuffers()
+{
+	VK_CHECK_RESULT(vulkanDevice->createBuffer(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &shaderData.buffer, sizeof(shaderData.values)));
+	VK_CHECK_RESULT(shaderData.buffer.map());
+	updateUniformBuffers();
+}
+
+void VulkanExample::updateUniformBuffers()
+{
+	shaderData.values.projection = camera.matrices.perspective;
+	shaderData.values.model      = camera.matrices.view;
+	memcpy(shaderData.buffer.mapped, &shaderData.values, sizeof(shaderData.values));
+}
+
+void VulkanExample::loadAssets()
+{
+	loadglTFFile(getAssetPath() + "models/CesiumMan/glTF/CesiumMan.gltf");
+}
+
+void VulkanExample::prepare()
+{
+	VulkanExampleBase::prepare();
+	loadAssets();
+	prepareUniformBuffers();
+	setupDescriptors();
+	preparePipelines();
+	buildCommandBuffers();
+	prepared = true;
+}
+
+void VulkanExample::render()
+{
+	renderFrame();
+	if (camera.updated)
+	{
+		updateUniformBuffers();
+	}
+	// POI: Advance animation
+	if (!paused)
+	{
+		glTFModel.updateAnimation(frameTimer);
+	}
+}
+
+void VulkanExample::OnUpdateUIOverlay(vks::UIOverlay *overlay)
+{
+	if (overlay->header("Settings"))
+	{
+		if (overlay->checkBox("Wireframe", &wireframe))
+		{
+			buildCommandBuffers();
+		}
+	}
+}
+
+VULKAN_EXAMPLE_MAIN()
\ No newline at end of file
diff --git a/external/Vulkan/examples/gltfskinning/gltfskinning.h b/external/Vulkan/examples/gltfskinning/gltfskinning.h
new file mode 100644
index 0000000000000000000000000000000000000000..9423c01c6ef71b34e12f9897d7990bc6621e36d3
--- /dev/null
+++ b/external/Vulkan/examples/gltfskinning/gltfskinning.h
@@ -0,0 +1,236 @@
+/*
+* Vulkan Example - glTF skinned animation
+*
+* Copyright (C) 2020 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+/*
+ * Shows how to load and display an animated scene from a glTF file using vertex skinning
+ * See the accompanying README.md for a short tutorial on the data structures and functions required for vertex skinning
+ *
+ * For details on how glTF 2.0 works, see the official spec at https://github.com/KhronosGroup/glTF/tree/master/specification/2.0
+ *
+ * If you are looking for a complete glTF implementation, check out https://github.com/SaschaWillems/Vulkan-glTF-PBR/
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <vector>
+
+#define GLM_FORCE_RADIANS
+#define GLM_FORCE_DEPTH_ZERO_TO_ONE
+#include <glm/glm.hpp>
+#include <glm/gtc/matrix_transform.hpp>
+#include <glm/gtc/type_ptr.hpp>
+
+#define TINYGLTF_IMPLEMENTATION
+#define STB_IMAGE_IMPLEMENTATION
+#define TINYGLTF_NO_STB_IMAGE_WRITE
+#ifdef VK_USE_PLATFORM_ANDROID_KHR
+#	define TINYGLTF_ANDROID_LOAD_FROM_ASSETS
+#endif
+#include "tiny_gltf.h"
+
+#include "vulkanexamplebase.h"
+#include <vulkan/vulkan.h>
+
+#define ENABLE_VALIDATION false
+
+// Contains everything required to render a glTF model in Vulkan
+// This class is heavily simplified (compared to glTF's feature set) but retains the basic glTF structure
+class VulkanglTFModel
+{
+  public:
+	vks::VulkanDevice *vulkanDevice;
+	VkQueue            copyQueue;
+
+	/*
+		Base glTF structures, see gltfscene sample for details
+	*/
+
+	struct Vertices
+	{
+		VkBuffer       buffer;
+		VkDeviceMemory memory;
+	} vertices;
+
+	struct Indices
+	{
+		int            count;
+		VkBuffer       buffer;
+		VkDeviceMemory memory;
+	} indices;
+
+	struct Node;
+
+	struct Material
+	{
+		glm::vec4 baseColorFactor = glm::vec4(1.0f);
+		uint32_t  baseColorTextureIndex;
+	};
+
+	struct Image
+	{
+		vks::Texture2D  texture;
+		VkDescriptorSet descriptorSet;
+	};
+
+	struct Texture
+	{
+		int32_t imageIndex;
+	};
+
+	struct Primitive
+	{
+		uint32_t firstIndex;
+		uint32_t indexCount;
+		int32_t  materialIndex;
+	};
+
+	struct Mesh
+	{
+		std::vector<Primitive> primitives;
+	};
+
+	struct Node
+	{
+		Node *              parent;
+		uint32_t            index;
+		std::vector<Node *> children;
+		Mesh                mesh;
+		glm::vec3           translation{};
+		glm::vec3           scale{1.0f};
+		glm::quat           rotation{};
+		int32_t             skin = -1;
+		glm::mat4           matrix;
+		glm::mat4           getLocalMatrix();
+	};
+
+	struct Vertex
+	{
+		glm::vec3 pos;
+		glm::vec3 normal;
+		glm::vec2 uv;
+		glm::vec3 color;
+		glm::vec4 jointIndices;
+		glm::vec4 jointWeights;
+	};
+
+	/*
+		Skin structure
+	*/
+
+	struct Skin
+	{
+		std::string            name;
+		Node *                 skeletonRoot = nullptr;
+		std::vector<glm::mat4> inverseBindMatrices;
+		std::vector<Node *>    joints;
+		vks::Buffer            ssbo;
+		VkDescriptorSet        descriptorSet;
+	};
+
+	/*
+		Animation related structures
+	*/
+
+	struct AnimationSampler
+	{
+		std::string            interpolation;
+		std::vector<float>     inputs;
+		std::vector<glm::vec4> outputsVec4;
+	};
+
+	struct AnimationChannel
+	{
+		std::string path;
+		Node *      node;
+		uint32_t    samplerIndex;
+	};
+
+	struct Animation
+	{
+		std::string                   name;
+		std::vector<AnimationSampler> samplers;
+		std::vector<AnimationChannel> channels;
+		float                         start       = std::numeric_limits<float>::max();
+		float                         end         = std::numeric_limits<float>::min();
+		float                         currentTime = 0.0f;
+	};
+
+	std::vector<Image>     images;
+	std::vector<Texture>   textures;
+	std::vector<Material>  materials;
+	std::vector<Node *>    nodes;
+	std::vector<Skin>      skins;
+	std::vector<Animation> animations;
+
+	uint32_t activeAnimation = 0;
+
+	~VulkanglTFModel();
+	void      loadImages(tinygltf::Model &input);
+	void      loadTextures(tinygltf::Model &input);
+	void      loadMaterials(tinygltf::Model &input);
+	Node *    findNode(Node *parent, uint32_t index);
+	Node *    nodeFromIndex(uint32_t index);
+	void      loadSkins(tinygltf::Model &input);
+	void      loadAnimations(tinygltf::Model &input);
+	void      loadNode(const tinygltf::Node &inputNode, const tinygltf::Model &input, VulkanglTFModel::Node *parent, uint32_t nodeIndex, std::vector<uint32_t> &indexBuffer, std::vector<VulkanglTFModel::Vertex> &vertexBuffer);
+	glm::mat4 getNodeMatrix(VulkanglTFModel::Node *node);
+	void      updateJoints(VulkanglTFModel::Node *node);
+	void      updateAnimation(float deltaTime);
+	void      drawNode(VkCommandBuffer commandBuffer, VkPipelineLayout pipelineLayout, VulkanglTFModel::Node node);
+	void      draw(VkCommandBuffer commandBuffer, VkPipelineLayout pipelineLayout);
+};
+
+class VulkanExample : public VulkanExampleBase
+{
+  public:
+	bool wireframe = false;
+
+	struct ShaderData
+	{
+		vks::Buffer buffer;
+		struct Values
+		{
+			glm::mat4 projection;
+			glm::mat4 model;
+			glm::vec4 lightPos = glm::vec4(5.0f, 5.0f, 5.0f, 1.0f);
+		} values;
+	} shaderData;
+
+	VkPipelineLayout pipelineLayout;
+	struct Pipelines
+	{
+		VkPipeline solid;
+		VkPipeline wireframe = VK_NULL_HANDLE;
+	} pipelines;
+
+	struct DescriptorSetLayouts
+	{
+		VkDescriptorSetLayout matrices;
+		VkDescriptorSetLayout textures;
+		VkDescriptorSetLayout jointMatrices;
+	} descriptorSetLayouts;
+	VkDescriptorSet descriptorSet;
+
+	VulkanglTFModel glTFModel;
+
+	VulkanExample();
+	~VulkanExample();
+	void         loadglTFFile(std::string filename);
+	virtual void getEnabledFeatures();
+	void         buildCommandBuffers();
+	void         loadAssets();
+	void         setupDescriptors();
+	void         preparePipelines();
+	void         prepareUniformBuffers();
+	void         updateUniformBuffers();
+	void         prepare();
+	virtual void render();
+	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay);
+};
diff --git a/external/Vulkan/examples/hdr/hdr.cpp b/external/Vulkan/examples/hdr/hdr.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..cda5b452dc47da85782080a64b926bbbdad03f55
--- /dev/null
+++ b/external/Vulkan/examples/hdr/hdr.cpp
@@ -0,0 +1,893 @@
+/*
+* Vulkan Example - High dynamic range rendering
+*
+* Note: Requires the separate asset pack (see data/README.md)
+*
+* Copyright by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkanexamplebase.h"
+#include "VulkanglTFModel.h"
+
+#define ENABLE_VALIDATION false
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	bool bloom = true;
+	bool displaySkybox = true;
+
+	struct {
+		vks::TextureCubeMap envmap;
+	} textures;
+
+	struct Models {
+		vkglTF::Model skybox;
+		std::vector<vkglTF::Model> objects;
+		int32_t objectIndex = 1;
+	} models;
+
+	struct {
+		vks::Buffer matrices;
+		vks::Buffer params;
+	} uniformBuffers;
+
+	struct UBOVS {
+		glm::mat4 projection;
+		glm::mat4 modelview;
+		glm::mat4 inverseModelview;
+	} uboVS;
+
+	struct UBOParams {
+		float exposure = 1.0f;
+	} uboParams;
+
+	struct {
+		VkPipeline skybox;
+		VkPipeline reflect;
+		VkPipeline composition;
+		VkPipeline bloom[2];
+	} pipelines;
+
+	struct {
+		VkPipelineLayout models;
+		VkPipelineLayout composition;
+		VkPipelineLayout bloomFilter;
+	} pipelineLayouts;
+
+	struct {
+		VkDescriptorSet object;
+		VkDescriptorSet skybox;
+		VkDescriptorSet composition;
+		VkDescriptorSet bloomFilter;
+	} descriptorSets;
+
+	struct {
+		VkDescriptorSetLayout models;
+		VkDescriptorSetLayout composition;
+		VkDescriptorSetLayout bloomFilter;
+	} descriptorSetLayouts;
+
+	// Framebuffer for offscreen rendering
+	struct FrameBufferAttachment {
+		VkImage image;
+		VkDeviceMemory mem;
+		VkImageView view;
+		VkFormat format;
+		void destroy(VkDevice device)
+		{
+			vkDestroyImageView(device, view, nullptr);
+			vkDestroyImage(device, image, nullptr);
+			vkFreeMemory(device, mem, nullptr);
+		}
+	};
+	struct FrameBuffer {
+		int32_t width, height;
+		VkFramebuffer frameBuffer;
+		FrameBufferAttachment color[2];
+		FrameBufferAttachment depth;
+		VkRenderPass renderPass;
+		VkSampler sampler;
+	} offscreen;
+
+	struct {
+		int32_t width, height;
+		VkFramebuffer frameBuffer;
+		FrameBufferAttachment color[1];
+		VkRenderPass renderPass;
+		VkSampler sampler;
+	} filterPass;
+
+	std::vector<std::string> objectNames;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "High dynamic range rendering";
+		camera.type = Camera::CameraType::lookat;
+		camera.setPosition(glm::vec3(0.0f, 0.0f, -6.0f));
+		camera.setRotation(glm::vec3(0.0f, 0.0f, 0.0f));
+		camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 256.0f);
+	}
+
+	~VulkanExample()
+	{
+		vkDestroyPipeline(device, pipelines.skybox, nullptr);
+		vkDestroyPipeline(device, pipelines.reflect, nullptr);
+		vkDestroyPipeline(device, pipelines.composition, nullptr);
+		vkDestroyPipeline(device, pipelines.bloom[0], nullptr);
+		vkDestroyPipeline(device, pipelines.bloom[1], nullptr);
+
+		vkDestroyPipelineLayout(device, pipelineLayouts.models, nullptr);
+		vkDestroyPipelineLayout(device, pipelineLayouts.composition, nullptr);
+		vkDestroyPipelineLayout(device, pipelineLayouts.bloomFilter, nullptr);
+
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.models, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.composition, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.bloomFilter, nullptr);
+
+		vkDestroyRenderPass(device, offscreen.renderPass, nullptr);
+		vkDestroyRenderPass(device, filterPass.renderPass, nullptr);
+
+		vkDestroyFramebuffer(device, offscreen.frameBuffer, nullptr);
+		vkDestroyFramebuffer(device, filterPass.frameBuffer, nullptr);
+
+		vkDestroySampler(device, offscreen.sampler, nullptr);
+		vkDestroySampler(device, filterPass.sampler, nullptr);
+
+		offscreen.depth.destroy(device);
+		offscreen.color[0].destroy(device);
+		offscreen.color[1].destroy(device);
+
+		filterPass.color[0].destroy(device);
+
+		uniformBuffers.matrices.destroy();
+		uniformBuffers.params.destroy();
+		textures.envmap.destroy();
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		clearValues[0].color = { { 0.0f, 0.0f, 0.0f, 0.0f } };
+		clearValues[1].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.offset.x = 0;
+		renderPassBeginInfo.renderArea.offset.y = 0;
+		renderPassBeginInfo.clearValueCount = 2;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		VkViewport viewport;
+		VkRect2D scissor;
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			{
+				/*
+					First pass: Render scene to offscreen framebuffer
+				*/
+
+				std::array<VkClearValue, 3> clearValues;
+				clearValues[0].color = { { 0.0f, 0.0f, 0.0f, 0.0f } };
+				clearValues[1].color = { { 0.0f, 0.0f, 0.0f, 0.0f } };
+				clearValues[2].depthStencil = { 1.0f, 0 };
+
+				VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+				renderPassBeginInfo.renderPass = offscreen.renderPass;
+				renderPassBeginInfo.framebuffer = offscreen.frameBuffer;
+				renderPassBeginInfo.renderArea.extent.width = offscreen.width;
+				renderPassBeginInfo.renderArea.extent.height = offscreen.height;
+				renderPassBeginInfo.clearValueCount = 3;
+				renderPassBeginInfo.pClearValues = clearValues.data();
+
+				vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+				VkViewport viewport = vks::initializers::viewport((float)offscreen.width, (float)offscreen.height, 0.0f, 1.0f);
+				vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+				VkRect2D scissor = vks::initializers::rect2D(offscreen.width, offscreen.height, 0, 0);
+				vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+				VkDeviceSize offsets[1] = { 0 };
+
+				// Skybox
+				if (displaySkybox)
+				{
+					vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayouts.models, 0, 1, &descriptorSets.skybox, 0, NULL);
+					vkCmdBindVertexBuffers(drawCmdBuffers[i], 0, 1, &models.skybox.vertices.buffer, offsets);
+					vkCmdBindIndexBuffer(drawCmdBuffers[i], models.skybox.indices.buffer, 0, VK_INDEX_TYPE_UINT32);
+					vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.skybox);
+					models.skybox.draw(drawCmdBuffers[i]);
+				}
+
+				// 3D object
+				vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayouts.models, 0, 1, &descriptorSets.object, 0, NULL);
+				vkCmdBindVertexBuffers(drawCmdBuffers[i], 0, 1, &models.objects[models.objectIndex].vertices.buffer, offsets);
+				vkCmdBindIndexBuffer(drawCmdBuffers[i], models.objects[models.objectIndex].indices.buffer, 0, VK_INDEX_TYPE_UINT32);
+				vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.reflect);
+				models.objects[models.objectIndex].draw(drawCmdBuffers[i]);
+
+				vkCmdEndRenderPass(drawCmdBuffers[i]);
+			}
+
+			/*
+				Second render pass: First bloom pass
+			*/
+			if (bloom) {
+				VkClearValue clearValues[2];
+				clearValues[0].color = { { 0.0f, 0.0f, 0.0f, 0.0f } };
+				clearValues[1].depthStencil = { 1.0f, 0 };
+
+				// Bloom filter
+				VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+				renderPassBeginInfo.framebuffer = filterPass.frameBuffer;
+				renderPassBeginInfo.renderPass = filterPass.renderPass;
+				renderPassBeginInfo.clearValueCount = 1;
+				renderPassBeginInfo.renderArea.extent.width = filterPass.width;
+				renderPassBeginInfo.renderArea.extent.height = filterPass.height;
+				renderPassBeginInfo.pClearValues = clearValues;
+
+				vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+				VkViewport viewport = vks::initializers::viewport((float)filterPass.width, (float)filterPass.height, 0.0f, 1.0f);
+				vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+				VkRect2D scissor = vks::initializers::rect2D(filterPass.width, filterPass.height, 0, 0);
+				vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+				vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayouts.bloomFilter, 0, 1, &descriptorSets.bloomFilter, 0, NULL);
+
+				vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.bloom[1]);
+				vkCmdDraw(drawCmdBuffers[i], 3, 1, 0, 0);
+
+				vkCmdEndRenderPass(drawCmdBuffers[i]);
+			}
+
+			/*
+				Note: Explicit synchronization is not required between the render pass, as this is done implicit via sub pass dependencies
+			*/
+
+			/*
+				Third render pass: Scene rendering with applied second bloom pass (when enabled)
+			*/
+			{
+				VkClearValue clearValues[2];
+				clearValues[0].color = { { 0.0f, 0.0f, 0.0f, 0.0f } };
+				clearValues[1].depthStencil = { 1.0f, 0 };
+
+				// Final composition
+				VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+				renderPassBeginInfo.framebuffer = frameBuffers[i];
+				renderPassBeginInfo.renderPass = renderPass;
+				renderPassBeginInfo.clearValueCount = 2;
+				renderPassBeginInfo.renderArea.extent.width = width;
+				renderPassBeginInfo.renderArea.extent.height = height;
+				renderPassBeginInfo.pClearValues = clearValues;
+
+				vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+				VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+				vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+				VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
+				vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+				vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayouts.composition, 0, 1, &descriptorSets.composition, 0, NULL);
+
+				// Scene
+				vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.composition);
+				vkCmdDraw(drawCmdBuffers[i], 3, 1, 0, 0);
+
+				// Bloom
+				if (bloom) {
+					vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.bloom[0]);
+					vkCmdDraw(drawCmdBuffers[i], 3, 1, 0, 0);
+				}
+
+				drawUI(drawCmdBuffers[i]);
+
+				vkCmdEndRenderPass(drawCmdBuffers[i]);
+			}
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void createAttachment(VkFormat format, VkImageUsageFlagBits usage, FrameBufferAttachment *attachment)
+	{
+		VkImageAspectFlags aspectMask = 0;
+		VkImageLayout imageLayout;
+
+		attachment->format = format;
+
+		if (usage & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT)
+		{
+			aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+			imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+		}
+		if (usage & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT)
+		{
+			aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
+			imageLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+		}
+
+		assert(aspectMask > 0);
+
+		VkImageCreateInfo image = vks::initializers::imageCreateInfo();
+		image.imageType = VK_IMAGE_TYPE_2D;
+		image.format = format;
+		image.extent.width = offscreen.width;
+		image.extent.height = offscreen.height;
+		image.extent.depth = 1;
+		image.mipLevels = 1;
+		image.arrayLayers = 1;
+		image.samples = VK_SAMPLE_COUNT_1_BIT;
+		image.tiling = VK_IMAGE_TILING_OPTIMAL;
+		image.usage = usage | VK_IMAGE_USAGE_SAMPLED_BIT;
+
+		VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo();
+		VkMemoryRequirements memReqs;
+
+		VK_CHECK_RESULT(vkCreateImage(device, &image, nullptr, &attachment->image));
+		vkGetImageMemoryRequirements(device, attachment->image, &memReqs);
+		memAlloc.allocationSize = memReqs.size;
+		memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+		VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &attachment->mem));
+		VK_CHECK_RESULT(vkBindImageMemory(device, attachment->image, attachment->mem, 0));
+
+		VkImageViewCreateInfo imageView = vks::initializers::imageViewCreateInfo();
+		imageView.viewType = VK_IMAGE_VIEW_TYPE_2D;
+		imageView.format = format;
+		imageView.subresourceRange = {};
+		imageView.subresourceRange.aspectMask = aspectMask;
+		imageView.subresourceRange.baseMipLevel = 0;
+		imageView.subresourceRange.levelCount = 1;
+		imageView.subresourceRange.baseArrayLayer = 0;
+		imageView.subresourceRange.layerCount = 1;
+		imageView.image = attachment->image;
+		VK_CHECK_RESULT(vkCreateImageView(device, &imageView, nullptr, &attachment->view));
+	}
+
+	// Prepare a new framebuffer and attachments for offscreen rendering (G-Buffer)
+	void prepareoffscreenfer()
+	{
+		{
+			offscreen.width = width;
+			offscreen.height = height;
+
+			// Color attachments
+
+			// Two floating point color buffers
+			createAttachment(VK_FORMAT_R32G32B32A32_SFLOAT, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, &offscreen.color[0]);
+			createAttachment(VK_FORMAT_R32G32B32A32_SFLOAT, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, &offscreen.color[1]);
+			// Depth attachment
+			createAttachment(depthFormat, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, &offscreen.depth);
+
+			// Set up separate renderpass with references to the color and depth attachments
+			std::array<VkAttachmentDescription, 3> attachmentDescs = {};
+
+			// Init attachment properties
+			for (uint32_t i = 0; i < 3; ++i)
+			{
+				attachmentDescs[i].samples = VK_SAMPLE_COUNT_1_BIT;
+				attachmentDescs[i].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+				attachmentDescs[i].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+				attachmentDescs[i].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+				attachmentDescs[i].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+				if (i == 2)
+				{
+					attachmentDescs[i].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+					attachmentDescs[i].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+				}
+				else
+				{
+					attachmentDescs[i].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+					attachmentDescs[i].finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+				}
+			}
+
+			// Formats
+			attachmentDescs[0].format = offscreen.color[0].format;
+			attachmentDescs[1].format = offscreen.color[1].format;
+			attachmentDescs[2].format = offscreen.depth.format;
+
+			std::vector<VkAttachmentReference> colorReferences;
+			colorReferences.push_back({ 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL });
+			colorReferences.push_back({ 1, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL });
+
+			VkAttachmentReference depthReference = {};
+			depthReference.attachment = 2;
+			depthReference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+
+			VkSubpassDescription subpass = {};
+			subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+			subpass.pColorAttachments = colorReferences.data();
+			subpass.colorAttachmentCount = 2;
+			subpass.pDepthStencilAttachment = &depthReference;
+
+			// Use subpass dependencies for attachment layout transitions
+			std::array<VkSubpassDependency, 2> dependencies;
+
+			dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
+			dependencies[0].dstSubpass = 0;
+			dependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+			dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+			dependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
+			dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+			dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+			dependencies[1].srcSubpass = 0;
+			dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
+			dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+			dependencies[1].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+			dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+			dependencies[1].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
+			dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+			VkRenderPassCreateInfo renderPassInfo = {};
+			renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
+			renderPassInfo.pAttachments = attachmentDescs.data();
+			renderPassInfo.attachmentCount = static_cast<uint32_t>(attachmentDescs.size());
+			renderPassInfo.subpassCount = 1;
+			renderPassInfo.pSubpasses = &subpass;
+			renderPassInfo.dependencyCount = 2;
+			renderPassInfo.pDependencies = dependencies.data();
+
+			VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassInfo, nullptr, &offscreen.renderPass));
+
+			std::array<VkImageView, 3> attachments;
+			attachments[0] = offscreen.color[0].view;
+			attachments[1] = offscreen.color[1].view;
+			attachments[2] = offscreen.depth.view;
+
+			VkFramebufferCreateInfo fbufCreateInfo = {};
+			fbufCreateInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
+			fbufCreateInfo.pNext = NULL;
+			fbufCreateInfo.renderPass = offscreen.renderPass;
+			fbufCreateInfo.pAttachments = attachments.data();
+			fbufCreateInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
+			fbufCreateInfo.width = offscreen.width;
+			fbufCreateInfo.height = offscreen.height;
+			fbufCreateInfo.layers = 1;
+			VK_CHECK_RESULT(vkCreateFramebuffer(device, &fbufCreateInfo, nullptr, &offscreen.frameBuffer));
+
+			// Create sampler to sample from the color attachments
+			VkSamplerCreateInfo sampler = vks::initializers::samplerCreateInfo();
+			sampler.magFilter = VK_FILTER_NEAREST;
+			sampler.minFilter = VK_FILTER_NEAREST;
+			sampler.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+			sampler.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+			sampler.addressModeV = sampler.addressModeU;
+			sampler.addressModeW = sampler.addressModeU;
+			sampler.mipLodBias = 0.0f;
+			sampler.maxAnisotropy = 1.0f;
+			sampler.minLod = 0.0f;
+			sampler.maxLod = 1.0f;
+			sampler.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
+			VK_CHECK_RESULT(vkCreateSampler(device, &sampler, nullptr, &offscreen.sampler));
+		}
+
+		// Bloom separable filter pass
+		{
+			filterPass.width = width;
+			filterPass.height = height;
+
+			// Color attachments
+
+			// Two floating point color buffers
+			createAttachment(VK_FORMAT_R32G32B32A32_SFLOAT, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, &filterPass.color[0]);
+
+			// Set up separate renderpass with references to the color and depth attachments
+			std::array<VkAttachmentDescription, 1> attachmentDescs = {};
+
+			// Init attachment properties
+			attachmentDescs[0].samples = VK_SAMPLE_COUNT_1_BIT;
+			attachmentDescs[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+			attachmentDescs[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+			attachmentDescs[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+			attachmentDescs[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+			attachmentDescs[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+			attachmentDescs[0].finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+			attachmentDescs[0].format = filterPass.color[0].format;
+
+			std::vector<VkAttachmentReference> colorReferences;
+			colorReferences.push_back({ 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL });
+
+			VkSubpassDescription subpass = {};
+			subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+			subpass.pColorAttachments = colorReferences.data();
+			subpass.colorAttachmentCount = 1;
+
+			// Use subpass dependencies for attachment layout transitions
+			std::array<VkSubpassDependency, 2> dependencies;
+
+			dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
+			dependencies[0].dstSubpass = 0;
+			dependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+			dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+			dependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
+			dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+			dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+			dependencies[1].srcSubpass = 0;
+			dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
+			dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+			dependencies[1].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+			dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+			dependencies[1].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
+			dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+			VkRenderPassCreateInfo renderPassInfo = {};
+			renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
+			renderPassInfo.pAttachments = attachmentDescs.data();
+			renderPassInfo.attachmentCount = static_cast<uint32_t>(attachmentDescs.size());
+			renderPassInfo.subpassCount = 1;
+			renderPassInfo.pSubpasses = &subpass;
+			renderPassInfo.dependencyCount = 2;
+			renderPassInfo.pDependencies = dependencies.data();
+
+			VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassInfo, nullptr, &filterPass.renderPass));
+
+			std::array<VkImageView, 1> attachments;
+			attachments[0] = filterPass.color[0].view;
+
+			VkFramebufferCreateInfo fbufCreateInfo = {};
+			fbufCreateInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
+			fbufCreateInfo.pNext = NULL;
+			fbufCreateInfo.renderPass = filterPass.renderPass;
+			fbufCreateInfo.pAttachments = attachments.data();
+			fbufCreateInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
+			fbufCreateInfo.width = filterPass.width;
+			fbufCreateInfo.height = filterPass.height;
+			fbufCreateInfo.layers = 1;
+			VK_CHECK_RESULT(vkCreateFramebuffer(device, &fbufCreateInfo, nullptr, &filterPass.frameBuffer));
+
+			// Create sampler to sample from the color attachments
+			VkSamplerCreateInfo sampler = vks::initializers::samplerCreateInfo();
+			sampler.magFilter = VK_FILTER_NEAREST;
+			sampler.minFilter = VK_FILTER_NEAREST;
+			sampler.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+			sampler.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+			sampler.addressModeV = sampler.addressModeU;
+			sampler.addressModeW = sampler.addressModeU;
+			sampler.mipLodBias = 0.0f;
+			sampler.maxAnisotropy = 1.0f;
+			sampler.minLod = 0.0f;
+			sampler.maxLod = 1.0f;
+			sampler.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
+			VK_CHECK_RESULT(vkCreateSampler(device, &sampler, nullptr, &filterPass.sampler));
+		}
+	}
+
+	void loadAssets()
+	{
+		// Load glTF models
+		const uint32_t glTFLoadingFlags = vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::FlipY;
+		models.skybox.loadFromFile(getAssetPath() + "models/cube.gltf", vulkanDevice, queue, glTFLoadingFlags);
+		std::vector<std::string> filenames = { "sphere.gltf", "teapot.gltf", "torusknot.gltf", "venus.gltf" };
+		objectNames = { "Sphere", "Teapot", "Torusknot", "Venus" };
+		models.objects.resize(filenames.size());
+		for (size_t i = 0; i < filenames.size(); i++) {
+			models.objects[i].loadFromFile(getAssetPath() + "models/" + filenames[i], vulkanDevice, queue, glTFLoadingFlags);
+		}
+		// Load HDR cube map
+		textures.envmap.loadFromFile(getAssetPath() + "textures/hdr/uffizi_cube.ktx", VK_FORMAT_R16G16B16A16_SFLOAT, vulkanDevice, queue);
+	}
+
+	void setupDescriptorPool()
+	{
+		std::vector<VkDescriptorPoolSize> poolSizes = {
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 4),
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 6)
+		};
+		uint32_t numDescriptorSets = 4;
+		VkDescriptorPoolCreateInfo descriptorPoolInfo =
+			vks::initializers::descriptorPoolCreateInfo(static_cast<uint32_t>(poolSizes.size()), poolSizes.data(), numDescriptorSets);
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+	}
+
+	void setupDescriptorSetLayout()
+	{
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0),
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1),
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_FRAGMENT_BIT, 2),
+		};
+
+		VkDescriptorSetLayoutCreateInfo descriptorLayoutInfo =
+			vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings.data(), static_cast<uint32_t>(setLayoutBindings.size()));
+
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayoutInfo, nullptr, &descriptorSetLayouts.models));
+
+		VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo =
+			vks::initializers::pipelineLayoutCreateInfo(
+				&descriptorSetLayouts.models,
+				1);
+
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayouts.models));
+
+		// Bloom filter
+		setLayoutBindings = {
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0),
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1),
+		};
+
+		descriptorLayoutInfo = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings.data(), static_cast<uint32_t>(setLayoutBindings.size()));
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayoutInfo, nullptr, &descriptorSetLayouts.bloomFilter));
+
+		pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayouts.bloomFilter, 1);
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayouts.bloomFilter));
+
+		// G-Buffer composition
+		setLayoutBindings = {
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0),
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1),
+		};
+
+		descriptorLayoutInfo = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings.data(), static_cast<uint32_t>(setLayoutBindings.size()));
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayoutInfo, nullptr, &descriptorSetLayouts.composition));
+
+		pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayouts.composition, 1);
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayouts.composition));
+	}
+
+	void setupDescriptorSets()
+	{
+		VkDescriptorSetAllocateInfo allocInfo =
+			vks::initializers::descriptorSetAllocateInfo(
+				descriptorPool,
+				&descriptorSetLayouts.models,
+				1);
+
+		// 3D object descriptor set
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.object));
+
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets = {
+			vks::initializers::writeDescriptorSet(descriptorSets.object, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.matrices.descriptor),
+			vks::initializers::writeDescriptorSet(descriptorSets.object, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &textures.envmap.descriptor),
+			vks::initializers::writeDescriptorSet(descriptorSets.object, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2, &uniformBuffers.params.descriptor),
+		};
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL);
+
+		// Sky box descriptor set
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.skybox));
+
+		writeDescriptorSets = {
+			vks::initializers::writeDescriptorSet(descriptorSets.skybox, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0,&uniformBuffers.matrices.descriptor),
+			vks::initializers::writeDescriptorSet(descriptorSets.skybox, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &textures.envmap.descriptor),
+			vks::initializers::writeDescriptorSet(descriptorSets.skybox, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2, &uniformBuffers.params.descriptor),
+		};
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL);
+
+		// Bloom filter
+		allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.bloomFilter, 1);
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.bloomFilter));
+
+		std::vector<VkDescriptorImageInfo> colorDescriptors = {
+			vks::initializers::descriptorImageInfo(offscreen.sampler, offscreen.color[0].view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL),
+			vks::initializers::descriptorImageInfo(offscreen.sampler, offscreen.color[1].view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL),
+		};
+
+		writeDescriptorSets = {
+			vks::initializers::writeDescriptorSet(descriptorSets.bloomFilter, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &colorDescriptors[0]),
+			vks::initializers::writeDescriptorSet(descriptorSets.bloomFilter, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &colorDescriptors[1]),
+		};
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL);
+
+		// Composition descriptor set
+		allocInfo =	vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.composition, 1);
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.composition));
+
+		colorDescriptors = {
+			vks::initializers::descriptorImageInfo(offscreen.sampler, offscreen.color[0].view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL),
+			vks::initializers::descriptorImageInfo(offscreen.sampler, filterPass.color[0].view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL),
+		};
+
+		writeDescriptorSets = {
+			vks::initializers::writeDescriptorSet(descriptorSets.composition, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &colorDescriptors[0]),
+			vks::initializers::writeDescriptorSet(descriptorSets.composition, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &colorDescriptors[1]),
+		};
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL);
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_FALSE, VK_FALSE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+		VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
+		std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
+		VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayouts.models, renderPass, 0);
+		pipelineCI.pInputAssemblyState = &inputAssemblyState;
+		pipelineCI.pRasterizationState = &rasterizationState;
+		pipelineCI.pColorBlendState = &colorBlendState;
+		pipelineCI.pMultisampleState = &multisampleState;
+		pipelineCI.pViewportState = &viewportState;
+		pipelineCI.pDepthStencilState = &depthStencilState;
+		pipelineCI.pDynamicState = &dynamicState;
+		pipelineCI.stageCount = static_cast<uint32_t>(shaderStages.size());
+		pipelineCI.pStages = shaderStages.data();
+
+		VkSpecializationInfo specializationInfo;
+		std::array<VkSpecializationMapEntry, 1> specializationMapEntries;
+
+		// Full screen pipelines
+
+		// Empty vertex input state, full screen triangles are generated by the vertex shader
+		VkPipelineVertexInputStateCreateInfo emptyInputState = vks::initializers::pipelineVertexInputStateCreateInfo();
+		pipelineCI.pVertexInputState = &emptyInputState;
+
+		// Final fullscreen composition pass pipeline
+		std::vector<VkPipelineColorBlendAttachmentState> blendAttachmentStates = {
+			vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE),
+			vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE),
+		};
+		pipelineCI.layout = pipelineLayouts.composition;
+		pipelineCI.renderPass = renderPass;
+		rasterizationState.cullMode = VK_CULL_MODE_NONE;
+		colorBlendState.attachmentCount = 1;
+		colorBlendState.pAttachments = blendAttachmentStates.data();
+		shaderStages[0] = loadShader(getShadersPath() + "hdr/composition.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "hdr/composition.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.composition));
+
+		// Bloom pass
+		shaderStages[0] = loadShader(getShadersPath() + "hdr/bloom.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "hdr/bloom.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		colorBlendState.pAttachments = &blendAttachmentState;
+		blendAttachmentState.colorWriteMask = 0xF;
+		blendAttachmentState.blendEnable = VK_TRUE;
+		blendAttachmentState.colorBlendOp = VK_BLEND_OP_ADD;
+		blendAttachmentState.srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
+		blendAttachmentState.dstColorBlendFactor = VK_BLEND_FACTOR_ONE;
+		blendAttachmentState.alphaBlendOp = VK_BLEND_OP_ADD;
+		blendAttachmentState.srcAlphaBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
+		blendAttachmentState.dstAlphaBlendFactor = VK_BLEND_FACTOR_DST_ALPHA;
+
+		// Set constant parameters via specialization constants
+		specializationMapEntries[0] = vks::initializers::specializationMapEntry(0, 0, sizeof(uint32_t));
+		uint32_t dir = 1;
+		specializationInfo = vks::initializers::specializationInfo(1, specializationMapEntries.data(), sizeof(dir), &dir);
+		shaderStages[1].pSpecializationInfo = &specializationInfo;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.bloom[0]));
+
+		// Second blur pass (into separate framebuffer)
+		pipelineCI.renderPass = filterPass.renderPass;
+		dir = 0;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.bloom[1]));
+
+		// Object rendering pipelines
+		// Use vertex input state from glTF model setup
+		pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal });
+
+		blendAttachmentState.blendEnable = VK_FALSE;
+		pipelineCI.layout = pipelineLayouts.models;
+		pipelineCI.renderPass = offscreen.renderPass;
+		colorBlendState.attachmentCount = 2;
+		colorBlendState.pAttachments = blendAttachmentStates.data();
+		shaderStages[0] = loadShader(getShadersPath() + "hdr/gbuffer.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "hdr/gbuffer.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		// Set constant parameters via specialization constants
+		specializationMapEntries[0] = vks::initializers::specializationMapEntry(0, 0, sizeof(uint32_t));
+		uint32_t shadertype = 0;
+		specializationInfo = vks::initializers::specializationInfo(1, specializationMapEntries.data(), sizeof(shadertype), &shadertype);
+		shaderStages[0].pSpecializationInfo = &specializationInfo;
+		shaderStages[1].pSpecializationInfo = &specializationInfo;
+		// Skybox pipeline (background cube)
+		rasterizationState.cullMode = VK_CULL_MODE_FRONT_BIT;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.skybox));
+
+		// Object rendering pipeline
+		shadertype = 1;
+		// Enable depth test and write
+		depthStencilState.depthWriteEnable = VK_TRUE;
+		depthStencilState.depthTestEnable = VK_TRUE;
+		// Flip cull mode
+		rasterizationState.cullMode = VK_CULL_MODE_BACK_BIT;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.reflect));
+	}
+
+	// Prepare and initialize uniform buffer containing shader uniforms
+	void prepareUniformBuffers()
+	{
+		// Matrices vertex shader uniform buffer
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.matrices,
+			sizeof(uboVS)));
+
+		// Params
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.params,
+			sizeof(uboParams)));
+
+		// Map persistent
+		VK_CHECK_RESULT(uniformBuffers.matrices.map());
+		VK_CHECK_RESULT(uniformBuffers.params.map());
+
+		updateUniformBuffers();
+		updateParams();
+	}
+
+	void updateUniformBuffers()
+	{
+		uboVS.projection = camera.matrices.perspective;
+		uboVS.modelview = camera.matrices.view;
+		uboVS.inverseModelview = glm::inverse(camera.matrices.view);
+		memcpy(uniformBuffers.matrices.mapped, &uboVS, sizeof(uboVS));
+	}
+
+	void updateParams()
+	{
+		memcpy(uniformBuffers.params.mapped, &uboParams, sizeof(uboParams));
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+		VulkanExampleBase::submitFrame();
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		loadAssets();
+		prepareUniformBuffers();
+		prepareoffscreenfer();
+		setupDescriptorSetLayout();
+		preparePipelines();
+		setupDescriptorPool();
+		setupDescriptorSets();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+		if (camera.updated)
+			updateUniformBuffers();
+	}
+
+	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
+	{
+		if (overlay->header("Settings")) {
+			if (overlay->comboBox("Object type", &models.objectIndex, objectNames)) {
+				updateUniformBuffers();
+				buildCommandBuffers();
+			}
+			if (overlay->inputFloat("Exposure", &uboParams.exposure, 0.025f, 3)) {
+				updateParams();
+			}
+			if (overlay->checkBox("Bloom", &bloom)) {
+				buildCommandBuffers();
+			}
+			if (overlay->checkBox("Skybox", &displaySkybox)) {
+				buildCommandBuffers();
+			}
+		}
+	}
+};
+
+VULKAN_EXAMPLE_MAIN()
diff --git a/external/Vulkan/examples/imgui/main.cpp b/external/Vulkan/examples/imgui/main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3f5f43824bdbb09240300116b94f2d9389fabe1d
--- /dev/null
+++ b/external/Vulkan/examples/imgui/main.cpp
@@ -0,0 +1,745 @@
+/*
+* Vulkan Example - imGui (https://github.com/ocornut/imgui)
+*
+* Copyright (C) 2017 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include <imgui.h>
+#include "vulkanexamplebase.h"
+#include "VulkanglTFModel.h"
+
+#define ENABLE_VALIDATION false
+
+// Options and values to display/toggle from the UI
+struct UISettings {
+	bool displayModels = true;
+	bool displayLogos = true;
+	bool displayBackground = true;
+	bool animateLight = false;
+	float lightSpeed = 0.25f;
+	std::array<float, 50> frameTimes{};
+	float frameTimeMin = 9999.0f, frameTimeMax = 0.0f;
+	float lightTimer = 0.0f;
+} uiSettings;
+
+// ----------------------------------------------------------------------------
+// ImGUI class
+// ----------------------------------------------------------------------------
+class ImGUI {
+private:
+	// Vulkan resources for rendering the UI
+	VkSampler sampler;
+	vks::Buffer vertexBuffer;
+	vks::Buffer indexBuffer;
+	int32_t vertexCount = 0;
+	int32_t indexCount = 0;
+	VkDeviceMemory fontMemory = VK_NULL_HANDLE;
+	VkImage fontImage = VK_NULL_HANDLE;
+	VkImageView fontView = VK_NULL_HANDLE;
+	VkPipelineCache pipelineCache;
+	VkPipelineLayout pipelineLayout;
+	VkPipeline pipeline;
+	VkDescriptorPool descriptorPool;
+	VkDescriptorSetLayout descriptorSetLayout;
+	VkDescriptorSet descriptorSet;
+	vks::VulkanDevice *device;
+	VulkanExampleBase *example;
+public:
+	// UI params are set via push constants
+	struct PushConstBlock {
+		glm::vec2 scale;
+		glm::vec2 translate;
+	} pushConstBlock;
+
+	ImGUI(VulkanExampleBase *example) : example(example)
+	{
+		device = example->vulkanDevice;
+		ImGui::CreateContext();
+	};
+
+	~ImGUI()
+	{
+		ImGui::DestroyContext();
+		// Release all Vulkan resources required for rendering imGui
+		vertexBuffer.destroy();
+		indexBuffer.destroy();
+		vkDestroyImage(device->logicalDevice, fontImage, nullptr);
+		vkDestroyImageView(device->logicalDevice, fontView, nullptr);
+		vkFreeMemory(device->logicalDevice, fontMemory, nullptr);
+		vkDestroySampler(device->logicalDevice, sampler, nullptr);
+		vkDestroyPipelineCache(device->logicalDevice, pipelineCache, nullptr);
+		vkDestroyPipeline(device->logicalDevice, pipeline, nullptr);
+		vkDestroyPipelineLayout(device->logicalDevice, pipelineLayout, nullptr);
+		vkDestroyDescriptorPool(device->logicalDevice, descriptorPool, nullptr);
+		vkDestroyDescriptorSetLayout(device->logicalDevice, descriptorSetLayout, nullptr);
+	}
+
+	// Initialize styles, keys, etc.
+	void init(float width, float height)
+	{
+		// Color scheme
+		ImGuiStyle& style = ImGui::GetStyle();
+		style.Colors[ImGuiCol_TitleBg] = ImVec4(1.0f, 0.0f, 0.0f, 0.6f);
+		style.Colors[ImGuiCol_TitleBgActive] = ImVec4(1.0f, 0.0f, 0.0f, 0.8f);
+		style.Colors[ImGuiCol_MenuBarBg] = ImVec4(1.0f, 0.0f, 0.0f, 0.4f);
+		style.Colors[ImGuiCol_Header] = ImVec4(1.0f, 0.0f, 0.0f, 0.4f);
+		style.Colors[ImGuiCol_CheckMark] = ImVec4(0.0f, 1.0f, 0.0f, 1.0f);
+		// Dimensions
+		ImGuiIO& io = ImGui::GetIO();
+		io.DisplaySize = ImVec2(width, height);
+		io.DisplayFramebufferScale = ImVec2(1.0f, 1.0f);
+	}
+
+	// Initialize all Vulkan resources used by the ui
+	void initResources(VkRenderPass renderPass, VkQueue copyQueue, const std::string& shadersPath)
+	{
+		ImGuiIO& io = ImGui::GetIO();
+
+		// Create font texture
+		unsigned char* fontData;
+		int texWidth, texHeight;
+		io.Fonts->GetTexDataAsRGBA32(&fontData, &texWidth, &texHeight);
+		VkDeviceSize uploadSize = texWidth*texHeight * 4 * sizeof(char);
+
+		// Create target image for copy
+		VkImageCreateInfo imageInfo = vks::initializers::imageCreateInfo();
+		imageInfo.imageType = VK_IMAGE_TYPE_2D;
+		imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
+		imageInfo.extent.width = texWidth;
+		imageInfo.extent.height = texHeight;
+		imageInfo.extent.depth = 1;
+		imageInfo.mipLevels = 1;
+		imageInfo.arrayLayers = 1;
+		imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
+		imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
+		imageInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
+		imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+		imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+		VK_CHECK_RESULT(vkCreateImage(device->logicalDevice, &imageInfo, nullptr, &fontImage));
+		VkMemoryRequirements memReqs;
+		vkGetImageMemoryRequirements(device->logicalDevice, fontImage, &memReqs);
+		VkMemoryAllocateInfo memAllocInfo = vks::initializers::memoryAllocateInfo();
+		memAllocInfo.allocationSize = memReqs.size;
+		memAllocInfo.memoryTypeIndex = device->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+		VK_CHECK_RESULT(vkAllocateMemory(device->logicalDevice, &memAllocInfo, nullptr, &fontMemory));
+		VK_CHECK_RESULT(vkBindImageMemory(device->logicalDevice, fontImage, fontMemory, 0));
+
+		// Image view
+		VkImageViewCreateInfo viewInfo = vks::initializers::imageViewCreateInfo();
+		viewInfo.image = fontImage;
+		viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
+		viewInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
+		viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+		viewInfo.subresourceRange.levelCount = 1;
+		viewInfo.subresourceRange.layerCount = 1;
+		VK_CHECK_RESULT(vkCreateImageView(device->logicalDevice, &viewInfo, nullptr, &fontView));
+
+		// Staging buffers for font data upload
+		vks::Buffer stagingBuffer;
+
+		VK_CHECK_RESULT(device->createBuffer(
+			VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&stagingBuffer,
+			uploadSize));
+
+		stagingBuffer.map();
+		memcpy(stagingBuffer.mapped, fontData, uploadSize);
+		stagingBuffer.unmap();
+
+		// Copy buffer data to font image
+		VkCommandBuffer copyCmd = device->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+
+		// Prepare for transfer
+		vks::tools::setImageLayout(
+			copyCmd,
+			fontImage,
+			VK_IMAGE_ASPECT_COLOR_BIT,
+			VK_IMAGE_LAYOUT_UNDEFINED,
+			VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+			VK_PIPELINE_STAGE_HOST_BIT,
+			VK_PIPELINE_STAGE_TRANSFER_BIT);
+
+		// Copy
+		VkBufferImageCopy bufferCopyRegion = {};
+		bufferCopyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+		bufferCopyRegion.imageSubresource.layerCount = 1;
+		bufferCopyRegion.imageExtent.width = texWidth;
+		bufferCopyRegion.imageExtent.height = texHeight;
+		bufferCopyRegion.imageExtent.depth = 1;
+
+		vkCmdCopyBufferToImage(
+			copyCmd,
+			stagingBuffer.buffer,
+			fontImage,
+			VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+			1,
+			&bufferCopyRegion
+		);
+
+		// Prepare for shader read
+		vks::tools::setImageLayout(
+			copyCmd,
+			fontImage,
+			VK_IMAGE_ASPECT_COLOR_BIT,
+			VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+			VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
+			VK_PIPELINE_STAGE_TRANSFER_BIT,
+			VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);
+
+		device->flushCommandBuffer(copyCmd, copyQueue, true);
+
+		stagingBuffer.destroy();
+
+		// Font texture Sampler
+		VkSamplerCreateInfo samplerInfo = vks::initializers::samplerCreateInfo();
+		samplerInfo.magFilter = VK_FILTER_LINEAR;
+		samplerInfo.minFilter = VK_FILTER_LINEAR;
+		samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+		samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+		samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+		samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+		samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
+		VK_CHECK_RESULT(vkCreateSampler(device->logicalDevice, &samplerInfo, nullptr, &sampler));
+
+		// Descriptor pool
+		std::vector<VkDescriptorPoolSize> poolSizes = {
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1)
+		};
+		VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 2);
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device->logicalDevice, &descriptorPoolInfo, nullptr, &descriptorPool));
+
+		// Descriptor set layout
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0),
+		};
+		VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device->logicalDevice, &descriptorLayout, nullptr, &descriptorSetLayout));
+
+		// Descriptor set
+		VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1);
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device->logicalDevice, &allocInfo, &descriptorSet));
+		VkDescriptorImageInfo fontDescriptor = vks::initializers::descriptorImageInfo(
+			sampler,
+			fontView,
+			VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
+		);
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets = {
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &fontDescriptor)
+		};
+		vkUpdateDescriptorSets(device->logicalDevice, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr);
+
+		// Pipeline cache
+		VkPipelineCacheCreateInfo pipelineCacheCreateInfo = {};
+		pipelineCacheCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO;
+		VK_CHECK_RESULT(vkCreatePipelineCache(device->logicalDevice, &pipelineCacheCreateInfo, nullptr, &pipelineCache));
+
+		// Pipeline layout
+		// Push constants for UI rendering parameters
+		VkPushConstantRange pushConstantRange = vks::initializers::pushConstantRange(VK_SHADER_STAGE_VERTEX_BIT, sizeof(PushConstBlock), 0);
+		VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1);
+		pipelineLayoutCreateInfo.pushConstantRangeCount = 1;
+		pipelineLayoutCreateInfo.pPushConstantRanges = &pushConstantRange;
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device->logicalDevice, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout));
+
+		// Setup graphics pipeline for UI rendering
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState =
+			vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+
+		VkPipelineRasterizationStateCreateInfo rasterizationState =
+			vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE, VK_FRONT_FACE_COUNTER_CLOCKWISE);
+
+		// Enable blending
+		VkPipelineColorBlendAttachmentState blendAttachmentState{};
+		blendAttachmentState.blendEnable = VK_TRUE;
+		blendAttachmentState.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
+		blendAttachmentState.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
+		blendAttachmentState.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
+		blendAttachmentState.colorBlendOp = VK_BLEND_OP_ADD;
+		blendAttachmentState.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
+		blendAttachmentState.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
+		blendAttachmentState.alphaBlendOp = VK_BLEND_OP_ADD;
+
+		VkPipelineColorBlendStateCreateInfo colorBlendState =
+			vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+
+		VkPipelineDepthStencilStateCreateInfo depthStencilState =
+			vks::initializers::pipelineDepthStencilStateCreateInfo(VK_FALSE, VK_FALSE, VK_COMPARE_OP_LESS_OR_EQUAL);
+
+		VkPipelineViewportStateCreateInfo viewportState =
+			vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+
+		VkPipelineMultisampleStateCreateInfo multisampleState =
+			vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT);
+
+		std::vector<VkDynamicState> dynamicStateEnables = {
+			VK_DYNAMIC_STATE_VIEWPORT,
+			VK_DYNAMIC_STATE_SCISSOR
+		};
+		VkPipelineDynamicStateCreateInfo dynamicState =
+			vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
+
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages{};
+
+		VkGraphicsPipelineCreateInfo pipelineCreateInfo = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass);
+
+		pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState;
+		pipelineCreateInfo.pRasterizationState = &rasterizationState;
+		pipelineCreateInfo.pColorBlendState = &colorBlendState;
+		pipelineCreateInfo.pMultisampleState = &multisampleState;
+		pipelineCreateInfo.pViewportState = &viewportState;
+		pipelineCreateInfo.pDepthStencilState = &depthStencilState;
+		pipelineCreateInfo.pDynamicState = &dynamicState;
+		pipelineCreateInfo.stageCount = static_cast<uint32_t>(shaderStages.size());
+		pipelineCreateInfo.pStages = shaderStages.data();
+
+		// Vertex bindings an attributes based on ImGui vertex definition
+		std::vector<VkVertexInputBindingDescription> vertexInputBindings = {
+			vks::initializers::vertexInputBindingDescription(0, sizeof(ImDrawVert), VK_VERTEX_INPUT_RATE_VERTEX),
+		};
+		std::vector<VkVertexInputAttributeDescription> vertexInputAttributes = {
+			vks::initializers::vertexInputAttributeDescription(0, 0, VK_FORMAT_R32G32_SFLOAT, offsetof(ImDrawVert, pos)),	// Location 0: Position
+			vks::initializers::vertexInputAttributeDescription(0, 1, VK_FORMAT_R32G32_SFLOAT, offsetof(ImDrawVert, uv)),	// Location 1: UV
+			vks::initializers::vertexInputAttributeDescription(0, 2, VK_FORMAT_R8G8B8A8_UNORM, offsetof(ImDrawVert, col)),	// Location 0: Color
+		};
+		VkPipelineVertexInputStateCreateInfo vertexInputState = vks::initializers::pipelineVertexInputStateCreateInfo();
+		vertexInputState.vertexBindingDescriptionCount = static_cast<uint32_t>(vertexInputBindings.size());
+		vertexInputState.pVertexBindingDescriptions = vertexInputBindings.data();
+		vertexInputState.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertexInputAttributes.size());
+		vertexInputState.pVertexAttributeDescriptions = vertexInputAttributes.data();
+
+		pipelineCreateInfo.pVertexInputState = &vertexInputState;
+
+		shaderStages[0] = example->loadShader(shadersPath + "imgui/ui.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = example->loadShader(shadersPath + "imgui/ui.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device->logicalDevice, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipeline));
+	}
+
+	// Starts a new imGui frame and sets up windows and ui elements
+	void newFrame(VulkanExampleBase *example, bool updateFrameGraph)
+	{
+		ImGui::NewFrame();
+
+		// Init imGui windows and elements
+
+		ImVec4 clear_color = ImColor(114, 144, 154);
+		static float f = 0.0f;
+		ImGui::TextUnformatted(example->title.c_str());
+		ImGui::TextUnformatted(device->properties.deviceName);
+
+		// Update frame time display
+		if (updateFrameGraph) {
+			std::rotate(uiSettings.frameTimes.begin(), uiSettings.frameTimes.begin() + 1, uiSettings.frameTimes.end());
+			float frameTime = 1000.0f / (example->frameTimer * 1000.0f);
+			uiSettings.frameTimes.back() = frameTime;
+			if (frameTime < uiSettings.frameTimeMin) {
+				uiSettings.frameTimeMin = frameTime;
+			}
+			if (frameTime > uiSettings.frameTimeMax) {
+				uiSettings.frameTimeMax = frameTime;
+			}
+		}
+
+		ImGui::PlotLines("Frame Times", &uiSettings.frameTimes[0], 50, 0, "", uiSettings.frameTimeMin, uiSettings.frameTimeMax, ImVec2(0, 80));
+
+		ImGui::Text("Camera");
+		ImGui::InputFloat3("position", &example->camera.position.x, 2);
+		ImGui::InputFloat3("rotation", &example->camera.rotation.x, 2);
+
+		ImGui::SetNextWindowSize(ImVec2(200, 200), ImGuiSetCond_FirstUseEver);
+		ImGui::Begin("Example settings");
+		ImGui::Checkbox("Render models", &uiSettings.displayModels);
+		ImGui::Checkbox("Display logos", &uiSettings.displayLogos);
+		ImGui::Checkbox("Display background", &uiSettings.displayBackground);
+		ImGui::Checkbox("Animate light", &uiSettings.animateLight);
+		ImGui::SliderFloat("Light speed", &uiSettings.lightSpeed, 0.1f, 1.0f);
+		ImGui::End();
+
+		ImGui::SetNextWindowPos(ImVec2(650, 20), ImGuiSetCond_FirstUseEver);
+		ImGui::ShowDemoWindow();
+
+		// Render to generate draw buffers
+		ImGui::Render();
+	}
+
+	// Update vertex and index buffer containing the imGui elements when required
+	void updateBuffers()
+	{
+		ImDrawData* imDrawData = ImGui::GetDrawData();
+
+		// Note: Alignment is done inside buffer creation
+		VkDeviceSize vertexBufferSize = imDrawData->TotalVtxCount * sizeof(ImDrawVert);
+		VkDeviceSize indexBufferSize = imDrawData->TotalIdxCount * sizeof(ImDrawIdx);
+
+		if ((vertexBufferSize == 0) || (indexBufferSize == 0)) {
+			return;
+		}
+
+		// Update buffers only if vertex or index count has been changed compared to current buffer size
+
+		// Vertex buffer
+		if ((vertexBuffer.buffer == VK_NULL_HANDLE) || (vertexCount != imDrawData->TotalVtxCount)) {
+			vertexBuffer.unmap();
+			vertexBuffer.destroy();
+			VK_CHECK_RESULT(device->createBuffer(VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, &vertexBuffer, vertexBufferSize));
+			vertexCount = imDrawData->TotalVtxCount;
+			vertexBuffer.map();
+		}
+
+		// Index buffer
+		if ((indexBuffer.buffer == VK_NULL_HANDLE) || (indexCount < imDrawData->TotalIdxCount)) {
+			indexBuffer.unmap();
+			indexBuffer.destroy();
+			VK_CHECK_RESULT(device->createBuffer(VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, &indexBuffer, indexBufferSize));
+			indexCount = imDrawData->TotalIdxCount;
+			indexBuffer.map();
+		}
+
+		// Upload data
+		ImDrawVert* vtxDst = (ImDrawVert*)vertexBuffer.mapped;
+		ImDrawIdx* idxDst = (ImDrawIdx*)indexBuffer.mapped;
+
+		for (int n = 0; n < imDrawData->CmdListsCount; n++) {
+			const ImDrawList* cmd_list = imDrawData->CmdLists[n];
+			memcpy(vtxDst, cmd_list->VtxBuffer.Data, cmd_list->VtxBuffer.Size * sizeof(ImDrawVert));
+			memcpy(idxDst, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx));
+			vtxDst += cmd_list->VtxBuffer.Size;
+			idxDst += cmd_list->IdxBuffer.Size;
+		}
+
+		// Flush to make writes visible to GPU
+		vertexBuffer.flush();
+		indexBuffer.flush();
+	}
+
+	// Draw current imGui frame into a command buffer
+	void drawFrame(VkCommandBuffer commandBuffer)
+	{
+		ImGuiIO& io = ImGui::GetIO();
+
+		vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr);
+		vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
+
+		VkViewport viewport = vks::initializers::viewport(ImGui::GetIO().DisplaySize.x, ImGui::GetIO().DisplaySize.y, 0.0f, 1.0f);
+		vkCmdSetViewport(commandBuffer, 0, 1, &viewport);
+
+		// UI scale and translate via push constants
+		pushConstBlock.scale = glm::vec2(2.0f / io.DisplaySize.x, 2.0f / io.DisplaySize.y);
+		pushConstBlock.translate = glm::vec2(-1.0f);
+		vkCmdPushConstants(commandBuffer, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(PushConstBlock), &pushConstBlock);
+
+		// Render commands
+		ImDrawData* imDrawData = ImGui::GetDrawData();
+		int32_t vertexOffset = 0;
+		int32_t indexOffset = 0;
+
+		if (imDrawData->CmdListsCount > 0) {
+
+			VkDeviceSize offsets[1] = { 0 };
+			vkCmdBindVertexBuffers(commandBuffer, 0, 1, &vertexBuffer.buffer, offsets);
+			vkCmdBindIndexBuffer(commandBuffer, indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT16);
+
+			for (int32_t i = 0; i < imDrawData->CmdListsCount; i++)
+			{
+				const ImDrawList* cmd_list = imDrawData->CmdLists[i];
+				for (int32_t j = 0; j < cmd_list->CmdBuffer.Size; j++)
+				{
+					const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[j];
+					VkRect2D scissorRect;
+					scissorRect.offset.x = std::max((int32_t)(pcmd->ClipRect.x), 0);
+					scissorRect.offset.y = std::max((int32_t)(pcmd->ClipRect.y), 0);
+					scissorRect.extent.width = (uint32_t)(pcmd->ClipRect.z - pcmd->ClipRect.x);
+					scissorRect.extent.height = (uint32_t)(pcmd->ClipRect.w - pcmd->ClipRect.y);
+					vkCmdSetScissor(commandBuffer, 0, 1, &scissorRect);
+					vkCmdDrawIndexed(commandBuffer, pcmd->ElemCount, 1, indexOffset, vertexOffset, 0);
+					indexOffset += pcmd->ElemCount;
+				}
+				vertexOffset += cmd_list->VtxBuffer.Size;
+			}
+		}
+	}
+
+};
+
+// ----------------------------------------------------------------------------
+// VulkanExample
+// ----------------------------------------------------------------------------
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	ImGUI *imGui = nullptr;
+
+	struct Models {
+		vkglTF::Model models;
+		vkglTF::Model logos;
+		vkglTF::Model background;
+	} models;
+
+	vks::Buffer uniformBufferVS;
+
+	struct UBOVS {
+		glm::mat4 projection;
+		glm::mat4 modelview;
+		glm::vec4 lightPos;
+	} uboVS;
+
+	VkPipelineLayout pipelineLayout;
+	VkPipeline pipeline;
+	VkDescriptorSetLayout descriptorSetLayout;
+	VkDescriptorSet descriptorSet;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Vulkan Example - ImGui";
+		camera.type = Camera::CameraType::lookat;
+		camera.setPosition(glm::vec3(0.0f, 0.0f, -4.8f));
+		camera.setRotation(glm::vec3(4.5f, -380.0f, 0.0f));
+		camera.setPerspective(45.0f, (float)width / (float)height, 0.1f, 256.0f);
+		// Don't use the ImGui overlay of the base framework in this sample
+		settings.overlay = false;
+	}
+
+	~VulkanExample()
+	{
+		vkDestroyPipeline(device, pipeline, nullptr);
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+
+		uniformBufferVS.destroy();
+
+		delete imGui;
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		clearValues[0].color = { { 0.2f, 0.2f, 0.2f, 1.0f} };
+		clearValues[1].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.offset.x = 0;
+		renderPassBeginInfo.renderArea.offset.y = 0;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		renderPassBeginInfo.clearValueCount = 2;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		imGui->newFrame(this, (frameCounter == 0));
+
+		imGui->updateBuffers();
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			// Set target frame buffer
+			renderPassBeginInfo.framebuffer = frameBuffers[i];
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+			VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+			// Render scene
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr);
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
+
+			VkDeviceSize offsets[1] = { 0 };
+			if (uiSettings.displayBackground) {
+				models.background.draw(drawCmdBuffers[i]);
+			}
+
+			if (uiSettings.displayModels) {
+				models.models.draw(drawCmdBuffers[i]);
+			}
+
+			if (uiSettings.displayLogos) {
+				models.logos.draw(drawCmdBuffers[i]);
+			}
+
+			// Render imGui
+			imGui->drawFrame(drawCmdBuffers[i]);
+
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void setupLayoutsAndDescriptors()
+	{
+		// descriptor pool
+		std::vector<VkDescriptorPoolSize> poolSizes = {
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2),
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1)
+		};
+		VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 2);
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+
+		// Set layout
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0),
+		};
+		VkDescriptorSetLayoutCreateInfo descriptorLayout =
+			vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout));
+
+		// Pipeline layout
+		VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1);
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayout));
+
+		// Descriptor set
+		VkDescriptorSetAllocateInfo allocInfo =	vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1);
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet));
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets = {
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBufferVS.descriptor),
+		};
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr);
+	}
+
+	void preparePipelines()
+	{
+		// Rendering
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0,	VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+		VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT);
+		std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
+		VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
+
+		std::array<VkPipelineShaderStageCreateInfo,2> shaderStages;
+
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass);
+		pipelineCI.pInputAssemblyState = &inputAssemblyState;
+		pipelineCI.pRasterizationState = &rasterizationState;
+		pipelineCI.pColorBlendState = &colorBlendState;
+		pipelineCI.pMultisampleState = &multisampleState;
+		pipelineCI.pViewportState = &viewportState;
+		pipelineCI.pDepthStencilState = &depthStencilState;
+		pipelineCI.pDynamicState = &dynamicState;
+		pipelineCI.stageCount = static_cast<uint32_t>(shaderStages.size());
+		pipelineCI.pStages = shaderStages.data();
+		pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::Color });;
+
+		shaderStages[0] = loadShader(getShadersPath() + "imgui/scene.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "imgui/scene.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipeline));
+	}
+
+	// Prepare and initialize uniform buffer containing shader uniforms
+	void prepareUniformBuffers()
+	{
+		// Vertex shader uniform buffer block
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBufferVS,
+			sizeof(uboVS),
+			&uboVS));
+
+		updateUniformBuffers();
+	}
+
+	void updateUniformBuffers()
+	{
+		// Vertex shader
+		uboVS.projection = camera.matrices.perspective;
+		uboVS.modelview = camera.matrices.view * glm::mat4(1.0f);
+
+		// Light source
+		if (uiSettings.animateLight) {
+			uiSettings.lightTimer += frameTimer * uiSettings.lightSpeed;
+			uboVS.lightPos.x = sin(glm::radians(uiSettings.lightTimer * 360.0f)) * 15.0f;
+			uboVS.lightPos.z = cos(glm::radians(uiSettings.lightTimer * 360.0f)) * 15.0f;
+		};
+
+		VK_CHECK_RESULT(uniformBufferVS.map());
+		memcpy(uniformBufferVS.mapped, &uboVS, sizeof(uboVS));
+		uniformBufferVS.unmap();
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+		buildCommandBuffers();
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+		VulkanExampleBase::submitFrame();
+	}
+
+	void loadAssets()
+	{
+		const uint32_t glTFLoadingFlags = vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::PreMultiplyVertexColors | vkglTF::FileLoadingFlags::FlipY;
+		models.models.loadFromFile(getAssetPath() + "models/vulkanscenemodels.gltf", vulkanDevice, queue, glTFLoadingFlags);
+		models.background.loadFromFile(getAssetPath() + "models/vulkanscenebackground.gltf", vulkanDevice, queue, glTFLoadingFlags);
+		models.logos.loadFromFile(getAssetPath() + "models/vulkanscenelogos.gltf", vulkanDevice, queue, glTFLoadingFlags);
+	}
+
+	void prepareImGui()
+	{
+		imGui = new ImGUI(this);
+		imGui->init((float)width, (float)height);
+		imGui->initResources(renderPass, queue, getShadersPath());
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		loadAssets();
+		prepareUniformBuffers();
+		setupLayoutsAndDescriptors();
+		preparePipelines();
+		prepareImGui();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+
+		// Update imGui
+		ImGuiIO& io = ImGui::GetIO();
+
+		io.DisplaySize = ImVec2((float)width, (float)height);
+		io.DeltaTime = frameTimer;
+
+		io.MousePos = ImVec2(mousePos.x, mousePos.y);
+		io.MouseDown[0] = mouseButtons.left;
+		io.MouseDown[1] = mouseButtons.right;
+
+		draw();
+
+		if (uiSettings.animateLight)
+			updateUniformBuffers();
+	}
+
+	virtual void viewChanged()
+	{
+		updateUniformBuffers();
+	}
+
+	virtual void mouseMoved(double x, double y, bool &handled)
+	{
+		ImGuiIO& io = ImGui::GetIO();
+		handled = io.WantCaptureMouse;
+	}
+
+};
+
+VULKAN_EXAMPLE_MAIN()
diff --git a/external/Vulkan/examples/indirectdraw/README.md b/external/Vulkan/examples/indirectdraw/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..3fdcd9afa6f97ef754e0fc03315984d120733b9b
--- /dev/null
+++ b/external/Vulkan/examples/indirectdraw/README.md
@@ -0,0 +1,109 @@
+# Indirect drawing
+
+<img src="../../screenshots/indirectdraw.jpg" height="256px">
+
+## Synopsis
+
+Issue multiple instanced draws for different meshes in one single draw call using indirect draw commands.
+
+## Requirements
+If the [`multiDrawIndirect`](http://vulkan.gpuinfo.org/listreports.php?feature=multiDrawIndirect) feature is supported, only one draw call is issued for the plants. If this feature is not available multiple indirect draw commands are used. ***Note:*** When issuing many draw counts also make sure to stay within the limitations of [`maxDrawIndirectCount`](http://vulkan.gpuinfo.org/listreports.php?limit=maxDrawIndirectCount).
+
+## Description
+
+This example demonstrates the use of indirect draw commands. In addition to draw functions like [`vkCmdDraw`](https://www.khronos.org/registry/vulkan/specs/1.0/man/html/vkCmdDraw.html) and [`vkCmdDrawIndexed`](https://www.khronos.org/registry/vulkan/specs/1.0/man/html/vkCmdDrawIndexed.html), where the parameters that specify what is drawn are passed directly to the function ("direct drawing"), there also exist indirect drawing commands.
+
+[`vkCmdDrawIndirect`](https://www.khronos.org/registry/vulkan/specs/1.0/man/html/vkCmdDrawIndirect.html) and [`vkCmdDrawIndexedIndirect`](https://www.khronos.org/registry/vulkan/specs/1.0/man/html/vkCmdDrawIndexedIndirect.html) take the draw commands from a buffer object that contains descriptions on the draw commands to be issued, including instance and index counts, vertex offsets, etc. This also allows to draw multiple geometries with a single draw command as long as they're backed up by the same vertex (and index) buffer.
+
+This adds several new possibilities of generating (and updating) actual draw commands, as that buffer can be generated and updated offline with no need to actually update the command buffers that contain the actual drawing functions.
+
+Using indirect drawing you can generate the draw commands offline ahead of time on the CPU and even update them using shaders (as they're stored in a device local buffer). This adds lots of new possibilities to update draw commands without the CPU being involved, including GPU-based culling.
+
+The example generates a single indirect buffer that contains draw commands for 12 different plants at random position, scale and rotation also using instancing to render the objects multiple times. The whole foliage (and trees) seen in the screen are drawn using only one draw call.
+
+The different plant meshes are loaded from a single file and stored inside a single index and vertex buffer, index offsets are stored inside the indirect draw commands.
+
+For details on the use of instancing (and instanced vertex attributes), see the [instancing example](../instancing)
+
+## Points of interest
+
+### Preparing the indirect draw
+The example generates the indirect drawing buffer right at the start. First step is to generate the data for the indirect draws. Vulkan has a dedicated struct for this called `VkDrawIndexedIndirectCommand` and the example uses a `std::vector<VkDrawIndexedIndirectCommand>` to store these before uploading them to the GPU:
+```cpp
+void prepareIndirectData()
+{
+  ...
+  uint32_t m = 0;
+  for (auto& meshDescriptor : meshes.plants.meshDescriptors)
+  {
+    VkDrawIndexedIndirectCommand indirectCmd{};
+    indirectCmd.instanceCount = OBJECT_INSTANCE_COUNT;
+    indirectCmd.firstInstance = m * OBJECT_INSTANCE_COUNT;
+    indirectCmd.firstIndex = meshDescriptor.indexBase;
+    indirectCmd.indexCount = meshDescriptor.indexCount;
+    indirectCommands.push_back(indirectCmd);
+    m++;
+  }
+  ...
+}
+```
+The meshDescriptor is generated by the mesh loader and contains the index base and count of that mesh inside the global index buffer containing all plant meshes for the scenery.
+
+The indirect draw command for the second plant mesh looks like this:
+```cpp
+indirectCmd.indexCount = 1668;
+indirectCmd.instanceCount = 2048;
+indirectCmd.firstIndex = 960;
+indirectCmd.vertexOffset = 0;
+indirectCmd.firstInstance = 2048;
+```
+Which will result in 2048 instances of the index data starting at index 960 (using 1668 indices) being drawn, the first instance is also important as the shader is using it for the instanced attributes of that object (position, rotation, scale).
+
+Once we have filled that vector we need to create the buffer that the GPU uses to read the indirect commands from:
+
+```cpp
+VK_CHECK_RESULT(vulkanDevice->createBuffer(
+  VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
+  VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
+  &indirectCommandsBuffer,
+  stagingBuffer.size));
+
+vulkanDevice->copyBuffer(&stagingBuffer, &indirectCommandsBuffer, queue);
+```
+To use a buffer for indirect draw commands you need to specify the ```VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT``` usage flag at creation time. As the buffer is never again changed on the host side we stage it to the GPU to maximize performance.
+
+### Rendering
+If the [`multiDrawIndirect`](http://vulkan.gpuinfo.org/listreports.php?feature=multiDrawIndirect) is supported, we can issue all indirect draws with one single draw call:
+```cpp
+void buildCommandBuffers()
+{
+  ...
+  for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+  {
+    vkCmdDrawIndexedIndirect(drawCmdBuffers[i], indirectCommandsBuffer.buffer, 0, indirectDrawCount, sizeof(VkDrawIndexedIndirectCommand));
+  }
+}
+```
+We just pass the buffer handle to the buffer containing the indirect draw commands and the number of drawCounts.
+
+The non-indirect, non-instanced equivalent of this would be:
+```cpp
+for (auto indirectCmd : indirectCommands)
+  {
+    for (uint32_t j = 0; j < indirectCmd.instanceCount; j++)
+    {
+      vkCmdDrawIndexed(drawCmdBuffers[i], indirectCmd.indexCount, 1, indirectCmd.firstIndex, 0, indirectCmd.firstInstance + j);
+    }
+  }
+```
+
+If the GPU does not support ```multiDrawIndirect``` we have to issue the indirect draw commands one-by-one using a buffer offset instead:
+```cpp
+for (auto j = 0; j < indirectCommands.size(); j++)
+{
+  vkCmdDrawIndexedIndirect(drawCmdBuffers[i], indirectCommandsBuffer.buffer, j * sizeof(VkDrawIndexedIndirectCommand), 1, sizeof(VkDrawIndexedIndirectCommand));
+}
+```
+
+### Acknowledgments
+- Plant and foliage models by [Hugues Muller](http://www.yughues-folio.com/)
diff --git a/external/Vulkan/examples/indirectdraw/indirectdraw.cpp b/external/Vulkan/examples/indirectdraw/indirectdraw.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c5e373a99c22a169557e9617196559fcd89530ac
--- /dev/null
+++ b/external/Vulkan/examples/indirectdraw/indirectdraw.cpp
@@ -0,0 +1,515 @@
+/*
+* Vulkan Example - Indirect drawing
+*
+* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*
+* Summary:
+* Use a device local buffer that stores draw commands for instanced rendering of different meshes stored
+* in the same buffer.
+*
+* Indirect drawing offloads draw command generation and offers the ability to update them on the GPU
+* without the CPU having to touch the buffer again, also reducing the number of drawcalls.
+*
+* The example shows how to setup and fill such a buffer on the CPU side, stages it to the device and
+* shows how to render it using only one draw command.
+*
+* See readme.md for details
+*
+*/
+
+#include "vulkanexamplebase.h"
+#include "VulkanglTFModel.h"
+
+#define VERTEX_BUFFER_BIND_ID 0
+#define INSTANCE_BUFFER_BIND_ID 1
+#define ENABLE_VALIDATION false
+
+// Number of instances per object
+#if defined(__ANDROID__)
+#define OBJECT_INSTANCE_COUNT 1024
+// Circular range of plant distribution
+#define PLANT_RADIUS 20.0f
+#else
+#define OBJECT_INSTANCE_COUNT 2048
+// Circular range of plant distribution
+#define PLANT_RADIUS 25.0f
+#endif
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	struct {
+		vks::Texture2DArray plants;
+		vks::Texture2D ground;
+	} textures;
+
+	struct {
+		vkglTF::Model plants;
+		vkglTF::Model ground;
+		vkglTF::Model skysphere;
+	} models;
+
+	// Per-instance data block
+	struct InstanceData {
+		glm::vec3 pos;
+		glm::vec3 rot;
+		float scale;
+		uint32_t texIndex;
+	};
+
+	// Contains the instanced data
+	vks::Buffer instanceBuffer;
+	// Contains the indirect drawing commands
+	vks::Buffer indirectCommandsBuffer;
+	uint32_t indirectDrawCount;
+
+	struct {
+		glm::mat4 projection;
+		glm::mat4 view;
+	} uboVS;
+
+	struct {
+		vks::Buffer scene;
+	} uniformData;
+
+	struct {
+		VkPipeline plants;
+		VkPipeline ground;
+		VkPipeline skysphere;
+	} pipelines;
+
+	VkPipelineLayout pipelineLayout;
+	VkDescriptorSet descriptorSet;
+	VkDescriptorSetLayout descriptorSetLayout;
+
+	VkSampler samplerRepeat;
+
+	uint32_t objectCount = 0;
+
+	// Store the indirect draw commands containing index offsets and instance count per object
+	std::vector<VkDrawIndexedIndirectCommand> indirectCommands;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Indirect rendering";
+		camera.type = Camera::CameraType::firstperson;
+		camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 512.0f);
+		camera.setRotation(glm::vec3(-12.0f, 159.0f, 0.0f));
+		camera.setTranslation(glm::vec3(0.4f, 1.25f, 0.0f));
+		camera.movementSpeed = 5.0f;
+	}
+
+	~VulkanExample()
+	{
+		vkDestroyPipeline(device, pipelines.plants, nullptr);
+		vkDestroyPipeline(device, pipelines.ground, nullptr);
+		vkDestroyPipeline(device, pipelines.skysphere, nullptr);
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+		textures.plants.destroy();
+		textures.ground.destroy();
+		instanceBuffer.destroy();
+		indirectCommandsBuffer.destroy();
+		uniformData.scene.destroy();
+	}
+
+	// Enable physical device features required for this example
+	virtual void getEnabledFeatures()
+	{
+		// Example uses multi draw indirect if available
+		if (deviceFeatures.multiDrawIndirect) {
+			enabledFeatures.multiDrawIndirect = VK_TRUE;
+		}
+		// Enable anisotropic filtering if supported
+		if (deviceFeatures.samplerAnisotropy) {
+			enabledFeatures.samplerAnisotropy = VK_TRUE;
+		}
+	};
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		clearValues[0].color = { { 0.18f, 0.27f, 0.5f, 0.0f } };
+		clearValues[1].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		renderPassBeginInfo.clearValueCount = 2;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			// Set target frame buffer
+			renderPassBeginInfo.framebuffer = frameBuffers[i];
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+			VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+			VkDeviceSize offsets[1] = { 0 };
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL);
+
+			// Skysphere
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.skysphere);
+			models.skysphere.draw(drawCmdBuffers[i]);
+			// Ground
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.ground);
+			models.ground.draw(drawCmdBuffers[i]);
+
+			// [POI] Instanced multi draw rendering of the plants
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.plants);
+			// Binding point 0 : Mesh vertex buffer
+			vkCmdBindVertexBuffers(drawCmdBuffers[i], VERTEX_BUFFER_BIND_ID, 1, &models.plants.vertices.buffer, offsets);
+			// Binding point 1 : Instance data buffer
+			vkCmdBindVertexBuffers(drawCmdBuffers[i], INSTANCE_BUFFER_BIND_ID, 1, &instanceBuffer.buffer, offsets);
+
+			vkCmdBindIndexBuffer(drawCmdBuffers[i], models.plants.indices.buffer, 0, VK_INDEX_TYPE_UINT32);
+
+			// If the multi draw feature is supported:
+			// One draw call for an arbitrary number of objects
+			// Index offsets and instance count are taken from the indirect buffer
+			if (vulkanDevice->features.multiDrawIndirect)
+			{
+				vkCmdDrawIndexedIndirect(drawCmdBuffers[i], indirectCommandsBuffer.buffer, 0, indirectDrawCount, sizeof(VkDrawIndexedIndirectCommand));
+			}
+			else
+			{
+				// If multi draw is not available, we must issue separate draw commands
+				for (auto j = 0; j < indirectCommands.size(); j++)
+				{
+					vkCmdDrawIndexedIndirect(drawCmdBuffers[i], indirectCommandsBuffer.buffer, j * sizeof(VkDrawIndexedIndirectCommand), 1, sizeof(VkDrawIndexedIndirectCommand));
+				}
+			}
+
+			drawUI(drawCmdBuffers[i]);
+
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void loadAssets()
+	{
+		const uint32_t glTFLoadingFlags = vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::PreMultiplyVertexColors | vkglTF::FileLoadingFlags::FlipY;
+		models.plants.loadFromFile(getAssetPath() + "models/plants.gltf", vulkanDevice, queue, glTFLoadingFlags);
+		models.ground.loadFromFile(getAssetPath() + "models/plane_circle.gltf", vulkanDevice, queue, glTFLoadingFlags);
+		models.skysphere.loadFromFile(getAssetPath() + "models/sphere.gltf", vulkanDevice, queue, glTFLoadingFlags);
+		textures.plants.loadFromFile(getAssetPath() + "textures/texturearray_plants_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue);
+		textures.ground.loadFromFile(getAssetPath() + "textures/ground_dry_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue);
+	}
+
+	void setupDescriptorPool()
+	{
+		std::vector<VkDescriptorPoolSize> poolSizes = {
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1),
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2),
+		};
+
+		VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 2);
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+	}
+
+	void setupDescriptorSetLayout()
+	{
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			// Binding 0: Vertex shader uniform buffer
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0),
+			// Binding 1: Fragment shader combined sampler (plants texture array)
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1),
+			// Binding 1: Fragment shader combined sampler (ground texture)
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 2),
+		};
+
+		VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout));
+
+		VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1);
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayout));
+	}
+
+	void setupDescriptorSet()
+	{
+		VkDescriptorSetAllocateInfo allocInfo =vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1);
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet));
+
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets = {
+			// Binding 0: Vertex shader uniform buffer
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformData.scene.descriptor),
+			// Binding 1: Plants texture array combined
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &textures.plants.descriptor),
+			// Binding 2: Ground texture combined
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2, &textures.ground.descriptor)
+		};
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr);
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+		VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
+		std::vector<VkDynamicState> dynamicStateEnables = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
+		VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+
+		VkGraphicsPipelineCreateInfo pipelineCreateInfo = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass);
+		pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState;
+		pipelineCreateInfo.pRasterizationState = &rasterizationState;
+		pipelineCreateInfo.pColorBlendState = &colorBlendState;
+		pipelineCreateInfo.pMultisampleState = &multisampleState;
+		pipelineCreateInfo.pViewportState = &viewportState;
+		pipelineCreateInfo.pDepthStencilState = &depthStencilState;
+		pipelineCreateInfo.pDynamicState = &dynamicState;
+		pipelineCreateInfo.stageCount = static_cast<uint32_t>(shaderStages.size());
+		pipelineCreateInfo.pStages = shaderStages.data();
+
+		// This example uses two different input states, one for the instanced part and one for non-instanced rendering
+		VkPipelineVertexInputStateCreateInfo inputState = vks::initializers::pipelineVertexInputStateCreateInfo();
+		std::vector<VkVertexInputBindingDescription> bindingDescriptions;
+		std::vector<VkVertexInputAttributeDescription> attributeDescriptions;
+
+		// Vertex input bindings
+		// The instancing pipeline uses a vertex input state with two bindings
+		bindingDescriptions = {
+		    // Binding point 0: Mesh vertex layout description at per-vertex rate
+		    vks::initializers::vertexInputBindingDescription(VERTEX_BUFFER_BIND_ID, sizeof(vkglTF::Vertex), VK_VERTEX_INPUT_RATE_VERTEX),
+		    // Binding point 1: Instanced data at per-instance rate
+		    vks::initializers::vertexInputBindingDescription(INSTANCE_BUFFER_BIND_ID, sizeof(InstanceData), VK_VERTEX_INPUT_RATE_INSTANCE)
+		};
+
+		// Vertex attribute bindings
+		// Note that the shader declaration for per-vertex and per-instance attributes is the same, the different input rates are only stored in the bindings:
+		// instanced.vert:
+		//	layout (location = 0) in vec3 inPos;		Per-Vertex
+		//	...
+		//	layout (location = 4) in vec3 instancePos;	Per-Instance
+		attributeDescriptions = {
+		    // Per-vertex attributes
+		    // These are advanced for each vertex fetched by the vertex shader
+		    vks::initializers::vertexInputAttributeDescription(VERTEX_BUFFER_BIND_ID, 0, VK_FORMAT_R32G32B32_SFLOAT, 0),								// Location 0: Position
+		    vks::initializers::vertexInputAttributeDescription(VERTEX_BUFFER_BIND_ID, 1, VK_FORMAT_R32G32B32_SFLOAT, sizeof(float) * 3),				// Location 1: Normal
+		    vks::initializers::vertexInputAttributeDescription(VERTEX_BUFFER_BIND_ID, 2, VK_FORMAT_R32G32_SFLOAT, sizeof(float) * 6),					// Location 2: Texture coordinates
+		    vks::initializers::vertexInputAttributeDescription(VERTEX_BUFFER_BIND_ID, 3, VK_FORMAT_R32G32B32_SFLOAT, sizeof(float) * 8),				// Location 3: Color
+		    // Per-Instance attributes
+		    // These are fetched for each instance rendered
+		    vks::initializers::vertexInputAttributeDescription(INSTANCE_BUFFER_BIND_ID, 4, VK_FORMAT_R32G32B32_SFLOAT, offsetof(InstanceData, pos)),	// Location 4: Position
+		    vks::initializers::vertexInputAttributeDescription(INSTANCE_BUFFER_BIND_ID, 5, VK_FORMAT_R32G32B32_SFLOAT, offsetof(InstanceData, rot)),	// Location 5: Rotation
+		    vks::initializers::vertexInputAttributeDescription(INSTANCE_BUFFER_BIND_ID, 6, VK_FORMAT_R32_SFLOAT, offsetof(InstanceData, scale)),		// Location 6: Scale
+		    vks::initializers::vertexInputAttributeDescription(INSTANCE_BUFFER_BIND_ID, 7, VK_FORMAT_R32_SINT, offsetof(InstanceData, texIndex)),		// Location 7: Texture array layer index
+		};
+		inputState.pVertexBindingDescriptions = bindingDescriptions.data();
+		inputState.pVertexAttributeDescriptions = attributeDescriptions.data();
+		inputState.vertexBindingDescriptionCount   = static_cast<uint32_t>(bindingDescriptions.size());
+		inputState.vertexAttributeDescriptionCount = static_cast<uint32_t>(attributeDescriptions.size());
+
+		pipelineCreateInfo.pVertexInputState = &inputState;
+
+		// Indirect (and instanced) pipeline for the plants
+		shaderStages[0] = loadShader(getShadersPath() + "indirectdraw/indirectdraw.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "indirectdraw/indirectdraw.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipelines.plants));
+
+		// Only use non-instanced vertex attributes for models rendered without instancing
+		inputState.vertexBindingDescriptionCount = 1;
+		inputState.vertexAttributeDescriptionCount = 4;
+
+		// Ground
+		shaderStages[0] = loadShader(getShadersPath() + "indirectdraw/ground.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "indirectdraw/ground.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		rasterizationState.cullMode = VK_CULL_MODE_BACK_BIT;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipelines.ground));
+
+		// Skysphere
+		shaderStages[0] = loadShader(getShadersPath() + "indirectdraw/skysphere.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "indirectdraw/skysphere.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		depthStencilState.depthWriteEnable = VK_FALSE;
+		rasterizationState.cullMode = VK_CULL_MODE_FRONT_BIT;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipelines.skysphere));
+	}
+
+	// Prepare (and stage) a buffer containing the indirect draw commands
+	void prepareIndirectData()
+	{
+		indirectCommands.clear();
+
+		// Create on indirect command for node in the scene with a mesh attached to it
+		uint32_t m = 0;
+		for (auto &node : models.plants.nodes)
+		{
+			if (node->mesh)
+			{
+				VkDrawIndexedIndirectCommand indirectCmd{};
+				indirectCmd.instanceCount = OBJECT_INSTANCE_COUNT;
+				indirectCmd.firstInstance = m * OBJECT_INSTANCE_COUNT;
+				// @todo: Multiple primitives
+				// A glTF node may consist of multiple primitives, so we may have to do multiple commands per mesh
+				indirectCmd.firstIndex = node->mesh->primitives[0]->firstIndex;
+				indirectCmd.indexCount = node->mesh->primitives[0]->indexCount;
+
+				indirectCommands.push_back(indirectCmd);
+
+				m++;
+			}
+		}
+
+		indirectDrawCount = static_cast<uint32_t>(indirectCommands.size());
+
+		objectCount = 0;
+		for (auto indirectCmd : indirectCommands)
+		{
+			objectCount += indirectCmd.instanceCount;
+		}
+
+		vks::Buffer stagingBuffer;
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&stagingBuffer,
+			indirectCommands.size() * sizeof(VkDrawIndexedIndirectCommand),
+			indirectCommands.data()));
+
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
+			VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
+			&indirectCommandsBuffer,
+			stagingBuffer.size));
+
+		vulkanDevice->copyBuffer(&stagingBuffer, &indirectCommandsBuffer, queue);
+
+		stagingBuffer.destroy();
+	}
+
+	// Prepare (and stage) a buffer containing instanced data for the mesh draws
+	void prepareInstanceData()
+	{
+		std::vector<InstanceData> instanceData;
+		instanceData.resize(objectCount);
+
+		std::default_random_engine rndEngine(benchmark.active ? 0 : (unsigned)time(nullptr));
+		std::uniform_real_distribution<float> uniformDist(0.0f, 1.0f);
+
+		for (uint32_t i = 0; i < objectCount; i++) {
+			float theta = 2 * float(M_PI) * uniformDist(rndEngine);
+			float phi = acos(1 - 2 * uniformDist(rndEngine));
+			instanceData[i].rot = glm::vec3(0.0f, float(M_PI) * uniformDist(rndEngine), 0.0f);
+			instanceData[i].pos = glm::vec3(sin(phi) * cos(theta), 0.0f, cos(phi)) * PLANT_RADIUS;
+			instanceData[i].scale = 1.0f + uniformDist(rndEngine) * 2.0f;
+			instanceData[i].texIndex = i / OBJECT_INSTANCE_COUNT;
+		}
+
+		vks::Buffer stagingBuffer;
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&stagingBuffer,
+			instanceData.size() * sizeof(InstanceData),
+			instanceData.data()));
+
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
+			VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
+			&instanceBuffer,
+			stagingBuffer.size));
+
+		vulkanDevice->copyBuffer(&stagingBuffer, &instanceBuffer, queue);
+
+		stagingBuffer.destroy();
+	}
+
+	void prepareUniformBuffers()
+	{
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformData.scene,
+			sizeof(uboVS)));
+
+		VK_CHECK_RESULT(uniformData.scene.map());
+
+		updateUniformBuffer(true);
+	}
+
+	void updateUniformBuffer(bool viewChanged)
+	{
+		if (viewChanged)
+		{
+			uboVS.projection = camera.matrices.perspective;
+			uboVS.view = camera.matrices.view;
+		}
+
+		memcpy(uniformData.scene.mapped, &uboVS, sizeof(uboVS));
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+
+		// Command buffer to be submitted to the queue
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+
+		// Submit to queue
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+
+		VulkanExampleBase::submitFrame();
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		loadAssets();
+		prepareIndirectData();
+		prepareInstanceData();
+		prepareUniformBuffers();
+		setupDescriptorSetLayout();
+		preparePipelines();
+		setupDescriptorPool();
+		setupDescriptorSet();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+		{
+			return;
+		}
+		draw();
+		if (camera.updated)
+		{
+			updateUniformBuffer(true);
+		}
+	}
+
+	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
+	{
+		if (!vulkanDevice->features.multiDrawIndirect) {
+			if (overlay->header("Info")) {
+				overlay->text("multiDrawIndirect not supported");
+			}
+		}
+		if (overlay->header("Statistics")) {
+			overlay->text("Objects: %d", objectCount);
+		}
+	}
+};
+
+VULKAN_EXAMPLE_MAIN()
\ No newline at end of file
diff --git a/external/Vulkan/examples/inlineuniformblocks/inlineuniformblocks.cpp b/external/Vulkan/examples/inlineuniformblocks/inlineuniformblocks.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..1dafe7110d2da151c2e877621eaaa1c700b273e0
--- /dev/null
+++ b/external/Vulkan/examples/inlineuniformblocks/inlineuniformblocks.cpp
@@ -0,0 +1,404 @@
+/*
+* Vulkan Example - Using inline uniform blocks for passing data to shader stages at descriptor setup
+
+* Note: Requires a device that supports the VK_EXT_inline_uniform_block extension
+*
+* Relevant code parts are marked with [POI]
+*
+* Copyright (C) 2018-2021 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkanexamplebase.h"
+#include "VulkanglTFModel.h"
+
+#define ENABLE_VALIDATION false
+
+float rnd() {
+	return ((float)rand() / (RAND_MAX));
+}
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	VkPhysicalDeviceInlineUniformBlockFeaturesEXT enabledInlineUniformBlockFeatures{};
+
+	vkglTF::Model model;
+
+	struct Object {
+		struct  Material {
+			float roughness;
+			float metallic;
+			float r, g, b;
+			float ambient;
+		} material;
+		VkDescriptorSet descriptorSet;
+		void setRandomMaterial() {
+			material.r = rnd();
+			material.g = rnd();
+			material.b = rnd();
+			material.ambient = 0.0025f;
+			material.roughness = glm::clamp(rnd(), 0.005f, 1.0f);
+			material.metallic = glm::clamp(rnd(), 0.005f, 1.0f);
+		}
+	};
+	std::array<Object, 16> objects;
+
+	struct {
+		vks::Buffer scene;
+	} uniformBuffers;
+
+	struct UBOMatrices {
+		glm::mat4 projection;
+		glm::mat4 model;
+		glm::mat4 view;
+		glm::vec3 camPos;
+	} uboMatrices;
+
+	VkPipelineLayout pipelineLayout;
+	VkPipeline pipeline;
+	VkDescriptorSet descriptorSet;
+
+	struct DescriptorSetLaysts {
+		VkDescriptorSetLayout scene;
+		VkDescriptorSetLayout object;
+	} descriptorSetLayouts;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Inline uniform blocks";
+		camera.type = Camera::CameraType::firstperson;
+		camera.setPosition(glm::vec3(0.0f, 0.0f, -10.0f));
+		camera.setRotation(glm::vec3(0.0, 0.0f, 0.0f));
+		camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 256.0f);
+		camera.movementSpeed = 4.0f;
+		camera.rotationSpeed = 0.25f;
+
+		srand((unsigned int)time(0));
+
+		/*
+			[POI] Enable extensions required for inline uniform blocks
+		*/
+		enabledDeviceExtensions.push_back(VK_EXT_INLINE_UNIFORM_BLOCK_EXTENSION_NAME);
+		enabledDeviceExtensions.push_back(VK_KHR_MAINTENANCE1_EXTENSION_NAME);
+		enabledInstanceExtensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
+	}
+
+	~VulkanExample()
+	{
+		vkDestroyPipeline(device, pipeline, nullptr);
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.scene, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.object, nullptr);
+		uniformBuffers.scene.destroy();
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		clearValues[0].color = { { 0.15f, 0.15f, 0.15f, 1.0f } };
+		clearValues[1].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.offset.x = 0;
+		renderPassBeginInfo.renderArea.offset.y = 0;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		renderPassBeginInfo.clearValueCount = 2;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			renderPassBeginInfo.framebuffer = frameBuffers[i];
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+			VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+			VkDeviceSize offsets[1] = { 0 };
+
+			// Render objects
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
+
+			uint32_t objcount = static_cast<uint32_t>(objects.size());
+			for (uint32_t x = 0; x < objcount; x++) {
+				/*
+					[POI] Bind descriptor sets
+					Set 0 = Scene matrices:
+					Set 1 = Object inline uniform block (In shader pbr.frag: layout (set = 1, binding = 0) uniform UniformInline ... )
+				*/
+				std::vector<VkDescriptorSet> descriptorSets = {
+					descriptorSet,
+					objects[x].descriptorSet
+				};
+				vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 2, descriptorSets.data(), 0, nullptr);
+
+				glm::vec3 pos = glm::vec3(sin(glm::radians(x * (360.0f / objcount))), cos(glm::radians(x * (360.0f / objcount))), 0.0f) * 3.5f;
+
+				vkCmdPushConstants(drawCmdBuffers[i], pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(glm::vec3), &pos);
+				model.draw(drawCmdBuffers[i]);
+			}
+			drawUI(drawCmdBuffers[i]);
+
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void loadAssets()
+	{
+		model.loadFromFile(getAssetPath() + "models/sphere.gltf", vulkanDevice, queue);
+
+		// Setup random materials for every object in the scene
+		for (uint32_t i = 0; i < objects.size(); i++) {
+			objects[i].setRandomMaterial();
+		}
+	}
+
+	void setupDescriptorSetLayout()
+	{
+		// Scene
+		{
+			std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+				vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0),
+			};
+			VkDescriptorSetLayoutCreateInfo descriptorLayoutCI = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
+			VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayoutCI, nullptr, &descriptorSetLayouts.scene));
+		}
+
+		// Objects
+		{
+			std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+				/*
+					[POI] Setup inline uniform block for set 1 at binding 0 (see fragment shader)
+					Descriptor count for an inline uniform block contains data sizes of the block (last parameter)
+				*/
+				vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT, VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(Object::Material)),
+			};
+			VkDescriptorSetLayoutCreateInfo descriptorLayoutCI = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
+			VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayoutCI, nullptr, &descriptorSetLayouts.object));
+		}
+
+		/*
+			[POI] Pipeline layout
+		*/
+		std::vector<VkDescriptorSetLayout> setLayouts = {
+			descriptorSetLayouts.scene, // Set 0 = Scene matrices
+			descriptorSetLayouts.object // Set 1 = Object inline uniform block
+		};
+		VkPipelineLayoutCreateInfo pipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(setLayouts.data(), static_cast<uint32_t>(setLayouts.size()));
+
+		std::vector<VkPushConstantRange> pushConstantRanges = {
+			vks::initializers::pushConstantRange(VK_SHADER_STAGE_VERTEX_BIT, sizeof(glm::vec3), 0),
+		};
+		pipelineLayoutCI.pushConstantRangeCount = 1;
+		pipelineLayoutCI.pPushConstantRanges = pushConstantRanges.data();
+
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelineLayout));
+	}
+
+	void setupDescriptorSets()
+	{
+		// Pool
+		std::vector<VkDescriptorPoolSize> poolSizes = {
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1),
+			/* [POI] Allocate inline uniform blocks */
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT, static_cast<uint32_t>(objects.size()) * sizeof(Object::Material)),
+		};
+		VkDescriptorPoolCreateInfo descriptorPoolCI = vks::initializers::descriptorPoolCreateInfo(poolSizes, static_cast<uint32_t>(objects.size()) + 1);
+
+		/*
+			[POI] New structure that has to be chained into the descriptor pool's createinfo if you want to allocate inline uniform blocks
+		*/
+		VkDescriptorPoolInlineUniformBlockCreateInfoEXT descriptorPoolInlineUniformBlockCreateInfo{};
+		descriptorPoolInlineUniformBlockCreateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_INLINE_UNIFORM_BLOCK_CREATE_INFO_EXT;
+		descriptorPoolInlineUniformBlockCreateInfo.maxInlineUniformBlockBindings = static_cast<uint32_t>(objects.size());
+		descriptorPoolCI.pNext = &descriptorPoolInlineUniformBlockCreateInfo;
+
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolCI, nullptr, &descriptorPool));
+
+		// Sets
+
+		// Scene
+		VkDescriptorSetAllocateInfo descriptorAllocateInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.scene, 1);
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &descriptorAllocateInfo, &descriptorSet));
+
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets = {
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.scene.descriptor),
+		};
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr);
+
+		// Objects
+		for (auto &object : objects) {
+			VkDescriptorSetAllocateInfo descriptorAllocateInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.object, 1);
+			VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &descriptorAllocateInfo, &object.descriptorSet));
+
+			/*
+				[POI] New structure that defines size and data of the inline uniform block needs to be chained into the write descriptor set
+				We will be using this inline uniform block to pass per-object material information to the fragment shader
+			*/
+			VkWriteDescriptorSetInlineUniformBlockEXT writeDescriptorSetInlineUniformBlock{};
+			writeDescriptorSetInlineUniformBlock.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_INLINE_UNIFORM_BLOCK_EXT;
+			writeDescriptorSetInlineUniformBlock.dataSize = sizeof(Object::Material);
+			// Uniform data for the inline block
+			writeDescriptorSetInlineUniformBlock.pData = &object.material;
+
+			/*
+				[POI] Setup the inline uniform block
+			*/
+			VkWriteDescriptorSet writeDescriptorSet{};
+			writeDescriptorSet.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+			writeDescriptorSet.descriptorType = VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT;
+			writeDescriptorSet.dstSet = object.descriptorSet;
+			writeDescriptorSet.dstBinding = 0;
+			// Descriptor count for an inline uniform block contains data sizes of the block(last parameter)
+			writeDescriptorSet.descriptorCount = sizeof(Object::Material);
+			// Chain inline uniform block structure
+			writeDescriptorSet.pNext = &writeDescriptorSetInlineUniformBlock;
+
+			vkUpdateDescriptorSets(device, 1, &writeDescriptorSet, 0, nullptr);
+		}
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyStateCI = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationStateCI = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_FRONT_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendStateCI = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilStateCI = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportStateCI = vks::initializers::pipelineViewportStateCreateInfo(1, 1);
+		VkPipelineMultisampleStateCreateInfo multisampleStateCI = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT);
+		std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
+		VkPipelineDynamicStateCreateInfo dynamicStateCI = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass);
+		pipelineCI.pInputAssemblyState = &inputAssemblyStateCI;
+		pipelineCI.pRasterizationState = &rasterizationStateCI;
+		pipelineCI.pColorBlendState = &colorBlendStateCI;
+		pipelineCI.pMultisampleState = &multisampleStateCI;
+		pipelineCI.pViewportState = &viewportStateCI;
+		pipelineCI.pDepthStencilState = &depthStencilStateCI;
+		pipelineCI.pDynamicState = &dynamicStateCI;
+		pipelineCI.stageCount = static_cast<uint32_t>(shaderStages.size());
+		pipelineCI.pStages = shaderStages.data();
+		pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal });
+
+		shaderStages[0] = loadShader(getShadersPath() + "inlineuniformblocks/pbr.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "inlineuniformblocks/pbr.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipeline));
+	}
+
+	void prepareUniformBuffers()
+	{
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &uniformBuffers.scene, sizeof(uboMatrices)));
+		VK_CHECK_RESULT(uniformBuffers.scene.map());
+		updateUniformBuffers();
+	}
+
+	void updateUniformBuffers()
+	{
+		uboMatrices.projection = camera.matrices.perspective;
+		uboMatrices.view = camera.matrices.view;
+		uboMatrices.model = glm::scale(glm::mat4(1.0f), glm::vec3(0.5f));
+		uboMatrices.camPos = camera.position * glm::vec3(-1.0f, 1.0f, -1.0f);
+		memcpy(uniformBuffers.scene.mapped, &uboMatrices, sizeof(uboMatrices));
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+
+		VulkanExampleBase::submitFrame();
+	}
+
+	void getEnabledFeatures()
+	{
+		// Enable the inline uniform block feature using the dedicated physical device structure
+		enabledInlineUniformBlockFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INLINE_UNIFORM_BLOCK_FEATURES_EXT;
+		enabledInlineUniformBlockFeatures.inlineUniformBlock = VK_TRUE;
+		deviceCreatepNextChain = &enabledInlineUniformBlockFeatures;
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		loadAssets();
+		prepareUniformBuffers();
+		setupDescriptorSetLayout();
+		preparePipelines();
+		setupDescriptorSets();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+		if (camera.updated)
+			updateUniformBuffers();
+	}
+
+	/*
+		[POI] Update descriptor sets at runtime
+	*/
+	void updateMaterials() {
+		// Setup random materials for every object in the scene
+		for (uint32_t i = 0; i < objects.size(); i++) {
+			objects[i].setRandomMaterial();
+		}
+
+		for (auto &object : objects) {
+			/*
+				[POI] New structure that defines size and data of the inline uniform block needs to be chained into the write descriptor set
+				We will be using this inline uniform block to pass per-object material information to the fragment shader
+			*/
+			VkWriteDescriptorSetInlineUniformBlockEXT writeDescriptorSetInlineUniformBlock{};
+			writeDescriptorSetInlineUniformBlock.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_INLINE_UNIFORM_BLOCK_EXT;
+			writeDescriptorSetInlineUniformBlock.dataSize = sizeof(Object::Material);
+			// Uniform data for the inline block
+			writeDescriptorSetInlineUniformBlock.pData = &object.material;
+
+			/*
+				[POI] Update the object's inline uniform block
+			*/
+			VkWriteDescriptorSet writeDescriptorSet{};
+			writeDescriptorSet.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+			writeDescriptorSet.descriptorType = VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT;
+			writeDescriptorSet.dstSet = object.descriptorSet;
+			writeDescriptorSet.dstBinding = 0;
+			writeDescriptorSet.descriptorCount = sizeof(Object::Material);
+			writeDescriptorSet.pNext = &writeDescriptorSetInlineUniformBlock;
+
+			vkUpdateDescriptorSets(device, 1, &writeDescriptorSet, 0, nullptr);
+		}
+	}
+
+	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
+	{
+		if (overlay->button("Randomize")) {
+			updateMaterials();
+		}
+	}
+
+};
+
+VULKAN_EXAMPLE_MAIN()
\ No newline at end of file
diff --git a/external/Vulkan/examples/inputattachments/inputattachments.cpp b/external/Vulkan/examples/inputattachments/inputattachments.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..4b454b08a8f21419d540db007c15c9339e52d856
--- /dev/null
+++ b/external/Vulkan/examples/inputattachments/inputattachments.cpp
@@ -0,0 +1,629 @@
+/*
+* Vulkan Example - Using input attachments
+*
+* Copyright (C) 2018-2021 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*
+* Summary:
+* Input attachments can be used to read attachment contents from a previous sub pass
+* at the same pixel position within a single render pass
+*/
+
+#include "vulkanexamplebase.h"
+#include "VulkanglTFModel.h"
+
+#define ENABLE_VALIDATION false
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	vkglTF::Model scene;
+
+	struct UBOMatrices {
+		glm::mat4 projection;
+		glm::mat4 model;
+		glm::mat4 view;
+	} uboMatrices;
+
+	struct UBOParams {
+		glm::vec2 brightnessContrast = glm::vec2(0.5f, 1.8f);
+		glm::vec2 range = glm::vec2(0.6f, 1.0f);
+		int32_t attachmentIndex = 1;
+	} uboParams;
+
+	struct {
+		vks::Buffer matrices;
+		vks::Buffer params;
+	} uniformBuffers;
+
+	struct {
+		VkPipeline attachmentWrite;
+		VkPipeline attachmentRead;
+	} pipelines;
+
+	struct {
+		VkPipelineLayout attachmentWrite;
+		VkPipelineLayout attachmentRead;
+	} pipelineLayouts;
+
+	struct {
+		VkDescriptorSet attachmentWrite;
+		std::vector<VkDescriptorSet> attachmentRead;
+	} descriptorSets;
+
+	struct {
+		VkDescriptorSetLayout attachmentWrite;
+		VkDescriptorSetLayout attachmentRead;
+	} descriptorSetLayouts;
+
+	struct FrameBufferAttachment {
+		VkImage image = VK_NULL_HANDLE;
+		VkDeviceMemory memory = VK_NULL_HANDLE;
+		VkImageView view = VK_NULL_HANDLE;
+		VkFormat format;
+	};
+	struct Attachments {
+		FrameBufferAttachment color, depth;
+	};
+	std::vector<Attachments> attachments;
+	VkExtent2D attachmentSize;
+
+	const VkFormat colorFormat = VK_FORMAT_R8G8B8A8_UNORM;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Input attachments";
+		camera.type = Camera::CameraType::firstperson;
+		camera.movementSpeed = 2.5f;
+		camera.setPosition(glm::vec3(1.65f, 1.75f, -6.15f));
+		camera.setRotation(glm::vec3(-12.75f, 380.0f, 0.0f));
+		camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 256.0f);
+		UIOverlay.subpass = 1;
+	}
+
+	~VulkanExample()
+	{
+		// Clean up used Vulkan resources
+		// Note : Inherited destructor cleans up resources stored in base class
+
+		for (uint32_t i = 0; i < attachments.size(); i++) {
+			vkDestroyImageView(device, attachments[i].color.view, nullptr);
+			vkDestroyImage(device, attachments[i].color.image, nullptr);
+			vkFreeMemory(device, attachments[i].color.memory, nullptr);
+			vkDestroyImageView(device, attachments[i].depth.view, nullptr);
+			vkDestroyImage(device, attachments[i].depth.image, nullptr);
+			vkFreeMemory(device, attachments[i].depth.memory, nullptr);
+		}
+
+		vkDestroyPipeline(device, pipelines.attachmentRead, nullptr);
+		vkDestroyPipeline(device, pipelines.attachmentWrite, nullptr);
+
+		vkDestroyPipelineLayout(device, pipelineLayouts.attachmentWrite, nullptr);
+		vkDestroyPipelineLayout(device, pipelineLayouts.attachmentRead, nullptr);
+
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.attachmentWrite, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.attachmentRead, nullptr);
+
+		uniformBuffers.matrices.destroy();
+		uniformBuffers.params.destroy();
+	}
+
+	void clearAttachment(FrameBufferAttachment* attachment)
+	{
+		vkDestroyImageView(device, attachment->view, nullptr);
+		vkDestroyImage(device, attachment->image, nullptr);
+		vkFreeMemory(device, attachment->memory, nullptr);
+	}
+
+	// Create a frame buffer attachment
+	void createAttachment(VkFormat format, VkImageUsageFlags usage, FrameBufferAttachment *attachment)
+	{
+		VkImageAspectFlags aspectMask = 0;
+		VkImageLayout imageLayout;
+
+		attachment->format = format;
+
+		if (usage & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) {
+			aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+			imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+		}
+		if (usage & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) {
+			aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
+			imageLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+		}
+
+		VkImageCreateInfo imageCI = vks::initializers::imageCreateInfo();
+		imageCI.imageType = VK_IMAGE_TYPE_2D;
+		imageCI.format = format;
+		imageCI.extent.width = width;
+		imageCI.extent.height = height;
+		imageCI.extent.depth = 1;
+		imageCI.mipLevels = 1;
+		imageCI.arrayLayers = 1;
+		imageCI.samples = VK_SAMPLE_COUNT_1_BIT;
+		imageCI.tiling = VK_IMAGE_TILING_OPTIMAL;
+		// VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT flag is required for input attachments;
+		imageCI.usage = usage | VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;
+		imageCI.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+		VK_CHECK_RESULT(vkCreateImage(device, &imageCI, nullptr, &attachment->image));
+
+		VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo();
+		VkMemoryRequirements memReqs;
+		vkGetImageMemoryRequirements(device, attachment->image, &memReqs);
+		memAlloc.allocationSize = memReqs.size;
+		memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+		VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &attachment->memory));
+		VK_CHECK_RESULT(vkBindImageMemory(device, attachment->image, attachment->memory, 0));
+
+		VkImageViewCreateInfo imageViewCI = vks::initializers::imageViewCreateInfo();
+		imageViewCI.viewType = VK_IMAGE_VIEW_TYPE_2D;
+		imageViewCI.format = format;
+		imageViewCI.subresourceRange = {};
+		imageViewCI.subresourceRange.aspectMask = aspectMask;
+		imageViewCI.subresourceRange.baseMipLevel = 0;
+		imageViewCI.subresourceRange.levelCount = 1;
+		imageViewCI.subresourceRange.baseArrayLayer = 0;
+		imageViewCI.subresourceRange.layerCount = 1;
+		imageViewCI.image = attachment->image;
+		VK_CHECK_RESULT(vkCreateImageView(device, &imageViewCI, nullptr, &attachment->view));
+	}
+
+	// Override framebuffer setup from base class
+	void setupFrameBuffer()
+	{
+		// If the window is resized, all the framebuffers/attachments used in our composition passes need to be recreated
+		if (attachmentSize.width != width || attachmentSize.height != height)
+		{
+			attachmentSize = { width, height };
+
+			for (auto i = 0; i < attachments.size(); i++) {
+				clearAttachment(&attachments[i].color);
+				clearAttachment(&attachments[i].depth);
+				createAttachment(colorFormat, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, &attachments[i].color);
+				createAttachment(depthFormat, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, &attachments[i].depth);
+				// Since the framebuffers/attachments are referred in the descriptor sets, these need to be updated too
+				updateAttachmentReadDescriptors(i);
+			}
+		}
+
+		VkImageView views[3];
+
+		VkFramebufferCreateInfo frameBufferCI{};
+		frameBufferCI.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
+		frameBufferCI.renderPass = renderPass;
+		frameBufferCI.attachmentCount = 3;
+		frameBufferCI.pAttachments = views;
+		frameBufferCI.width = width;
+		frameBufferCI.height = height;
+		frameBufferCI.layers = 1;
+
+		frameBuffers.resize(swapChain.imageCount);
+		for (uint32_t i = 0; i < frameBuffers.size(); i++)
+		{
+			views[0] = swapChain.buffers[i].view;
+			views[1] = attachments[i].color.view;
+			views[2] = attachments[i].depth.view;
+			VK_CHECK_RESULT(vkCreateFramebuffer(device, &frameBufferCI, nullptr, &frameBuffers[i]));
+		}
+	}
+
+	// Override render pass setup from base class
+	void setupRenderPass()
+	{
+		attachmentSize = { width, height };		
+
+		attachments.resize(swapChain.imageCount);
+		for (auto i = 0; i < attachments.size(); i++) {
+			createAttachment(colorFormat, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, &attachments[i].color);
+			createAttachment(depthFormat, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, &attachments[i].depth);
+		}
+
+		std::array<VkAttachmentDescription, 3> attachments{};
+
+		// Swap chain image color attachment
+		// Will be transitioned to present layout
+		attachments[0].format = swapChain.colorFormat;
+		attachments[0].samples = VK_SAMPLE_COUNT_1_BIT;
+		attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+		attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+		attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+		attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+		attachments[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+		attachments[0].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
+
+		// Input attachments
+		// These will be written in the first subpass, transitioned to input attachments
+		// and then read in the secod subpass
+
+		// Color
+		attachments[1].format = colorFormat;
+		attachments[1].samples = VK_SAMPLE_COUNT_1_BIT;
+		attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+		attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+		attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+		attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+		attachments[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+		attachments[1].finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+		// Depth
+		attachments[2].format = depthFormat;
+		attachments[2].samples = VK_SAMPLE_COUNT_1_BIT;
+		attachments[2].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+		attachments[2].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+		attachments[2].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+		attachments[2].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+		attachments[2].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+		attachments[2].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+
+		std::array<VkSubpassDescription,2> subpassDescriptions{};
+
+		/*
+			First subpass
+			Fill the color and depth attachments
+		*/
+		VkAttachmentReference colorReference = { 1, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
+		VkAttachmentReference depthReference = { 2, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL };
+
+		subpassDescriptions[0].pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+		subpassDescriptions[0].colorAttachmentCount = 1;
+		subpassDescriptions[0].pColorAttachments = &colorReference;
+		subpassDescriptions[0].pDepthStencilAttachment = &depthReference;
+
+		/*
+			Second subpass
+			Input attachment read and swap chain color attachment write
+		*/
+
+		// Color reference (target) for this sub pass is the swap chain color attachment
+		VkAttachmentReference colorReferenceSwapchain = { 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
+
+		subpassDescriptions[1].pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+		subpassDescriptions[1].colorAttachmentCount = 1;
+		subpassDescriptions[1].pColorAttachments = &colorReferenceSwapchain;
+
+		// Color and depth attachment written to in first sub pass will be used as input attachments to be read in the fragment shader
+		VkAttachmentReference inputReferences[2];
+		inputReferences[0] = { 1, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL };
+		inputReferences[1] = { 2, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL };
+
+		// Use the attachments filled in the first pass as input attachments
+		subpassDescriptions[1].inputAttachmentCount = 2;
+		subpassDescriptions[1].pInputAttachments = inputReferences;
+
+		/*
+			Subpass dependencies for layout transitions
+		*/
+		std::array<VkSubpassDependency, 3> dependencies;
+
+		dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
+		dependencies[0].dstSubpass = 0;
+		dependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+		dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+		dependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
+		dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+		dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+		// This dependency transitions the input attachment from color attachment to shader read
+		dependencies[1].srcSubpass = 0;
+		dependencies[1].dstSubpass = 1;
+		dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+		dependencies[1].dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
+		dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+		dependencies[1].dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
+		dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+		dependencies[2].srcSubpass = 0;
+		dependencies[2].dstSubpass = VK_SUBPASS_EXTERNAL;
+		dependencies[2].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+		dependencies[2].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+		dependencies[2].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+		dependencies[2].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
+		dependencies[2].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+		VkRenderPassCreateInfo renderPassInfoCI{};
+		renderPassInfoCI.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
+		renderPassInfoCI.attachmentCount = static_cast<uint32_t>(attachments.size());
+		renderPassInfoCI.pAttachments = attachments.data();
+		renderPassInfoCI.subpassCount = static_cast<uint32_t>(subpassDescriptions.size());
+		renderPassInfoCI.pSubpasses = subpassDescriptions.data();
+		renderPassInfoCI.dependencyCount = static_cast<uint32_t>(dependencies.size());
+		renderPassInfoCI.pDependencies = dependencies.data();
+		VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassInfoCI, nullptr, &renderPass));
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[3];
+		clearValues[0].color = { { 0.0f, 0.0f, 0.2f, 0.0f } };
+		clearValues[1].color = { { 0.0f, 0.0f, 0.2f, 0.0f } };
+		clearValues[2].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.offset.x = 0;
+		renderPassBeginInfo.renderArea.offset.y = 0;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		renderPassBeginInfo.clearValueCount = 3;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i) {
+			renderPassBeginInfo.framebuffer = frameBuffers[i];
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+			VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+			VkDeviceSize offsets[1] = { 0 };
+
+			/*
+				First sub pass
+				Fills the attachments
+			*/
+			{
+				vks::debugmarker::beginRegion(drawCmdBuffers[i], "Subpass 0: Writing attachments", glm::vec4(1.0f, 1.0f, 1.0f, 1.0f));
+
+				vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.attachmentWrite);
+				vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayouts.attachmentWrite, 0, 1, &descriptorSets.attachmentWrite, 0, NULL);
+				scene.draw(drawCmdBuffers[i]);
+
+				vks::debugmarker::endRegion(drawCmdBuffers[i]);
+			}
+
+			/*
+				Second sub pass
+				Render a full screen quad, reading from the previously written attachments via input attachments
+			*/
+			{
+				vks::debugmarker::beginRegion(drawCmdBuffers[i], "Subpass 1: Reading attachments", glm::vec4(1.0f, 1.0f, 1.0f, 1.0f));
+
+				vkCmdNextSubpass(drawCmdBuffers[i], VK_SUBPASS_CONTENTS_INLINE);
+
+				vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.attachmentRead);
+				vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayouts.attachmentRead, 0, 1, &descriptorSets.attachmentRead[i], 0, NULL);
+				vkCmdDraw(drawCmdBuffers[i], 3, 1, 0, 0);
+
+				vks::debugmarker::endRegion(drawCmdBuffers[i]);
+			}
+
+			drawUI(drawCmdBuffers[i]);
+
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void loadAssets()
+	{
+		const uint32_t glTFLoadingFlags = vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::PreMultiplyVertexColors | vkglTF::FileLoadingFlags::FlipY;
+		scene.loadFromFile(getAssetPath() + "models/treasure_smooth.gltf", vulkanDevice, queue, glTFLoadingFlags);
+	}
+
+	void updateAttachmentReadDescriptors(uint32_t index)
+	{
+		// Image descriptors for the input attachments read by the shader
+		std::vector<VkDescriptorImageInfo> descriptors = {
+			vks::initializers::descriptorImageInfo(VK_NULL_HANDLE, attachments[index].color.view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL),
+			vks::initializers::descriptorImageInfo(VK_NULL_HANDLE, attachments[index].depth.view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
+		};
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets = {
+			// Binding 0: Color input attachment
+			vks::initializers::writeDescriptorSet(descriptorSets.attachmentRead[index], VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 0, &descriptors[0]),
+			// Binding 1: Depth input attachment
+			vks::initializers::writeDescriptorSet(descriptorSets.attachmentRead[index], VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 1, &descriptors[1]),
+			// Binding 2: Display parameters uniform buffer
+			vks::initializers::writeDescriptorSet(descriptorSets.attachmentRead[index], VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2, &uniformBuffers.params.descriptor),
+		};
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr);
+	}
+
+	void setupDescriptors()
+	{
+		/*
+			Pool
+		*/
+		std::vector<VkDescriptorPoolSize> poolSizes = {
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, attachments.size() + 1),
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, attachments.size() + 1),
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, attachments.size() * 2 + 1),
+		};
+		VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(static_cast<uint32_t>(poolSizes.size()), poolSizes.data(), attachments.size() + 1);
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+
+		/*
+			Attachment write
+		*/
+		{
+			std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+				vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0)
+			};
+			VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
+			VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayouts.attachmentWrite));
+
+			VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayouts.attachmentWrite, 1);
+			VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayouts.attachmentWrite));
+
+			VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.attachmentWrite, 1);
+			VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.attachmentWrite));
+
+			VkWriteDescriptorSet writeDescriptorSet = vks::initializers::writeDescriptorSet(descriptorSets.attachmentWrite, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.matrices.descriptor);
+			vkUpdateDescriptorSets(device, 1, &writeDescriptorSet, 0, nullptr);
+		}
+
+		/*
+			Attachment read
+		*/
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			// Binding 0: Color input attachment
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, VK_SHADER_STAGE_FRAGMENT_BIT, 0),
+			// Binding 1: Depth input attachment
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, VK_SHADER_STAGE_FRAGMENT_BIT, 1),
+			// Binding 2: Display parameters uniform buffer
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_FRAGMENT_BIT, 2),
+		};
+		VkDescriptorSetLayoutCreateInfo descriptorLayoutCI = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayoutCI, nullptr, &descriptorSetLayouts.attachmentRead));
+
+		VkPipelineLayoutCreateInfo pipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayouts.attachmentRead, 1);
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelineLayouts.attachmentRead));
+
+		descriptorSets.attachmentRead.resize(attachments.size());
+		for (auto i = 0; i < descriptorSets.attachmentRead.size(); i++) {
+			VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.attachmentRead, 1);
+			VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.attachmentRead[i]));
+			updateAttachmentReadDescriptors(i);
+		}
+
+	}
+
+	void preparePipelines()
+	{
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyStateCI = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationStateCI = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendStateCI = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilStateCI = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportStateCI = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+		VkPipelineMultisampleStateCreateInfo multisampleStateCI = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
+		std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
+		VkPipelineDynamicStateCreateInfo dynamicStateCI = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo();
+
+		pipelineCI.renderPass = renderPass;
+		pipelineCI.pInputAssemblyState = &inputAssemblyStateCI;
+		pipelineCI.pRasterizationState = &rasterizationStateCI;
+		pipelineCI.pColorBlendState = &colorBlendStateCI;
+		pipelineCI.pMultisampleState = &multisampleStateCI;
+		pipelineCI.pViewportState = &viewportStateCI;
+		pipelineCI.pDepthStencilState = &depthStencilStateCI;
+		pipelineCI.pDynamicState = &dynamicStateCI;
+		pipelineCI.stageCount = static_cast<uint32_t>(shaderStages.size());
+		pipelineCI.pStages = shaderStages.data();
+
+		/*
+			Attachment write
+		*/
+
+		// Pipeline will be used in first sub pass
+		pipelineCI.subpass = 0;
+		pipelineCI.layout = pipelineLayouts.attachmentWrite;
+		pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Color, vkglTF::VertexComponent::Normal});
+
+		shaderStages[0] = loadShader(getShadersPath() + "inputattachments/attachmentwrite.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "inputattachments/attachmentwrite.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.attachmentWrite));
+
+		/*
+			Attachment read
+		*/
+
+		// Pipeline will be used in second sub pass
+		pipelineCI.subpass = 1;
+		pipelineCI.layout = pipelineLayouts.attachmentRead;
+
+		VkPipelineVertexInputStateCreateInfo emptyInputStateCI{};
+		emptyInputStateCI.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
+
+		pipelineCI.pVertexInputState = &emptyInputStateCI;
+		colorBlendStateCI.attachmentCount = 1;
+		rasterizationStateCI.cullMode = VK_CULL_MODE_NONE;
+		depthStencilStateCI.depthWriteEnable = VK_FALSE;
+
+		shaderStages[0] = loadShader(getShadersPath() + "inputattachments/attachmentread.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "inputattachments/attachmentread.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.attachmentRead));
+	}
+
+	void prepareUniformBuffers()
+	{
+		vulkanDevice->createBuffer(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &uniformBuffers.matrices, sizeof(uboMatrices));
+		vulkanDevice->createBuffer(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &uniformBuffers.params, sizeof(uboParams));
+		VK_CHECK_RESULT(uniformBuffers.matrices.map());
+		VK_CHECK_RESULT(uniformBuffers.params.map());
+		updateUniformBuffers();
+	}
+
+	void updateUniformBuffers()
+	{
+		uboMatrices.projection = camera.matrices.perspective;
+		uboMatrices.view = camera.matrices.view;
+		uboMatrices.model = glm::mat4(1.0f);
+		memcpy(uniformBuffers.matrices.mapped, &uboMatrices, sizeof(uboMatrices));
+		memcpy(uniformBuffers.params.mapped, &uboParams, sizeof(uboParams));
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+		VulkanExampleBase::submitFrame();
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		loadAssets();
+		prepareUniformBuffers();
+		setupDescriptors();
+		preparePipelines();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+		if (camera.updated) {
+			updateUniformBuffers();
+		}
+	}
+
+	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
+	{
+		if (overlay->header("Settings")) {
+			overlay->text("Input attachment");
+			if (overlay->comboBox("##attachment", &uboParams.attachmentIndex, { "color", "depth" })) {
+				updateUniformBuffers();
+			}
+			switch (uboParams.attachmentIndex) {
+			case 0:
+				overlay->text("Brightness");
+				if (overlay->sliderFloat("##b", &uboParams.brightnessContrast[0], 0.0f, 2.0f)) {
+					updateUniformBuffers();
+				}
+				overlay->text("Contrast");
+				if (overlay->sliderFloat("##c", &uboParams.brightnessContrast[1], 0.0f, 4.0f)) {
+					updateUniformBuffers();
+				}
+				break;
+			case 1:
+				overlay->text("Visible range");
+				if (overlay->sliderFloat("min", &uboParams.range[0], 0.0f, uboParams.range[1])) {
+					updateUniformBuffers();
+				}
+				if (overlay->sliderFloat("max", &uboParams.range[1], uboParams.range[0], 1.0f)) {
+					updateUniformBuffers();
+				}
+				break;
+			}
+		}
+	}
+};
+
+VULKAN_EXAMPLE_MAIN()
diff --git a/external/Vulkan/examples/instancing/instancing.cpp b/external/Vulkan/examples/instancing/instancing.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d8dbe2b74e1e7d905c2025c3c2849400a4ea160d
--- /dev/null
+++ b/external/Vulkan/examples/instancing/instancing.cpp
@@ -0,0 +1,493 @@
+/*
+* Vulkan Example - Instanced mesh rendering, uses a separate vertex buffer for instanced data
+*
+* Copyright (C) 2016-2021 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkanexamplebase.h"
+#include "VulkanglTFModel.h"
+
+#define VERTEX_BUFFER_BIND_ID 0
+#define INSTANCE_BUFFER_BIND_ID 1
+#define ENABLE_VALIDATION false
+#if defined(__ANDROID__)
+#define INSTANCE_COUNT 4096
+#else
+#define INSTANCE_COUNT 8192
+#endif
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+
+	struct {
+		vks::Texture2DArray rocks;
+		vks::Texture2D planet;
+	} textures;
+
+	struct {
+		vkglTF::Model rock;
+		vkglTF::Model planet;
+	} models;
+
+	// Per-instance data block
+	struct InstanceData {
+		glm::vec3 pos;
+		glm::vec3 rot;
+		float scale;
+		uint32_t texIndex;
+	};
+	// Contains the instanced data
+	struct InstanceBuffer {
+		VkBuffer buffer = VK_NULL_HANDLE;
+		VkDeviceMemory memory = VK_NULL_HANDLE;
+		size_t size = 0;
+		VkDescriptorBufferInfo descriptor;
+	} instanceBuffer;
+
+	struct UBOVS {
+		glm::mat4 projection;
+		glm::mat4 view;
+		glm::vec4 lightPos = glm::vec4(0.0f, -5.0f, 0.0f, 1.0f);
+		float locSpeed = 0.0f;
+		float globSpeed = 0.0f;
+	} uboVS;
+
+	struct {
+		vks::Buffer scene;
+	} uniformBuffers;
+
+	VkPipelineLayout pipelineLayout;
+	struct {
+		VkPipeline instancedRocks;
+		VkPipeline planet;
+		VkPipeline starfield;
+	} pipelines;
+
+	VkDescriptorSetLayout descriptorSetLayout;
+	struct {
+		VkDescriptorSet instancedRocks;
+		VkDescriptorSet planet;
+	} descriptorSets;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Instanced mesh rendering";
+		camera.type = Camera::CameraType::lookat;
+		camera.setPosition(glm::vec3(5.5f, -1.85f, -18.5f));
+		camera.setRotation(glm::vec3(-17.2f, -4.7f, 0.0f));
+		camera.setPerspective(60.0f, (float)width / (float)height, 1.0f, 256.0f);
+	}
+
+	~VulkanExample()
+	{
+		vkDestroyPipeline(device, pipelines.instancedRocks, nullptr);
+		vkDestroyPipeline(device, pipelines.planet, nullptr);
+		vkDestroyPipeline(device, pipelines.starfield, nullptr);
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+		vkDestroyBuffer(device, instanceBuffer.buffer, nullptr);
+		vkFreeMemory(device, instanceBuffer.memory, nullptr);
+		textures.rocks.destroy();
+		textures.planet.destroy();
+		uniformBuffers.scene.destroy();
+	}
+
+	// Enable physical device features required for this example
+	virtual void getEnabledFeatures()
+	{
+		// Enable anisotropic filtering if supported
+		if (deviceFeatures.samplerAnisotropy) {
+			enabledFeatures.samplerAnisotropy = VK_TRUE;
+		}
+	};
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		clearValues[0].color = { { 0.0f, 0.0f, 0.2f, 0.0f } };
+		clearValues[1].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		renderPassBeginInfo.clearValueCount = 2;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			// Set target frame buffer
+			renderPassBeginInfo.framebuffer = frameBuffers[i];
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+			VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+			VkDeviceSize offsets[1] = { 0 };
+
+			// Star field
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets.planet, 0, NULL);
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.starfield);
+			vkCmdDraw(drawCmdBuffers[i], 3, 1, 0, 0);
+
+			// Planet
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets.planet, 0, NULL);
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.planet);
+			models.planet.draw(drawCmdBuffers[i]);
+
+			// Instanced rocks
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets.instancedRocks, 0, NULL);
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.instancedRocks);
+			// Binding point 0 : Mesh vertex buffer
+			vkCmdBindVertexBuffers(drawCmdBuffers[i], VERTEX_BUFFER_BIND_ID, 1, &models.rock.vertices.buffer, offsets);
+			// Binding point 1 : Instance data buffer
+			vkCmdBindVertexBuffers(drawCmdBuffers[i], INSTANCE_BUFFER_BIND_ID, 1, &instanceBuffer.buffer, offsets);
+			// Bind index buffer
+			vkCmdBindIndexBuffer(drawCmdBuffers[i], models.rock.indices.buffer, 0, VK_INDEX_TYPE_UINT32);
+
+			// Render instances
+			vkCmdDrawIndexed(drawCmdBuffers[i], models.rock.indices.count, INSTANCE_COUNT, 0, 0, 0);
+
+			drawUI(drawCmdBuffers[i]);
+
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void loadAssets()
+	{
+		const uint32_t glTFLoadingFlags = vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::PreMultiplyVertexColors | vkglTF::FileLoadingFlags::FlipY;
+		models.rock.loadFromFile(getAssetPath() + "models/rock01.gltf", vulkanDevice, queue, glTFLoadingFlags);
+		models.planet.loadFromFile(getAssetPath() + "models/lavaplanet.gltf", vulkanDevice, queue, glTFLoadingFlags);
+
+		textures.planet.loadFromFile(getAssetPath() + "textures/lavaplanet_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue);
+		textures.rocks.loadFromFile(getAssetPath() + "textures/texturearray_rocks_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue);
+	}
+
+	void setupDescriptorPool()
+	{
+		// Example uses one ubo
+		std::vector<VkDescriptorPoolSize> poolSizes =
+		{
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2),
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2),
+		};
+
+		VkDescriptorPoolCreateInfo descriptorPoolInfo =
+			vks::initializers::descriptorPoolCreateInfo(
+				poolSizes.size(),
+				poolSizes.data(),
+				2);
+
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+	}
+
+	void setupDescriptorSetLayout()
+	{
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			// Binding 0 : Vertex shader uniform buffer
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0),
+			// Binding 1 : Fragment shader combined sampler
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1),
+		};
+		VkDescriptorSetLayoutCreateInfo descriptorLayout =
+			vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout));
+
+		VkPipelineLayoutCreateInfo pipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1);
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelineLayout));
+	}
+
+	void setupDescriptorSet()
+	{
+		VkDescriptorSetAllocateInfo descripotrSetAllocInfo;
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets;
+
+		descripotrSetAllocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1);;
+
+		// Instanced rocks
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &descripotrSetAllocInfo, &descriptorSets.instancedRocks));
+		writeDescriptorSets = {
+			vks::initializers::writeDescriptorSet(descriptorSets.instancedRocks, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,	0, &uniformBuffers.scene.descriptor),	// Binding 0 : Vertex shader uniform buffer
+			vks::initializers::writeDescriptorSet(descriptorSets.instancedRocks, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &textures.rocks.descriptor)	// Binding 1 : Color map
+		};
+		vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, NULL);
+
+		// Planet
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &descripotrSetAllocInfo, &descriptorSets.planet));
+		writeDescriptorSets = {
+			vks::initializers::writeDescriptorSet(descriptorSets.planet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,	0, &uniformBuffers.scene.descriptor),			// Binding 0 : Vertex shader uniform buffer
+			vks::initializers::writeDescriptorSet(descriptorSets.planet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &textures.planet.descriptor)			// Binding 1 : Color map
+		};
+		vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, NULL);
+
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationState =vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+		VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
+		std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
+		VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass);
+		pipelineCI.pInputAssemblyState = &inputAssemblyState;
+		pipelineCI.pRasterizationState = &rasterizationState;
+		pipelineCI.pColorBlendState = &colorBlendState;
+		pipelineCI.pMultisampleState = &multisampleState;
+		pipelineCI.pViewportState = &viewportState;
+		pipelineCI.pDepthStencilState = &depthStencilState;
+		pipelineCI.pDynamicState = &dynamicState;
+		pipelineCI.stageCount = shaderStages.size();
+		pipelineCI.pStages = shaderStages.data();
+
+		// This example uses two different input states, one for the instanced part and one for non-instanced rendering
+		VkPipelineVertexInputStateCreateInfo inputState = vks::initializers::pipelineVertexInputStateCreateInfo();
+		std::vector<VkVertexInputBindingDescription> bindingDescriptions;
+		std::vector<VkVertexInputAttributeDescription> attributeDescriptions;
+
+		// Vertex input bindings
+		// The instancing pipeline uses a vertex input state with two bindings
+		bindingDescriptions = {
+			// Binding point 0: Mesh vertex layout description at per-vertex rate
+			vks::initializers::vertexInputBindingDescription(VERTEX_BUFFER_BIND_ID, sizeof(vkglTF::Vertex), VK_VERTEX_INPUT_RATE_VERTEX),
+			// Binding point 1: Instanced data at per-instance rate
+			vks::initializers::vertexInputBindingDescription(INSTANCE_BUFFER_BIND_ID, sizeof(InstanceData), VK_VERTEX_INPUT_RATE_INSTANCE)
+		};
+
+		// Vertex attribute bindings
+		// Note that the shader declaration for per-vertex and per-instance attributes is the same, the different input rates are only stored in the bindings:
+		// instanced.vert:
+		//	layout (location = 0) in vec3 inPos;		Per-Vertex
+		//	...
+		//	layout (location = 4) in vec3 instancePos;	Per-Instance
+		attributeDescriptions = {
+			// Per-vertex attributes
+			// These are advanced for each vertex fetched by the vertex shader
+			vks::initializers::vertexInputAttributeDescription(VERTEX_BUFFER_BIND_ID, 0, VK_FORMAT_R32G32B32_SFLOAT, 0),					// Location 0: Position
+			vks::initializers::vertexInputAttributeDescription(VERTEX_BUFFER_BIND_ID, 1, VK_FORMAT_R32G32B32_SFLOAT, sizeof(float) * 3),	// Location 1: Normal
+			vks::initializers::vertexInputAttributeDescription(VERTEX_BUFFER_BIND_ID, 2, VK_FORMAT_R32G32_SFLOAT, sizeof(float) * 6),		// Location 2: Texture coordinates
+			vks::initializers::vertexInputAttributeDescription(VERTEX_BUFFER_BIND_ID, 3, VK_FORMAT_R32G32B32_SFLOAT, sizeof(float) * 8),	// Location 3: Color
+			// Per-Instance attributes
+			// These are fetched for each instance rendered
+			vks::initializers::vertexInputAttributeDescription(INSTANCE_BUFFER_BIND_ID, 4, VK_FORMAT_R32G32B32_SFLOAT, 0),					// Location 4: Position
+			vks::initializers::vertexInputAttributeDescription(INSTANCE_BUFFER_BIND_ID, 5, VK_FORMAT_R32G32B32_SFLOAT, sizeof(float) * 3),	// Location 5: Rotation
+			vks::initializers::vertexInputAttributeDescription(INSTANCE_BUFFER_BIND_ID, 6, VK_FORMAT_R32_SFLOAT,sizeof(float) * 6),			// Location 6: Scale
+			vks::initializers::vertexInputAttributeDescription(INSTANCE_BUFFER_BIND_ID, 7, VK_FORMAT_R32_SINT, sizeof(float) * 7),			// Location 7: Texture array layer index
+		};
+		inputState.pVertexBindingDescriptions = bindingDescriptions.data();
+		inputState.pVertexAttributeDescriptions = attributeDescriptions.data();
+
+		pipelineCI.pVertexInputState = &inputState;
+
+		// Instancing pipeline
+		shaderStages[0] = loadShader(getShadersPath() + "instancing/instancing.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "instancing/instancing.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		// Use all input bindings and attribute descriptions
+		inputState.vertexBindingDescriptionCount = static_cast<uint32_t>(bindingDescriptions.size());
+		inputState.vertexAttributeDescriptionCount = static_cast<uint32_t>(attributeDescriptions.size());
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.instancedRocks));
+
+		// Planet rendering pipeline
+		shaderStages[0] = loadShader(getShadersPath() + "instancing/planet.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "instancing/planet.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		// Only use the non-instanced input bindings and attribute descriptions
+		inputState.vertexBindingDescriptionCount = 1;
+		inputState.vertexAttributeDescriptionCount = 4;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.planet));
+
+		// Star field pipeline
+		rasterizationState.cullMode = VK_CULL_MODE_NONE;
+		depthStencilState.depthWriteEnable = VK_FALSE;
+		shaderStages[0] = loadShader(getShadersPath() + "instancing/starfield.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "instancing/starfield.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		// Vertices are generated in the vertex shader
+		inputState.vertexBindingDescriptionCount = 0;
+		inputState.vertexAttributeDescriptionCount = 0;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.starfield));
+	}
+
+	void prepareInstanceData()
+	{
+		std::vector<InstanceData> instanceData;
+		instanceData.resize(INSTANCE_COUNT);
+
+		std::default_random_engine rndGenerator(benchmark.active ? 0 : (unsigned)time(nullptr));
+		std::uniform_real_distribution<float> uniformDist(0.0, 1.0);
+		std::uniform_int_distribution<uint32_t> rndTextureIndex(0, textures.rocks.layerCount);
+
+		// Distribute rocks randomly on two different rings
+		for (auto i = 0; i < INSTANCE_COUNT / 2; i++) {
+			glm::vec2 ring0 { 7.0f, 11.0f };
+			glm::vec2 ring1 { 14.0f, 18.0f };
+
+			float rho, theta;
+
+			// Inner ring
+			rho = sqrt((pow(ring0[1], 2.0f) - pow(ring0[0], 2.0f)) * uniformDist(rndGenerator) + pow(ring0[0], 2.0f));
+			theta = 2.0 * M_PI * uniformDist(rndGenerator);
+			instanceData[i].pos = glm::vec3(rho*cos(theta), uniformDist(rndGenerator) * 0.5f - 0.25f, rho*sin(theta));
+			instanceData[i].rot = glm::vec3(M_PI * uniformDist(rndGenerator), M_PI * uniformDist(rndGenerator), M_PI * uniformDist(rndGenerator));
+			instanceData[i].scale = 1.5f + uniformDist(rndGenerator) - uniformDist(rndGenerator);
+			instanceData[i].texIndex = rndTextureIndex(rndGenerator);
+			instanceData[i].scale *= 0.75f;
+
+			// Outer ring
+			rho = sqrt((pow(ring1[1], 2.0f) - pow(ring1[0], 2.0f)) * uniformDist(rndGenerator) + pow(ring1[0], 2.0f));
+			theta = 2.0 * M_PI * uniformDist(rndGenerator);
+			instanceData[i + INSTANCE_COUNT / 2].pos = glm::vec3(rho*cos(theta), uniformDist(rndGenerator) * 0.5f - 0.25f, rho*sin(theta));
+			instanceData[i + INSTANCE_COUNT / 2].rot = glm::vec3(M_PI * uniformDist(rndGenerator), M_PI * uniformDist(rndGenerator), M_PI * uniformDist(rndGenerator));
+			instanceData[i + INSTANCE_COUNT / 2].scale = 1.5f + uniformDist(rndGenerator) - uniformDist(rndGenerator);
+			instanceData[i + INSTANCE_COUNT / 2].texIndex = rndTextureIndex(rndGenerator);
+			instanceData[i + INSTANCE_COUNT / 2].scale *= 0.75f;
+		}
+
+		instanceBuffer.size = instanceData.size() * sizeof(InstanceData);
+
+		// Staging
+		// Instanced data is static, copy to device local memory
+		// This results in better performance
+
+		struct {
+			VkDeviceMemory memory;
+			VkBuffer buffer;
+		} stagingBuffer;
+
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			instanceBuffer.size,
+			&stagingBuffer.buffer,
+			&stagingBuffer.memory,
+			instanceData.data()));
+
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
+			VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
+			instanceBuffer.size,
+			&instanceBuffer.buffer,
+			&instanceBuffer.memory));
+
+		// Copy to staging buffer
+		VkCommandBuffer copyCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+
+		VkBufferCopy copyRegion = { };
+		copyRegion.size = instanceBuffer.size;
+		vkCmdCopyBuffer(
+			copyCmd,
+			stagingBuffer.buffer,
+			instanceBuffer.buffer,
+			1,
+			&copyRegion);
+
+		vulkanDevice->flushCommandBuffer(copyCmd, queue, true);
+
+		instanceBuffer.descriptor.range = instanceBuffer.size;
+		instanceBuffer.descriptor.buffer = instanceBuffer.buffer;
+		instanceBuffer.descriptor.offset = 0;
+
+		// Destroy staging resources
+		vkDestroyBuffer(device, stagingBuffer.buffer, nullptr);
+		vkFreeMemory(device, stagingBuffer.memory, nullptr);
+	}
+
+	void prepareUniformBuffers()
+	{
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.scene,
+			sizeof(uboVS)));
+
+		// Map persistent
+		VK_CHECK_RESULT(uniformBuffers.scene.map());
+
+		updateUniformBuffer(true);
+	}
+
+	void updateUniformBuffer(bool viewChanged)
+	{
+		if (viewChanged)
+		{
+			uboVS.projection = camera.matrices.perspective;
+			uboVS.view = camera.matrices.view;
+		}
+
+		if (!paused)
+		{
+			uboVS.locSpeed += frameTimer * 0.35f;
+			uboVS.globSpeed += frameTimer * 0.01f;
+		}
+
+		memcpy(uniformBuffers.scene.mapped, &uboVS, sizeof(uboVS));
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+
+		// Command buffer to be sumitted to the queue
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+
+		// Submit to queue
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+
+		VulkanExampleBase::submitFrame();
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		loadAssets();
+		prepareInstanceData();
+		prepareUniformBuffers();
+		setupDescriptorSetLayout();
+		preparePipelines();
+		setupDescriptorPool();
+		setupDescriptorSet();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+		{
+			return;
+		}
+		draw();
+		if ((!paused) || (camera.updated))
+		{			
+			updateUniformBuffer(camera.updated);
+		}
+	}
+
+	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
+	{
+		if (overlay->header("Statistics")) {
+			overlay->text("Instances: %d", INSTANCE_COUNT);
+		}
+	}
+};
+
+VULKAN_EXAMPLE_MAIN()
\ No newline at end of file
diff --git a/external/Vulkan/examples/multisampling/multisampling.cpp b/external/Vulkan/examples/multisampling/multisampling.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..408e18f78303c204ed6d5137bcb314f5829e20a7
--- /dev/null
+++ b/external/Vulkan/examples/multisampling/multisampling.cpp
@@ -0,0 +1,552 @@
+/*
+* Vulkan Example - Multisampling using resolve attachments
+*
+* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkanexamplebase.h"
+#include "VulkanglTFModel.h"
+
+#define ENABLE_VALIDATION false
+
+struct {
+	struct {
+		VkImage image;
+		VkImageView view;
+		VkDeviceMemory memory;
+	} color;
+	struct {
+		VkImage image;
+		VkImageView view;
+		VkDeviceMemory memory;
+	} depth;
+} multisampleTarget;
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	bool useSampleShading = false;
+	VkSampleCountFlagBits sampleCount = VK_SAMPLE_COUNT_1_BIT;
+
+	vkglTF::Model model;
+
+	vks::Buffer uniformBuffer;
+
+	struct UBOVS {
+		glm::mat4 projection;
+		glm::mat4 model;
+		glm::vec4 lightPos = glm::vec4(5.0f, -5.0f, 5.0f, 1.0f);
+	} uboVS;
+
+	struct {
+		VkPipeline MSAA;
+		VkPipeline MSAASampleShading;
+	} pipelines;
+
+	VkPipelineLayout pipelineLayout;
+	VkDescriptorSet descriptorSet;
+	VkDescriptorSetLayout descriptorSetLayout;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Multisampling";
+		camera.type = Camera::CameraType::lookat;
+		camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 256.0f);
+		camera.setRotation(glm::vec3(0.0f, -90.0f, 0.0f));
+		camera.setTranslation(glm::vec3(2.5f, 2.5f, -7.5f));
+	}
+
+	~VulkanExample()
+	{
+		// Clean up used Vulkan resources
+		// Note : Inherited destructor cleans up resources stored in base class
+		vkDestroyPipeline(device, pipelines.MSAA, nullptr);
+		vkDestroyPipeline(device, pipelines.MSAASampleShading, nullptr);
+
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+
+		// Destroy MSAA target
+		vkDestroyImage(device, multisampleTarget.color.image, nullptr);
+		vkDestroyImageView(device, multisampleTarget.color.view, nullptr);
+		vkFreeMemory(device, multisampleTarget.color.memory, nullptr);
+		vkDestroyImage(device, multisampleTarget.depth.image, nullptr);
+		vkDestroyImageView(device, multisampleTarget.depth.view, nullptr);
+		vkFreeMemory(device, multisampleTarget.depth.memory, nullptr);
+
+		uniformBuffer.destroy();
+	}
+
+	// Enable physical device features required for this example
+	virtual void getEnabledFeatures()
+	{
+		// Enable sample rate shading filtering if supported
+		if (deviceFeatures.sampleRateShading) {
+			enabledFeatures.sampleRateShading = VK_TRUE;
+		}
+		// Enable anisotropic filtering if supported
+		if (deviceFeatures.samplerAnisotropy) {
+			enabledFeatures.samplerAnisotropy = VK_TRUE;
+		}
+	}
+
+	// Creates a multi sample render target (image and view) that is used to resolve
+	// into the visible frame buffer target in the render pass
+	void setupMultisampleTarget()
+	{
+		// Check if device supports requested sample count for color and depth frame buffer
+		assert((deviceProperties.limits.framebufferColorSampleCounts >= sampleCount) && (deviceProperties.limits.framebufferDepthSampleCounts >= sampleCount));
+
+		// Color target
+		VkImageCreateInfo info = vks::initializers::imageCreateInfo();
+		info.imageType = VK_IMAGE_TYPE_2D;
+		info.format = swapChain.colorFormat;
+		info.extent.width = width;
+		info.extent.height = height;
+		info.extent.depth = 1;
+		info.mipLevels = 1;
+		info.arrayLayers = 1;
+		info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+		info.tiling = VK_IMAGE_TILING_OPTIMAL;
+		info.samples = sampleCount;
+		// Image will only be used as a transient target
+		info.usage = VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
+		info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+
+		VK_CHECK_RESULT(vkCreateImage(device, &info, nullptr, &multisampleTarget.color.image));
+
+		VkMemoryRequirements memReqs;
+		vkGetImageMemoryRequirements(device, multisampleTarget.color.image, &memReqs);
+		VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo();
+		memAlloc.allocationSize = memReqs.size;
+		// We prefer a lazily allocated memory type
+		// This means that the memory gets allocated when the implementation sees fit, e.g. when first using the images
+		VkBool32 lazyMemTypePresent;
+		memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT, &lazyMemTypePresent);
+		if (!lazyMemTypePresent)
+		{
+			// If this is not available, fall back to device local memory
+			memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+		}
+		VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &multisampleTarget.color.memory));
+		vkBindImageMemory(device, multisampleTarget.color.image, multisampleTarget.color.memory, 0);
+
+		// Create image view for the MSAA target
+		VkImageViewCreateInfo viewInfo = vks::initializers::imageViewCreateInfo();
+		viewInfo.image = multisampleTarget.color.image;
+		viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
+		viewInfo.format = swapChain.colorFormat;
+		viewInfo.components.r = VK_COMPONENT_SWIZZLE_R;
+		viewInfo.components.g = VK_COMPONENT_SWIZZLE_G;
+		viewInfo.components.b = VK_COMPONENT_SWIZZLE_B;
+		viewInfo.components.a = VK_COMPONENT_SWIZZLE_A;
+		viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+		viewInfo.subresourceRange.levelCount = 1;
+		viewInfo.subresourceRange.layerCount = 1;
+
+		VK_CHECK_RESULT(vkCreateImageView(device, &viewInfo, nullptr, &multisampleTarget.color.view));
+
+		// Depth target
+		info.imageType = VK_IMAGE_TYPE_2D;
+		info.format = depthFormat;
+		info.extent.width = width;
+		info.extent.height = height;
+		info.extent.depth = 1;
+		info.mipLevels = 1;
+		info.arrayLayers = 1;
+		info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+		info.tiling = VK_IMAGE_TILING_OPTIMAL;
+		info.samples = sampleCount;
+		// Image will only be used as a transient target
+		info.usage = VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT | VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
+		info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+
+		VK_CHECK_RESULT(vkCreateImage(device, &info, nullptr, &multisampleTarget.depth.image));
+
+		vkGetImageMemoryRequirements(device, multisampleTarget.depth.image, &memReqs);
+		memAlloc = vks::initializers::memoryAllocateInfo();
+		memAlloc.allocationSize = memReqs.size;
+
+		memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT, &lazyMemTypePresent);
+		if (!lazyMemTypePresent)
+		{
+			memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+		}
+
+		VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &multisampleTarget.depth.memory));
+		vkBindImageMemory(device, multisampleTarget.depth.image, multisampleTarget.depth.memory, 0);
+
+		// Create image view for the MSAA target
+		viewInfo.image = multisampleTarget.depth.image;
+		viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
+		viewInfo.format = depthFormat;
+		viewInfo.components.r = VK_COMPONENT_SWIZZLE_R;
+		viewInfo.components.g = VK_COMPONENT_SWIZZLE_G;
+		viewInfo.components.b = VK_COMPONENT_SWIZZLE_B;
+		viewInfo.components.a = VK_COMPONENT_SWIZZLE_A;
+		viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
+		viewInfo.subresourceRange.levelCount = 1;
+		viewInfo.subresourceRange.layerCount = 1;
+
+		VK_CHECK_RESULT(vkCreateImageView(device, &viewInfo, nullptr, &multisampleTarget.depth.view));
+	}
+
+	// Setup a render pass for using a multi sampled attachment
+	// and a resolve attachment that the msaa image is resolved
+	// to at the end of the render pass
+	void setupRenderPass()
+	{
+		// Overrides the virtual function of the base class
+
+		std::array<VkAttachmentDescription, 3> attachments = {};
+
+		// Multisampled attachment that we render to
+		attachments[0].format = swapChain.colorFormat;
+		attachments[0].samples = sampleCount;
+		attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+		attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+		attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+		attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+		attachments[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+		attachments[0].finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+
+		// This is the frame buffer attachment to where the multisampled image
+		// will be resolved to and which will be presented to the swapchain
+		attachments[1].format = swapChain.colorFormat;
+		attachments[1].samples = VK_SAMPLE_COUNT_1_BIT;
+		attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+		attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+		attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+		attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+		attachments[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+		attachments[1].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
+
+		// Multisampled depth attachment we render to
+		attachments[2].format = depthFormat;
+		attachments[2].samples = sampleCount;
+		attachments[2].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+		attachments[2].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+		attachments[2].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+		attachments[2].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+		attachments[2].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+		attachments[2].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+
+		VkAttachmentReference colorReference = {};
+		colorReference.attachment = 0;
+		colorReference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+
+		VkAttachmentReference depthReference = {};
+		depthReference.attachment = 2;
+		depthReference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+
+		// Resolve attachment reference for the color attachment
+		VkAttachmentReference resolveReference = {};
+		resolveReference.attachment = 1;
+		resolveReference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+
+		VkSubpassDescription subpass = {};
+		subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+		subpass.colorAttachmentCount = 1;
+		subpass.pColorAttachments = &colorReference;
+		// Pass our resolve attachments to the sub pass
+		subpass.pResolveAttachments = &resolveReference;
+		subpass.pDepthStencilAttachment = &depthReference;
+
+		std::array<VkSubpassDependency, 2> dependencies;
+
+		dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
+		dependencies[0].dstSubpass = 0;
+		dependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+		dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+		dependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
+		dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+		dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+		dependencies[1].srcSubpass = 0;
+		dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
+		dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+		dependencies[1].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+		dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+		dependencies[1].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
+		dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+		VkRenderPassCreateInfo renderPassInfo = vks::initializers::renderPassCreateInfo();
+		renderPassInfo.attachmentCount = attachments.size();
+		renderPassInfo.pAttachments = attachments.data();
+		renderPassInfo.subpassCount = 1;
+		renderPassInfo.pSubpasses = &subpass;
+		renderPassInfo.dependencyCount = 2;
+		renderPassInfo.pDependencies = dependencies.data();
+
+		VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass));
+	}
+
+	// Frame buffer attachments must match with render pass setup,
+	// so we need to adjust frame buffer creation to cover our
+	// multisample target
+	void setupFrameBuffer()
+	{
+		// Overrides the virtual function of the base class
+
+		std::array<VkImageView, 3> attachments;
+
+		setupMultisampleTarget();
+
+		attachments[0] = multisampleTarget.color.view;
+		// attachment[1] = swapchain image
+		attachments[2] = multisampleTarget.depth.view;
+
+		VkFramebufferCreateInfo frameBufferCreateInfo = {};
+		frameBufferCreateInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
+		frameBufferCreateInfo.pNext = NULL;
+		frameBufferCreateInfo.renderPass = renderPass;
+		frameBufferCreateInfo.attachmentCount = attachments.size();
+		frameBufferCreateInfo.pAttachments = attachments.data();
+		frameBufferCreateInfo.width = width;
+		frameBufferCreateInfo.height = height;
+		frameBufferCreateInfo.layers = 1;
+
+		// Create frame buffers for every swap chain image
+		frameBuffers.resize(swapChain.imageCount);
+		for (uint32_t i = 0; i < frameBuffers.size(); i++)
+		{
+			attachments[1] = swapChain.buffers[i].view;
+			VK_CHECK_RESULT(vkCreateFramebuffer(device, &frameBufferCreateInfo, nullptr, &frameBuffers[i]));
+		}
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[3];
+		// Clear to a white background for higher contrast
+		clearValues[0].color = { { 1.0f, 1.0f, 1.0f, 1.0f } };
+		clearValues[1].color = { { 1.0f, 1.0f, 1.0f, 1.0f } };
+		clearValues[2].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		renderPassBeginInfo.clearValueCount = 3;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			// Set target frame buffer
+			renderPassBeginInfo.framebuffer = frameBuffers[i];
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+			VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL);
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, useSampleShading ? pipelines.MSAASampleShading : pipelines.MSAA);
+			model.draw(drawCmdBuffers[i], vkglTF::RenderFlags::BindImages, pipelineLayout);
+
+			drawUI(drawCmdBuffers[i]);
+
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void loadAssets()
+	{
+		model.loadFromFile(getAssetPath() + "models/voyager.gltf", vulkanDevice, queue, vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::FlipY);
+	}
+
+	void setupDescriptorPool()
+	{
+		// Example uses one ubo and one combined image sampler
+		std::vector<VkDescriptorPoolSize> poolSizes =
+		{
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1),
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1),
+		};
+
+		VkDescriptorPoolCreateInfo descriptorPoolInfo =
+			vks::initializers::descriptorPoolCreateInfo(
+				poolSizes.size(),
+				poolSizes.data(),
+				2);
+
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+	}
+
+	void setupDescriptorSetLayout()
+	{
+		const std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			// Binding 0 : Vertex shader uniform buffer
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0),
+		};
+		VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout));
+
+		// Layout uses set 0 for passing vertex shader ubo and set 1 for fragment shader images (taken from glTF model)
+		const std::vector<VkDescriptorSetLayout> setLayouts = {
+			descriptorSetLayout,
+			vkglTF::descriptorSetLayoutImage,
+		};
+		VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(setLayouts.data(), 2);
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayout));
+	}
+
+	void setupDescriptorSet()
+	{
+		VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1);
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet));
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets = {
+			// Binding 0 : Vertex shader uniform buffer
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffer.descriptor),
+		};
+		vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, NULL);
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+		std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
+		VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+
+		// Setup multi sampling
+		VkPipelineMultisampleStateCreateInfo multisampleState{};
+		multisampleState.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
+		// Number of samples to use for rasterization
+		multisampleState.rasterizationSamples = sampleCount;
+
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass, 0);
+		pipelineCI.pInputAssemblyState = &inputAssemblyState;
+		pipelineCI.pRasterizationState = &rasterizationState;
+		pipelineCI.pColorBlendState = &colorBlendState;
+		pipelineCI.pMultisampleState = &multisampleState;
+		pipelineCI.pViewportState = &viewportState;
+		pipelineCI.pDepthStencilState = &depthStencilState;
+		pipelineCI.pDynamicState = &dynamicState;
+		pipelineCI.stageCount = shaderStages.size();
+		pipelineCI.pStages = shaderStages.data();
+		pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::UV, vkglTF::VertexComponent::Color });
+
+		// MSAA rendering pipeline
+		shaderStages[0] = loadShader(getShadersPath() + "multisampling/mesh.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "multisampling/mesh.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.MSAA));
+
+		if (vulkanDevice->features.sampleRateShading)
+		{
+			// MSAA with sample shading pipeline
+			// Sample shading enables per-sample shading to avoid shader aliasing and smooth out e.g. high frequency texture maps
+			// Note: This will trade performance for are more stable image
+			
+			// Enable per-sample shading (instead of per-fragment)
+			multisampleState.sampleShadingEnable = VK_TRUE;
+			// Minimum fraction for sample shading
+			multisampleState.minSampleShading = 0.25f;
+			VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.MSAASampleShading));
+		}
+	}
+
+	// Prepare and initialize uniform buffer containing shader uniforms
+	void prepareUniformBuffers()
+	{
+		// Vertex shader uniform buffer block
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffer,
+			sizeof(uboVS)));
+
+		// Map persistent
+		VK_CHECK_RESULT(uniformBuffer.map());
+
+		updateUniformBuffers();
+	}
+
+	void updateUniformBuffers()
+	{
+		uboVS.projection = camera.matrices.perspective;
+		uboVS.model = camera.matrices.view;
+		memcpy(uniformBuffer.mapped, &uboVS, sizeof(uboVS));
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+
+		// Command buffer to be sumitted to the queue
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+
+		// Submit to queue
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+
+		VulkanExampleBase::submitFrame();
+	}
+
+	void prepare()
+	{
+		sampleCount = getMaxUsableSampleCount();
+		UIOverlay.rasterizationSamples = sampleCount;
+		VulkanExampleBase::prepare();
+		loadAssets();
+		prepareUniformBuffers();
+		setupDescriptorSetLayout();
+		preparePipelines();
+		setupDescriptorPool();
+		setupDescriptorSet();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+		if (camera.updated) {
+			updateUniformBuffers();
+		}
+	}
+
+	// Returns the maximum sample count usable by the platform
+	VkSampleCountFlagBits getMaxUsableSampleCount()
+	{
+		VkSampleCountFlags counts = std::min(deviceProperties.limits.framebufferColorSampleCounts, deviceProperties.limits.framebufferDepthSampleCounts);
+		if (counts & VK_SAMPLE_COUNT_64_BIT) { return VK_SAMPLE_COUNT_64_BIT; }
+		if (counts & VK_SAMPLE_COUNT_32_BIT) { return VK_SAMPLE_COUNT_32_BIT; }
+		if (counts & VK_SAMPLE_COUNT_16_BIT) { return VK_SAMPLE_COUNT_16_BIT; }
+		if (counts & VK_SAMPLE_COUNT_8_BIT) { return VK_SAMPLE_COUNT_8_BIT; }
+		if (counts & VK_SAMPLE_COUNT_4_BIT) { return VK_SAMPLE_COUNT_4_BIT; }
+		if (counts & VK_SAMPLE_COUNT_2_BIT) { return VK_SAMPLE_COUNT_2_BIT; }
+		return VK_SAMPLE_COUNT_1_BIT;
+	}
+
+	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
+	{
+		if (vulkanDevice->features.sampleRateShading) {
+			if (overlay->header("Settings")) {
+				if (overlay->checkBox("Sample rate shading", &useSampleShading)) {
+					buildCommandBuffers();
+				}
+			}
+		}
+	}
+};
+
+VULKAN_EXAMPLE_MAIN()
diff --git a/external/Vulkan/examples/multithreading/multithreading.cpp b/external/Vulkan/examples/multithreading/multithreading.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..38ef02f2d09767da4f7a24dbb8767b5445b45440
--- /dev/null
+++ b/external/Vulkan/examples/multithreading/multithreading.cpp
@@ -0,0 +1,542 @@
+/*
+* Vulkan Example - Multi threaded command buffer generation and rendering
+*
+* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkanexamplebase.h"
+
+#include "threadpool.hpp"
+#include "frustum.hpp"
+
+#include "VulkanglTFModel.h"
+
+#define ENABLE_VALIDATION false
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	bool displayStarSphere = true;
+
+	struct {
+		vkglTF::Model ufo;
+		vkglTF::Model starSphere;
+	} models;
+
+	// Shared matrices used for thread push constant blocks
+	struct {
+		glm::mat4 projection;
+		glm::mat4 view;
+	} matrices;
+
+	struct {
+		VkPipeline phong;
+		VkPipeline starsphere;
+	} pipelines;
+
+	VkPipelineLayout pipelineLayout;
+
+	VkCommandBuffer primaryCommandBuffer;
+
+	// Secondary scene command buffers used to store backdrop and user interface
+	struct SecondaryCommandBuffers {
+		VkCommandBuffer background;
+		VkCommandBuffer ui;
+	} secondaryCommandBuffers;
+
+	// Number of animated objects to be renderer
+	// by using threads and secondary command buffers
+	uint32_t numObjectsPerThread;
+
+	// Multi threaded stuff
+	// Max. number of concurrent threads
+	uint32_t numThreads;
+
+	// Use push constants to update shader
+	// parameters on a per-thread base
+	struct ThreadPushConstantBlock {
+		glm::mat4 mvp;
+		glm::vec3 color;
+	};
+
+	struct ObjectData {
+		glm::mat4 model;
+		glm::vec3 pos;
+		glm::vec3 rotation;
+		float rotationDir;
+		float rotationSpeed;
+		float scale;
+		float deltaT;
+		float stateT = 0;
+		bool visible = true;
+	};
+
+	struct ThreadData {
+		VkCommandPool commandPool;
+		// One command buffer per render object
+		std::vector<VkCommandBuffer> commandBuffer;
+		// One push constant block per render object
+		std::vector<ThreadPushConstantBlock> pushConstBlock;
+		// Per object information (position, rotation, etc.)
+		std::vector<ObjectData> objectData;
+	};
+	std::vector<ThreadData> threadData;
+
+	vks::ThreadPool threadPool;
+
+	// Fence to wait for all command buffers to finish before
+	// presenting to the swap chain
+	VkFence renderFence = {};
+
+	// View frustum for culling invisible objects
+	vks::Frustum frustum;
+
+	std::default_random_engine rndEngine;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Multi threaded command buffer";
+		camera.type = Camera::CameraType::lookat;
+		camera.setPosition(glm::vec3(0.0f, -0.0f, -32.5f));
+		camera.setRotation(glm::vec3(0.0f));
+		camera.setRotationSpeed(0.5f);
+		camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 256.0f);
+		// Get number of max. concurrent threads
+		numThreads = std::thread::hardware_concurrency();
+		assert(numThreads > 0);
+#if defined(__ANDROID__)
+		LOGD("numThreads = %d", numThreads);
+#else
+		std::cout << "numThreads = " << numThreads << std::endl;
+#endif
+		threadPool.setThreadCount(numThreads);
+		numObjectsPerThread = 512 / numThreads;
+		rndEngine.seed(benchmark.active ? 0 : (unsigned)time(nullptr));
+	}
+
+	~VulkanExample()
+	{
+		// Clean up used Vulkan resources
+		// Note : Inherited destructor cleans up resources stored in base class
+		vkDestroyPipeline(device, pipelines.phong, nullptr);
+		vkDestroyPipeline(device, pipelines.starsphere, nullptr);
+
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+
+		for (auto& thread : threadData) {
+			vkFreeCommandBuffers(device, thread.commandPool, thread.commandBuffer.size(), thread.commandBuffer.data());
+			vkDestroyCommandPool(device, thread.commandPool, nullptr);
+		}
+
+		vkDestroyFence(device, renderFence, nullptr);
+	}
+
+	float rnd(float range)
+	{
+		std::uniform_real_distribution<float> rndDist(0.0f, range);
+		return rndDist(rndEngine);
+	}
+
+	// Create all threads and initialize shader push constants
+	void prepareMultiThreadedRenderer()
+	{
+		// Since this demo updates the command buffers on each frame
+		// we don't use the per-framebuffer command buffers from the
+		// base class, and create a single primary command buffer instead
+		VkCommandBufferAllocateInfo cmdBufAllocateInfo =
+			vks::initializers::commandBufferAllocateInfo(
+				cmdPool,
+				VK_COMMAND_BUFFER_LEVEL_PRIMARY,
+				1);
+		VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, &primaryCommandBuffer));
+
+		// Create additional secondary CBs for background and ui
+		cmdBufAllocateInfo.level = VK_COMMAND_BUFFER_LEVEL_SECONDARY;
+		VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, &secondaryCommandBuffers.background));
+		VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, &secondaryCommandBuffers.ui));
+
+		threadData.resize(numThreads);
+
+		float maxX = std::floor(std::sqrt(numThreads * numObjectsPerThread));
+		uint32_t posX = 0;
+		uint32_t posZ = 0;
+
+		for (uint32_t i = 0; i < numThreads; i++) {
+			ThreadData *thread = &threadData[i];
+
+			// Create one command pool for each thread
+			VkCommandPoolCreateInfo cmdPoolInfo = vks::initializers::commandPoolCreateInfo();
+			cmdPoolInfo.queueFamilyIndex = swapChain.queueNodeIndex;
+			cmdPoolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
+			VK_CHECK_RESULT(vkCreateCommandPool(device, &cmdPoolInfo, nullptr, &thread->commandPool));
+
+			// One secondary command buffer per object that is updated by this thread
+			thread->commandBuffer.resize(numObjectsPerThread);
+			// Generate secondary command buffers for each thread
+			VkCommandBufferAllocateInfo secondaryCmdBufAllocateInfo =
+				vks::initializers::commandBufferAllocateInfo(
+					thread->commandPool,
+					VK_COMMAND_BUFFER_LEVEL_SECONDARY,
+					thread->commandBuffer.size());
+			VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &secondaryCmdBufAllocateInfo, thread->commandBuffer.data()));
+
+			thread->pushConstBlock.resize(numObjectsPerThread);
+			thread->objectData.resize(numObjectsPerThread);
+
+			for (uint32_t j = 0; j < numObjectsPerThread; j++) {
+				float theta = 2.0f * float(M_PI) * rnd(1.0f);
+				float phi = acos(1.0f - 2.0f * rnd(1.0f));
+				thread->objectData[j].pos = glm::vec3(sin(phi) * cos(theta), 0.0f, cos(phi)) * 35.0f;
+
+				thread->objectData[j].rotation = glm::vec3(0.0f, rnd(360.0f), 0.0f);
+				thread->objectData[j].deltaT = rnd(1.0f);
+				thread->objectData[j].rotationDir = (rnd(100.0f) < 50.0f) ? 1.0f : -1.0f;
+				thread->objectData[j].rotationSpeed = (2.0f + rnd(4.0f)) * thread->objectData[j].rotationDir;
+				thread->objectData[j].scale = 0.75f + rnd(0.5f);
+
+				thread->pushConstBlock[j].color = glm::vec3(rnd(1.0f), rnd(1.0f), rnd(1.0f));
+			}
+		}
+
+	}
+
+	// Builds the secondary command buffer for each thread
+	void threadRenderCode(uint32_t threadIndex, uint32_t cmdBufferIndex, VkCommandBufferInheritanceInfo inheritanceInfo)
+	{
+		ThreadData *thread = &threadData[threadIndex];
+		ObjectData *objectData = &thread->objectData[cmdBufferIndex];
+
+		// Check visibility against view frustum using a simple sphere check based on the radius of the mesh
+		objectData->visible = frustum.checkSphere(objectData->pos, models.ufo.dimensions.radius * 0.5f);
+
+		if (!objectData->visible)
+		{
+			return;
+		}
+
+		VkCommandBufferBeginInfo commandBufferBeginInfo = vks::initializers::commandBufferBeginInfo();
+		commandBufferBeginInfo.flags = VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT;
+		commandBufferBeginInfo.pInheritanceInfo = &inheritanceInfo;
+
+		VkCommandBuffer cmdBuffer = thread->commandBuffer[cmdBufferIndex];
+
+		VK_CHECK_RESULT(vkBeginCommandBuffer(cmdBuffer, &commandBufferBeginInfo));
+
+		VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+		vkCmdSetViewport(cmdBuffer, 0, 1, &viewport);
+
+		VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
+		vkCmdSetScissor(cmdBuffer, 0, 1, &scissor);
+
+		vkCmdBindPipeline(cmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.phong);
+
+		// Update
+		if (!paused) {
+			objectData->rotation.y += 2.5f * objectData->rotationSpeed * frameTimer;
+			if (objectData->rotation.y > 360.0f) {
+				objectData->rotation.y -= 360.0f;
+			}
+			objectData->deltaT += 0.15f * frameTimer;
+			if (objectData->deltaT > 1.0f)
+				objectData->deltaT -= 1.0f;
+			objectData->pos.y = sin(glm::radians(objectData->deltaT * 360.0f)) * 2.5f;
+		}
+
+		objectData->model = glm::translate(glm::mat4(1.0f), objectData->pos);
+		objectData->model = glm::rotate(objectData->model, -sinf(glm::radians(objectData->deltaT * 360.0f)) * 0.25f, glm::vec3(objectData->rotationDir, 0.0f, 0.0f));
+		objectData->model = glm::rotate(objectData->model, glm::radians(objectData->rotation.y), glm::vec3(0.0f, objectData->rotationDir, 0.0f));
+		objectData->model = glm::rotate(objectData->model, glm::radians(objectData->deltaT * 360.0f), glm::vec3(0.0f, objectData->rotationDir, 0.0f));
+		objectData->model = glm::scale(objectData->model, glm::vec3(objectData->scale));
+
+		thread->pushConstBlock[cmdBufferIndex].mvp = matrices.projection * matrices.view * objectData->model;
+
+		// Update shader push constant block
+		// Contains model view matrix
+		vkCmdPushConstants(
+			cmdBuffer,
+			pipelineLayout,
+			VK_SHADER_STAGE_VERTEX_BIT,
+			0,
+			sizeof(ThreadPushConstantBlock),
+			&thread->pushConstBlock[cmdBufferIndex]);
+
+		VkDeviceSize offsets[1] = { 0 };
+		vkCmdBindVertexBuffers(cmdBuffer, 0, 1, &models.ufo.vertices.buffer, offsets);
+		vkCmdBindIndexBuffer(cmdBuffer, models.ufo.indices.buffer, 0, VK_INDEX_TYPE_UINT32);
+		vkCmdDrawIndexed(cmdBuffer, models.ufo.indices.count, 1, 0, 0, 0);
+
+		VK_CHECK_RESULT(vkEndCommandBuffer(cmdBuffer));
+	}
+
+	void updateSecondaryCommandBuffers(VkCommandBufferInheritanceInfo inheritanceInfo)
+	{
+		// Secondary command buffer for the sky sphere
+		VkCommandBufferBeginInfo commandBufferBeginInfo = vks::initializers::commandBufferBeginInfo();
+		commandBufferBeginInfo.flags = VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT;
+		commandBufferBeginInfo.pInheritanceInfo = &inheritanceInfo;
+
+		VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+		VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
+
+		/*
+			Background
+		*/
+
+		VK_CHECK_RESULT(vkBeginCommandBuffer(secondaryCommandBuffers.background, &commandBufferBeginInfo));
+
+		vkCmdSetViewport(secondaryCommandBuffers.background, 0, 1, &viewport);
+		vkCmdSetScissor(secondaryCommandBuffers.background, 0, 1, &scissor);
+
+		vkCmdBindPipeline(secondaryCommandBuffers.background, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.starsphere);
+
+		glm::mat4 mvp = matrices.projection * matrices.view;
+		mvp[3] = glm::vec4(0.0f, 0.0f, 0.0f, 1.0f);
+		mvp = glm::scale(mvp, glm::vec3(2.0f));
+
+		vkCmdPushConstants(
+			secondaryCommandBuffers.background,
+			pipelineLayout,
+			VK_SHADER_STAGE_VERTEX_BIT,
+			0,
+			sizeof(mvp),
+			&mvp);
+
+		models.starSphere.draw(secondaryCommandBuffers.background);
+		
+		VK_CHECK_RESULT(vkEndCommandBuffer(secondaryCommandBuffers.background));
+
+		/*
+			User interface
+
+			With VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS, the primary command buffer's content has to be defined
+			by secondary command buffers, which also applies to the UI overlay command buffer
+		*/
+
+		VK_CHECK_RESULT(vkBeginCommandBuffer(secondaryCommandBuffers.ui, &commandBufferBeginInfo));
+
+		vkCmdSetViewport(secondaryCommandBuffers.ui, 0, 1, &viewport);
+		vkCmdSetScissor(secondaryCommandBuffers.ui, 0, 1, &scissor);
+
+		vkCmdBindPipeline(secondaryCommandBuffers.ui, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.starsphere);
+
+		if (settings.overlay) {
+			drawUI(secondaryCommandBuffers.ui);
+		}
+
+		VK_CHECK_RESULT(vkEndCommandBuffer(secondaryCommandBuffers.ui));
+	}
+
+	// Updates the secondary command buffers using a thread pool
+	// and puts them into the primary command buffer that's
+	// lat submitted to the queue for rendering
+	void updateCommandBuffers(VkFramebuffer frameBuffer)
+	{
+		// Contains the list of secondary command buffers to be submitted
+		std::vector<VkCommandBuffer> commandBuffers;
+
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		clearValues[0].color = defaultClearColor;
+		clearValues[1].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.offset.x = 0;
+		renderPassBeginInfo.renderArea.offset.y = 0;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		renderPassBeginInfo.clearValueCount = 2;
+		renderPassBeginInfo.pClearValues = clearValues;
+		renderPassBeginInfo.framebuffer = frameBuffer;
+
+		// Set target frame buffer
+
+		VK_CHECK_RESULT(vkBeginCommandBuffer(primaryCommandBuffer, &cmdBufInfo));
+
+		// The primary command buffer does not contain any rendering commands
+		// These are stored (and retrieved) from the secondary command buffers
+		vkCmdBeginRenderPass(primaryCommandBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS);
+
+		// Inheritance info for the secondary command buffers
+		VkCommandBufferInheritanceInfo inheritanceInfo = vks::initializers::commandBufferInheritanceInfo();
+		inheritanceInfo.renderPass = renderPass;
+		// Secondary command buffer also use the currently active framebuffer
+		inheritanceInfo.framebuffer = frameBuffer;
+
+		// Update secondary sene command buffers
+		updateSecondaryCommandBuffers(inheritanceInfo);
+
+		if (displayStarSphere) {
+			commandBuffers.push_back(secondaryCommandBuffers.background);
+		}
+
+		// Add a job to the thread's queue for each object to be rendered
+		for (uint32_t t = 0; t < numThreads; t++)
+		{
+			for (uint32_t i = 0; i < numObjectsPerThread; i++)
+			{
+				threadPool.threads[t]->addJob([=] { threadRenderCode(t, i, inheritanceInfo); });
+			}
+		}
+
+		threadPool.wait();
+
+		// Only submit if object is within the current view frustum
+		for (uint32_t t = 0; t < numThreads; t++)
+		{
+			for (uint32_t i = 0; i < numObjectsPerThread; i++)
+			{
+				if (threadData[t].objectData[i].visible)
+				{
+					commandBuffers.push_back(threadData[t].commandBuffer[i]);
+				}
+			}
+		}
+
+		// Render ui last
+		if (UIOverlay.visible) {
+			commandBuffers.push_back(secondaryCommandBuffers.ui);
+		}
+
+		// Execute render commands from the secondary command buffer
+		vkCmdExecuteCommands(primaryCommandBuffer, commandBuffers.size(), commandBuffers.data());
+
+		vkCmdEndRenderPass(primaryCommandBuffer);
+
+		VK_CHECK_RESULT(vkEndCommandBuffer(primaryCommandBuffer));
+	}
+
+	void loadAssets()
+	{
+		const uint32_t glTFLoadingFlags = vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::PreMultiplyVertexColors | vkglTF::FileLoadingFlags::FlipY;
+		models.ufo.loadFromFile(getAssetPath() + "models/retroufo_red_lowpoly.gltf",vulkanDevice, queue,glTFLoadingFlags);
+		models.starSphere.loadFromFile(getAssetPath() + "models/sphere.gltf", vulkanDevice, queue, glTFLoadingFlags);
+	}
+
+	void setupPipelineLayout()
+	{
+		VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo =
+			vks::initializers::pipelineLayoutCreateInfo(nullptr, 0);
+
+		// Push constants for model matrices
+		VkPushConstantRange pushConstantRange =
+			vks::initializers::pushConstantRange(
+				VK_SHADER_STAGE_VERTEX_BIT,
+				sizeof(ThreadPushConstantBlock),
+				0);
+
+		// Push constant ranges are part of the pipeline layout
+		pPipelineLayoutCreateInfo.pushConstantRangeCount = 1;
+		pPipelineLayoutCreateInfo.pPushConstantRanges = &pushConstantRange;
+
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayout));
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+		VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
+		std::vector<VkDynamicState> dynamicStateEnables = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
+		VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass, 0);
+		pipelineCI.pInputAssemblyState = &inputAssemblyState;
+		pipelineCI.pRasterizationState = &rasterizationState;
+		pipelineCI.pColorBlendState = &colorBlendState;
+		pipelineCI.pMultisampleState = &multisampleState;
+		pipelineCI.pViewportState = &viewportState;
+		pipelineCI.pDepthStencilState = &depthStencilState;
+		pipelineCI.pDynamicState = &dynamicState;
+		pipelineCI.stageCount = shaderStages.size();
+		pipelineCI.pStages = shaderStages.data();
+		pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::Color});
+
+		// Object rendering pipeline
+		shaderStages[0] = loadShader(getShadersPath() + "multithreading/phong.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "multithreading/phong.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.phong));
+
+		// Star sphere rendering pipeline
+		rasterizationState.cullMode = VK_CULL_MODE_FRONT_BIT;
+		depthStencilState.depthWriteEnable = VK_FALSE;
+		shaderStages[0] = loadShader(getShadersPath() + "multithreading/starsphere.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "multithreading/starsphere.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.starsphere));
+	}
+
+	void updateMatrices()
+	{
+		matrices.projection = camera.matrices.perspective;
+		matrices.view = camera.matrices.view;
+		frustum.update(matrices.projection * matrices.view);
+	}
+
+	void draw()
+	{
+		// Wait for fence to signal that all command buffers are ready
+		VkResult fenceRes;
+		do {
+			fenceRes = vkWaitForFences(device, 1, &renderFence, VK_TRUE, 100000000);
+		} while (fenceRes == VK_TIMEOUT);
+		VK_CHECK_RESULT(fenceRes);
+		vkResetFences(device, 1, &renderFence);
+
+		VulkanExampleBase::prepareFrame();
+
+		updateCommandBuffers(frameBuffers[currentBuffer]);
+
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &primaryCommandBuffer;
+
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, renderFence));
+
+		VulkanExampleBase::submitFrame();
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		// Create a fence for synchronization
+		VkFenceCreateInfo fenceCreateInfo = vks::initializers::fenceCreateInfo(VK_FENCE_CREATE_SIGNALED_BIT);
+		vkCreateFence(device, &fenceCreateInfo, nullptr, &renderFence);
+		loadAssets();
+		setupPipelineLayout();
+		preparePipelines();
+		prepareMultiThreadedRenderer();
+		updateMatrices();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+		if (camera.updated)
+		{
+			updateMatrices();
+		}
+	}
+
+	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
+	{
+		if (overlay->header("Statistics")) {
+			overlay->text("Active threads: %d", numThreads);
+		}
+		if (overlay->header("Settings")) {
+			overlay->checkBox("Stars", &displayStarSphere);
+		}
+
+	}
+};
+
+VULKAN_EXAMPLE_MAIN()
\ No newline at end of file
diff --git a/external/Vulkan/examples/multiview/multiview.cpp b/external/Vulkan/examples/multiview/multiview.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a73992f3af9a4431c1b6bd5348d80437614dc4a7
--- /dev/null
+++ b/external/Vulkan/examples/multiview/multiview.cpp
@@ -0,0 +1,707 @@
+/*
+* Vulkan Example - Multiview (VK_KHR_multiview)
+*
+* Uses VK_KHR_multiview for simultaneously rendering to multiple views and displays these with barrel distortion using a fragment shader
+*
+* Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkanexamplebase.h"
+#include "VulkanglTFModel.h"
+
+#define ENABLE_VALIDATION false
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	struct MultiviewPass {
+		struct FrameBufferAttachment {
+			VkImage image;
+			VkDeviceMemory memory;
+			VkImageView view;
+		} color, depth;
+		VkFramebuffer frameBuffer;
+		VkRenderPass renderPass;
+		VkDescriptorImageInfo descriptor;
+		VkSampler sampler;
+		VkSemaphore semaphore;
+		std::vector<VkCommandBuffer> commandBuffers;
+		std::vector<VkFence> waitFences;
+	} multiviewPass;
+
+	vkglTF::Model scene;
+
+	struct UBO {
+		glm::mat4 projection[2];
+		glm::mat4 modelview[2];
+		glm::vec4 lightPos = glm::vec4(-2.5f, -3.5f, 0.0f, 1.0f);
+		float distortionAlpha = 0.2f;
+	} ubo;
+
+	vks::Buffer uniformBuffer;
+
+	VkPipeline pipeline;
+	VkPipelineLayout pipelineLayout;
+	VkDescriptorSet descriptorSet;
+	VkDescriptorSetLayout descriptorSetLayout;
+
+	VkPipeline viewDisplayPipelines[2];
+
+	VkPhysicalDeviceMultiviewFeaturesKHR physicalDeviceMultiviewFeatures{};
+
+	// Camera and view properties
+	float eyeSeparation = 0.08f;
+	const float focalLength = 0.5f;
+	const float fov = 90.0f;
+	const float zNear = 0.1f;
+	const float zFar = 256.0f;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Multiview rendering";
+		camera.type = Camera::CameraType::firstperson;
+		camera.setRotation(glm::vec3(0.0f, 90.0f, 0.0f));
+		camera.setTranslation(glm::vec3(7.0f, 3.2f, 0.0f));
+		camera.movementSpeed = 5.0f;
+
+		// Enable extension required for multiview
+		enabledDeviceExtensions.push_back(VK_KHR_MULTIVIEW_EXTENSION_NAME);
+
+		// Reading device properties and features for multiview requires VK_KHR_get_physical_device_properties2 to be enabled
+		enabledInstanceExtensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
+
+		// Enable required extension features
+		physicalDeviceMultiviewFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES_KHR;
+		physicalDeviceMultiviewFeatures.multiview = VK_TRUE;
+		deviceCreatepNextChain = &physicalDeviceMultiviewFeatures;
+	}
+
+	~VulkanExample()
+	{
+		vkDestroyPipeline(device, pipeline, nullptr);
+
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+
+		// Multiview pass
+
+		vkDestroyImageView(device, multiviewPass.color.view, nullptr);
+		vkDestroyImage(device, multiviewPass.color.image, nullptr);
+		vkFreeMemory(device, multiviewPass.color.memory, nullptr);
+		vkDestroyImageView(device, multiviewPass.depth.view, nullptr);
+		vkDestroyImage(device, multiviewPass.depth.image, nullptr);
+		vkFreeMemory(device, multiviewPass.depth.memory, nullptr);
+
+		vkDestroyRenderPass(device, multiviewPass.renderPass, nullptr);
+		vkDestroySampler(device, multiviewPass.sampler, nullptr);
+		vkDestroyFramebuffer(device, multiviewPass.frameBuffer, nullptr);
+
+		vkDestroySemaphore(device, multiviewPass.semaphore, nullptr);
+		for (auto& fence : multiviewPass.waitFences) {
+			vkDestroyFence(device, fence, nullptr);
+		}
+
+		for (auto& pipeline : viewDisplayPipelines) {
+			vkDestroyPipeline(device, pipeline, nullptr);
+		}
+
+		uniformBuffer.destroy();
+	}
+
+	/*
+		Prepares all resources required for the multiview attachment
+		Images, views, attachments, renderpass, framebuffer, etc.
+	*/
+	void prepareMultiview()
+	{
+		// Example renders to two views (left/right)
+		const uint32_t multiviewLayerCount = 2;
+
+		/*
+			Layered depth/stencil framebuffer
+		*/
+		{
+			VkImageCreateInfo imageCI= vks::initializers::imageCreateInfo();
+			imageCI.imageType = VK_IMAGE_TYPE_2D;
+			imageCI.format = depthFormat;
+			imageCI.extent = { width, height, 1 };
+			imageCI.mipLevels = 1;
+			imageCI.arrayLayers = multiviewLayerCount;
+			imageCI.samples = VK_SAMPLE_COUNT_1_BIT;
+			imageCI.tiling = VK_IMAGE_TILING_OPTIMAL;
+			imageCI.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
+			imageCI.flags = 0;
+			VK_CHECK_RESULT(vkCreateImage(device, &imageCI, nullptr, &multiviewPass.depth.image));
+
+			VkMemoryRequirements memReqs;
+			vkGetImageMemoryRequirements(device, multiviewPass.depth.image, &memReqs);
+
+			VkMemoryAllocateInfo memAllocInfo{};
+			memAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+			memAllocInfo.allocationSize = 0;
+			memAllocInfo.memoryTypeIndex = 0;
+
+			VkImageViewCreateInfo depthStencilView = {};
+			depthStencilView.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+			depthStencilView.pNext = NULL;
+			depthStencilView.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY;
+			depthStencilView.format = depthFormat;
+			depthStencilView.flags = 0;
+			depthStencilView.subresourceRange = {};
+			depthStencilView.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
+			depthStencilView.subresourceRange.baseMipLevel = 0;
+			depthStencilView.subresourceRange.levelCount = 1;
+			depthStencilView.subresourceRange.baseArrayLayer = 0;
+			depthStencilView.subresourceRange.layerCount = multiviewLayerCount;
+			depthStencilView.image = multiviewPass.depth.image;
+
+			memAllocInfo.allocationSize = memReqs.size;
+			memAllocInfo.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+			VK_CHECK_RESULT(vkAllocateMemory(device, &memAllocInfo, nullptr, &multiviewPass.depth.memory));
+			VK_CHECK_RESULT(vkBindImageMemory(device, multiviewPass.depth.image, multiviewPass.depth.memory, 0));
+			VK_CHECK_RESULT(vkCreateImageView(device, &depthStencilView, nullptr, &multiviewPass.depth.view));
+		}
+
+		/*
+			Layered color attachment
+		*/
+		{
+			VkImageCreateInfo imageCI = vks::initializers::imageCreateInfo();
+			imageCI.imageType = VK_IMAGE_TYPE_2D;
+			imageCI.format = swapChain.colorFormat;
+			imageCI.extent = { width, height, 1 };
+			imageCI.mipLevels = 1;
+			imageCI.arrayLayers = multiviewLayerCount;
+			imageCI.samples = VK_SAMPLE_COUNT_1_BIT;
+			imageCI.tiling = VK_IMAGE_TILING_OPTIMAL;
+			imageCI.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
+			VK_CHECK_RESULT(vkCreateImage(device, &imageCI, nullptr, &multiviewPass.color.image));
+
+			VkMemoryRequirements memReqs;
+			vkGetImageMemoryRequirements(device, multiviewPass.color.image, &memReqs);
+
+			VkMemoryAllocateInfo memoryAllocInfo = vks::initializers::memoryAllocateInfo();
+			memoryAllocInfo.allocationSize = memReqs.size;
+			memoryAllocInfo.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+			VK_CHECK_RESULT(vkAllocateMemory(device, &memoryAllocInfo, nullptr, &multiviewPass.color.memory));
+			VK_CHECK_RESULT(vkBindImageMemory(device, multiviewPass.color.image, multiviewPass.color.memory, 0));
+
+			VkImageViewCreateInfo imageViewCI = vks::initializers::imageViewCreateInfo();
+			imageViewCI.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY;
+			imageViewCI.format = swapChain.colorFormat;
+			imageViewCI.flags = 0;
+			imageViewCI.subresourceRange = {};
+			imageViewCI.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+			imageViewCI.subresourceRange.baseMipLevel = 0;
+			imageViewCI.subresourceRange.levelCount = 1;
+			imageViewCI.subresourceRange.baseArrayLayer = 0;
+			imageViewCI.subresourceRange.layerCount = multiviewLayerCount;
+			imageViewCI.image = multiviewPass.color.image;
+			VK_CHECK_RESULT(vkCreateImageView(device, &imageViewCI, nullptr, &multiviewPass.color.view));
+
+			// Create sampler to sample from the attachment in the fragment shader
+			VkSamplerCreateInfo samplerCI = vks::initializers::samplerCreateInfo();
+			samplerCI.magFilter = VK_FILTER_NEAREST;
+			samplerCI.minFilter = VK_FILTER_NEAREST;
+			samplerCI.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+			samplerCI.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+			samplerCI.addressModeV = samplerCI.addressModeU;
+			samplerCI.addressModeW = samplerCI.addressModeU;
+			samplerCI.mipLodBias = 0.0f;
+			samplerCI.maxAnisotropy = 1.0f;
+			samplerCI.minLod = 0.0f;
+			samplerCI.maxLod = 1.0f;
+			samplerCI.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
+			VK_CHECK_RESULT(vkCreateSampler(device, &samplerCI, nullptr, &multiviewPass.sampler));
+
+			// Fill a descriptor for later use in a descriptor set
+			multiviewPass.descriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+			multiviewPass.descriptor.imageView = multiviewPass.color.view;
+			multiviewPass.descriptor.sampler = multiviewPass.sampler;
+		}
+
+		/*
+			Renderpass
+		*/
+		{
+			std::array<VkAttachmentDescription, 2> attachments = {};
+			// Color attachment
+			attachments[0].format = swapChain.colorFormat;
+			attachments[0].samples = VK_SAMPLE_COUNT_1_BIT;
+			attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+			attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+			attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+			attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+			attachments[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+			attachments[0].finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+			// Depth attachment
+			attachments[1].format = depthFormat;
+			attachments[1].samples = VK_SAMPLE_COUNT_1_BIT;
+			attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+			attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+			attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+			attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+			attachments[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+			attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+
+			VkAttachmentReference colorReference = {};
+			colorReference.attachment = 0;
+			colorReference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+
+			VkAttachmentReference depthReference = {};
+			depthReference.attachment = 1;
+			depthReference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+
+			VkSubpassDescription subpassDescription = {};
+			subpassDescription.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+			subpassDescription.colorAttachmentCount = 1;
+			subpassDescription.pColorAttachments = &colorReference;
+			subpassDescription.pDepthStencilAttachment = &depthReference;
+
+			// Subpass dependencies for layout transitions
+			std::array<VkSubpassDependency, 2> dependencies;
+
+			dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
+			dependencies[0].dstSubpass = 0;
+			dependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+			dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+			dependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
+			dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+			dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+			dependencies[1].srcSubpass = 0;
+			dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
+			dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+			dependencies[1].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+			dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+			dependencies[1].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
+			dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+			VkRenderPassCreateInfo renderPassCI{};
+			renderPassCI.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
+			renderPassCI.attachmentCount = static_cast<uint32_t>(attachments.size());
+			renderPassCI.pAttachments = attachments.data();
+			renderPassCI.subpassCount = 1;
+			renderPassCI.pSubpasses = &subpassDescription;
+			renderPassCI.dependencyCount = static_cast<uint32_t>(dependencies.size());
+			renderPassCI.pDependencies = dependencies.data();
+
+			/*
+				Setup multiview info for the renderpass
+			*/
+
+			/*
+				Bit mask that specifies which view rendering is broadcast to
+				0011 = Broadcast to first and second view (layer)
+			*/
+			const uint32_t viewMask = 0b00000011;
+
+			/*
+				Bit mask that specifies correlation between views
+				An implementation may use this for optimizations (concurrent render)
+			*/
+			const uint32_t correlationMask = 0b00000011;
+
+			VkRenderPassMultiviewCreateInfo renderPassMultiviewCI{};
+			renderPassMultiviewCI.sType = VK_STRUCTURE_TYPE_RENDER_PASS_MULTIVIEW_CREATE_INFO;
+			renderPassMultiviewCI.subpassCount = 1;
+			renderPassMultiviewCI.pViewMasks = &viewMask;
+			renderPassMultiviewCI.correlationMaskCount = 1;
+			renderPassMultiviewCI.pCorrelationMasks = &correlationMask;
+
+			renderPassCI.pNext = &renderPassMultiviewCI;
+
+			VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassCI, nullptr, &multiviewPass.renderPass));
+		}
+
+		/*
+			Framebuffer
+		*/
+		{
+			VkImageView attachments[2];
+			attachments[0] = multiviewPass.color.view;
+			attachments[1] = multiviewPass.depth.view;
+
+			VkFramebufferCreateInfo framebufferCI = vks::initializers::framebufferCreateInfo();
+			framebufferCI.renderPass = multiviewPass.renderPass;
+			framebufferCI.attachmentCount = 2;
+			framebufferCI.pAttachments = attachments;
+			framebufferCI.width = width;
+			framebufferCI.height = height;
+			framebufferCI.layers = 1;
+			VK_CHECK_RESULT(vkCreateFramebuffer(device, &framebufferCI, nullptr, &multiviewPass.frameBuffer));
+		}
+	}
+
+	void buildCommandBuffers()
+	{
+		/*
+			View display
+		*/
+		{
+			VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+			VkClearValue clearValues[2];
+			clearValues[0].color = defaultClearColor;
+			clearValues[1].depthStencil = { 1.0f, 0 };
+
+			VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+			renderPassBeginInfo.renderPass = renderPass;
+			renderPassBeginInfo.renderArea.offset.x = 0;
+			renderPassBeginInfo.renderArea.offset.y = 0;
+			renderPassBeginInfo.renderArea.extent.width = width;
+			renderPassBeginInfo.renderArea.extent.height = height;
+			renderPassBeginInfo.clearValueCount = 2;
+			renderPassBeginInfo.pClearValues = clearValues;
+
+			for (int32_t i = 0; i < drawCmdBuffers.size(); ++i) {
+				renderPassBeginInfo.framebuffer = frameBuffers[i];
+
+				VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+				vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+				VkViewport viewport = vks::initializers::viewport((float)width / 2.0f, (float)height, 0.0f, 1.0f);
+				VkRect2D scissor = vks::initializers::rect2D(width / 2, height, 0, 0);
+				vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+				vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+				vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr);
+
+				// Left eye
+				vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, viewDisplayPipelines[0]);
+				vkCmdDraw(drawCmdBuffers[i], 3, 1, 0, 0);
+
+				// Right eye
+				viewport.x = (float)width / 2;
+				scissor.offset.x = width / 2;
+				vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+				vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+				vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, viewDisplayPipelines[1]);
+				vkCmdDraw(drawCmdBuffers[i], 3, 1, 0, 0);
+
+				drawUI(drawCmdBuffers[i]);
+
+				vkCmdEndRenderPass(drawCmdBuffers[i]);
+				VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+			}
+		}
+
+		/*
+			Multiview layered attachment scene rendering
+		*/
+
+		multiviewPass.commandBuffers.resize(drawCmdBuffers.size());
+
+		VkCommandBufferAllocateInfo cmdBufAllocateInfo = vks::initializers::commandBufferAllocateInfo(cmdPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, static_cast<uint32_t>(drawCmdBuffers.size()));
+		VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, multiviewPass.commandBuffers.data()));
+
+		{
+			VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+			VkClearValue clearValues[2];
+			clearValues[0].color = defaultClearColor;
+			clearValues[1].depthStencil = { 1.0f, 0 };
+
+			VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+			renderPassBeginInfo.renderPass = multiviewPass.renderPass;
+			renderPassBeginInfo.renderArea.offset.x = 0;
+			renderPassBeginInfo.renderArea.offset.y = 0;
+			renderPassBeginInfo.renderArea.extent.width = width;
+			renderPassBeginInfo.renderArea.extent.height = height;
+			renderPassBeginInfo.clearValueCount = 2;
+			renderPassBeginInfo.pClearValues = clearValues;
+
+			for (int32_t i = 0; i < multiviewPass.commandBuffers.size(); ++i) {
+				renderPassBeginInfo.framebuffer = multiviewPass.frameBuffer;
+
+				VK_CHECK_RESULT(vkBeginCommandBuffer(multiviewPass.commandBuffers[i], &cmdBufInfo));
+				vkCmdBeginRenderPass(multiviewPass.commandBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+				VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+				vkCmdSetViewport(multiviewPass.commandBuffers[i], 0, 1, &viewport);
+				VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
+				vkCmdSetScissor(multiviewPass.commandBuffers[i], 0, 1, &scissor);
+
+				vkCmdBindDescriptorSets(multiviewPass.commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr);
+				vkCmdBindPipeline(multiviewPass.commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
+				scene.draw(multiviewPass.commandBuffers[i]);
+
+				vkCmdEndRenderPass(multiviewPass.commandBuffers[i]);
+				VK_CHECK_RESULT(vkEndCommandBuffer(multiviewPass.commandBuffers[i]));
+			}
+		}
+	}
+
+	void loadAssets()
+	{
+		scene.loadFromFile(getAssetPath() + "models/sampleroom.gltf", vulkanDevice, queue, vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::PreMultiplyVertexColors | vkglTF::FileLoadingFlags::FlipY);
+	}
+
+	void prepareDescriptors()
+	{
+		/*
+			Pool
+		*/
+		std::vector<VkDescriptorPoolSize> poolSizes = {
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1),
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1)
+		};
+		VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(static_cast<uint32_t>(poolSizes.size()), poolSizes.data(), 1);
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+
+		/*
+			Layouts
+		*/
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0),
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1)
+		};
+		VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout));
+		VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo =vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1);
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayout));
+
+		/*
+			Descriptors
+		*/
+		VkDescriptorSetAllocateInfo allocateInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1);
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocateInfo, &descriptorSet));
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets = {
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffer.descriptor),
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &multiviewPass.descriptor),
+		};
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr);
+	}
+
+	void preparePipelines()
+	{
+
+		VkSemaphoreCreateInfo semaphoreCI = vks::initializers::semaphoreCreateInfo();
+		VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCI, nullptr, &multiviewPass.semaphore));
+
+		/*
+			Display multi view features and properties
+		*/
+
+		VkPhysicalDeviceFeatures2KHR deviceFeatures2{};
+		VkPhysicalDeviceMultiviewFeaturesKHR extFeatures{};
+		extFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES_KHR;
+		deviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR;
+		deviceFeatures2.pNext = &extFeatures;
+		PFN_vkGetPhysicalDeviceFeatures2KHR vkGetPhysicalDeviceFeatures2KHR = reinterpret_cast<PFN_vkGetPhysicalDeviceFeatures2KHR>(vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceFeatures2KHR"));
+		vkGetPhysicalDeviceFeatures2KHR(physicalDevice, &deviceFeatures2);
+		std::cout << "Multiview features:" << std::endl;
+		std::cout << "\tmultiview = " << extFeatures.multiview << std::endl;
+		std::cout << "\tmultiviewGeometryShader = " << extFeatures.multiviewGeometryShader << std::endl;
+		std::cout << "\tmultiviewTessellationShader = " << extFeatures.multiviewTessellationShader << std::endl;
+		std::cout << std::endl;
+
+		VkPhysicalDeviceProperties2KHR deviceProps2{};
+		VkPhysicalDeviceMultiviewPropertiesKHR extProps{};
+		extProps.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_PROPERTIES_KHR;
+		deviceProps2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR;
+		deviceProps2.pNext = &extProps;
+		PFN_vkGetPhysicalDeviceProperties2KHR vkGetPhysicalDeviceProperties2KHR = reinterpret_cast<PFN_vkGetPhysicalDeviceProperties2KHR>(vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceProperties2KHR"));
+		vkGetPhysicalDeviceProperties2KHR(physicalDevice, &deviceProps2);
+		std::cout << "Multiview properties:" << std::endl;
+		std::cout << "\tmaxMultiviewViewCount = " << extProps.maxMultiviewViewCount << std::endl;
+		std::cout << "\tmaxMultiviewInstanceIndex = " << extProps.maxMultiviewInstanceIndex << std::endl;
+
+		/*
+			Create graphics pipeline
+		*/
+
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyStateCI = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationStateCI = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendStateCI =	vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilStateCI = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportStateCI = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+		VkPipelineMultisampleStateCreateInfo multisampleStateCI = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT);
+		std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
+		VkPipelineDynamicStateCreateInfo dynamicStateCI = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
+
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, multiviewPass.renderPass);
+		pipelineCI.pInputAssemblyState = &inputAssemblyStateCI;
+		pipelineCI.pRasterizationState = &rasterizationStateCI;
+		pipelineCI.pColorBlendState = &colorBlendStateCI;
+		pipelineCI.pMultisampleState = &multisampleStateCI;
+		pipelineCI.pViewportState = &viewportStateCI;
+		pipelineCI.pDepthStencilState = &depthStencilStateCI;
+		pipelineCI.pDynamicState = &dynamicStateCI;
+		pipelineCI.pVertexInputState  = vkglTF::Vertex::getPipelineVertexInputState({vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::Color});
+
+		/*
+			Load shaders
+			Contrary to the viewport array example we don't need a geometry shader for broadcasting
+		*/
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+		shaderStages[0] = loadShader(getShadersPath() + "multiview/multiview.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "multiview/multiview.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		pipelineCI.stageCount = 2;
+		pipelineCI.pStages = shaderStages.data();
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipeline));
+
+		/*
+			Full screen pass
+		*/
+
+		float multiviewArrayLayer = 0.0f;
+
+		VkSpecializationMapEntry specializationMapEntry{ 0, 0, sizeof(float) };
+
+		VkSpecializationInfo specializationInfo{};
+		specializationInfo.dataSize = sizeof(float);
+		specializationInfo.mapEntryCount = 1;
+		specializationInfo.pMapEntries = &specializationMapEntry;
+		specializationInfo.pData = &multiviewArrayLayer;
+
+		rasterizationStateCI.cullMode = VK_CULL_MODE_FRONT_BIT;
+
+		/*
+			Separate pipelines per eye (view) using specialization constants to set view array layer to sample from
+		*/
+		for (uint32_t i = 0; i < 2; i++) {
+			shaderStages[0] = loadShader(getShadersPath() + "multiview/viewdisplay.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+			shaderStages[1] = loadShader(getShadersPath() + "multiview/viewdisplay.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+			shaderStages[1].pSpecializationInfo = &specializationInfo;
+			multiviewArrayLayer = (float)i;
+			VkPipelineVertexInputStateCreateInfo emptyInputState = vks::initializers::pipelineVertexInputStateCreateInfo();
+			pipelineCI.pVertexInputState = &emptyInputState;
+			pipelineCI.layout = pipelineLayout;
+			pipelineCI.renderPass = renderPass;
+			VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &viewDisplayPipelines[i]));
+		}
+
+	}
+
+	// Prepare and initialize uniform buffer containing shader uniforms
+	void prepareUniformBuffers()
+	{
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffer,
+			sizeof(ubo)));
+		VK_CHECK_RESULT(uniformBuffer.map());
+		updateUniformBuffers();
+	}
+
+	void updateUniformBuffers()
+	{
+		// Matrices for the two viewports
+		// See http://paulbourke.net/stereographics/stereorender/
+
+		// Calculate some variables
+		float aspectRatio = (float)(width * 0.5f) / (float)height;
+		float wd2 = zNear * tan(glm::radians(fov / 2.0f));
+		float ndfl = zNear / focalLength;
+		float left, right;
+		float top = wd2;
+		float bottom = -wd2;
+
+		glm::vec3 camFront;
+		camFront.x = -cos(glm::radians(camera.rotation.x)) * sin(glm::radians(camera.rotation.y));
+		camFront.y = sin(glm::radians(camera.rotation.x));
+		camFront.z = cos(glm::radians(camera.rotation.x)) * cos(glm::radians(camera.rotation.y));
+		camFront = glm::normalize(camFront);
+		glm::vec3 camRight = glm::normalize(glm::cross(camFront, glm::vec3(0.0f, 1.0f, 0.0f)));
+
+		glm::mat4 rotM = glm::mat4(1.0f);
+		glm::mat4 transM;
+
+		rotM = glm::rotate(rotM, glm::radians(camera.rotation.x), glm::vec3(1.0f, 0.0f, 0.0f));
+		rotM = glm::rotate(rotM, glm::radians(camera.rotation.y), glm::vec3(0.0f, 1.0f, 0.0f));
+		rotM = glm::rotate(rotM, glm::radians(camera.rotation.z), glm::vec3(0.0f, 0.0f, 1.0f));
+
+		// Left eye
+		left = -aspectRatio * wd2 + 0.5f * eyeSeparation * ndfl;
+		right = aspectRatio * wd2 + 0.5f * eyeSeparation * ndfl;
+
+		transM = glm::translate(glm::mat4(1.0f), camera.position - camRight * (eyeSeparation / 2.0f));
+
+		ubo.projection[0] = glm::frustum(left, right, bottom, top, zNear, zFar);
+		ubo.modelview[0] = rotM * transM;
+
+		// Right eye
+		left = -aspectRatio * wd2 - 0.5f * eyeSeparation * ndfl;
+		right = aspectRatio * wd2 - 0.5f * eyeSeparation * ndfl;
+
+		transM = glm::translate(glm::mat4(1.0f), camera.position + camRight * (eyeSeparation / 2.0f));
+
+		ubo.projection[1] = glm::frustum(left, right, bottom, top, zNear, zFar);
+		ubo.modelview[1] = rotM * transM;
+
+		memcpy(uniformBuffer.mapped, &ubo, sizeof(ubo));
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+
+		// Multiview offscreen render
+		VK_CHECK_RESULT(vkWaitForFences(device, 1, &multiviewPass.waitFences[currentBuffer], VK_TRUE, UINT64_MAX));
+		VK_CHECK_RESULT(vkResetFences(device, 1, &multiviewPass.waitFences[currentBuffer]));
+		submitInfo.pWaitSemaphores = &semaphores.presentComplete;
+		submitInfo.pSignalSemaphores = &multiviewPass.semaphore;
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &multiviewPass.commandBuffers[currentBuffer];
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, multiviewPass.waitFences[currentBuffer]));
+
+		// View display
+		VK_CHECK_RESULT(vkWaitForFences(device, 1, &waitFences[currentBuffer], VK_TRUE, UINT64_MAX));
+		VK_CHECK_RESULT(vkResetFences(device, 1, &waitFences[currentBuffer]));
+		submitInfo.pWaitSemaphores = &multiviewPass.semaphore;
+		submitInfo.pSignalSemaphores = &semaphores.renderComplete;
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, waitFences[currentBuffer]));
+
+		VulkanExampleBase::submitFrame();
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		loadAssets();
+		prepareMultiview();
+		prepareUniformBuffers();
+		prepareDescriptors();
+		preparePipelines();
+		buildCommandBuffers();
+
+		VkFenceCreateInfo fenceCreateInfo = vks::initializers::fenceCreateInfo(VK_FENCE_CREATE_SIGNALED_BIT);
+		multiviewPass.waitFences.resize(multiviewPass.commandBuffers.size());
+		for (auto& fence : multiviewPass.waitFences) {
+			VK_CHECK_RESULT(vkCreateFence(device, &fenceCreateInfo, nullptr, &fence));
+		}
+
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+		if (camera.updated) {
+			updateUniformBuffers();
+		}
+	}
+
+	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
+	{
+		if (overlay->header("Settings")) {
+			if (overlay->sliderFloat("Eye separation", &eyeSeparation, -1.0f, 1.0f)) {
+				updateUniformBuffers();
+			}
+			if (overlay->sliderFloat("Barrel distortion", &ubo.distortionAlpha, -0.6f, 0.6f)) {
+				updateUniformBuffers();
+			}
+		}
+	}
+
+};
+
+VULKAN_EXAMPLE_MAIN()
\ No newline at end of file
diff --git a/external/Vulkan/examples/negativeviewportheight/negativeviewportheight.cpp b/external/Vulkan/examples/negativeviewportheight/negativeviewportheight.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..bf86730b2e171e841c2e8cb3af8a550b60b3602b
--- /dev/null
+++ b/external/Vulkan/examples/negativeviewportheight/negativeviewportheight.cpp
@@ -0,0 +1,326 @@
+/*
+* Vulkan Example - Using negative viewport heights for changing Vulkan's coordinate system
+*
+* Note: Requires a device that supports VK_KHR_MAINTENANCE1
+*
+* Copyright (C) by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkanexamplebase.h"
+
+#define ENABLE_VALIDATION false
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	bool negativeViewport = true;
+	int32_t offsety = 0;
+	int32_t offsetx = 0;
+	int32_t windingOrder = 1;
+	int32_t cullMode = (int32_t)VK_CULL_MODE_BACK_BIT;
+	int32_t quadType = 0;
+
+	VkPipelineLayout pipelineLayout;
+	VkPipeline pipeline = VK_NULL_HANDLE;
+	VkDescriptorSetLayout descriptorSetLayout;
+	struct DescriptorSets {
+		VkDescriptorSet CW;
+		VkDescriptorSet CCW;
+	} descriptorSets;
+
+	struct Textures {
+		vks::Texture2D CW;
+		vks::Texture2D CCW;
+	} textures;
+
+	struct Quad {
+		vks::Buffer verticesYUp;
+		vks::Buffer verticesYDown;
+		vks::Buffer indicesCCW;
+		vks::Buffer indicesCW;
+		void destroy()
+		{
+			verticesYUp.destroy();
+			verticesYDown.destroy();
+			indicesCCW.destroy();
+			indicesCW.destroy();
+		}
+	} quad;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Negative Viewport height";
+		// [POI] VK_KHR_MAINTENANCE1 is required for using negative viewport heights
+		// Note: This is core as of Vulkan 1.1. So if you target 1.1 you don't have to explicitly enable this
+		enabledDeviceExtensions.push_back(VK_KHR_MAINTENANCE1_EXTENSION_NAME);
+	}
+
+	~VulkanExample()
+	{
+		vkDestroyPipeline(device, pipeline, nullptr);
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+		textures.CW.destroy();
+		textures.CCW.destroy();
+		quad.destroy();
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		clearValues[0].color = defaultClearColor;
+		clearValues[1].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.offset.x = 0;
+		renderPassBeginInfo.renderArea.offset.y = 0;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		renderPassBeginInfo.clearValueCount = 2;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i) {
+			renderPassBeginInfo.framebuffer = frameBuffers[i];
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
+
+			// [POI] Viewport setup
+			VkViewport viewport{};
+			if (negativeViewport) {
+				viewport.x = offsetx;
+				// [POI] When using a negative viewport height, the origin needs to be adjusted too
+				viewport.y = (float)height - offsety;
+				viewport.width = (float)width;
+				// [POI] Flip the sign of the viewport's height
+				viewport.height = -(float)height;
+			}
+			else {
+				viewport.x = offsetx;
+				viewport.y = offsety;
+				viewport.width = (float)width;
+				viewport.height = (float)height;
+			}
+			viewport.minDepth = 0.0f;
+			viewport.maxDepth = 1.0f;
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+			VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+			VkDeviceSize offsets[1] = { 0 };
+
+			// Render the quad with clock wise and counter clock wise indices, visibility is determined by pipeline settings
+
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets.CW, 0, nullptr);
+			vkCmdBindIndexBuffer(drawCmdBuffers[i], quad.indicesCW.buffer, 0, VK_INDEX_TYPE_UINT32);
+			vkCmdBindVertexBuffers(drawCmdBuffers[i], 0, 1, quadType == 0 ? &quad.verticesYDown.buffer : &quad.verticesYUp.buffer, offsets);
+			vkCmdDrawIndexed(drawCmdBuffers[i], 6, 1, 0, 0, 0);
+
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets.CCW, 0, nullptr);
+			vkCmdBindIndexBuffer(drawCmdBuffers[i], quad.indicesCCW.buffer, 0, VK_INDEX_TYPE_UINT32);
+			vkCmdDrawIndexed(drawCmdBuffers[i], 6, 1, 0, 0, 0);
+
+			drawUI(drawCmdBuffers[i]);
+
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void loadAssets()
+	{
+		textures.CW.loadFromFile(getAssetPath() + "textures/texture_orientation_cw_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue);
+		textures.CCW.loadFromFile(getAssetPath() + "textures/texture_orientation_ccw_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue);
+
+		// [POI] Create two quads with different Y orientations
+
+		struct Vertex {
+			float pos[3];
+			float uv[2];
+		};
+
+		const float ar = (float)height / (float)width;
+
+		// OpenGL style (y points upwards)
+		std::vector<Vertex> verticesYPos = {
+			{ -1.0f * ar,  1.0f, 1.0f, 0.0f, 1.0f },
+			{ -1.0f * ar, -1.0f, 1.0f, 0.0f, 0.0f },
+			{  1.0f * ar, -1.0f, 1.0f, 1.0f, 0.0f },
+			{  1.0f * ar,  1.0f, 1.0f, 1.0f, 1.0f },
+		};
+
+		// Vulkan style (y points downwards)
+		std::vector<Vertex> verticesYNeg = {
+			{ -1.0f * ar, -1.0f, 1.0f, 0.0f, 1.0f },
+			{ -1.0f * ar,  1.0f, 1.0f, 0.0f, 0.0f },
+			{  1.0f * ar,  1.0f, 1.0f, 1.0f, 0.0f },
+			{  1.0f * ar, -1.0f, 1.0f, 1.0f, 1.0f },
+		};
+
+		const VkMemoryPropertyFlags memoryPropertyFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
+
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, memoryPropertyFlags, &quad.verticesYUp, sizeof(Vertex) * 4, verticesYPos.data()));
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, memoryPropertyFlags, &quad.verticesYDown,  sizeof(Vertex) * 4, verticesYNeg.data()));
+
+		// [POI] Create two set of indices, one for counter clock wise, and one for clock wise rendering
+		std::vector<uint32_t> indices = { 2,1,0, 0,3,2 };
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(VK_BUFFER_USAGE_INDEX_BUFFER_BIT, memoryPropertyFlags, &quad.indicesCCW, indices.size() * sizeof(uint32_t), indices.data()));
+		indices = { 0,1,2, 2,3,0 };
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(VK_BUFFER_USAGE_INDEX_BUFFER_BIT, memoryPropertyFlags, &quad.indicesCW, indices.size() * sizeof(uint32_t), indices.data()));
+	}
+
+	void setupDescriptors()
+	{
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0)
+		};
+		VkDescriptorSetLayoutCreateInfo descriptorLayoutCI = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayoutCI, nullptr, &descriptorSetLayout));
+		VkPipelineLayoutCreateInfo pipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1);
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelineLayout));
+
+		VkDescriptorPoolSize poolSize = vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2);
+		VkDescriptorPoolCreateInfo descriptorPoolCI = vks::initializers::descriptorPoolCreateInfo(1, &poolSize, 2);
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolCI, nullptr, &descriptorPool));
+
+		VkDescriptorSetAllocateInfo descriptorSetAI = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1);
+
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &descriptorSetAI, &descriptorSets.CW));
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &descriptorSetAI, &descriptorSets.CCW));
+
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets = {
+			vks::initializers::writeDescriptorSet(descriptorSets.CW, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &textures.CW.descriptor),
+			vks::initializers::writeDescriptorSet(descriptorSets.CCW, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &textures.CCW.descriptor)
+		};
+		vkUpdateDescriptorSets(device, 2, &writeDescriptorSets[0], 0, nullptr);
+	}
+
+	void preparePipelines()
+	{
+		if (pipeline != VK_NULL_HANDLE) {
+			vkDestroyPipeline(device, pipeline, nullptr);
+		}
+
+		const std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
+
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyStateCI = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendStateCI = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilStateCI = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_FALSE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportStateCI = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+		VkPipelineMultisampleStateCreateInfo multisampleStateCI = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
+		VkPipelineDynamicStateCreateInfo dynamicStateCI = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables.data(), static_cast<uint32_t>(dynamicStateEnables.size()), 0);
+
+		VkPipelineRasterizationStateCreateInfo rasterizationStateCI{};
+		rasterizationStateCI.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
+		rasterizationStateCI.polygonMode = VK_POLYGON_MODE_FILL;
+		rasterizationStateCI.lineWidth = 1.0f;
+		rasterizationStateCI.cullMode = VK_CULL_MODE_NONE + cullMode;
+		rasterizationStateCI.frontFace = windingOrder == 0 ? VK_FRONT_FACE_CLOCKWISE : VK_FRONT_FACE_COUNTER_CLOCKWISE;
+
+		// Vertex bindings and attributes
+		std::vector<VkVertexInputBindingDescription> vertexInputBindings = {
+			vks::initializers::vertexInputBindingDescription(0, sizeof(float) * 5, VK_VERTEX_INPUT_RATE_VERTEX),
+		};
+		std::vector<VkVertexInputAttributeDescription> vertexInputAttributes = {
+			vks::initializers::vertexInputAttributeDescription(0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0),				// Position
+			vks::initializers::vertexInputAttributeDescription(0, 1, VK_FORMAT_R32G32_SFLOAT, sizeof(float) * 3),	// uv
+		};
+		VkPipelineVertexInputStateCreateInfo vertexInputState = vks::initializers::pipelineVertexInputStateCreateInfo();
+		vertexInputState.vertexBindingDescriptionCount = static_cast<uint32_t>(vertexInputBindings.size());
+		vertexInputState.pVertexBindingDescriptions = vertexInputBindings.data();
+		vertexInputState.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertexInputAttributes.size());
+		vertexInputState.pVertexAttributeDescriptions = vertexInputAttributes.data();
+
+		VkGraphicsPipelineCreateInfo pipelineCreateInfoCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass, 0);
+		//pipelineCreateInfoCI.pVertexInputState = &emptyInputState;
+		pipelineCreateInfoCI.pVertexInputState = &vertexInputState;
+		pipelineCreateInfoCI.pInputAssemblyState = &inputAssemblyStateCI;
+		pipelineCreateInfoCI.pRasterizationState = &rasterizationStateCI;
+		pipelineCreateInfoCI.pColorBlendState = &colorBlendStateCI;
+		pipelineCreateInfoCI.pMultisampleState = &multisampleStateCI;
+		pipelineCreateInfoCI.pViewportState = &viewportStateCI;
+		pipelineCreateInfoCI.pDepthStencilState = &depthStencilStateCI;
+		pipelineCreateInfoCI.pDynamicState = &dynamicStateCI;
+
+		const std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages = {
+			loadShader(getShadersPath() + "negativeviewportheight/quad.vert.spv", VK_SHADER_STAGE_VERTEX_BIT),
+			loadShader(getShadersPath() + "negativeviewportheight/quad.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT)
+		};
+
+		pipelineCreateInfoCI.stageCount = static_cast<uint32_t>(shaderStages.size());
+		pipelineCreateInfoCI.pStages = shaderStages.data();
+
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfoCI, nullptr, &pipeline));
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+		VulkanExampleBase::submitFrame();
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		loadAssets();
+		setupDescriptors();
+		preparePipelines();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+	}
+
+	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
+	{
+		if (overlay->header("Scene")) {
+			overlay->text("Quad type");
+			if (overlay->comboBox("##quadtype", &quadType, { "VK (y negative)", "GL (y positive)" })) {
+				buildCommandBuffers();
+			}
+		}
+
+		if (overlay->header("Viewport")) {
+			if (overlay->checkBox("Negative viewport height", &negativeViewport)) {
+				buildCommandBuffers();
+			}
+			if (overlay->sliderInt("offset x", &offsetx, -(int32_t)width, (int32_t)width)) {
+				buildCommandBuffers();
+			}
+			if (overlay->sliderInt("offset y", &offsety, -(int32_t)height, (int32_t)height)) {
+				buildCommandBuffers();
+			}
+		}
+		if (overlay->header("Pipeline")) {
+			overlay->text("Winding order");
+			if (overlay->comboBox("##windingorder", &windingOrder, { "clock wise", "counter clock wise" })) {
+				preparePipelines();
+			}
+			overlay->text("Cull mode");
+			if (overlay->comboBox("##cullmode", &cullMode, { "none", "front face", "back face" })) {
+				preparePipelines();
+			}
+		}
+	}
+};
+
+VULKAN_EXAMPLE_MAIN()
\ No newline at end of file
diff --git a/external/Vulkan/examples/occlusionquery/occlusionquery.cpp b/external/Vulkan/examples/occlusionquery/occlusionquery.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..9db43e6d94b17f0118c0b923dbb528615f7eeacb
--- /dev/null
+++ b/external/Vulkan/examples/occlusionquery/occlusionquery.cpp
@@ -0,0 +1,470 @@
+/*
+* Vulkan Example - Using occlusion query for visibility testing
+*
+* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkanexamplebase.h"
+#include "VulkanglTFModel.h"
+
+#define VERTEX_BUFFER_BIND_ID 0
+#define ENABLE_VALIDATION false
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+
+	struct {
+		vkglTF::Model teapot;
+		vkglTF::Model plane;
+		vkglTF::Model sphere;
+	} models;
+
+	struct {
+		vks::Buffer occluder;
+		vks::Buffer teapot;
+		vks::Buffer sphere;
+	} uniformBuffers;
+
+	struct UBOVS {
+		glm::mat4 projection;
+		glm::mat4 view;
+		glm::mat4 model;
+		glm::vec4 color = glm::vec4(0.0f);
+		glm::vec4 lightPos = glm::vec4(10.0f, -10.0f, 10.0f, 1.0f);
+		float visible;
+	} uboVS;
+
+	struct {
+		VkPipeline solid;
+		VkPipeline occluder;
+		// Pipeline with basic shaders used for occlusion pass
+		VkPipeline simple;
+	} pipelines;
+
+	struct {
+		VkDescriptorSet teapot;
+		VkDescriptorSet sphere;
+	} descriptorSets;
+
+	VkPipelineLayout pipelineLayout;
+	VkDescriptorSet descriptorSet;
+	VkDescriptorSetLayout descriptorSetLayout;
+
+	// Pool that stores all occlusion queries
+	VkQueryPool queryPool;
+
+	// Passed query samples
+	uint64_t passedSamples[2] = { 1,1 };
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Occlusion queries";
+		camera.type = Camera::CameraType::lookat;
+		camera.setPosition(glm::vec3(0.0f, 0.0f, -7.5f));
+		camera.setRotation(glm::vec3(0.0f, -123.75f, 0.0f));
+		camera.setRotationSpeed(0.5f);
+		camera.setPerspective(60.0f, (float)width / (float)height, 1.0f, 256.0f);
+	}
+
+	~VulkanExample()
+	{
+		// Clean up used Vulkan resources
+		// Note : Inherited destructor cleans up resources stored in base class
+		vkDestroyPipeline(device, pipelines.solid, nullptr);
+		vkDestroyPipeline(device, pipelines.occluder, nullptr);
+		vkDestroyPipeline(device, pipelines.simple, nullptr);
+
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+
+		vkDestroyQueryPool(device, queryPool, nullptr);
+
+		uniformBuffers.occluder.destroy();
+		uniformBuffers.sphere.destroy();
+		uniformBuffers.teapot.destroy();
+	}
+
+	// Create a query pool for storing the occlusion query result
+	void setupQueryPool()
+	{
+		VkQueryPoolCreateInfo queryPoolInfo = {};
+		queryPoolInfo.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO;
+		queryPoolInfo.queryType = VK_QUERY_TYPE_OCCLUSION;
+		queryPoolInfo.queryCount = 2;
+		VK_CHECK_RESULT(vkCreateQueryPool(device, &queryPoolInfo, NULL, &queryPool));
+	}
+
+	// Retrieves the results of the occlusion queries submitted to the command buffer
+	void getQueryResults()
+	{
+		// We use vkGetQueryResults to copy the results into a host visible buffer
+		vkGetQueryPoolResults(
+			device,
+			queryPool,
+			0,
+			2,
+			sizeof(passedSamples),
+			passedSamples,
+			sizeof(uint64_t),
+			// Store results a 64 bit values and wait until the results have been finished
+			// If you don't want to wait, you can use VK_QUERY_RESULT_WITH_AVAILABILITY_BIT
+			// which also returns the state of the result (ready) in the result
+			VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT);
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		clearValues[0].color = defaultClearColor;
+		clearValues[1].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.offset.x = 0;
+		renderPassBeginInfo.renderArea.offset.y = 0;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		renderPassBeginInfo.clearValueCount = 2;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			// Set target frame buffer
+			renderPassBeginInfo.framebuffer = frameBuffers[i];
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			// Reset query pool
+			// Must be done outside of render pass
+			vkCmdResetQueryPool(drawCmdBuffers[i], queryPool, 0, 2);
+
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			VkViewport viewport = vks::initializers::viewport(
+				(float)width,
+				(float)height,
+				0.0f,
+				1.0f);
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+			VkRect2D scissor = vks::initializers::rect2D(
+				width,
+				height,
+				0,
+				0);
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+			VkDeviceSize offsets[1] = { 0 };
+
+			glm::mat4 modelMatrix = glm::mat4(1.0f);
+
+			// Occlusion pass
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.simple);
+
+			// Occluder first
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL);
+			models.plane.draw(drawCmdBuffers[i]);
+
+			// Teapot
+			vkCmdBeginQuery(drawCmdBuffers[i], queryPool, 0, VK_FLAGS_NONE);
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets.teapot, 0, NULL);
+			models.teapot.draw(drawCmdBuffers[i]);
+			vkCmdEndQuery(drawCmdBuffers[i], queryPool, 0);
+
+			// Sphere
+			vkCmdBeginQuery(drawCmdBuffers[i], queryPool, 1, VK_FLAGS_NONE);
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets.sphere, 0, NULL);
+			models.sphere.draw(drawCmdBuffers[i]);
+			vkCmdEndQuery(drawCmdBuffers[i], queryPool, 1);
+
+			// Visible pass
+			// Clear color and depth attachments
+			VkClearAttachment clearAttachments[2] = {};
+
+			clearAttachments[0].aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+			clearAttachments[0].clearValue.color = defaultClearColor;
+			clearAttachments[0].colorAttachment = 0;
+
+			clearAttachments[1].aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
+			clearAttachments[1].clearValue.depthStencil = { 1.0f, 0 };
+
+			VkClearRect clearRect = {};
+			clearRect.layerCount = 1;
+			clearRect.rect.offset = { 0, 0 };
+			clearRect.rect.extent = { width, height };
+
+			vkCmdClearAttachments(
+				drawCmdBuffers[i],
+				2,
+				clearAttachments,
+				1,
+				&clearRect);
+
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.solid);
+
+			// Teapot
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets.teapot, 0, NULL);
+			models.teapot.draw(drawCmdBuffers[i]);
+
+			// Sphere
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets.sphere, 0, NULL);
+			models.sphere.draw(drawCmdBuffers[i]);
+
+			// Occluder
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.occluder);
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL);
+			models.plane.draw(drawCmdBuffers[i]);
+
+			drawUI(drawCmdBuffers[i]);
+
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void draw()
+	{
+		updateUniformBuffers();
+		VulkanExampleBase::prepareFrame();
+
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+
+		// Read query results for displaying in next frame
+		getQueryResults();
+
+		VulkanExampleBase::submitFrame();
+	}
+
+	void loadAssets()
+	{
+		const uint32_t glTFLoadingFlags = vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::PreMultiplyVertexColors | vkglTF::FileLoadingFlags::FlipY;
+		models.plane.loadFromFile(getAssetPath() + "models/plane_z.gltf", vulkanDevice, queue, glTFLoadingFlags);
+		models.teapot.loadFromFile(getAssetPath() + "models/teapot.gltf", vulkanDevice, queue, glTFLoadingFlags);
+		models.sphere.loadFromFile(getAssetPath() + "models/sphere.gltf", vulkanDevice, queue, glTFLoadingFlags);
+	}
+
+	void setupDescriptorPool()
+	{
+		std::vector<VkDescriptorPoolSize> poolSizes =
+		{
+			// One uniform buffer block for each mesh
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 3)
+		};
+
+		VkDescriptorPoolCreateInfo descriptorPoolInfo =
+			vks::initializers::descriptorPoolCreateInfo(
+				poolSizes.size(),
+				poolSizes.data(),
+				3);
+
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+	}
+
+	void setupDescriptorSetLayout()
+	{
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings =
+		{
+			// Binding 0 : Vertex shader uniform buffer
+			vks::initializers::descriptorSetLayoutBinding(
+				VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+				VK_SHADER_STAGE_VERTEX_BIT,
+				0)
+		};
+
+		VkDescriptorSetLayoutCreateInfo descriptorLayout =
+			vks::initializers::descriptorSetLayoutCreateInfo(
+				setLayoutBindings.data(),
+				setLayoutBindings.size());
+
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout));
+
+		VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo =
+			vks::initializers::pipelineLayoutCreateInfo(
+				&descriptorSetLayout,
+				1);
+
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayout));
+	}
+
+	void setupDescriptorSets()
+	{
+		VkDescriptorSetAllocateInfo allocInfo =
+			vks::initializers::descriptorSetAllocateInfo(
+				descriptorPool,
+				&descriptorSetLayout,
+				1);
+
+		// Occluder (plane)
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet));
+
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets =
+		{
+			// Binding 0 : Vertex shader uniform buffer
+			vks::initializers::writeDescriptorSet(
+				descriptorSet,
+				VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+				0,
+				&uniformBuffers.occluder.descriptor)
+		};
+
+		vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, NULL);
+
+		// Teapot
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.teapot));
+		writeDescriptorSets[0].dstSet = descriptorSets.teapot;
+		writeDescriptorSets[0].pBufferInfo = &uniformBuffers.teapot.descriptor;
+		vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, NULL);
+
+		// Sphere
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.sphere));
+		writeDescriptorSets[0].dstSet = descriptorSets.sphere;
+		writeDescriptorSets[0].pBufferInfo = &uniformBuffers.sphere.descriptor;
+		vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, NULL);
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+		VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
+		std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
+		VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass, 0);
+		pipelineCI.pInputAssemblyState = &inputAssemblyState;
+		pipelineCI.pRasterizationState = &rasterizationState;
+		pipelineCI.pColorBlendState = &colorBlendState;
+		pipelineCI.pMultisampleState = &multisampleState;
+		pipelineCI.pViewportState = &viewportState;
+		pipelineCI.pDepthStencilState = &depthStencilState;
+		pipelineCI.pDynamicState = &dynamicState;
+		pipelineCI.stageCount = shaderStages.size();
+		pipelineCI.pStages = shaderStages.data();
+		pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::Color });;
+
+		// Solid rendering pipeline
+		shaderStages[0] = loadShader(getShadersPath() + "occlusionquery/mesh.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "occlusionquery/mesh.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.solid));
+
+		// Basic pipeline for coloring occluded objects
+		shaderStages[0] = loadShader(getShadersPath() + "occlusionquery/simple.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "occlusionquery/simple.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		rasterizationState.cullMode = VK_CULL_MODE_NONE;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.simple));
+
+		// Visual pipeline for the occluder
+		shaderStages[0] = loadShader(getShadersPath() + "occlusionquery/occluder.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "occlusionquery/occluder.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		// Enable blending
+		blendAttachmentState.blendEnable = VK_TRUE;
+		blendAttachmentState.colorBlendOp = VK_BLEND_OP_ADD;
+		blendAttachmentState.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_COLOR;
+		blendAttachmentState.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.occluder));
+	}
+
+	// Prepare and initialize uniform buffer containing shader uniforms
+	void prepareUniformBuffers()
+	{
+		// Vertex shader uniform buffer block
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.occluder,
+			sizeof(uboVS)));
+
+		// Teapot
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.teapot,
+			sizeof(uboVS)));
+
+		// Sphere
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.sphere,
+			sizeof(uboVS)));
+
+		// Map persistent
+		VK_CHECK_RESULT(uniformBuffers.occluder.map());
+		VK_CHECK_RESULT(uniformBuffers.teapot.map());
+		VK_CHECK_RESULT(uniformBuffers.sphere.map());
+
+		updateUniformBuffers();
+	}
+
+	void updateUniformBuffers()
+	{
+		uboVS.projection = camera.matrices.perspective;
+		uboVS.view = camera.matrices.view;
+
+		uint8_t *pData;
+		// Occluder
+		uboVS.visible = 1.0f;
+		uboVS.model = glm::scale(glm::mat4(1.0f), glm::vec3(6.0f));
+		uboVS.color = glm::vec4(0.0f, 0.0f, 1.0f, 0.5f);
+		memcpy(uniformBuffers.occluder.mapped, &uboVS, sizeof(uboVS));
+
+		// Teapot
+		// Toggle color depending on visibility
+		uboVS.visible = (passedSamples[0] > 0) ? 1.0f : 0.0f;
+		uboVS.model = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, -3.0f));
+		uboVS.color = glm::vec4(1.0f, 0.0f, 0.0f, 1.0f);
+		memcpy(uniformBuffers.teapot.mapped, &uboVS, sizeof(uboVS));
+
+		// Sphere
+		// Toggle color depending on visibility
+		uboVS.visible = (passedSamples[1] > 0) ? 1.0f : 0.0f;
+		uboVS.model = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, 3.0f));
+		uboVS.color = glm::vec4(0.0f, 1.0f, 0.0f, 1.0f);
+		memcpy(uniformBuffers.sphere.mapped, &uboVS, sizeof(uboVS));
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		loadAssets();
+		setupQueryPool();
+		prepareUniformBuffers();
+		setupDescriptorSetLayout();
+		preparePipelines();
+		setupDescriptorPool();
+		setupDescriptorSets();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+	}
+
+	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
+	{
+		if (overlay->header("Occlusion query results")) {
+			overlay->text("Teapot: %d samples passed", passedSamples[0]);
+			overlay->text("Sphere: %d samples passed", passedSamples[1]);
+		}
+	}
+
+};
+
+VULKAN_EXAMPLE_MAIN()
diff --git a/external/Vulkan/examples/offscreen/offscreen.cpp b/external/Vulkan/examples/offscreen/offscreen.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..4ae6cbe70c9964212abde348ce8b03cf0bf2d47a
--- /dev/null
+++ b/external/Vulkan/examples/offscreen/offscreen.cpp
@@ -0,0 +1,684 @@
+/*
+* Vulkan Example - Offscreen rendering using a separate framebuffer
+*
+* Copyright (C) Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkanexamplebase.h"
+#include "VulkanglTFModel.h"
+
+#define ENABLE_VALIDATION false
+
+// Offscreen frame buffer properties
+#define FB_DIM 512
+#define FB_COLOR_FORMAT VK_FORMAT_R8G8B8A8_UNORM
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	bool debugDisplay = false;
+
+	struct {
+		vkglTF::Model example;
+		vkglTF::Model plane;
+	} models;
+
+	struct {
+		vks::Buffer vsShared;
+		vks::Buffer vsMirror;
+		vks::Buffer vsOffScreen;
+	} uniformBuffers;
+
+	struct UBO {
+		glm::mat4 projection;
+		glm::mat4 view;
+		glm::mat4 model;
+		glm::vec4 lightPos = glm::vec4(0.0f, 0.0f, 0.0f, 1.0f);
+	} uboShared;
+
+	struct {
+		VkPipeline debug;
+		VkPipeline shaded;
+		VkPipeline shadedOffscreen;
+		VkPipeline mirror;
+	} pipelines;
+
+	struct {
+		VkPipelineLayout textured;
+		VkPipelineLayout shaded;
+	} pipelineLayouts;
+
+	struct {
+		VkDescriptorSet offscreen;
+		VkDescriptorSet mirror;
+		VkDescriptorSet model;
+	} descriptorSets;
+
+	struct {
+		VkDescriptorSetLayout textured;
+		VkDescriptorSetLayout shaded;
+	} descriptorSetLayouts;
+
+	// Framebuffer for offscreen rendering
+	struct FrameBufferAttachment {
+		VkImage image;
+		VkDeviceMemory mem;
+		VkImageView view;
+	};
+	struct OffscreenPass {
+		int32_t width, height;
+		VkFramebuffer frameBuffer;
+		FrameBufferAttachment color, depth;
+		VkRenderPass renderPass;
+		VkSampler sampler;
+		VkDescriptorImageInfo descriptor;
+	} offscreenPass;
+
+	glm::vec3 modelPosition = glm::vec3(0.0f, -1.0f, 0.0f);
+	glm::vec3 modelRotation = glm::vec3(0.0f);
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Offscreen rendering";
+		timerSpeed *= 0.25f;
+		camera.type = Camera::CameraType::lookat;
+		camera.setPosition(glm::vec3(0.0f, 1.0f, -6.0f));
+		camera.setRotation(glm::vec3(-2.5f, 0.0f, 0.0f));
+		camera.setRotationSpeed(0.5f);
+		camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 256.0f);
+		// The scene shader uses a clipping plane, so this feature has to be enabled
+		enabledFeatures.shaderClipDistance = VK_TRUE;
+	}
+
+	~VulkanExample()
+	{
+		// Clean up used Vulkan resources
+		// Note : Inherited destructor cleans up resources stored in base class
+
+		// Frame buffer
+
+		// Color attachment
+		vkDestroyImageView(device, offscreenPass.color.view, nullptr);
+		vkDestroyImage(device, offscreenPass.color.image, nullptr);
+		vkFreeMemory(device, offscreenPass.color.mem, nullptr);
+
+		// Depth attachment
+		vkDestroyImageView(device, offscreenPass.depth.view, nullptr);
+		vkDestroyImage(device, offscreenPass.depth.image, nullptr);
+		vkFreeMemory(device, offscreenPass.depth.mem, nullptr);
+
+		vkDestroyRenderPass(device, offscreenPass.renderPass, nullptr);
+		vkDestroySampler(device, offscreenPass.sampler, nullptr);
+		vkDestroyFramebuffer(device, offscreenPass.frameBuffer, nullptr);
+
+		vkDestroyPipeline(device, pipelines.debug, nullptr);
+		vkDestroyPipeline(device, pipelines.shaded, nullptr);
+		vkDestroyPipeline(device, pipelines.shadedOffscreen, nullptr);
+		vkDestroyPipeline(device, pipelines.mirror, nullptr);
+
+		vkDestroyPipelineLayout(device, pipelineLayouts.textured, nullptr);
+		vkDestroyPipelineLayout(device, pipelineLayouts.shaded, nullptr);
+
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.shaded, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.textured, nullptr);
+
+		// Uniform buffers
+		uniformBuffers.vsShared.destroy();
+		uniformBuffers.vsMirror.destroy();
+		uniformBuffers.vsOffScreen.destroy();
+	}
+
+	// Setup the offscreen framebuffer for rendering the mirrored scene
+	// The color attachment of this framebuffer will then be used to sample from in the fragment shader of the final pass
+	void prepareOffscreen()
+	{
+		offscreenPass.width = FB_DIM;
+		offscreenPass.height = FB_DIM;
+
+		// Find a suitable depth format
+		VkFormat fbDepthFormat;
+		VkBool32 validDepthFormat = vks::tools::getSupportedDepthFormat(physicalDevice, &fbDepthFormat);
+		assert(validDepthFormat);
+
+		// Color attachment
+		VkImageCreateInfo image = vks::initializers::imageCreateInfo();
+		image.imageType = VK_IMAGE_TYPE_2D;
+		image.format = FB_COLOR_FORMAT;
+		image.extent.width = offscreenPass.width;
+		image.extent.height = offscreenPass.height;
+		image.extent.depth = 1;
+		image.mipLevels = 1;
+		image.arrayLayers = 1;
+		image.samples = VK_SAMPLE_COUNT_1_BIT;
+		image.tiling = VK_IMAGE_TILING_OPTIMAL;
+		// We will sample directly from the color attachment
+		image.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
+
+		VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo();
+		VkMemoryRequirements memReqs;
+
+		VK_CHECK_RESULT(vkCreateImage(device, &image, nullptr, &offscreenPass.color.image));
+		vkGetImageMemoryRequirements(device, offscreenPass.color.image, &memReqs);
+		memAlloc.allocationSize = memReqs.size;
+		memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+		VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &offscreenPass.color.mem));
+		VK_CHECK_RESULT(vkBindImageMemory(device, offscreenPass.color.image, offscreenPass.color.mem, 0));
+
+		VkImageViewCreateInfo colorImageView = vks::initializers::imageViewCreateInfo();
+		colorImageView.viewType = VK_IMAGE_VIEW_TYPE_2D;
+		colorImageView.format = FB_COLOR_FORMAT;
+		colorImageView.subresourceRange = {};
+		colorImageView.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+		colorImageView.subresourceRange.baseMipLevel = 0;
+		colorImageView.subresourceRange.levelCount = 1;
+		colorImageView.subresourceRange.baseArrayLayer = 0;
+		colorImageView.subresourceRange.layerCount = 1;
+		colorImageView.image = offscreenPass.color.image;
+		VK_CHECK_RESULT(vkCreateImageView(device, &colorImageView, nullptr, &offscreenPass.color.view));
+
+		// Create sampler to sample from the attachment in the fragment shader
+		VkSamplerCreateInfo samplerInfo = vks::initializers::samplerCreateInfo();
+		samplerInfo.magFilter = VK_FILTER_LINEAR;
+		samplerInfo.minFilter = VK_FILTER_LINEAR;
+		samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+		samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+		samplerInfo.addressModeV = samplerInfo.addressModeU;
+		samplerInfo.addressModeW = samplerInfo.addressModeU;
+		samplerInfo.mipLodBias = 0.0f;
+		samplerInfo.maxAnisotropy = 1.0f;
+		samplerInfo.minLod = 0.0f;
+		samplerInfo.maxLod = 1.0f;
+		samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
+		VK_CHECK_RESULT(vkCreateSampler(device, &samplerInfo, nullptr, &offscreenPass.sampler));
+
+		// Depth stencil attachment
+		image.format = fbDepthFormat;
+		image.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
+
+		VK_CHECK_RESULT(vkCreateImage(device, &image, nullptr, &offscreenPass.depth.image));
+		vkGetImageMemoryRequirements(device, offscreenPass.depth.image, &memReqs);
+		memAlloc.allocationSize = memReqs.size;
+		memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+		VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &offscreenPass.depth.mem));
+		VK_CHECK_RESULT(vkBindImageMemory(device, offscreenPass.depth.image, offscreenPass.depth.mem, 0));
+
+		VkImageViewCreateInfo depthStencilView = vks::initializers::imageViewCreateInfo();
+		depthStencilView.viewType = VK_IMAGE_VIEW_TYPE_2D;
+		depthStencilView.format = fbDepthFormat;
+		depthStencilView.flags = 0;
+		depthStencilView.subresourceRange = {};
+		depthStencilView.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
+		depthStencilView.subresourceRange.baseMipLevel = 0;
+		depthStencilView.subresourceRange.levelCount = 1;
+		depthStencilView.subresourceRange.baseArrayLayer = 0;
+		depthStencilView.subresourceRange.layerCount = 1;
+		depthStencilView.image = offscreenPass.depth.image;
+		VK_CHECK_RESULT(vkCreateImageView(device, &depthStencilView, nullptr, &offscreenPass.depth.view));
+
+		// Create a separate render pass for the offscreen rendering as it may differ from the one used for scene rendering
+
+		std::array<VkAttachmentDescription, 2> attchmentDescriptions = {};
+		// Color attachment
+		attchmentDescriptions[0].format = FB_COLOR_FORMAT;
+		attchmentDescriptions[0].samples = VK_SAMPLE_COUNT_1_BIT;
+		attchmentDescriptions[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+		attchmentDescriptions[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+		attchmentDescriptions[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+		attchmentDescriptions[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+		attchmentDescriptions[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+		attchmentDescriptions[0].finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+		// Depth attachment
+		attchmentDescriptions[1].format = fbDepthFormat;
+		attchmentDescriptions[1].samples = VK_SAMPLE_COUNT_1_BIT;
+		attchmentDescriptions[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+		attchmentDescriptions[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+		attchmentDescriptions[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+		attchmentDescriptions[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+		attchmentDescriptions[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+		attchmentDescriptions[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+
+		VkAttachmentReference colorReference = { 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
+		VkAttachmentReference depthReference = { 1, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL };
+
+		VkSubpassDescription subpassDescription = {};
+		subpassDescription.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+		subpassDescription.colorAttachmentCount = 1;
+		subpassDescription.pColorAttachments = &colorReference;
+		subpassDescription.pDepthStencilAttachment = &depthReference;
+
+		// Use subpass dependencies for layout transitions
+		std::array<VkSubpassDependency, 2> dependencies;
+
+		dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
+		dependencies[0].dstSubpass = 0;
+		dependencies[0].srcStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
+		dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+		dependencies[0].srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
+		dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+		dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+		dependencies[1].srcSubpass = 0;
+		dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
+		dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+		dependencies[1].dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
+		dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+		dependencies[1].dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
+		dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+		// Create the actual renderpass
+		VkRenderPassCreateInfo renderPassInfo = {};
+		renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
+		renderPassInfo.attachmentCount = static_cast<uint32_t>(attchmentDescriptions.size());
+		renderPassInfo.pAttachments = attchmentDescriptions.data();
+		renderPassInfo.subpassCount = 1;
+		renderPassInfo.pSubpasses = &subpassDescription;
+		renderPassInfo.dependencyCount = static_cast<uint32_t>(dependencies.size());
+		renderPassInfo.pDependencies = dependencies.data();
+
+		VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassInfo, nullptr, &offscreenPass.renderPass));
+
+		VkImageView attachments[2];
+		attachments[0] = offscreenPass.color.view;
+		attachments[1] = offscreenPass.depth.view;
+
+		VkFramebufferCreateInfo fbufCreateInfo = vks::initializers::framebufferCreateInfo();
+		fbufCreateInfo.renderPass = offscreenPass.renderPass;
+		fbufCreateInfo.attachmentCount = 2;
+		fbufCreateInfo.pAttachments = attachments;
+		fbufCreateInfo.width = offscreenPass.width;
+		fbufCreateInfo.height = offscreenPass.height;
+		fbufCreateInfo.layers = 1;
+
+		VK_CHECK_RESULT(vkCreateFramebuffer(device, &fbufCreateInfo, nullptr, &offscreenPass.frameBuffer));
+
+		// Fill a descriptor for later use in a descriptor set
+		offscreenPass.descriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+		offscreenPass.descriptor.imageView = offscreenPass.color.view;
+		offscreenPass.descriptor.sampler = offscreenPass.sampler;
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		VkDeviceSize offsets[1] = { 0 };
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			/*
+				First render pass: Offscreen rendering
+			*/
+			{
+				VkClearValue clearValues[2];
+				clearValues[0].color = { { 0.0f, 0.0f, 0.0f, 0.0f } };
+				clearValues[1].depthStencil = { 1.0f, 0 };
+
+				VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+				renderPassBeginInfo.renderPass = offscreenPass.renderPass;
+				renderPassBeginInfo.framebuffer = offscreenPass.frameBuffer;
+				renderPassBeginInfo.renderArea.extent.width = offscreenPass.width;
+				renderPassBeginInfo.renderArea.extent.height = offscreenPass.height;
+				renderPassBeginInfo.clearValueCount = 2;
+				renderPassBeginInfo.pClearValues = clearValues;
+
+				vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+				VkViewport viewport = vks::initializers::viewport((float)offscreenPass.width, (float)offscreenPass.height, 0.0f, 1.0f);
+				vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+				VkRect2D scissor = vks::initializers::rect2D(offscreenPass.width, offscreenPass.height, 0, 0);
+				vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+				VkDeviceSize offsets[1] = { 0 };
+
+				// Mirrored scene
+				vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayouts.shaded, 0, 1, &descriptorSets.offscreen, 0, NULL);
+				vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.shadedOffscreen);
+				models.example.draw(drawCmdBuffers[i]);
+
+				vkCmdEndRenderPass(drawCmdBuffers[i]);
+			}
+
+			/*
+				Note: Explicit synchronization is not required between the render pass, as this is done implicit via sub pass dependencies
+			*/
+
+			/*
+				Second render pass: Scene rendering with applied radial blur
+			*/
+			{
+				clearValues[0].color = defaultClearColor;
+				clearValues[1].depthStencil = { 1.0f, 0 };
+
+				VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+				renderPassBeginInfo.renderPass = renderPass;
+				renderPassBeginInfo.framebuffer = frameBuffers[i];
+				renderPassBeginInfo.renderArea.extent.width = width;
+				renderPassBeginInfo.renderArea.extent.height = height;
+				renderPassBeginInfo.clearValueCount = 2;
+				renderPassBeginInfo.pClearValues = clearValues;
+
+				vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+				VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+				vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+				VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
+				vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+				VkDeviceSize offsets[1] = { 0 };
+
+				if (debugDisplay)
+				{
+					// Display the offscreen render target
+					vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayouts.textured, 0, 1, &descriptorSets.mirror, 0, nullptr);
+					vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.debug);
+					vkCmdDraw(drawCmdBuffers[i], 3, 1, 0, 0);
+				} else {
+					// Render the scene
+					// Reflection plane
+					vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayouts.textured, 0, 1, &descriptorSets.mirror, 0, nullptr);
+					vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.mirror);
+					models.plane.draw(drawCmdBuffers[i]);
+					// Model
+					vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayouts.shaded, 0, 1, &descriptorSets.model, 0, nullptr);
+					vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.shaded);
+					models.example.draw(drawCmdBuffers[i]);
+				}
+
+				drawUI(drawCmdBuffers[i]);
+
+				vkCmdEndRenderPass(drawCmdBuffers[i]);
+			}
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void loadAssets()
+	{
+		const uint32_t glTFLoadingFlags = vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::PreMultiplyVertexColors | vkglTF::FileLoadingFlags::FlipY;
+		models.plane.loadFromFile(getAssetPath() + "models/plane.gltf", vulkanDevice, queue, glTFLoadingFlags);
+		models.example.loadFromFile(getAssetPath() + "models/chinesedragon.gltf", vulkanDevice, queue, glTFLoadingFlags);
+	}
+
+	void setupDescriptorPool()
+	{
+		std::vector<VkDescriptorPoolSize> poolSizes = {
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 6),
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 8)
+		};
+		VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 5);
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+	}
+
+	void setupDescriptorSetLayout()
+	{
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings;
+		VkDescriptorSetLayoutCreateInfo descriptorLayoutInfo;
+		VkPipelineLayoutCreateInfo pipelineLayoutInfo;
+
+		// Binding 0 : Vertex shader uniform buffer
+		setLayoutBindings.push_back(vks::initializers::descriptorSetLayoutBinding(
+			VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+			VK_SHADER_STAGE_VERTEX_BIT,
+			0));
+		// Binding 1 : Fragment shader image sampler
+		setLayoutBindings.push_back(vks::initializers::descriptorSetLayoutBinding(
+			VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+			VK_SHADER_STAGE_FRAGMENT_BIT,
+			1));
+		// Binding 2 : Fragment shader image sampler
+		setLayoutBindings.push_back(vks::initializers::descriptorSetLayoutBinding(
+			VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+			VK_SHADER_STAGE_FRAGMENT_BIT,
+			2));
+
+		// Shaded layouts (only use first layout binding)
+		descriptorLayoutInfo = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings.data(), 1);
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayoutInfo, nullptr, &descriptorSetLayouts.shaded));
+
+		pipelineLayoutInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayouts.shaded, 1);
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayouts.shaded));
+
+		// Textured layouts (use all layout bindings)
+		descriptorLayoutInfo = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings.data(), static_cast<uint32_t>(setLayoutBindings.size()));
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayoutInfo, nullptr, &descriptorSetLayouts.textured));
+
+		pipelineLayoutInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayouts.textured, 1);
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayouts.textured));
+	}
+
+	void setupDescriptorSet()
+	{
+		// Mirror plane descriptor set
+		VkDescriptorSetAllocateInfo allocInfo =
+			vks::initializers::descriptorSetAllocateInfo(
+				descriptorPool,
+				&descriptorSetLayouts.textured,
+				1);
+
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.mirror));
+
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets =
+		{
+			// Binding 0 : Vertex shader uniform buffer
+			vks::initializers::writeDescriptorSet(
+				descriptorSets.mirror,
+				VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+				0,
+				&uniformBuffers.vsMirror.descriptor),
+			// Binding 1 : Fragment shader texture sampler
+			vks::initializers::writeDescriptorSet(
+				descriptorSets.mirror,
+				VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+				1,
+				&offscreenPass.descriptor),
+		};
+
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr);
+
+		// Shaded descriptor sets
+		allocInfo.pSetLayouts = &descriptorSetLayouts.shaded;
+
+		// Model
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.model));
+
+		std::vector<VkWriteDescriptorSet> modelWriteDescriptorSets =
+		{
+			// Binding 0 : Vertex shader uniform buffer
+			vks::initializers::writeDescriptorSet(
+				descriptorSets.model,
+				VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+				0,
+				&uniformBuffers.vsShared.descriptor)
+		};
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(modelWriteDescriptorSets.size()), modelWriteDescriptorSets.data(), 0, nullptr);
+
+		// Offscreen
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.offscreen));
+
+		std::vector<VkWriteDescriptorSet> offScreenWriteDescriptorSets =
+		{
+			// Binding 0 : Vertex shader uniform buffer
+			vks::initializers::writeDescriptorSet(
+				descriptorSets.offscreen,
+				VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+				0,
+				&uniformBuffers.vsOffScreen.descriptor)
+		};
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(offScreenWriteDescriptorSets.size()), offScreenWriteDescriptorSets.data(), 0, nullptr);
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+ 		VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE,0);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+		VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
+		std::vector<VkDynamicState> dynamicStateEnables = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
+		VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayouts.textured, renderPass, 0);
+		pipelineCI.pInputAssemblyState = &inputAssemblyState;
+		pipelineCI.pRasterizationState = &rasterizationState;
+		pipelineCI.pColorBlendState = &colorBlendState;
+		pipelineCI.pMultisampleState = &multisampleState;
+		pipelineCI.pViewportState = &viewportState;
+		pipelineCI.pDepthStencilState = &depthStencilState;
+		pipelineCI.pDynamicState = &dynamicState;
+		pipelineCI.stageCount = static_cast<uint32_t>(shaderStages.size());
+		pipelineCI.pStages = shaderStages.data();
+		pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Color, vkglTF::VertexComponent::Normal});
+
+		rasterizationState.cullMode = VK_CULL_MODE_NONE;
+
+		// Render-target debug display
+		shaderStages[0] = loadShader(getShadersPath() + "offscreen/quad.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "offscreen/quad.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.debug));
+
+		// Mirror
+		shaderStages[0] = loadShader(getShadersPath() + "offscreen/mirror.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "offscreen/mirror.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.mirror));
+
+		rasterizationState.cullMode = VK_CULL_MODE_BACK_BIT;
+
+		// Phong shading pipelines
+		pipelineCI.layout = pipelineLayouts.shaded;
+		// Scene
+		shaderStages[0] = loadShader(getShadersPath() + "offscreen/phong.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "offscreen/phong.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.shaded));
+		// Offscreen
+		// Flip cull mode
+		rasterizationState.cullMode = VK_CULL_MODE_FRONT_BIT;
+		pipelineCI.renderPass = offscreenPass.renderPass;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.shadedOffscreen));
+
+	}
+
+	// Prepare and initialize uniform buffer containing shader uniforms
+	void prepareUniformBuffers()
+	{
+		// Mesh vertex shader uniform buffer block
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.vsShared,
+			sizeof(uboShared)));
+
+		// Mirror plane vertex shader uniform buffer block
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.vsMirror,
+			sizeof(uboShared)));
+
+		// Offscreen vertex shader uniform buffer block
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.vsOffScreen,
+			sizeof(uboShared)));
+
+		// Map persistent
+		VK_CHECK_RESULT(uniformBuffers.vsShared.map());
+		VK_CHECK_RESULT(uniformBuffers.vsMirror.map());
+		VK_CHECK_RESULT(uniformBuffers.vsOffScreen.map());
+
+		updateUniformBuffers();
+		updateUniformBufferOffscreen();
+	}
+
+	void updateUniformBuffers()
+	{
+		uboShared.projection = camera.matrices.perspective;
+		uboShared.view = camera.matrices.view;
+
+		// Model
+		uboShared.model = glm::mat4(1.0f);
+		uboShared.model = glm::rotate(uboShared.model, glm::radians(modelRotation.y), glm::vec3(0.0f, 1.0f, 0.0f));
+		uboShared.model = glm::translate(uboShared.model, modelPosition);
+		memcpy(uniformBuffers.vsShared.mapped, &uboShared, sizeof(uboShared));
+
+		// Mirror
+		uboShared.model = glm::mat4(1.0f);
+		memcpy(uniformBuffers.vsMirror.mapped, &uboShared, sizeof(uboShared));
+	}
+
+	void updateUniformBufferOffscreen()
+	{
+		uboShared.projection = camera.matrices.perspective;
+		uboShared.view = camera.matrices.view;
+		uboShared.model = glm::mat4(1.0f);
+		uboShared.model = glm::rotate(uboShared.model, glm::radians(modelRotation.y), glm::vec3(0.0f, 1.0f, 0.0f));
+		uboShared.model = glm::scale(uboShared.model, glm::vec3(1.0f, -1.0f, 1.0f));
+		uboShared.model = glm::translate(uboShared.model, modelPosition);
+		memcpy(uniformBuffers.vsOffScreen.mapped, &uboShared, sizeof(uboShared));
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+
+		// Command buffer to be submitted to the queue
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+
+		// Submit to queue
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+
+		VulkanExampleBase::submitFrame();
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		loadAssets();
+		prepareOffscreen();
+		prepareUniformBuffers();
+		setupDescriptorSetLayout();
+		preparePipelines();
+		setupDescriptorPool();
+		setupDescriptorSet();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+		if (!paused || camera.updated)
+		{
+			if (!paused) {
+				modelRotation.y += frameTimer * 10.0f;
+			}
+			updateUniformBuffers();
+			updateUniformBufferOffscreen();
+		}
+	}
+
+	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
+	{
+		if (overlay->header("Settings")) {
+			if (overlay->checkBox("Display render target", &debugDisplay)) {
+				buildCommandBuffers();
+			}
+		}
+	}
+};
+
+VULKAN_EXAMPLE_MAIN()
diff --git a/external/Vulkan/examples/oit/oit.cpp b/external/Vulkan/examples/oit/oit.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..2743f974291beb45ed8bc466a3f041f1de4736fa
--- /dev/null
+++ b/external/Vulkan/examples/oit/oit.cpp
@@ -0,0 +1,659 @@
+/*
+* Vulkan Example - Order Independent Transparency rendering
+*
+* Note: Requires the separate asset pack (see data/README.md)
+*
+* Copyright by Sascha Willems - www.saschawillems.de
+* Copyright by Daemyung Jang  - dm86.jang@gmail.com
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkanexamplebase.h"
+#include "VulkanglTFModel.h"
+
+#define ENABLE_VALIDATION false
+#define NODE_COUNT 20
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	struct {
+		vkglTF::Model sphere;
+		vkglTF::Model cube;
+	} models;
+
+	struct {
+		vks::Buffer renderPass;
+	} uniformBuffers;
+
+	struct Node {
+		glm::vec4 color;
+		float depth;
+		uint32_t next;
+	};
+
+	struct {
+		uint32_t count;
+		uint32_t maxNodeCount;
+	} geometrySBO;
+
+	struct GeometryPass {
+		VkRenderPass renderPass;
+		VkFramebuffer framebuffer;
+		vks::Buffer geometry;
+		vks::Texture headIndex;
+		vks::Buffer linkedList;
+	} geometryPass;
+
+	struct {
+		glm::mat4 projection;
+		glm::mat4 view;
+	} renderPassUBO;
+
+	struct ObjectData {
+		glm::mat4 model;
+		glm::vec4 color;
+	};
+
+	struct {
+		VkDescriptorSetLayout geometry;
+		VkDescriptorSetLayout color;
+	} descriptorSetLayouts;
+
+	struct {
+		VkPipelineLayout geometry;
+		VkPipelineLayout color;
+	} pipelineLayouts;
+
+	struct {
+		VkPipeline geometry;
+		VkPipeline color;
+	} pipelines;
+
+	struct {
+		VkDescriptorSet geometry;
+		VkDescriptorSet color;
+	} descriptorSets;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Order independent transparency rendering";
+		camera.type = Camera::CameraType::lookat;
+		camera.setPosition(glm::vec3(0.0f, 0.0f, -6.0f));
+		camera.setRotation(glm::vec3(0.0f, 0.0f, 0.0f));
+		camera.setPerspective(60.0f, (float) width / (float) height, 0.1f, 256.0f);
+	}
+
+	~VulkanExample()
+	{
+		vkDestroyPipeline(device, pipelines.geometry, nullptr);
+		vkDestroyPipeline(device, pipelines.color, nullptr);
+
+		vkDestroyPipelineLayout(device, pipelineLayouts.geometry, nullptr);
+		vkDestroyPipelineLayout(device, pipelineLayouts.color, nullptr);
+
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.geometry, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.color, nullptr);
+
+		destroyGeometryPass();
+
+		uniformBuffers.renderPass.destroy();
+	}
+
+	void getEnabledFeatures() override
+	{
+		if (deviceFeatures.fragmentStoresAndAtomics) {
+			enabledFeatures.fragmentStoresAndAtomics = VK_TRUE;
+		} else {
+			vks::tools::exitFatal("Selected GPU does not support stores and atomic operations in the fragment stage", VK_ERROR_FEATURE_NOT_PRESENT);
+		}
+	};
+
+	void prepare() override
+	{
+		VulkanExampleBase::prepare();
+		loadAssets();
+		prepareUniformBuffers();
+		prepareGeometryPass();
+		setupDescriptorSetLayout();
+		preparePipelines();
+		setupDescriptorPool();
+		setupDescriptorSets();
+		buildCommandBuffers();
+		updateUniformBuffers();
+		prepared = true;
+	}
+
+	void render() override
+	{
+		if (!prepared)
+			return;
+		draw();
+	}
+
+	void windowResized() override
+	{
+		destroyGeometryPass();
+		prepareGeometryPass();
+		vkResetDescriptorPool(device, descriptorPool, 0);
+		setupDescriptorSets();
+
+		resized = false;
+		buildCommandBuffers();
+	}
+
+	void viewChanged() override
+	{		
+		updateUniformBuffers();
+	}
+
+private:
+	void loadAssets()
+	{
+		const uint32_t glTFLoadingFlags = vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::FlipY;
+		models.sphere.loadFromFile(getAssetPath() + "models/sphere.gltf", vulkanDevice, queue, glTFLoadingFlags);
+		models.cube.loadFromFile(getAssetPath() + "models/cube.gltf", vulkanDevice, queue, glTFLoadingFlags);
+	}
+
+	void prepareUniformBuffers()
+	{
+		// Create an uniform buffer for a render pass.
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.renderPass,
+			sizeof(renderPassUBO)));
+
+		VK_CHECK_RESULT(uniformBuffers.renderPass.map());
+	}
+
+	void prepareGeometryPass()
+	{
+		VkSubpassDescription subpassDescription = {};
+		subpassDescription.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+
+		// Geometry render pass doesn't need any output attachment.
+		VkRenderPassCreateInfo renderPassInfo = vks::initializers::renderPassCreateInfo();
+		renderPassInfo.attachmentCount = 0;
+		renderPassInfo.subpassCount = 1;
+		renderPassInfo.pSubpasses = &subpassDescription;
+
+		VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassInfo, nullptr, &geometryPass.renderPass));
+
+		// Geometry frame buffer doesn't need any output attachment.
+		VkFramebufferCreateInfo fbufCreateInfo = vks::initializers::framebufferCreateInfo();
+		fbufCreateInfo.renderPass = geometryPass.renderPass;
+		fbufCreateInfo.attachmentCount = 0;
+		fbufCreateInfo.width = width;
+		fbufCreateInfo.height = height;
+		fbufCreateInfo.layers = 1;
+
+		VK_CHECK_RESULT(vkCreateFramebuffer(device, &fbufCreateInfo, nullptr, &geometryPass.framebuffer));
+
+		// Create a buffer for GeometrySBO
+		vks::Buffer stagingBuffer;
+	
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&stagingBuffer,
+			sizeof(geometrySBO)));
+		VK_CHECK_RESULT(stagingBuffer.map());
+
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
+			VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
+			&geometryPass.geometry,
+			sizeof(geometrySBO)));
+
+		// Set up GeometrySBO data.
+		geometrySBO.count = 0;
+		geometrySBO.maxNodeCount = NODE_COUNT * width * height;
+		memcpy(stagingBuffer.mapped, &geometrySBO, sizeof(geometrySBO));
+
+		// Copy data to device
+		VkCommandBuffer copyCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+		VkBufferCopy copyRegion = {};
+		copyRegion.size = sizeof(geometrySBO);
+		vkCmdCopyBuffer(copyCmd, stagingBuffer.buffer, geometryPass.geometry.buffer, 1, &copyRegion);
+		vulkanDevice->flushCommandBuffer(copyCmd, queue, true);
+
+		stagingBuffer.destroy();
+		
+		// Create a texture for HeadIndex.
+		// This image will track the head index of each fragment.
+		geometryPass.headIndex.device = vulkanDevice;
+
+		VkImageCreateInfo imageInfo = vks::initializers::imageCreateInfo();
+		imageInfo.imageType = VK_IMAGE_TYPE_2D;
+		imageInfo.format = VK_FORMAT_R32_UINT;
+		imageInfo.extent.width = width;
+		imageInfo.extent.height = height;
+		imageInfo.extent.depth = 1;
+		imageInfo.mipLevels = 1;
+		imageInfo.arrayLayers = 1;
+		imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
+		imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
+		imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_STORAGE_BIT;
+
+		VK_CHECK_RESULT(vkCreateImage(device, &imageInfo, nullptr, &geometryPass.headIndex.image));
+
+		geometryPass.headIndex.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
+
+		VkMemoryRequirements memReqs;
+		vkGetImageMemoryRequirements(device, geometryPass.headIndex.image, &memReqs);
+
+		VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo();
+		memAlloc.allocationSize = memReqs.size;
+		memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+
+		VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &geometryPass.headIndex.deviceMemory));
+		VK_CHECK_RESULT(vkBindImageMemory(device, geometryPass.headIndex.image, geometryPass.headIndex.deviceMemory, 0));
+
+		VkImageViewCreateInfo imageViewInfo = vks::initializers::imageViewCreateInfo();
+		imageViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
+		imageViewInfo.format = VK_FORMAT_R32_UINT;
+		imageViewInfo.flags = 0;
+		imageViewInfo.image = geometryPass.headIndex.image;
+		imageViewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+		imageViewInfo.subresourceRange.baseMipLevel = 0;
+		imageViewInfo.subresourceRange.levelCount = 1;
+		imageViewInfo.subresourceRange.baseArrayLayer = 0;
+		imageViewInfo.subresourceRange.layerCount = 1;
+
+		VK_CHECK_RESULT(vkCreateImageView(device, &imageViewInfo, nullptr, &geometryPass.headIndex.view));
+
+		geometryPass.headIndex.width = width;
+		geometryPass.headIndex.height = height;
+		geometryPass.headIndex.mipLevels = 1;
+		geometryPass.headIndex.layerCount = 1;
+		geometryPass.headIndex.descriptor.imageView = geometryPass.headIndex.view;
+		geometryPass.headIndex.descriptor.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
+		geometryPass.headIndex.sampler = VK_NULL_HANDLE;
+
+		// Create a buffer for LinkedListSBO
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
+			&geometryPass.linkedList,
+			sizeof(Node) * geometrySBO.maxNodeCount));
+
+		// Change HeadIndex image's layout from UNDEFINED to GENERAL
+		VkCommandBufferAllocateInfo cmdBufAllocInfo = vks::initializers::commandBufferAllocateInfo(cmdPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1);
+
+		VkCommandBuffer cmdBuf;
+		VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocInfo, &cmdBuf));
+
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+		VK_CHECK_RESULT(vkBeginCommandBuffer(cmdBuf, &cmdBufInfo));
+
+		VkImageMemoryBarrier barrier = vks::initializers::imageMemoryBarrier();
+		barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+		barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+		barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL;
+		barrier.image = geometryPass.headIndex.image;
+		barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+		barrier.subresourceRange.levelCount = 1;
+		barrier.subresourceRange.layerCount = 1;
+
+		vkCmdPipelineBarrier(cmdBuf, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier);
+
+		VK_CHECK_RESULT(vkEndCommandBuffer(cmdBuf));
+
+		VkSubmitInfo submitInfo = vks::initializers::submitInfo();
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &cmdBuf;
+
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+		VK_CHECK_RESULT(vkQueueWaitIdle(queue));
+	}
+
+	void setupDescriptorSetLayout()
+	{
+		// Create a geometry descriptor set layout.
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			// RenderPassUBO
+			vks::initializers::descriptorSetLayoutBinding(
+				VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+				VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
+				0),
+			// AtomicSBO
+			vks::initializers::descriptorSetLayoutBinding(
+				VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
+				VK_SHADER_STAGE_FRAGMENT_BIT,
+				1),
+			// headIndexImage
+			vks::initializers::descriptorSetLayoutBinding(
+				VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
+				VK_SHADER_STAGE_FRAGMENT_BIT,
+				2),
+			// LinkedListSBO
+			vks::initializers::descriptorSetLayoutBinding(
+				VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
+				VK_SHADER_STAGE_FRAGMENT_BIT,
+				3),
+		};
+
+		VkDescriptorSetLayoutCreateInfo descriptorLayoutCI = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayoutCI, nullptr, &descriptorSetLayouts.geometry));
+
+		// Create a geometry pipeline layout.
+		VkPipelineLayoutCreateInfo pipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayouts.geometry, 1);
+		// Static object data passed using push constants
+		VkPushConstantRange pushConstantRange = vks::initializers::pushConstantRange(VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(ObjectData), 0);
+		pipelineLayoutCI.pushConstantRangeCount = 1;
+		pipelineLayoutCI.pPushConstantRanges = &pushConstantRange;
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelineLayouts.geometry));
+
+		// Create a color descriptor set layout.
+		setLayoutBindings = {
+			// headIndexImage
+			vks::initializers::descriptorSetLayoutBinding(
+				VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
+				VK_SHADER_STAGE_FRAGMENT_BIT,
+				0),
+			// LinkedListSBO
+			vks::initializers::descriptorSetLayoutBinding(
+				VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
+				VK_SHADER_STAGE_FRAGMENT_BIT,
+				1),
+		};
+
+		descriptorLayoutCI = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayoutCI, nullptr, &descriptorSetLayouts.color));
+
+		// Create a color pipeline layout.
+		pipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayouts.color, 1);
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelineLayouts.color));
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
+		VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(0, nullptr);
+		VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_FALSE, VK_FALSE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+		VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
+		std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
+		VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+
+		// Create a geometry pipeline.
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayouts.geometry, geometryPass.renderPass);
+		pipelineCI.pInputAssemblyState = &inputAssemblyState;
+		pipelineCI.pRasterizationState = &rasterizationState;
+		pipelineCI.pColorBlendState = &colorBlendState;
+		pipelineCI.pMultisampleState = &multisampleState;
+		pipelineCI.pViewportState = &viewportState;
+		pipelineCI.pDepthStencilState = &depthStencilState;
+		pipelineCI.pDynamicState = &dynamicState;
+		pipelineCI.stageCount = static_cast<uint32_t>(shaderStages.size());
+		pipelineCI.pStages = shaderStages.data();
+		pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position });
+
+		shaderStages[0] = loadShader(getShadersPath() + "oit/geometry.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "oit/geometry.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.geometry));
+
+		// Create a color pipeline.
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+
+		VkPipelineVertexInputStateCreateInfo vertexInputInfo = {};
+		vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
+
+		pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayouts.color, renderPass);
+		pipelineCI.pInputAssemblyState = &inputAssemblyState;
+		pipelineCI.pRasterizationState = &rasterizationState;
+		pipelineCI.pColorBlendState = &colorBlendState;
+		pipelineCI.pMultisampleState = &multisampleState;
+		pipelineCI.pViewportState = &viewportState;
+		pipelineCI.pDepthStencilState = &depthStencilState;
+		pipelineCI.pDynamicState = &dynamicState;
+		pipelineCI.stageCount = static_cast<uint32_t>(shaderStages.size());
+		pipelineCI.pStages = shaderStages.data();
+		pipelineCI.pVertexInputState = &vertexInputInfo;
+
+		shaderStages[0] = loadShader(getShadersPath() + "oit/color.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "oit/color.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		rasterizationState.cullMode = VK_CULL_MODE_FRONT_BIT;
+		rasterizationState.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
+
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.color));
+	}
+
+	void setupDescriptorPool()
+	{
+		std::vector<VkDescriptorPoolSize> poolSizes = {
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1),
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1),
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 3),
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 2),
+		};
+
+		VkDescriptorPoolCreateInfo descriptorPoolInfo =
+			vks::initializers::descriptorPoolCreateInfo(poolSizes, 2);
+
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+	}
+
+	void setupDescriptorSets()
+	{
+		// Update a geometry descriptor set
+		VkDescriptorSetAllocateInfo allocInfo =
+			vks::initializers::descriptorSetAllocateInfo(
+				descriptorPool,
+				&descriptorSetLayouts.geometry,
+				1);
+
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.geometry));
+
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets = {
+			// Binding 0: RenderPassUBO
+			vks::initializers::writeDescriptorSet(
+				descriptorSets.geometry,
+				VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+				0,
+				&uniformBuffers.renderPass.descriptor),
+			// Binding 2: GeometrySBO
+			vks::initializers::writeDescriptorSet(
+				descriptorSets.geometry,
+				VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
+				1,
+				&geometryPass.geometry.descriptor),
+			// Binding 3: headIndexImage
+			vks::initializers::writeDescriptorSet(
+				descriptorSets.geometry,
+				VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
+				2,
+				&geometryPass.headIndex.descriptor),
+			// Binding 4: LinkedListSBO
+			vks::initializers::writeDescriptorSet(
+				descriptorSets.geometry,
+				VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
+				3,
+				&geometryPass.linkedList.descriptor)
+		};
+
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL);
+
+		// Update a color descriptor set.
+		allocInfo =
+			vks::initializers::descriptorSetAllocateInfo(
+				descriptorPool,
+				&descriptorSetLayouts.color,
+				1);
+
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.color));
+
+		writeDescriptorSets = {
+			// Binding 0: headIndexImage
+			vks::initializers::writeDescriptorSet(
+				descriptorSets.color,
+				VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
+				0,
+				&geometryPass.headIndex.descriptor),
+			// Binding 1: LinkedListSBO
+			vks::initializers::writeDescriptorSet(
+				descriptorSets.color,
+				VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
+				1,
+				&geometryPass.linkedList.descriptor)
+		};
+
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL);
+	}
+
+	void buildCommandBuffers()
+	{
+		if (resized)
+			return;
+
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		clearValues[0].color = defaultClearColor;
+		clearValues[1].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderArea.offset.x = 0;
+		renderPassBeginInfo.renderArea.offset.y = 0;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		
+		VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+		VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			// Update dynamic viewport state
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+			// Update dynamic scissor state
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+			VkClearColorValue clearColor;
+			clearColor.uint32[0] = 0xffffffff;
+
+			VkImageSubresourceRange subresRange = {};
+
+			subresRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+			subresRange.levelCount = 1;
+			subresRange.layerCount = 1;
+
+			vkCmdClearColorImage(drawCmdBuffers[i], geometryPass.headIndex.image, VK_IMAGE_LAYOUT_GENERAL, &clearColor, 1, &subresRange);
+
+			// Clear previous geometry pass data
+			vkCmdFillBuffer(drawCmdBuffers[i], geometryPass.geometry.buffer, 0, sizeof(uint32_t), 0);
+
+			// We need a barrier to make sure all writes are finished before starting to write again
+			VkMemoryBarrier memoryBarrier = vks::initializers::memoryBarrier();
+			memoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+			memoryBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
+			vkCmdPipelineBarrier(drawCmdBuffers[i], VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 1, &memoryBarrier, 0, nullptr, 0, nullptr);
+
+			// Begin the geometry render pass
+			renderPassBeginInfo.renderPass = geometryPass.renderPass;
+			renderPassBeginInfo.framebuffer = geometryPass.framebuffer;
+			renderPassBeginInfo.clearValueCount = 0;
+			renderPassBeginInfo.pClearValues = nullptr;
+
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.geometry);
+			uint32_t dynamicOffset = 0;
+			models.sphere.bindBuffers(drawCmdBuffers[i]);
+
+			// Render the scene
+			ObjectData objectData;
+
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayouts.geometry, 0, 1, &descriptorSets.geometry, 0, nullptr);
+			objectData.color = glm::vec4(1.0f, 0.0f, 0.0f, 0.5f);
+			for (int32_t x = 0; x < 5; x++)
+			{
+				for (int32_t y = 0; y < 5; y++)
+				{
+					for (int32_t z = 0; z < 5; z++)
+					{
+						glm::mat4 T = glm::translate(glm::mat4(1.0f), glm::vec3(x - 2, y - 2, z - 2));
+						glm::mat4 S = glm::scale(glm::mat4(1.0f), glm::vec3(0.3f));
+						objectData.model = T * S;
+						vkCmdPushConstants(drawCmdBuffers[i], pipelineLayouts.geometry, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(ObjectData), &objectData);
+						models.sphere.draw(drawCmdBuffers[i]);
+					}
+				}
+			}
+
+			models.cube.bindBuffers(drawCmdBuffers[i]);
+			objectData.color = glm::vec4(0.0f, 0.0f, 1.0f, 0.5f);
+			for (uint32_t x = 0; x < 2; x++)
+			{
+				glm::mat4 T = glm::translate(glm::mat4(1.0f), glm::vec3(3.0f * x - 1.5f, 0.0f, 0.0f));
+				glm::mat4 S = glm::scale(glm::mat4(1.0f), glm::vec3(0.2f));
+				objectData.model = T * S;
+				vkCmdPushConstants(drawCmdBuffers[i], pipelineLayouts.geometry, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(ObjectData), &objectData);
+				models.cube.draw(drawCmdBuffers[i]);
+			}
+
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			// Make a pipeline barrier to guarantee the geometry pass is done
+			vkCmdPipelineBarrier(drawCmdBuffers[i], VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 0, nullptr);
+
+			// We need a barrier to make sure all writes are finished before starting to write again
+			memoryBarrier = vks::initializers::memoryBarrier();
+			memoryBarrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
+			memoryBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
+			vkCmdPipelineBarrier(drawCmdBuffers[i], VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 1, &memoryBarrier, 0, nullptr, 0, nullptr);
+
+			// Begin the color render pass
+			renderPassBeginInfo.renderPass = renderPass;
+			renderPassBeginInfo.framebuffer = frameBuffers[i];
+			renderPassBeginInfo.clearValueCount = 2;
+			renderPassBeginInfo.pClearValues = clearValues;
+
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.color);
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayouts.color, 0, 1, &descriptorSets.color, 0, nullptr);
+			vkCmdDraw(drawCmdBuffers[i], 3, 1, 0, 0);
+			drawUI(drawCmdBuffers[i]);
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void updateUniformBuffers()
+	{
+		renderPassUBO.projection = camera.matrices.perspective;
+		renderPassUBO.view = camera.matrices.view;
+		memcpy(uniformBuffers.renderPass.mapped, &renderPassUBO, sizeof(renderPassUBO));
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+		VulkanExampleBase::submitFrame();
+	}
+
+	void destroyGeometryPass()
+	{
+		vkDestroyRenderPass(device, geometryPass.renderPass, nullptr);
+		vkDestroyFramebuffer(device, geometryPass.framebuffer, nullptr);
+		geometryPass.geometry.destroy();
+		geometryPass.headIndex.destroy();
+		geometryPass.linkedList.destroy();
+	}
+
+private:
+	VkDeviceSize objectUniformBufferSize;
+};
+
+VULKAN_EXAMPLE_MAIN()
\ No newline at end of file
diff --git a/external/Vulkan/examples/parallaxmapping/parallaxmapping.cpp b/external/Vulkan/examples/parallaxmapping/parallaxmapping.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d9af12e867acc82a5ccf3cc4bfb0a13ca213e8b1
--- /dev/null
+++ b/external/Vulkan/examples/parallaxmapping/parallaxmapping.cpp
@@ -0,0 +1,302 @@
+/*
+* Vulkan Example - Parallax Mapping
+*
+* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkanexamplebase.h"
+#include "VulkanglTFModel.h"
+
+#define ENABLE_VALIDATION false
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	struct {
+		vks::Texture2D colorMap;
+		// Normals and height are combined into one texture (height = alpha channel)
+		vks::Texture2D normalHeightMap;
+	} textures;
+
+	vkglTF::Model plane;
+
+	struct {
+		vks::Buffer vertexShader;
+		vks::Buffer fragmentShader;
+	} uniformBuffers;
+
+	struct {
+
+		struct {
+			glm::mat4 projection;
+			glm::mat4 view;
+			glm::mat4 model;
+			glm::vec4 lightPos = glm::vec4(0.0f, -2.0f, 0.0f, 1.0f);
+			glm::vec4 cameraPos;
+		} vertexShader;
+
+		struct {
+			float heightScale = 0.1f;
+			// Basic parallax mapping needs a bias to look any good (and is hard to tweak)
+			float parallaxBias = -0.02f;
+			// Number of layers for steep parallax and parallax occlusion (more layer = better result for less performance)
+			float numLayers = 48.0f;
+			// (Parallax) mapping mode to use
+			int32_t mappingMode = 4;
+		} fragmentShader;
+
+	} ubos;
+
+	VkPipelineLayout pipelineLayout;
+	VkPipeline pipeline;
+	VkDescriptorSetLayout descriptorSetLayout;
+	VkDescriptorSet descriptorSet;
+
+	const std::vector<std::string> mappingModes = {
+		"Color only",
+		"Normal mapping",
+		"Parallax mapping",
+		"Steep parallax mapping",
+		"Parallax occlusion mapping",
+	};
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Parallax Mapping";
+		timerSpeed *= 0.5f;
+		camera.type = Camera::CameraType::firstperson;
+		camera.setPosition(glm::vec3(0.0f, 1.25f, -1.5f));
+		camera.setRotation(glm::vec3(-45.0f, 0.0f, 0.0f));
+		camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 256.0f);
+	}
+
+	~VulkanExample()
+	{
+		vkDestroyPipeline(device, pipeline, nullptr);
+
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+
+		uniformBuffers.vertexShader.destroy();
+		uniformBuffers.fragmentShader.destroy();
+
+		textures.colorMap.destroy();
+		textures.normalHeightMap.destroy();
+	}
+
+	void loadAssets()
+	{
+		const uint32_t glTFLoadingFlags = vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::PreMultiplyVertexColors | vkglTF::FileLoadingFlags::FlipY;
+		plane.loadFromFile(getAssetPath() + "models/plane.gltf", vulkanDevice, queue, glTFLoadingFlags);
+		textures.normalHeightMap.loadFromFile(getAssetPath() + "textures/rocks_normal_height_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue);
+		textures.colorMap.loadFromFile(getAssetPath() + "textures/rocks_color_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue);
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		clearValues[0].color = defaultClearColor;
+		clearValues[1].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.offset.x = 0;
+		renderPassBeginInfo.renderArea.offset.y = 0;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		renderPassBeginInfo.clearValueCount = 2;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			// Set target frame buffer
+			renderPassBeginInfo.framebuffer = frameBuffers[i];
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+			VkRect2D scissor = vks::initializers::rect2D(width, height,	0, 0);
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL);
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
+			plane.draw(drawCmdBuffers[i]);
+
+			drawUI(drawCmdBuffers[i]);
+
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void setupDescriptorPool()
+	{
+		// Example uses two ubos and two image sampler
+		std::vector<VkDescriptorPoolSize> poolSizes =
+		{
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2),
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2)
+		};
+
+		VkDescriptorPoolCreateInfo descriptorPoolInfo =
+			vks::initializers::descriptorPoolCreateInfo(poolSizes, 2);
+
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+	}
+
+	void setupDescriptorSetLayout()
+	{
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0),			// Binding 0: Vertex shader uniform buffer
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1),	// Binding 1: Fragment shader color map image sampler
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 2),	// Binding 2: Fragment combined normal and heightmap
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_FRAGMENT_BIT, 3),			// Binding 3: Fragment shader uniform buffer
+		};
+		VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout));
+
+		VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1);
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayout));
+	}
+
+	void setupDescriptorSet()
+	{
+		VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1);
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet));
+
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets = {
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.vertexShader.descriptor),		// Binding 0: Vertex shader uniform buffer
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &textures.colorMap.descriptor),			// Binding 1: Fragment shader image sampler
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2, &textures.normalHeightMap.descriptor),	// Binding 2: Combined normal and heightmap
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 3, &uniformBuffers.fragmentShader.descriptor),		// Binding 3: Fragment shader uniform buffer
+		};
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL);
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE, VK_FRONT_FACE_COUNTER_CLOCKWISE);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+		VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT);
+		std::vector<VkDynamicState> dynamicStateEnables = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
+		VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass);
+		pipelineCI.pInputAssemblyState = &inputAssemblyState;
+		pipelineCI.pRasterizationState = &rasterizationState;
+		pipelineCI.pColorBlendState = &colorBlendState;
+		pipelineCI.pMultisampleState = &multisampleState;
+		pipelineCI.pViewportState = &viewportState;
+		pipelineCI.pDepthStencilState = &depthStencilState;
+		pipelineCI.pDynamicState = &dynamicState;
+		pipelineCI.stageCount = static_cast<uint32_t>(shaderStages.size());
+		pipelineCI.pStages = shaderStages.data();
+		pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position, vkglTF::VertexComponent::UV, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::Tangent });
+
+		// Parallax mapping modes pipeline
+		shaderStages[0] = loadShader(getShadersPath() + "parallaxmapping/parallax.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "parallaxmapping/parallax.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipeline));
+	}
+
+	void prepareUniformBuffers()
+	{
+		// Vertex shader uniform buffer
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.vertexShader,
+			sizeof(ubos.vertexShader)));
+
+		// Fragment shader uniform buffer
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.fragmentShader,
+			sizeof(ubos.fragmentShader)));
+
+		// Map persistent
+		VK_CHECK_RESULT(uniformBuffers.vertexShader.map());
+		VK_CHECK_RESULT(uniformBuffers.fragmentShader.map());
+
+		updateUniformBuffers();
+	}
+
+	void updateUniformBuffers()
+	{
+		// Vertex shader
+		ubos.vertexShader.projection = camera.matrices.perspective;
+		ubos.vertexShader.view = camera.matrices.view;
+		ubos.vertexShader.model = glm::scale(glm::mat4(1.0f), glm::vec3(0.2f));
+
+		if (!paused) {
+			ubos.vertexShader.lightPos.x = sin(glm::radians(timer * 360.0f)) * 1.5f;
+			ubos.vertexShader.lightPos.z = cos(glm::radians(timer * 360.0f)) * 1.5f;
+		}
+
+		ubos.vertexShader.cameraPos = glm::vec4(camera.position, -1.0f) * -1.0f;
+		memcpy(uniformBuffers.vertexShader.mapped, &ubos.vertexShader, sizeof(ubos.vertexShader));
+
+		// Fragment shader
+		memcpy(uniformBuffers.fragmentShader.mapped, &ubos.fragmentShader, sizeof(ubos.fragmentShader));
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+		VulkanExampleBase::submitFrame();
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		loadAssets();
+		prepareUniformBuffers();
+		setupDescriptorSetLayout();
+		preparePipelines();
+		setupDescriptorPool();
+		setupDescriptorSet();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+		if (!paused || camera.updated)
+		{
+			updateUniformBuffers();
+		}
+	}
+
+	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
+	{
+		if (overlay->header("Settings")) {
+			if (overlay->comboBox("Mode", &ubos.fragmentShader.mappingMode, mappingModes)) {
+				updateUniformBuffers();
+			}
+		}
+	}
+
+};
+
+VULKAN_EXAMPLE_MAIN()
\ No newline at end of file
diff --git a/external/Vulkan/examples/particlefire/particlefire.cpp b/external/Vulkan/examples/particlefire/particlefire.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b4b9bb552c9b39b0d29f84537ff4217da8603235
--- /dev/null
+++ b/external/Vulkan/examples/particlefire/particlefire.cpp
@@ -0,0 +1,594 @@
+/*
+* Vulkan Example - CPU based fire particle system
+*
+* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkanexamplebase.h"
+#include "VulkanglTFModel.h"
+
+#define ENABLE_VALIDATION false
+#define PARTICLE_COUNT 512
+#define PARTICLE_SIZE 10.0f
+
+#define FLAME_RADIUS 8.0f
+
+#define PARTICLE_TYPE_FLAME 0
+#define PARTICLE_TYPE_SMOKE 1
+
+struct Particle {
+	glm::vec4 pos;
+	glm::vec4 color;
+	float alpha;
+	float size;
+	float rotation;
+	uint32_t type;
+	// Attributes not used in shader
+	glm::vec4 vel;
+	float rotationSpeed;
+};
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	struct {
+		struct {
+			vks::Texture2D smoke;
+			vks::Texture2D fire;
+			// Use a custom sampler to change sampler attributes required for rotating the uvs in the shader for alpha blended textures
+			VkSampler sampler;
+		} particles;
+		struct {
+			vks::Texture2D colorMap;
+			vks::Texture2D normalMap;
+		} floor;
+	} textures;
+
+	vkglTF::Model environment;
+
+	glm::vec3 emitterPos = glm::vec3(0.0f, -FLAME_RADIUS + 2.0f, 0.0f);
+	glm::vec3 minVel = glm::vec3(-3.0f, 0.5f, -3.0f);
+	glm::vec3 maxVel = glm::vec3(3.0f, 7.0f, 3.0f);
+
+	struct {
+		VkBuffer buffer;
+		VkDeviceMemory memory;
+		// Store the mapped address of the particle data for reuse
+		void *mappedMemory;
+		// Size of the particle buffer in bytes
+		size_t size;
+	} particles;
+
+	struct {
+		vks::Buffer fire;
+		vks::Buffer environment;
+	} uniformBuffers;
+
+	struct UBOVS {
+		glm::mat4 projection;
+		glm::mat4 modelView;
+		glm::vec2 viewportDim;
+		float pointSize = PARTICLE_SIZE;
+	} uboVS;
+
+	struct UBOEnv {
+		glm::mat4 projection;
+		glm::mat4 modelView;
+		glm::mat4 normal;
+		glm::vec4 lightPos = glm::vec4(0.0f, 0.0f, 0.0f, 0.0f);
+	} uboEnv;
+
+	struct {
+		VkPipeline particles;
+		VkPipeline environment;
+	} pipelines;
+
+	VkPipelineLayout pipelineLayout;
+	VkDescriptorSetLayout descriptorSetLayout;
+
+	struct {
+		VkDescriptorSet particles;
+		VkDescriptorSet environment;
+	} descriptorSets;
+
+	std::vector<Particle> particleBuffer;
+
+	std::default_random_engine rndEngine;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "CPU based particle system";
+		camera.type = Camera::CameraType::lookat;
+		camera.setPosition(glm::vec3(0.0f, 0.0f, -75.0f));
+		camera.setRotation(glm::vec3(-15.0f, 45.0f, 0.0f));
+		camera.setPerspective(60.0f, (float)width / (float)height, 1.0f, 256.0f);
+		timerSpeed *= 8.0f;
+		rndEngine.seed(benchmark.active ? 0 : (unsigned)time(nullptr));
+	}
+
+	~VulkanExample()
+	{
+		// Clean up used Vulkan resources
+		// Note : Inherited destructor cleans up resources stored in base class
+
+		textures.particles.smoke.destroy();
+		textures.particles.fire.destroy();
+		textures.floor.colorMap.destroy();
+		textures.floor.normalMap.destroy();
+
+		vkDestroyPipeline(device, pipelines.particles, nullptr);
+		vkDestroyPipeline(device, pipelines.environment, nullptr);
+
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+
+		vkUnmapMemory(device, particles.memory);
+		vkDestroyBuffer(device, particles.buffer, nullptr);
+		vkFreeMemory(device, particles.memory, nullptr);
+
+		uniformBuffers.environment.destroy();
+		uniformBuffers.fire.destroy();
+
+		vkDestroySampler(device, textures.particles.sampler, nullptr);
+	}
+
+	virtual void getEnabledFeatures()
+	{
+		// Enable anisotropic filtering if supported
+		if (deviceFeatures.samplerAnisotropy) {
+			enabledFeatures.samplerAnisotropy = VK_TRUE;
+		};
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		clearValues[0].color = defaultClearColor;
+		clearValues[1].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.offset.x = 0;
+		renderPassBeginInfo.renderArea.offset.y = 0;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		renderPassBeginInfo.clearValueCount = 2;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			// Set target frame buffer
+			renderPassBeginInfo.framebuffer = frameBuffers[i];
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+			VkRect2D scissor = vks::initializers::rect2D(width, height, 0,0);
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+			VkDeviceSize offsets[1] = { 0 };
+
+			// Environment
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets.environment, 0, nullptr);
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.environment);
+			environment.draw(drawCmdBuffers[i]);
+
+			// Particle system (no index buffer)
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets.particles, 0, nullptr);
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.particles);
+			vkCmdBindVertexBuffers(drawCmdBuffers[i], 0, 1, &particles.buffer, offsets);
+			vkCmdDraw(drawCmdBuffers[i], PARTICLE_COUNT, 1, 0, 0);
+
+			drawUI(drawCmdBuffers[i]);
+
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	float rnd(float range)
+	{
+		std::uniform_real_distribution<float> rndDist(0.0f, range);
+		return rndDist(rndEngine);
+	}
+
+	void initParticle(Particle *particle, glm::vec3 emitterPos)
+	{
+		particle->vel = glm::vec4(0.0f, minVel.y + rnd(maxVel.y - minVel.y), 0.0f, 0.0f);
+		particle->alpha = rnd(0.75f);
+		particle->size = 1.0f + rnd(0.5f);
+		particle->color = glm::vec4(1.0f);
+		particle->type = PARTICLE_TYPE_FLAME;
+		particle->rotation = rnd(2.0f * float(M_PI));
+		particle->rotationSpeed = rnd(2.0f) - rnd(2.0f);
+
+		// Get random sphere point
+		float theta = rnd(2.0f * float(M_PI));
+		float phi = rnd(float(M_PI)) - float(M_PI) / 2.0f;
+		float r = rnd(FLAME_RADIUS);
+
+		particle->pos.x = r * cos(theta) * cos(phi);
+		particle->pos.y = r * sin(phi);
+		particle->pos.z = r * sin(theta) * cos(phi);
+
+		particle->pos += glm::vec4(emitterPos, 0.0f);
+	}
+
+	void transitionParticle(Particle *particle)
+	{
+		switch (particle->type)
+		{
+		case PARTICLE_TYPE_FLAME:
+			// Flame particles have a chance of turning into smoke
+			if (rnd(1.0f) < 0.05f)
+			{
+				particle->alpha = 0.0f;
+				particle->color = glm::vec4(0.25f + rnd(0.25f));
+				particle->pos.x *= 0.5f;
+				particle->pos.z *= 0.5f;
+				particle->vel = glm::vec4(rnd(1.0f) - rnd(1.0f), (minVel.y * 2) + rnd(maxVel.y - minVel.y), rnd(1.0f) - rnd(1.0f), 0.0f);
+				particle->size = 1.0f + rnd(0.5f);
+				particle->rotationSpeed = rnd(1.0f) - rnd(1.0f);
+				particle->type = PARTICLE_TYPE_SMOKE;
+			}
+			else
+			{
+				initParticle(particle, emitterPos);
+			}
+			break;
+		case PARTICLE_TYPE_SMOKE:
+			// Respawn at end of life
+			initParticle(particle, emitterPos);
+			break;
+		}
+	}
+
+	void prepareParticles()
+	{
+		particleBuffer.resize(PARTICLE_COUNT);
+		for (auto& particle : particleBuffer)
+		{
+			initParticle(&particle, emitterPos);
+			particle.alpha = 1.0f - (abs(particle.pos.y) / (FLAME_RADIUS * 2.0f));
+		}
+
+		particles.size = particleBuffer.size() * sizeof(Particle);
+
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			particles.size,
+			&particles.buffer,
+			&particles.memory,
+			particleBuffer.data()));
+
+		// Map the memory and store the pointer for reuse
+		VK_CHECK_RESULT(vkMapMemory(device, particles.memory, 0, particles.size, 0, &particles.mappedMemory));
+	}
+
+	void updateParticles()
+	{
+		float particleTimer = frameTimer * 0.45f;
+		for (auto& particle : particleBuffer)
+		{
+			switch (particle.type)
+			{
+			case PARTICLE_TYPE_FLAME:
+				particle.pos.y -= particle.vel.y * particleTimer * 3.5f;
+				particle.alpha += particleTimer * 2.5f;
+				particle.size -= particleTimer * 0.5f;
+				break;
+			case PARTICLE_TYPE_SMOKE:
+				particle.pos -= particle.vel * frameTimer * 1.0f;
+				particle.alpha += particleTimer * 1.25f;
+				particle.size += particleTimer * 0.125f;
+				particle.color -= particleTimer * 0.05f;
+				break;
+			}
+			particle.rotation += particleTimer * particle.rotationSpeed;
+			// Transition particle state
+			if (particle.alpha > 2.0f)
+			{
+				transitionParticle(&particle);
+			}
+		}
+		size_t size = particleBuffer.size() * sizeof(Particle);
+		memcpy(particles.mappedMemory, particleBuffer.data(), size);
+	}
+
+	void loadAssets()
+	{
+		// Particles
+		textures.particles.smoke.loadFromFile(getAssetPath() + "textures/particle_smoke.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue);
+		textures.particles.fire.loadFromFile(getAssetPath() + "textures/particle_fire.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue);
+
+		// Floor
+		textures.floor.colorMap.loadFromFile(getAssetPath() + "textures/fireplace_colormap_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue);
+		textures.floor.normalMap.loadFromFile(getAssetPath() + "textures/fireplace_normalmap_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue);
+
+		// Create a custom sampler to be used with the particle textures
+		// Create sampler
+		VkSamplerCreateInfo samplerCreateInfo = vks::initializers::samplerCreateInfo();
+		samplerCreateInfo.magFilter = VK_FILTER_LINEAR;
+		samplerCreateInfo.minFilter = VK_FILTER_LINEAR;
+		samplerCreateInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+		// Different address mode
+		samplerCreateInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
+		samplerCreateInfo.addressModeV = samplerCreateInfo.addressModeU;
+		samplerCreateInfo.addressModeW = samplerCreateInfo.addressModeU;
+		samplerCreateInfo.mipLodBias = 0.0f;
+		samplerCreateInfo.compareOp = VK_COMPARE_OP_NEVER;
+		samplerCreateInfo.minLod = 0.0f;
+		// Both particle textures have the same number of mip maps
+		samplerCreateInfo.maxLod = float(textures.particles.fire.mipLevels);
+
+		if (vulkanDevice->features.samplerAnisotropy)
+		{
+			// Enable anisotropic filtering
+			samplerCreateInfo.maxAnisotropy = 8.0f;
+			samplerCreateInfo.anisotropyEnable = VK_TRUE;
+		}
+
+		// Use a different border color (than the normal texture loader) for additive blending
+		samplerCreateInfo.borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK;
+		VK_CHECK_RESULT(vkCreateSampler(device, &samplerCreateInfo, nullptr, &textures.particles.sampler));
+
+		const uint32_t glTFLoadingFlags = vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::PreMultiplyVertexColors | vkglTF::FileLoadingFlags::FlipY;
+		environment.loadFromFile(getAssetPath() + "models/fireplace.gltf", vulkanDevice, queue, glTFLoadingFlags);
+	}
+
+	void setupDescriptorPool()
+	{
+		std::vector<VkDescriptorPoolSize> poolSizes = {
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2),
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 4)
+		};
+		VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 2);
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+	}
+
+	void setupDescriptorSetLayout()
+	{
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			// Binding 0 : Vertex shader uniform buffer
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0),
+			// Binding 1 : Fragment shader image sampler
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1),
+			// Binding 1 : Fragment shader image sampler
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT,2)
+		};
+
+		VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout));
+
+		VkPipelineLayoutCreateInfo pipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1);
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelineLayout));
+	}
+
+	void setupDescriptorSets()
+	{
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets;
+
+		VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1);
+
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.particles));
+
+		// Image descriptor for the color map texture
+		VkDescriptorImageInfo texDescriptorSmoke =
+			vks::initializers::descriptorImageInfo(
+				textures.particles.sampler,
+				textures.particles.smoke.view,
+				VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+		VkDescriptorImageInfo texDescriptorFire =
+			vks::initializers::descriptorImageInfo(
+				textures.particles.sampler,
+				textures.particles.fire.view,
+				VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+
+		writeDescriptorSets = {
+			// Binding 0: Vertex shader uniform buffer
+			vks::initializers::writeDescriptorSet(descriptorSets.particles, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.fire.descriptor),
+			// Binding 1: Smoke texture
+			vks::initializers::writeDescriptorSet(descriptorSets.particles, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &texDescriptorSmoke),
+			// Binding 1: Fire texture array
+			vks::initializers::writeDescriptorSet(descriptorSets.particles, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2, &texDescriptorFire)
+		};
+		vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, NULL);
+
+		// Environment
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.environment));
+
+		writeDescriptorSets = {
+			// Binding 0: Vertex shader uniform buffer
+			vks::initializers::writeDescriptorSet(descriptorSets.environment, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.environment.descriptor),
+			// Binding 1: Color map
+			vks::initializers::writeDescriptorSet(descriptorSets.environment, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &textures.floor.colorMap.descriptor),
+			// Binding 2: Normal map
+			vks::initializers::writeDescriptorSet(descriptorSets.environment, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2, &textures.floor.normalMap.descriptor),
+		};
+		vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, NULL);
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_POINT_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+		VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
+		std::vector<VkDynamicState> dynamicStateEnables = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
+		VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass);
+		pipelineCI.pInputAssemblyState = &inputAssemblyState;
+		pipelineCI.pRasterizationState = &rasterizationState;
+		pipelineCI.pColorBlendState = &colorBlendState;
+		pipelineCI.pMultisampleState = &multisampleState;
+		pipelineCI.pViewportState = &viewportState;
+		pipelineCI.pDepthStencilState = &depthStencilState;
+		pipelineCI.pDynamicState = &dynamicState;
+		pipelineCI.stageCount = shaderStages.size();
+		pipelineCI.pStages = shaderStages.data();
+
+		// Particle rendering pipeline
+		{
+			// Vertex input state
+			VkVertexInputBindingDescription vertexInputBinding =
+				vks::initializers::vertexInputBindingDescription(0, sizeof(Particle), VK_VERTEX_INPUT_RATE_VERTEX);
+
+			std::vector<VkVertexInputAttributeDescription> vertexInputAttributes = {
+				vks::initializers::vertexInputAttributeDescription(0, 0, VK_FORMAT_R32G32B32A32_SFLOAT,	offsetof(Particle, pos)),	// Location 0: Position
+				vks::initializers::vertexInputAttributeDescription(0, 1, VK_FORMAT_R32G32B32A32_SFLOAT,	offsetof(Particle, color)),	// Location 1: Color
+				vks::initializers::vertexInputAttributeDescription(0, 2, VK_FORMAT_R32_SFLOAT, offsetof(Particle, alpha)),			// Location 2: Alpha
+				vks::initializers::vertexInputAttributeDescription(0, 3, VK_FORMAT_R32_SFLOAT, offsetof(Particle, size)),			// Location 3: Size
+				vks::initializers::vertexInputAttributeDescription(0, 4, VK_FORMAT_R32_SFLOAT, offsetof(Particle, rotation)),		// Location 4: Rotation
+				vks::initializers::vertexInputAttributeDescription(0, 5, VK_FORMAT_R32_SINT, offsetof(Particle, type)),				// Location 5: Particle type
+			};
+
+			VkPipelineVertexInputStateCreateInfo vertexInputState = vks::initializers::pipelineVertexInputStateCreateInfo();
+			vertexInputState.vertexBindingDescriptionCount = 1;
+			vertexInputState.pVertexBindingDescriptions = &vertexInputBinding;
+			vertexInputState.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertexInputAttributes.size());
+			vertexInputState.pVertexAttributeDescriptions = vertexInputAttributes.data();
+
+			pipelineCI.pVertexInputState = &vertexInputState;
+
+			// Don t' write to depth buffer
+			depthStencilState.depthWriteEnable = VK_FALSE;
+
+			// Premulitplied alpha
+			blendAttachmentState.blendEnable = VK_TRUE;
+			blendAttachmentState.srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
+			blendAttachmentState.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
+			blendAttachmentState.colorBlendOp = VK_BLEND_OP_ADD;
+			blendAttachmentState.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
+			blendAttachmentState.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
+			blendAttachmentState.alphaBlendOp = VK_BLEND_OP_ADD;
+			blendAttachmentState.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
+
+			shaderStages[0] = loadShader(getShadersPath() + "particlefire/particle.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+			shaderStages[1] = loadShader(getShadersPath() + "particlefire/particle.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+			VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.particles));
+		}
+
+		// Environment rendering pipeline (normal mapped)
+		{
+			// Vertex input state is taken from the glTF model loader
+			pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position, vkglTF::VertexComponent::UV, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::Tangent });
+
+			blendAttachmentState.blendEnable = VK_FALSE;
+			depthStencilState.depthWriteEnable = VK_TRUE;
+			inputAssemblyState.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
+
+			shaderStages[0] = loadShader(getShadersPath() + "particlefire/normalmap.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+			shaderStages[1] = loadShader(getShadersPath() + "particlefire/normalmap.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+			VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.environment));
+		}
+	}
+
+	// Prepare and initialize uniform buffer containing shader uniforms
+	void prepareUniformBuffers()
+	{
+		// Vertex shader uniform buffer block
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.fire,
+			sizeof(uboVS)));
+
+		// Vertex shader uniform buffer block
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.environment,
+			sizeof(uboEnv)));
+
+		// Map persistent
+		VK_CHECK_RESULT(uniformBuffers.fire.map());
+		VK_CHECK_RESULT(uniformBuffers.environment.map());
+
+		updateUniformBuffers();
+	}
+
+	void updateUniformBufferLight()
+	{
+		// Environment
+		uboEnv.lightPos.x = sin(timer * 2.0f * float(M_PI)) * 1.5f;
+		uboEnv.lightPos.y = 0.0f;
+		uboEnv.lightPos.z = cos(timer * 2.0f * float(M_PI)) * 1.5f;
+		memcpy(uniformBuffers.environment.mapped, &uboEnv, sizeof(uboEnv));
+	}
+
+	void updateUniformBuffers()
+	{
+		// Particle system fire
+		uboVS.projection = camera.matrices.perspective;
+		uboVS.modelView = camera.matrices.view;
+		uboVS.viewportDim = glm::vec2((float)width, (float)height);
+		memcpy(uniformBuffers.fire.mapped, &uboVS, sizeof(uboVS));
+
+		// Environment
+		uboEnv.projection = camera.matrices.perspective;
+		uboEnv.modelView = camera.matrices.view;
+		uboEnv.normal = glm::inverseTranspose(uboEnv.modelView);
+		memcpy(uniformBuffers.environment.mapped, &uboEnv, sizeof(uboEnv));
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+
+		// Command buffer to be submitted to the queue
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+
+		// Submit to queue
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+
+		VulkanExampleBase::submitFrame();
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		loadAssets();
+		prepareParticles();
+		prepareUniformBuffers();
+		setupDescriptorSetLayout();
+		preparePipelines();
+		setupDescriptorPool();
+		setupDescriptorSets();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+		if (!paused)
+		{
+			updateUniformBufferLight();
+			updateParticles();
+		}
+		if (camera.updated)
+		{
+			updateUniformBuffers();
+		}
+	}
+};
+
+VULKAN_EXAMPLE_MAIN()
\ No newline at end of file
diff --git a/external/Vulkan/examples/pbrbasic/pbrbasic.cpp b/external/Vulkan/examples/pbrbasic/pbrbasic.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..8eb6a1763f0968175fa6a2f8de5bcaf149c18a51
--- /dev/null
+++ b/external/Vulkan/examples/pbrbasic/pbrbasic.cpp
@@ -0,0 +1,391 @@
+/*
+* Vulkan Example - Physical based shading basics
+*
+* See http://graphicrants.blogspot.de/2013/08/specular-brdf-reference.html for a good reference to the different functions that make up a specular BRDF
+*
+* Copyright (C) 2017 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkanexamplebase.h"
+#include "VulkanglTFModel.h"
+
+#define VERTEX_BUFFER_BIND_ID 0
+#define ENABLE_VALIDATION false
+#define GRID_DIM 7
+#define OBJ_DIM 0.05f
+
+struct Material {
+	// Parameter block used as push constant block
+	struct PushBlock {
+		float roughness;
+		float metallic;
+		float r, g, b;
+	} params;
+	std::string name;
+	Material() {};
+	Material(std::string n, glm::vec3 c, float r, float m) : name(n) {
+		params.roughness = r;
+		params.metallic = m;
+		params.r = c.r;
+		params.g = c.g;
+		params.b = c.b;
+	};
+};
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	struct Meshes {
+		std::vector<vkglTF::Model> objects;
+		int32_t objectIndex = 0;
+	} models;
+
+	struct {
+		vks::Buffer object;
+		vks::Buffer params;
+	} uniformBuffers;
+
+	struct UBOMatrices {
+		glm::mat4 projection;
+		glm::mat4 model;
+		glm::mat4 view;
+		glm::vec3 camPos;
+	} uboMatrices;
+
+	struct UBOParams {
+		glm::vec4 lights[4];
+	} uboParams;
+
+	VkPipelineLayout pipelineLayout;
+	VkPipeline pipeline;
+	VkDescriptorSetLayout descriptorSetLayout;
+	VkDescriptorSet descriptorSet;
+
+	// Default materials to select from
+	std::vector<Material> materials;
+	int32_t materialIndex = 0;
+
+	std::vector<std::string> materialNames;
+	std::vector<std::string> objectNames;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Physical based shading basics";
+		camera.type = Camera::CameraType::firstperson;
+		camera.setPosition(glm::vec3(10.0f, 13.0f, 1.8f));
+		camera.setRotation(glm::vec3(-62.5f, 90.0f, 0.0f));
+		camera.movementSpeed = 4.0f;
+		camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 256.0f);
+		camera.rotationSpeed = 0.25f;
+		paused = true;
+		timerSpeed *= 0.25f;
+
+		// Setup some default materials (source: https://seblagarde.wordpress.com/2011/08/17/feeding-a-physical-based-lighting-mode/)
+		materials.push_back(Material("Gold", glm::vec3(1.0f, 0.765557f, 0.336057f), 0.1f, 1.0f));
+		materials.push_back(Material("Copper", glm::vec3(0.955008f, 0.637427f, 0.538163f), 0.1f, 1.0f));
+		materials.push_back(Material("Chromium", glm::vec3(0.549585f, 0.556114f, 0.554256f), 0.1f, 1.0f));
+		materials.push_back(Material("Nickel", glm::vec3(0.659777f, 0.608679f, 0.525649f), 0.1f, 1.0f));
+		materials.push_back(Material("Titanium", glm::vec3(0.541931f, 0.496791f, 0.449419f), 0.1f, 1.0f));
+		materials.push_back(Material("Cobalt", glm::vec3(0.662124f, 0.654864f, 0.633732f), 0.1f, 1.0f));
+		materials.push_back(Material("Platinum", glm::vec3(0.672411f, 0.637331f, 0.585456f), 0.1f, 1.0f));
+		// Testing materials
+		materials.push_back(Material("White", glm::vec3(1.0f), 0.1f, 1.0f));
+		materials.push_back(Material("Red", glm::vec3(1.0f, 0.0f, 0.0f), 0.1f, 1.0f));
+		materials.push_back(Material("Blue", glm::vec3(0.0f, 0.0f, 1.0f), 0.1f, 1.0f));
+		materials.push_back(Material("Black", glm::vec3(0.0f), 0.1f, 1.0f));
+
+		for (auto material : materials) {
+			materialNames.push_back(material.name);
+		}
+		objectNames = { "Sphere", "Teapot", "Torusknot", "Venus" };
+
+		materialIndex = 0;
+	}
+
+	~VulkanExample()
+	{
+		vkDestroyPipeline(device, pipeline, nullptr);
+
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+
+		uniformBuffers.object.destroy();
+		uniformBuffers.params.destroy();
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		clearValues[0].color = defaultClearColor;
+		clearValues[1].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.offset.x = 0;
+		renderPassBeginInfo.renderArea.offset.y = 0;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		renderPassBeginInfo.clearValueCount = 2;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			// Set target frame buffer
+			renderPassBeginInfo.framebuffer = frameBuffers[i];
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+			VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+			VkDeviceSize offsets[1] = { 0 };
+
+			// Objects
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL);
+
+			Material mat = materials[materialIndex];
+
+//#define SINGLE_ROW 1
+#ifdef SINGLE_ROW
+			mat.params.metallic = 1.0;
+
+			uint32_t objcount = 10;
+			for (uint32_t x = 0; x < objcount; x++) {
+				glm::vec3 pos = glm::vec3(float(x - (objcount / 2.0f)) * 2.5f, 0.0f, 0.0f);
+				mat.params.roughness = glm::clamp((float)x / (float)objcount, 0.005f, 1.0f);
+				vkCmdPushConstants(drawCmdBuffers[i], pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(glm::vec3), &pos);
+				vkCmdPushConstants(drawCmdBuffers[i], pipelineLayout, VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(glm::vec3), sizeof(Material::PushBlock), &mat);
+				models.objects[models.objectIndex].draw(drawCmdBuffers[i]);
+			}
+#else
+			for (uint32_t y = 0; y < GRID_DIM; y++) {
+				for (uint32_t x = 0; x < GRID_DIM; x++) {
+					glm::vec3 pos = glm::vec3(float(x - (GRID_DIM / 2.0f)) * 2.5f, 0.0f, float(y - (GRID_DIM / 2.0f)) * 2.5f);
+					vkCmdPushConstants(drawCmdBuffers[i], pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(glm::vec3), &pos);
+					mat.params.metallic = glm::clamp((float)x / (float)(GRID_DIM - 1), 0.1f, 1.0f);
+					mat.params.roughness = glm::clamp((float)y / (float)(GRID_DIM - 1), 0.05f, 1.0f);
+					vkCmdPushConstants(drawCmdBuffers[i], pipelineLayout, VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(glm::vec3), sizeof(Material::PushBlock), &mat);
+					models.objects[models.objectIndex].draw(drawCmdBuffers[i]);
+				}
+			}
+#endif
+			drawUI(drawCmdBuffers[i]);
+
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void loadAssets()
+	{
+		std::vector<std::string> filenames = { "sphere.gltf", "teapot.gltf", "torusknot.gltf", "venus.gltf" };
+		models.objects.resize(filenames.size());
+		for (size_t i = 0; i < filenames.size(); i++) {			
+			models.objects[i].loadFromFile(getAssetPath() + "models/" + filenames[i], vulkanDevice, queue, vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::FlipY);
+		}
+	}
+
+	void setupDescriptorSetLayout()
+	{
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0),
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_FRAGMENT_BIT, 1),
+		};
+
+		VkDescriptorSetLayoutCreateInfo descriptorLayout =
+			vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
+
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout));
+
+		VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo =
+			vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1);
+
+		std::vector<VkPushConstantRange> pushConstantRanges = {
+			vks::initializers::pushConstantRange(VK_SHADER_STAGE_VERTEX_BIT, sizeof(glm::vec3), 0),
+			vks::initializers::pushConstantRange(VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(Material::PushBlock), sizeof(glm::vec3)),
+		};
+
+		pipelineLayoutCreateInfo.pushConstantRangeCount = 2;
+		pipelineLayoutCreateInfo.pPushConstantRanges = pushConstantRanges.data();
+
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout));
+	}
+
+	void setupDescriptorSets()
+	{
+		// Descriptor Pool
+		std::vector<VkDescriptorPoolSize> poolSizes = {
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 4),
+		};
+
+		VkDescriptorPoolCreateInfo descriptorPoolInfo =
+			vks::initializers::descriptorPoolCreateInfo(poolSizes, 2);
+
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+
+		// Descriptor sets
+
+		VkDescriptorSetAllocateInfo allocInfo =
+			vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1);
+
+		// 3D object descriptor set
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet));
+
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets = {
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.object.descriptor),
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, &uniformBuffers.params.descriptor),
+		};
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL);
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState =  vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_FALSE, VK_FALSE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1);
+		VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT);
+		std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
+		VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass);
+
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+		pipelineCI.pInputAssemblyState = &inputAssemblyState;
+		pipelineCI.pRasterizationState = &rasterizationState;
+		pipelineCI.pColorBlendState = &colorBlendState;
+		pipelineCI.pMultisampleState = &multisampleState;
+		pipelineCI.pViewportState = &viewportState;
+		pipelineCI.pDepthStencilState = &depthStencilState;
+		pipelineCI.pDynamicState = &dynamicState;
+		pipelineCI.stageCount = static_cast<uint32_t>(shaderStages.size());
+		pipelineCI.pStages = shaderStages.data();
+		pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal });
+
+		// PBR pipeline
+		shaderStages[0] = loadShader(getShadersPath() + "pbrbasic/pbr.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "pbrbasic/pbr.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		// Enable depth test and write
+		depthStencilState.depthWriteEnable = VK_TRUE;
+		depthStencilState.depthTestEnable = VK_TRUE;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipeline));
+	}
+
+	// Prepare and initialize uniform buffer containing shader uniforms
+	void prepareUniformBuffers()
+	{
+		// Object vertex shader uniform buffer
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.object,
+			sizeof(uboMatrices)));
+
+		// Shared parameter uniform buffer
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.params,
+			sizeof(uboParams)));
+
+		// Map persistent
+		VK_CHECK_RESULT(uniformBuffers.object.map());
+		VK_CHECK_RESULT(uniformBuffers.params.map());
+
+		updateUniformBuffers();
+		updateLights();
+	}
+
+	void updateUniformBuffers()
+	{
+		// 3D object
+		uboMatrices.projection = camera.matrices.perspective;
+		uboMatrices.view = camera.matrices.view;
+		uboMatrices.model = glm::rotate(glm::mat4(1.0f), glm::radians(-90.0f + (models.objectIndex == 1 ? 45.0f : 0.0f)), glm::vec3(0.0f, 1.0f, 0.0f));
+		uboMatrices.camPos = camera.position * -1.0f;
+		memcpy(uniformBuffers.object.mapped, &uboMatrices, sizeof(uboMatrices));
+	}
+
+	void updateLights()
+	{
+		const float p = 15.0f;
+		uboParams.lights[0] = glm::vec4(-p, -p*0.5f, -p, 1.0f);
+		uboParams.lights[1] = glm::vec4(-p, -p*0.5f,  p, 1.0f);
+		uboParams.lights[2] = glm::vec4( p, -p*0.5f,  p, 1.0f);
+		uboParams.lights[3] = glm::vec4( p, -p*0.5f, -p, 1.0f);
+
+		if (!paused)
+		{
+			uboParams.lights[0].x = sin(glm::radians(timer * 360.0f)) * 20.0f;
+			uboParams.lights[0].z = cos(glm::radians(timer * 360.0f)) * 20.0f;
+			uboParams.lights[1].x = cos(glm::radians(timer * 360.0f)) * 20.0f;
+			uboParams.lights[1].y = sin(glm::radians(timer * 360.0f)) * 20.0f;
+		}
+
+		memcpy(uniformBuffers.params.mapped, &uboParams, sizeof(uboParams));
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+
+		VulkanExampleBase::submitFrame();
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		loadAssets();
+		prepareUniformBuffers();
+		setupDescriptorSetLayout();
+		preparePipelines();
+		setupDescriptorSets();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+		if (!paused)
+			updateLights();
+	}
+
+	virtual void viewChanged()
+	{
+		updateUniformBuffers();
+	}
+
+	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
+	{
+		if (overlay->header("Settings")) {
+			if (overlay->comboBox("Material", &materialIndex, materialNames)) {
+				buildCommandBuffers();
+			}
+			if (overlay->comboBox("Object type", &models.objectIndex, objectNames)) {
+				updateUniformBuffers();
+				buildCommandBuffers();
+			}
+		}
+	}
+};
+
+VULKAN_EXAMPLE_MAIN()
\ No newline at end of file
diff --git a/external/Vulkan/examples/pbribl/pbribl.cpp b/external/Vulkan/examples/pbribl/pbribl.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..49daccb33cc5704d1bf4eff4c16d3cfecce8a703
--- /dev/null
+++ b/external/Vulkan/examples/pbribl/pbribl.cpp
@@ -0,0 +1,1442 @@
+/*
+* Vulkan Example - Physical based rendering with image based lighting
+*
+* Note: Requires the separate asset pack (see data/README.md)
+*
+* Copyright (C) 2016-2017 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+// For reference see http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf
+
+#include "vulkanexamplebase.h"
+#include "VulkanglTFModel.h"
+
+#define ENABLE_VALIDATION false
+#define GRID_DIM 7
+
+struct Material {
+	// Parameter block used as push constant block
+	struct PushBlock {
+		float roughness = 0.0f;
+		float metallic = 0.0f;
+		float specular = 0.0f;
+		float r, g, b;
+	} params;
+	std::string name;
+	Material() {};
+	Material(std::string n, glm::vec3 c) : name(n) {
+		params.r = c.r;
+		params.g = c.g;
+		params.b = c.b;
+	};
+};
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	bool displaySkybox = true;
+
+	struct Textures {
+		vks::TextureCubeMap environmentCube;
+		// Generated at runtime
+		vks::Texture2D lutBrdf;
+		vks::TextureCubeMap irradianceCube;
+		vks::TextureCubeMap prefilteredCube;
+	} textures;
+
+	struct Meshes {
+		vkglTF::Model skybox;
+		std::vector<vkglTF::Model> objects;
+		int32_t objectIndex = 0;
+	} models;
+
+	struct {
+		vks::Buffer object;
+		vks::Buffer skybox;
+		vks::Buffer params;
+	} uniformBuffers;
+
+	struct UBOMatrices {
+		glm::mat4 projection;
+		glm::mat4 model;
+		glm::mat4 view;
+		glm::vec3 camPos;
+	} uboMatrices;
+
+	struct UBOParams {
+		glm::vec4 lights[4];
+		float exposure = 4.5f;
+		float gamma = 2.2f;
+	} uboParams;
+
+	struct {
+		VkPipeline skybox;
+		VkPipeline pbr;
+	} pipelines;
+
+	struct {
+		VkDescriptorSet object;
+		VkDescriptorSet skybox;
+	} descriptorSets;
+
+	VkPipelineLayout pipelineLayout;
+	VkDescriptorSetLayout descriptorSetLayout;
+
+	// Default materials to select from
+	std::vector<Material> materials;
+	int32_t materialIndex = 0;
+
+	std::vector<std::string> materialNames;
+	std::vector<std::string> objectNames;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "PBR with image based lighting";
+
+		camera.type = Camera::CameraType::firstperson;
+		camera.movementSpeed = 4.0f;
+		camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 256.0f);
+		camera.rotationSpeed = 0.25f;
+
+		camera.setRotation({ -3.75f, 180.0f, 0.0f });
+		camera.setPosition({ 0.55f, 0.85f, 12.0f });
+
+		// Setup some default materials (source: https://seblagarde.wordpress.com/2011/08/17/feeding-a-physical-based-lighting-mode/)
+		materials.push_back(Material("Gold", glm::vec3(1.0f, 0.765557f, 0.336057f)));
+		materials.push_back(Material("Copper", glm::vec3(0.955008f, 0.637427f, 0.538163f)));
+		materials.push_back(Material("Chromium", glm::vec3(0.549585f, 0.556114f, 0.554256f)));
+		materials.push_back(Material("Nickel", glm::vec3(0.659777f, 0.608679f, 0.525649f)));
+		materials.push_back(Material("Titanium", glm::vec3(0.541931f, 0.496791f, 0.449419f)));
+		materials.push_back(Material("Cobalt", glm::vec3(0.662124f, 0.654864f, 0.633732f)));
+		materials.push_back(Material("Platinum", glm::vec3(0.672411f, 0.637331f, 0.585456f)));
+		// Testing materials
+		materials.push_back(Material("White", glm::vec3(1.0f)));
+		materials.push_back(Material("Dark", glm::vec3(0.1f)));
+		materials.push_back(Material("Black", glm::vec3(0.0f)));
+		materials.push_back(Material("Red", glm::vec3(1.0f, 0.0f, 0.0f)));
+		materials.push_back(Material("Blue", glm::vec3(0.0f, 0.0f, 1.0f)));
+
+		for (auto material : materials) {
+			materialNames.push_back(material.name);
+		}
+		objectNames = { "Sphere", "Teapot", "Torusknot", "Venus" };
+
+		materialIndex = 9;
+	}
+
+	~VulkanExample()
+	{
+		vkDestroyPipeline(device, pipelines.skybox, nullptr);
+		vkDestroyPipeline(device, pipelines.pbr, nullptr);
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+		uniformBuffers.object.destroy();
+		uniformBuffers.skybox.destroy();
+		uniformBuffers.params.destroy();	
+		textures.environmentCube.destroy();
+		textures.irradianceCube.destroy();
+		textures.prefilteredCube.destroy();
+		textures.lutBrdf.destroy();
+	}
+
+	virtual void getEnabledFeatures()
+	{
+		if (deviceFeatures.samplerAnisotropy) {
+			enabledFeatures.samplerAnisotropy = VK_TRUE;
+		}
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		clearValues[0].color = { { 0.1f, 0.1f, 0.1f, 1.0f } };
+		clearValues[1].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.offset.x = 0;
+		renderPassBeginInfo.renderArea.offset.y = 0;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		renderPassBeginInfo.clearValueCount = 2;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		for (size_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			// Set target frame buffer
+			renderPassBeginInfo.framebuffer = frameBuffers[i];
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			VkViewport viewport = vks::initializers::viewport((float)width,	(float)height, 0.0f, 1.0f);
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+			VkRect2D scissor = vks::initializers::rect2D(width,	height,	0, 0);
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+			// Skybox
+			if (displaySkybox)
+			{
+				vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets.skybox, 0, NULL);
+				vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.skybox);
+				models.skybox.draw(drawCmdBuffers[i]);
+			}
+
+			// Objects
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets.object, 0, NULL);
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.pbr);
+
+			Material mat = materials[materialIndex];
+
+#define SINGLE_ROW 1
+#ifdef SINGLE_ROW
+			uint32_t objcount = 10;
+			for (uint32_t x = 0; x < objcount; x++) {
+				glm::vec3 pos = glm::vec3(float(x - (objcount / 2.0f)) * 2.15f, 0.0f, 0.0f);
+				mat.params.roughness = 1.0f-glm::clamp((float)x / (float)objcount, 0.005f, 1.0f);
+				mat.params.metallic = glm::clamp((float)x / (float)objcount, 0.005f, 1.0f);
+				vkCmdPushConstants(drawCmdBuffers[i], pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(glm::vec3), &pos);
+				vkCmdPushConstants(drawCmdBuffers[i], pipelineLayout, VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(glm::vec3), sizeof(Material::PushBlock), &mat);
+				models.objects[models.objectIndex].draw(drawCmdBuffers[i]);
+
+			}
+#else
+			for (uint32_t y = 0; y < GRID_DIM; y++) {
+				mat.params.metallic = (float)y / (float)(GRID_DIM);
+				for (uint32_t x = 0; x < GRID_DIM; x++) {
+					glm::vec3 pos = glm::vec3(float(x - (GRID_DIM / 2.0f)) * 2.5f, 0.0f, float(y - (GRID_DIM / 2.0f)) * 2.5f);
+					vkCmdPushConstants(drawCmdBuffers[i], pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(glm::vec3), &pos);
+					mat.params.roughness = glm::clamp((float)x / (float)(GRID_DIM), 0.05f, 1.0f);
+					vkCmdPushConstants(drawCmdBuffers[i], pipelineLayout, VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(glm::vec3), sizeof(Material::PushBlock), &mat);
+					models.objects[models.objectIndex].draw(drawCmdBuffers[i]);
+				}
+			}
+#endif
+			drawUI(drawCmdBuffers[i]);
+
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void loadAssets()
+	{
+		uint32_t glTFLoadingFlags = vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::FlipY;
+		// Skybox
+		models.skybox.loadFromFile(getAssetPath() + "models/cube.gltf", vulkanDevice, queue, glTFLoadingFlags);
+		// Objects
+		std::vector<std::string> filenames = { "sphere.gltf", "teapot.gltf", "torusknot.gltf", "venus.gltf" };
+		models.objects.resize(filenames.size());
+		for (size_t i = 0; i < filenames.size(); i++) {
+			models.objects[i].loadFromFile(getAssetPath() + "models/" + filenames[i], vulkanDevice, queue, glTFLoadingFlags);
+		}
+		// HDR cubemap
+		textures.environmentCube.loadFromFile(getAssetPath() + "textures/hdr/pisa_cube.ktx", VK_FORMAT_R16G16B16A16_SFLOAT, vulkanDevice, queue);
+	}
+
+	void setupDescriptors()
+	{
+		// Descriptor Pool
+		std::vector<VkDescriptorPoolSize> poolSizes = {
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 4),
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 6)
+		};
+		VkDescriptorPoolCreateInfo descriptorPoolInfo =	vks::initializers::descriptorPoolCreateInfo(poolSizes, 2);
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+
+		// Descriptor set layout
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0),
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_FRAGMENT_BIT, 1),
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 2),
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 3),
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 4),
+		};
+		VkDescriptorSetLayoutCreateInfo descriptorLayout = 	vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout));
+
+		// Descriptor sets
+		VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1);
+
+		// Objects
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.object));
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets = {
+			vks::initializers::writeDescriptorSet(descriptorSets.object, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.object.descriptor),
+			vks::initializers::writeDescriptorSet(descriptorSets.object, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, &uniformBuffers.params.descriptor),
+			vks::initializers::writeDescriptorSet(descriptorSets.object, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2, &textures.irradianceCube.descriptor),
+			vks::initializers::writeDescriptorSet(descriptorSets.object, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 3, &textures.lutBrdf.descriptor),
+			vks::initializers::writeDescriptorSet(descriptorSets.object, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 4, &textures.prefilteredCube.descriptor),
+		};
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL);
+
+		// Sky box
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.skybox));
+		writeDescriptorSets = {
+			vks::initializers::writeDescriptorSet(descriptorSets.skybox, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.skybox.descriptor),
+			vks::initializers::writeDescriptorSet(descriptorSets.skybox, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, &uniformBuffers.params.descriptor),
+			vks::initializers::writeDescriptorSet(descriptorSets.skybox, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2, &textures.environmentCube.descriptor),
+		};
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL);
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState =
+			vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+
+		VkPipelineRasterizationStateCreateInfo rasterizationState =
+			vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE, VK_FRONT_FACE_COUNTER_CLOCKWISE);
+
+		VkPipelineColorBlendAttachmentState blendAttachmentState =
+			vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+
+		VkPipelineColorBlendStateCreateInfo colorBlendState =
+			vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+
+		VkPipelineDepthStencilStateCreateInfo depthStencilState =
+			vks::initializers::pipelineDepthStencilStateCreateInfo(VK_FALSE, VK_FALSE, VK_COMPARE_OP_LESS_OR_EQUAL);
+
+		VkPipelineViewportStateCreateInfo viewportState =
+			vks::initializers::pipelineViewportStateCreateInfo(1, 1);
+
+		VkPipelineMultisampleStateCreateInfo multisampleState =
+			vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT);
+
+		std::vector<VkDynamicState> dynamicStateEnables = {
+			VK_DYNAMIC_STATE_VIEWPORT,
+			VK_DYNAMIC_STATE_SCISSOR
+		};
+		VkPipelineDynamicStateCreateInfo dynamicState =
+			vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
+
+		// Pipeline layout
+		VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1);
+		// Push constant ranges
+		std::vector<VkPushConstantRange> pushConstantRanges = {
+			vks::initializers::pushConstantRange(VK_SHADER_STAGE_VERTEX_BIT, sizeof(glm::vec3), 0),
+			vks::initializers::pushConstantRange(VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(Material::PushBlock), sizeof(glm::vec3)),
+		};
+		pipelineLayoutCreateInfo.pushConstantRangeCount = 2;
+		pipelineLayoutCreateInfo.pPushConstantRanges = pushConstantRanges.data();
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout));
+
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+
+		// Pipelines
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass);
+		pipelineCI.pInputAssemblyState = &inputAssemblyState;
+		pipelineCI.pRasterizationState = &rasterizationState;
+		pipelineCI.pColorBlendState = &colorBlendState;
+		pipelineCI.pMultisampleState = &multisampleState;
+		pipelineCI.pViewportState = &viewportState;
+		pipelineCI.pDepthStencilState = &depthStencilState;
+		pipelineCI.pDynamicState = &dynamicState;
+		pipelineCI.stageCount = static_cast<uint32_t>(shaderStages.size());
+		pipelineCI.pStages = shaderStages.data();
+		pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::UV });
+
+		// Skybox pipeline (background cube)
+		shaderStages[0] = loadShader(getShadersPath() + "pbribl/skybox.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "pbribl/skybox.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.skybox));
+
+		// PBR pipeline
+		shaderStages[0] = loadShader(getShadersPath() + "pbribl/pbribl.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "pbribl/pbribl.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		// Enable depth test and write
+		depthStencilState.depthWriteEnable = VK_TRUE;
+		depthStencilState.depthTestEnable = VK_TRUE;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.pbr));
+	}
+
+	// Generate a BRDF integration map used as a look-up-table (stores roughness / NdotV)
+	void generateBRDFLUT()
+	{
+		auto tStart = std::chrono::high_resolution_clock::now();
+
+		const VkFormat format = VK_FORMAT_R16G16_SFLOAT;	// R16G16 is supported pretty much everywhere
+		const int32_t dim = 512;
+
+		// Image
+		VkImageCreateInfo imageCI = vks::initializers::imageCreateInfo();
+		imageCI.imageType = VK_IMAGE_TYPE_2D;
+		imageCI.format = format;
+		imageCI.extent.width = dim;
+		imageCI.extent.height = dim;
+		imageCI.extent.depth = 1;
+		imageCI.mipLevels = 1;
+		imageCI.arrayLayers = 1;
+		imageCI.samples = VK_SAMPLE_COUNT_1_BIT;
+		imageCI.tiling = VK_IMAGE_TILING_OPTIMAL;
+		imageCI.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
+		VK_CHECK_RESULT(vkCreateImage(device, &imageCI, nullptr, &textures.lutBrdf.image));
+		VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo();
+		VkMemoryRequirements memReqs;
+		vkGetImageMemoryRequirements(device, textures.lutBrdf.image, &memReqs);
+		memAlloc.allocationSize = memReqs.size;
+		memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+		VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &textures.lutBrdf.deviceMemory));
+		VK_CHECK_RESULT(vkBindImageMemory(device, textures.lutBrdf.image, textures.lutBrdf.deviceMemory, 0));
+		// Image view
+		VkImageViewCreateInfo viewCI = vks::initializers::imageViewCreateInfo();
+		viewCI.viewType = VK_IMAGE_VIEW_TYPE_2D;
+		viewCI.format = format;
+		viewCI.subresourceRange = {};
+		viewCI.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+		viewCI.subresourceRange.levelCount = 1;
+		viewCI.subresourceRange.layerCount = 1;
+		viewCI.image = textures.lutBrdf.image;
+		VK_CHECK_RESULT(vkCreateImageView(device, &viewCI, nullptr, &textures.lutBrdf.view));
+		// Sampler
+		VkSamplerCreateInfo samplerCI = vks::initializers::samplerCreateInfo();
+		samplerCI.magFilter = VK_FILTER_LINEAR;
+		samplerCI.minFilter = VK_FILTER_LINEAR;
+		samplerCI.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+		samplerCI.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+		samplerCI.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+		samplerCI.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+		samplerCI.minLod = 0.0f;
+		samplerCI.maxLod = 1.0f;
+		samplerCI.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
+		VK_CHECK_RESULT(vkCreateSampler(device, &samplerCI, nullptr, &textures.lutBrdf.sampler));
+
+		textures.lutBrdf.descriptor.imageView = textures.lutBrdf.view;
+		textures.lutBrdf.descriptor.sampler = textures.lutBrdf.sampler;
+		textures.lutBrdf.descriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+		textures.lutBrdf.device = vulkanDevice;
+
+		// FB, Att, RP, Pipe, etc.
+		VkAttachmentDescription attDesc = {};
+		// Color attachment
+		attDesc.format = format;
+		attDesc.samples = VK_SAMPLE_COUNT_1_BIT;
+		attDesc.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+		attDesc.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+		attDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+		attDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+		attDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+		attDesc.finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+		VkAttachmentReference colorReference = { 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
+
+		VkSubpassDescription subpassDescription = {};
+		subpassDescription.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+		subpassDescription.colorAttachmentCount = 1;
+		subpassDescription.pColorAttachments = &colorReference;
+
+		// Use subpass dependencies for layout transitions
+		std::array<VkSubpassDependency, 2> dependencies;
+		dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
+		dependencies[0].dstSubpass = 0;
+		dependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+		dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+		dependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
+		dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+		dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+		dependencies[1].srcSubpass = 0;
+		dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
+		dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+		dependencies[1].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+		dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+		dependencies[1].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
+		dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+		// Create the actual renderpass
+		VkRenderPassCreateInfo renderPassCI = vks::initializers::renderPassCreateInfo();
+		renderPassCI.attachmentCount = 1;
+		renderPassCI.pAttachments = &attDesc;
+		renderPassCI.subpassCount = 1;
+		renderPassCI.pSubpasses = &subpassDescription;
+		renderPassCI.dependencyCount = 2;
+		renderPassCI.pDependencies = dependencies.data();
+
+		VkRenderPass renderpass;
+		VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassCI, nullptr, &renderpass));
+
+		VkFramebufferCreateInfo framebufferCI = vks::initializers::framebufferCreateInfo();
+		framebufferCI.renderPass = renderpass;
+		framebufferCI.attachmentCount = 1;
+		framebufferCI.pAttachments = &textures.lutBrdf.view;
+		framebufferCI.width = dim;
+		framebufferCI.height = dim;
+		framebufferCI.layers = 1;
+
+		VkFramebuffer framebuffer;
+		VK_CHECK_RESULT(vkCreateFramebuffer(device, &framebufferCI, nullptr, &framebuffer));
+
+		// Descriptors
+		VkDescriptorSetLayout descriptorsetlayout;
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {};
+		VkDescriptorSetLayoutCreateInfo descriptorsetlayoutCI = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorsetlayoutCI, nullptr, &descriptorsetlayout));
+
+		// Descriptor Pool
+		std::vector<VkDescriptorPoolSize> poolSizes = { vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1) };
+		VkDescriptorPoolCreateInfo descriptorPoolCI = vks::initializers::descriptorPoolCreateInfo(poolSizes, 2);
+		VkDescriptorPool descriptorpool;
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolCI, nullptr, &descriptorpool));
+
+		// Descriptor sets
+		VkDescriptorSet descriptorset;
+		VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorpool, &descriptorsetlayout, 1);
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorset));
+
+		// Pipeline layout
+		VkPipelineLayout pipelinelayout;
+		VkPipelineLayoutCreateInfo pipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(&descriptorsetlayout, 1);
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelinelayout));
+
+		// Pipeline
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE, VK_FRONT_FACE_COUNTER_CLOCKWISE);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_FALSE, VK_FALSE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1);
+		VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT);
+		std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
+		VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
+		VkPipelineVertexInputStateCreateInfo emptyInputState = vks::initializers::pipelineVertexInputStateCreateInfo();
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelinelayout, renderpass);
+		pipelineCI.pInputAssemblyState = &inputAssemblyState;
+		pipelineCI.pRasterizationState = &rasterizationState;
+		pipelineCI.pColorBlendState = &colorBlendState;
+		pipelineCI.pMultisampleState = &multisampleState;
+		pipelineCI.pViewportState = &viewportState;
+		pipelineCI.pDepthStencilState = &depthStencilState;
+		pipelineCI.pDynamicState = &dynamicState;
+		pipelineCI.stageCount = 2;
+		pipelineCI.pStages = shaderStages.data();
+		pipelineCI.pVertexInputState = &emptyInputState;
+
+		// Look-up-table (from BRDF) pipeline
+		shaderStages[0] = loadShader(getShadersPath() + "pbribl/genbrdflut.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "pbribl/genbrdflut.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		VkPipeline pipeline;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipeline));
+
+		// Render
+		VkClearValue clearValues[1];
+		clearValues[0].color = { { 0.0f, 0.0f, 0.0f, 1.0f } };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderpass;
+		renderPassBeginInfo.renderArea.extent.width = dim;
+		renderPassBeginInfo.renderArea.extent.height = dim;
+		renderPassBeginInfo.clearValueCount = 1;
+		renderPassBeginInfo.pClearValues = clearValues;
+		renderPassBeginInfo.framebuffer = framebuffer;
+
+		VkCommandBuffer cmdBuf = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+		vkCmdBeginRenderPass(cmdBuf, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+		VkViewport viewport = vks::initializers::viewport((float)dim, (float)dim, 0.0f, 1.0f);
+		VkRect2D scissor = vks::initializers::rect2D(dim, dim, 0, 0);
+		vkCmdSetViewport(cmdBuf, 0, 1, &viewport);
+		vkCmdSetScissor(cmdBuf, 0, 1, &scissor);
+		vkCmdBindPipeline(cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
+		vkCmdDraw(cmdBuf, 3, 1, 0, 0);
+		vkCmdEndRenderPass(cmdBuf);
+		vulkanDevice->flushCommandBuffer(cmdBuf, queue);
+
+		vkQueueWaitIdle(queue);
+
+		// todo: cleanup
+		vkDestroyPipeline(device, pipeline, nullptr);
+		vkDestroyPipelineLayout(device, pipelinelayout, nullptr);
+		vkDestroyRenderPass(device, renderpass, nullptr);
+		vkDestroyFramebuffer(device, framebuffer, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorsetlayout, nullptr);
+		vkDestroyDescriptorPool(device, descriptorpool, nullptr);
+
+		auto tEnd = std::chrono::high_resolution_clock::now();
+		auto tDiff = std::chrono::duration<double, std::milli>(tEnd - tStart).count();
+		std::cout << "Generating BRDF LUT took " << tDiff << " ms" << std::endl;
+	}
+
+	// Generate an irradiance cube map from the environment cube map
+	void generateIrradianceCube()
+	{
+		auto tStart = std::chrono::high_resolution_clock::now();
+
+		const VkFormat format = VK_FORMAT_R32G32B32A32_SFLOAT;
+		const int32_t dim = 64;
+		const uint32_t numMips = static_cast<uint32_t>(floor(log2(dim))) + 1;
+
+		// Pre-filtered cube map
+		// Image
+		VkImageCreateInfo imageCI = vks::initializers::imageCreateInfo();
+		imageCI.imageType = VK_IMAGE_TYPE_2D;
+		imageCI.format = format;
+		imageCI.extent.width = dim;
+		imageCI.extent.height = dim;
+		imageCI.extent.depth = 1;
+		imageCI.mipLevels = numMips;
+		imageCI.arrayLayers = 6;
+		imageCI.samples = VK_SAMPLE_COUNT_1_BIT;
+		imageCI.tiling = VK_IMAGE_TILING_OPTIMAL;
+		imageCI.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
+		imageCI.flags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;
+		VK_CHECK_RESULT(vkCreateImage(device, &imageCI, nullptr, &textures.irradianceCube.image));
+		VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo();
+		VkMemoryRequirements memReqs;
+		vkGetImageMemoryRequirements(device, textures.irradianceCube.image, &memReqs);
+		memAlloc.allocationSize = memReqs.size;
+		memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+		VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &textures.irradianceCube.deviceMemory));
+		VK_CHECK_RESULT(vkBindImageMemory(device, textures.irradianceCube.image, textures.irradianceCube.deviceMemory, 0));
+		// Image view
+		VkImageViewCreateInfo viewCI = vks::initializers::imageViewCreateInfo();
+		viewCI.viewType = VK_IMAGE_VIEW_TYPE_CUBE;
+		viewCI.format = format;
+		viewCI.subresourceRange = {};
+		viewCI.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+		viewCI.subresourceRange.levelCount = numMips;
+		viewCI.subresourceRange.layerCount = 6;
+		viewCI.image = textures.irradianceCube.image;
+		VK_CHECK_RESULT(vkCreateImageView(device, &viewCI, nullptr, &textures.irradianceCube.view));
+		// Sampler
+		VkSamplerCreateInfo samplerCI = vks::initializers::samplerCreateInfo();
+		samplerCI.magFilter = VK_FILTER_LINEAR;
+		samplerCI.minFilter = VK_FILTER_LINEAR;
+		samplerCI.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+		samplerCI.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+		samplerCI.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+		samplerCI.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+		samplerCI.minLod = 0.0f;
+		samplerCI.maxLod = static_cast<float>(numMips);
+		samplerCI.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
+		VK_CHECK_RESULT(vkCreateSampler(device, &samplerCI, nullptr, &textures.irradianceCube.sampler));
+
+		textures.irradianceCube.descriptor.imageView = textures.irradianceCube.view;
+		textures.irradianceCube.descriptor.sampler = textures.irradianceCube.sampler;
+		textures.irradianceCube.descriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+		textures.irradianceCube.device = vulkanDevice;
+
+		// FB, Att, RP, Pipe, etc.
+		VkAttachmentDescription attDesc = {};
+		// Color attachment
+		attDesc.format = format;
+		attDesc.samples = VK_SAMPLE_COUNT_1_BIT;
+		attDesc.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+		attDesc.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+		attDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+		attDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+		attDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+		attDesc.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+		VkAttachmentReference colorReference = { 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
+
+		VkSubpassDescription subpassDescription = {};
+		subpassDescription.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+		subpassDescription.colorAttachmentCount = 1;
+		subpassDescription.pColorAttachments = &colorReference;
+
+		// Use subpass dependencies for layout transitions
+		std::array<VkSubpassDependency, 2> dependencies;
+		dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
+		dependencies[0].dstSubpass = 0;
+		dependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+		dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+		dependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
+		dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+		dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+		dependencies[1].srcSubpass = 0;
+		dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
+		dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+		dependencies[1].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+		dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+		dependencies[1].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
+		dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+		// Renderpass
+		VkRenderPassCreateInfo renderPassCI = vks::initializers::renderPassCreateInfo();
+		renderPassCI.attachmentCount = 1;
+		renderPassCI.pAttachments = &attDesc;
+		renderPassCI.subpassCount = 1;
+		renderPassCI.pSubpasses = &subpassDescription;
+		renderPassCI.dependencyCount = 2;
+		renderPassCI.pDependencies = dependencies.data();
+		VkRenderPass renderpass;
+		VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassCI, nullptr, &renderpass));
+
+		struct {
+			VkImage image;
+			VkImageView view;
+			VkDeviceMemory memory;
+			VkFramebuffer framebuffer;
+		} offscreen;
+
+		// Offfscreen framebuffer
+		{
+			// Color attachment
+			VkImageCreateInfo imageCreateInfo = vks::initializers::imageCreateInfo();
+			imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
+			imageCreateInfo.format = format;
+			imageCreateInfo.extent.width = dim;
+			imageCreateInfo.extent.height = dim;
+			imageCreateInfo.extent.depth = 1;
+			imageCreateInfo.mipLevels = 1;
+			imageCreateInfo.arrayLayers = 1;
+			imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
+			imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
+			imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+			imageCreateInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
+			imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+			VK_CHECK_RESULT(vkCreateImage(device, &imageCreateInfo, nullptr, &offscreen.image));
+
+			VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo();
+			VkMemoryRequirements memReqs;
+			vkGetImageMemoryRequirements(device, offscreen.image, &memReqs);
+			memAlloc.allocationSize = memReqs.size;
+			memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+			VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &offscreen.memory));
+			VK_CHECK_RESULT(vkBindImageMemory(device, offscreen.image, offscreen.memory, 0));
+
+			VkImageViewCreateInfo colorImageView = vks::initializers::imageViewCreateInfo();
+			colorImageView.viewType = VK_IMAGE_VIEW_TYPE_2D;
+			colorImageView.format = format;
+			colorImageView.flags = 0;
+			colorImageView.subresourceRange = {};
+			colorImageView.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+			colorImageView.subresourceRange.baseMipLevel = 0;
+			colorImageView.subresourceRange.levelCount = 1;
+			colorImageView.subresourceRange.baseArrayLayer = 0;
+			colorImageView.subresourceRange.layerCount = 1;
+			colorImageView.image = offscreen.image;
+			VK_CHECK_RESULT(vkCreateImageView(device, &colorImageView, nullptr, &offscreen.view));
+
+			VkFramebufferCreateInfo fbufCreateInfo = vks::initializers::framebufferCreateInfo();
+			fbufCreateInfo.renderPass = renderpass;
+			fbufCreateInfo.attachmentCount = 1;
+			fbufCreateInfo.pAttachments = &offscreen.view;
+			fbufCreateInfo.width = dim;
+			fbufCreateInfo.height = dim;
+			fbufCreateInfo.layers = 1;
+			VK_CHECK_RESULT(vkCreateFramebuffer(device, &fbufCreateInfo, nullptr, &offscreen.framebuffer));
+
+			VkCommandBuffer layoutCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+			vks::tools::setImageLayout(
+				layoutCmd,
+				offscreen.image,
+				VK_IMAGE_ASPECT_COLOR_BIT,
+				VK_IMAGE_LAYOUT_UNDEFINED,
+				VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
+			vulkanDevice->flushCommandBuffer(layoutCmd, queue, true);
+		}
+
+		// Descriptors
+		VkDescriptorSetLayout descriptorsetlayout;
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0),
+		};
+		VkDescriptorSetLayoutCreateInfo descriptorsetlayoutCI = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorsetlayoutCI, nullptr, &descriptorsetlayout));
+
+		// Descriptor Pool
+		std::vector<VkDescriptorPoolSize> poolSizes = { vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1) };
+		VkDescriptorPoolCreateInfo descriptorPoolCI = vks::initializers::descriptorPoolCreateInfo(poolSizes, 2);
+		VkDescriptorPool descriptorpool;
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolCI, nullptr, &descriptorpool));
+
+		// Descriptor sets
+		VkDescriptorSet descriptorset;
+		VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorpool, &descriptorsetlayout, 1);
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorset));
+		VkWriteDescriptorSet writeDescriptorSet = vks::initializers::writeDescriptorSet(descriptorset, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &textures.environmentCube.descriptor);
+		vkUpdateDescriptorSets(device, 1, &writeDescriptorSet, 0, nullptr);
+
+		// Pipeline layout
+		struct PushBlock {
+			glm::mat4 mvp;
+			// Sampling deltas
+			float deltaPhi = (2.0f * float(M_PI)) / 180.0f;
+			float deltaTheta = (0.5f * float(M_PI)) / 64.0f;
+		} pushBlock;
+
+		VkPipelineLayout pipelinelayout;
+		std::vector<VkPushConstantRange> pushConstantRanges = {
+			vks::initializers::pushConstantRange(VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(PushBlock), 0),
+		};
+		VkPipelineLayoutCreateInfo pipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(&descriptorsetlayout, 1);
+		pipelineLayoutCI.pushConstantRangeCount = 1;
+		pipelineLayoutCI.pPushConstantRanges = pushConstantRanges.data();
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelinelayout));
+
+		// Pipeline
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE, VK_FRONT_FACE_COUNTER_CLOCKWISE);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_FALSE, VK_FALSE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1);
+		VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT);
+		std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
+		VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelinelayout, renderpass);
+		pipelineCI.pInputAssemblyState = &inputAssemblyState;
+		pipelineCI.pRasterizationState = &rasterizationState;
+		pipelineCI.pColorBlendState = &colorBlendState;
+		pipelineCI.pMultisampleState = &multisampleState;
+		pipelineCI.pViewportState = &viewportState;
+		pipelineCI.pDepthStencilState = &depthStencilState;
+		pipelineCI.pDynamicState = &dynamicState;
+		pipelineCI.stageCount = 2;
+		pipelineCI.pStages = shaderStages.data();
+		pipelineCI.renderPass = renderpass;
+		pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::UV });
+
+		shaderStages[0] = loadShader(getShadersPath() + "pbribl/filtercube.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "pbribl/irradiancecube.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		VkPipeline pipeline;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipeline));
+
+		// Render
+
+		VkClearValue clearValues[1];
+		clearValues[0].color = { { 0.0f, 0.0f, 0.2f, 0.0f } };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		// Reuse render pass from example pass
+		renderPassBeginInfo.renderPass = renderpass;
+		renderPassBeginInfo.framebuffer = offscreen.framebuffer;
+		renderPassBeginInfo.renderArea.extent.width = dim;
+		renderPassBeginInfo.renderArea.extent.height = dim;
+		renderPassBeginInfo.clearValueCount = 1;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		std::vector<glm::mat4> matrices = {
+			// POSITIVE_X
+			glm::rotate(glm::rotate(glm::mat4(1.0f), glm::radians(90.0f), glm::vec3(0.0f, 1.0f, 0.0f)), glm::radians(180.0f), glm::vec3(1.0f, 0.0f, 0.0f)),
+			// NEGATIVE_X
+			glm::rotate(glm::rotate(glm::mat4(1.0f), glm::radians(-90.0f), glm::vec3(0.0f, 1.0f, 0.0f)), glm::radians(180.0f), glm::vec3(1.0f, 0.0f, 0.0f)),
+			// POSITIVE_Y
+			glm::rotate(glm::mat4(1.0f), glm::radians(-90.0f), glm::vec3(1.0f, 0.0f, 0.0f)),
+			// NEGATIVE_Y
+			glm::rotate(glm::mat4(1.0f), glm::radians(90.0f), glm::vec3(1.0f, 0.0f, 0.0f)),
+			// POSITIVE_Z
+			glm::rotate(glm::mat4(1.0f), glm::radians(180.0f), glm::vec3(1.0f, 0.0f, 0.0f)),
+			// NEGATIVE_Z
+			glm::rotate(glm::mat4(1.0f), glm::radians(180.0f), glm::vec3(0.0f, 0.0f, 1.0f)),
+		};
+
+		VkCommandBuffer cmdBuf = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+
+		VkViewport viewport = vks::initializers::viewport((float)dim, (float)dim, 0.0f, 1.0f);
+		VkRect2D scissor = vks::initializers::rect2D(dim, dim, 0, 0);
+
+		vkCmdSetViewport(cmdBuf, 0, 1, &viewport);
+		vkCmdSetScissor(cmdBuf, 0, 1, &scissor);
+
+		VkImageSubresourceRange subresourceRange = {};
+		subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+		subresourceRange.baseMipLevel = 0;
+		subresourceRange.levelCount = numMips;
+		subresourceRange.layerCount = 6;
+
+		// Change image layout for all cubemap faces to transfer destination
+		vks::tools::setImageLayout(
+			cmdBuf,
+			textures.irradianceCube.image,
+			VK_IMAGE_LAYOUT_UNDEFINED,
+			VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+			subresourceRange);
+
+		for (uint32_t m = 0; m < numMips; m++) {
+			for (uint32_t f = 0; f < 6; f++) {
+				viewport.width = static_cast<float>(dim * std::pow(0.5f, m));
+				viewport.height = static_cast<float>(dim * std::pow(0.5f, m));
+				vkCmdSetViewport(cmdBuf, 0, 1, &viewport);
+
+				// Render scene from cube face's point of view
+				vkCmdBeginRenderPass(cmdBuf, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+				// Update shader push constant block
+				pushBlock.mvp = glm::perspective((float)(M_PI / 2.0), 1.0f, 0.1f, 512.0f) * matrices[f];
+
+				vkCmdPushConstants(cmdBuf, pipelinelayout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushBlock), &pushBlock);
+
+				vkCmdBindPipeline(cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
+				vkCmdBindDescriptorSets(cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelinelayout, 0, 1, &descriptorset, 0, NULL);
+
+				models.skybox.draw(cmdBuf);
+
+				vkCmdEndRenderPass(cmdBuf);
+
+				vks::tools::setImageLayout(
+					cmdBuf,
+					offscreen.image,
+					VK_IMAGE_ASPECT_COLOR_BIT,
+					VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+					VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
+
+				// Copy region for transfer from framebuffer to cube face
+				VkImageCopy copyRegion = {};
+
+				copyRegion.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+				copyRegion.srcSubresource.baseArrayLayer = 0;
+				copyRegion.srcSubresource.mipLevel = 0;
+				copyRegion.srcSubresource.layerCount = 1;
+				copyRegion.srcOffset = { 0, 0, 0 };
+
+				copyRegion.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+				copyRegion.dstSubresource.baseArrayLayer = f;
+				copyRegion.dstSubresource.mipLevel = m;
+				copyRegion.dstSubresource.layerCount = 1;
+				copyRegion.dstOffset = { 0, 0, 0 };
+
+				copyRegion.extent.width = static_cast<uint32_t>(viewport.width);
+				copyRegion.extent.height = static_cast<uint32_t>(viewport.height);
+				copyRegion.extent.depth = 1;
+
+				vkCmdCopyImage(
+					cmdBuf,
+					offscreen.image,
+					VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+					textures.irradianceCube.image,
+					VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+					1,
+					&copyRegion);
+
+				// Transform framebuffer color attachment back
+				vks::tools::setImageLayout(
+					cmdBuf,
+					offscreen.image,
+					VK_IMAGE_ASPECT_COLOR_BIT,
+					VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+					VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
+			}
+		}
+
+		vks::tools::setImageLayout(
+			cmdBuf,
+			textures.irradianceCube.image,
+			VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+			VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
+			subresourceRange);
+
+		vulkanDevice->flushCommandBuffer(cmdBuf, queue);
+
+		vkDestroyRenderPass(device, renderpass, nullptr);
+		vkDestroyFramebuffer(device, offscreen.framebuffer, nullptr);
+		vkFreeMemory(device, offscreen.memory, nullptr);
+		vkDestroyImageView(device, offscreen.view, nullptr);
+		vkDestroyImage(device, offscreen.image, nullptr);
+		vkDestroyDescriptorPool(device, descriptorpool, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorsetlayout, nullptr);
+		vkDestroyPipeline(device, pipeline, nullptr);
+		vkDestroyPipelineLayout(device, pipelinelayout, nullptr);
+
+		auto tEnd = std::chrono::high_resolution_clock::now();
+		auto tDiff = std::chrono::duration<double, std::milli>(tEnd - tStart).count();
+		std::cout << "Generating irradiance cube with " << numMips << " mip levels took " << tDiff << " ms" << std::endl;
+	}
+
+	// Prefilter environment cubemap
+	// See https://placeholderart.wordpress.com/2015/07/28/implementation-notes-runtime-environment-map-filtering-for-image-based-lighting/
+	void generatePrefilteredCube()
+	{
+		auto tStart = std::chrono::high_resolution_clock::now();
+
+		const VkFormat format = VK_FORMAT_R16G16B16A16_SFLOAT;
+		const int32_t dim = 512;
+		const uint32_t numMips = static_cast<uint32_t>(floor(log2(dim))) + 1;
+
+		// Pre-filtered cube map
+		// Image
+		VkImageCreateInfo imageCI = vks::initializers::imageCreateInfo();
+		imageCI.imageType = VK_IMAGE_TYPE_2D;
+		imageCI.format = format;
+		imageCI.extent.width = dim;
+		imageCI.extent.height = dim;
+		imageCI.extent.depth = 1;
+		imageCI.mipLevels = numMips;
+		imageCI.arrayLayers = 6;
+		imageCI.samples = VK_SAMPLE_COUNT_1_BIT;
+		imageCI.tiling = VK_IMAGE_TILING_OPTIMAL;
+		imageCI.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
+		imageCI.flags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;
+		VK_CHECK_RESULT(vkCreateImage(device, &imageCI, nullptr, &textures.prefilteredCube.image));
+		VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo();
+		VkMemoryRequirements memReqs;
+		vkGetImageMemoryRequirements(device, textures.prefilteredCube.image, &memReqs);
+		memAlloc.allocationSize = memReqs.size;
+		memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+		VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &textures.prefilteredCube.deviceMemory));
+		VK_CHECK_RESULT(vkBindImageMemory(device, textures.prefilteredCube.image, textures.prefilteredCube.deviceMemory, 0));
+		// Image view
+		VkImageViewCreateInfo viewCI = vks::initializers::imageViewCreateInfo();
+		viewCI.viewType = VK_IMAGE_VIEW_TYPE_CUBE;
+		viewCI.format = format;
+		viewCI.subresourceRange = {};
+		viewCI.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+		viewCI.subresourceRange.levelCount = numMips;
+		viewCI.subresourceRange.layerCount = 6;
+		viewCI.image = textures.prefilteredCube.image;
+		VK_CHECK_RESULT(vkCreateImageView(device, &viewCI, nullptr, &textures.prefilteredCube.view));
+		// Sampler
+		VkSamplerCreateInfo samplerCI = vks::initializers::samplerCreateInfo();
+		samplerCI.magFilter = VK_FILTER_LINEAR;
+		samplerCI.minFilter = VK_FILTER_LINEAR;
+		samplerCI.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+		samplerCI.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+		samplerCI.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+		samplerCI.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+		samplerCI.minLod = 0.0f;
+		samplerCI.maxLod = static_cast<float>(numMips);
+		samplerCI.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
+		VK_CHECK_RESULT(vkCreateSampler(device, &samplerCI, nullptr, &textures.prefilteredCube.sampler));
+
+		textures.prefilteredCube.descriptor.imageView = textures.prefilteredCube.view;
+		textures.prefilteredCube.descriptor.sampler = textures.prefilteredCube.sampler;
+		textures.prefilteredCube.descriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+		textures.prefilteredCube.device = vulkanDevice;
+
+		// FB, Att, RP, Pipe, etc.
+		VkAttachmentDescription attDesc = {};
+		// Color attachment
+		attDesc.format = format;
+		attDesc.samples = VK_SAMPLE_COUNT_1_BIT;
+		attDesc.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+		attDesc.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+		attDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+		attDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+		attDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+		attDesc.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+		VkAttachmentReference colorReference = { 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
+
+		VkSubpassDescription subpassDescription = {};
+		subpassDescription.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+		subpassDescription.colorAttachmentCount = 1;
+		subpassDescription.pColorAttachments = &colorReference;
+
+		// Use subpass dependencies for layout transitions
+		std::array<VkSubpassDependency, 2> dependencies;
+		dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
+		dependencies[0].dstSubpass = 0;
+		dependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+		dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+		dependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
+		dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+		dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+		dependencies[1].srcSubpass = 0;
+		dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
+		dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+		dependencies[1].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+		dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+		dependencies[1].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
+		dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+		// Renderpass
+		VkRenderPassCreateInfo renderPassCI = vks::initializers::renderPassCreateInfo();
+		renderPassCI.attachmentCount = 1;
+		renderPassCI.pAttachments = &attDesc;
+		renderPassCI.subpassCount = 1;
+		renderPassCI.pSubpasses = &subpassDescription;
+		renderPassCI.dependencyCount = 2;
+		renderPassCI.pDependencies = dependencies.data();
+		VkRenderPass renderpass;
+		VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassCI, nullptr, &renderpass));
+
+		struct {
+			VkImage image;
+			VkImageView view;
+			VkDeviceMemory memory;
+			VkFramebuffer framebuffer;
+		} offscreen;
+
+		// Offfscreen framebuffer
+		{
+			// Color attachment
+			VkImageCreateInfo imageCreateInfo = vks::initializers::imageCreateInfo();
+			imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
+			imageCreateInfo.format = format;
+			imageCreateInfo.extent.width = dim;
+			imageCreateInfo.extent.height = dim;
+			imageCreateInfo.extent.depth = 1;
+			imageCreateInfo.mipLevels = 1;
+			imageCreateInfo.arrayLayers = 1;
+			imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
+			imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
+			imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+			imageCreateInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
+			imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+			VK_CHECK_RESULT(vkCreateImage(device, &imageCreateInfo, nullptr, &offscreen.image));
+
+			VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo();
+			VkMemoryRequirements memReqs;
+			vkGetImageMemoryRequirements(device, offscreen.image, &memReqs);
+			memAlloc.allocationSize = memReqs.size;
+			memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+			VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &offscreen.memory));
+			VK_CHECK_RESULT(vkBindImageMemory(device, offscreen.image, offscreen.memory, 0));
+
+			VkImageViewCreateInfo colorImageView = vks::initializers::imageViewCreateInfo();
+			colorImageView.viewType = VK_IMAGE_VIEW_TYPE_2D;
+			colorImageView.format = format;
+			colorImageView.flags = 0;
+			colorImageView.subresourceRange = {};
+			colorImageView.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+			colorImageView.subresourceRange.baseMipLevel = 0;
+			colorImageView.subresourceRange.levelCount = 1;
+			colorImageView.subresourceRange.baseArrayLayer = 0;
+			colorImageView.subresourceRange.layerCount = 1;
+			colorImageView.image = offscreen.image;
+			VK_CHECK_RESULT(vkCreateImageView(device, &colorImageView, nullptr, &offscreen.view));
+
+			VkFramebufferCreateInfo fbufCreateInfo = vks::initializers::framebufferCreateInfo();
+			fbufCreateInfo.renderPass = renderpass;
+			fbufCreateInfo.attachmentCount = 1;
+			fbufCreateInfo.pAttachments = &offscreen.view;
+			fbufCreateInfo.width = dim;
+			fbufCreateInfo.height = dim;
+			fbufCreateInfo.layers = 1;
+			VK_CHECK_RESULT(vkCreateFramebuffer(device, &fbufCreateInfo, nullptr, &offscreen.framebuffer));
+
+			VkCommandBuffer layoutCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+			vks::tools::setImageLayout(
+				layoutCmd,
+				offscreen.image,
+				VK_IMAGE_ASPECT_COLOR_BIT,
+				VK_IMAGE_LAYOUT_UNDEFINED,
+				VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
+			vulkanDevice->flushCommandBuffer(layoutCmd, queue, true);
+		}
+
+		// Descriptors
+		VkDescriptorSetLayout descriptorsetlayout;
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0),
+		};
+		VkDescriptorSetLayoutCreateInfo descriptorsetlayoutCI = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorsetlayoutCI, nullptr, &descriptorsetlayout));
+
+		// Descriptor Pool
+		std::vector<VkDescriptorPoolSize> poolSizes = { vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1) };
+		VkDescriptorPoolCreateInfo descriptorPoolCI = vks::initializers::descriptorPoolCreateInfo(poolSizes, 2);
+		VkDescriptorPool descriptorpool;
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolCI, nullptr, &descriptorpool));
+
+		// Descriptor sets
+		VkDescriptorSet descriptorset;
+		VkDescriptorSetAllocateInfo allocInfo =	vks::initializers::descriptorSetAllocateInfo(descriptorpool, &descriptorsetlayout, 1);
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorset));
+		VkWriteDescriptorSet writeDescriptorSet = vks::initializers::writeDescriptorSet(descriptorset, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &textures.environmentCube.descriptor);
+		vkUpdateDescriptorSets(device, 1, &writeDescriptorSet, 0, nullptr);
+
+		// Pipeline layout
+		struct PushBlock {
+			glm::mat4 mvp;
+			float roughness;
+			uint32_t numSamples = 32u;
+		} pushBlock;
+
+		VkPipelineLayout pipelinelayout;
+		std::vector<VkPushConstantRange> pushConstantRanges = {
+			vks::initializers::pushConstantRange(VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(PushBlock), 0),
+		};
+		VkPipelineLayoutCreateInfo pipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(&descriptorsetlayout, 1);
+		pipelineLayoutCI.pushConstantRangeCount = 1;
+		pipelineLayoutCI.pPushConstantRanges = pushConstantRanges.data();
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelinelayout));
+
+		// Pipeline
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE, VK_FRONT_FACE_COUNTER_CLOCKWISE);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_FALSE, VK_FALSE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1);
+		VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT);
+		std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
+		VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelinelayout, renderpass);
+		pipelineCI.pInputAssemblyState = &inputAssemblyState;
+		pipelineCI.pRasterizationState = &rasterizationState;
+		pipelineCI.pColorBlendState = &colorBlendState;
+		pipelineCI.pMultisampleState = &multisampleState;
+		pipelineCI.pViewportState = &viewportState;
+		pipelineCI.pDepthStencilState = &depthStencilState;
+		pipelineCI.pDynamicState = &dynamicState;
+		pipelineCI.stageCount = 2;
+		pipelineCI.pStages = shaderStages.data();
+		pipelineCI.renderPass = renderpass;
+		pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::UV });
+
+		shaderStages[0] = loadShader(getShadersPath() + "pbribl/filtercube.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "pbribl/prefilterenvmap.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		VkPipeline pipeline;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipeline));
+
+		// Render
+
+		VkClearValue clearValues[1];
+		clearValues[0].color = { { 0.0f, 0.0f, 0.2f, 0.0f } };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		// Reuse render pass from example pass
+		renderPassBeginInfo.renderPass = renderpass;
+		renderPassBeginInfo.framebuffer = offscreen.framebuffer;
+		renderPassBeginInfo.renderArea.extent.width = dim;
+		renderPassBeginInfo.renderArea.extent.height = dim;
+		renderPassBeginInfo.clearValueCount = 1;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		std::vector<glm::mat4> matrices = {
+			// POSITIVE_X
+			glm::rotate(glm::rotate(glm::mat4(1.0f), glm::radians(90.0f), glm::vec3(0.0f, 1.0f, 0.0f)), glm::radians(180.0f), glm::vec3(1.0f, 0.0f, 0.0f)),
+			// NEGATIVE_X
+			glm::rotate(glm::rotate(glm::mat4(1.0f), glm::radians(-90.0f), glm::vec3(0.0f, 1.0f, 0.0f)), glm::radians(180.0f), glm::vec3(1.0f, 0.0f, 0.0f)),
+			// POSITIVE_Y
+			glm::rotate(glm::mat4(1.0f), glm::radians(-90.0f), glm::vec3(1.0f, 0.0f, 0.0f)),
+			// NEGATIVE_Y
+			glm::rotate(glm::mat4(1.0f), glm::radians(90.0f), glm::vec3(1.0f, 0.0f, 0.0f)),
+			// POSITIVE_Z
+			glm::rotate(glm::mat4(1.0f), glm::radians(180.0f), glm::vec3(1.0f, 0.0f, 0.0f)),
+			// NEGATIVE_Z
+			glm::rotate(glm::mat4(1.0f), glm::radians(180.0f), glm::vec3(0.0f, 0.0f, 1.0f)),
+		};
+
+		VkCommandBuffer cmdBuf = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+
+		VkViewport viewport = vks::initializers::viewport((float)dim, (float)dim, 0.0f, 1.0f);
+		VkRect2D scissor = vks::initializers::rect2D(dim, dim, 0, 0);
+
+		vkCmdSetViewport(cmdBuf, 0, 1, &viewport);
+		vkCmdSetScissor(cmdBuf, 0, 1, &scissor);
+
+		VkImageSubresourceRange subresourceRange = {};
+		subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+		subresourceRange.baseMipLevel = 0;
+		subresourceRange.levelCount = numMips;
+		subresourceRange.layerCount = 6;
+
+		// Change image layout for all cubemap faces to transfer destination
+		vks::tools::setImageLayout(
+			cmdBuf,
+			textures.prefilteredCube.image,
+			VK_IMAGE_LAYOUT_UNDEFINED,
+			VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+			subresourceRange);
+
+		for (uint32_t m = 0; m < numMips; m++) {
+			pushBlock.roughness = (float)m / (float)(numMips - 1);
+			for (uint32_t f = 0; f < 6; f++) {
+				viewport.width = static_cast<float>(dim * std::pow(0.5f, m));
+				viewport.height = static_cast<float>(dim * std::pow(0.5f, m));
+				vkCmdSetViewport(cmdBuf, 0, 1, &viewport);
+
+				// Render scene from cube face's point of view
+				vkCmdBeginRenderPass(cmdBuf, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+				// Update shader push constant block
+				pushBlock.mvp = glm::perspective((float)(M_PI / 2.0), 1.0f, 0.1f, 512.0f) * matrices[f];
+
+				vkCmdPushConstants(cmdBuf, pipelinelayout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushBlock), &pushBlock);
+
+				vkCmdBindPipeline(cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
+				vkCmdBindDescriptorSets(cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelinelayout, 0, 1, &descriptorset, 0, NULL);
+
+				models.skybox.draw(cmdBuf);
+
+				vkCmdEndRenderPass(cmdBuf);
+
+				vks::tools::setImageLayout(
+					cmdBuf,
+					offscreen.image,
+					VK_IMAGE_ASPECT_COLOR_BIT,
+					VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+					VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
+
+				// Copy region for transfer from framebuffer to cube face
+				VkImageCopy copyRegion = {};
+
+				copyRegion.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+				copyRegion.srcSubresource.baseArrayLayer = 0;
+				copyRegion.srcSubresource.mipLevel = 0;
+				copyRegion.srcSubresource.layerCount = 1;
+				copyRegion.srcOffset = { 0, 0, 0 };
+
+				copyRegion.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+				copyRegion.dstSubresource.baseArrayLayer = f;
+				copyRegion.dstSubresource.mipLevel = m;
+				copyRegion.dstSubresource.layerCount = 1;
+				copyRegion.dstOffset = { 0, 0, 0 };
+
+				copyRegion.extent.width = static_cast<uint32_t>(viewport.width);
+				copyRegion.extent.height = static_cast<uint32_t>(viewport.height);
+				copyRegion.extent.depth = 1;
+
+				vkCmdCopyImage(
+					cmdBuf,
+					offscreen.image,
+					VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+					textures.prefilteredCube.image,
+					VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+					1,
+					&copyRegion);
+
+				// Transform framebuffer color attachment back
+				vks::tools::setImageLayout(
+					cmdBuf,
+					offscreen.image,
+					VK_IMAGE_ASPECT_COLOR_BIT,
+					VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+					VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
+			}
+		}
+
+		vks::tools::setImageLayout(
+			cmdBuf,
+			textures.prefilteredCube.image,
+			VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+			VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
+			subresourceRange);
+
+		vulkanDevice->flushCommandBuffer(cmdBuf, queue);
+
+		vkDestroyRenderPass(device, renderpass, nullptr);
+		vkDestroyFramebuffer(device, offscreen.framebuffer, nullptr);
+		vkFreeMemory(device, offscreen.memory, nullptr);
+		vkDestroyImageView(device, offscreen.view, nullptr);
+		vkDestroyImage(device, offscreen.image, nullptr);
+		vkDestroyDescriptorPool(device, descriptorpool, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorsetlayout, nullptr);
+		vkDestroyPipeline(device, pipeline, nullptr);
+		vkDestroyPipelineLayout(device, pipelinelayout, nullptr);
+
+		auto tEnd = std::chrono::high_resolution_clock::now();
+		auto tDiff = std::chrono::duration<double, std::milli>(tEnd - tStart).count();
+		std::cout << "Generating pre-filtered enivornment cube with " << numMips << " mip levels took " << tDiff << " ms" << std::endl;
+	}
+
+	// Prepare and initialize uniform buffer containing shader uniforms
+	void prepareUniformBuffers()
+	{
+		// Object vertex shader uniform buffer
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.object,
+			sizeof(uboMatrices)));
+
+		// Skybox vertex shader uniform buffer
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.skybox,
+			sizeof(uboMatrices)));
+
+		// Shared parameter uniform buffer
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.params,
+			sizeof(uboParams)));
+
+		// Map persistent
+		VK_CHECK_RESULT(uniformBuffers.object.map());
+		VK_CHECK_RESULT(uniformBuffers.skybox.map());
+		VK_CHECK_RESULT(uniformBuffers.params.map());
+
+		updateUniformBuffers();
+		updateParams();
+	}
+
+	void updateUniformBuffers()
+	{
+		// 3D object
+		uboMatrices.projection = camera.matrices.perspective;
+		uboMatrices.view = camera.matrices.view;
+		uboMatrices.model = glm::rotate(glm::mat4(1.0f), glm::radians(90.0f + (models.objectIndex == 1 ? 45.0f : 0.0f)), glm::vec3(0.0f, 1.0f, 0.0f));
+		uboMatrices.camPos = camera.position * -1.0f;
+		memcpy(uniformBuffers.object.mapped, &uboMatrices, sizeof(uboMatrices));
+
+		// Skybox
+		uboMatrices.model = glm::mat4(glm::mat3(camera.matrices.view));
+		memcpy(uniformBuffers.skybox.mapped, &uboMatrices, sizeof(uboMatrices));
+	}
+
+	void updateParams()
+	{
+		const float p = 15.0f;
+		uboParams.lights[0] = glm::vec4(-p, -p*0.5f, -p, 1.0f);
+		uboParams.lights[1] = glm::vec4(-p, -p*0.5f,  p, 1.0f);
+		uboParams.lights[2] = glm::vec4( p, -p*0.5f,  p, 1.0f);
+		uboParams.lights[3] = glm::vec4( p, -p*0.5f, -p, 1.0f);
+
+		memcpy(uniformBuffers.params.mapped, &uboParams, sizeof(uboParams));
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+
+		VulkanExampleBase::submitFrame();
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		loadAssets();
+		generateBRDFLUT();
+		generateIrradianceCube();
+		generatePrefilteredCube();
+		prepareUniformBuffers();
+		setupDescriptors();
+		preparePipelines();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+	}
+
+	virtual void viewChanged()
+	{
+		updateUniformBuffers();
+	}
+
+	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
+	{
+		if (overlay->header("Settings")) {
+			if (overlay->comboBox("Material", &materialIndex, materialNames)) {
+				buildCommandBuffers();
+			}
+			if (overlay->comboBox("Object type", &models.objectIndex, objectNames)) {
+				updateUniformBuffers();
+				buildCommandBuffers();
+			}
+			if (overlay->inputFloat("Exposure", &uboParams.exposure, 0.1f, 2)) {
+				updateParams();
+			}
+			if (overlay->inputFloat("Gamma", &uboParams.gamma, 0.1f, 2)) {
+				updateParams();
+			}
+			if (overlay->checkBox("Skybox", &displaySkybox)) {
+				buildCommandBuffers();
+			}
+		}
+	}
+
+};
+
+VULKAN_EXAMPLE_MAIN()
\ No newline at end of file
diff --git a/external/Vulkan/examples/pbrtexture/pbrtexture.cpp b/external/Vulkan/examples/pbrtexture/pbrtexture.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5be56716327fcbc23302d14dbade531305b4760f
--- /dev/null
+++ b/external/Vulkan/examples/pbrtexture/pbrtexture.cpp
@@ -0,0 +1,1359 @@
+/*
+* Vulkan Example - Physical based rendering a textured object (metal/roughness workflow) with image based lighting
+*
+* Note: Requires the separate asset pack (see data/README.md)
+*
+* Copyright (C) 2016-2017 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+// For reference see http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf
+
+#include "vulkanexamplebase.h"
+#include "VulkanglTFModel.h"
+
+#define ENABLE_VALIDATION false
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	bool displaySkybox = true;
+
+	struct Textures {
+		vks::TextureCubeMap environmentCube;
+		// Generated at runtime
+		vks::Texture2D lutBrdf;
+		vks::TextureCubeMap irradianceCube;
+		vks::TextureCubeMap prefilteredCube;
+		// Object texture maps
+		vks::Texture2D albedoMap;
+		vks::Texture2D normalMap;
+		vks::Texture2D aoMap;
+		vks::Texture2D metallicMap;
+		vks::Texture2D roughnessMap;
+	} textures;
+
+	struct Meshes {
+		vkglTF::Model skybox;
+		vkglTF::Model object;
+	} models;
+
+	struct {
+		vks::Buffer object;
+		vks::Buffer skybox;
+		vks::Buffer params;
+	} uniformBuffers;
+
+	struct UBOMatrices {
+		glm::mat4 projection;
+		glm::mat4 model;
+		glm::mat4 view;
+		glm::vec3 camPos;
+	} uboMatrices;
+
+	struct UBOParams {
+		glm::vec4 lights[4];
+		float exposure = 4.5f;
+		float gamma = 2.2f;
+	} uboParams;
+
+	struct {
+		VkPipeline skybox;
+		VkPipeline pbr;
+	} pipelines;
+
+	struct {
+		VkDescriptorSet object;
+		VkDescriptorSet skybox;
+	} descriptorSets;
+
+	VkPipelineLayout pipelineLayout;
+	VkDescriptorSetLayout descriptorSetLayout;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Textured PBR with IBL";
+
+		camera.type = Camera::CameraType::firstperson;
+		camera.movementSpeed = 4.0f;
+		camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 256.0f);
+		camera.rotationSpeed = 0.25f;
+
+		camera.setRotation({ -7.75f, 150.25f, 0.0f });
+		camera.setPosition({ 0.7f, 0.1f, 1.7f });
+	}
+
+	~VulkanExample()
+	{
+		vkDestroyPipeline(device, pipelines.skybox, nullptr);
+		vkDestroyPipeline(device, pipelines.pbr, nullptr);
+
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+
+		uniformBuffers.object.destroy();
+		uniformBuffers.skybox.destroy();
+		uniformBuffers.params.destroy();
+
+		textures.environmentCube.destroy();
+		textures.irradianceCube.destroy();
+		textures.prefilteredCube.destroy();
+		textures.lutBrdf.destroy();
+		textures.albedoMap.destroy();
+		textures.normalMap.destroy();
+		textures.aoMap.destroy();
+		textures.metallicMap.destroy();
+		textures.roughnessMap.destroy();
+	}
+
+	virtual void getEnabledFeatures()
+	{
+		if (deviceFeatures.samplerAnisotropy) {
+			enabledFeatures.samplerAnisotropy = VK_TRUE;
+		}
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		clearValues[0].color = { { 0.1f, 0.1f, 0.1f, 1.0f } };
+		clearValues[1].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.offset.x = 0;
+		renderPassBeginInfo.renderArea.offset.y = 0;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		renderPassBeginInfo.clearValueCount = 2;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		for (size_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			// Set target frame buffer
+			renderPassBeginInfo.framebuffer = frameBuffers[i];
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			VkViewport viewport = vks::initializers::viewport((float)width,	(float)height, 0.0f, 1.0f);
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+			VkRect2D scissor = vks::initializers::rect2D(width,	height,	0, 0);
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+			VkDeviceSize offsets[1] = { 0 };
+
+			// Skybox
+			if (displaySkybox)
+			{
+				vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets.skybox, 0, NULL);
+				vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.skybox);
+				models.skybox.draw(drawCmdBuffers[i]);
+			}
+
+			// Objects
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets.object, 0, NULL);
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.pbr);
+			models.object.draw(drawCmdBuffers[i]);
+
+			drawUI(drawCmdBuffers[i]);
+
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void loadAssets()
+	{
+		const uint32_t glTFLoadingFlags = vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::PreMultiplyVertexColors | vkglTF::FileLoadingFlags::FlipY;
+		models.skybox.loadFromFile(getAssetPath() + "models/cube.gltf", vulkanDevice, queue, glTFLoadingFlags);
+		models.object.loadFromFile(getAssetPath() + "models/cerberus/cerberus.gltf", vulkanDevice, queue, glTFLoadingFlags);
+		textures.environmentCube.loadFromFile(getAssetPath() + "textures/hdr/gcanyon_cube.ktx", VK_FORMAT_R16G16B16A16_SFLOAT, vulkanDevice, queue);
+		textures.albedoMap.loadFromFile(getAssetPath() + "models/cerberus/albedo.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue);
+		textures.normalMap.loadFromFile(getAssetPath() + "models/cerberus/normal.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue);
+		textures.aoMap.loadFromFile(getAssetPath() + "models/cerberus/ao.ktx", VK_FORMAT_R8_UNORM, vulkanDevice, queue);
+		textures.metallicMap.loadFromFile(getAssetPath() + "models/cerberus/metallic.ktx", VK_FORMAT_R8_UNORM, vulkanDevice, queue);
+		textures.roughnessMap.loadFromFile(getAssetPath() + "models/cerberus/roughness.ktx", VK_FORMAT_R8_UNORM, vulkanDevice, queue);
+	}
+
+	void setupDescriptors()
+	{
+		// Descriptor Pool
+		std::vector<VkDescriptorPoolSize> poolSizes = {
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 4),
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 16)
+		};
+		VkDescriptorPoolCreateInfo descriptorPoolInfo =	vks::initializers::descriptorPoolCreateInfo(poolSizes, 2);
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+
+		// Descriptor set layout
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0),
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_FRAGMENT_BIT, 1),
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 2),
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 3),
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 4),
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 5),
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 6),
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 7),
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 8),
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 9),
+		};
+		VkDescriptorSetLayoutCreateInfo descriptorLayout = 	vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout));
+
+		// Descriptor sets
+		VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1);
+
+		// Objects
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.object));
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets = {
+			vks::initializers::writeDescriptorSet(descriptorSets.object, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.object.descriptor),
+			vks::initializers::writeDescriptorSet(descriptorSets.object, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, &uniformBuffers.params.descriptor),
+			vks::initializers::writeDescriptorSet(descriptorSets.object, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2, &textures.irradianceCube.descriptor),
+			vks::initializers::writeDescriptorSet(descriptorSets.object, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 3, &textures.lutBrdf.descriptor),
+			vks::initializers::writeDescriptorSet(descriptorSets.object, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 4, &textures.prefilteredCube.descriptor),
+			vks::initializers::writeDescriptorSet(descriptorSets.object, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 5, &textures.albedoMap.descriptor),
+			vks::initializers::writeDescriptorSet(descriptorSets.object, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 6, &textures.normalMap.descriptor),
+			vks::initializers::writeDescriptorSet(descriptorSets.object, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 7, &textures.aoMap.descriptor),
+			vks::initializers::writeDescriptorSet(descriptorSets.object, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 8, &textures.metallicMap.descriptor),
+			vks::initializers::writeDescriptorSet(descriptorSets.object, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 9, &textures.roughnessMap.descriptor),
+		};
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL);
+
+		// Sky box
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.skybox));
+		writeDescriptorSets = {
+			vks::initializers::writeDescriptorSet(descriptorSets.skybox, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.skybox.descriptor),
+			vks::initializers::writeDescriptorSet(descriptorSets.skybox, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, &uniformBuffers.params.descriptor),
+			vks::initializers::writeDescriptorSet(descriptorSets.skybox, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2, &textures.environmentCube.descriptor),
+		};
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL);
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_FALSE, VK_FALSE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1);
+		VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT);
+		std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
+		VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+
+		// Pipeline layout
+		VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1);
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout));
+
+		// Pipelines
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass);
+		pipelineCI.pInputAssemblyState = &inputAssemblyState;
+		pipelineCI.pRasterizationState = &rasterizationState;
+		pipelineCI.pColorBlendState = &colorBlendState;
+		pipelineCI.pMultisampleState = &multisampleState;
+		pipelineCI.pViewportState = &viewportState;
+		pipelineCI.pDepthStencilState = &depthStencilState;
+		pipelineCI.pDynamicState = &dynamicState;
+		pipelineCI.stageCount = static_cast<uint32_t>(shaderStages.size());
+		pipelineCI.pStages = shaderStages.data();
+		pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::UV, vkglTF::VertexComponent::Tangent });
+
+		// Skybox pipeline (background cube)
+		rasterizationState.cullMode = VK_CULL_MODE_FRONT_BIT;
+		shaderStages[0] = loadShader(getShadersPath() + "pbrtexture/skybox.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "pbrtexture/skybox.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.skybox));
+
+		// PBR pipeline
+		rasterizationState.cullMode = VK_CULL_MODE_BACK_BIT;
+		shaderStages[0] = loadShader(getShadersPath() + "pbrtexture/pbrtexture.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "pbrtexture/pbrtexture.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		// Enable depth test and write
+		depthStencilState.depthWriteEnable = VK_TRUE;
+		depthStencilState.depthTestEnable = VK_TRUE;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.pbr));
+	}
+
+	// Generate a BRDF integration map used as a look-up-table (stores roughness / NdotV)
+	void generateBRDFLUT()
+	{
+		auto tStart = std::chrono::high_resolution_clock::now();
+
+		const VkFormat format = VK_FORMAT_R16G16_SFLOAT;	// R16G16 is supported pretty much everywhere
+		const int32_t dim = 512;
+
+		// Image
+		VkImageCreateInfo imageCI = vks::initializers::imageCreateInfo();
+		imageCI.imageType = VK_IMAGE_TYPE_2D;
+		imageCI.format = format;
+		imageCI.extent.width = dim;
+		imageCI.extent.height = dim;
+		imageCI.extent.depth = 1;
+		imageCI.mipLevels = 1;
+		imageCI.arrayLayers = 1;
+		imageCI.samples = VK_SAMPLE_COUNT_1_BIT;
+		imageCI.tiling = VK_IMAGE_TILING_OPTIMAL;
+		imageCI.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
+		VK_CHECK_RESULT(vkCreateImage(device, &imageCI, nullptr, &textures.lutBrdf.image));
+		VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo();
+		VkMemoryRequirements memReqs;
+		vkGetImageMemoryRequirements(device, textures.lutBrdf.image, &memReqs);
+		memAlloc.allocationSize = memReqs.size;
+		memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+		VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &textures.lutBrdf.deviceMemory));
+		VK_CHECK_RESULT(vkBindImageMemory(device, textures.lutBrdf.image, textures.lutBrdf.deviceMemory, 0));
+		// Image view
+		VkImageViewCreateInfo viewCI = vks::initializers::imageViewCreateInfo();
+		viewCI.viewType = VK_IMAGE_VIEW_TYPE_2D;
+		viewCI.format = format;
+		viewCI.subresourceRange = {};
+		viewCI.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+		viewCI.subresourceRange.levelCount = 1;
+		viewCI.subresourceRange.layerCount = 1;
+		viewCI.image = textures.lutBrdf.image;
+		VK_CHECK_RESULT(vkCreateImageView(device, &viewCI, nullptr, &textures.lutBrdf.view));
+		// Sampler
+		VkSamplerCreateInfo samplerCI = vks::initializers::samplerCreateInfo();
+		samplerCI.magFilter = VK_FILTER_LINEAR;
+		samplerCI.minFilter = VK_FILTER_LINEAR;
+		samplerCI.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+		samplerCI.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+		samplerCI.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+		samplerCI.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+		samplerCI.minLod = 0.0f;
+		samplerCI.maxLod = 1.0f;
+		samplerCI.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
+		VK_CHECK_RESULT(vkCreateSampler(device, &samplerCI, nullptr, &textures.lutBrdf.sampler));
+
+		textures.lutBrdf.descriptor.imageView = textures.lutBrdf.view;
+		textures.lutBrdf.descriptor.sampler = textures.lutBrdf.sampler;
+		textures.lutBrdf.descriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+		textures.lutBrdf.device = vulkanDevice;
+
+		// FB, Att, RP, Pipe, etc.
+		VkAttachmentDescription attDesc = {};
+		// Color attachment
+		attDesc.format = format;
+		attDesc.samples = VK_SAMPLE_COUNT_1_BIT;
+		attDesc.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+		attDesc.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+		attDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+		attDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+		attDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+		attDesc.finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+		VkAttachmentReference colorReference = { 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
+
+		VkSubpassDescription subpassDescription = {};
+		subpassDescription.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+		subpassDescription.colorAttachmentCount = 1;
+		subpassDescription.pColorAttachments = &colorReference;
+
+		// Use subpass dependencies for layout transitions
+		std::array<VkSubpassDependency, 2> dependencies;
+		dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
+		dependencies[0].dstSubpass = 0;
+		dependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+		dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+		dependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
+		dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+		dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+		dependencies[1].srcSubpass = 0;
+		dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
+		dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+		dependencies[1].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+		dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+		dependencies[1].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
+		dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+		// Create the actual renderpass
+		VkRenderPassCreateInfo renderPassCI = vks::initializers::renderPassCreateInfo();
+		renderPassCI.attachmentCount = 1;
+		renderPassCI.pAttachments = &attDesc;
+		renderPassCI.subpassCount = 1;
+		renderPassCI.pSubpasses = &subpassDescription;
+		renderPassCI.dependencyCount = 2;
+		renderPassCI.pDependencies = dependencies.data();
+
+		VkRenderPass renderpass;
+		VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassCI, nullptr, &renderpass));
+
+		VkFramebufferCreateInfo framebufferCI = vks::initializers::framebufferCreateInfo();
+		framebufferCI.renderPass = renderpass;
+		framebufferCI.attachmentCount = 1;
+		framebufferCI.pAttachments = &textures.lutBrdf.view;
+		framebufferCI.width = dim;
+		framebufferCI.height = dim;
+		framebufferCI.layers = 1;
+
+		VkFramebuffer framebuffer;
+		VK_CHECK_RESULT(vkCreateFramebuffer(device, &framebufferCI, nullptr, &framebuffer));
+
+		// Descriptors
+		VkDescriptorSetLayout descriptorsetlayout;
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {};
+		VkDescriptorSetLayoutCreateInfo descriptorsetlayoutCI = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorsetlayoutCI, nullptr, &descriptorsetlayout));
+
+		// Descriptor Pool
+		std::vector<VkDescriptorPoolSize> poolSizes = { vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1) };
+		VkDescriptorPoolCreateInfo descriptorPoolCI = vks::initializers::descriptorPoolCreateInfo(poolSizes, 2);
+		VkDescriptorPool descriptorpool;
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolCI, nullptr, &descriptorpool));
+
+		// Descriptor sets
+		VkDescriptorSet descriptorset;
+		VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorpool, &descriptorsetlayout, 1);
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorset));
+
+		// Pipeline layout
+		VkPipelineLayout pipelinelayout;
+		VkPipelineLayoutCreateInfo pipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(&descriptorsetlayout, 1);
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelinelayout));
+
+		// Pipeline
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE, VK_FRONT_FACE_COUNTER_CLOCKWISE);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_FALSE, VK_FALSE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1);
+		VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT);
+		std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
+		VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
+		VkPipelineVertexInputStateCreateInfo emptyInputState = vks::initializers::pipelineVertexInputStateCreateInfo();
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelinelayout, renderpass);
+		pipelineCI.pInputAssemblyState = &inputAssemblyState;
+		pipelineCI.pRasterizationState = &rasterizationState;
+		pipelineCI.pColorBlendState = &colorBlendState;
+		pipelineCI.pMultisampleState = &multisampleState;
+		pipelineCI.pViewportState = &viewportState;
+		pipelineCI.pDepthStencilState = &depthStencilState;
+		pipelineCI.pDynamicState = &dynamicState;
+		pipelineCI.stageCount = 2;
+		pipelineCI.pStages = shaderStages.data();
+		pipelineCI.pVertexInputState = &emptyInputState;
+
+		// Look-up-table (from BRDF) pipeline
+		shaderStages[0] = loadShader(getShadersPath() + "pbrtexture/genbrdflut.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "pbrtexture/genbrdflut.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		VkPipeline pipeline;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipeline));
+
+		// Render
+		VkClearValue clearValues[1];
+		clearValues[0].color = { { 0.0f, 0.0f, 0.0f, 1.0f } };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderpass;
+		renderPassBeginInfo.renderArea.extent.width = dim;
+		renderPassBeginInfo.renderArea.extent.height = dim;
+		renderPassBeginInfo.clearValueCount = 1;
+		renderPassBeginInfo.pClearValues = clearValues;
+		renderPassBeginInfo.framebuffer = framebuffer;
+
+		VkCommandBuffer cmdBuf = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+		vkCmdBeginRenderPass(cmdBuf, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+		VkViewport viewport = vks::initializers::viewport((float)dim, (float)dim, 0.0f, 1.0f);
+		VkRect2D scissor = vks::initializers::rect2D(dim, dim, 0, 0);
+		vkCmdSetViewport(cmdBuf, 0, 1, &viewport);
+		vkCmdSetScissor(cmdBuf, 0, 1, &scissor);
+		vkCmdBindPipeline(cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
+		vkCmdDraw(cmdBuf, 3, 1, 0, 0);
+		vkCmdEndRenderPass(cmdBuf);
+		vulkanDevice->flushCommandBuffer(cmdBuf, queue);
+
+		vkQueueWaitIdle(queue);
+
+		vkDestroyPipeline(device, pipeline, nullptr);
+		vkDestroyPipelineLayout(device, pipelinelayout, nullptr);
+		vkDestroyRenderPass(device, renderpass, nullptr);
+		vkDestroyFramebuffer(device, framebuffer, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorsetlayout, nullptr);
+		vkDestroyDescriptorPool(device, descriptorpool, nullptr);
+
+		auto tEnd = std::chrono::high_resolution_clock::now();
+		auto tDiff = std::chrono::duration<double, std::milli>(tEnd - tStart).count();
+		std::cout << "Generating BRDF LUT took " << tDiff << " ms" << std::endl;
+	}
+
+	// Generate an irradiance cube map from the environment cube map
+	void generateIrradianceCube()
+	{
+		auto tStart = std::chrono::high_resolution_clock::now();
+
+		const VkFormat format = VK_FORMAT_R32G32B32A32_SFLOAT;
+		const int32_t dim = 64;
+		const uint32_t numMips = static_cast<uint32_t>(floor(log2(dim))) + 1;
+
+		// Pre-filtered cube map
+		// Image
+		VkImageCreateInfo imageCI = vks::initializers::imageCreateInfo();
+		imageCI.imageType = VK_IMAGE_TYPE_2D;
+		imageCI.format = format;
+		imageCI.extent.width = dim;
+		imageCI.extent.height = dim;
+		imageCI.extent.depth = 1;
+		imageCI.mipLevels = numMips;
+		imageCI.arrayLayers = 6;
+		imageCI.samples = VK_SAMPLE_COUNT_1_BIT;
+		imageCI.tiling = VK_IMAGE_TILING_OPTIMAL;
+		imageCI.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
+		imageCI.flags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;
+		VK_CHECK_RESULT(vkCreateImage(device, &imageCI, nullptr, &textures.irradianceCube.image));
+		VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo();
+		VkMemoryRequirements memReqs;
+		vkGetImageMemoryRequirements(device, textures.irradianceCube.image, &memReqs);
+		memAlloc.allocationSize = memReqs.size;
+		memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+		VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &textures.irradianceCube.deviceMemory));
+		VK_CHECK_RESULT(vkBindImageMemory(device, textures.irradianceCube.image, textures.irradianceCube.deviceMemory, 0));
+		// Image view
+		VkImageViewCreateInfo viewCI = vks::initializers::imageViewCreateInfo();
+		viewCI.viewType = VK_IMAGE_VIEW_TYPE_CUBE;
+		viewCI.format = format;
+		viewCI.subresourceRange = {};
+		viewCI.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+		viewCI.subresourceRange.levelCount = numMips;
+		viewCI.subresourceRange.layerCount = 6;
+		viewCI.image = textures.irradianceCube.image;
+		VK_CHECK_RESULT(vkCreateImageView(device, &viewCI, nullptr, &textures.irradianceCube.view));
+		// Sampler
+		VkSamplerCreateInfo samplerCI = vks::initializers::samplerCreateInfo();
+		samplerCI.magFilter = VK_FILTER_LINEAR;
+		samplerCI.minFilter = VK_FILTER_LINEAR;
+		samplerCI.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+		samplerCI.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+		samplerCI.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+		samplerCI.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+		samplerCI.minLod = 0.0f;
+		samplerCI.maxLod = static_cast<float>(numMips);
+		samplerCI.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
+		VK_CHECK_RESULT(vkCreateSampler(device, &samplerCI, nullptr, &textures.irradianceCube.sampler));
+
+		textures.irradianceCube.descriptor.imageView = textures.irradianceCube.view;
+		textures.irradianceCube.descriptor.sampler = textures.irradianceCube.sampler;
+		textures.irradianceCube.descriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+		textures.irradianceCube.device = vulkanDevice;
+
+		// FB, Att, RP, Pipe, etc.
+		VkAttachmentDescription attDesc = {};
+		// Color attachment
+		attDesc.format = format;
+		attDesc.samples = VK_SAMPLE_COUNT_1_BIT;
+		attDesc.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+		attDesc.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+		attDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+		attDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+		attDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+		attDesc.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+		VkAttachmentReference colorReference = { 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
+
+		VkSubpassDescription subpassDescription = {};
+		subpassDescription.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+		subpassDescription.colorAttachmentCount = 1;
+		subpassDescription.pColorAttachments = &colorReference;
+
+		// Use subpass dependencies for layout transitions
+		std::array<VkSubpassDependency, 2> dependencies;
+		dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
+		dependencies[0].dstSubpass = 0;
+		dependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+		dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+		dependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
+		dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+		dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+		dependencies[1].srcSubpass = 0;
+		dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
+		dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+		dependencies[1].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+		dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+		dependencies[1].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
+		dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+		// Renderpass
+		VkRenderPassCreateInfo renderPassCI = vks::initializers::renderPassCreateInfo();
+		renderPassCI.attachmentCount = 1;
+		renderPassCI.pAttachments = &attDesc;
+		renderPassCI.subpassCount = 1;
+		renderPassCI.pSubpasses = &subpassDescription;
+		renderPassCI.dependencyCount = 2;
+		renderPassCI.pDependencies = dependencies.data();
+		VkRenderPass renderpass;
+		VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassCI, nullptr, &renderpass));
+
+		struct {
+			VkImage image;
+			VkImageView view;
+			VkDeviceMemory memory;
+			VkFramebuffer framebuffer;
+		} offscreen;
+
+		// Offfscreen framebuffer
+		{
+			// Color attachment
+			VkImageCreateInfo imageCreateInfo = vks::initializers::imageCreateInfo();
+			imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
+			imageCreateInfo.format = format;
+			imageCreateInfo.extent.width = dim;
+			imageCreateInfo.extent.height = dim;
+			imageCreateInfo.extent.depth = 1;
+			imageCreateInfo.mipLevels = 1;
+			imageCreateInfo.arrayLayers = 1;
+			imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
+			imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
+			imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+			imageCreateInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
+			imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+			VK_CHECK_RESULT(vkCreateImage(device, &imageCreateInfo, nullptr, &offscreen.image));
+
+			VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo();
+			VkMemoryRequirements memReqs;
+			vkGetImageMemoryRequirements(device, offscreen.image, &memReqs);
+			memAlloc.allocationSize = memReqs.size;
+			memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+			VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &offscreen.memory));
+			VK_CHECK_RESULT(vkBindImageMemory(device, offscreen.image, offscreen.memory, 0));
+
+			VkImageViewCreateInfo colorImageView = vks::initializers::imageViewCreateInfo();
+			colorImageView.viewType = VK_IMAGE_VIEW_TYPE_2D;
+			colorImageView.format = format;
+			colorImageView.flags = 0;
+			colorImageView.subresourceRange = {};
+			colorImageView.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+			colorImageView.subresourceRange.baseMipLevel = 0;
+			colorImageView.subresourceRange.levelCount = 1;
+			colorImageView.subresourceRange.baseArrayLayer = 0;
+			colorImageView.subresourceRange.layerCount = 1;
+			colorImageView.image = offscreen.image;
+			VK_CHECK_RESULT(vkCreateImageView(device, &colorImageView, nullptr, &offscreen.view));
+
+			VkFramebufferCreateInfo fbufCreateInfo = vks::initializers::framebufferCreateInfo();
+			fbufCreateInfo.renderPass = renderpass;
+			fbufCreateInfo.attachmentCount = 1;
+			fbufCreateInfo.pAttachments = &offscreen.view;
+			fbufCreateInfo.width = dim;
+			fbufCreateInfo.height = dim;
+			fbufCreateInfo.layers = 1;
+			VK_CHECK_RESULT(vkCreateFramebuffer(device, &fbufCreateInfo, nullptr, &offscreen.framebuffer));
+
+			VkCommandBuffer layoutCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+			vks::tools::setImageLayout(
+				layoutCmd,
+				offscreen.image,
+				VK_IMAGE_ASPECT_COLOR_BIT,
+				VK_IMAGE_LAYOUT_UNDEFINED,
+				VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
+			vulkanDevice->flushCommandBuffer(layoutCmd, queue, true);
+		}
+
+		// Descriptors
+		VkDescriptorSetLayout descriptorsetlayout;
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0),
+		};
+		VkDescriptorSetLayoutCreateInfo descriptorsetlayoutCI = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorsetlayoutCI, nullptr, &descriptorsetlayout));
+
+		// Descriptor Pool
+		std::vector<VkDescriptorPoolSize> poolSizes = { vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1) };
+		VkDescriptorPoolCreateInfo descriptorPoolCI = vks::initializers::descriptorPoolCreateInfo(poolSizes, 2);
+		VkDescriptorPool descriptorpool;
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolCI, nullptr, &descriptorpool));
+
+		// Descriptor sets
+		VkDescriptorSet descriptorset;
+		VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorpool, &descriptorsetlayout, 1);
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorset));
+		VkWriteDescriptorSet writeDescriptorSet = vks::initializers::writeDescriptorSet(descriptorset, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &textures.environmentCube.descriptor);
+		vkUpdateDescriptorSets(device, 1, &writeDescriptorSet, 0, nullptr);
+
+		// Pipeline layout
+		struct PushBlock {
+			glm::mat4 mvp;
+			// Sampling deltas
+			float deltaPhi = (2.0f * float(M_PI)) / 180.0f;
+			float deltaTheta = (0.5f * float(M_PI)) / 64.0f;
+		} pushBlock;
+
+		VkPipelineLayout pipelinelayout;
+		std::vector<VkPushConstantRange> pushConstantRanges = {
+			vks::initializers::pushConstantRange(VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(PushBlock), 0),
+		};
+		VkPipelineLayoutCreateInfo pipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(&descriptorsetlayout, 1);
+		pipelineLayoutCI.pushConstantRangeCount = 1;
+		pipelineLayoutCI.pPushConstantRanges = pushConstantRanges.data();
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelinelayout));
+
+		// Pipeline
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE, VK_FRONT_FACE_COUNTER_CLOCKWISE);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_FALSE, VK_FALSE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1);
+		VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT);
+		std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
+		VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelinelayout, renderpass);
+		pipelineCI.pInputAssemblyState = &inputAssemblyState;
+		pipelineCI.pRasterizationState = &rasterizationState;
+		pipelineCI.pColorBlendState = &colorBlendState;
+		pipelineCI.pMultisampleState = &multisampleState;
+		pipelineCI.pViewportState = &viewportState;
+		pipelineCI.pDepthStencilState = &depthStencilState;
+		pipelineCI.pDynamicState = &dynamicState;
+		pipelineCI.stageCount = 2;
+		pipelineCI.pStages = shaderStages.data();
+		pipelineCI.renderPass = renderpass;
+		pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::UV });
+
+		shaderStages[0] = loadShader(getShadersPath() + "pbrtexture/filtercube.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "pbrtexture/irradiancecube.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		VkPipeline pipeline;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipeline));
+
+		// Render
+
+		VkClearValue clearValues[1];
+		clearValues[0].color = { { 0.0f, 0.0f, 0.2f, 0.0f } };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		// Reuse render pass from example pass
+		renderPassBeginInfo.renderPass = renderpass;
+		renderPassBeginInfo.framebuffer = offscreen.framebuffer;
+		renderPassBeginInfo.renderArea.extent.width = dim;
+		renderPassBeginInfo.renderArea.extent.height = dim;
+		renderPassBeginInfo.clearValueCount = 1;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		std::vector<glm::mat4> matrices = {
+			// POSITIVE_X
+			glm::rotate(glm::rotate(glm::mat4(1.0f), glm::radians(90.0f), glm::vec3(0.0f, 1.0f, 0.0f)), glm::radians(180.0f), glm::vec3(1.0f, 0.0f, 0.0f)),
+			// NEGATIVE_X
+			glm::rotate(glm::rotate(glm::mat4(1.0f), glm::radians(-90.0f), glm::vec3(0.0f, 1.0f, 0.0f)), glm::radians(180.0f), glm::vec3(1.0f, 0.0f, 0.0f)),
+			// POSITIVE_Y
+			glm::rotate(glm::mat4(1.0f), glm::radians(-90.0f), glm::vec3(1.0f, 0.0f, 0.0f)),
+			// NEGATIVE_Y
+			glm::rotate(glm::mat4(1.0f), glm::radians(90.0f), glm::vec3(1.0f, 0.0f, 0.0f)),
+			// POSITIVE_Z
+			glm::rotate(glm::mat4(1.0f), glm::radians(180.0f), glm::vec3(1.0f, 0.0f, 0.0f)),
+			// NEGATIVE_Z
+			glm::rotate(glm::mat4(1.0f), glm::radians(180.0f), glm::vec3(0.0f, 0.0f, 1.0f)),
+		};
+
+		VkCommandBuffer cmdBuf = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+
+		VkViewport viewport = vks::initializers::viewport((float)dim, (float)dim, 0.0f, 1.0f);
+		VkRect2D scissor = vks::initializers::rect2D(dim, dim, 0, 0);
+
+		vkCmdSetViewport(cmdBuf, 0, 1, &viewport);
+		vkCmdSetScissor(cmdBuf, 0, 1, &scissor);
+
+		VkImageSubresourceRange subresourceRange = {};
+		subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+		subresourceRange.baseMipLevel = 0;
+		subresourceRange.levelCount = numMips;
+		subresourceRange.layerCount = 6;
+
+		// Change image layout for all cubemap faces to transfer destination
+		vks::tools::setImageLayout(
+			cmdBuf,
+			textures.irradianceCube.image,
+			VK_IMAGE_LAYOUT_UNDEFINED,
+			VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+			subresourceRange);
+
+		for (uint32_t m = 0; m < numMips; m++) {
+			for (uint32_t f = 0; f < 6; f++) {
+				viewport.width = static_cast<float>(dim * std::pow(0.5f, m));
+				viewport.height = static_cast<float>(dim * std::pow(0.5f, m));
+				vkCmdSetViewport(cmdBuf, 0, 1, &viewport);
+
+				// Render scene from cube face's point of view
+				vkCmdBeginRenderPass(cmdBuf, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+				// Update shader push constant block
+				pushBlock.mvp = glm::perspective((float)(M_PI / 2.0), 1.0f, 0.1f, 512.0f) * matrices[f];
+
+				vkCmdPushConstants(cmdBuf, pipelinelayout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushBlock), &pushBlock);
+
+				vkCmdBindPipeline(cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
+				vkCmdBindDescriptorSets(cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelinelayout, 0, 1, &descriptorset, 0, NULL);
+
+				models.skybox.draw(cmdBuf);
+
+				vkCmdEndRenderPass(cmdBuf);
+
+				vks::tools::setImageLayout(
+					cmdBuf,
+					offscreen.image,
+					VK_IMAGE_ASPECT_COLOR_BIT,
+					VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+					VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
+
+				// Copy region for transfer from framebuffer to cube face
+				VkImageCopy copyRegion = {};
+
+				copyRegion.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+				copyRegion.srcSubresource.baseArrayLayer = 0;
+				copyRegion.srcSubresource.mipLevel = 0;
+				copyRegion.srcSubresource.layerCount = 1;
+				copyRegion.srcOffset = { 0, 0, 0 };
+
+				copyRegion.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+				copyRegion.dstSubresource.baseArrayLayer = f;
+				copyRegion.dstSubresource.mipLevel = m;
+				copyRegion.dstSubresource.layerCount = 1;
+				copyRegion.dstOffset = { 0, 0, 0 };
+
+				copyRegion.extent.width = static_cast<uint32_t>(viewport.width);
+				copyRegion.extent.height = static_cast<uint32_t>(viewport.height);
+				copyRegion.extent.depth = 1;
+
+				vkCmdCopyImage(
+					cmdBuf,
+					offscreen.image,
+					VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+					textures.irradianceCube.image,
+					VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+					1,
+					&copyRegion);
+
+				// Transform framebuffer color attachment back
+				vks::tools::setImageLayout(
+					cmdBuf,
+					offscreen.image,
+					VK_IMAGE_ASPECT_COLOR_BIT,
+					VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+					VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
+			}
+		}
+
+		vks::tools::setImageLayout(
+			cmdBuf,
+			textures.irradianceCube.image,
+			VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+			VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
+			subresourceRange);
+
+		vulkanDevice->flushCommandBuffer(cmdBuf, queue);
+
+		vkDestroyRenderPass(device, renderpass, nullptr);
+		vkDestroyFramebuffer(device, offscreen.framebuffer, nullptr);
+		vkFreeMemory(device, offscreen.memory, nullptr);
+		vkDestroyImageView(device, offscreen.view, nullptr);
+		vkDestroyImage(device, offscreen.image, nullptr);
+		vkDestroyDescriptorPool(device, descriptorpool, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorsetlayout, nullptr);
+		vkDestroyPipeline(device, pipeline, nullptr);
+		vkDestroyPipelineLayout(device, pipelinelayout, nullptr);
+
+		auto tEnd = std::chrono::high_resolution_clock::now();
+		auto tDiff = std::chrono::duration<double, std::milli>(tEnd - tStart).count();
+		std::cout << "Generating irradiance cube with " << numMips << " mip levels took " << tDiff << " ms" << std::endl;
+	}
+
+	// Prefilter environment cubemap
+	// See https://placeholderart.wordpress.com/2015/07/28/implementation-notes-runtime-environment-map-filtering-for-image-based-lighting/
+	void generatePrefilteredCube()
+	{
+		auto tStart = std::chrono::high_resolution_clock::now();
+
+		const VkFormat format = VK_FORMAT_R16G16B16A16_SFLOAT;
+		const int32_t dim = 512;
+		const uint32_t numMips = static_cast<uint32_t>(floor(log2(dim))) + 1;
+
+		// Pre-filtered cube map
+		// Image
+		VkImageCreateInfo imageCI = vks::initializers::imageCreateInfo();
+		imageCI.imageType = VK_IMAGE_TYPE_2D;
+		imageCI.format = format;
+		imageCI.extent.width = dim;
+		imageCI.extent.height = dim;
+		imageCI.extent.depth = 1;
+		imageCI.mipLevels = numMips;
+		imageCI.arrayLayers = 6;
+		imageCI.samples = VK_SAMPLE_COUNT_1_BIT;
+		imageCI.tiling = VK_IMAGE_TILING_OPTIMAL;
+		imageCI.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
+		imageCI.flags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;
+		VK_CHECK_RESULT(vkCreateImage(device, &imageCI, nullptr, &textures.prefilteredCube.image));
+		VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo();
+		VkMemoryRequirements memReqs;
+		vkGetImageMemoryRequirements(device, textures.prefilteredCube.image, &memReqs);
+		memAlloc.allocationSize = memReqs.size;
+		memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+		VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &textures.prefilteredCube.deviceMemory));
+		VK_CHECK_RESULT(vkBindImageMemory(device, textures.prefilteredCube.image, textures.prefilteredCube.deviceMemory, 0));
+		// Image view
+		VkImageViewCreateInfo viewCI = vks::initializers::imageViewCreateInfo();
+		viewCI.viewType = VK_IMAGE_VIEW_TYPE_CUBE;
+		viewCI.format = format;
+		viewCI.subresourceRange = {};
+		viewCI.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+		viewCI.subresourceRange.levelCount = numMips;
+		viewCI.subresourceRange.layerCount = 6;
+		viewCI.image = textures.prefilteredCube.image;
+		VK_CHECK_RESULT(vkCreateImageView(device, &viewCI, nullptr, &textures.prefilteredCube.view));
+		// Sampler
+		VkSamplerCreateInfo samplerCI = vks::initializers::samplerCreateInfo();
+		samplerCI.magFilter = VK_FILTER_LINEAR;
+		samplerCI.minFilter = VK_FILTER_LINEAR;
+		samplerCI.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+		samplerCI.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+		samplerCI.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+		samplerCI.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+		samplerCI.minLod = 0.0f;
+		samplerCI.maxLod = static_cast<float>(numMips);
+		samplerCI.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
+		VK_CHECK_RESULT(vkCreateSampler(device, &samplerCI, nullptr, &textures.prefilteredCube.sampler));
+
+		textures.prefilteredCube.descriptor.imageView = textures.prefilteredCube.view;
+		textures.prefilteredCube.descriptor.sampler = textures.prefilteredCube.sampler;
+		textures.prefilteredCube.descriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+		textures.prefilteredCube.device = vulkanDevice;
+
+		// FB, Att, RP, Pipe, etc.
+		VkAttachmentDescription attDesc = {};
+		// Color attachment
+		attDesc.format = format;
+		attDesc.samples = VK_SAMPLE_COUNT_1_BIT;
+		attDesc.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+		attDesc.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+		attDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+		attDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+		attDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+		attDesc.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+		VkAttachmentReference colorReference = { 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
+
+		VkSubpassDescription subpassDescription = {};
+		subpassDescription.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+		subpassDescription.colorAttachmentCount = 1;
+		subpassDescription.pColorAttachments = &colorReference;
+
+		// Use subpass dependencies for layout transitions
+		std::array<VkSubpassDependency, 2> dependencies;
+		dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
+		dependencies[0].dstSubpass = 0;
+		dependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+		dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+		dependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
+		dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+		dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+		dependencies[1].srcSubpass = 0;
+		dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
+		dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+		dependencies[1].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+		dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+		dependencies[1].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
+		dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+		// Renderpass
+		VkRenderPassCreateInfo renderPassCI = vks::initializers::renderPassCreateInfo();
+		renderPassCI.attachmentCount = 1;
+		renderPassCI.pAttachments = &attDesc;
+		renderPassCI.subpassCount = 1;
+		renderPassCI.pSubpasses = &subpassDescription;
+		renderPassCI.dependencyCount = 2;
+		renderPassCI.pDependencies = dependencies.data();
+		VkRenderPass renderpass;
+		VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassCI, nullptr, &renderpass));
+
+		struct {
+			VkImage image;
+			VkImageView view;
+			VkDeviceMemory memory;
+			VkFramebuffer framebuffer;
+		} offscreen;
+
+		// Offfscreen framebuffer
+		{
+			// Color attachment
+			VkImageCreateInfo imageCreateInfo = vks::initializers::imageCreateInfo();
+			imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
+			imageCreateInfo.format = format;
+			imageCreateInfo.extent.width = dim;
+			imageCreateInfo.extent.height = dim;
+			imageCreateInfo.extent.depth = 1;
+			imageCreateInfo.mipLevels = 1;
+			imageCreateInfo.arrayLayers = 1;
+			imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
+			imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
+			imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+			imageCreateInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
+			imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+			VK_CHECK_RESULT(vkCreateImage(device, &imageCreateInfo, nullptr, &offscreen.image));
+
+			VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo();
+			VkMemoryRequirements memReqs;
+			vkGetImageMemoryRequirements(device, offscreen.image, &memReqs);
+			memAlloc.allocationSize = memReqs.size;
+			memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+			VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &offscreen.memory));
+			VK_CHECK_RESULT(vkBindImageMemory(device, offscreen.image, offscreen.memory, 0));
+
+			VkImageViewCreateInfo colorImageView = vks::initializers::imageViewCreateInfo();
+			colorImageView.viewType = VK_IMAGE_VIEW_TYPE_2D;
+			colorImageView.format = format;
+			colorImageView.flags = 0;
+			colorImageView.subresourceRange = {};
+			colorImageView.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+			colorImageView.subresourceRange.baseMipLevel = 0;
+			colorImageView.subresourceRange.levelCount = 1;
+			colorImageView.subresourceRange.baseArrayLayer = 0;
+			colorImageView.subresourceRange.layerCount = 1;
+			colorImageView.image = offscreen.image;
+			VK_CHECK_RESULT(vkCreateImageView(device, &colorImageView, nullptr, &offscreen.view));
+
+			VkFramebufferCreateInfo fbufCreateInfo = vks::initializers::framebufferCreateInfo();
+			fbufCreateInfo.renderPass = renderpass;
+			fbufCreateInfo.attachmentCount = 1;
+			fbufCreateInfo.pAttachments = &offscreen.view;
+			fbufCreateInfo.width = dim;
+			fbufCreateInfo.height = dim;
+			fbufCreateInfo.layers = 1;
+			VK_CHECK_RESULT(vkCreateFramebuffer(device, &fbufCreateInfo, nullptr, &offscreen.framebuffer));
+
+			VkCommandBuffer layoutCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+			vks::tools::setImageLayout(
+				layoutCmd,
+				offscreen.image,
+				VK_IMAGE_ASPECT_COLOR_BIT,
+				VK_IMAGE_LAYOUT_UNDEFINED,
+				VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
+			vulkanDevice->flushCommandBuffer(layoutCmd, queue, true);
+		}
+
+		// Descriptors
+		VkDescriptorSetLayout descriptorsetlayout;
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0),
+		};
+		VkDescriptorSetLayoutCreateInfo descriptorsetlayoutCI = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorsetlayoutCI, nullptr, &descriptorsetlayout));
+
+		// Descriptor Pool
+		std::vector<VkDescriptorPoolSize> poolSizes = { vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1) };
+		VkDescriptorPoolCreateInfo descriptorPoolCI = vks::initializers::descriptorPoolCreateInfo(poolSizes, 2);
+		VkDescriptorPool descriptorpool;
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolCI, nullptr, &descriptorpool));
+
+		// Descriptor sets
+		VkDescriptorSet descriptorset;
+		VkDescriptorSetAllocateInfo allocInfo =	vks::initializers::descriptorSetAllocateInfo(descriptorpool, &descriptorsetlayout, 1);
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorset));
+		VkWriteDescriptorSet writeDescriptorSet = vks::initializers::writeDescriptorSet(descriptorset, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &textures.environmentCube.descriptor);
+		vkUpdateDescriptorSets(device, 1, &writeDescriptorSet, 0, nullptr);
+
+		// Pipeline layout
+		struct PushBlock {
+			glm::mat4 mvp;
+			float roughness;
+			uint32_t numSamples = 32u;
+		} pushBlock;
+
+		VkPipelineLayout pipelinelayout;
+		std::vector<VkPushConstantRange> pushConstantRanges = {
+			vks::initializers::pushConstantRange(VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(PushBlock), 0),
+		};
+		VkPipelineLayoutCreateInfo pipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(&descriptorsetlayout, 1);
+		pipelineLayoutCI.pushConstantRangeCount = 1;
+		pipelineLayoutCI.pPushConstantRanges = pushConstantRanges.data();
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelinelayout));
+
+		// Pipeline
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE, VK_FRONT_FACE_COUNTER_CLOCKWISE);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_FALSE, VK_FALSE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1);
+		VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT);
+		std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
+		VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelinelayout, renderpass);
+		pipelineCI.pInputAssemblyState = &inputAssemblyState;
+		pipelineCI.pRasterizationState = &rasterizationState;
+		pipelineCI.pColorBlendState = &colorBlendState;
+		pipelineCI.pMultisampleState = &multisampleState;
+		pipelineCI.pViewportState = &viewportState;
+		pipelineCI.pDepthStencilState = &depthStencilState;
+		pipelineCI.pDynamicState = &dynamicState;
+		pipelineCI.stageCount = 2;
+		pipelineCI.pStages = shaderStages.data();
+		pipelineCI.renderPass = renderpass;
+		pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::UV });
+
+		shaderStages[0] = loadShader(getShadersPath() + "pbrtexture/filtercube.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "pbrtexture/prefilterenvmap.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		VkPipeline pipeline;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipeline));
+
+		// Render
+
+		VkClearValue clearValues[1];
+		clearValues[0].color = { { 0.0f, 0.0f, 0.2f, 0.0f } };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		// Reuse render pass from example pass
+		renderPassBeginInfo.renderPass = renderpass;
+		renderPassBeginInfo.framebuffer = offscreen.framebuffer;
+		renderPassBeginInfo.renderArea.extent.width = dim;
+		renderPassBeginInfo.renderArea.extent.height = dim;
+		renderPassBeginInfo.clearValueCount = 1;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		std::vector<glm::mat4> matrices = {
+			// POSITIVE_X
+			glm::rotate(glm::rotate(glm::mat4(1.0f), glm::radians(90.0f), glm::vec3(0.0f, 1.0f, 0.0f)), glm::radians(180.0f), glm::vec3(1.0f, 0.0f, 0.0f)),
+			// NEGATIVE_X
+			glm::rotate(glm::rotate(glm::mat4(1.0f), glm::radians(-90.0f), glm::vec3(0.0f, 1.0f, 0.0f)), glm::radians(180.0f), glm::vec3(1.0f, 0.0f, 0.0f)),
+			// POSITIVE_Y
+			glm::rotate(glm::mat4(1.0f), glm::radians(-90.0f), glm::vec3(1.0f, 0.0f, 0.0f)),
+			// NEGATIVE_Y
+			glm::rotate(glm::mat4(1.0f), glm::radians(90.0f), glm::vec3(1.0f, 0.0f, 0.0f)),
+			// POSITIVE_Z
+			glm::rotate(glm::mat4(1.0f), glm::radians(180.0f), glm::vec3(1.0f, 0.0f, 0.0f)),
+			// NEGATIVE_Z
+			glm::rotate(glm::mat4(1.0f), glm::radians(180.0f), glm::vec3(0.0f, 0.0f, 1.0f)),
+		};
+
+		VkCommandBuffer cmdBuf = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+
+		VkViewport viewport = vks::initializers::viewport((float)dim, (float)dim, 0.0f, 1.0f);
+		VkRect2D scissor = vks::initializers::rect2D(dim, dim, 0, 0);
+
+		vkCmdSetViewport(cmdBuf, 0, 1, &viewport);
+		vkCmdSetScissor(cmdBuf, 0, 1, &scissor);
+
+		VkImageSubresourceRange subresourceRange = {};
+		subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+		subresourceRange.baseMipLevel = 0;
+		subresourceRange.levelCount = numMips;
+		subresourceRange.layerCount = 6;
+
+		// Change image layout for all cubemap faces to transfer destination
+		vks::tools::setImageLayout(
+			cmdBuf,
+			textures.prefilteredCube.image,
+			VK_IMAGE_LAYOUT_UNDEFINED,
+			VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+			subresourceRange);
+
+		for (uint32_t m = 0; m < numMips; m++) {
+			pushBlock.roughness = (float)m / (float)(numMips - 1);
+			for (uint32_t f = 0; f < 6; f++) {
+				viewport.width = static_cast<float>(dim * std::pow(0.5f, m));
+				viewport.height = static_cast<float>(dim * std::pow(0.5f, m));
+				vkCmdSetViewport(cmdBuf, 0, 1, &viewport);
+
+				// Render scene from cube face's point of view
+				vkCmdBeginRenderPass(cmdBuf, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+				// Update shader push constant block
+				pushBlock.mvp = glm::perspective((float)(M_PI / 2.0), 1.0f, 0.1f, 512.0f) * matrices[f];
+
+				vkCmdPushConstants(cmdBuf, pipelinelayout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushBlock), &pushBlock);
+
+				vkCmdBindPipeline(cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
+				vkCmdBindDescriptorSets(cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelinelayout, 0, 1, &descriptorset, 0, NULL);
+
+				models.skybox.draw(cmdBuf);
+
+				vkCmdEndRenderPass(cmdBuf);
+
+				vks::tools::setImageLayout(
+					cmdBuf,
+					offscreen.image,
+					VK_IMAGE_ASPECT_COLOR_BIT,
+					VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+					VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
+
+				// Copy region for transfer from framebuffer to cube face
+				VkImageCopy copyRegion = {};
+
+				copyRegion.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+				copyRegion.srcSubresource.baseArrayLayer = 0;
+				copyRegion.srcSubresource.mipLevel = 0;
+				copyRegion.srcSubresource.layerCount = 1;
+				copyRegion.srcOffset = { 0, 0, 0 };
+
+				copyRegion.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+				copyRegion.dstSubresource.baseArrayLayer = f;
+				copyRegion.dstSubresource.mipLevel = m;
+				copyRegion.dstSubresource.layerCount = 1;
+				copyRegion.dstOffset = { 0, 0, 0 };
+
+				copyRegion.extent.width = static_cast<uint32_t>(viewport.width);
+				copyRegion.extent.height = static_cast<uint32_t>(viewport.height);
+				copyRegion.extent.depth = 1;
+
+				vkCmdCopyImage(
+					cmdBuf,
+					offscreen.image,
+					VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+					textures.prefilteredCube.image,
+					VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+					1,
+					&copyRegion);
+
+				// Transform framebuffer color attachment back
+				vks::tools::setImageLayout(
+					cmdBuf,
+					offscreen.image,
+					VK_IMAGE_ASPECT_COLOR_BIT,
+					VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+					VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
+			}
+		}
+
+		vks::tools::setImageLayout(
+			cmdBuf,
+			textures.prefilteredCube.image,
+			VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+			VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
+			subresourceRange);
+
+		vulkanDevice->flushCommandBuffer(cmdBuf, queue);
+
+		vkDestroyRenderPass(device, renderpass, nullptr);
+		vkDestroyFramebuffer(device, offscreen.framebuffer, nullptr);
+		vkFreeMemory(device, offscreen.memory, nullptr);
+		vkDestroyImageView(device, offscreen.view, nullptr);
+		vkDestroyImage(device, offscreen.image, nullptr);
+		vkDestroyDescriptorPool(device, descriptorpool, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorsetlayout, nullptr);
+		vkDestroyPipeline(device, pipeline, nullptr);
+		vkDestroyPipelineLayout(device, pipelinelayout, nullptr);
+
+		auto tEnd = std::chrono::high_resolution_clock::now();
+		auto tDiff = std::chrono::duration<double, std::milli>(tEnd - tStart).count();
+		std::cout << "Generating pre-filtered enivornment cube with " << numMips << " mip levels took " << tDiff << " ms" << std::endl;
+	}
+
+	// Prepare and initialize uniform buffer containing shader uniforms
+	void prepareUniformBuffers()
+	{
+		// Object vertex shader uniform buffer
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.object,
+			sizeof(uboMatrices)));
+
+		// Skybox vertex shader uniform buffer
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.skybox,
+			sizeof(uboMatrices)));
+
+		// Shared parameter uniform buffer
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.params,
+			sizeof(uboParams)));
+
+		// Map persistent
+		VK_CHECK_RESULT(uniformBuffers.object.map());
+		VK_CHECK_RESULT(uniformBuffers.skybox.map());
+		VK_CHECK_RESULT(uniformBuffers.params.map());
+
+		updateUniformBuffers();
+		updateParams();
+	}
+
+	void updateUniformBuffers()
+	{
+		// 3D object
+		uboMatrices.projection = camera.matrices.perspective;
+		uboMatrices.view = camera.matrices.view;
+		uboMatrices.model = glm::rotate(glm::mat4(1.0f), glm::radians(-90.0f), glm::vec3(0.0f, 1.0f, 0.0f));
+		uboMatrices.camPos = camera.position * -1.0f;
+		memcpy(uniformBuffers.object.mapped, &uboMatrices, sizeof(uboMatrices));
+
+		// Skybox
+		uboMatrices.model = glm::mat4(glm::mat3(camera.matrices.view));
+		memcpy(uniformBuffers.skybox.mapped, &uboMatrices, sizeof(uboMatrices));
+	}
+
+	void updateParams()
+	{
+		const float p = 15.0f;
+		uboParams.lights[0] = glm::vec4(-p, -p*0.5f, -p, 1.0f);
+		uboParams.lights[1] = glm::vec4(-p, -p*0.5f,  p, 1.0f);
+		uboParams.lights[2] = glm::vec4( p, -p*0.5f,  p, 1.0f);
+		uboParams.lights[3] = glm::vec4( p, -p*0.5f, -p, 1.0f);
+
+		memcpy(uniformBuffers.params.mapped, &uboParams, sizeof(uboParams));
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+
+		VulkanExampleBase::submitFrame();
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		loadAssets();
+		generateBRDFLUT();
+		generateIrradianceCube();
+		generatePrefilteredCube();
+		prepareUniformBuffers();
+		setupDescriptors();
+		preparePipelines();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+		if (camera.updated)
+		{
+			updateUniformBuffers();
+		}
+	}
+
+	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
+	{
+		if (overlay->header("Settings")) {
+			if (overlay->inputFloat("Exposure", &uboParams.exposure, 0.1f, 2)) {
+				updateParams();
+			}
+			if (overlay->inputFloat("Gamma", &uboParams.gamma, 0.1f, 2)) {
+				updateParams();
+			}
+			if (overlay->checkBox("Skybox", &displaySkybox)) {
+				buildCommandBuffers();
+			}
+		}
+	}
+};
+
+VULKAN_EXAMPLE_MAIN()
diff --git a/external/Vulkan/examples/pipelines/pipelines.cpp b/external/Vulkan/examples/pipelines/pipelines.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..68d0415ab44a461327d7b3561ccadfd10c976fa0
--- /dev/null
+++ b/external/Vulkan/examples/pipelines/pipelines.cpp
@@ -0,0 +1,347 @@
+/*
+* Vulkan Example - Using different pipelines in one single renderpass
+*
+* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkanexamplebase.h"
+#include "VulkanglTFModel.h"
+
+#define ENABLE_VALIDATION false
+
+class VulkanExample: public VulkanExampleBase
+{
+public:
+	vkglTF::Model scene;
+
+	vks::Buffer uniformBuffer;
+
+	// Same uniform buffer layout as shader
+	struct UBOVS {
+		glm::mat4 projection;
+		glm::mat4 modelView;
+		glm::vec4 lightPos = glm::vec4(0.0f, 2.0f, 1.0f, 0.0f);
+	} uboVS;
+
+	VkPipelineLayout pipelineLayout;
+	VkDescriptorSet descriptorSet;
+	VkDescriptorSetLayout descriptorSetLayout;
+
+	struct {
+		VkPipeline phong;
+		VkPipeline wireframe;
+		VkPipeline toon;
+	} pipelines;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Pipeline state objects";
+		camera.type = Camera::CameraType::lookat;
+		camera.setPosition(glm::vec3(0.0f, 0.0f, -10.5f));
+		camera.setRotation(glm::vec3(-25.0f, 15.0f, 0.0f));
+		camera.setRotationSpeed(0.5f);
+		camera.setPerspective(60.0f, (float)(width / 3.0f) / (float)height, 0.1f, 256.0f);
+	}
+
+	~VulkanExample()
+	{
+		// Clean up used Vulkan resources
+		// Note : Inherited destructor cleans up resources stored in base class
+		vkDestroyPipeline(device, pipelines.phong, nullptr);
+		if (deviceFeatures.fillModeNonSolid)
+		{
+			vkDestroyPipeline(device, pipelines.wireframe, nullptr);
+		}
+		vkDestroyPipeline(device, pipelines.toon, nullptr);
+
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+
+		uniformBuffer.destroy();
+	}
+
+	// Enable physical device features required for this example
+	virtual void getEnabledFeatures()
+	{
+		// Fill mode non solid is required for wireframe display
+		if (deviceFeatures.fillModeNonSolid) {
+			enabledFeatures.fillModeNonSolid = VK_TRUE;
+			// Wide lines must be present for line width > 1.0f
+			if (deviceFeatures.wideLines) {
+				enabledFeatures.wideLines = VK_TRUE;
+			}
+		};
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		clearValues[0].color = defaultClearColor;
+		clearValues[1].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.offset.x = 0;
+		renderPassBeginInfo.renderArea.offset.y = 0;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		renderPassBeginInfo.clearValueCount = 2;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			// Set target frame buffer
+			renderPassBeginInfo.framebuffer = frameBuffers[i];
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+			VkRect2D scissor = vks::initializers::rect2D(width, height,	0, 0);
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL);
+			scene.bindBuffers(drawCmdBuffers[i]);
+
+			// Left : Solid colored
+			viewport.width = (float)width / 3.0;
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.phong);
+			scene.draw(drawCmdBuffers[i]);
+
+			// Center : Toon
+			viewport.x = (float)width / 3.0;
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.toon);
+			// Line width > 1.0f only if wide lines feature is supported
+			if (deviceFeatures.wideLines) {
+				vkCmdSetLineWidth(drawCmdBuffers[i], 2.0f);
+			}
+			scene.draw(drawCmdBuffers[i]);
+
+			if (deviceFeatures.fillModeNonSolid)
+			{
+				// Right : Wireframe
+				viewport.x = (float)width / 3.0 + (float)width / 3.0;
+				vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+				vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.wireframe);
+				scene.draw(drawCmdBuffers[i]);
+			}
+
+			drawUI(drawCmdBuffers[i]);
+
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void loadAssets()
+	{
+		const uint32_t glTFLoadingFlags = vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::PreMultiplyVertexColors | vkglTF::FileLoadingFlags::FlipY;
+		scene.loadFromFile(getAssetPath() + "models/treasure_smooth.gltf", vulkanDevice, queue, glTFLoadingFlags);
+	}
+
+	void setupDescriptorPool()
+	{
+		std::vector<VkDescriptorPoolSize> poolSizes =
+		{
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1)
+		};
+
+		VkDescriptorPoolCreateInfo descriptorPoolInfo =
+			vks::initializers::descriptorPoolCreateInfo(
+				poolSizes.size(),
+				poolSizes.data(),
+				2);
+
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+	}
+
+	void setupDescriptorSetLayout()
+	{
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings =
+		{
+			// Binding 0 : Vertex shader uniform buffer
+			vks::initializers::descriptorSetLayoutBinding(
+				VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+				VK_SHADER_STAGE_VERTEX_BIT,
+				0)
+		};
+
+		VkDescriptorSetLayoutCreateInfo descriptorLayout =
+			vks::initializers::descriptorSetLayoutCreateInfo(
+				setLayoutBindings.data(),
+				setLayoutBindings.size());
+
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout));
+
+		VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo =
+			vks::initializers::pipelineLayoutCreateInfo(
+				&descriptorSetLayout,
+				1);
+
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayout));
+	}
+
+	void setupDescriptorSet()
+	{
+		VkDescriptorSetAllocateInfo allocInfo =
+			vks::initializers::descriptorSetAllocateInfo(
+				descriptorPool,
+				&descriptorSetLayout,
+				1);
+
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet));
+
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets =
+		{
+			// Binding 0 : Vertex shader uniform buffer
+			vks::initializers::writeDescriptorSet(
+				descriptorSet,
+				VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+				0,
+				&uniformBuffer.descriptor)
+		};
+
+		vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, NULL);
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+		VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT);
+		std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR, VK_DYNAMIC_STATE_LINE_WIDTH, };
+		VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass);
+		pipelineCI.pInputAssemblyState = &inputAssemblyState;
+		pipelineCI.pRasterizationState = &rasterizationState;
+		pipelineCI.pColorBlendState = &colorBlendState;
+		pipelineCI.pMultisampleState = &multisampleState;
+		pipelineCI.pViewportState = &viewportState;
+		pipelineCI.pDepthStencilState = &depthStencilState;
+		pipelineCI.pDynamicState = &dynamicState;
+		pipelineCI.stageCount = shaderStages.size();
+		pipelineCI.pStages = shaderStages.data();
+		pipelineCI.pVertexInputState  = vkglTF::Vertex::getPipelineVertexInputState({vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::Color});
+
+		// Create the graphics pipeline state objects
+
+		// We are using this pipeline as the base for the other pipelines (derivatives)
+		// Pipeline derivatives can be used for pipelines that share most of their state
+		// Depending on the implementation this may result in better performance for pipeline
+		// switching and faster creation time
+		pipelineCI.flags = VK_PIPELINE_CREATE_ALLOW_DERIVATIVES_BIT;
+
+		// Textured pipeline
+		// Phong shading pipeline
+		shaderStages[0] = loadShader(getShadersPath() + "pipelines/phong.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "pipelines/phong.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.phong));
+
+		// All pipelines created after the base pipeline will be derivatives
+		pipelineCI.flags = VK_PIPELINE_CREATE_DERIVATIVE_BIT;
+		// Base pipeline will be our first created pipeline
+		pipelineCI.basePipelineHandle = pipelines.phong;
+		// It's only allowed to either use a handle or index for the base pipeline
+		// As we use the handle, we must set the index to -1 (see section 9.5 of the specification)
+		pipelineCI.basePipelineIndex = -1;
+
+		// Toon shading pipeline
+		shaderStages[0] = loadShader(getShadersPath() + "pipelines/toon.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "pipelines/toon.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.toon));
+
+		// Pipeline for wire frame rendering
+		// Non solid rendering is not a mandatory Vulkan feature
+		if (deviceFeatures.fillModeNonSolid)
+		{
+			rasterizationState.polygonMode = VK_POLYGON_MODE_LINE;
+			shaderStages[0] = loadShader(getShadersPath() + "pipelines/wireframe.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+			shaderStages[1] = loadShader(getShadersPath() + "pipelines/wireframe.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+			VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.wireframe));
+		}
+	}
+
+	// Prepare and initialize uniform buffer containing shader uniforms
+	void prepareUniformBuffers()
+	{
+		// Create the vertex shader uniform buffer block
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffer,
+			sizeof(uboVS)));
+
+		// Map persistent
+		VK_CHECK_RESULT(uniformBuffer.map());
+
+		updateUniformBuffers();
+	}
+
+	void updateUniformBuffers()
+	{
+		uboVS.projection = camera.matrices.perspective;
+		uboVS.modelView = camera.matrices.view;
+		memcpy(uniformBuffer.mapped, &uboVS, sizeof(uboVS));
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+
+		VulkanExampleBase::submitFrame();
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		loadAssets();
+		prepareUniformBuffers();
+		setupDescriptorSetLayout();
+		preparePipelines();
+		setupDescriptorPool();
+		setupDescriptorSet();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+		if (camera.updated) {
+			updateUniformBuffers();
+		}
+	}
+
+	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
+	{
+		if (!deviceFeatures.fillModeNonSolid) {
+			if (overlay->header("Info")) {
+				overlay->text("Non solid fill modes not supported!");
+			}
+		}
+	}
+};
+
+VULKAN_EXAMPLE_MAIN()
\ No newline at end of file
diff --git a/external/Vulkan/examples/pipelinestatistics/pipelinestatistics.cpp b/external/Vulkan/examples/pipelinestatistics/pipelinestatistics.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3d4292c036ce8f684183cc527d467f63c479d89b
--- /dev/null
+++ b/external/Vulkan/examples/pipelinestatistics/pipelinestatistics.cpp
@@ -0,0 +1,429 @@
+/*
+* Vulkan Example - Retrieving pipeline statistics
+*
+* Copyright (C) 2017 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkanexamplebase.h"
+#include "VulkanglTFModel.h"
+
+#define ENABLE_VALIDATION false
+#define OBJ_DIM 0.05f
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	struct Models {
+		std::vector<vkglTF::Model> objects;
+		int32_t objectIndex = 3;
+		std::vector<std::string> names;
+	} models;
+
+	struct UniformBuffers {
+		vks::Buffer VS;
+	} uniformBuffers;
+
+	struct UBOVS {
+		glm::mat4 projection;
+		glm::mat4 modelview;
+		glm::vec4 lightPos = glm::vec4(-10.0f, -10.0f, 10.0f, 1.0f);
+	} uboVS;
+
+	VkPipeline pipeline = VK_NULL_HANDLE;
+
+	int32_t cullMode = VK_CULL_MODE_BACK_BIT;
+	bool blending = false;
+	bool discard = false;
+	bool wireframe = false;
+	bool tessellation = false;
+
+	VkPipelineLayout pipelineLayout;
+	VkDescriptorSet descriptorSet;
+	VkDescriptorSetLayout descriptorSetLayout;
+
+	VkQueryPool queryPool;
+
+	// Vector for storing pipeline statistics results
+	std::vector<uint64_t> pipelineStats;
+	std::vector<std::string> pipelineStatNames;
+
+	int32_t gridSize = 3;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Pipeline statistics";
+		camera.type = Camera::CameraType::firstperson;
+		camera.setPosition(glm::vec3(-3.0f, 1.0f, -2.75f));
+		camera.setRotation(glm::vec3(-15.25f, -46.5f, 0.0f));
+		camera.movementSpeed = 4.0f;
+		camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 256.0f);
+		camera.rotationSpeed = 0.25f;
+	}
+
+	~VulkanExample()
+	{
+		vkDestroyPipeline(device, pipeline, nullptr);
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+		vkDestroyQueryPool(device, queryPool, nullptr);
+		uniformBuffers.VS.destroy();
+	}
+
+	virtual void getEnabledFeatures()
+	{
+		// Support for pipeline statistics is optional
+		if (deviceFeatures.pipelineStatisticsQuery) {
+			enabledFeatures.pipelineStatisticsQuery = VK_TRUE;
+		}
+		else {
+			vks::tools::exitFatal("Selected GPU does not support pipeline statistics!", VK_ERROR_FEATURE_NOT_PRESENT);
+		}
+		if (deviceFeatures.fillModeNonSolid) {
+			enabledFeatures.fillModeNonSolid = VK_TRUE;
+		}
+		if (deviceFeatures.tessellationShader) {
+			enabledFeatures.tessellationShader = VK_TRUE;
+		}
+	}
+
+	// Setup a query pool for storing pipeline statistics
+	void setupQueryPool()
+	{
+		pipelineStatNames = {
+			"Input assembly vertex count        ",
+			"Input assembly primitives count    ",
+			"Vertex shader invocations          ",
+			"Clipping stage primitives processed",
+			"Clipping stage primitives output    ",
+			"Fragment shader invocations        "
+		};
+		if (deviceFeatures.tessellationShader) {
+			pipelineStatNames.push_back("Tess. control shader patches       ");
+			pipelineStatNames.push_back("Tess. eval. shader invocations     ");
+		}
+		pipelineStats.resize(pipelineStatNames.size());
+
+		VkQueryPoolCreateInfo queryPoolInfo = {};
+		queryPoolInfo.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO;
+		// This query pool will store pipeline statistics
+		queryPoolInfo.queryType = VK_QUERY_TYPE_PIPELINE_STATISTICS;
+		// Pipeline counters to be returned for this pool
+		queryPoolInfo.pipelineStatistics =
+			VK_QUERY_PIPELINE_STATISTIC_INPUT_ASSEMBLY_VERTICES_BIT |
+			VK_QUERY_PIPELINE_STATISTIC_INPUT_ASSEMBLY_PRIMITIVES_BIT |
+			VK_QUERY_PIPELINE_STATISTIC_VERTEX_SHADER_INVOCATIONS_BIT |
+			VK_QUERY_PIPELINE_STATISTIC_CLIPPING_INVOCATIONS_BIT |
+			VK_QUERY_PIPELINE_STATISTIC_CLIPPING_PRIMITIVES_BIT |
+			VK_QUERY_PIPELINE_STATISTIC_FRAGMENT_SHADER_INVOCATIONS_BIT;
+		if (deviceFeatures.tessellationShader) {
+			queryPoolInfo.pipelineStatistics |=
+				VK_QUERY_PIPELINE_STATISTIC_TESSELLATION_CONTROL_SHADER_PATCHES_BIT |
+				VK_QUERY_PIPELINE_STATISTIC_TESSELLATION_EVALUATION_SHADER_INVOCATIONS_BIT;
+		}
+		queryPoolInfo.queryCount = deviceFeatures.tessellationShader ? 8 : 6;
+		VK_CHECK_RESULT(vkCreateQueryPool(device, &queryPoolInfo, NULL, &queryPool));
+	}
+
+	// Retrieves the results of the pipeline statistics query submitted to the command buffer
+	void getQueryResults()
+	{
+		uint32_t count = static_cast<uint32_t>(pipelineStats.size());
+		vkGetQueryPoolResults(
+			device,
+			queryPool,
+			0,
+			1,
+			count * sizeof(uint64_t),
+			pipelineStats.data(),
+			sizeof(uint64_t),
+			VK_QUERY_RESULT_64_BIT);
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		clearValues[0].color = defaultClearColor;
+		clearValues[1].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.offset.x = 0;
+		renderPassBeginInfo.renderArea.offset.y = 0;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		renderPassBeginInfo.clearValueCount = 2;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i) {
+			renderPassBeginInfo.framebuffer = frameBuffers[i];
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			// Reset timestamp query pool
+			vkCmdResetQueryPool(drawCmdBuffers[i], queryPool, 0, static_cast<uint32_t>(pipelineStats.size()));
+
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			VkViewport viewport = vks::initializers::viewport((float)width,	(float)height, 0.0f, 1.0f);
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+			VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+			VkDeviceSize offsets[1] = { 0 };
+
+			// Start capture of pipeline statistics
+			vkCmdBeginQuery(drawCmdBuffers[i], queryPool, 0, 0);
+
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL);
+			vkCmdBindVertexBuffers(drawCmdBuffers[i], 0, 1, &models.objects[models.objectIndex].vertices.buffer, offsets);
+			vkCmdBindIndexBuffer(drawCmdBuffers[i], models.objects[models.objectIndex].indices.buffer, 0, VK_INDEX_TYPE_UINT32);
+
+			for (int32_t y = 0; y < gridSize; y++) {
+				for (int32_t x = 0; x < gridSize; x++) {
+					glm::vec3 pos = glm::vec3(float(x - (gridSize / 2.0f)) * 2.5f, 0.0f, float(y - (gridSize / 2.0f)) * 2.5f);
+					vkCmdPushConstants(drawCmdBuffers[i], pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(glm::vec3), &pos);
+					models.objects[models.objectIndex].draw(drawCmdBuffers[i]);
+				}
+			}
+
+			// End capture of pipeline statistics
+			vkCmdEndQuery(drawCmdBuffers[i], queryPool, 0);
+
+			drawUI(drawCmdBuffers[i]);
+
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+
+		// Read query results for displaying in next frame
+		getQueryResults();
+
+		VulkanExampleBase::submitFrame();
+	}
+
+	void loadAssets()
+	{
+		// Objects
+		std::vector<std::string> filenames = { "sphere.gltf", "teapot.gltf", "torusknot.gltf", "venus.gltf" };
+		models.names = { "Sphere", "Teapot", "Torusknot", "Venus" };
+		models.objects.resize(filenames.size());
+		for (size_t i = 0; i < filenames.size(); i++) {
+			models.objects[i].loadFromFile(getAssetPath() + "models/" + filenames[i], vulkanDevice, queue, vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::FlipY);
+		}
+	}
+
+	void setupDescriptorPool()
+	{
+		std::vector<VkDescriptorPoolSize> poolSizes = {
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 3)
+		};
+		VkDescriptorPoolCreateInfo descriptorPoolInfo =
+			vks::initializers::descriptorPoolCreateInfo(poolSizes, 3);
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+	}
+
+	void setupDescriptorSetLayout()
+	{
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0)
+		};
+		VkDescriptorSetLayoutCreateInfo descriptorLayout =
+			vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout));
+
+		VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo =
+			vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1);
+		VkPushConstantRange pushConstantRange = vks::initializers::pushConstantRange(VK_SHADER_STAGE_VERTEX_BIT, sizeof(glm::vec3), 0);
+		pipelineLayoutCreateInfo.pushConstantRangeCount = 1;
+		pipelineLayoutCreateInfo.pPushConstantRanges = &pushConstantRange;
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout));
+	}
+
+	void setupDescriptorSets()
+	{
+		VkDescriptorSetAllocateInfo allocInfo =
+			vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1);
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet));
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets = {
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.VS.descriptor)
+		};
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL);
+	}
+
+	void preparePipelines()
+	{
+		if (pipeline != VK_NULL_HANDLE) {
+			vkDestroyPipeline(device, pipeline, nullptr);
+		}
+
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, cullMode, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);		
+		VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE,	VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+		VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
+		std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
+		VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables.data(), static_cast<uint32_t>(dynamicStateEnables.size()), 0);
+		VkPipelineTessellationStateCreateInfo tessellationState = vks::initializers::pipelineTessellationStateCreateInfo(3);
+
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass, 0);
+		pipelineCI.pInputAssemblyState = &inputAssemblyState;
+		pipelineCI.pRasterizationState = &rasterizationState;
+		pipelineCI.pColorBlendState = &colorBlendState;
+		pipelineCI.pMultisampleState = &multisampleState;
+		pipelineCI.pViewportState = &viewportState;
+		pipelineCI.pDepthStencilState = &depthStencilState;
+		pipelineCI.pDynamicState = &dynamicState;
+		pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::Color });
+
+		if (blending) {
+			blendAttachmentState.blendEnable = VK_TRUE;
+			blendAttachmentState.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
+			blendAttachmentState.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
+			blendAttachmentState.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
+			blendAttachmentState.colorBlendOp = VK_BLEND_OP_ADD;
+			blendAttachmentState.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
+			blendAttachmentState.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
+			blendAttachmentState.alphaBlendOp = VK_BLEND_OP_ADD;
+			depthStencilState.depthWriteEnable = VK_FALSE;
+		}
+
+		if (discard) {
+			rasterizationState.rasterizerDiscardEnable = VK_TRUE;
+		}
+
+		if (wireframe) {
+			rasterizationState.polygonMode = VK_POLYGON_MODE_LINE;
+		}
+
+		std::vector<VkPipelineShaderStageCreateInfo> shaderStages;
+		shaderStages.resize(tessellation ? 4 : 2);
+		shaderStages[0] = loadShader(getShadersPath() + "pipelinestatistics/scene.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "pipelinestatistics/scene.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+
+		if (tessellation) {
+			inputAssemblyState.topology = VK_PRIMITIVE_TOPOLOGY_PATCH_LIST;
+			pipelineCI.pTessellationState = &tessellationState;
+			shaderStages[2] = loadShader(getShadersPath() + "pipelinestatistics/scene.tesc.spv", VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT);
+			shaderStages[3] = loadShader(getShadersPath() + "pipelinestatistics/scene.tese.spv", VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT);
+		}
+
+		pipelineCI.stageCount = static_cast<uint32_t>(shaderStages.size());
+		pipelineCI.pStages = shaderStages.data();
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipeline));
+	}
+
+	// Prepare and initialize uniform buffer containing shader uniforms
+	void prepareUniformBuffers()
+	{
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.VS,
+			sizeof(uboVS)));
+
+		// Map persistent
+		VK_CHECK_RESULT(uniformBuffers.VS.map());
+
+		updateUniformBuffers();
+	}
+
+	void updateUniformBuffers()
+	{
+		uboVS.projection = camera.matrices.perspective;
+		uboVS.modelview = camera.matrices.view;
+		memcpy(uniformBuffers.VS.mapped, &uboVS, sizeof(uboVS));
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		loadAssets();
+		setupQueryPool();
+		prepareUniformBuffers();
+		setupDescriptorSetLayout();
+		preparePipelines();
+		setupDescriptorPool();
+		setupDescriptorSets();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+	}
+
+	virtual void viewChanged()
+	{
+		updateUniformBuffers();
+	}
+
+	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
+	{
+		if (overlay->header("Settings")) {
+			if (overlay->comboBox("Object type", &models.objectIndex, models.names)) {
+				updateUniformBuffers();
+				buildCommandBuffers();
+			}
+			if (overlay->sliderInt("Grid size", &gridSize, 1, 10)) {
+				buildCommandBuffers();
+			}
+			std::vector<std::string> cullModeNames = { "None", "Front", "Back", "Back and front" };
+			if (overlay->comboBox("Cull mode", &cullMode, cullModeNames)) {
+				preparePipelines();
+				buildCommandBuffers();
+			}
+			if (overlay->checkBox("Blending", &blending)) {
+				preparePipelines();
+				buildCommandBuffers();
+			}
+			if (deviceFeatures.fillModeNonSolid) {
+				if (overlay->checkBox("Wireframe", &wireframe)) {
+					preparePipelines();
+					buildCommandBuffers();
+				}
+			}
+			if (deviceFeatures.tessellationShader) {
+				if (overlay->checkBox("Tessellation", &tessellation)) {
+					preparePipelines();
+					buildCommandBuffers();
+				}
+			}
+			if (overlay->checkBox("Discard", &discard)) {
+				preparePipelines();
+				buildCommandBuffers();
+			}
+		}
+		if (!pipelineStats.empty()) {
+			if (overlay->header("Pipeline statistics")) {
+				for (auto i = 0; i < pipelineStats.size(); i++) {
+					std::string caption = pipelineStatNames[i] + ": %d";
+					overlay->text(caption.c_str(), pipelineStats[i]);
+				}
+			}
+		}
+	}
+
+};
+
+VULKAN_EXAMPLE_MAIN()
\ No newline at end of file
diff --git a/external/Vulkan/examples/pushconstants/pushconstants.cpp b/external/Vulkan/examples/pushconstants/pushconstants.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5005698a8f9af91b92a97227bf409a221c9c6303
--- /dev/null
+++ b/external/Vulkan/examples/pushconstants/pushconstants.cpp
@@ -0,0 +1,295 @@
+/*
+* Vulkan Example - Push constants example (small shader block accessed outside of uniforms for fast updates)
+*
+* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+/*
+* Summary:
+* Using push constants it's possible to pass a small bit of static data to a shader, which is stored in the command buffer stat
+* This is perfect for passing e.g. static per-object data or parameters without the need for descriptor sets
+* The sample uses these to push different static parameters for rendering multiple objects
+*/
+
+#include "vulkanexamplebase.h"
+#include "VulkanglTFModel.h"
+
+#define VERTEX_BUFFER_BIND_ID 0
+#define ENABLE_VALIDATION false
+
+float rnd()
+{
+	return ((float) rand() / (RAND_MAX));
+}
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	vkglTF::Model model;
+
+	// Color and position data for each sphere is uploaded using push constants
+	struct SpherePushConstantData {
+		glm::vec4 color;
+		glm::vec4 position;
+	};
+	std::array<SpherePushConstantData, 16> spheres;
+
+	vks::Buffer uniformBuffer;
+
+	struct UBOMatrices {
+		glm::mat4 projection;
+		glm::mat4 model;
+		glm::mat4 view;
+	} uboMatrices;
+
+	VkPipeline pipeline;
+	VkPipelineLayout pipelineLayout;
+	VkDescriptorSet descriptorSet;
+	VkDescriptorSetLayout descriptorSetLayout;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Push constants";
+		camera.type = Camera::CameraType::lookat;
+		camera.setPosition(glm::vec3(0.0f, 0.0f, -10.0f));
+		camera.setRotation(glm::vec3(0.0, 0.0f, 0.0f));
+		camera.setPerspective(60.0f, (float) width / (float) height, 0.1f, 256.0f);
+		camera.setRotationSpeed(0.5f);
+	}
+
+	~VulkanExample()
+	{
+		// Clean up used Vulkan resources
+		// Note : Inherited destructor cleans up resources stored in base class
+		vkDestroyPipeline(device, pipeline, nullptr);
+
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+
+		uniformBuffer.destroy();
+	}
+
+	void setupSpheres()
+	{
+		// Setup random colors and fixed positions for every spheres in the scene
+		for (uint32_t i = 0; i < spheres.size(); i++) {
+			spheres[i].color = glm::vec4(rnd(), rnd(), rnd(), 1.0f);
+			const float rad = glm::radians(i * 360.0f / static_cast<uint32_t>(spheres.size()));
+			spheres[i].position = glm::vec4(glm::vec3(sin(rad), cos(rad), 0.0f) * 3.5f, 1.0f);
+		}
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		clearValues[0].color = defaultClearColor;
+		clearValues[1].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.offset.x = 0;
+		renderPassBeginInfo.renderArea.offset.y = 0;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		renderPassBeginInfo.clearValueCount = 2;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			// Set target frame buffer
+			renderPassBeginInfo.framebuffer = frameBuffers[i];
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+			VkRect2D scissor = vks::initializers::rect2D(width,	height,	0,	0);
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr);
+
+			// [POI] Render the spheres passing color and position via push constants
+			uint32_t spherecount = static_cast<uint32_t>(spheres.size());
+			for (uint32_t j = 0; j < spherecount; j++) {
+				// [POI] Pass static sphere data as push constants
+				vkCmdPushConstants(
+				    drawCmdBuffers[i],
+				    pipelineLayout,
+				    VK_SHADER_STAGE_VERTEX_BIT,
+				    0,
+				    sizeof(SpherePushConstantData),
+				    &spheres[j]);
+				model.draw(drawCmdBuffers[i]);
+			}
+
+			drawUI(drawCmdBuffers[i]);
+
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void loadAssets()
+	{
+		const uint32_t glTFLoadingFlags = vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::PreMultiplyVertexColors | vkglTF::FileLoadingFlags::FlipY;
+		model.loadFromFile(getAssetPath() + "models/sphere.gltf", vulkanDevice, queue, glTFLoadingFlags);
+	}
+
+	void setupDescriptorPool()
+	{
+		std::vector<VkDescriptorPoolSize> poolSizes = {
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1),
+		};
+		VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 2);
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+	}
+
+	void setupDescriptorSetLayout()
+	{
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			// Binding 0 : Vertex shader uniform buffer
+			vks::initializers::descriptorSetLayoutBinding(
+				VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+				VK_SHADER_STAGE_VERTEX_BIT,
+				0),
+		};
+
+		VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout));
+
+		// Define the push constant range used by the pipeline layout
+		// Note that the spec only requires a minimum of 128 bytes, so for passing larger blocks of data you'd use UBOs or SSBOs
+		VkPushConstantRange pushConstantRange{};
+		pushConstantRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
+		pushConstantRange.offset = 0;
+		pushConstantRange.size = sizeof(SpherePushConstantData);
+
+		VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1);
+		pipelineLayoutCreateInfo.pushConstantRangeCount  = 1;
+		pipelineLayoutCreateInfo.pPushConstantRanges = &pushConstantRange;
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout));
+	}
+
+	void setupDescriptorSet()
+	{
+		VkDescriptorSetAllocateInfo allocInfo =
+			vks::initializers::descriptorSetAllocateInfo(
+				descriptorPool,
+				&descriptorSetLayout,
+				1);
+
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet));
+
+		// Binding 0 : Vertex shader uniform buffer
+		VkWriteDescriptorSet writeDescriptorSet =
+			vks::initializers::writeDescriptorSet(
+				descriptorSet,
+				VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+				0,
+				&uniformBuffer.descriptor);
+
+		vkUpdateDescriptorSets(device, 1, &writeDescriptorSet, 0, NULL);
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+		VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
+		std::vector<VkDynamicState> dynamicStateEnables = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
+		VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass, 0);
+		pipelineCI.pInputAssemblyState = &inputAssemblyState;
+		pipelineCI.pRasterizationState = &rasterizationState;
+		pipelineCI.pColorBlendState = &colorBlendState;
+		pipelineCI.pMultisampleState = &multisampleState;
+		pipelineCI.pViewportState = &viewportState;
+		pipelineCI.pDepthStencilState = &depthStencilState;
+		pipelineCI.pDynamicState = &dynamicState;
+		pipelineCI.stageCount = static_cast<uint32_t>(shaderStages.size());
+		pipelineCI.pStages = shaderStages.data();
+		pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::Color});
+		shaderStages[0] = loadShader(getShadersPath() + "pushconstants/pushconstants.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "pushconstants/pushconstants.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipeline));
+	}
+
+	void prepareUniformBuffers()
+	{
+		// Vertex shader uniform buffer block
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffer,
+		    sizeof(uboMatrices)));
+
+		// Map persistent
+		VK_CHECK_RESULT(uniformBuffer.map());
+
+		updateUniformBuffers();
+	}
+
+	void updateUniformBuffers()
+	{
+		uboMatrices.projection = camera.matrices.perspective;
+		uboMatrices.view = camera.matrices.view;
+		uboMatrices.model = glm::scale(glm::mat4(1.0f), glm::vec3(0.5f));
+		memcpy(uniformBuffer.mapped, &uboMatrices, sizeof(uboMatrices));
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+
+		// Command buffer to be submitted to the queue
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+
+		// Submit to queue
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+
+		VulkanExampleBase::submitFrame();
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		loadAssets();
+		setupSpheres();
+		prepareUniformBuffers();
+		setupDescriptorSetLayout();
+		preparePipelines();
+		setupDescriptorPool();
+		setupDescriptorSet();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+		if (!camera.updated)
+		{
+			updateUniformBuffers();
+		}
+	}
+};
+
+VULKAN_EXAMPLE_MAIN()
\ No newline at end of file
diff --git a/external/Vulkan/examples/pushdescriptors/pushdescriptors.cpp b/external/Vulkan/examples/pushdescriptors/pushdescriptors.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..080f5b41d710c871699b620e11e271625b29e2eb
--- /dev/null
+++ b/external/Vulkan/examples/pushdescriptors/pushdescriptors.cpp
@@ -0,0 +1,351 @@
+/*
+* Vulkan Example - Push descriptors
+*
+* Note: Requires a device that supports the VK_KHR_push_descriptor extension
+*
+* Push descriptors apply the push constants concept to descriptor sets. So instead of creating
+* per-model descriptor sets (along with a pool for each descriptor type) for rendering multiple objects,
+* this example uses push descriptors to pass descriptor sets for per-model textures and matrices
+* at command buffer creation time.
+*
+* Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkanexamplebase.h"
+#include "VulkanglTFModel.h"
+
+#define ENABLE_VALIDATION false
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	bool animate = true;
+
+	PFN_vkCmdPushDescriptorSetKHR vkCmdPushDescriptorSetKHR;
+	VkPhysicalDevicePushDescriptorPropertiesKHR pushDescriptorProps{};
+
+	struct Cube {
+		vks::Texture2D texture;
+		vks::Buffer uniformBuffer;
+		glm::vec3 rotation;
+		glm::mat4 modelMat;
+	};
+	std::array<Cube, 2> cubes;
+
+	vkglTF::Model model;
+
+	struct UniformBuffers {
+		vks::Buffer scene;
+	} uniformBuffers;
+
+	struct UboScene {
+		glm::mat4 projection;
+		glm::mat4 view;
+	} uboScene;
+
+	VkPipeline pipeline;
+	VkPipelineLayout pipelineLayout;
+	VkDescriptorSetLayout descriptorSetLayout;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Push descriptors";
+		camera.type = Camera::CameraType::lookat;
+		camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 512.0f);
+		camera.setRotation(glm::vec3(0.0f, 0.0f, 0.0f));
+		camera.setTranslation(glm::vec3(0.0f, 0.0f, -5.0f));
+		// Enable extension required for push descriptors
+		enabledInstanceExtensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
+		enabledDeviceExtensions.push_back(VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
+	}
+
+	~VulkanExample()
+	{
+		vkDestroyPipeline(device, pipeline, nullptr);
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+		for (auto cube : cubes) {
+			cube.uniformBuffer.destroy();
+			cube.texture.destroy();
+		}
+		uniformBuffers.scene.destroy();
+	}
+
+	virtual void getEnabledFeatures()
+	{
+		if (deviceFeatures.samplerAnisotropy) {
+			enabledFeatures.samplerAnisotropy = VK_TRUE;
+		};
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		clearValues[0].color = defaultClearColor;
+		clearValues[1].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.offset.x = 0;
+		renderPassBeginInfo.renderArea.offset.y = 0;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		renderPassBeginInfo.clearValueCount = 2;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i) {
+			renderPassBeginInfo.framebuffer = frameBuffers[i];
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
+
+			VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+			VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+			model.bindBuffers(drawCmdBuffers[i]);
+
+			// Render two cubes using different descriptor sets using push descriptors
+			for (auto cube : cubes) {
+
+				// Instead of preparing the descriptor sets up-front, using push descriptors we can set (push) them inside of a command buffer
+				// This allows a more dynamic approach without the need to create descriptor sets for each model
+				// Note: dstSet for each descriptor set write is left at zero as this is ignored when using push descriptors
+
+				std::array<VkWriteDescriptorSet, 3> writeDescriptorSets{};
+
+				// Scene matrices
+				writeDescriptorSets[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+				writeDescriptorSets[0].dstSet = 0;
+				writeDescriptorSets[0].dstBinding = 0;
+				writeDescriptorSets[0].descriptorCount = 1;
+				writeDescriptorSets[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+				writeDescriptorSets[0].pBufferInfo = &uniformBuffers.scene.descriptor;
+
+				// Model matrices
+				writeDescriptorSets[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+				writeDescriptorSets[1].dstSet = 0;
+				writeDescriptorSets[1].dstBinding = 1;
+				writeDescriptorSets[1].descriptorCount = 1;
+				writeDescriptorSets[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+				writeDescriptorSets[1].pBufferInfo = &cube.uniformBuffer.descriptor;
+
+				// Texture
+				writeDescriptorSets[2].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+				writeDescriptorSets[2].dstSet = 0;
+				writeDescriptorSets[2].dstBinding = 2;
+				writeDescriptorSets[2].descriptorCount = 1;
+				writeDescriptorSets[2].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+				writeDescriptorSets[2].pImageInfo = &cube.texture.descriptor;
+
+				vkCmdPushDescriptorSetKHR(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 3, writeDescriptorSets.data());
+
+				model.draw(drawCmdBuffers[i]);
+			}
+
+			drawUI(drawCmdBuffers[i]);
+
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void loadAssets()
+	{
+		const uint32_t glTFLoadingFlags = vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::PreMultiplyVertexColors | vkglTF::FileLoadingFlags::FlipY;
+		model.loadFromFile(getAssetPath() + "models/cube.gltf", vulkanDevice, queue, glTFLoadingFlags);
+		cubes[0].texture.loadFromFile(getAssetPath() + "textures/crate01_color_height_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue);
+		cubes[1].texture.loadFromFile(getAssetPath() + "textures/crate02_color_height_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue);
+	}
+
+	void setupDescriptorSetLayout()
+	{
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0),
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 1),
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 2),
+		};
+
+		VkDescriptorSetLayoutCreateInfo descriptorLayoutCI{};
+		descriptorLayoutCI.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
+		// Setting this flag tells the descriptor set layouts that no actual descriptor sets are allocated but instead pushed at command buffer creation time
+		descriptorLayoutCI.flags = VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT_KHR;
+		descriptorLayoutCI.bindingCount = static_cast<uint32_t>(setLayoutBindings.size());
+		descriptorLayoutCI.pBindings = setLayoutBindings.data();
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayoutCI, nullptr, &descriptorSetLayout));
+
+		VkPipelineLayoutCreateInfo pipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1);
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelineLayout));
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyStateCI = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationStateCI = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendStateCI = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilStateCI = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportStateCI = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+		VkPipelineMultisampleStateCreateInfo multisampleStateCI = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
+		const std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
+		VkPipelineDynamicStateCreateInfo dynamicStateCI = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables.data(), static_cast<uint32_t>(dynamicStateEnables.size()),0);
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+
+		VkGraphicsPipelineCreateInfo pipelineCI  = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass, 0);
+		pipelineCI.pInputAssemblyState = &inputAssemblyStateCI;
+		pipelineCI.pRasterizationState = &rasterizationStateCI;
+		pipelineCI.pColorBlendState = &colorBlendStateCI;
+		pipelineCI.pMultisampleState = &multisampleStateCI;
+		pipelineCI.pViewportState = &viewportStateCI;
+		pipelineCI.pDepthStencilState = &depthStencilStateCI;
+		pipelineCI.pDynamicState = &dynamicStateCI;
+		pipelineCI.stageCount = static_cast<uint32_t>(shaderStages.size());
+		pipelineCI.pStages = shaderStages.data();
+		pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::UV, vkglTF::VertexComponent::Color});
+
+		shaderStages[0] = loadShader(getShadersPath() + "pushdescriptors/cube.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "pushdescriptors/cube.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipeline));
+	}
+
+	void prepareUniformBuffers()
+	{
+		// Vertex shader scene uniform buffer block
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.scene,
+			sizeof(UboScene)));
+		VK_CHECK_RESULT(uniformBuffers.scene.map());
+
+		// Vertex shader cube model uniform buffer blocks
+		for (auto& cube : cubes) {
+			VK_CHECK_RESULT(vulkanDevice->createBuffer(
+				VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+				VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+				&cube.uniformBuffer,
+				sizeof(glm::mat4)));
+			VK_CHECK_RESULT(cube.uniformBuffer.map());
+		}
+
+		updateUniformBuffers();
+		updateCubeUniformBuffers();
+	}
+
+	void updateUniformBuffers()
+	{
+		uboScene.projection = camera.matrices.perspective;
+		uboScene.view = camera.matrices.view;
+		memcpy(uniformBuffers.scene.mapped, &uboScene, sizeof(UboScene));
+	}
+
+	void updateCubeUniformBuffers()
+	{
+		cubes[0].modelMat = glm::translate(glm::mat4(1.0f), glm::vec3(-2.0f, 0.0f, 0.0f));
+		cubes[1].modelMat = glm::translate(glm::mat4(1.0f), glm::vec3( 1.5f, 0.5f, 0.0f));
+
+		for (auto& cube : cubes) {
+			cube.modelMat = glm::rotate(cube.modelMat, glm::radians(cube.rotation.x), glm::vec3(1.0f, 0.0f, 0.0f));
+			cube.modelMat = glm::rotate(cube.modelMat, glm::radians(cube.rotation.y), glm::vec3(0.0f, 1.0f, 0.0f));
+			cube.modelMat = glm::rotate(cube.modelMat, glm::radians(cube.rotation.z), glm::vec3(0.0f, 0.0f, 1.0f));
+			cube.modelMat = glm::scale(cube.modelMat, glm::vec3(0.25f));
+			memcpy(cube.uniformBuffer.mapped, &cube.modelMat, sizeof(glm::mat4));
+		}
+
+		if (animate) {
+			cubes[0].rotation.x += 2.5f * frameTimer;
+			if (cubes[0].rotation.x > 360.0f)
+				cubes[0].rotation.x -= 360.0f;
+			cubes[1].rotation.y += 2.0f * frameTimer;
+			if (cubes[1].rotation.x > 360.0f)
+				cubes[1].rotation.x -= 360.0f;
+		}
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+		VulkanExampleBase::submitFrame();
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+
+		/*
+			Extension specific functions
+		*/
+
+		// The push descriptor update function is part of an extension so it has to be manually loaded
+		vkCmdPushDescriptorSetKHR = (PFN_vkCmdPushDescriptorSetKHR)vkGetDeviceProcAddr(device, "vkCmdPushDescriptorSetKHR");
+		if (!vkCmdPushDescriptorSetKHR) {
+			vks::tools::exitFatal("Could not get a valid function pointer for vkCmdPushDescriptorSetKHR", -1);
+		}
+
+		// Get device push descriptor properties (to display them)
+		PFN_vkGetPhysicalDeviceProperties2KHR vkGetPhysicalDeviceProperties2KHR = reinterpret_cast<PFN_vkGetPhysicalDeviceProperties2KHR>(vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceProperties2KHR"));
+		if (!vkGetPhysicalDeviceProperties2KHR) {
+			vks::tools::exitFatal("Could not get a valid function pointer for vkGetPhysicalDeviceProperties2KHR", -1);
+		}
+		VkPhysicalDeviceProperties2KHR deviceProps2{};
+		pushDescriptorProps.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PUSH_DESCRIPTOR_PROPERTIES_KHR;
+		deviceProps2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR;
+		deviceProps2.pNext = &pushDescriptorProps;
+		vkGetPhysicalDeviceProperties2KHR(physicalDevice, &deviceProps2);
+
+		/*
+			End of extension specific functions
+		*/
+
+		loadAssets();
+		prepareUniformBuffers();
+		setupDescriptorSetLayout();
+		preparePipelines();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+		if (animate) {
+			cubes[0].rotation.x += 2.5f * frameTimer;
+			if (cubes[0].rotation.x > 360.0f)
+				cubes[0].rotation.x -= 360.0f;
+			cubes[1].rotation.y += 2.0f * frameTimer;
+			if (cubes[1].rotation.x > 360.0f)
+				cubes[1].rotation.x -= 360.0f;
+			updateCubeUniformBuffers();
+		}
+		if (camera.updated) {
+			updateUniformBuffers();
+		}
+	}
+
+	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
+	{
+		if (overlay->header("Settings")) {
+			overlay->checkBox("Animate", &animate);
+		}
+		if (overlay->header("Device properties")) {
+			overlay->text("maxPushDescriptors: %d", pushDescriptorProps.maxPushDescriptors);
+		}
+	}
+};
+
+VULKAN_EXAMPLE_MAIN()
\ No newline at end of file
diff --git a/external/Vulkan/examples/radialblur/radialblur.cpp b/external/Vulkan/examples/radialblur/radialblur.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..22daed6010c2f15bee9af59bb879b3dbf38cf8a9
--- /dev/null
+++ b/external/Vulkan/examples/radialblur/radialblur.cpp
@@ -0,0 +1,630 @@
+/*
+* Vulkan Example - Fullscreen radial blur (Single pass offscreen effect)
+*
+* Copyright (C) Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkanexamplebase.h"
+#include "VulkanglTFModel.h"
+
+#define ENABLE_VALIDATION false
+
+// Offscreen frame buffer properties
+#define FB_DIM 512
+#define FB_COLOR_FORMAT VK_FORMAT_R8G8B8A8_UNORM
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	bool blur = true;
+	bool displayTexture = false;
+
+	struct {
+		vks::Texture2D gradient;
+	} textures;
+
+	vkglTF::Model scene;
+
+	struct {
+		vks::Buffer scene;
+		vks::Buffer blurParams;
+	} uniformBuffers;
+
+	struct UboVS {
+		glm::mat4 projection;
+		glm::mat4 modelView;
+		float gradientPos = 0.0f;
+	} uboScene;
+
+	struct UboBlurParams {
+		float radialBlurScale = 0.35f;
+		float radialBlurStrength = 0.75f;
+		glm::vec2 radialOrigin = glm::vec2(0.5f, 0.5f);
+	} uboBlurParams;
+
+	struct {
+		VkPipeline radialBlur;
+		VkPipeline colorPass;
+		VkPipeline phongPass;
+		VkPipeline offscreenDisplay;
+	} pipelines;
+
+	struct {
+		VkPipelineLayout radialBlur;
+		VkPipelineLayout scene;
+	} pipelineLayouts;
+
+	struct {
+		VkDescriptorSet scene;
+		VkDescriptorSet radialBlur;
+	} descriptorSets;
+
+	struct {
+		VkDescriptorSetLayout scene;
+		VkDescriptorSetLayout radialBlur;
+	} descriptorSetLayouts;
+
+	// Framebuffer for offscreen rendering
+	struct FrameBufferAttachment {
+		VkImage image;
+		VkDeviceMemory mem;
+		VkImageView view;
+	};
+	struct OffscreenPass {
+		int32_t width, height;
+		VkFramebuffer frameBuffer;
+		FrameBufferAttachment color, depth;
+		VkRenderPass renderPass;
+		VkSampler sampler;
+		VkDescriptorImageInfo descriptor;
+	} offscreenPass;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Full screen radial blur effect";
+		camera.type = Camera::CameraType::lookat;
+		camera.setPosition(glm::vec3(0.0f, 0.0f, -17.5f));
+		camera.setRotation(glm::vec3(-16.25f, -28.75f, 0.0f));
+		camera.setPerspective(45.0f, (float)width / (float)height, 1.0f, 256.0f);
+		timerSpeed *= 0.5f;
+	}
+
+	~VulkanExample()
+	{
+		// Clean up used Vulkan resources
+		// Note : Inherited destructor cleans up resources stored in base class
+
+		// Frame buffer
+
+		// Color attachment
+		vkDestroyImageView(device, offscreenPass.color.view, nullptr);
+		vkDestroyImage(device, offscreenPass.color.image, nullptr);
+		vkFreeMemory(device, offscreenPass.color.mem, nullptr);
+
+		// Depth attachment
+		vkDestroyImageView(device, offscreenPass.depth.view, nullptr);
+		vkDestroyImage(device, offscreenPass.depth.image, nullptr);
+		vkFreeMemory(device, offscreenPass.depth.mem, nullptr);
+
+		vkDestroyRenderPass(device, offscreenPass.renderPass, nullptr);
+		vkDestroySampler(device, offscreenPass.sampler, nullptr);
+		vkDestroyFramebuffer(device, offscreenPass.frameBuffer, nullptr);
+
+		vkDestroyPipeline(device, pipelines.radialBlur, nullptr);
+		vkDestroyPipeline(device, pipelines.phongPass, nullptr);
+		vkDestroyPipeline(device, pipelines.colorPass, nullptr);
+		vkDestroyPipeline(device, pipelines.offscreenDisplay, nullptr);
+
+		vkDestroyPipelineLayout(device, pipelineLayouts.radialBlur, nullptr);
+		vkDestroyPipelineLayout(device, pipelineLayouts.scene, nullptr);
+
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.scene, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.radialBlur, nullptr);
+
+		uniformBuffers.scene.destroy();
+		uniformBuffers.blurParams.destroy();
+
+		textures.gradient.destroy();
+	}
+
+	// Setup the offscreen framebuffer for rendering the blurred scene
+	// The color attachment of this framebuffer will then be used to sample frame in the fragment shader of the final pass
+	void prepareOffscreen()
+	{
+		offscreenPass.width = FB_DIM;
+		offscreenPass.height = FB_DIM;
+
+		// Find a suitable depth format
+		VkFormat fbDepthFormat;
+		VkBool32 validDepthFormat = vks::tools::getSupportedDepthFormat(physicalDevice, &fbDepthFormat);
+		assert(validDepthFormat);
+
+		// Color attachment
+		VkImageCreateInfo image = vks::initializers::imageCreateInfo();
+		image.imageType = VK_IMAGE_TYPE_2D;
+		image.format = FB_COLOR_FORMAT;
+		image.extent.width = offscreenPass.width;
+		image.extent.height = offscreenPass.height;
+		image.extent.depth = 1;
+		image.mipLevels = 1;
+		image.arrayLayers = 1;
+		image.samples = VK_SAMPLE_COUNT_1_BIT;
+		image.tiling = VK_IMAGE_TILING_OPTIMAL;
+		// We will sample directly from the color attachment
+		image.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
+
+		VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo();
+		VkMemoryRequirements memReqs;
+
+		VK_CHECK_RESULT(vkCreateImage(device, &image, nullptr, &offscreenPass.color.image));
+		vkGetImageMemoryRequirements(device, offscreenPass.color.image, &memReqs);
+		memAlloc.allocationSize = memReqs.size;
+		memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+		VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &offscreenPass.color.mem));
+		VK_CHECK_RESULT(vkBindImageMemory(device, offscreenPass.color.image, offscreenPass.color.mem, 0));
+
+		VkImageViewCreateInfo colorImageView = vks::initializers::imageViewCreateInfo();
+		colorImageView.viewType = VK_IMAGE_VIEW_TYPE_2D;
+		colorImageView.format = FB_COLOR_FORMAT;
+		colorImageView.subresourceRange = {};
+		colorImageView.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+		colorImageView.subresourceRange.baseMipLevel = 0;
+		colorImageView.subresourceRange.levelCount = 1;
+		colorImageView.subresourceRange.baseArrayLayer = 0;
+		colorImageView.subresourceRange.layerCount = 1;
+		colorImageView.image = offscreenPass.color.image;
+		VK_CHECK_RESULT(vkCreateImageView(device, &colorImageView, nullptr, &offscreenPass.color.view));
+
+		// Create sampler to sample from the attachment in the fragment shader
+		VkSamplerCreateInfo samplerInfo = vks::initializers::samplerCreateInfo();
+		samplerInfo.magFilter = VK_FILTER_LINEAR;
+		samplerInfo.minFilter = VK_FILTER_LINEAR;
+		samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+		samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+		samplerInfo.addressModeV = samplerInfo.addressModeU;
+		samplerInfo.addressModeW = samplerInfo.addressModeU;
+		samplerInfo.mipLodBias = 0.0f;
+		samplerInfo.maxAnisotropy = 1.0f;
+		samplerInfo.minLod = 0.0f;
+		samplerInfo.maxLod = 1.0f;
+		samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
+		VK_CHECK_RESULT(vkCreateSampler(device, &samplerInfo, nullptr, &offscreenPass.sampler));
+
+		// Depth stencil attachment
+		image.format = fbDepthFormat;
+		image.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
+
+		VK_CHECK_RESULT(vkCreateImage(device, &image, nullptr, &offscreenPass.depth.image));
+		vkGetImageMemoryRequirements(device, offscreenPass.depth.image, &memReqs);
+		memAlloc.allocationSize = memReqs.size;
+		memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+		VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &offscreenPass.depth.mem));
+		VK_CHECK_RESULT(vkBindImageMemory(device, offscreenPass.depth.image, offscreenPass.depth.mem, 0));
+
+		VkImageViewCreateInfo depthStencilView = vks::initializers::imageViewCreateInfo();
+		depthStencilView.viewType = VK_IMAGE_VIEW_TYPE_2D;
+		depthStencilView.format = fbDepthFormat;
+		depthStencilView.flags = 0;
+		depthStencilView.subresourceRange = {};
+		depthStencilView.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
+		depthStencilView.subresourceRange.baseMipLevel = 0;
+		depthStencilView.subresourceRange.levelCount = 1;
+		depthStencilView.subresourceRange.baseArrayLayer = 0;
+		depthStencilView.subresourceRange.layerCount = 1;
+		depthStencilView.image = offscreenPass.depth.image;
+		VK_CHECK_RESULT(vkCreateImageView(device, &depthStencilView, nullptr, &offscreenPass.depth.view));
+
+		// Create a separate render pass for the offscreen rendering as it may differ from the one used for scene rendering
+
+		std::array<VkAttachmentDescription, 2> attchmentDescriptions = {};
+		// Color attachment
+		attchmentDescriptions[0].format = FB_COLOR_FORMAT;
+		attchmentDescriptions[0].samples = VK_SAMPLE_COUNT_1_BIT;
+		attchmentDescriptions[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+		attchmentDescriptions[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+		attchmentDescriptions[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+		attchmentDescriptions[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+		attchmentDescriptions[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+		attchmentDescriptions[0].finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+		// Depth attachment
+		attchmentDescriptions[1].format = fbDepthFormat;
+		attchmentDescriptions[1].samples = VK_SAMPLE_COUNT_1_BIT;
+		attchmentDescriptions[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+		attchmentDescriptions[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+		attchmentDescriptions[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+		attchmentDescriptions[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+		attchmentDescriptions[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+		attchmentDescriptions[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+
+		VkAttachmentReference colorReference = { 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
+		VkAttachmentReference depthReference = { 1, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL };
+
+		VkSubpassDescription subpassDescription = {};
+		subpassDescription.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+		subpassDescription.colorAttachmentCount = 1;
+		subpassDescription.pColorAttachments = &colorReference;
+		subpassDescription.pDepthStencilAttachment = &depthReference;
+
+		// Use subpass dependencies for layout transitions
+		std::array<VkSubpassDependency, 2> dependencies;
+
+		dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
+		dependencies[0].dstSubpass = 0;
+		dependencies[0].srcStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
+		dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+		dependencies[0].srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
+		dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+		dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+		dependencies[1].srcSubpass = 0;
+		dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
+		dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+		dependencies[1].dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
+		dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+		dependencies[1].dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
+		dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+		// Create the actual renderpass
+		VkRenderPassCreateInfo renderPassInfo = {};
+		renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
+		renderPassInfo.attachmentCount = static_cast<uint32_t>(attchmentDescriptions.size());
+		renderPassInfo.pAttachments = attchmentDescriptions.data();
+		renderPassInfo.subpassCount = 1;
+		renderPassInfo.pSubpasses = &subpassDescription;
+		renderPassInfo.dependencyCount = static_cast<uint32_t>(dependencies.size());
+		renderPassInfo.pDependencies = dependencies.data();
+
+		VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassInfo, nullptr, &offscreenPass.renderPass));
+
+		VkImageView attachments[2];
+		attachments[0] = offscreenPass.color.view;
+		attachments[1] = offscreenPass.depth.view;
+
+		VkFramebufferCreateInfo fbufCreateInfo = vks::initializers::framebufferCreateInfo();
+		fbufCreateInfo.renderPass = offscreenPass.renderPass;
+		fbufCreateInfo.attachmentCount = 2;
+		fbufCreateInfo.pAttachments = attachments;
+		fbufCreateInfo.width = offscreenPass.width;
+		fbufCreateInfo.height = offscreenPass.height;
+		fbufCreateInfo.layers = 1;
+
+		VK_CHECK_RESULT(vkCreateFramebuffer(device, &fbufCreateInfo, nullptr, &offscreenPass.frameBuffer));
+
+		// Fill a descriptor for later use in a descriptor set
+		offscreenPass.descriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+		offscreenPass.descriptor.imageView = offscreenPass.color.view;
+		offscreenPass.descriptor.sampler = offscreenPass.sampler;
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		VkViewport viewport;
+		VkRect2D scissor;
+		VkDeviceSize offsets[1] = { 0 };
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			/*
+				First render pass: Offscreen rendering
+			*/
+			{
+				clearValues[0].color = { { 0.0f, 0.0f, 0.0f, 0.0f } };
+				clearValues[1].depthStencil = { 1.0f, 0 };
+
+				VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+				renderPassBeginInfo.renderPass = offscreenPass.renderPass;
+				renderPassBeginInfo.framebuffer = offscreenPass.frameBuffer;
+				renderPassBeginInfo.renderArea.extent.width = offscreenPass.width;
+				renderPassBeginInfo.renderArea.extent.height = offscreenPass.height;
+				renderPassBeginInfo.clearValueCount = 2;
+				renderPassBeginInfo.pClearValues = clearValues;
+
+				viewport = vks::initializers::viewport((float)offscreenPass.width, (float)offscreenPass.height, 0.0f, 1.0f);
+				vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+				scissor = vks::initializers::rect2D(offscreenPass.width, offscreenPass.height, 0, 0);
+				vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+				vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+				vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayouts.scene, 0, 1, &descriptorSets.scene, 0, NULL);
+				vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.colorPass);
+				scene.draw(drawCmdBuffers[i]);
+				vkCmdEndRenderPass(drawCmdBuffers[i]);
+			}
+
+			/*
+				Note: Explicit synchronization is not required between the render pass, as this is done implicit via sub pass dependencies
+			*/
+
+			/*
+				Second render pass: Scene rendering with applied radial blur
+			*/
+			{
+				clearValues[0].color = defaultClearColor;
+				clearValues[1].depthStencil = { 1.0f, 0 };
+
+				VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+				renderPassBeginInfo.renderPass = renderPass;
+				renderPassBeginInfo.framebuffer = frameBuffers[i];
+				renderPassBeginInfo.renderArea.extent.width = width;
+				renderPassBeginInfo.renderArea.extent.height = height;
+				renderPassBeginInfo.clearValueCount = 2;
+				renderPassBeginInfo.pClearValues = clearValues;
+
+				vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+				viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+				vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+				scissor = vks::initializers::rect2D(width, height, 0, 0);
+				vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+				// 3D scene
+				vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayouts.scene, 0, 1, &descriptorSets.scene, 0, NULL);
+				vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.phongPass);
+				scene.draw(drawCmdBuffers[i]);
+
+				// Fullscreen triangle (clipped to a quad) with radial blur
+				if (blur)
+				{
+					vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayouts.radialBlur, 0, 1, &descriptorSets.radialBlur, 0, NULL);
+					vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, (displayTexture) ? pipelines.offscreenDisplay : pipelines.radialBlur);
+					vkCmdDraw(drawCmdBuffers[i], 3, 1, 0, 0);
+				}
+
+				drawUI(drawCmdBuffers[i]);
+
+				vkCmdEndRenderPass(drawCmdBuffers[i]);
+			}
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void loadAssets()
+	{
+		scene.loadFromFile(getAssetPath() + "models/glowsphere.gltf", vulkanDevice, queue, vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::PreMultiplyVertexColors | vkglTF::FileLoadingFlags::FlipY);
+		textures.gradient.loadFromFile(getAssetPath() + "textures/particle_gradient_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue);
+	}
+
+	void setupDescriptorPool()
+	{
+		// Example uses three ubos and one image sampler
+		std::vector<VkDescriptorPoolSize> poolSizes =
+		{
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 4),
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 6)
+		};
+
+		VkDescriptorPoolCreateInfo descriptorPoolInfo =
+			vks::initializers::descriptorPoolCreateInfo(
+				poolSizes.size(),
+				poolSizes.data(),
+				2);
+
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+	}
+
+	void setupDescriptorSetLayout()
+	{
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings;
+		VkDescriptorSetLayoutCreateInfo descriptorLayout;
+		VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo;
+
+		// Scene rendering
+		setLayoutBindings = {
+			// Binding 0: Vertex shader uniform buffer
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0),
+			// Binding 1: Fragment shader image sampler
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1),
+			// Binding 2: Fragment shader uniform buffer
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_FRAGMENT_BIT, 2)
+		};
+		descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings.data(), static_cast<uint32_t>(setLayoutBindings.size()));
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayouts.scene));
+		pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayouts.scene, 1);
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayouts.scene));
+
+		// Fullscreen radial blur
+		setLayoutBindings = {
+			// Binding 0 : Vertex shader uniform buffer
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_FRAGMENT_BIT, 0),
+			// Binding 0: Fragment shader image sampler
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1)
+		};
+		descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings.data(), static_cast<uint32_t>(setLayoutBindings.size()));
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayouts.radialBlur));
+		pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayouts.radialBlur, 1);
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayouts.radialBlur));
+	}
+
+	void setupDescriptorSet()
+	{
+		VkDescriptorSetAllocateInfo descriptorSetAllocInfo;
+
+		// Scene rendering
+		descriptorSetAllocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.scene, 1);
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &descriptorSetAllocInfo, &descriptorSets.scene));
+
+		std::vector<VkWriteDescriptorSet> offScreenWriteDescriptorSets = {
+			// Binding 0: Vertex shader uniform buffer
+			vks::initializers::writeDescriptorSet(descriptorSets.scene, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.scene.descriptor),
+			// Binding 1: Color gradient sampler
+			vks::initializers::writeDescriptorSet(descriptorSets.scene, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &textures.gradient.descriptor),
+		};
+		vkUpdateDescriptorSets(device, offScreenWriteDescriptorSets.size(), offScreenWriteDescriptorSets.data(), 0, NULL);
+
+		// Fullscreen radial blur
+		descriptorSetAllocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.radialBlur, 1);
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &descriptorSetAllocInfo, &descriptorSets.radialBlur));
+
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets = {
+			// Binding 0: Vertex shader uniform buffer
+			vks::initializers::writeDescriptorSet(descriptorSets.radialBlur, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.blurParams.descriptor),
+			// Binding 0: Fragment shader texture sampler
+			vks::initializers::writeDescriptorSet(descriptorSets.radialBlur, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,	1, &offscreenPass.descriptor),
+		};
+
+		vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, NULL);
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyStateCI = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationStateCI = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendStateCI = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilStateCI = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportStateCI = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+		VkPipelineMultisampleStateCreateInfo multisampleStateCI = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
+		std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
+		VkPipelineDynamicStateCreateInfo dynamicStateCI = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables.data(), dynamicStateEnables.size(), 0);
+
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayouts.radialBlur, renderPass, 0);
+		pipelineCI.pInputAssemblyState = &inputAssemblyStateCI;
+		pipelineCI.pRasterizationState = &rasterizationStateCI;
+		pipelineCI.pColorBlendState = &colorBlendStateCI;
+		pipelineCI.pMultisampleState = &multisampleStateCI;
+		pipelineCI.pViewportState = &viewportStateCI;
+		pipelineCI.pDepthStencilState = &depthStencilStateCI;
+		pipelineCI.pDynamicState = &dynamicStateCI;
+		pipelineCI.stageCount = shaderStages.size();
+		pipelineCI.pStages = shaderStages.data();
+
+		// Radial blur pipeline
+		shaderStages[0] = loadShader(getShadersPath() + "radialblur/radialblur.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "radialblur/radialblur.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		// Empty vertex input state
+		VkPipelineVertexInputStateCreateInfo emptyInputState = vks::initializers::pipelineVertexInputStateCreateInfo();
+		pipelineCI.pVertexInputState = &emptyInputState;
+		pipelineCI.layout = pipelineLayouts.radialBlur;
+		// Additive blending
+		blendAttachmentState.colorWriteMask = 0xF;
+		blendAttachmentState.blendEnable = VK_TRUE;
+		blendAttachmentState.colorBlendOp = VK_BLEND_OP_ADD;
+		blendAttachmentState.srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
+		blendAttachmentState.dstColorBlendFactor = VK_BLEND_FACTOR_ONE;
+		blendAttachmentState.alphaBlendOp = VK_BLEND_OP_ADD;
+		blendAttachmentState.srcAlphaBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
+		blendAttachmentState.dstAlphaBlendFactor = VK_BLEND_FACTOR_DST_ALPHA;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.radialBlur));
+
+		// No blending (for debug display)
+		blendAttachmentState.blendEnable = VK_FALSE;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.offscreenDisplay));
+
+		// Phong pass
+		pipelineCI.layout = pipelineLayouts.scene;
+		shaderStages[0] = loadShader(getShadersPath() + "radialblur/phongpass.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "radialblur/phongpass.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		blendAttachmentState.blendEnable = VK_FALSE;
+		depthStencilStateCI.depthWriteEnable = VK_TRUE;
+		pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position, vkglTF::VertexComponent::UV, vkglTF::VertexComponent::Color, vkglTF::VertexComponent::Normal });;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.phongPass));
+
+		// Color only pass (offscreen blur base)
+		shaderStages[0] = loadShader(getShadersPath() + "radialblur/colorpass.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "radialblur/colorpass.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		pipelineCI.renderPass = offscreenPass.renderPass;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.colorPass));
+	}
+
+	// Prepare and initialize uniform buffer containing shader uniforms
+	void prepareUniformBuffers()
+	{
+		// Phong and color pass vertex shader uniform buffer
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.scene,
+			sizeof(uboScene)));
+
+		// Fullscreen radial blur parameters
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.blurParams,
+			sizeof(uboBlurParams),
+			&uboBlurParams));
+
+		// Map persistent
+		VK_CHECK_RESULT(uniformBuffers.scene.map());
+		VK_CHECK_RESULT(uniformBuffers.blurParams.map());
+
+		updateUniformBuffersScene();
+	}
+
+	// Update uniform buffers for rendering the 3D scene
+	void updateUniformBuffersScene()
+	{
+		uboScene.projection = glm::perspective(glm::radians(45.0f), (float)width / (float)height, 1.0f, 256.0f);
+		camera.setRotation(camera.rotation + glm::vec3(0.0f, frameTimer * 10.0f, 0.0f));
+		uboScene.projection = camera.matrices.perspective;
+		uboScene.modelView = camera.matrices.view;
+		// split into model view for separating rotation
+		if (!paused)
+		{
+			uboScene.gradientPos += frameTimer * 0.1f;
+		}
+
+		memcpy(uniformBuffers.scene.mapped, &uboScene, sizeof(uboScene));
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+
+		// Command buffer to be submitted to the queue
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+
+		// Submit to queue
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+
+		VulkanExampleBase::submitFrame();
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		loadAssets();
+		prepareOffscreen();
+		prepareUniformBuffers();
+		setupDescriptorSetLayout();
+		preparePipelines();
+		setupDescriptorPool();
+		setupDescriptorSet();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+		if (!paused || camera.updated)
+			updateUniformBuffersScene();
+	}
+
+	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
+	{
+		if (overlay->header("Settings")) {
+			if (overlay->checkBox("Radial blur", &blur)) {
+				buildCommandBuffers();
+			}
+			if (overlay->checkBox("Display render target", &displayTexture)) {
+				buildCommandBuffers();
+			}
+		}
+	}
+};
+
+VULKAN_EXAMPLE_MAIN()
diff --git a/external/Vulkan/examples/rayquery/rayquery.cpp b/external/Vulkan/examples/rayquery/rayquery.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a432fb3a7ce915e887d92482c6a87f934da62367
--- /dev/null
+++ b/external/Vulkan/examples/rayquery/rayquery.cpp
@@ -0,0 +1,480 @@
+/*
+* Vulkan Example - Using ray queries for hardware accelerated ray tracing queries in a fragment shader
+*
+* Copyright (C) 2020 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkanexamplebase.h"
+#include "VulkanglTFModel.h"
+#include "VulkanRaytracingSample.h"
+
+#define ENABLE_VALIDATION false
+
+class VulkanExample : public VulkanRaytracingSample
+{
+public:
+	glm::vec3 lightPos = glm::vec3();
+
+	struct UniformData {
+		glm::mat4 projection;
+		glm::mat4 view;
+		glm::mat4 model;
+		glm::vec3 lightPos;
+	} uniformData;
+	vks::Buffer ubo;
+
+	vkglTF::Model scene;
+
+	VkPipeline pipeline;
+	VkPipelineLayout pipelineLayout;
+
+	VkDescriptorSet descriptorSet;
+	VkDescriptorSetLayout descriptorSetLayout;
+
+	VulkanRaytracingSample::AccelerationStructure bottomLevelAS{};
+	VulkanRaytracingSample::AccelerationStructure topLevelAS{};
+
+	VkPhysicalDeviceRayQueryFeaturesKHR enabledRayQueryFeatures{};
+
+	VulkanExample() : VulkanRaytracingSample()
+	{
+		title = "Ray queries for ray traced shadows";
+		camera.type = Camera::CameraType::lookat;
+		timerSpeed *= 0.25f;
+		camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 512.0f);
+		camera.setRotation(glm::vec3(0.0f, 0.0f, 0.0f));
+		camera.setTranslation(glm::vec3(0.0f, 3.0f, -10.0f));
+		rayQueryOnly = true;
+		enableExtensions();
+		enabledDeviceExtensions.push_back(VK_KHR_RAY_QUERY_EXTENSION_NAME);
+	}
+
+	~VulkanExample()
+	{
+		vkDestroyPipeline(device, pipeline, nullptr);
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+		ubo.destroy();
+		deleteAccelerationStructure(bottomLevelAS);
+		deleteAccelerationStructure(topLevelAS);
+	}
+
+	/*
+		Create the bottom level acceleration structure contains the scene's actual geometry (vertices, triangles)
+	*/
+	void createBottomLevelAccelerationStructure()
+	{
+		VkDeviceOrHostAddressConstKHR vertexBufferDeviceAddress{};
+		VkDeviceOrHostAddressConstKHR indexBufferDeviceAddress{};
+
+		vertexBufferDeviceAddress.deviceAddress = getBufferDeviceAddress(scene.vertices.buffer);
+		indexBufferDeviceAddress.deviceAddress = getBufferDeviceAddress(scene.indices.buffer);
+
+		uint32_t numTriangles = static_cast<uint32_t>(scene.indices.count) / 3;
+		uint32_t maxVertex = scene.vertices.count;
+
+		// Build
+		VkAccelerationStructureGeometryKHR accelerationStructureGeometry = vks::initializers::accelerationStructureGeometryKHR();
+		accelerationStructureGeometry.flags = VK_GEOMETRY_OPAQUE_BIT_KHR;
+		accelerationStructureGeometry.geometryType = VK_GEOMETRY_TYPE_TRIANGLES_KHR;
+		accelerationStructureGeometry.geometry.triangles.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR;
+		accelerationStructureGeometry.geometry.triangles.vertexFormat = VK_FORMAT_R32G32B32_SFLOAT;
+		accelerationStructureGeometry.geometry.triangles.vertexData = vertexBufferDeviceAddress;
+		accelerationStructureGeometry.geometry.triangles.maxVertex = maxVertex;
+		accelerationStructureGeometry.geometry.triangles.vertexStride = sizeof(vkglTF::Vertex);
+		accelerationStructureGeometry.geometry.triangles.indexType = VK_INDEX_TYPE_UINT32;
+		accelerationStructureGeometry.geometry.triangles.indexData = indexBufferDeviceAddress;
+		accelerationStructureGeometry.geometry.triangles.transformData.deviceAddress = 0;
+		accelerationStructureGeometry.geometry.triangles.transformData.hostAddress = nullptr;
+
+		// Get size info
+		VkAccelerationStructureBuildGeometryInfoKHR accelerationStructureBuildGeometryInfo = vks::initializers::accelerationStructureBuildGeometryInfoKHR();
+		accelerationStructureBuildGeometryInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR;
+		accelerationStructureBuildGeometryInfo.flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR;
+		accelerationStructureBuildGeometryInfo.geometryCount = 1;
+		accelerationStructureBuildGeometryInfo.pGeometries = &accelerationStructureGeometry;
+
+		VkAccelerationStructureBuildSizesInfoKHR accelerationStructureBuildSizesInfo = vks::initializers::accelerationStructureBuildSizesInfoKHR();
+		vkGetAccelerationStructureBuildSizesKHR(
+			device,
+			VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR,
+			&accelerationStructureBuildGeometryInfo,
+			&numTriangles,
+			&accelerationStructureBuildSizesInfo);
+
+		createAccelerationStructure(bottomLevelAS, VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR, accelerationStructureBuildSizesInfo);
+
+		// Create a small scratch buffer used during build of the bottom level acceleration structure
+		ScratchBuffer scratchBuffer = createScratchBuffer(accelerationStructureBuildSizesInfo.buildScratchSize);
+
+		VkAccelerationStructureBuildGeometryInfoKHR accelerationBuildGeometryInfo = vks::initializers::accelerationStructureBuildGeometryInfoKHR();
+		accelerationBuildGeometryInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR;
+		accelerationBuildGeometryInfo.flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR;
+		accelerationBuildGeometryInfo.mode = VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR;
+		accelerationBuildGeometryInfo.dstAccelerationStructure = bottomLevelAS.handle;
+		accelerationBuildGeometryInfo.geometryCount = 1;
+		accelerationBuildGeometryInfo.pGeometries = &accelerationStructureGeometry;
+		accelerationBuildGeometryInfo.scratchData.deviceAddress = scratchBuffer.deviceAddress;
+
+		VkAccelerationStructureBuildRangeInfoKHR accelerationStructureBuildRangeInfo{};
+		accelerationStructureBuildRangeInfo.primitiveCount = numTriangles;
+		accelerationStructureBuildRangeInfo.primitiveOffset = 0;
+		accelerationStructureBuildRangeInfo.firstVertex = 0;
+		accelerationStructureBuildRangeInfo.transformOffset = 0;
+		std::vector<VkAccelerationStructureBuildRangeInfoKHR*> accelerationBuildStructureRangeInfos = { &accelerationStructureBuildRangeInfo };
+
+		// Build the acceleration structure on the device via a one-time command buffer submission
+		// Some implementations may support acceleration structure building on the host (VkPhysicalDeviceAccelerationStructureFeaturesKHR->accelerationStructureHostCommands), but we prefer device builds
+		VkCommandBuffer commandBuffer = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+		vkCmdBuildAccelerationStructuresKHR(
+			commandBuffer,
+			1,
+			&accelerationBuildGeometryInfo,
+			accelerationBuildStructureRangeInfos.data());
+		vulkanDevice->flushCommandBuffer(commandBuffer, queue);
+
+		deleteScratchBuffer(scratchBuffer);
+	}
+
+	/*
+		The top level acceleration structure contains the scene's object instances
+	*/
+	void createTopLevelAccelerationStructure()
+	{
+		VkTransformMatrixKHR transformMatrix = {
+			1.0f, 0.0f, 0.0f, 0.0f,
+			0.0f, 1.0f, 0.0f, 0.0f,
+			0.0f, 0.0f, 1.0f, 0.0f };
+
+		VkAccelerationStructureInstanceKHR instance{};
+		instance.transform = transformMatrix;
+		instance.instanceCustomIndex = 0;
+		instance.mask = 0xFF;
+		instance.instanceShaderBindingTableRecordOffset = 0;
+		instance.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR;
+		instance.accelerationStructureReference = bottomLevelAS.deviceAddress;
+
+		// Buffer for instance data
+		vks::Buffer instancesBuffer;
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&instancesBuffer,
+			sizeof(VkAccelerationStructureInstanceKHR),
+			&instance));
+
+		VkDeviceOrHostAddressConstKHR instanceDataDeviceAddress{};
+		instanceDataDeviceAddress.deviceAddress = getBufferDeviceAddress(instancesBuffer.buffer);
+
+		VkAccelerationStructureGeometryKHR accelerationStructureGeometry = vks::initializers::accelerationStructureGeometryKHR();
+		accelerationStructureGeometry.geometryType = VK_GEOMETRY_TYPE_INSTANCES_KHR;
+		accelerationStructureGeometry.flags = VK_GEOMETRY_OPAQUE_BIT_KHR;
+		accelerationStructureGeometry.geometry.instances.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_INSTANCES_DATA_KHR;
+		accelerationStructureGeometry.geometry.instances.arrayOfPointers = VK_FALSE;
+		accelerationStructureGeometry.geometry.instances.data = instanceDataDeviceAddress;
+
+		// Get size info
+		VkAccelerationStructureBuildGeometryInfoKHR accelerationStructureBuildGeometryInfo = vks::initializers::accelerationStructureBuildGeometryInfoKHR();
+		accelerationStructureBuildGeometryInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR;
+		accelerationStructureBuildGeometryInfo.flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR;
+		accelerationStructureBuildGeometryInfo.geometryCount = 1;
+		accelerationStructureBuildGeometryInfo.pGeometries = &accelerationStructureGeometry;
+
+		uint32_t primitive_count = 1;
+
+		VkAccelerationStructureBuildSizesInfoKHR accelerationStructureBuildSizesInfo = vks::initializers::accelerationStructureBuildSizesInfoKHR();
+		vkGetAccelerationStructureBuildSizesKHR(
+			device,
+			VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR,
+			&accelerationStructureBuildGeometryInfo,
+			&primitive_count,
+			&accelerationStructureBuildSizesInfo);
+
+		createAccelerationStructure(topLevelAS, VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR, accelerationStructureBuildSizesInfo);
+
+		// Create a small scratch buffer used during build of the top level acceleration structure
+		ScratchBuffer scratchBuffer = createScratchBuffer(accelerationStructureBuildSizesInfo.buildScratchSize);
+
+		VkAccelerationStructureBuildGeometryInfoKHR accelerationBuildGeometryInfo = vks::initializers::accelerationStructureBuildGeometryInfoKHR();
+		accelerationBuildGeometryInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR;
+		accelerationBuildGeometryInfo.flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR;
+		accelerationBuildGeometryInfo.mode = VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR;
+		accelerationBuildGeometryInfo.dstAccelerationStructure = topLevelAS.handle;
+		accelerationBuildGeometryInfo.geometryCount = 1;
+		accelerationBuildGeometryInfo.pGeometries = &accelerationStructureGeometry;
+		accelerationBuildGeometryInfo.scratchData.deviceAddress = scratchBuffer.deviceAddress;
+
+		VkAccelerationStructureBuildRangeInfoKHR accelerationStructureBuildRangeInfo{};
+		accelerationStructureBuildRangeInfo.primitiveCount = 1;
+		accelerationStructureBuildRangeInfo.primitiveOffset = 0;
+		accelerationStructureBuildRangeInfo.firstVertex = 0;
+		accelerationStructureBuildRangeInfo.transformOffset = 0;
+		std::vector<VkAccelerationStructureBuildRangeInfoKHR*> accelerationBuildStructureRangeInfos = { &accelerationStructureBuildRangeInfo };
+
+		// Build the acceleration structure on the device via a one-time command buffer submission
+		// Some implementations may support acceleration structure building on the host (VkPhysicalDeviceAccelerationStructureFeaturesKHR->accelerationStructureHostCommands), but we prefer device builds
+		VkCommandBuffer commandBuffer = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+		vkCmdBuildAccelerationStructuresKHR(
+			commandBuffer,
+			1,
+			&accelerationBuildGeometryInfo,
+			accelerationBuildStructureRangeInfos.data());
+		vulkanDevice->flushCommandBuffer(commandBuffer, queue);
+
+		deleteScratchBuffer(scratchBuffer);
+		instancesBuffer.destroy();
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		VkViewport viewport;
+		VkRect2D scissor;
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			/*
+				Note: Explicit synchronization is not required between the render pass, as this is done implicit via sub pass dependencies
+			*/
+
+			/*
+				Second pass: Scene rendering with applied shadow map
+			*/
+
+			clearValues[0].color = { { 0.0f, 0.0f, 0.2f, 1.0f } };;
+			clearValues[1].depthStencil = { 1.0f, 0 };
+
+			VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+			renderPassBeginInfo.renderPass = renderPass;
+			renderPassBeginInfo.framebuffer = frameBuffers[i];
+			renderPassBeginInfo.renderArea.extent.width = width;
+			renderPassBeginInfo.renderArea.extent.height = height;
+			renderPassBeginInfo.clearValueCount = 2;
+			renderPassBeginInfo.pClearValues = clearValues;
+
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+			scissor = vks::initializers::rect2D(width, height, 0, 0);
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+			// 3D scene
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr);
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
+			scene.draw(drawCmdBuffers[i]);
+
+			VulkanExampleBase::drawUI(drawCmdBuffers[i]);
+
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void loadAssets()
+	{
+		vkglTF::memoryPropertyFlags = VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
+		const uint32_t glTFLoadingFlags = vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::PreMultiplyVertexColors | vkglTF::FileLoadingFlags::FlipY;
+		scene.loadFromFile(getAssetPath() + "models/vulkanscene_shadow.gltf", vulkanDevice, queue, glTFLoadingFlags);
+	}
+
+	void setupDescriptorPool()
+	{
+		std::vector<VkDescriptorPoolSize> poolSizes = {
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 3),
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 3),
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 3)
+		};
+		VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 3);
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+	}
+
+	void setupDescriptorSetLayout()
+	{
+		// Shared pipeline layout for all pipelines used in this sample
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			// Binding 0 : Vertex shader uniform buffer
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0),
+			// Binding 1 : Fragment shader image sampler (shadow map)
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1),
+			// Binding 2: Acceleration structure
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, VK_SHADER_STAGE_FRAGMENT_BIT, 2),
+		};
+		VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout));
+		VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1);
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayout));
+	}
+
+	void setupDescriptorSets()
+	{
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets;
+
+		// Debug display
+		VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1);
+
+		vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, nullptr);
+
+		// Scene rendering with shadow map applied
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet));
+		writeDescriptorSets = {
+			// Binding 0 : Vertex shader uniform buffer
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &ubo.descriptor)
+		};
+
+		VkWriteDescriptorSetAccelerationStructureKHR descriptorAccelerationStructureInfo = vks::initializers::writeDescriptorSetAccelerationStructureKHR();
+		descriptorAccelerationStructureInfo.accelerationStructureCount = 1;
+		descriptorAccelerationStructureInfo.pAccelerationStructures = &topLevelAS.handle;
+
+		VkWriteDescriptorSet accelerationStructureWrite{};
+		accelerationStructureWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+		// The specialized acceleration structure descriptor has to be chained
+		accelerationStructureWrite.pNext = &descriptorAccelerationStructureInfo;
+		accelerationStructureWrite.dstSet = descriptorSet;
+		accelerationStructureWrite.dstBinding = 2;
+		accelerationStructureWrite.descriptorCount = 1;
+		accelerationStructureWrite.descriptorType = VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR;
+
+		writeDescriptorSets.push_back(accelerationStructureWrite);
+		vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, nullptr);
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyStateCI = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationStateCI = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendStateCI = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilStateCI = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportStateCI = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+		VkPipelineMultisampleStateCreateInfo multisampleStateCI = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
+		std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
+		VkPipelineDynamicStateCreateInfo dynamicStateCI = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables.data(), dynamicStateEnables.size(), 0);
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass, 0);
+		pipelineCI.pInputAssemblyState = &inputAssemblyStateCI;
+		pipelineCI.pRasterizationState = &rasterizationStateCI;
+		pipelineCI.pColorBlendState = &colorBlendStateCI;
+		pipelineCI.pMultisampleState = &multisampleStateCI;
+		pipelineCI.pViewportState = &viewportStateCI;
+		pipelineCI.pDepthStencilState = &depthStencilStateCI;
+		pipelineCI.pDynamicState = &dynamicStateCI;
+		pipelineCI.stageCount = shaderStages.size();
+		pipelineCI.pStages = shaderStages.data();
+
+		// Scene rendering with ray traced shadows applied
+		pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position, vkglTF::VertexComponent::UV, vkglTF::VertexComponent::Color, vkglTF::VertexComponent::Normal });
+		rasterizationStateCI.cullMode = VK_CULL_MODE_BACK_BIT;
+		shaderStages[0] = loadShader(getShadersPath() + "rayquery/scene.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "rayquery/scene.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipeline));
+	}
+
+
+	// Prepare and initialize uniform buffer containing shader uniforms
+	void prepareUniformBuffers()
+	{
+		// Scene vertex shader uniform buffer block
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&ubo,
+			sizeof(UniformData)));
+
+		// Map persistent
+		VK_CHECK_RESULT(ubo.map());
+
+		updateLight();
+		updateUniformBuffers();
+	}
+
+	void updateLight()
+	{
+		// Animate the light source
+		lightPos.x = cos(glm::radians(timer * 360.0f)) * 40.0f;
+		lightPos.y = -50.0f + sin(glm::radians(timer * 360.0f)) * 20.0f;
+		lightPos.z = 25.0f + sin(glm::radians(timer * 360.0f)) * 5.0f;
+	}
+
+	void updateUniformBuffers()
+	{
+		uniformData.projection = camera.matrices.perspective;
+		uniformData.view = camera.matrices.view;
+		uniformData.model = glm::mat4(1.0f);
+		uniformData.lightPos = lightPos;
+		memcpy(ubo.mapped, &uniformData, sizeof(UniformData));
+	}
+
+	void getEnabledFeatures()
+	{
+		// Enable features required for ray tracing using feature chaining via pNext		
+		enabledBufferDeviceAddresFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES;
+		enabledBufferDeviceAddresFeatures.bufferDeviceAddress = VK_TRUE;
+
+		enabledRayTracingPipelineFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_FEATURES_KHR;
+		enabledRayTracingPipelineFeatures.rayTracingPipeline = VK_TRUE;
+		enabledRayTracingPipelineFeatures.pNext = &enabledBufferDeviceAddresFeatures;
+
+		enabledAccelerationStructureFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_FEATURES_KHR;
+		enabledAccelerationStructureFeatures.accelerationStructure = VK_TRUE;
+		enabledAccelerationStructureFeatures.pNext = &enabledRayTracingPipelineFeatures;
+
+		enabledRayQueryFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_QUERY_FEATURES_KHR;
+		enabledRayQueryFeatures.rayQuery = VK_TRUE;
+		enabledRayQueryFeatures.pNext = &enabledAccelerationStructureFeatures;
+
+		deviceCreatepNextChain = &enabledRayQueryFeatures;
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+
+		// Command buffer to be submitted to the queue
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+
+		// Submit to queue
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+
+		VulkanExampleBase::submitFrame();
+	}
+
+	void prepare()
+	{
+		VulkanRaytracingSample::prepare();
+		loadAssets();
+		prepareUniformBuffers();
+		setupDescriptorSetLayout();
+		preparePipelines();
+		createBottomLevelAccelerationStructure();
+		createTopLevelAccelerationStructure();
+		setupDescriptorPool();
+		setupDescriptorSets();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+		if (!paused || camera.updated)
+		{
+			updateLight();
+			updateUniformBuffers();
+		}
+	}
+};
+
+VULKAN_EXAMPLE_MAIN()
diff --git a/external/Vulkan/examples/raytracingbasic/raytracingbasic.cpp b/external/Vulkan/examples/raytracingbasic/raytracingbasic.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f4e4b38abc787fdfa84b2f92acf388f98d8136b0
--- /dev/null
+++ b/external/Vulkan/examples/raytracingbasic/raytracingbasic.cpp
@@ -0,0 +1,908 @@
+/*
+* Vulkan Example - Basic hardware accelerated ray tracing example
+*
+* Copyright (C) 2019-2020 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkanexamplebase.h"
+
+// Holds data for a ray tracing scratch buffer that is used as a temporary storage
+struct RayTracingScratchBuffer
+{
+	uint64_t deviceAddress = 0;
+	VkBuffer handle = VK_NULL_HANDLE;
+	VkDeviceMemory memory = VK_NULL_HANDLE;
+};
+
+// Ray tracing acceleration structure
+struct AccelerationStructure {
+	VkAccelerationStructureKHR handle;
+	uint64_t deviceAddress = 0;
+	VkDeviceMemory memory;
+	VkBuffer buffer;
+};
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	PFN_vkGetBufferDeviceAddressKHR vkGetBufferDeviceAddressKHR;
+	PFN_vkCreateAccelerationStructureKHR vkCreateAccelerationStructureKHR;
+	PFN_vkDestroyAccelerationStructureKHR vkDestroyAccelerationStructureKHR;
+	PFN_vkGetAccelerationStructureBuildSizesKHR vkGetAccelerationStructureBuildSizesKHR;
+	PFN_vkGetAccelerationStructureDeviceAddressKHR vkGetAccelerationStructureDeviceAddressKHR;
+	PFN_vkCmdBuildAccelerationStructuresKHR vkCmdBuildAccelerationStructuresKHR;
+	PFN_vkBuildAccelerationStructuresKHR vkBuildAccelerationStructuresKHR;
+	PFN_vkCmdTraceRaysKHR vkCmdTraceRaysKHR;
+	PFN_vkGetRayTracingShaderGroupHandlesKHR vkGetRayTracingShaderGroupHandlesKHR;
+	PFN_vkCreateRayTracingPipelinesKHR vkCreateRayTracingPipelinesKHR;
+
+	VkPhysicalDeviceRayTracingPipelinePropertiesKHR  rayTracingPipelineProperties{};
+	VkPhysicalDeviceAccelerationStructureFeaturesKHR accelerationStructureFeatures{};
+
+	VkPhysicalDeviceBufferDeviceAddressFeatures enabledBufferDeviceAddresFeatures{};
+	VkPhysicalDeviceRayTracingPipelineFeaturesKHR enabledRayTracingPipelineFeatures{};
+	VkPhysicalDeviceAccelerationStructureFeaturesKHR enabledAccelerationStructureFeatures{};
+
+	AccelerationStructure bottomLevelAS{};
+	AccelerationStructure topLevelAS{};
+
+	vks::Buffer vertexBuffer;
+	vks::Buffer indexBuffer;
+	uint32_t indexCount;
+	vks::Buffer transformBuffer;
+	std::vector<VkRayTracingShaderGroupCreateInfoKHR> shaderGroups{};
+	vks::Buffer raygenShaderBindingTable;
+	vks::Buffer missShaderBindingTable;
+	vks::Buffer hitShaderBindingTable;
+
+	struct StorageImage {
+		VkDeviceMemory memory;
+		VkImage image;
+		VkImageView view;
+		VkFormat format;
+	} storageImage;
+
+	struct UniformData {
+		glm::mat4 viewInverse;
+		glm::mat4 projInverse;
+	} uniformData;
+	vks::Buffer ubo;
+
+	VkPipeline pipeline;
+	VkPipelineLayout pipelineLayout;
+	VkDescriptorSet descriptorSet;
+	VkDescriptorSetLayout descriptorSetLayout;
+
+	VulkanExample() : VulkanExampleBase()
+	{
+		title = "Ray tracing basic";
+		settings.overlay = false;
+		camera.type = Camera::CameraType::lookat;
+		camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 512.0f);
+		camera.setRotation(glm::vec3(0.0f, 0.0f, 0.0f));
+		camera.setTranslation(glm::vec3(0.0f, 0.0f, -2.5f));
+
+		// Require Vulkan 1.1
+		apiVersion = VK_API_VERSION_1_1;
+
+		// Ray tracing related extensions required by this sample
+		enabledDeviceExtensions.push_back(VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME);
+		enabledDeviceExtensions.push_back(VK_KHR_RAY_TRACING_PIPELINE_EXTENSION_NAME);
+
+		// Required by VK_KHR_acceleration_structure
+		enabledDeviceExtensions.push_back(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME);
+		enabledDeviceExtensions.push_back(VK_KHR_DEFERRED_HOST_OPERATIONS_EXTENSION_NAME);
+		enabledDeviceExtensions.push_back(VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME);
+
+		// Required for VK_KHR_ray_tracing_pipeline
+		enabledDeviceExtensions.push_back(VK_KHR_SPIRV_1_4_EXTENSION_NAME);
+
+		// Required by VK_KHR_spirv_1_4
+		enabledDeviceExtensions.push_back(VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME);
+	}
+
+	~VulkanExample()
+	{
+		vkDestroyPipeline(device, pipeline, nullptr);
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+		vkDestroyImageView(device, storageImage.view, nullptr);
+		vkDestroyImage(device, storageImage.image, nullptr);
+		vkFreeMemory(device, storageImage.memory, nullptr);
+		vkFreeMemory(device, bottomLevelAS.memory, nullptr);
+		vkDestroyBuffer(device, bottomLevelAS.buffer, nullptr);
+		vkDestroyAccelerationStructureKHR(device, bottomLevelAS.handle, nullptr);
+		vkFreeMemory(device, topLevelAS.memory, nullptr);
+		vkDestroyBuffer(device, topLevelAS.buffer, nullptr);
+		vkDestroyAccelerationStructureKHR(device, topLevelAS.handle, nullptr);
+		vertexBuffer.destroy();
+		indexBuffer.destroy();
+		transformBuffer.destroy();
+		raygenShaderBindingTable.destroy();
+		missShaderBindingTable.destroy();
+		hitShaderBindingTable.destroy();
+		ubo.destroy();
+	}
+
+	/*	
+		Create a scratch buffer to hold temporary data for a ray tracing acceleration structure
+	*/
+	RayTracingScratchBuffer createScratchBuffer(VkDeviceSize size)
+	{
+		RayTracingScratchBuffer scratchBuffer{};
+
+		VkBufferCreateInfo bufferCreateInfo{};
+		bufferCreateInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+		bufferCreateInfo.size = size;
+		bufferCreateInfo.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT;
+		VK_CHECK_RESULT(vkCreateBuffer(device, &bufferCreateInfo, nullptr, &scratchBuffer.handle));
+
+		VkMemoryRequirements memoryRequirements{};
+		vkGetBufferMemoryRequirements(device, scratchBuffer.handle, &memoryRequirements);
+
+		VkMemoryAllocateFlagsInfo memoryAllocateFlagsInfo{};
+		memoryAllocateFlagsInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO;
+		memoryAllocateFlagsInfo.flags = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT_KHR;
+
+		VkMemoryAllocateInfo memoryAllocateInfo = {};
+		memoryAllocateInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+		memoryAllocateInfo.pNext = &memoryAllocateFlagsInfo;
+		memoryAllocateInfo.allocationSize = memoryRequirements.size;
+		memoryAllocateInfo.memoryTypeIndex = vulkanDevice->getMemoryType(memoryRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+		VK_CHECK_RESULT(vkAllocateMemory(device, &memoryAllocateInfo, nullptr, &scratchBuffer.memory));
+		VK_CHECK_RESULT(vkBindBufferMemory(device, scratchBuffer.handle, scratchBuffer.memory, 0));
+
+		VkBufferDeviceAddressInfoKHR bufferDeviceAddressInfo{};
+		bufferDeviceAddressInfo.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO;
+		bufferDeviceAddressInfo.buffer = scratchBuffer.handle;
+		scratchBuffer.deviceAddress = vkGetBufferDeviceAddressKHR(device, &bufferDeviceAddressInfo);
+
+		return scratchBuffer;
+	}
+
+	void deleteScratchBuffer(RayTracingScratchBuffer& scratchBuffer) 
+	{
+		if (scratchBuffer.memory != VK_NULL_HANDLE) {
+			vkFreeMemory(device, scratchBuffer.memory, nullptr);
+		}
+		if (scratchBuffer.handle != VK_NULL_HANDLE) {
+			vkDestroyBuffer(device, scratchBuffer.handle, nullptr);
+		}
+	}
+
+	void createAccelerationStructureBuffer(AccelerationStructure &accelerationStructure, VkAccelerationStructureBuildSizesInfoKHR buildSizeInfo)
+	{
+		VkBufferCreateInfo bufferCreateInfo{};
+		bufferCreateInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+		bufferCreateInfo.size = buildSizeInfo.accelerationStructureSize;
+		bufferCreateInfo.usage = VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT;
+		VK_CHECK_RESULT(vkCreateBuffer(device, &bufferCreateInfo, nullptr, &accelerationStructure.buffer));
+		VkMemoryRequirements memoryRequirements{};
+		vkGetBufferMemoryRequirements(device, accelerationStructure.buffer, &memoryRequirements);
+		VkMemoryAllocateFlagsInfo memoryAllocateFlagsInfo{};
+		memoryAllocateFlagsInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO;
+		memoryAllocateFlagsInfo.flags = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT_KHR;
+		VkMemoryAllocateInfo memoryAllocateInfo{};
+		memoryAllocateInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+		memoryAllocateInfo.pNext = &memoryAllocateFlagsInfo;
+		memoryAllocateInfo.allocationSize = memoryRequirements.size;
+		memoryAllocateInfo.memoryTypeIndex = vulkanDevice->getMemoryType(memoryRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+		VK_CHECK_RESULT(vkAllocateMemory(device, &memoryAllocateInfo, nullptr, &accelerationStructure.memory));
+		VK_CHECK_RESULT(vkBindBufferMemory(device, accelerationStructure.buffer, accelerationStructure.memory, 0));
+	}
+
+
+	/*
+		Gets the device address from a buffer that's required for some of the buffers used for ray tracing
+	*/
+	uint64_t getBufferDeviceAddress(VkBuffer buffer)
+	{
+		VkBufferDeviceAddressInfoKHR bufferDeviceAI{};
+		bufferDeviceAI.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO;
+		bufferDeviceAI.buffer = buffer;
+		return vkGetBufferDeviceAddressKHR(device, &bufferDeviceAI);
+	}
+
+	/*
+		Set up a storage image that the ray generation shader will be writing to
+	*/
+	void createStorageImage()
+	{
+		VkImageCreateInfo image = vks::initializers::imageCreateInfo();
+		image.imageType = VK_IMAGE_TYPE_2D;
+		image.format = swapChain.colorFormat;
+		image.extent.width = width;
+		image.extent.height = height;
+		image.extent.depth = 1;
+		image.mipLevels = 1;
+		image.arrayLayers = 1;
+		image.samples = VK_SAMPLE_COUNT_1_BIT;
+		image.tiling = VK_IMAGE_TILING_OPTIMAL;
+		image.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_STORAGE_BIT;
+		image.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+		VK_CHECK_RESULT(vkCreateImage(device, &image, nullptr, &storageImage.image));
+
+		VkMemoryRequirements memReqs;
+		vkGetImageMemoryRequirements(device, storageImage.image, &memReqs);
+		VkMemoryAllocateInfo memoryAllocateInfo = vks::initializers::memoryAllocateInfo();
+		memoryAllocateInfo.allocationSize = memReqs.size;
+		memoryAllocateInfo.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+		VK_CHECK_RESULT(vkAllocateMemory(device, &memoryAllocateInfo, nullptr, &storageImage.memory));
+		VK_CHECK_RESULT(vkBindImageMemory(device, storageImage.image, storageImage.memory, 0));
+
+		VkImageViewCreateInfo colorImageView = vks::initializers::imageViewCreateInfo();
+		colorImageView.viewType = VK_IMAGE_VIEW_TYPE_2D;
+		colorImageView.format = swapChain.colorFormat;
+		colorImageView.subresourceRange = {};
+		colorImageView.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+		colorImageView.subresourceRange.baseMipLevel = 0;
+		colorImageView.subresourceRange.levelCount = 1;
+		colorImageView.subresourceRange.baseArrayLayer = 0;
+		colorImageView.subresourceRange.layerCount = 1;
+		colorImageView.image = storageImage.image;
+		VK_CHECK_RESULT(vkCreateImageView(device, &colorImageView, nullptr, &storageImage.view));
+
+		VkCommandBuffer cmdBuffer = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+		vks::tools::setImageLayout(cmdBuffer, storageImage.image,
+			VK_IMAGE_LAYOUT_UNDEFINED,
+			VK_IMAGE_LAYOUT_GENERAL,
+			{ VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 });
+		vulkanDevice->flushCommandBuffer(cmdBuffer, queue);
+	}
+
+	/*
+		Create the bottom level acceleration structure contains the scene's actual geometry (vertices, triangles)
+	*/
+	void createBottomLevelAccelerationStructure()
+	{
+		// Setup vertices for a single triangle
+		struct Vertex {
+			float pos[3];
+		};
+		std::vector<Vertex> vertices = {
+			{ {  1.0f,  1.0f, 0.0f } },
+			{ { -1.0f,  1.0f, 0.0f } },
+			{ {  0.0f, -1.0f, 0.0f } }
+		};
+
+		// Setup indices
+		std::vector<uint32_t> indices = { 0, 1, 2 };
+		indexCount = static_cast<uint32_t>(indices.size());
+
+		// Setup identity transform matrix
+		VkTransformMatrixKHR transformMatrix = {
+			1.0f, 0.0f, 0.0f, 0.0f,
+			0.0f, 1.0f, 0.0f, 0.0f,
+			0.0f, 0.0f, 1.0f, 0.0f
+		};
+
+		// Create buffers
+		// For the sake of simplicity we won't stage the vertex data to the GPU memory
+		// Vertex buffer
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&vertexBuffer,
+			vertices.size() * sizeof(Vertex),
+			vertices.data()));
+		// Index buffer
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&indexBuffer,
+			indices.size() * sizeof(uint32_t),
+			indices.data()));
+		// Transform buffer
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&transformBuffer,
+			sizeof(VkTransformMatrixKHR),
+			&transformMatrix));
+
+		VkDeviceOrHostAddressConstKHR vertexBufferDeviceAddress{};
+		VkDeviceOrHostAddressConstKHR indexBufferDeviceAddress{};
+		VkDeviceOrHostAddressConstKHR transformBufferDeviceAddress{};
+		
+		vertexBufferDeviceAddress.deviceAddress = getBufferDeviceAddress(vertexBuffer.buffer);
+		indexBufferDeviceAddress.deviceAddress = getBufferDeviceAddress(indexBuffer.buffer);
+		transformBufferDeviceAddress.deviceAddress = getBufferDeviceAddress(transformBuffer.buffer);
+
+		// Build
+		VkAccelerationStructureGeometryKHR accelerationStructureGeometry{};
+		accelerationStructureGeometry.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR;
+		accelerationStructureGeometry.flags = VK_GEOMETRY_OPAQUE_BIT_KHR;
+		accelerationStructureGeometry.geometryType = VK_GEOMETRY_TYPE_TRIANGLES_KHR;
+		accelerationStructureGeometry.geometry.triangles.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR;
+		accelerationStructureGeometry.geometry.triangles.vertexFormat = VK_FORMAT_R32G32B32_SFLOAT;
+		accelerationStructureGeometry.geometry.triangles.vertexData = vertexBufferDeviceAddress;
+		accelerationStructureGeometry.geometry.triangles.maxVertex = 3;
+		accelerationStructureGeometry.geometry.triangles.vertexStride = sizeof(Vertex);
+		accelerationStructureGeometry.geometry.triangles.indexType = VK_INDEX_TYPE_UINT32;
+		accelerationStructureGeometry.geometry.triangles.indexData = indexBufferDeviceAddress;
+		accelerationStructureGeometry.geometry.triangles.transformData.deviceAddress = 0;
+		accelerationStructureGeometry.geometry.triangles.transformData.hostAddress = nullptr;
+		accelerationStructureGeometry.geometry.triangles.transformData = transformBufferDeviceAddress;
+		
+		// Get size info
+		VkAccelerationStructureBuildGeometryInfoKHR accelerationStructureBuildGeometryInfo{};
+		accelerationStructureBuildGeometryInfo.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR;
+		accelerationStructureBuildGeometryInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR;
+		accelerationStructureBuildGeometryInfo.flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR;
+		accelerationStructureBuildGeometryInfo.geometryCount = 1;
+		accelerationStructureBuildGeometryInfo.pGeometries = &accelerationStructureGeometry;
+		
+		const uint32_t numTriangles = 1;
+		VkAccelerationStructureBuildSizesInfoKHR accelerationStructureBuildSizesInfo{};
+		accelerationStructureBuildSizesInfo.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_SIZES_INFO_KHR;
+		vkGetAccelerationStructureBuildSizesKHR(
+			device,
+			VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR,
+			&accelerationStructureBuildGeometryInfo,
+			&numTriangles,
+			&accelerationStructureBuildSizesInfo);
+
+		createAccelerationStructureBuffer(bottomLevelAS, accelerationStructureBuildSizesInfo);
+
+		VkAccelerationStructureCreateInfoKHR accelerationStructureCreateInfo{};
+		accelerationStructureCreateInfo.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_CREATE_INFO_KHR;
+		accelerationStructureCreateInfo.buffer = bottomLevelAS.buffer;
+		accelerationStructureCreateInfo.size = accelerationStructureBuildSizesInfo.accelerationStructureSize;
+		accelerationStructureCreateInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR;
+		vkCreateAccelerationStructureKHR(device, &accelerationStructureCreateInfo, nullptr, &bottomLevelAS.handle);
+
+		// Create a small scratch buffer used during build of the bottom level acceleration structure
+		RayTracingScratchBuffer scratchBuffer = createScratchBuffer(accelerationStructureBuildSizesInfo.buildScratchSize);
+
+		VkAccelerationStructureBuildGeometryInfoKHR accelerationBuildGeometryInfo{};
+		accelerationBuildGeometryInfo.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR;
+		accelerationBuildGeometryInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR;
+		accelerationBuildGeometryInfo.flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR;
+		accelerationBuildGeometryInfo.mode = VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR;
+		accelerationBuildGeometryInfo.dstAccelerationStructure = bottomLevelAS.handle;
+		accelerationBuildGeometryInfo.geometryCount = 1;
+		accelerationBuildGeometryInfo.pGeometries = &accelerationStructureGeometry;
+		accelerationBuildGeometryInfo.scratchData.deviceAddress = scratchBuffer.deviceAddress;
+
+		VkAccelerationStructureBuildRangeInfoKHR accelerationStructureBuildRangeInfo{};
+		accelerationStructureBuildRangeInfo.primitiveCount = numTriangles;
+		accelerationStructureBuildRangeInfo.primitiveOffset = 0;
+		accelerationStructureBuildRangeInfo.firstVertex = 0;
+		accelerationStructureBuildRangeInfo.transformOffset = 0;
+		std::vector<VkAccelerationStructureBuildRangeInfoKHR*> accelerationBuildStructureRangeInfos = { &accelerationStructureBuildRangeInfo };
+
+		// Build the acceleration structure on the device via a one-time command buffer submission
+		// Some implementations may support acceleration structure building on the host (VkPhysicalDeviceAccelerationStructureFeaturesKHR->accelerationStructureHostCommands), but we prefer device builds
+		VkCommandBuffer commandBuffer = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+		vkCmdBuildAccelerationStructuresKHR(
+			commandBuffer,
+			1,
+			&accelerationBuildGeometryInfo,
+			accelerationBuildStructureRangeInfos.data());
+		vulkanDevice->flushCommandBuffer(commandBuffer, queue);
+
+		VkAccelerationStructureDeviceAddressInfoKHR accelerationDeviceAddressInfo{};
+		accelerationDeviceAddressInfo.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_DEVICE_ADDRESS_INFO_KHR;
+		accelerationDeviceAddressInfo.accelerationStructure = bottomLevelAS.handle;
+		bottomLevelAS.deviceAddress = vkGetAccelerationStructureDeviceAddressKHR(device, &accelerationDeviceAddressInfo);
+
+		deleteScratchBuffer(scratchBuffer);
+	}
+
+	/*
+		The top level acceleration structure contains the scene's object instances
+	*/
+	void createTopLevelAccelerationStructure()
+	{
+		VkTransformMatrixKHR transformMatrix = {
+			1.0f, 0.0f, 0.0f, 0.0f,
+			0.0f, 1.0f, 0.0f, 0.0f,
+			0.0f, 0.0f, 1.0f, 0.0f };
+
+		VkAccelerationStructureInstanceKHR instance{};
+		instance.transform = transformMatrix;
+		instance.instanceCustomIndex = 0;
+		instance.mask = 0xFF;
+		instance.instanceShaderBindingTableRecordOffset = 0;
+		instance.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR;
+		instance.accelerationStructureReference = bottomLevelAS.deviceAddress;
+
+		// Buffer for instance data
+		vks::Buffer instancesBuffer;
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&instancesBuffer,
+			sizeof(VkAccelerationStructureInstanceKHR),
+			&instance));
+
+		VkDeviceOrHostAddressConstKHR instanceDataDeviceAddress{};
+		instanceDataDeviceAddress.deviceAddress = getBufferDeviceAddress(instancesBuffer.buffer);
+
+		VkAccelerationStructureGeometryKHR accelerationStructureGeometry{};
+		accelerationStructureGeometry.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR;
+		accelerationStructureGeometry.geometryType = VK_GEOMETRY_TYPE_INSTANCES_KHR;
+		accelerationStructureGeometry.flags = VK_GEOMETRY_OPAQUE_BIT_KHR;
+		accelerationStructureGeometry.geometry.instances.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_INSTANCES_DATA_KHR;
+		accelerationStructureGeometry.geometry.instances.arrayOfPointers = VK_FALSE;
+		accelerationStructureGeometry.geometry.instances.data = instanceDataDeviceAddress;
+
+		// Get size info
+		/*
+		The pSrcAccelerationStructure, dstAccelerationStructure, and mode members of pBuildInfo are ignored. Any VkDeviceOrHostAddressKHR members of pBuildInfo are ignored by this command, except that the hostAddress member of VkAccelerationStructureGeometryTrianglesDataKHR::transformData will be examined to check if it is NULL.*
+		*/
+		VkAccelerationStructureBuildGeometryInfoKHR accelerationStructureBuildGeometryInfo{};
+		accelerationStructureBuildGeometryInfo.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR;
+		accelerationStructureBuildGeometryInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR;
+		accelerationStructureBuildGeometryInfo.flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR;
+		accelerationStructureBuildGeometryInfo.geometryCount = 1;
+		accelerationStructureBuildGeometryInfo.pGeometries = &accelerationStructureGeometry;
+
+		uint32_t primitive_count = 1;
+
+		VkAccelerationStructureBuildSizesInfoKHR accelerationStructureBuildSizesInfo{};
+		accelerationStructureBuildSizesInfo.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_SIZES_INFO_KHR;
+		vkGetAccelerationStructureBuildSizesKHR(
+			device, 
+			VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR,
+			&accelerationStructureBuildGeometryInfo,
+			&primitive_count,
+			&accelerationStructureBuildSizesInfo);
+
+		createAccelerationStructureBuffer(topLevelAS, accelerationStructureBuildSizesInfo);
+
+		VkAccelerationStructureCreateInfoKHR accelerationStructureCreateInfo{};
+		accelerationStructureCreateInfo.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_CREATE_INFO_KHR;
+		accelerationStructureCreateInfo.buffer = topLevelAS.buffer;
+		accelerationStructureCreateInfo.size = accelerationStructureBuildSizesInfo.accelerationStructureSize;
+		accelerationStructureCreateInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR;
+		vkCreateAccelerationStructureKHR(device, &accelerationStructureCreateInfo, nullptr, &topLevelAS.handle);
+
+		// Create a small scratch buffer used during build of the top level acceleration structure
+		RayTracingScratchBuffer scratchBuffer = createScratchBuffer(accelerationStructureBuildSizesInfo.buildScratchSize);
+
+		VkAccelerationStructureBuildGeometryInfoKHR accelerationBuildGeometryInfo{};
+		accelerationBuildGeometryInfo.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR;
+		accelerationBuildGeometryInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR;
+		accelerationBuildGeometryInfo.flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR;
+		accelerationBuildGeometryInfo.mode = VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR;
+		accelerationBuildGeometryInfo.dstAccelerationStructure = topLevelAS.handle;
+		accelerationBuildGeometryInfo.geometryCount = 1;
+		accelerationBuildGeometryInfo.pGeometries = &accelerationStructureGeometry;
+		accelerationBuildGeometryInfo.scratchData.deviceAddress = scratchBuffer.deviceAddress;
+
+		VkAccelerationStructureBuildRangeInfoKHR accelerationStructureBuildRangeInfo{};
+		accelerationStructureBuildRangeInfo.primitiveCount = 1;
+		accelerationStructureBuildRangeInfo.primitiveOffset = 0;
+		accelerationStructureBuildRangeInfo.firstVertex = 0;
+		accelerationStructureBuildRangeInfo.transformOffset = 0;
+		std::vector<VkAccelerationStructureBuildRangeInfoKHR*> accelerationBuildStructureRangeInfos = { &accelerationStructureBuildRangeInfo };
+
+		// Build the acceleration structure on the device via a one-time command buffer submission
+		// Some implementations may support acceleration structure building on the host (VkPhysicalDeviceAccelerationStructureFeaturesKHR->accelerationStructureHostCommands), but we prefer device builds
+		VkCommandBuffer commandBuffer = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+		vkCmdBuildAccelerationStructuresKHR(
+			commandBuffer,
+			1,
+			&accelerationBuildGeometryInfo,
+			accelerationBuildStructureRangeInfos.data());
+		vulkanDevice->flushCommandBuffer(commandBuffer, queue);
+
+		VkAccelerationStructureDeviceAddressInfoKHR accelerationDeviceAddressInfo{};
+		accelerationDeviceAddressInfo.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_DEVICE_ADDRESS_INFO_KHR;
+		accelerationDeviceAddressInfo.accelerationStructure = topLevelAS.handle;
+		topLevelAS.deviceAddress = vkGetAccelerationStructureDeviceAddressKHR(device, &accelerationDeviceAddressInfo);
+
+		deleteScratchBuffer(scratchBuffer);
+		instancesBuffer.destroy();
+	}
+
+	/*
+		Create the Shader Binding Tables that binds the programs and top-level acceleration structure
+
+		SBT Layout used in this sample:
+
+			/-----------\
+			| raygen    |
+			|-----------|
+			| miss      |
+			|-----------|
+			| hit       |
+			\-----------/
+
+	*/
+	void createShaderBindingTable() {
+		const uint32_t handleSize = rayTracingPipelineProperties.shaderGroupHandleSize;
+		const uint32_t handleSizeAligned = vks::tools::alignedSize(rayTracingPipelineProperties.shaderGroupHandleSize, rayTracingPipelineProperties.shaderGroupHandleAlignment);
+		const uint32_t groupCount = static_cast<uint32_t>(shaderGroups.size());
+		const uint32_t sbtSize = groupCount * handleSizeAligned;
+
+		std::vector<uint8_t> shaderHandleStorage(sbtSize);
+		VK_CHECK_RESULT(vkGetRayTracingShaderGroupHandlesKHR(device, pipeline, 0, groupCount, sbtSize, shaderHandleStorage.data()));
+
+		const VkBufferUsageFlags bufferUsageFlags = VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT;
+		const VkMemoryPropertyFlags memoryUsageFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(bufferUsageFlags, memoryUsageFlags, &raygenShaderBindingTable, handleSize));
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(bufferUsageFlags, memoryUsageFlags, &missShaderBindingTable, handleSize));
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(bufferUsageFlags, memoryUsageFlags, &hitShaderBindingTable, handleSize));
+
+		// Copy handles
+		raygenShaderBindingTable.map();
+		missShaderBindingTable.map();
+		hitShaderBindingTable.map();
+		memcpy(raygenShaderBindingTable.mapped, shaderHandleStorage.data(), handleSize);
+		memcpy(missShaderBindingTable.mapped, shaderHandleStorage.data() + handleSizeAligned, handleSize);
+		memcpy(hitShaderBindingTable.mapped, shaderHandleStorage.data() + handleSizeAligned * 2, handleSize);
+	}
+
+	/*
+		Create the descriptor sets used for the ray tracing dispatch
+	*/
+	void createDescriptorSets()
+	{
+		std::vector<VkDescriptorPoolSize> poolSizes = {
+			{ VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1 },
+			{ VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1 },
+			{ VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1 }
+		};
+		VkDescriptorPoolCreateInfo descriptorPoolCreateInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 1);
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolCreateInfo, nullptr, &descriptorPool));
+
+		VkDescriptorSetAllocateInfo descriptorSetAllocateInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1);
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &descriptorSetAllocateInfo, &descriptorSet));
+
+		VkWriteDescriptorSetAccelerationStructureKHR descriptorAccelerationStructureInfo{};
+		descriptorAccelerationStructureInfo.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR;
+		descriptorAccelerationStructureInfo.accelerationStructureCount = 1;
+		descriptorAccelerationStructureInfo.pAccelerationStructures = &topLevelAS.handle;
+
+		VkWriteDescriptorSet accelerationStructureWrite{};
+		accelerationStructureWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+		// The specialized acceleration structure descriptor has to be chained
+		accelerationStructureWrite.pNext = &descriptorAccelerationStructureInfo;
+		accelerationStructureWrite.dstSet = descriptorSet;
+		accelerationStructureWrite.dstBinding = 0;
+		accelerationStructureWrite.descriptorCount = 1;
+		accelerationStructureWrite.descriptorType = VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR;
+
+		VkDescriptorImageInfo storageImageDescriptor{};
+		storageImageDescriptor.imageView = storageImage.view;
+		storageImageDescriptor.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
+
+		VkWriteDescriptorSet resultImageWrite = vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, &storageImageDescriptor);
+		VkWriteDescriptorSet uniformBufferWrite = vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2, &ubo.descriptor);
+
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets = {
+			accelerationStructureWrite,
+			resultImageWrite,
+			uniformBufferWrite
+		};
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, VK_NULL_HANDLE);
+	}
+
+	/*
+		Create our ray tracing pipeline
+	*/
+	void createRayTracingPipeline()
+	{
+		VkDescriptorSetLayoutBinding accelerationStructureLayoutBinding{};
+		accelerationStructureLayoutBinding.binding = 0;
+		accelerationStructureLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR;
+		accelerationStructureLayoutBinding.descriptorCount = 1;
+		accelerationStructureLayoutBinding.stageFlags = VK_SHADER_STAGE_RAYGEN_BIT_KHR;
+
+		VkDescriptorSetLayoutBinding resultImageLayoutBinding{};
+		resultImageLayoutBinding.binding = 1;
+		resultImageLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
+		resultImageLayoutBinding.descriptorCount = 1;
+		resultImageLayoutBinding.stageFlags = VK_SHADER_STAGE_RAYGEN_BIT_KHR;
+
+		VkDescriptorSetLayoutBinding uniformBufferBinding{};
+		uniformBufferBinding.binding = 2;
+		uniformBufferBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+		uniformBufferBinding.descriptorCount = 1;
+		uniformBufferBinding.stageFlags = VK_SHADER_STAGE_RAYGEN_BIT_KHR;
+
+		std::vector<VkDescriptorSetLayoutBinding> bindings({
+			accelerationStructureLayoutBinding,
+			resultImageLayoutBinding,
+			uniformBufferBinding
+			});
+
+		VkDescriptorSetLayoutCreateInfo descriptorSetlayoutCI{};
+		descriptorSetlayoutCI.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
+		descriptorSetlayoutCI.bindingCount = static_cast<uint32_t>(bindings.size());
+		descriptorSetlayoutCI.pBindings = bindings.data();
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorSetlayoutCI, nullptr, &descriptorSetLayout));
+
+		VkPipelineLayoutCreateInfo pipelineLayoutCI{};
+		pipelineLayoutCI.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
+		pipelineLayoutCI.setLayoutCount = 1;
+		pipelineLayoutCI.pSetLayouts = &descriptorSetLayout;
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelineLayout));
+
+		/*
+			Setup ray tracing shader groups
+		*/
+		std::vector<VkPipelineShaderStageCreateInfo> shaderStages;
+
+		// Ray generation group
+		{
+			shaderStages.push_back(loadShader(getShadersPath() + "raytracingbasic/raygen.rgen.spv", VK_SHADER_STAGE_RAYGEN_BIT_KHR));
+			VkRayTracingShaderGroupCreateInfoKHR shaderGroup{};
+			shaderGroup.sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR;
+			shaderGroup.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR;
+			shaderGroup.generalShader = static_cast<uint32_t>(shaderStages.size()) - 1;
+			shaderGroup.closestHitShader = VK_SHADER_UNUSED_KHR;
+			shaderGroup.anyHitShader = VK_SHADER_UNUSED_KHR;
+			shaderGroup.intersectionShader = VK_SHADER_UNUSED_KHR;
+			shaderGroups.push_back(shaderGroup);
+		}
+
+		// Miss group
+		{
+			shaderStages.push_back(loadShader(getShadersPath() + "raytracingbasic/miss.rmiss.spv", VK_SHADER_STAGE_MISS_BIT_KHR));
+			VkRayTracingShaderGroupCreateInfoKHR shaderGroup{};
+			shaderGroup.sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR;
+			shaderGroup.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR;
+			shaderGroup.generalShader = static_cast<uint32_t>(shaderStages.size()) - 1;
+			shaderGroup.closestHitShader = VK_SHADER_UNUSED_KHR;
+			shaderGroup.anyHitShader = VK_SHADER_UNUSED_KHR;
+			shaderGroup.intersectionShader = VK_SHADER_UNUSED_KHR;
+			shaderGroups.push_back(shaderGroup);
+		}
+
+		// Closest hit group
+		{
+			shaderStages.push_back(loadShader(getShadersPath() + "raytracingbasic/closesthit.rchit.spv", VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR));
+			VkRayTracingShaderGroupCreateInfoKHR shaderGroup{};
+			shaderGroup.sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR;
+			shaderGroup.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_TRIANGLES_HIT_GROUP_KHR;
+			shaderGroup.generalShader = VK_SHADER_UNUSED_KHR;
+			shaderGroup.closestHitShader = static_cast<uint32_t>(shaderStages.size()) - 1;
+			shaderGroup.anyHitShader = VK_SHADER_UNUSED_KHR;
+			shaderGroup.intersectionShader = VK_SHADER_UNUSED_KHR;
+			shaderGroups.push_back(shaderGroup);
+		}
+
+		/*
+			Create the ray tracing pipeline
+		*/
+		VkRayTracingPipelineCreateInfoKHR rayTracingPipelineCI{};
+		rayTracingPipelineCI.sType = VK_STRUCTURE_TYPE_RAY_TRACING_PIPELINE_CREATE_INFO_KHR;
+		rayTracingPipelineCI.stageCount = static_cast<uint32_t>(shaderStages.size());
+		rayTracingPipelineCI.pStages = shaderStages.data();
+		rayTracingPipelineCI.groupCount = static_cast<uint32_t>(shaderGroups.size());
+		rayTracingPipelineCI.pGroups = shaderGroups.data();
+		rayTracingPipelineCI.maxPipelineRayRecursionDepth = 1;
+		rayTracingPipelineCI.layout = pipelineLayout;
+		VK_CHECK_RESULT(vkCreateRayTracingPipelinesKHR(device, VK_NULL_HANDLE, VK_NULL_HANDLE, 1, &rayTracingPipelineCI, nullptr, &pipeline));
+	}
+
+	/*
+		Create the uniform buffer used to pass matrices to the ray tracing ray generation shader
+	*/
+	void createUniformBuffer()
+	{
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&ubo,
+			sizeof(uniformData),
+			&uniformData));
+		VK_CHECK_RESULT(ubo.map());
+
+		updateUniformBuffers();
+	}
+
+	/*
+		If the window has been resized, we need to recreate the storage image and it's descriptor
+	*/
+	void handleResize()
+	{
+		// Delete allocated resources
+		vkDestroyImageView(device, storageImage.view, nullptr);
+		vkDestroyImage(device, storageImage.image, nullptr);
+		vkFreeMemory(device, storageImage.memory, nullptr);
+		// Recreate image
+		createStorageImage();
+		// Update descriptor
+		VkDescriptorImageInfo storageImageDescriptor{ VK_NULL_HANDLE, storageImage.view, VK_IMAGE_LAYOUT_GENERAL };
+		VkWriteDescriptorSet resultImageWrite = vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, &storageImageDescriptor);
+		vkUpdateDescriptorSets(device, 1, &resultImageWrite, 0, VK_NULL_HANDLE);
+	}
+
+	/*
+		Command buffer generation
+	*/
+	void buildCommandBuffers()
+	{
+		if (resized)
+		{
+			handleResize();
+		}
+
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkImageSubresourceRange subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 };
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			/*
+				Setup the buffer regions pointing to the shaders in our shader binding table
+			*/
+
+			const uint32_t handleSizeAligned = vks::tools::alignedSize(rayTracingPipelineProperties.shaderGroupHandleSize, rayTracingPipelineProperties.shaderGroupHandleAlignment);
+
+			VkStridedDeviceAddressRegionKHR raygenShaderSbtEntry{};
+			raygenShaderSbtEntry.deviceAddress = getBufferDeviceAddress(raygenShaderBindingTable.buffer);
+			raygenShaderSbtEntry.stride = handleSizeAligned;
+			raygenShaderSbtEntry.size = handleSizeAligned;
+
+			VkStridedDeviceAddressRegionKHR missShaderSbtEntry{};
+			missShaderSbtEntry.deviceAddress = getBufferDeviceAddress(missShaderBindingTable.buffer);
+			missShaderSbtEntry.stride = handleSizeAligned;
+			missShaderSbtEntry.size = handleSizeAligned;
+
+			VkStridedDeviceAddressRegionKHR hitShaderSbtEntry{};
+			hitShaderSbtEntry.deviceAddress = getBufferDeviceAddress(hitShaderBindingTable.buffer);
+			hitShaderSbtEntry.stride = handleSizeAligned;
+			hitShaderSbtEntry.size = handleSizeAligned;
+
+			VkStridedDeviceAddressRegionKHR callableShaderSbtEntry{};
+
+			/*
+				Dispatch the ray tracing commands
+			*/
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, pipeline);
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, pipelineLayout, 0, 1, &descriptorSet, 0, 0);
+
+			vkCmdTraceRaysKHR(
+				drawCmdBuffers[i],
+				&raygenShaderSbtEntry,
+				&missShaderSbtEntry,
+				&hitShaderSbtEntry,
+				&callableShaderSbtEntry,
+				width,
+				height,
+				1);
+
+			/*
+				Copy ray tracing output to swap chain image
+			*/
+
+			// Prepare current swap chain image as transfer destination
+			vks::tools::setImageLayout(
+				drawCmdBuffers[i],
+				swapChain.images[i],
+				VK_IMAGE_LAYOUT_UNDEFINED,
+				VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+				subresourceRange);
+
+			// Prepare ray tracing output image as transfer source
+			vks::tools::setImageLayout(
+				drawCmdBuffers[i],
+				storageImage.image,
+				VK_IMAGE_LAYOUT_GENERAL,
+				VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+				subresourceRange);
+
+			VkImageCopy copyRegion{};
+			copyRegion.srcSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 };
+			copyRegion.srcOffset = { 0, 0, 0 };
+			copyRegion.dstSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 };
+			copyRegion.dstOffset = { 0, 0, 0 };
+			copyRegion.extent = { width, height, 1 };
+			vkCmdCopyImage(drawCmdBuffers[i], storageImage.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, swapChain.images[i], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &copyRegion);
+
+			// Transition swap chain image back for presentation
+			vks::tools::setImageLayout(
+				drawCmdBuffers[i],
+				swapChain.images[i],
+				VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+				VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
+				subresourceRange);
+
+			// Transition ray tracing output image back to general layout
+			vks::tools::setImageLayout(
+				drawCmdBuffers[i],
+				storageImage.image,
+				VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+				VK_IMAGE_LAYOUT_GENERAL,
+				subresourceRange);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void updateUniformBuffers()
+	{
+		uniformData.projInverse = glm::inverse(camera.matrices.perspective);
+		uniformData.viewInverse = glm::inverse(camera.matrices.view);
+		memcpy(ubo.mapped, &uniformData, sizeof(uniformData));
+	}
+
+	void getEnabledFeatures()
+	{
+		// Enable features required for ray tracing using feature chaining via pNext		
+		enabledBufferDeviceAddresFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES;
+		enabledBufferDeviceAddresFeatures.bufferDeviceAddress = VK_TRUE;
+
+		enabledRayTracingPipelineFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_FEATURES_KHR;
+		enabledRayTracingPipelineFeatures.rayTracingPipeline = VK_TRUE;
+		enabledRayTracingPipelineFeatures.pNext = &enabledBufferDeviceAddresFeatures;
+
+		enabledAccelerationStructureFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_FEATURES_KHR;
+		enabledAccelerationStructureFeatures.accelerationStructure = VK_TRUE;
+		enabledAccelerationStructureFeatures.pNext = &enabledRayTracingPipelineFeatures;
+
+		deviceCreatepNextChain = &enabledAccelerationStructureFeatures;
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+
+		// Get ray tracing pipeline properties, which will be used later on in the sample
+		rayTracingPipelineProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_PROPERTIES_KHR;
+		VkPhysicalDeviceProperties2 deviceProperties2{};
+		deviceProperties2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
+		deviceProperties2.pNext = &rayTracingPipelineProperties;
+		vkGetPhysicalDeviceProperties2(physicalDevice, &deviceProperties2);
+
+		// Get acceleration structure properties, which will be used later on in the sample
+		accelerationStructureFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_FEATURES_KHR;
+		VkPhysicalDeviceFeatures2 deviceFeatures2{};
+		deviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
+		deviceFeatures2.pNext = &accelerationStructureFeatures;
+		vkGetPhysicalDeviceFeatures2(physicalDevice, &deviceFeatures2);
+
+		// Get the ray tracing and accelertion structure related function pointers required by this sample
+		vkGetBufferDeviceAddressKHR = reinterpret_cast<PFN_vkGetBufferDeviceAddressKHR>(vkGetDeviceProcAddr(device, "vkGetBufferDeviceAddressKHR"));
+		vkCmdBuildAccelerationStructuresKHR = reinterpret_cast<PFN_vkCmdBuildAccelerationStructuresKHR>(vkGetDeviceProcAddr(device, "vkCmdBuildAccelerationStructuresKHR"));
+		vkBuildAccelerationStructuresKHR = reinterpret_cast<PFN_vkBuildAccelerationStructuresKHR>(vkGetDeviceProcAddr(device, "vkBuildAccelerationStructuresKHR"));
+		vkCreateAccelerationStructureKHR = reinterpret_cast<PFN_vkCreateAccelerationStructureKHR>(vkGetDeviceProcAddr(device, "vkCreateAccelerationStructureKHR"));
+		vkDestroyAccelerationStructureKHR = reinterpret_cast<PFN_vkDestroyAccelerationStructureKHR>(vkGetDeviceProcAddr(device, "vkDestroyAccelerationStructureKHR"));
+		vkGetAccelerationStructureBuildSizesKHR = reinterpret_cast<PFN_vkGetAccelerationStructureBuildSizesKHR>(vkGetDeviceProcAddr(device, "vkGetAccelerationStructureBuildSizesKHR"));
+		vkGetAccelerationStructureDeviceAddressKHR = reinterpret_cast<PFN_vkGetAccelerationStructureDeviceAddressKHR>(vkGetDeviceProcAddr(device, "vkGetAccelerationStructureDeviceAddressKHR"));
+		vkCmdTraceRaysKHR = reinterpret_cast<PFN_vkCmdTraceRaysKHR>(vkGetDeviceProcAddr(device, "vkCmdTraceRaysKHR"));
+		vkGetRayTracingShaderGroupHandlesKHR = reinterpret_cast<PFN_vkGetRayTracingShaderGroupHandlesKHR>(vkGetDeviceProcAddr(device, "vkGetRayTracingShaderGroupHandlesKHR"));
+		vkCreateRayTracingPipelinesKHR = reinterpret_cast<PFN_vkCreateRayTracingPipelinesKHR>(vkGetDeviceProcAddr(device, "vkCreateRayTracingPipelinesKHR"));
+
+		// Create the acceleration structures used to render the ray traced scene
+		createBottomLevelAccelerationStructure();
+		createTopLevelAccelerationStructure();
+
+		createStorageImage();
+		createUniformBuffer();
+		createRayTracingPipeline();
+		createShaderBindingTable();
+		createDescriptorSets();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+		VulkanExampleBase::submitFrame();
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+		if (camera.updated)
+			updateUniformBuffers();
+	}
+};
+
+VULKAN_EXAMPLE_MAIN()
diff --git a/external/Vulkan/examples/raytracingcallable/raytracingcallable.cpp b/external/Vulkan/examples/raytracingcallable/raytracingcallable.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..411a7331a9c37a4e70cddfe29e669524c76594b3
--- /dev/null
+++ b/external/Vulkan/examples/raytracingcallable/raytracingcallable.cpp
@@ -0,0 +1,649 @@
+/*
+* Vulkan Example - Hardware accelerated ray tracing callable shaders example
+*
+* Dynamically calls different shaders based on the geometry id in the closest hit shader
+*
+* Relevant code parts are marked with [POI]
+*
+* Copyright (C) 2021 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "VulkanRaytracingSample.h"
+
+class VulkanExample : public VulkanRaytracingSample
+{
+public:
+	AccelerationStructure bottomLevelAS;
+	AccelerationStructure topLevelAS;
+
+	std::vector<VkRayTracingShaderGroupCreateInfoKHR> shaderGroups{};
+	struct ShaderBindingTables {
+		ShaderBindingTable raygen;
+		ShaderBindingTable miss;
+		ShaderBindingTable hit;
+		ShaderBindingTable callable;
+	} shaderBindingTables;
+
+	struct UniformData {
+		glm::mat4 viewInverse;
+		glm::mat4 projInverse;
+	} uniformData;
+	vks::Buffer ubo;
+
+	VkPipeline pipeline;
+	VkPipelineLayout pipelineLayout;
+	VkDescriptorSet descriptorSet;
+	VkDescriptorSetLayout descriptorSetLayout;
+
+	vks::Buffer vertexBuffer;
+	vks::Buffer indexBuffer;
+	vks::Buffer transformBuffer;
+
+	uint32_t objectCount = 3;
+
+	// This sample is derived from an extended base class that saves most of the ray tracing setup boiler plate
+	VulkanExample() : VulkanRaytracingSample()
+	{
+		title = "Ray tracing callable shaders";
+		timerSpeed *= 0.25f;
+		camera.type = Camera::CameraType::lookat;
+		camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 512.0f);
+		camera.setRotation(glm::vec3(0.0f, 0.0f, 0.0f));
+		camera.setTranslation(glm::vec3(0.0f, 0.0f, -10.0f));
+		enableExtensions();
+	}
+
+	~VulkanExample()
+	{
+		if (device) {
+			vkDestroyPipeline(device, pipeline, nullptr);
+			vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+			vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+			deleteStorageImage();
+			deleteAccelerationStructure(bottomLevelAS);
+			deleteAccelerationStructure(topLevelAS);
+			shaderBindingTables.raygen.destroy();
+			shaderBindingTables.miss.destroy();
+			shaderBindingTables.hit.destroy();
+			shaderBindingTables.callable.destroy();
+			vertexBuffer.destroy();
+			indexBuffer.destroy();
+			transformBuffer.destroy();
+			ubo.destroy();
+		}
+	}
+
+	/*
+		Create the bottom level acceleration structure contains the scene's actual geometry (vertices, triangles)
+	*/
+	void createBottomLevelAccelerationStructure()
+	{
+		// Setup vertices for a single triangle
+		struct Vertex {
+			float pos[3];
+		};
+		std::vector<Vertex> vertices = {
+			{ {  1.0f,  1.0f, 0.0f } },
+			{ { -1.0f,  1.0f, 0.0f } },
+			{ {  0.0f, -1.0f, 0.0f } }
+		};
+
+		// Setup indices
+		std::vector<uint32_t> indices = { 0, 1, 2 };
+		uint32_t indexCount = static_cast<uint32_t>(indices.size());
+
+		// Setup transform matrices for the geometries in the bottom level AS
+		std::vector<VkTransformMatrixKHR> transformMatrices(objectCount);
+		for (uint32_t i = 0; i < objectCount; i++) {
+			transformMatrices[i] = {
+				1.0f, 0.0f, 0.0f, (float)i * 3.0f - 3.0f,
+				0.0f, 1.0f, 0.0f, 0.0f,
+				0.0f, 0.0f, 1.0f, 0.0f
+			};
+		}
+		// Transform buffer
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&transformBuffer,
+			objectCount * sizeof(VkTransformMatrixKHR),
+			transformMatrices.data()));
+
+		// Create buffers
+		// For the sake of simplicity we won't stage the vertex data to the GPU memory
+		// Vertex buffer
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&vertexBuffer,
+			vertices.size() * sizeof(Vertex),
+			vertices.data()));
+		// Index buffer
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&indexBuffer,
+			indices.size() * sizeof(uint32_t),
+			indices.data()));
+
+		VkDeviceOrHostAddressConstKHR vertexBufferDeviceAddress{};
+		VkDeviceOrHostAddressConstKHR indexBufferDeviceAddress{};
+		VkDeviceOrHostAddressConstKHR transformBufferDeviceAddress{};
+
+		vertexBufferDeviceAddress.deviceAddress = getBufferDeviceAddress(vertexBuffer.buffer);
+		indexBufferDeviceAddress.deviceAddress = getBufferDeviceAddress(indexBuffer.buffer);
+		transformBufferDeviceAddress.deviceAddress = getBufferDeviceAddress(transformBuffer.buffer);
+
+		uint32_t numTriangles = 1;
+
+		// Our scene will consist of three different triangles, that'll be distinguished in the shader via gl_GeometryIndexEXT, so we add three geometries to the bottom level AS
+		std::vector<uint32_t> geometryCounts;
+		std::vector<VkAccelerationStructureGeometryKHR> accelerationStructureGeometries;
+		for (uint32_t i = 0; i < objectCount; i++) {
+			VkAccelerationStructureGeometryKHR accelerationStructureGeometry = vks::initializers::accelerationStructureGeometryKHR();
+			accelerationStructureGeometry.flags = VK_GEOMETRY_OPAQUE_BIT_KHR;
+			accelerationStructureGeometry.geometryType = VK_GEOMETRY_TYPE_TRIANGLES_KHR;
+			accelerationStructureGeometry.geometry.triangles.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR;
+			accelerationStructureGeometry.geometry.triangles.vertexFormat = VK_FORMAT_R32G32B32_SFLOAT;
+			accelerationStructureGeometry.geometry.triangles.vertexData = vertexBufferDeviceAddress;
+			accelerationStructureGeometry.geometry.triangles.maxVertex = 3;
+			accelerationStructureGeometry.geometry.triangles.vertexStride = sizeof(Vertex);
+			accelerationStructureGeometry.geometry.triangles.indexType = VK_INDEX_TYPE_UINT32;
+			accelerationStructureGeometry.geometry.triangles.indexData = indexBufferDeviceAddress;
+			accelerationStructureGeometry.geometry.triangles.transformData = transformBufferDeviceAddress;
+			accelerationStructureGeometries.push_back(accelerationStructureGeometry);
+			geometryCounts.push_back(1);
+		}
+
+		// Get size info
+		VkAccelerationStructureBuildGeometryInfoKHR accelerationStructureBuildGeometryInfo = vks::initializers::accelerationStructureBuildGeometryInfoKHR();
+		accelerationStructureBuildGeometryInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR;
+		accelerationStructureBuildGeometryInfo.flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR;
+		accelerationStructureBuildGeometryInfo.geometryCount = static_cast<uint32_t>(accelerationStructureGeometries.size());
+		accelerationStructureBuildGeometryInfo.pGeometries = accelerationStructureGeometries.data();
+
+		VkAccelerationStructureBuildSizesInfoKHR accelerationStructureBuildSizesInfo = vks::initializers::accelerationStructureBuildSizesInfoKHR();
+		vkGetAccelerationStructureBuildSizesKHR(
+			device,
+			VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR,
+			&accelerationStructureBuildGeometryInfo,
+			geometryCounts.data(),
+			&accelerationStructureBuildSizesInfo);
+
+		createAccelerationStructure(bottomLevelAS, VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR, accelerationStructureBuildSizesInfo);
+
+		// Create a small scratch buffer used during build of the bottom level acceleration structure
+		ScratchBuffer scratchBuffer = createScratchBuffer(accelerationStructureBuildSizesInfo.buildScratchSize);
+
+		VkAccelerationStructureBuildGeometryInfoKHR accelerationBuildGeometryInfo = vks::initializers::accelerationStructureBuildGeometryInfoKHR();
+		accelerationBuildGeometryInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR;
+		accelerationBuildGeometryInfo.flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR;
+		accelerationBuildGeometryInfo.mode = VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR;
+		accelerationBuildGeometryInfo.dstAccelerationStructure = bottomLevelAS.handle;
+		accelerationBuildGeometryInfo.geometryCount = static_cast<uint32_t>(accelerationStructureGeometries.size());
+		accelerationBuildGeometryInfo.pGeometries = accelerationStructureGeometries.data();
+		accelerationBuildGeometryInfo.scratchData.deviceAddress = scratchBuffer.deviceAddress;
+
+		// [POI] The bottom level acceleration structure for this sample contains three separate triangle geometries, so we can use gl_GeometryIndexEXT in the closest hit shader to select different callable shaders
+		std::vector<VkAccelerationStructureBuildRangeInfoKHR> accelerationStructureBuildRangeInfos{};
+		for (uint32_t i = 0; i < objectCount; i++) {
+			VkAccelerationStructureBuildRangeInfoKHR accelerationStructureBuildRangeInfo{};
+			accelerationStructureBuildRangeInfo.primitiveCount = numTriangles;
+			accelerationStructureBuildRangeInfo.primitiveOffset = 0;
+			accelerationStructureBuildRangeInfo.firstVertex = 0;
+			accelerationStructureBuildRangeInfo.transformOffset = i * sizeof(VkTransformMatrixKHR);
+			accelerationStructureBuildRangeInfos.push_back(accelerationStructureBuildRangeInfo);
+		}
+		std::vector<VkAccelerationStructureBuildRangeInfoKHR*> accelerationBuildStructureRangeInfos = { &accelerationStructureBuildRangeInfos[0], &accelerationStructureBuildRangeInfos[1], &accelerationStructureBuildRangeInfos[2] };
+
+		// Build the acceleration structure on the device via a one-time command buffer submission
+		// Some implementations may support acceleration structure building on the host (VkPhysicalDeviceAccelerationStructureFeaturesKHR->accelerationStructureHostCommands), but we prefer device builds
+		VkCommandBuffer commandBuffer = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+		vkCmdBuildAccelerationStructuresKHR(
+			commandBuffer,
+			1,
+			&accelerationBuildGeometryInfo,
+			accelerationBuildStructureRangeInfos.data());
+		vulkanDevice->flushCommandBuffer(commandBuffer, queue);
+
+		deleteScratchBuffer(scratchBuffer);
+	}
+
+	/*
+		The top level acceleration structure contains the scene's object instances
+	*/
+	void createTopLevelAccelerationStructure()
+	{
+		VkTransformMatrixKHR transformMatrix = {
+			1.0f, 0.0f, 0.0f, 0.0f,
+			0.0f, 1.0f, 0.0f, 0.0f,
+			0.0f, 0.0f, 1.0f, 0.0f };
+
+		VkAccelerationStructureInstanceKHR instance{};
+		instance.transform = transformMatrix;
+		instance.instanceCustomIndex = 0;
+		instance.mask = 0xFF;
+		instance.instanceShaderBindingTableRecordOffset = 0;
+		instance.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR;
+		instance.accelerationStructureReference = bottomLevelAS.deviceAddress;
+
+		// Buffer for instance data
+		vks::Buffer instancesBuffer;
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&instancesBuffer,
+			sizeof(VkAccelerationStructureInstanceKHR),
+			&instance));
+
+		VkDeviceOrHostAddressConstKHR instanceDataDeviceAddress{};
+		instanceDataDeviceAddress.deviceAddress = getBufferDeviceAddress(instancesBuffer.buffer);
+
+		VkAccelerationStructureGeometryKHR accelerationStructureGeometry = vks::initializers::accelerationStructureGeometryKHR();
+		accelerationStructureGeometry.geometryType = VK_GEOMETRY_TYPE_INSTANCES_KHR;
+		accelerationStructureGeometry.flags = VK_GEOMETRY_OPAQUE_BIT_KHR;
+		accelerationStructureGeometry.geometry.instances.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_INSTANCES_DATA_KHR;
+		accelerationStructureGeometry.geometry.instances.arrayOfPointers = VK_FALSE;
+		accelerationStructureGeometry.geometry.instances.data = instanceDataDeviceAddress;
+
+		// Get size info
+		VkAccelerationStructureBuildGeometryInfoKHR accelerationStructureBuildGeometryInfo = vks::initializers::accelerationStructureBuildGeometryInfoKHR();
+		accelerationStructureBuildGeometryInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR;
+		accelerationStructureBuildGeometryInfo.flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR;
+		accelerationStructureBuildGeometryInfo.geometryCount = 1;
+		accelerationStructureBuildGeometryInfo.pGeometries = &accelerationStructureGeometry;
+
+		uint32_t primitive_count = 1;
+
+		VkAccelerationStructureBuildSizesInfoKHR accelerationStructureBuildSizesInfo = vks::initializers::accelerationStructureBuildSizesInfoKHR();
+		vkGetAccelerationStructureBuildSizesKHR(
+			device,
+			VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR,
+			&accelerationStructureBuildGeometryInfo,
+			&primitive_count,
+			&accelerationStructureBuildSizesInfo);
+
+		createAccelerationStructure(topLevelAS, VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR, accelerationStructureBuildSizesInfo);
+
+		// Create a small scratch buffer used during build of the top level acceleration structure
+		ScratchBuffer scratchBuffer = createScratchBuffer(accelerationStructureBuildSizesInfo.buildScratchSize);
+
+		VkAccelerationStructureBuildGeometryInfoKHR accelerationBuildGeometryInfo = vks::initializers::accelerationStructureBuildGeometryInfoKHR();
+		accelerationBuildGeometryInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR;
+		accelerationBuildGeometryInfo.flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR;
+		accelerationBuildGeometryInfo.mode = VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR;
+		accelerationBuildGeometryInfo.dstAccelerationStructure = topLevelAS.handle;
+		accelerationBuildGeometryInfo.geometryCount = 1;
+		accelerationBuildGeometryInfo.pGeometries = &accelerationStructureGeometry;
+		accelerationBuildGeometryInfo.scratchData.deviceAddress = scratchBuffer.deviceAddress;
+
+		VkAccelerationStructureBuildRangeInfoKHR accelerationStructureBuildRangeInfo{};
+		accelerationStructureBuildRangeInfo.primitiveCount = 1;
+		accelerationStructureBuildRangeInfo.primitiveOffset = 0;
+		accelerationStructureBuildRangeInfo.firstVertex = 0;
+		accelerationStructureBuildRangeInfo.transformOffset = 0;
+		std::vector<VkAccelerationStructureBuildRangeInfoKHR*> accelerationBuildStructureRangeInfos = { &accelerationStructureBuildRangeInfo };
+
+		// Build the acceleration structure on the device via a one-time command buffer submission
+		// Some implementations may support acceleration structure building on the host (VkPhysicalDeviceAccelerationStructureFeaturesKHR->accelerationStructureHostCommands), but we prefer device builds
+		VkCommandBuffer commandBuffer = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+		vkCmdBuildAccelerationStructuresKHR(
+			commandBuffer,
+			1,
+			&accelerationBuildGeometryInfo,
+			accelerationBuildStructureRangeInfos.data());
+		vulkanDevice->flushCommandBuffer(commandBuffer, queue);
+
+		deleteScratchBuffer(scratchBuffer);
+		instancesBuffer.destroy();
+	}
+
+	/*
+		Create the Shader Binding Tables that binds the programs and top-level acceleration structure
+			
+		SBT Layout used in this sample:
+
+			/-----------\
+			| raygen    |
+			|-----------|
+			| miss      |
+			|-----------|
+			| hit       |
+			|-----------|
+			| callable0 |
+			| callable1 |
+			| callabel2 |
+			\-----------/
+
+	*/
+	void createShaderBindingTables() {
+		const uint32_t handleSize = rayTracingPipelineProperties.shaderGroupHandleSize;
+		const uint32_t handleSizeAligned = vks::tools::alignedSize(rayTracingPipelineProperties.shaderGroupHandleSize, rayTracingPipelineProperties.shaderGroupHandleAlignment);
+		const uint32_t groupCount = static_cast<uint32_t>(shaderGroups.size());
+		const uint32_t sbtSize = groupCount * handleSizeAligned;
+
+		std::vector<uint8_t> shaderHandleStorage(sbtSize);
+		VK_CHECK_RESULT(vkGetRayTracingShaderGroupHandlesKHR(device, pipeline, 0, groupCount, sbtSize, shaderHandleStorage.data()));
+
+		createShaderBindingTable(shaderBindingTables.raygen, 1);
+		createShaderBindingTable(shaderBindingTables.miss, 1);
+		createShaderBindingTable(shaderBindingTables.hit, 1);
+		// [POI] The callable shader binding table contains one shader handle per ray traced object
+		createShaderBindingTable(shaderBindingTables.callable, objectCount);
+
+		// Copy handles
+		memcpy(shaderBindingTables.raygen.mapped, shaderHandleStorage.data(), handleSize);
+		memcpy(shaderBindingTables.miss.mapped, shaderHandleStorage.data() + handleSizeAligned, handleSize);
+		memcpy(shaderBindingTables.hit.mapped, shaderHandleStorage.data() + handleSizeAligned * 2, handleSize);
+		memcpy(shaderBindingTables.callable.mapped, shaderHandleStorage.data() + handleSizeAligned * 3, handleSize * 3);
+	}
+
+	/*
+		Create the descriptor sets used for the ray tracing dispatch
+	*/
+	void createDescriptorSets()
+	{
+		std::vector<VkDescriptorPoolSize> poolSizes = {
+			{ VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1 },
+			{ VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1 },
+			{ VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1 },
+			{ VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 2 }
+		};
+		VkDescriptorPoolCreateInfo descriptorPoolCreateInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 1);
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolCreateInfo, nullptr, &descriptorPool));
+
+		VkDescriptorSetAllocateInfo descriptorSetAllocateInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1);
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &descriptorSetAllocateInfo, &descriptorSet));
+
+		VkWriteDescriptorSetAccelerationStructureKHR descriptorAccelerationStructureInfo = vks::initializers::writeDescriptorSetAccelerationStructureKHR();
+		descriptorAccelerationStructureInfo.accelerationStructureCount = 1;
+		descriptorAccelerationStructureInfo.pAccelerationStructures = &topLevelAS.handle;
+
+		VkWriteDescriptorSet accelerationStructureWrite{};
+		accelerationStructureWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+		// The specialized acceleration structure descriptor has to be chained
+		accelerationStructureWrite.pNext = &descriptorAccelerationStructureInfo;
+		accelerationStructureWrite.dstSet = descriptorSet;
+		accelerationStructureWrite.dstBinding = 0;
+		accelerationStructureWrite.descriptorCount = 1;
+		accelerationStructureWrite.descriptorType = VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR;
+
+		VkDescriptorImageInfo storageImageDescriptor{ VK_NULL_HANDLE, storageImage.view, VK_IMAGE_LAYOUT_GENERAL };
+		VkDescriptorBufferInfo vertexBufferDescriptor{ vertexBuffer.buffer, 0, VK_WHOLE_SIZE };
+		VkDescriptorBufferInfo indexBufferDescriptor{ indexBuffer.buffer, 0, VK_WHOLE_SIZE };
+
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets = {
+			// Binding 0: Top level acceleration structure
+			accelerationStructureWrite,
+			// Binding 1: Ray tracing result image
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, &storageImageDescriptor),
+			// Binding 2: Uniform data
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2, &ubo.descriptor),
+			// Binding 3: Scene vertex buffer
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 3, &vertexBufferDescriptor),
+			// Binding 4: Scene index buffer
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 4, &indexBufferDescriptor),
+		};
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, VK_NULL_HANDLE);
+	}
+
+	/*
+		Create our ray tracing pipeline
+	*/
+	void createRayTracingPipeline()
+	{
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			// Binding 0: Acceleration structure
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR, 0),
+			// Binding 1: Storage image
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_SHADER_STAGE_RAYGEN_BIT_KHR, 1),
+			// Binding 2: Uniform buffer
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_MISS_BIT_KHR, 2),
+			// Binding 3: Vertex buffer 
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR, 3),
+			// Binding 4: Index buffer
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR, 4),
+		};
+
+		VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCI = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorSetLayoutCI, nullptr, &descriptorSetLayout));
+
+		VkPipelineLayoutCreateInfo pPipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1);
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCI, nullptr, &pipelineLayout));
+	
+		/*
+			Setup ray tracing shader groups
+		*/
+		std::vector<VkPipelineShaderStageCreateInfo> shaderStages;
+		VkRayTracingShaderGroupCreateInfoKHR shaderGroup;
+
+		// Ray generation shader group
+		shaderStages.push_back(loadShader(getShadersPath() + "raytracingcallable/raygen.rgen.spv", VK_SHADER_STAGE_RAYGEN_BIT_KHR));
+		shaderGroup = vks::initializers::rayTracingShaderGroupCreateInfoKHR();
+		shaderGroup.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR;
+		shaderGroup.generalShader = static_cast<uint32_t>(shaderStages.size()) - 1;
+		shaderGroup.closestHitShader = VK_SHADER_UNUSED_KHR;
+		shaderGroup.anyHitShader = VK_SHADER_UNUSED_KHR;
+		shaderGroup.intersectionShader = VK_SHADER_UNUSED_KHR;
+		shaderGroups.push_back(shaderGroup);
+
+		// Miss shader group
+		shaderStages.push_back(loadShader(getShadersPath() + "raytracingcallable/miss.rmiss.spv", VK_SHADER_STAGE_MISS_BIT_KHR));
+		shaderGroup = vks::initializers::rayTracingShaderGroupCreateInfoKHR();
+		shaderGroup.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR;
+		shaderGroup.generalShader = static_cast<uint32_t>(shaderStages.size()) - 1;
+		shaderGroup.closestHitShader = VK_SHADER_UNUSED_KHR;
+		shaderGroup.anyHitShader = VK_SHADER_UNUSED_KHR;
+		shaderGroup.intersectionShader = VK_SHADER_UNUSED_KHR;
+		shaderGroups.push_back(shaderGroup);
+
+		// Closest hit shader group
+		shaderStages.push_back(loadShader(getShadersPath() + "raytracingcallable/closesthit.rchit.spv", VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR));
+		shaderGroup = vks::initializers::rayTracingShaderGroupCreateInfoKHR();
+		shaderGroup.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_TRIANGLES_HIT_GROUP_KHR;
+		shaderGroup.generalShader = VK_SHADER_UNUSED_KHR;
+		shaderGroup.closestHitShader = static_cast<uint32_t>(shaderStages.size()) - 1;
+		shaderGroup.anyHitShader = VK_SHADER_UNUSED_KHR;
+		shaderGroup.intersectionShader = VK_SHADER_UNUSED_KHR;
+		shaderGroups.push_back(shaderGroup);
+
+		// [POI] Callable shader group
+		// This sample's hit shader will call different callable shaders depending on the geometry index using executeCallableEXT, so as we render three geometries, we'll also use three callable shaders
+		for (uint32_t i = 0; i < objectCount; i++) 
+		{
+			shaderStages.push_back(loadShader(getShadersPath() + "raytracingcallable/callable" + std::to_string(i+1) + ".rcall.spv", VK_SHADER_STAGE_CALLABLE_BIT_KHR));
+			shaderGroup = vks::initializers::rayTracingShaderGroupCreateInfoKHR();
+			shaderGroup.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR;
+			shaderGroup.generalShader = static_cast<uint32_t>(shaderStages.size()) - 1;
+			shaderGroup.closestHitShader = VK_SHADER_UNUSED_KHR;
+			shaderGroup.anyHitShader = VK_SHADER_UNUSED_KHR;
+			shaderGroup.intersectionShader = VK_SHADER_UNUSED_KHR;
+			shaderGroups.push_back(shaderGroup);
+		}
+
+		VkRayTracingPipelineCreateInfoKHR rayTracingPipelineCI = vks::initializers::rayTracingPipelineCreateInfoKHR();
+		rayTracingPipelineCI.stageCount = static_cast<uint32_t>(shaderStages.size());
+		rayTracingPipelineCI.pStages = shaderStages.data();
+		rayTracingPipelineCI.groupCount = static_cast<uint32_t>(shaderGroups.size());
+		rayTracingPipelineCI.pGroups = shaderGroups.data();
+		rayTracingPipelineCI.maxPipelineRayRecursionDepth = 2;
+		rayTracingPipelineCI.layout = pipelineLayout;
+		VK_CHECK_RESULT(vkCreateRayTracingPipelinesKHR(device, VK_NULL_HANDLE, VK_NULL_HANDLE, 1, &rayTracingPipelineCI, nullptr, &pipeline));
+	}
+
+	/*
+		Create the uniform buffer used to pass matrices to the ray tracing ray generation shader
+	*/
+	void createUniformBuffer()
+	{
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&ubo,
+			sizeof(uniformData),
+			&uniformData));
+		VK_CHECK_RESULT(ubo.map());
+
+		updateUniformBuffers();
+	}
+
+	/*
+		If the window has been resized, we need to recreate the storage image and it's descriptor
+	*/
+	void handleResize()
+	{
+		// Recreate image
+		createStorageImage(swapChain.colorFormat, { width, height, 1 });
+		// Update descriptor
+		VkDescriptorImageInfo storageImageDescriptor{ VK_NULL_HANDLE, storageImage.view, VK_IMAGE_LAYOUT_GENERAL };
+		VkWriteDescriptorSet resultImageWrite = vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, &storageImageDescriptor);
+		vkUpdateDescriptorSets(device, 1, &resultImageWrite, 0, VK_NULL_HANDLE);
+	}
+
+	/*
+		Command buffer generation
+	*/
+	void buildCommandBuffers()
+	{
+		if (resized)
+		{
+			handleResize();
+		}
+
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkImageSubresourceRange subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 };
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			/*
+				Dispatch the ray tracing commands
+			*/
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, pipeline);
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, pipelineLayout, 0, 1, &descriptorSet, 0, 0);
+
+			vkCmdTraceRaysKHR(
+				drawCmdBuffers[i],
+				&shaderBindingTables.raygen.stridedDeviceAddressRegion,
+				&shaderBindingTables.miss.stridedDeviceAddressRegion,
+				&shaderBindingTables.hit.stridedDeviceAddressRegion,
+				&shaderBindingTables.callable.stridedDeviceAddressRegion,
+				width,
+				height,
+				1);
+
+			/*
+				Copy ray tracing output to swap chain image
+			*/
+
+			// Prepare current swap chain image as transfer destination
+			vks::tools::setImageLayout(
+				drawCmdBuffers[i],
+				swapChain.images[i],
+				VK_IMAGE_LAYOUT_UNDEFINED,
+				VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+				subresourceRange);
+
+			// Prepare ray tracing output image as transfer source
+			vks::tools::setImageLayout(
+				drawCmdBuffers[i],
+				storageImage.image,
+				VK_IMAGE_LAYOUT_GENERAL,
+				VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+				subresourceRange);
+
+			VkImageCopy copyRegion{};
+			copyRegion.srcSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 };
+			copyRegion.srcOffset = { 0, 0, 0 };
+			copyRegion.dstSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 };
+			copyRegion.dstOffset = { 0, 0, 0 };
+			copyRegion.extent = { width, height, 1 };
+			vkCmdCopyImage(drawCmdBuffers[i], storageImage.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, swapChain.images[i], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &copyRegion);
+
+			// Transition swap chain image back for presentation
+			vks::tools::setImageLayout(
+				drawCmdBuffers[i],
+				swapChain.images[i],
+				VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+				VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
+				subresourceRange);
+
+			// Transition ray tracing output image back to general layout
+			vks::tools::setImageLayout(
+				drawCmdBuffers[i],
+				storageImage.image,
+				VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+				VK_IMAGE_LAYOUT_GENERAL,
+				subresourceRange);
+
+			drawUI(drawCmdBuffers[i], frameBuffers[i]);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void updateUniformBuffers()
+	{
+		uniformData.projInverse = glm::inverse(camera.matrices.perspective);
+		uniformData.viewInverse = glm::inverse(camera.matrices.view);
+		memcpy(ubo.mapped, &uniformData, sizeof(uniformData));
+	}
+
+	void getEnabledFeatures()
+	{
+		// Enable features required for ray tracing using feature chaining via pNext		
+		enabledBufferDeviceAddresFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES;
+		enabledBufferDeviceAddresFeatures.bufferDeviceAddress = VK_TRUE;
+
+		enabledRayTracingPipelineFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_FEATURES_KHR;
+		enabledRayTracingPipelineFeatures.rayTracingPipeline = VK_TRUE;
+		enabledRayTracingPipelineFeatures.pNext = &enabledBufferDeviceAddresFeatures;
+
+		enabledAccelerationStructureFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_FEATURES_KHR;
+		enabledAccelerationStructureFeatures.accelerationStructure = VK_TRUE;
+		enabledAccelerationStructureFeatures.pNext = &enabledRayTracingPipelineFeatures;
+
+		deviceCreatepNextChain = &enabledAccelerationStructureFeatures;
+	}
+
+	void prepare()
+	{
+		VulkanRaytracingSample::prepare();
+
+		// Create the acceleration structures used to render the ray traced scene
+		createBottomLevelAccelerationStructure();
+		createTopLevelAccelerationStructure();
+
+		createStorageImage(swapChain.colorFormat, { width, height, 1 });
+		createUniformBuffer();
+		createRayTracingPipeline();
+		createShaderBindingTables();
+		createDescriptorSets();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+		VulkanExampleBase::submitFrame();
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+		if (!paused || camera.updated)
+			updateUniformBuffers();
+	}
+};
+
+VULKAN_EXAMPLE_MAIN()
diff --git a/external/Vulkan/examples/raytracingreflections/raytracingreflections.cpp b/external/Vulkan/examples/raytracingreflections/raytracingreflections.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..359b027a35f3ab9c5da2f52daf7f002a576fff1d
--- /dev/null
+++ b/external/Vulkan/examples/raytracingreflections/raytracingreflections.cpp
@@ -0,0 +1,590 @@
+/*
+* Vulkan Example - Hardware accelerated ray tracing example for doing reflections
+*
+* Renders a complex scene doing recursion inside the shaders for creating reflections
+*
+* Copyright (C) 2019-2020 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "VulkanRaytracingSample.h"
+#include "VulkanglTFModel.h"
+
+class VulkanExample : public VulkanRaytracingSample
+{
+public:
+	AccelerationStructure bottomLevelAS{};
+	AccelerationStructure topLevelAS{};
+
+	std::vector<VkRayTracingShaderGroupCreateInfoKHR> shaderGroups{};
+	struct ShaderBindingTables {
+		ShaderBindingTable raygen;
+		ShaderBindingTable miss;
+		ShaderBindingTable hit;
+	} shaderBindingTables;
+
+	struct UniformData {
+		glm::mat4 viewInverse;
+		glm::mat4 projInverse;
+		glm::vec4 lightPos;
+		int32_t vertexSize;
+	} uniformData;
+	vks::Buffer ubo;
+
+	VkPipeline pipeline;
+	VkPipelineLayout pipelineLayout;
+	VkDescriptorSet descriptorSet;
+	VkDescriptorSetLayout descriptorSetLayout;
+
+	vkglTF::Model scene;
+
+	// This sample is derived from an extended base class that saves most of the ray tracing setup boiler plate
+	VulkanExample() : VulkanRaytracingSample()
+	{
+		title = "Ray tracing reflections";
+		timerSpeed *= 0.5f;
+		camera.rotationSpeed *= 0.25f;
+		camera.type = Camera::CameraType::firstperson;
+		camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 512.0f);
+		camera.setRotation(glm::vec3(0.0f, 0.0f, 0.0f));
+		camera.setTranslation(glm::vec3(0.0f, 0.5f, -2.0f));
+		enableExtensions();
+	}
+
+	~VulkanExample()
+	{
+		vkDestroyPipeline(device, pipeline, nullptr);
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+		deleteStorageImage();
+		deleteAccelerationStructure(bottomLevelAS);
+		deleteAccelerationStructure(topLevelAS);
+		shaderBindingTables.raygen.destroy();
+		shaderBindingTables.miss.destroy();
+		shaderBindingTables.hit.destroy();
+		ubo.destroy();
+	}
+
+	/*
+		Create the bottom level acceleration structure contains the scene's actual geometry (vertices, triangles)
+	*/
+	void createBottomLevelAccelerationStructure()
+	{
+		// Instead of a simple triangle, we'll be loading a more complex scene for this example
+		// The shaders are accessing the vertex and index buffers of the scene, so the proper usage flag has to be set on the vertex and index buffers for the scene
+		vkglTF::memoryPropertyFlags = VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
+		const uint32_t glTFLoadingFlags = vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::PreMultiplyVertexColors | vkglTF::FileLoadingFlags::FlipY;
+		scene.loadFromFile(getAssetPath() + "models/reflection_scene.gltf", vulkanDevice, queue, glTFLoadingFlags);
+
+		VkDeviceOrHostAddressConstKHR vertexBufferDeviceAddress{};
+		VkDeviceOrHostAddressConstKHR indexBufferDeviceAddress{};
+
+		vertexBufferDeviceAddress.deviceAddress = getBufferDeviceAddress(scene.vertices.buffer);
+		indexBufferDeviceAddress.deviceAddress = getBufferDeviceAddress(scene.indices.buffer);
+
+		uint32_t numTriangles = static_cast<uint32_t>(scene.indices.count) / 3;
+		uint32_t maxVertex = scene.vertices.count;
+
+		// Build
+		VkAccelerationStructureGeometryKHR accelerationStructureGeometry = vks::initializers::accelerationStructureGeometryKHR();
+		accelerationStructureGeometry.flags = VK_GEOMETRY_OPAQUE_BIT_KHR;
+		accelerationStructureGeometry.geometryType = VK_GEOMETRY_TYPE_TRIANGLES_KHR;
+		accelerationStructureGeometry.geometry.triangles.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR;
+		accelerationStructureGeometry.geometry.triangles.vertexFormat = VK_FORMAT_R32G32B32_SFLOAT;
+		accelerationStructureGeometry.geometry.triangles.vertexData = vertexBufferDeviceAddress;
+		accelerationStructureGeometry.geometry.triangles.maxVertex = maxVertex;
+		accelerationStructureGeometry.geometry.triangles.vertexStride = sizeof(vkglTF::Vertex);
+		accelerationStructureGeometry.geometry.triangles.indexType = VK_INDEX_TYPE_UINT32;
+		accelerationStructureGeometry.geometry.triangles.indexData = indexBufferDeviceAddress;
+		accelerationStructureGeometry.geometry.triangles.transformData.deviceAddress = 0;
+		accelerationStructureGeometry.geometry.triangles.transformData.hostAddress = nullptr;
+
+		// Get size info
+		VkAccelerationStructureBuildGeometryInfoKHR accelerationStructureBuildGeometryInfo = vks::initializers::accelerationStructureBuildGeometryInfoKHR();
+		accelerationStructureBuildGeometryInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR;
+		accelerationStructureBuildGeometryInfo.flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR;
+		accelerationStructureBuildGeometryInfo.geometryCount = 1;
+		accelerationStructureBuildGeometryInfo.pGeometries = &accelerationStructureGeometry;
+
+		VkAccelerationStructureBuildSizesInfoKHR accelerationStructureBuildSizesInfo = vks::initializers::accelerationStructureBuildSizesInfoKHR();
+		vkGetAccelerationStructureBuildSizesKHR(
+			device,
+			VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR,
+			&accelerationStructureBuildGeometryInfo,
+			&numTriangles,
+			&accelerationStructureBuildSizesInfo);
+
+		createAccelerationStructure(bottomLevelAS, VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR, accelerationStructureBuildSizesInfo);
+
+		// Create a small scratch buffer used during build of the bottom level acceleration structure
+		ScratchBuffer scratchBuffer = createScratchBuffer(accelerationStructureBuildSizesInfo.buildScratchSize);
+
+		VkAccelerationStructureBuildGeometryInfoKHR accelerationBuildGeometryInfo = vks::initializers::accelerationStructureBuildGeometryInfoKHR();
+		accelerationBuildGeometryInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR;
+		accelerationBuildGeometryInfo.flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR;
+		accelerationBuildGeometryInfo.mode = VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR;
+		accelerationBuildGeometryInfo.dstAccelerationStructure = bottomLevelAS.handle;
+		accelerationBuildGeometryInfo.geometryCount = 1;
+		accelerationBuildGeometryInfo.pGeometries = &accelerationStructureGeometry;
+		accelerationBuildGeometryInfo.scratchData.deviceAddress = scratchBuffer.deviceAddress;
+
+		VkAccelerationStructureBuildRangeInfoKHR accelerationStructureBuildRangeInfo{};
+		accelerationStructureBuildRangeInfo.primitiveCount = numTriangles;
+		accelerationStructureBuildRangeInfo.primitiveOffset = 0;
+		accelerationStructureBuildRangeInfo.firstVertex = 0;
+		accelerationStructureBuildRangeInfo.transformOffset = 0;
+		std::vector<VkAccelerationStructureBuildRangeInfoKHR*> accelerationBuildStructureRangeInfos = { &accelerationStructureBuildRangeInfo };
+
+		// Build the acceleration structure on the device via a one-time command buffer submission
+		// Some implementations may support acceleration structure building on the host (VkPhysicalDeviceAccelerationStructureFeaturesKHR->accelerationStructureHostCommands), but we prefer device builds
+		VkCommandBuffer commandBuffer = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+		vkCmdBuildAccelerationStructuresKHR(
+			commandBuffer,
+			1,
+			&accelerationBuildGeometryInfo,
+			accelerationBuildStructureRangeInfos.data());
+		vulkanDevice->flushCommandBuffer(commandBuffer, queue);
+
+		deleteScratchBuffer(scratchBuffer);
+	}
+
+	/*
+		The top level acceleration structure contains the scene's object instances
+	*/
+	void createTopLevelAccelerationStructure()
+	{
+		VkTransformMatrixKHR transformMatrix = {
+			1.0f, 0.0f, 0.0f, 0.0f,
+			0.0f, 1.0f, 0.0f, 0.0f,
+			0.0f, 0.0f, 1.0f, 0.0f };
+
+		VkAccelerationStructureInstanceKHR instance{};
+		instance.transform = transformMatrix;
+		instance.instanceCustomIndex = 0;
+		instance.mask = 0xFF;
+		instance.instanceShaderBindingTableRecordOffset = 0;
+		instance.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR;
+		instance.accelerationStructureReference = bottomLevelAS.deviceAddress;
+
+		// Buffer for instance data
+		vks::Buffer instancesBuffer;
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&instancesBuffer,
+			sizeof(VkAccelerationStructureInstanceKHR),
+			&instance));
+
+		VkDeviceOrHostAddressConstKHR instanceDataDeviceAddress{};
+		instanceDataDeviceAddress.deviceAddress = getBufferDeviceAddress(instancesBuffer.buffer);
+
+		VkAccelerationStructureGeometryKHR accelerationStructureGeometry = vks::initializers::accelerationStructureGeometryKHR();
+		accelerationStructureGeometry.geometryType = VK_GEOMETRY_TYPE_INSTANCES_KHR;
+		accelerationStructureGeometry.flags = VK_GEOMETRY_OPAQUE_BIT_KHR;
+		accelerationStructureGeometry.geometry.instances.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_INSTANCES_DATA_KHR;
+		accelerationStructureGeometry.geometry.instances.arrayOfPointers = VK_FALSE;
+		accelerationStructureGeometry.geometry.instances.data = instanceDataDeviceAddress;
+
+		// Get size info
+		VkAccelerationStructureBuildGeometryInfoKHR accelerationStructureBuildGeometryInfo = vks::initializers::accelerationStructureBuildGeometryInfoKHR();
+		accelerationStructureBuildGeometryInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR;
+		accelerationStructureBuildGeometryInfo.flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR;
+		accelerationStructureBuildGeometryInfo.geometryCount = 1;
+		accelerationStructureBuildGeometryInfo.pGeometries = &accelerationStructureGeometry;
+
+		uint32_t primitive_count = 1;
+
+		VkAccelerationStructureBuildSizesInfoKHR accelerationStructureBuildSizesInfo = vks::initializers::accelerationStructureBuildSizesInfoKHR();
+		vkGetAccelerationStructureBuildSizesKHR(
+			device,
+			VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR,
+			&accelerationStructureBuildGeometryInfo,
+			&primitive_count,
+			&accelerationStructureBuildSizesInfo);
+
+		createAccelerationStructure(topLevelAS, VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR, accelerationStructureBuildSizesInfo);
+
+		// Create a small scratch buffer used during build of the top level acceleration structure
+		ScratchBuffer scratchBuffer = createScratchBuffer(accelerationStructureBuildSizesInfo.buildScratchSize);
+
+		VkAccelerationStructureBuildGeometryInfoKHR accelerationBuildGeometryInfo = vks::initializers::accelerationStructureBuildGeometryInfoKHR();
+		accelerationBuildGeometryInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR;
+		accelerationBuildGeometryInfo.flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR;
+		accelerationBuildGeometryInfo.mode = VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR;
+		accelerationBuildGeometryInfo.dstAccelerationStructure = topLevelAS.handle;
+		accelerationBuildGeometryInfo.geometryCount = 1;
+		accelerationBuildGeometryInfo.pGeometries = &accelerationStructureGeometry;
+		accelerationBuildGeometryInfo.scratchData.deviceAddress = scratchBuffer.deviceAddress;
+
+		VkAccelerationStructureBuildRangeInfoKHR accelerationStructureBuildRangeInfo{};
+		accelerationStructureBuildRangeInfo.primitiveCount = 1;
+		accelerationStructureBuildRangeInfo.primitiveOffset = 0;
+		accelerationStructureBuildRangeInfo.firstVertex = 0;
+		accelerationStructureBuildRangeInfo.transformOffset = 0;
+		std::vector<VkAccelerationStructureBuildRangeInfoKHR*> accelerationBuildStructureRangeInfos = { &accelerationStructureBuildRangeInfo };
+
+		// Build the acceleration structure on the device via a one-time command buffer submission
+		// Some implementations may support acceleration structure building on the host (VkPhysicalDeviceAccelerationStructureFeaturesKHR->accelerationStructureHostCommands), but we prefer device builds
+		VkCommandBuffer commandBuffer = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+		vkCmdBuildAccelerationStructuresKHR(
+			commandBuffer,
+			1,
+			&accelerationBuildGeometryInfo,
+			accelerationBuildStructureRangeInfos.data());
+		vulkanDevice->flushCommandBuffer(commandBuffer, queue);
+
+		deleteScratchBuffer(scratchBuffer);
+		instancesBuffer.destroy();
+	}
+
+	/*
+		Create the Shader Binding Tables that binds the programs and top-level acceleration structure
+
+		SBT Layout used in this sample:
+
+			/-----------\
+			| raygen    |
+			|-----------|
+			| miss      |
+			|-----------|
+			| hit       |
+			\-----------/
+
+	*/
+	void createShaderBindingTables() {
+		const uint32_t handleSize = rayTracingPipelineProperties.shaderGroupHandleSize;
+		const uint32_t handleSizeAligned = vks::tools::alignedSize(rayTracingPipelineProperties.shaderGroupHandleSize, rayTracingPipelineProperties.shaderGroupHandleAlignment);
+		const uint32_t groupCount = static_cast<uint32_t>(shaderGroups.size());
+		const uint32_t sbtSize = groupCount * handleSizeAligned;
+
+		std::vector<uint8_t> shaderHandleStorage(sbtSize);
+		VK_CHECK_RESULT(vkGetRayTracingShaderGroupHandlesKHR(device, pipeline, 0, groupCount, sbtSize, shaderHandleStorage.data()));
+
+		createShaderBindingTable(shaderBindingTables.raygen, 1);
+		createShaderBindingTable(shaderBindingTables.miss, 1);
+		createShaderBindingTable(shaderBindingTables.hit, 1);
+
+		// Copy handles
+		memcpy(shaderBindingTables.raygen.mapped, shaderHandleStorage.data(), handleSize);
+		memcpy(shaderBindingTables.miss.mapped, shaderHandleStorage.data() + handleSizeAligned, handleSize);
+		memcpy(shaderBindingTables.hit.mapped, shaderHandleStorage.data() + handleSizeAligned * 2, handleSize);
+	}
+
+	/*
+		Create the descriptor sets used for the ray tracing dispatch
+	*/
+	void createDescriptorSets()
+	{
+		std::vector<VkDescriptorPoolSize> poolSizes = {
+			{ VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1 },
+			{ VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1 },
+			{ VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1 },
+			{ VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 2 }
+		};
+		VkDescriptorPoolCreateInfo descriptorPoolCreateInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 1);
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolCreateInfo, nullptr, &descriptorPool));
+
+		VkDescriptorSetAllocateInfo descriptorSetAllocateInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1);
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &descriptorSetAllocateInfo, &descriptorSet));
+
+		VkWriteDescriptorSetAccelerationStructureKHR descriptorAccelerationStructureInfo = vks::initializers::writeDescriptorSetAccelerationStructureKHR();
+		descriptorAccelerationStructureInfo.accelerationStructureCount = 1;
+		descriptorAccelerationStructureInfo.pAccelerationStructures = &topLevelAS.handle;
+
+		VkWriteDescriptorSet accelerationStructureWrite{};
+		accelerationStructureWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+		// The specialized acceleration structure descriptor has to be chained
+		accelerationStructureWrite.pNext = &descriptorAccelerationStructureInfo;
+		accelerationStructureWrite.dstSet = descriptorSet;
+		accelerationStructureWrite.dstBinding = 0;
+		accelerationStructureWrite.descriptorCount = 1;
+		accelerationStructureWrite.descriptorType = VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR;
+
+		VkDescriptorImageInfo storageImageDescriptor{ VK_NULL_HANDLE, storageImage.view, VK_IMAGE_LAYOUT_GENERAL };
+		VkDescriptorBufferInfo vertexBufferDescriptor{ scene.vertices.buffer, 0, VK_WHOLE_SIZE };
+		VkDescriptorBufferInfo indexBufferDescriptor{ scene.indices.buffer, 0, VK_WHOLE_SIZE };
+
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets = {
+			// Binding 0: Top level acceleration structure
+			accelerationStructureWrite,
+			// Binding 1: Ray tracing result image
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, &storageImageDescriptor),
+			// Binding 2: Uniform data
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2, &ubo.descriptor),
+			// Binding 3: Scene vertex buffer
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 3, &vertexBufferDescriptor),
+			// Binding 4: Scene index buffer
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 4, &indexBufferDescriptor),
+		};
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, VK_NULL_HANDLE);
+	}
+
+	/*
+		Create our ray tracing pipeline
+	*/
+	void createRayTracingPipeline()
+	{
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			// Binding 0: Acceleration structure
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR, 0),
+			// Binding 1: Storage image
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_SHADER_STAGE_RAYGEN_BIT_KHR, 1),
+			// Binding 2: Uniform buffer
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_MISS_BIT_KHR, 2),
+			// Binding 3: Vertex buffer 
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR, 3),
+			// Binding 4: Index buffer
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR, 4),
+		};
+
+		VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCI = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorSetLayoutCI, nullptr, &descriptorSetLayout));
+
+		VkPipelineLayoutCreateInfo pPipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1);
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCI, nullptr, &pipelineLayout));
+
+		/*
+			Setup ray tracing shader groups
+		*/
+		std::vector<VkPipelineShaderStageCreateInfo> shaderStages;
+
+		VkSpecializationMapEntry specializationMapEntry = vks::initializers::specializationMapEntry(0, 0, sizeof(uint32_t));
+		uint32_t maxRecursion = 4;
+		VkSpecializationInfo specializationInfo = vks::initializers::specializationInfo(1, &specializationMapEntry, sizeof(maxRecursion), &maxRecursion);
+
+		// Ray generation group
+		{
+			shaderStages.push_back(loadShader(getShadersPath() + "raytracingreflections/raygen.rgen.spv", VK_SHADER_STAGE_RAYGEN_BIT_KHR));
+			// Pass recursion depth for reflections to ray generation shader via specialization constant
+			shaderStages.back().pSpecializationInfo = &specializationInfo;
+			VkRayTracingShaderGroupCreateInfoKHR shaderGroup{};
+			shaderGroup.sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR;
+			shaderGroup.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR;
+			shaderGroup.generalShader = static_cast<uint32_t>(shaderStages.size()) - 1;
+			shaderGroup.closestHitShader = VK_SHADER_UNUSED_KHR;
+			shaderGroup.anyHitShader = VK_SHADER_UNUSED_KHR;
+			shaderGroup.intersectionShader = VK_SHADER_UNUSED_KHR;
+			shaderGroups.push_back(shaderGroup);
+		}
+
+		// Miss group
+		{
+			shaderStages.push_back(loadShader(getShadersPath() + "raytracingreflections/miss.rmiss.spv", VK_SHADER_STAGE_MISS_BIT_KHR));
+			VkRayTracingShaderGroupCreateInfoKHR shaderGroup{};
+			shaderGroup.sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR;
+			shaderGroup.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR;
+			shaderGroup.generalShader = static_cast<uint32_t>(shaderStages.size()) - 1;
+			shaderGroup.closestHitShader = VK_SHADER_UNUSED_KHR;
+			shaderGroup.anyHitShader = VK_SHADER_UNUSED_KHR;
+			shaderGroup.intersectionShader = VK_SHADER_UNUSED_KHR;
+			shaderGroups.push_back(shaderGroup);
+		}
+
+		// Closest hit group
+		{
+			shaderStages.push_back(loadShader(getShadersPath() + "raytracingreflections/closesthit.rchit.spv", VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR));
+			VkRayTracingShaderGroupCreateInfoKHR shaderGroup{};
+			shaderGroup.sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR;
+			shaderGroup.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_TRIANGLES_HIT_GROUP_KHR;
+			shaderGroup.generalShader = VK_SHADER_UNUSED_KHR;
+			shaderGroup.closestHitShader = static_cast<uint32_t>(shaderStages.size()) - 1;
+			shaderGroup.anyHitShader = VK_SHADER_UNUSED_KHR;
+			shaderGroup.intersectionShader = VK_SHADER_UNUSED_KHR;
+			shaderGroups.push_back(shaderGroup);
+		}
+
+		VkRayTracingPipelineCreateInfoKHR rayTracingPipelineCI = vks::initializers::rayTracingPipelineCreateInfoKHR();
+		rayTracingPipelineCI.stageCount = static_cast<uint32_t>(shaderStages.size());
+		rayTracingPipelineCI.pStages = shaderStages.data();
+		rayTracingPipelineCI.groupCount = static_cast<uint32_t>(shaderGroups.size());
+		rayTracingPipelineCI.pGroups = shaderGroups.data();
+		rayTracingPipelineCI.maxPipelineRayRecursionDepth = 4;
+		rayTracingPipelineCI.layout = pipelineLayout;
+		VK_CHECK_RESULT(vkCreateRayTracingPipelinesKHR(device, VK_NULL_HANDLE, VK_NULL_HANDLE, 1, &rayTracingPipelineCI, nullptr, &pipeline));
+	}
+
+	/*
+		Create the uniform buffer used to pass matrices to the ray tracing ray generation shader
+	*/
+	void createUniformBuffer()
+	{
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&ubo,
+			sizeof(uniformData),
+			&uniformData));
+		VK_CHECK_RESULT(ubo.map());
+
+		updateUniformBuffers();
+	}
+
+	/*
+		If the window has been resized, we need to recreate the storage image and it's descriptor
+	*/
+	void handleResize()
+	{
+		// Recreate image
+		createStorageImage(swapChain.colorFormat, { width, height, 1 });
+		// Update descriptor
+		VkDescriptorImageInfo storageImageDescriptor{ VK_NULL_HANDLE, storageImage.view, VK_IMAGE_LAYOUT_GENERAL };
+		VkWriteDescriptorSet resultImageWrite = vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, &storageImageDescriptor);
+		vkUpdateDescriptorSets(device, 1, &resultImageWrite, 0, VK_NULL_HANDLE);
+	}
+
+	/*
+		Command buffer generation
+	*/
+	void buildCommandBuffers()
+	{
+		if (resized)
+		{
+			handleResize();
+		}
+
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkImageSubresourceRange subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 };
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			/*
+				Dispatch the ray tracing commands
+			*/
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, pipeline);
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, pipelineLayout, 0, 1, &descriptorSet, 0, 0);
+
+			/*
+				Dispatch the ray tracing commands
+			*/
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, pipeline);
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, pipelineLayout, 0, 1, &descriptorSet, 0, 0);
+
+			VkStridedDeviceAddressRegionKHR emptySbtEntry = {};
+			vkCmdTraceRaysKHR(
+				drawCmdBuffers[i],
+				&shaderBindingTables.raygen.stridedDeviceAddressRegion,
+				&shaderBindingTables.miss.stridedDeviceAddressRegion,
+				&shaderBindingTables.hit.stridedDeviceAddressRegion,
+				&emptySbtEntry,
+				width,
+				height,
+				1);
+
+			/*
+				Copy ray tracing output to swap chain image
+			*/
+
+			// Prepare current swap chain image as transfer destination
+			vks::tools::setImageLayout(
+				drawCmdBuffers[i],
+				swapChain.images[i],
+				VK_IMAGE_LAYOUT_UNDEFINED,
+				VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+				subresourceRange);
+
+			// Prepare ray tracing output image as transfer source
+			vks::tools::setImageLayout(
+				drawCmdBuffers[i],
+				storageImage.image,
+				VK_IMAGE_LAYOUT_GENERAL,
+				VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+				subresourceRange);
+
+			VkImageCopy copyRegion{};
+			copyRegion.srcSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 };
+			copyRegion.srcOffset = { 0, 0, 0 };
+			copyRegion.dstSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 };
+			copyRegion.dstOffset = { 0, 0, 0 };
+			copyRegion.extent = { width, height, 1 };
+			vkCmdCopyImage(drawCmdBuffers[i], storageImage.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, swapChain.images[i], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &copyRegion);
+
+			// Transition swap chain image back for presentation
+			vks::tools::setImageLayout(
+				drawCmdBuffers[i],
+				swapChain.images[i],
+				VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+				VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
+				subresourceRange);
+
+			// Transition ray tracing output image back to general layout
+			vks::tools::setImageLayout(
+				drawCmdBuffers[i],
+				storageImage.image,
+				VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+				VK_IMAGE_LAYOUT_GENERAL,
+				subresourceRange);
+
+			drawUI(drawCmdBuffers[i], frameBuffers[i]);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void updateUniformBuffers()
+	{
+		uniformData.projInverse = glm::inverse(camera.matrices.perspective);
+		uniformData.viewInverse = glm::inverse(camera.matrices.view);
+		uniformData.lightPos = glm::vec4(cos(glm::radians(timer * 360.0f)) * 40.0f, -20.0f + sin(glm::radians(timer * 360.0f)) * 20.0f, 25.0f + sin(glm::radians(timer * 360.0f)) * 5.0f, 0.0f);
+		// Pass the vertex size to the shader for unpacking vertices
+		uniformData.vertexSize = sizeof(vkglTF::Vertex);
+		memcpy(ubo.mapped, &uniformData, sizeof(uniformData));
+	}
+
+	void getEnabledFeatures()
+	{
+		// Enable features required for ray tracing using feature chaining via pNext		
+		enabledBufferDeviceAddresFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES;
+		enabledBufferDeviceAddresFeatures.bufferDeviceAddress = VK_TRUE;
+
+		enabledRayTracingPipelineFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_FEATURES_KHR;
+		enabledRayTracingPipelineFeatures.rayTracingPipeline = VK_TRUE;
+		enabledRayTracingPipelineFeatures.pNext = &enabledBufferDeviceAddresFeatures;
+
+		enabledAccelerationStructureFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_FEATURES_KHR;
+		enabledAccelerationStructureFeatures.accelerationStructure = VK_TRUE;
+		enabledAccelerationStructureFeatures.pNext = &enabledRayTracingPipelineFeatures;
+
+		deviceCreatepNextChain = &enabledAccelerationStructureFeatures;
+	}
+
+	void prepare()
+	{
+		VulkanRaytracingSample::prepare();
+
+		// Create the acceleration structures used to render the ray traced scene
+		createBottomLevelAccelerationStructure();
+		createTopLevelAccelerationStructure();
+
+		createStorageImage(swapChain.colorFormat, { width, height, 1 });
+		createUniformBuffer();
+		createRayTracingPipeline();
+		createShaderBindingTables();
+		createDescriptorSets();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+		VulkanExampleBase::submitFrame();
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+		if (!paused || camera.updated)
+			updateUniformBuffers();
+	}
+};
+
+VULKAN_EXAMPLE_MAIN()
diff --git a/external/Vulkan/examples/raytracingshadows/raytracingshadows.cpp b/external/Vulkan/examples/raytracingshadows/raytracingshadows.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..bdd08b56a3032baddf65ca8657bcb8252e51ea81
--- /dev/null
+++ b/external/Vulkan/examples/raytracingshadows/raytracingshadows.cpp
@@ -0,0 +1,585 @@
+/*
+* Vulkan Example - Hardware accelerated ray tracing shadow example
+*
+* Renders a complex scene using multiple hit and miss shaders for implementing shadows
+*
+* Copyright (C) by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "VulkanRaytracingSample.h"
+#include "VulkanglTFModel.h"
+
+class VulkanExample : public VulkanRaytracingSample
+{
+public:
+	AccelerationStructure bottomLevelAS;
+	AccelerationStructure topLevelAS;
+
+	std::vector<VkRayTracingShaderGroupCreateInfoKHR> shaderGroups{};
+	struct ShaderBindingTables {
+		ShaderBindingTable raygen;
+		ShaderBindingTable miss;
+		ShaderBindingTable hit;
+	} shaderBindingTables;
+
+	struct UniformData {
+		glm::mat4 viewInverse;
+		glm::mat4 projInverse;
+		glm::vec4 lightPos;
+		int32_t vertexSize;
+	} uniformData;
+	vks::Buffer ubo;
+
+	VkPipeline pipeline;
+	VkPipelineLayout pipelineLayout;
+	VkDescriptorSet descriptorSet;
+	VkDescriptorSetLayout descriptorSetLayout;
+
+	vkglTF::Model scene;
+
+	// This sample is derived from an extended base class that saves most of the ray tracing setup boiler plate
+	VulkanExample() : VulkanRaytracingSample()
+	{
+		title = "Ray traced shadows";
+		timerSpeed *= 0.25f;
+		camera.type = Camera::CameraType::lookat;
+		camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 512.0f);
+		camera.setRotation(glm::vec3(0.0f, 0.0f, 0.0f));
+		camera.setTranslation(glm::vec3(0.0f, 3.0f, -10.0f));
+		enableExtensions();
+	}
+
+	~VulkanExample()
+	{
+		vkDestroyPipeline(device, pipeline, nullptr);
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+		deleteStorageImage();
+		deleteAccelerationStructure(bottomLevelAS);
+		deleteAccelerationStructure(topLevelAS);
+		shaderBindingTables.raygen.destroy();
+		shaderBindingTables.miss.destroy();
+		shaderBindingTables.hit.destroy();
+		ubo.destroy();
+	}
+
+	/*
+		Create the bottom level acceleration structure contains the scene's actual geometry (vertices, triangles)
+	*/
+	void createBottomLevelAccelerationStructure()
+	{
+		// Instead of a simple triangle, we'll be loading a more complex scene for this example
+		// The shaders are accessing the vertex and index buffers of the scene, so the proper usage flag has to be set on the vertex and index buffers for the scene
+		vkglTF::memoryPropertyFlags = VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
+		const uint32_t glTFLoadingFlags = vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::PreMultiplyVertexColors | vkglTF::FileLoadingFlags::FlipY;
+		scene.loadFromFile(getAssetPath() + "models/vulkanscene_shadow.gltf", vulkanDevice, queue, glTFLoadingFlags);
+
+		VkDeviceOrHostAddressConstKHR vertexBufferDeviceAddress{};
+		VkDeviceOrHostAddressConstKHR indexBufferDeviceAddress{};
+
+		vertexBufferDeviceAddress.deviceAddress = getBufferDeviceAddress(scene.vertices.buffer);
+		indexBufferDeviceAddress.deviceAddress = getBufferDeviceAddress(scene.indices.buffer);
+
+		uint32_t numTriangles = static_cast<uint32_t>(scene.indices.count) / 3;
+		uint32_t maxVertex = scene.vertices.count;
+
+		// Build
+		VkAccelerationStructureGeometryKHR accelerationStructureGeometry = vks::initializers::accelerationStructureGeometryKHR();
+		accelerationStructureGeometry.flags = VK_GEOMETRY_OPAQUE_BIT_KHR;
+		accelerationStructureGeometry.geometryType = VK_GEOMETRY_TYPE_TRIANGLES_KHR;
+		accelerationStructureGeometry.geometry.triangles.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR;
+		accelerationStructureGeometry.geometry.triangles.vertexFormat = VK_FORMAT_R32G32B32_SFLOAT;
+		accelerationStructureGeometry.geometry.triangles.vertexData = vertexBufferDeviceAddress;
+		accelerationStructureGeometry.geometry.triangles.maxVertex = maxVertex;
+		accelerationStructureGeometry.geometry.triangles.vertexStride = sizeof(vkglTF::Vertex);
+		accelerationStructureGeometry.geometry.triangles.indexType = VK_INDEX_TYPE_UINT32;
+		accelerationStructureGeometry.geometry.triangles.indexData = indexBufferDeviceAddress;
+		accelerationStructureGeometry.geometry.triangles.transformData.deviceAddress = 0;
+		accelerationStructureGeometry.geometry.triangles.transformData.hostAddress = nullptr;
+
+		// Get size info
+		VkAccelerationStructureBuildGeometryInfoKHR accelerationStructureBuildGeometryInfo = vks::initializers::accelerationStructureBuildGeometryInfoKHR();
+		accelerationStructureBuildGeometryInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR;
+		accelerationStructureBuildGeometryInfo.flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR;
+		accelerationStructureBuildGeometryInfo.geometryCount = 1;
+		accelerationStructureBuildGeometryInfo.pGeometries = &accelerationStructureGeometry;
+
+		VkAccelerationStructureBuildSizesInfoKHR accelerationStructureBuildSizesInfo = vks::initializers::accelerationStructureBuildSizesInfoKHR();
+		vkGetAccelerationStructureBuildSizesKHR(
+			device,
+			VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR,
+			&accelerationStructureBuildGeometryInfo,
+			&numTriangles,
+			&accelerationStructureBuildSizesInfo);
+
+		createAccelerationStructure(bottomLevelAS, VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR, accelerationStructureBuildSizesInfo);
+
+		// Create a small scratch buffer used during build of the bottom level acceleration structure
+		ScratchBuffer scratchBuffer = createScratchBuffer(accelerationStructureBuildSizesInfo.buildScratchSize);
+
+		VkAccelerationStructureBuildGeometryInfoKHR accelerationBuildGeometryInfo = vks::initializers::accelerationStructureBuildGeometryInfoKHR();
+		accelerationBuildGeometryInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR;
+		accelerationBuildGeometryInfo.flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR;
+		accelerationBuildGeometryInfo.mode = VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR;
+		accelerationBuildGeometryInfo.dstAccelerationStructure = bottomLevelAS.handle;
+		accelerationBuildGeometryInfo.geometryCount = 1;
+		accelerationBuildGeometryInfo.pGeometries = &accelerationStructureGeometry;
+		accelerationBuildGeometryInfo.scratchData.deviceAddress = scratchBuffer.deviceAddress;
+
+		VkAccelerationStructureBuildRangeInfoKHR accelerationStructureBuildRangeInfo{};
+		accelerationStructureBuildRangeInfo.primitiveCount = numTriangles;
+		accelerationStructureBuildRangeInfo.primitiveOffset = 0;
+		accelerationStructureBuildRangeInfo.firstVertex = 0;
+		accelerationStructureBuildRangeInfo.transformOffset = 0;
+		std::vector<VkAccelerationStructureBuildRangeInfoKHR*> accelerationBuildStructureRangeInfos = { &accelerationStructureBuildRangeInfo };
+
+		// Build the acceleration structure on the device via a one-time command buffer submission
+		// Some implementations may support acceleration structure building on the host (VkPhysicalDeviceAccelerationStructureFeaturesKHR->accelerationStructureHostCommands), but we prefer device builds
+		VkCommandBuffer commandBuffer = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+		vkCmdBuildAccelerationStructuresKHR(
+			commandBuffer,
+			1,
+			&accelerationBuildGeometryInfo,
+			accelerationBuildStructureRangeInfos.data());
+		vulkanDevice->flushCommandBuffer(commandBuffer, queue);
+
+		deleteScratchBuffer(scratchBuffer);
+	}
+
+	/*
+		The top level acceleration structure contains the scene's object instances
+	*/
+	void createTopLevelAccelerationStructure()
+	{
+		VkTransformMatrixKHR transformMatrix = {
+			1.0f, 0.0f, 0.0f, 0.0f,
+			0.0f, 1.0f, 0.0f, 0.0f,
+			0.0f, 0.0f, 1.0f, 0.0f };
+
+		VkAccelerationStructureInstanceKHR instance{};
+		instance.transform = transformMatrix;
+		instance.instanceCustomIndex = 0;
+		instance.mask = 0xFF;
+		instance.instanceShaderBindingTableRecordOffset = 0;
+		instance.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR;
+		instance.accelerationStructureReference = bottomLevelAS.deviceAddress;
+
+		// Buffer for instance data
+		vks::Buffer instancesBuffer;
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&instancesBuffer,
+			sizeof(VkAccelerationStructureInstanceKHR),
+			&instance));
+
+		VkDeviceOrHostAddressConstKHR instanceDataDeviceAddress{};
+		instanceDataDeviceAddress.deviceAddress = getBufferDeviceAddress(instancesBuffer.buffer);
+
+		VkAccelerationStructureGeometryKHR accelerationStructureGeometry = vks::initializers::accelerationStructureGeometryKHR();
+		accelerationStructureGeometry.geometryType = VK_GEOMETRY_TYPE_INSTANCES_KHR;
+		accelerationStructureGeometry.flags = VK_GEOMETRY_OPAQUE_BIT_KHR;
+		accelerationStructureGeometry.geometry.instances.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_INSTANCES_DATA_KHR;
+		accelerationStructureGeometry.geometry.instances.arrayOfPointers = VK_FALSE;
+		accelerationStructureGeometry.geometry.instances.data = instanceDataDeviceAddress;
+
+		// Get size info
+		VkAccelerationStructureBuildGeometryInfoKHR accelerationStructureBuildGeometryInfo = vks::initializers::accelerationStructureBuildGeometryInfoKHR();
+		accelerationStructureBuildGeometryInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR;
+		accelerationStructureBuildGeometryInfo.flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR;
+		accelerationStructureBuildGeometryInfo.geometryCount = 1;
+		accelerationStructureBuildGeometryInfo.pGeometries = &accelerationStructureGeometry;
+
+		uint32_t primitive_count = 1;
+
+		VkAccelerationStructureBuildSizesInfoKHR accelerationStructureBuildSizesInfo = vks::initializers::accelerationStructureBuildSizesInfoKHR();
+		vkGetAccelerationStructureBuildSizesKHR(
+			device,
+			VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR,
+			&accelerationStructureBuildGeometryInfo,
+			&primitive_count,
+			&accelerationStructureBuildSizesInfo);
+
+		// @todo: as return value?
+		createAccelerationStructure(topLevelAS, VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR, accelerationStructureBuildSizesInfo);
+
+		// Create a small scratch buffer used during build of the top level acceleration structure
+		ScratchBuffer scratchBuffer = createScratchBuffer(accelerationStructureBuildSizesInfo.buildScratchSize);
+
+		VkAccelerationStructureBuildGeometryInfoKHR accelerationBuildGeometryInfo = vks::initializers::accelerationStructureBuildGeometryInfoKHR();
+		accelerationBuildGeometryInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR;
+		accelerationBuildGeometryInfo.flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR;
+		accelerationBuildGeometryInfo.mode = VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR;
+		accelerationBuildGeometryInfo.dstAccelerationStructure = topLevelAS.handle;
+		accelerationBuildGeometryInfo.geometryCount = 1;
+		accelerationBuildGeometryInfo.pGeometries = &accelerationStructureGeometry;
+		accelerationBuildGeometryInfo.scratchData.deviceAddress = scratchBuffer.deviceAddress;
+
+		VkAccelerationStructureBuildRangeInfoKHR accelerationStructureBuildRangeInfo{};
+		accelerationStructureBuildRangeInfo.primitiveCount = 1;
+		accelerationStructureBuildRangeInfo.primitiveOffset = 0;
+		accelerationStructureBuildRangeInfo.firstVertex = 0;
+		accelerationStructureBuildRangeInfo.transformOffset = 0;
+		std::vector<VkAccelerationStructureBuildRangeInfoKHR*> accelerationBuildStructureRangeInfos = { &accelerationStructureBuildRangeInfo };
+
+		// Build the acceleration structure on the device via a one-time command buffer submission
+		// Some implementations may support acceleration structure building on the host (VkPhysicalDeviceAccelerationStructureFeaturesKHR->accelerationStructureHostCommands), but we prefer device builds
+		VkCommandBuffer commandBuffer = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+		vkCmdBuildAccelerationStructuresKHR(
+			commandBuffer,
+			1,
+			&accelerationBuildGeometryInfo,
+			accelerationBuildStructureRangeInfos.data());
+		vulkanDevice->flushCommandBuffer(commandBuffer, queue);
+
+		deleteScratchBuffer(scratchBuffer);
+		instancesBuffer.destroy();
+	}
+
+
+	/*
+		Create the Shader Binding Tables that binds the programs and top-level acceleration structure
+
+		SBT Layout used in this sample:
+
+			/-----------\
+			| raygen    |
+			|-----------|
+			| miss      |
+			|-----------|
+			| hit       |
+			\-----------/
+
+	*/
+	void createShaderBindingTables() {
+		const uint32_t handleSize = rayTracingPipelineProperties.shaderGroupHandleSize;
+		const uint32_t handleSizeAligned = vks::tools::alignedSize(rayTracingPipelineProperties.shaderGroupHandleSize, rayTracingPipelineProperties.shaderGroupHandleAlignment);
+		const uint32_t groupCount = static_cast<uint32_t>(shaderGroups.size());
+		const uint32_t sbtSize = groupCount * handleSizeAligned;
+
+		std::vector<uint8_t> shaderHandleStorage(sbtSize);
+		VK_CHECK_RESULT(vkGetRayTracingShaderGroupHandlesKHR(device, pipeline, 0, groupCount, sbtSize, shaderHandleStorage.data()));
+
+		createShaderBindingTable(shaderBindingTables.raygen, 1);
+		// We are using two miss shaders
+		createShaderBindingTable(shaderBindingTables.miss, 2);
+		createShaderBindingTable(shaderBindingTables.hit, 1);
+
+		// Copy handles
+		memcpy(shaderBindingTables.raygen.mapped, shaderHandleStorage.data(), handleSize);
+		// We are using two miss shaders, so we need to get two handles for the miss shader binding table
+		memcpy(shaderBindingTables.miss.mapped, shaderHandleStorage.data() + handleSizeAligned, handleSize * 2);
+		memcpy(shaderBindingTables.hit.mapped, shaderHandleStorage.data() + handleSizeAligned * 3, handleSize);
+	}
+
+	/*
+		Create the descriptor sets used for the ray tracing dispatch
+	*/
+	void createDescriptorSets()
+	{
+		std::vector<VkDescriptorPoolSize> poolSizes = {
+			{ VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1 },
+			{ VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1 },
+			{ VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1 },
+			{ VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 2 }
+		};
+		VkDescriptorPoolCreateInfo descriptorPoolCreateInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 1);
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolCreateInfo, nullptr, &descriptorPool));
+
+		VkDescriptorSetAllocateInfo descriptorSetAllocateInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1);
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &descriptorSetAllocateInfo, &descriptorSet));
+
+		VkWriteDescriptorSetAccelerationStructureKHR descriptorAccelerationStructureInfo = vks::initializers::writeDescriptorSetAccelerationStructureKHR();
+		descriptorAccelerationStructureInfo.accelerationStructureCount = 1;
+		descriptorAccelerationStructureInfo.pAccelerationStructures = &topLevelAS.handle;
+
+		VkWriteDescriptorSet accelerationStructureWrite{};
+		accelerationStructureWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+		// The specialized acceleration structure descriptor has to be chained
+		accelerationStructureWrite.pNext = &descriptorAccelerationStructureInfo;
+		accelerationStructureWrite.dstSet = descriptorSet;
+		accelerationStructureWrite.dstBinding = 0;
+		accelerationStructureWrite.descriptorCount = 1;
+		accelerationStructureWrite.descriptorType = VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR;
+
+		VkDescriptorImageInfo storageImageDescriptor{ VK_NULL_HANDLE, storageImage.view, VK_IMAGE_LAYOUT_GENERAL };
+		VkDescriptorBufferInfo vertexBufferDescriptor{ scene.vertices.buffer, 0, VK_WHOLE_SIZE };
+		VkDescriptorBufferInfo indexBufferDescriptor{ scene.indices.buffer, 0, VK_WHOLE_SIZE };
+
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets = {
+			// Binding 0: Top level acceleration structure
+			accelerationStructureWrite,
+			// Binding 1: Ray tracing result image
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, &storageImageDescriptor),
+			// Binding 2: Uniform data
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2, &ubo.descriptor),
+			// Binding 3: Scene vertex buffer
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 3, &vertexBufferDescriptor),
+			// Binding 4: Scene index buffer
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 4, &indexBufferDescriptor),
+		};
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, VK_NULL_HANDLE);
+	}
+
+	/*
+		Create our ray tracing pipeline
+	*/
+	void createRayTracingPipeline()
+	{
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			// Binding 0: Acceleration structure
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR, 0),
+			// Binding 1: Storage image
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_SHADER_STAGE_RAYGEN_BIT_KHR, 1),
+			// Binding 2: Uniform buffer
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_MISS_BIT_KHR, 2),
+			// Binding 3: Vertex buffer 
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR, 3),
+			// Binding 4: Index buffer
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR, 4),
+		};
+
+		VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCI = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorSetLayoutCI, nullptr, &descriptorSetLayout));
+
+		VkPipelineLayoutCreateInfo pPipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1);
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCI, nullptr, &pipelineLayout));
+	
+		/*
+			Setup ray tracing shader groups
+		*/
+		std::vector<VkPipelineShaderStageCreateInfo> shaderStages;
+
+		// Ray generation group
+		{
+			shaderStages.push_back(loadShader(getShadersPath() + "raytracingshadows/raygen.rgen.spv", VK_SHADER_STAGE_RAYGEN_BIT_KHR));
+			VkRayTracingShaderGroupCreateInfoKHR shaderGroup{};
+			shaderGroup.sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR;
+			shaderGroup.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR;
+			shaderGroup.generalShader = static_cast<uint32_t>(shaderStages.size()) - 1;
+			shaderGroup.closestHitShader = VK_SHADER_UNUSED_KHR;
+			shaderGroup.anyHitShader = VK_SHADER_UNUSED_KHR;
+			shaderGroup.intersectionShader = VK_SHADER_UNUSED_KHR;
+			shaderGroups.push_back(shaderGroup);
+		}
+
+		// Miss group
+		{
+			shaderStages.push_back(loadShader(getShadersPath() + "raytracingshadows/miss.rmiss.spv", VK_SHADER_STAGE_MISS_BIT_KHR));
+			VkRayTracingShaderGroupCreateInfoKHR shaderGroup{};
+			shaderGroup.sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR;
+			shaderGroup.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR;
+			shaderGroup.generalShader = static_cast<uint32_t>(shaderStages.size()) - 1;
+			shaderGroup.closestHitShader = VK_SHADER_UNUSED_KHR;
+			shaderGroup.anyHitShader = VK_SHADER_UNUSED_KHR;
+			shaderGroup.intersectionShader = VK_SHADER_UNUSED_KHR;
+			shaderGroups.push_back(shaderGroup);
+			// Second shader for shadows
+			shaderStages.push_back(loadShader(getShadersPath() + "raytracingshadows/shadow.rmiss.spv", VK_SHADER_STAGE_MISS_BIT_KHR));
+			shaderGroup.generalShader = static_cast<uint32_t>(shaderStages.size()) - 1;
+			shaderGroups.push_back(shaderGroup);
+		}
+
+		// Closest hit group
+		{
+			shaderStages.push_back(loadShader(getShadersPath() + "raytracingshadows/closesthit.rchit.spv", VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR));
+			VkRayTracingShaderGroupCreateInfoKHR shaderGroup{};
+			shaderGroup.sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR;
+			shaderGroup.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_TRIANGLES_HIT_GROUP_KHR;
+			shaderGroup.generalShader = VK_SHADER_UNUSED_KHR;
+			shaderGroup.closestHitShader = static_cast<uint32_t>(shaderStages.size()) - 1;
+			shaderGroup.anyHitShader = VK_SHADER_UNUSED_KHR;
+			shaderGroup.intersectionShader = VK_SHADER_UNUSED_KHR;
+			shaderGroups.push_back(shaderGroup);
+		}
+
+		VkRayTracingPipelineCreateInfoKHR rayTracingPipelineCI = vks::initializers::rayTracingPipelineCreateInfoKHR();
+		rayTracingPipelineCI.stageCount = static_cast<uint32_t>(shaderStages.size());
+		rayTracingPipelineCI.pStages = shaderStages.data();
+		rayTracingPipelineCI.groupCount = static_cast<uint32_t>(shaderGroups.size());
+		rayTracingPipelineCI.pGroups = shaderGroups.data();
+		rayTracingPipelineCI.maxPipelineRayRecursionDepth = 2;
+		rayTracingPipelineCI.layout = pipelineLayout;
+		VK_CHECK_RESULT(vkCreateRayTracingPipelinesKHR(device, VK_NULL_HANDLE, VK_NULL_HANDLE, 1, &rayTracingPipelineCI, nullptr, &pipeline));
+	}
+
+	/*
+		Create the uniform buffer used to pass matrices to the ray tracing ray generation shader
+	*/
+	void createUniformBuffer()
+	{
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&ubo,
+			sizeof(uniformData),
+			&uniformData));
+		VK_CHECK_RESULT(ubo.map());
+
+		updateUniformBuffers();
+	}
+
+	/*
+		If the window has been resized, we need to recreate the storage image and it's descriptor
+	*/
+	void handleResize()
+	{
+		// Recreate image
+		createStorageImage(swapChain.colorFormat, { width, height, 1 });
+		// Update descriptor
+		VkDescriptorImageInfo storageImageDescriptor{ VK_NULL_HANDLE, storageImage.view, VK_IMAGE_LAYOUT_GENERAL };
+		VkWriteDescriptorSet resultImageWrite = vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, &storageImageDescriptor);
+		vkUpdateDescriptorSets(device, 1, &resultImageWrite, 0, VK_NULL_HANDLE);
+	}
+
+	/*
+		Command buffer generation
+	*/
+	void buildCommandBuffers()
+	{
+		if (resized)
+		{
+			handleResize();
+		}
+
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkImageSubresourceRange subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 };
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			/*
+				Dispatch the ray tracing commands
+			*/
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, pipeline);
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, pipelineLayout, 0, 1, &descriptorSet, 0, 0);
+
+			VkStridedDeviceAddressRegionKHR emptySbtEntry = {};
+			vkCmdTraceRaysKHR(
+				drawCmdBuffers[i],
+				&shaderBindingTables.raygen.stridedDeviceAddressRegion,
+				&shaderBindingTables.miss.stridedDeviceAddressRegion,
+				&shaderBindingTables.hit.stridedDeviceAddressRegion,
+				&emptySbtEntry,
+				width,
+				height,
+				1);
+
+			/*
+				Copy ray tracing output to swap chain image
+			*/
+
+			// Prepare current swap chain image as transfer destination
+			vks::tools::setImageLayout(
+				drawCmdBuffers[i],
+				swapChain.images[i],
+				VK_IMAGE_LAYOUT_UNDEFINED,
+				VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+				subresourceRange);
+
+			// Prepare ray tracing output image as transfer source
+			vks::tools::setImageLayout(
+				drawCmdBuffers[i],
+				storageImage.image,
+				VK_IMAGE_LAYOUT_GENERAL,
+				VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+				subresourceRange);
+
+			VkImageCopy copyRegion{};
+			copyRegion.srcSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 };
+			copyRegion.srcOffset = { 0, 0, 0 };
+			copyRegion.dstSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 };
+			copyRegion.dstOffset = { 0, 0, 0 };
+			copyRegion.extent = { width, height, 1 };
+			vkCmdCopyImage(drawCmdBuffers[i], storageImage.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, swapChain.images[i], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &copyRegion);
+
+			// Transition swap chain image back for presentation
+			vks::tools::setImageLayout(
+				drawCmdBuffers[i],
+				swapChain.images[i],
+				VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+				VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
+				subresourceRange);
+
+			// Transition ray tracing output image back to general layout
+			vks::tools::setImageLayout(
+				drawCmdBuffers[i],
+				storageImage.image,
+				VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+				VK_IMAGE_LAYOUT_GENERAL,
+				subresourceRange);
+
+			drawUI(drawCmdBuffers[i], frameBuffers[i]);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void updateUniformBuffers()
+	{
+		uniformData.projInverse = glm::inverse(camera.matrices.perspective);
+		uniformData.viewInverse = glm::inverse(camera.matrices.view);
+		uniformData.lightPos = glm::vec4(cos(glm::radians(timer * 360.0f)) * 40.0f, -50.0f + sin(glm::radians(timer * 360.0f)) * 20.0f, 25.0f + sin(glm::radians(timer * 360.0f)) * 5.0f, 0.0f);
+		// Pass the vertex size to the shader for unpacking vertices
+		uniformData.vertexSize = sizeof(vkglTF::Vertex);
+		memcpy(ubo.mapped, &uniformData, sizeof(uniformData));
+	}
+
+	void getEnabledFeatures()
+	{
+		// Enable features required for ray tracing using feature chaining via pNext		
+		enabledBufferDeviceAddresFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES;
+		enabledBufferDeviceAddresFeatures.bufferDeviceAddress = VK_TRUE;
+
+		enabledRayTracingPipelineFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_FEATURES_KHR;
+		enabledRayTracingPipelineFeatures.rayTracingPipeline = VK_TRUE;
+		enabledRayTracingPipelineFeatures.pNext = &enabledBufferDeviceAddresFeatures;
+
+		enabledAccelerationStructureFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_FEATURES_KHR;
+		enabledAccelerationStructureFeatures.accelerationStructure = VK_TRUE;
+		enabledAccelerationStructureFeatures.pNext = &enabledRayTracingPipelineFeatures;
+
+		deviceCreatepNextChain = &enabledAccelerationStructureFeatures;
+	}
+
+	void prepare()
+	{
+		VulkanRaytracingSample::prepare();
+
+		// Create the acceleration structures used to render the ray traced scene
+		createBottomLevelAccelerationStructure();
+		createTopLevelAccelerationStructure();
+
+		createStorageImage(swapChain.colorFormat, { width, height, 1 });
+		createUniformBuffer();
+		createRayTracingPipeline();
+		createShaderBindingTables();
+		createDescriptorSets();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+		VulkanExampleBase::submitFrame();
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+		if (!paused || camera.updated)
+			updateUniformBuffers();
+	}
+};
+
+VULKAN_EXAMPLE_MAIN()
diff --git a/external/Vulkan/examples/renderheadless/renderheadless.cpp b/external/Vulkan/examples/renderheadless/renderheadless.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ba41bf8aad7c8321af69ee271e2f4d65434f9aa2
--- /dev/null
+++ b/external/Vulkan/examples/renderheadless/renderheadless.cpp
@@ -0,0 +1,897 @@
+/*
+* Vulkan Example - Minimal headless rendering example
+*
+* Copyright (C) 2017 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#if defined(_WIN32)
+#pragma comment(linker, "/subsystem:console")
+#elif defined(VK_USE_PLATFORM_ANDROID_KHR)
+#include <android/native_activity.h>
+#include <android/asset_manager.h>
+#include <android_native_app_glue.h>
+#include <android/log.h>
+#include "VulkanAndroid.h"
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <vector>
+#include <array>
+#include <iostream>
+#include <algorithm>
+
+#define GLM_FORCE_RADIANS
+#define GLM_FORCE_DEPTH_ZERO_TO_ONE
+#include <glm/glm.hpp>
+#include <glm/gtc/matrix_transform.hpp>
+
+#include <vulkan/vulkan.h>
+#include "VulkanTools.h"
+
+#if defined(VK_USE_PLATFORM_ANDROID_KHR)
+android_app* androidapp;
+#endif
+
+#define DEBUG (!NDEBUG)
+
+#define BUFFER_ELEMENTS 32
+
+#if defined(VK_USE_PLATFORM_ANDROID_KHR)
+#define LOG(...) ((void)__android_log_print(ANDROID_LOG_INFO, "vulkanExample", __VA_ARGS__))
+#else
+#define LOG(...) printf(__VA_ARGS__)
+#endif
+
+static VKAPI_ATTR VkBool32 VKAPI_CALL debugMessageCallback(
+	VkDebugReportFlagsEXT flags,
+	VkDebugReportObjectTypeEXT objectType,
+	uint64_t object,
+	size_t location,
+	int32_t messageCode,
+	const char* pLayerPrefix,
+	const char* pMessage,
+	void* pUserData)
+{
+	LOG("[VALIDATION]: %s - %s\n", pLayerPrefix, pMessage);
+	return VK_FALSE;
+}
+
+class VulkanExample
+{
+public:
+	VkInstance instance;
+	VkPhysicalDevice physicalDevice;
+	VkDevice device;
+	uint32_t queueFamilyIndex;
+	VkPipelineCache pipelineCache;
+	VkQueue queue;
+	VkCommandPool commandPool;
+	VkCommandBuffer commandBuffer;
+	VkDescriptorSetLayout descriptorSetLayout;
+	VkPipelineLayout pipelineLayout;
+	VkPipeline pipeline;
+	std::vector<VkShaderModule> shaderModules;
+	VkBuffer vertexBuffer, indexBuffer;
+	VkDeviceMemory vertexMemory, indexMemory;
+
+	struct FrameBufferAttachment {
+		VkImage image;
+		VkDeviceMemory memory;
+		VkImageView view;
+	};
+	int32_t width, height;
+	VkFramebuffer framebuffer;
+	FrameBufferAttachment colorAttachment, depthAttachment;
+	VkRenderPass renderPass;
+
+	VkDebugReportCallbackEXT debugReportCallback{};
+
+	uint32_t getMemoryTypeIndex(uint32_t typeBits, VkMemoryPropertyFlags properties) {
+		VkPhysicalDeviceMemoryProperties deviceMemoryProperties;
+		vkGetPhysicalDeviceMemoryProperties(physicalDevice, &deviceMemoryProperties);
+		for (uint32_t i = 0; i < deviceMemoryProperties.memoryTypeCount; i++) {
+			if ((typeBits & 1) == 1) {
+				if ((deviceMemoryProperties.memoryTypes[i].propertyFlags & properties) == properties) {
+					return i;
+				}
+			}
+			typeBits >>= 1;
+		}
+		return 0;
+	}
+
+	VkResult createBuffer(VkBufferUsageFlags usageFlags, VkMemoryPropertyFlags memoryPropertyFlags, VkBuffer *buffer, VkDeviceMemory *memory, VkDeviceSize size, void *data = nullptr)
+	{
+		// Create the buffer handle
+		VkBufferCreateInfo bufferCreateInfo = vks::initializers::bufferCreateInfo(usageFlags, size);
+		bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+		VK_CHECK_RESULT(vkCreateBuffer(device, &bufferCreateInfo, nullptr, buffer));
+
+		// Create the memory backing up the buffer handle
+		VkMemoryRequirements memReqs;
+		VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo();
+		vkGetBufferMemoryRequirements(device, *buffer, &memReqs);
+		memAlloc.allocationSize = memReqs.size;
+		memAlloc.memoryTypeIndex = getMemoryTypeIndex(memReqs.memoryTypeBits, memoryPropertyFlags);
+		VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, memory));
+
+		if (data != nullptr) {
+			void *mapped;
+			VK_CHECK_RESULT(vkMapMemory(device, *memory, 0, size, 0, &mapped));
+			memcpy(mapped, data, size);
+			vkUnmapMemory(device, *memory);
+		}
+
+		VK_CHECK_RESULT(vkBindBufferMemory(device, *buffer, *memory, 0));
+
+		return VK_SUCCESS;
+	}
+
+	/*
+		Submit command buffer to a queue and wait for fence until queue operations have been finished
+	*/
+	void submitWork(VkCommandBuffer cmdBuffer, VkQueue queue)
+	{
+		VkSubmitInfo submitInfo = vks::initializers::submitInfo();
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &cmdBuffer;
+		VkFenceCreateInfo fenceInfo = vks::initializers::fenceCreateInfo();
+		VkFence fence;
+		VK_CHECK_RESULT(vkCreateFence(device, &fenceInfo, nullptr, &fence));
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, fence));
+		VK_CHECK_RESULT(vkWaitForFences(device, 1, &fence, VK_TRUE, UINT64_MAX));
+		vkDestroyFence(device, fence, nullptr);
+	}
+
+	VulkanExample()
+	{
+		LOG("Running headless rendering example\n");
+
+#if defined(VK_USE_PLATFORM_ANDROID_KHR)
+		LOG("loading vulkan lib");
+		vks::android::loadVulkanLibrary();
+#endif
+
+		VkApplicationInfo appInfo = {};
+		appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
+		appInfo.pApplicationName = "Vulkan headless example";
+		appInfo.pEngineName = "VulkanExample";
+		appInfo.apiVersion = VK_API_VERSION_1_0;
+
+		/*
+			Vulkan instance creation (without surface extensions)
+		*/
+		VkInstanceCreateInfo instanceCreateInfo = {};
+		instanceCreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
+		instanceCreateInfo.pApplicationInfo = &appInfo;
+
+		uint32_t layerCount = 0;
+#if defined(VK_USE_PLATFORM_ANDROID_KHR)
+		const char* validationLayers[] = { "VK_LAYER_GOOGLE_threading",	"VK_LAYER_LUNARG_parameter_validation",	"VK_LAYER_LUNARG_object_tracker","VK_LAYER_LUNARG_core_validation",	"VK_LAYER_LUNARG_swapchain", "VK_LAYER_GOOGLE_unique_objects" };
+		layerCount = 6;
+#else
+		const char* validationLayers[] = { "VK_LAYER_LUNARG_standard_validation" };
+		layerCount = 1;
+#endif
+#if DEBUG
+		// Check if layers are available
+		uint32_t instanceLayerCount;
+		vkEnumerateInstanceLayerProperties(&instanceLayerCount, nullptr);
+		std::vector<VkLayerProperties> instanceLayers(instanceLayerCount);
+		vkEnumerateInstanceLayerProperties(&instanceLayerCount, instanceLayers.data());
+
+		bool layersAvailable = true;
+		for (auto layerName : validationLayers) {
+			bool layerAvailable = false;
+			for (auto instanceLayer : instanceLayers) {
+				if (strcmp(instanceLayer.layerName, layerName) == 0) {
+					layerAvailable = true;
+					break;
+				}
+			}
+			if (!layerAvailable) {
+				layersAvailable = false;
+				break;
+			}
+		}
+
+		const char *validationExt = VK_EXT_DEBUG_REPORT_EXTENSION_NAME;
+		if (layersAvailable) {
+			instanceCreateInfo.ppEnabledLayerNames = validationLayers;
+			instanceCreateInfo.enabledLayerCount = layerCount;
+			instanceCreateInfo.enabledExtensionCount = 1;
+			instanceCreateInfo.ppEnabledExtensionNames = &validationExt;
+		}
+#endif
+		VK_CHECK_RESULT(vkCreateInstance(&instanceCreateInfo, nullptr, &instance));
+
+#if defined(VK_USE_PLATFORM_ANDROID_KHR)
+		vks::android::loadVulkanFunctions(instance);
+#endif
+#if DEBUG
+		if (layersAvailable) {
+			VkDebugReportCallbackCreateInfoEXT debugReportCreateInfo = {};
+			debugReportCreateInfo.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT;
+			debugReportCreateInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT;
+			debugReportCreateInfo.pfnCallback = (PFN_vkDebugReportCallbackEXT)debugMessageCallback;
+
+			// We have to explicitly load this function.
+			PFN_vkCreateDebugReportCallbackEXT vkCreateDebugReportCallbackEXT = reinterpret_cast<PFN_vkCreateDebugReportCallbackEXT>(vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT"));
+			assert(vkCreateDebugReportCallbackEXT);
+			VK_CHECK_RESULT(vkCreateDebugReportCallbackEXT(instance, &debugReportCreateInfo, nullptr, &debugReportCallback));
+		}
+#endif
+
+		/*
+			Vulkan device creation
+		*/
+		uint32_t deviceCount = 0;
+		VK_CHECK_RESULT(vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr));
+		std::vector<VkPhysicalDevice> physicalDevices(deviceCount);
+		VK_CHECK_RESULT(vkEnumeratePhysicalDevices(instance, &deviceCount, physicalDevices.data()));
+		physicalDevice = physicalDevices[0];
+
+		VkPhysicalDeviceProperties deviceProperties;
+		vkGetPhysicalDeviceProperties(physicalDevice, &deviceProperties);
+		LOG("GPU: %s\n", deviceProperties.deviceName);
+
+		// Request a single graphics queue
+		const float defaultQueuePriority(0.0f);
+		VkDeviceQueueCreateInfo queueCreateInfo = {};
+		uint32_t queueFamilyCount;
+		vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, nullptr);
+		std::vector<VkQueueFamilyProperties> queueFamilyProperties(queueFamilyCount);
+		vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, queueFamilyProperties.data());
+		for (uint32_t i = 0; i < static_cast<uint32_t>(queueFamilyProperties.size()); i++) {
+			if (queueFamilyProperties[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
+				queueFamilyIndex = i;
+				queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
+				queueCreateInfo.queueFamilyIndex = i;
+				queueCreateInfo.queueCount = 1;
+				queueCreateInfo.pQueuePriorities = &defaultQueuePriority;
+				break;
+			}
+		}
+		// Create logical device
+		VkDeviceCreateInfo deviceCreateInfo = {};
+		deviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
+		deviceCreateInfo.queueCreateInfoCount = 1;
+		deviceCreateInfo.pQueueCreateInfos = &queueCreateInfo;
+		VK_CHECK_RESULT(vkCreateDevice(physicalDevice, &deviceCreateInfo, nullptr, &device));
+
+		// Get a graphics queue
+		vkGetDeviceQueue(device, queueFamilyIndex, 0, &queue);
+
+		// Command pool
+		VkCommandPoolCreateInfo cmdPoolInfo = {};
+		cmdPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
+		cmdPoolInfo.queueFamilyIndex = queueFamilyIndex;
+		cmdPoolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
+		VK_CHECK_RESULT(vkCreateCommandPool(device, &cmdPoolInfo, nullptr, &commandPool));
+
+		/*
+			Prepare vertex and index buffers
+		*/
+		struct Vertex {
+			float position[3];
+			float color[3];
+		};
+		{
+			std::vector<Vertex> vertices = {
+				{ {  1.0f,  1.0f, 0.0f }, { 1.0f, 0.0f, 0.0f } },
+				{ { -1.0f,  1.0f, 0.0f }, { 0.0f, 1.0f, 0.0f } },
+				{ {  0.0f, -1.0f, 0.0f }, { 0.0f, 0.0f, 1.0f } }
+			};
+			std::vector<uint32_t> indices = { 0, 1, 2 };
+
+			const VkDeviceSize vertexBufferSize = vertices.size() * sizeof(Vertex);
+			const VkDeviceSize indexBufferSize = indices.size() * sizeof(uint32_t);
+
+			VkBuffer stagingBuffer;
+			VkDeviceMemory stagingMemory;
+
+			// Command buffer for copy commands (reused)
+			VkCommandBufferAllocateInfo cmdBufAllocateInfo = vks::initializers::commandBufferAllocateInfo(commandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1);
+			VkCommandBuffer copyCmd;
+			VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, &copyCmd));
+			VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+			// Copy input data to VRAM using a staging buffer
+			{
+				// Vertices
+				createBuffer(
+					VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
+					VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+					&stagingBuffer,
+					&stagingMemory,
+					vertexBufferSize,
+					vertices.data());
+
+				createBuffer(
+					VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
+					VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
+					&vertexBuffer,
+					&vertexMemory,
+					vertexBufferSize);
+
+				VK_CHECK_RESULT(vkBeginCommandBuffer(copyCmd, &cmdBufInfo));
+				VkBufferCopy copyRegion = {};
+				copyRegion.size = vertexBufferSize;
+				vkCmdCopyBuffer(copyCmd, stagingBuffer, vertexBuffer, 1, &copyRegion);
+				VK_CHECK_RESULT(vkEndCommandBuffer(copyCmd));
+
+				submitWork(copyCmd, queue);
+
+				vkDestroyBuffer(device, stagingBuffer, nullptr);
+				vkFreeMemory(device, stagingMemory, nullptr);
+
+				// Indices
+				createBuffer(
+					VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
+					VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+					&stagingBuffer,
+					&stagingMemory,
+					indexBufferSize,
+					indices.data());
+
+				createBuffer(
+					VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
+					VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
+					&indexBuffer,
+					&indexMemory,
+					indexBufferSize);
+
+				VK_CHECK_RESULT(vkBeginCommandBuffer(copyCmd, &cmdBufInfo));
+				copyRegion.size = indexBufferSize;
+				vkCmdCopyBuffer(copyCmd, stagingBuffer, indexBuffer, 1, &copyRegion);
+				VK_CHECK_RESULT(vkEndCommandBuffer(copyCmd));
+
+				submitWork(copyCmd, queue);
+
+				vkDestroyBuffer(device, stagingBuffer, nullptr);
+				vkFreeMemory(device, stagingMemory, nullptr);
+			}
+		}
+
+		/*
+			Create framebuffer attachments
+		*/
+		width = 1024;
+		height = 1024;
+		VkFormat colorFormat = VK_FORMAT_R8G8B8A8_UNORM;
+		VkFormat depthFormat;
+		vks::tools::getSupportedDepthFormat(physicalDevice, &depthFormat);
+		{
+			// Color attachment
+			VkImageCreateInfo image = vks::initializers::imageCreateInfo();
+			image.imageType = VK_IMAGE_TYPE_2D;
+			image.format = colorFormat;
+			image.extent.width = width;
+			image.extent.height = height;
+			image.extent.depth = 1;
+			image.mipLevels = 1;
+			image.arrayLayers = 1;
+			image.samples = VK_SAMPLE_COUNT_1_BIT;
+			image.tiling = VK_IMAGE_TILING_OPTIMAL;
+			image.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
+
+			VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo();
+			VkMemoryRequirements memReqs;
+
+			VK_CHECK_RESULT(vkCreateImage(device, &image, nullptr, &colorAttachment.image));
+			vkGetImageMemoryRequirements(device, colorAttachment.image, &memReqs);
+			memAlloc.allocationSize = memReqs.size;
+			memAlloc.memoryTypeIndex = getMemoryTypeIndex(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+			VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &colorAttachment.memory));
+			VK_CHECK_RESULT(vkBindImageMemory(device, colorAttachment.image, colorAttachment.memory, 0));
+
+			VkImageViewCreateInfo colorImageView = vks::initializers::imageViewCreateInfo();
+			colorImageView.viewType = VK_IMAGE_VIEW_TYPE_2D;
+			colorImageView.format = colorFormat;
+			colorImageView.subresourceRange = {};
+			colorImageView.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+			colorImageView.subresourceRange.baseMipLevel = 0;
+			colorImageView.subresourceRange.levelCount = 1;
+			colorImageView.subresourceRange.baseArrayLayer = 0;
+			colorImageView.subresourceRange.layerCount = 1;
+			colorImageView.image = colorAttachment.image;
+			VK_CHECK_RESULT(vkCreateImageView(device, &colorImageView, nullptr, &colorAttachment.view));
+
+			// Depth stencil attachment
+			image.format = depthFormat;
+			image.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
+
+			VK_CHECK_RESULT(vkCreateImage(device, &image, nullptr, &depthAttachment.image));
+			vkGetImageMemoryRequirements(device, depthAttachment.image, &memReqs);
+			memAlloc.allocationSize = memReqs.size;
+			memAlloc.memoryTypeIndex = getMemoryTypeIndex(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+			VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &depthAttachment.memory));
+			VK_CHECK_RESULT(vkBindImageMemory(device, depthAttachment.image, depthAttachment.memory, 0));
+
+			VkImageViewCreateInfo depthStencilView = vks::initializers::imageViewCreateInfo();
+			depthStencilView.viewType = VK_IMAGE_VIEW_TYPE_2D;
+			depthStencilView.format = depthFormat;
+			depthStencilView.flags = 0;
+			depthStencilView.subresourceRange = {};
+			depthStencilView.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
+			depthStencilView.subresourceRange.baseMipLevel = 0;
+			depthStencilView.subresourceRange.levelCount = 1;
+			depthStencilView.subresourceRange.baseArrayLayer = 0;
+			depthStencilView.subresourceRange.layerCount = 1;
+			depthStencilView.image = depthAttachment.image;
+			VK_CHECK_RESULT(vkCreateImageView(device, &depthStencilView, nullptr, &depthAttachment.view));
+		}
+
+		/*
+			Create renderpass
+		*/
+		{
+			std::array<VkAttachmentDescription, 2> attchmentDescriptions = {};
+			// Color attachment
+			attchmentDescriptions[0].format = colorFormat;
+			attchmentDescriptions[0].samples = VK_SAMPLE_COUNT_1_BIT;
+			attchmentDescriptions[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+			attchmentDescriptions[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+			attchmentDescriptions[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+			attchmentDescriptions[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+			attchmentDescriptions[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+			attchmentDescriptions[0].finalLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
+			// Depth attachment
+			attchmentDescriptions[1].format = depthFormat;
+			attchmentDescriptions[1].samples = VK_SAMPLE_COUNT_1_BIT;
+			attchmentDescriptions[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+			attchmentDescriptions[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+			attchmentDescriptions[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+			attchmentDescriptions[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+			attchmentDescriptions[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+			attchmentDescriptions[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+
+			VkAttachmentReference colorReference = { 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
+			VkAttachmentReference depthReference = { 1, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL };
+
+			VkSubpassDescription subpassDescription = {};
+			subpassDescription.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+			subpassDescription.colorAttachmentCount = 1;
+			subpassDescription.pColorAttachments = &colorReference;
+			subpassDescription.pDepthStencilAttachment = &depthReference;
+
+			// Use subpass dependencies for layout transitions
+			std::array<VkSubpassDependency, 2> dependencies;
+
+			dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
+			dependencies[0].dstSubpass = 0;
+			dependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+			dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+			dependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
+			dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+			dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+			dependencies[1].srcSubpass = 0;
+			dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
+			dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+			dependencies[1].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+			dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+			dependencies[1].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
+			dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+			// Create the actual renderpass
+			VkRenderPassCreateInfo renderPassInfo = {};
+			renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
+			renderPassInfo.attachmentCount = static_cast<uint32_t>(attchmentDescriptions.size());
+			renderPassInfo.pAttachments = attchmentDescriptions.data();
+			renderPassInfo.subpassCount = 1;
+			renderPassInfo.pSubpasses = &subpassDescription;
+			renderPassInfo.dependencyCount = static_cast<uint32_t>(dependencies.size());
+			renderPassInfo.pDependencies = dependencies.data();
+			VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass));
+
+			VkImageView attachments[2];
+			attachments[0] = colorAttachment.view;
+			attachments[1] = depthAttachment.view;
+
+			VkFramebufferCreateInfo framebufferCreateInfo = vks::initializers::framebufferCreateInfo();
+			framebufferCreateInfo.renderPass = renderPass;
+			framebufferCreateInfo.attachmentCount = 2;
+			framebufferCreateInfo.pAttachments = attachments;
+			framebufferCreateInfo.width = width;
+			framebufferCreateInfo.height = height;
+			framebufferCreateInfo.layers = 1;
+			VK_CHECK_RESULT(vkCreateFramebuffer(device, &framebufferCreateInfo, nullptr, &framebuffer));
+		}
+
+		/*
+			Prepare graphics pipeline
+		*/
+		{
+			std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {};
+			VkDescriptorSetLayoutCreateInfo descriptorLayout =
+				vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
+			VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout));
+
+			VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo =
+				vks::initializers::pipelineLayoutCreateInfo(nullptr, 0);
+
+			// MVP via push constant block
+			VkPushConstantRange pushConstantRange = vks::initializers::pushConstantRange(VK_SHADER_STAGE_VERTEX_BIT, sizeof(glm::mat4), 0);
+			pipelineLayoutCreateInfo.pushConstantRangeCount = 1;
+			pipelineLayoutCreateInfo.pPushConstantRanges = &pushConstantRange;
+
+			VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout));
+
+			VkPipelineCacheCreateInfo pipelineCacheCreateInfo = {};
+			pipelineCacheCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO;
+			VK_CHECK_RESULT(vkCreatePipelineCache(device, &pipelineCacheCreateInfo, nullptr, &pipelineCache));
+
+			// Create pipeline
+			VkPipelineInputAssemblyStateCreateInfo inputAssemblyState =
+				vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+
+			VkPipelineRasterizationStateCreateInfo rasterizationState =
+				vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_CLOCKWISE);
+
+			VkPipelineColorBlendAttachmentState blendAttachmentState =
+				vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+
+			VkPipelineColorBlendStateCreateInfo colorBlendState =
+				vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+
+			VkPipelineDepthStencilStateCreateInfo depthStencilState =
+				vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+
+			VkPipelineViewportStateCreateInfo viewportState =
+				vks::initializers::pipelineViewportStateCreateInfo(1, 1);
+
+			VkPipelineMultisampleStateCreateInfo multisampleState =
+				vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT);
+
+			std::vector<VkDynamicState> dynamicStateEnables = {
+				VK_DYNAMIC_STATE_VIEWPORT,
+				VK_DYNAMIC_STATE_SCISSOR
+			};
+			VkPipelineDynamicStateCreateInfo dynamicState =
+				vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
+
+			VkGraphicsPipelineCreateInfo pipelineCreateInfo =
+				vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass);
+
+			std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages{};
+
+			pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState;
+			pipelineCreateInfo.pRasterizationState = &rasterizationState;
+			pipelineCreateInfo.pColorBlendState = &colorBlendState;
+			pipelineCreateInfo.pMultisampleState = &multisampleState;
+			pipelineCreateInfo.pViewportState = &viewportState;
+			pipelineCreateInfo.pDepthStencilState = &depthStencilState;
+			pipelineCreateInfo.pDynamicState = &dynamicState;
+			pipelineCreateInfo.stageCount = static_cast<uint32_t>(shaderStages.size());
+			pipelineCreateInfo.pStages = shaderStages.data();
+
+			// Vertex bindings an attributes
+			// Binding description
+			std::vector<VkVertexInputBindingDescription> vertexInputBindings = {
+				vks::initializers::vertexInputBindingDescription(0, sizeof(Vertex), VK_VERTEX_INPUT_RATE_VERTEX),
+			};
+
+			// Attribute descriptions
+			std::vector<VkVertexInputAttributeDescription> vertexInputAttributes = {
+				vks::initializers::vertexInputAttributeDescription(0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0),					// Position
+				vks::initializers::vertexInputAttributeDescription(0, 1, VK_FORMAT_R32G32B32_SFLOAT, sizeof(float) * 3),	// Color
+			};
+
+			VkPipelineVertexInputStateCreateInfo vertexInputState = vks::initializers::pipelineVertexInputStateCreateInfo();
+			vertexInputState.vertexBindingDescriptionCount = static_cast<uint32_t>(vertexInputBindings.size());
+			vertexInputState.pVertexBindingDescriptions = vertexInputBindings.data();
+			vertexInputState.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertexInputAttributes.size());
+			vertexInputState.pVertexAttributeDescriptions = vertexInputAttributes.data();
+
+			pipelineCreateInfo.pVertexInputState = &vertexInputState;
+
+			// TODO: There is no command line arguments parsing (nor Android settings) for this
+			// example, so we have no way of picking between GLSL or HLSL shaders.
+			// Hard-code to glsl for now.
+			const std::string shadersPath = getAssetPath() + "shaders/glsl/renderheadless/";
+
+			shaderStages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+			shaderStages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
+			shaderStages[0].pName = "main";
+			shaderStages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+			shaderStages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
+			shaderStages[1].pName = "main";
+#if defined(VK_USE_PLATFORM_ANDROID_KHR)
+			shaderStages[0].module = vks::tools::loadShader(androidapp->activity->assetManager, (shadersPath + "triangle.vert.spv").c_str(), device);
+			shaderStages[1].module = vks::tools::loadShader(androidapp->activity->assetManager, (shadersPath + "triangle.frag.spv").c_str(), device);
+#else
+			shaderStages[0].module = vks::tools::loadShader((shadersPath + "triangle.vert.spv").c_str(), device);
+			shaderStages[1].module = vks::tools::loadShader((shadersPath + "triangle.frag.spv").c_str(), device);
+#endif
+			shaderModules = { shaderStages[0].module, shaderStages[1].module };
+			VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipeline));
+		}
+
+		/*
+			Command buffer creation
+		*/
+		{
+			VkCommandBuffer commandBuffer;
+			VkCommandBufferAllocateInfo cmdBufAllocateInfo =
+				vks::initializers::commandBufferAllocateInfo(commandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1);
+			VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, &commandBuffer));
+
+			VkCommandBufferBeginInfo cmdBufInfo =
+				vks::initializers::commandBufferBeginInfo();
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(commandBuffer, &cmdBufInfo));
+
+			VkClearValue clearValues[2];
+			clearValues[0].color = { { 0.0f, 0.0f, 0.2f, 1.0f } };
+			clearValues[1].depthStencil = { 1.0f, 0 };
+
+			VkRenderPassBeginInfo renderPassBeginInfo = {};
+			renderPassBeginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
+			renderPassBeginInfo.renderArea.extent.width = width;
+			renderPassBeginInfo.renderArea.extent.height = height;
+			renderPassBeginInfo.clearValueCount = 2;
+			renderPassBeginInfo.pClearValues = clearValues;
+			renderPassBeginInfo.renderPass = renderPass;
+			renderPassBeginInfo.framebuffer = framebuffer;
+
+			vkCmdBeginRenderPass(commandBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			VkViewport viewport = {};
+			viewport.height = (float)height;
+			viewport.width = (float)width;
+			viewport.minDepth = (float)0.0f;
+			viewport.maxDepth = (float)1.0f;
+			vkCmdSetViewport(commandBuffer, 0, 1, &viewport);
+
+			// Update dynamic scissor state
+			VkRect2D scissor = {};
+			scissor.extent.width = width;
+			scissor.extent.height = height;
+			vkCmdSetScissor(commandBuffer, 0, 1, &scissor);
+
+			vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
+
+			// Render scene
+			VkDeviceSize offsets[1] = { 0 };
+			vkCmdBindVertexBuffers(commandBuffer, 0, 1, &vertexBuffer, offsets);
+			vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT32);
+
+			std::vector<glm::vec3> pos = {
+				glm::vec3(-1.5f, 0.0f, -4.0f),
+				glm::vec3( 0.0f, 0.0f, -2.5f),
+				glm::vec3( 1.5f, 0.0f, -4.0f),
+			};
+
+			for (auto v : pos) {
+				glm::mat4 mvpMatrix = glm::perspective(glm::radians(60.0f), (float)width / (float)height, 0.1f, 256.0f) * glm::translate(glm::mat4(1.0f), v);
+				vkCmdPushConstants(commandBuffer, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(mvpMatrix), &mvpMatrix);
+				vkCmdDrawIndexed(commandBuffer, 3, 1, 0, 0, 0);
+			}
+
+			vkCmdEndRenderPass(commandBuffer);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(commandBuffer));
+
+			submitWork(commandBuffer, queue);
+
+			vkDeviceWaitIdle(device);
+		}
+
+		/*
+			Copy framebuffer image to host visible image
+		*/
+		const char* imagedata;
+		{
+			// Create the linear tiled destination image to copy to and to read the memory from
+			VkImageCreateInfo imgCreateInfo(vks::initializers::imageCreateInfo());
+			imgCreateInfo.imageType = VK_IMAGE_TYPE_2D;
+			imgCreateInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
+			imgCreateInfo.extent.width = width;
+			imgCreateInfo.extent.height = height;
+			imgCreateInfo.extent.depth = 1;
+			imgCreateInfo.arrayLayers = 1;
+			imgCreateInfo.mipLevels = 1;
+			imgCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+			imgCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
+			imgCreateInfo.tiling = VK_IMAGE_TILING_LINEAR;
+			imgCreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT;
+			// Create the image
+			VkImage dstImage;
+			VK_CHECK_RESULT(vkCreateImage(device, &imgCreateInfo, nullptr, &dstImage));
+			// Create memory to back up the image
+			VkMemoryRequirements memRequirements;
+			VkMemoryAllocateInfo memAllocInfo(vks::initializers::memoryAllocateInfo());
+			VkDeviceMemory dstImageMemory;
+			vkGetImageMemoryRequirements(device, dstImage, &memRequirements);
+			memAllocInfo.allocationSize = memRequirements.size;
+			// Memory must be host visible to copy from
+			memAllocInfo.memoryTypeIndex = getMemoryTypeIndex(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
+			VK_CHECK_RESULT(vkAllocateMemory(device, &memAllocInfo, nullptr, &dstImageMemory));
+			VK_CHECK_RESULT(vkBindImageMemory(device, dstImage, dstImageMemory, 0));
+
+			// Do the actual blit from the offscreen image to our host visible destination image
+			VkCommandBufferAllocateInfo cmdBufAllocateInfo = vks::initializers::commandBufferAllocateInfo(commandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1);
+			VkCommandBuffer copyCmd;
+			VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, &copyCmd));
+			VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+			VK_CHECK_RESULT(vkBeginCommandBuffer(copyCmd, &cmdBufInfo));
+
+			// Transition destination image to transfer destination layout
+			vks::tools::insertImageMemoryBarrier(
+				copyCmd,
+				dstImage,
+				0,
+				VK_ACCESS_TRANSFER_WRITE_BIT,
+				VK_IMAGE_LAYOUT_UNDEFINED,
+				VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+				VK_PIPELINE_STAGE_TRANSFER_BIT,
+				VK_PIPELINE_STAGE_TRANSFER_BIT,
+				VkImageSubresourceRange{ VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 });
+
+			// colorAttachment.image is already in VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, and does not need to be transitioned
+
+			VkImageCopy imageCopyRegion{};
+			imageCopyRegion.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+			imageCopyRegion.srcSubresource.layerCount = 1;
+			imageCopyRegion.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+			imageCopyRegion.dstSubresource.layerCount = 1;
+			imageCopyRegion.extent.width = width;
+			imageCopyRegion.extent.height = height;
+			imageCopyRegion.extent.depth = 1;
+
+			vkCmdCopyImage(
+				copyCmd,
+				colorAttachment.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+				dstImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+				1,
+				&imageCopyRegion);
+
+			// Transition destination image to general layout, which is the required layout for mapping the image memory later on
+			vks::tools::insertImageMemoryBarrier(
+				copyCmd,
+				dstImage,
+				VK_ACCESS_TRANSFER_WRITE_BIT,
+				VK_ACCESS_MEMORY_READ_BIT,
+				VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+				VK_IMAGE_LAYOUT_GENERAL,
+				VK_PIPELINE_STAGE_TRANSFER_BIT,
+				VK_PIPELINE_STAGE_TRANSFER_BIT,
+				VkImageSubresourceRange{ VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 });
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(copyCmd));
+
+			submitWork(copyCmd, queue);
+
+			// Get layout of the image (including row pitch)
+			VkImageSubresource subResource{};
+			subResource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+			VkSubresourceLayout subResourceLayout;
+
+			vkGetImageSubresourceLayout(device, dstImage, &subResource, &subResourceLayout);
+
+			// Map image memory so we can start copying from it
+			vkMapMemory(device, dstImageMemory, 0, VK_WHOLE_SIZE, 0, (void**)&imagedata);
+			imagedata += subResourceLayout.offset;
+
+		/*
+			Save host visible framebuffer image to disk (ppm format)
+		*/
+
+#if defined (VK_USE_PLATFORM_ANDROID_KHR)
+			const char* filename = strcat(getenv("EXTERNAL_STORAGE"), "/headless.ppm");
+#else
+			const char* filename = "headless.ppm";
+#endif
+			std::ofstream file(filename, std::ios::out | std::ios::binary);
+
+			// ppm header
+			file << "P6\n" << width << "\n" << height << "\n" << 255 << "\n";
+
+			// If source is BGR (destination is always RGB) and we can't use blit (which does automatic conversion), we'll have to manually swizzle color components
+			// Check if source is BGR and needs swizzle
+			std::vector<VkFormat> formatsBGR = { VK_FORMAT_B8G8R8A8_SRGB, VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_B8G8R8A8_SNORM };
+			const bool colorSwizzle = (std::find(formatsBGR.begin(), formatsBGR.end(), VK_FORMAT_R8G8B8A8_UNORM) != formatsBGR.end());
+
+			// ppm binary pixel data
+			for (int32_t y = 0; y < height; y++) {
+				unsigned int *row = (unsigned int*)imagedata;
+				for (int32_t x = 0; x < width; x++) {
+					if (colorSwizzle) {
+						file.write((char*)row + 2, 1);
+						file.write((char*)row + 1, 1);
+						file.write((char*)row, 1);
+					}
+					else {
+						file.write((char*)row, 3);
+					}
+					row++;
+				}
+				imagedata += subResourceLayout.rowPitch;
+			}
+			file.close();
+
+			LOG("Framebuffer image saved to %s\n", filename);
+
+			// Clean up resources
+			vkUnmapMemory(device, dstImageMemory);
+			vkFreeMemory(device, dstImageMemory, nullptr);
+			vkDestroyImage(device, dstImage, nullptr);
+		}
+
+		vkQueueWaitIdle(queue);
+	}
+
+	~VulkanExample()
+	{
+		vkDestroyBuffer(device, vertexBuffer, nullptr);
+		vkFreeMemory(device, vertexMemory, nullptr);
+		vkDestroyBuffer(device, indexBuffer, nullptr);
+		vkFreeMemory(device, indexMemory, nullptr);
+		vkDestroyImageView(device, colorAttachment.view, nullptr);
+		vkDestroyImage(device, colorAttachment.image, nullptr);
+		vkFreeMemory(device, colorAttachment.memory, nullptr);
+		vkDestroyImageView(device, depthAttachment.view, nullptr);
+		vkDestroyImage(device, depthAttachment.image, nullptr);
+		vkFreeMemory(device, depthAttachment.memory, nullptr);
+		vkDestroyRenderPass(device, renderPass, nullptr);
+		vkDestroyFramebuffer(device, framebuffer, nullptr);
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+		vkDestroyPipeline(device, pipeline, nullptr);
+		vkDestroyPipelineCache(device, pipelineCache, nullptr);
+		vkDestroyCommandPool(device, commandPool, nullptr);
+		for (auto shadermodule : shaderModules) {
+			vkDestroyShaderModule(device, shadermodule, nullptr);
+		}
+		vkDestroyDevice(device, nullptr);
+#if DEBUG
+		if (debugReportCallback) {
+			PFN_vkDestroyDebugReportCallbackEXT vkDestroyDebugReportCallback = reinterpret_cast<PFN_vkDestroyDebugReportCallbackEXT>(vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT"));
+			assert(vkDestroyDebugReportCallback);
+			vkDestroyDebugReportCallback(instance, debugReportCallback, nullptr);
+		}
+#endif
+		vkDestroyInstance(instance, nullptr);
+#if defined(VK_USE_PLATFORM_ANDROID_KHR)
+		vks::android::freeVulkanLibrary();
+#endif
+	}
+};
+
+#if defined(VK_USE_PLATFORM_ANDROID_KHR)
+void handleAppCommand(android_app * app, int32_t cmd) {
+	if (cmd == APP_CMD_INIT_WINDOW) {
+		VulkanExample *vulkanExample = new VulkanExample();
+		delete(vulkanExample);
+		ANativeActivity_finish(app->activity);
+	}
+}
+void android_main(android_app* state) {
+	androidapp = state;
+	androidapp->onAppCmd = handleAppCommand;
+	int ident, events;
+	struct android_poll_source* source;
+	while ((ident = ALooper_pollAll(-1, NULL, &events, (void**)&source)) >= 0) {
+		if (source != NULL)	{
+			source->process(androidapp, source);
+		}
+		if (androidapp->destroyRequested != 0) {
+			break;
+		}
+	}
+}
+#else
+int main() {
+	VulkanExample *vulkanExample = new VulkanExample();
+	std::cout << "Finished. Press enter to terminate...";
+	getchar();
+	delete(vulkanExample);
+	return 0;
+}
+#endif
\ No newline at end of file
diff --git a/external/Vulkan/examples/screenshot/screenshot.cpp b/external/Vulkan/examples/screenshot/screenshot.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5e34c3038469e835ea94e7fa83aa17d0330d779c
--- /dev/null
+++ b/external/Vulkan/examples/screenshot/screenshot.cpp
@@ -0,0 +1,445 @@
+/*
+* Vulkan Example - Taking screenshots
+*
+* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkanexamplebase.h"
+#include "VulkanglTFModel.h"
+
+#define ENABLE_VALIDATION false
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	vkglTF::Model model;
+	vks::Buffer uniformBuffer;
+
+	struct {
+		glm::mat4 projection;
+		glm::mat4 model;
+		glm::mat4 view;
+		int32_t texIndex = 0;
+	} uboVS;
+
+	VkPipelineLayout pipelineLayout;
+	VkPipeline pipeline;
+	VkDescriptorSetLayout descriptorSetLayout;
+	VkDescriptorSet descriptorSet;
+
+	bool screenshotSaved = false;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Saving framebuffer to screenshot";
+		camera.type = Camera::CameraType::lookat;
+		camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 512.0f);
+		camera.setRotation(glm::vec3(-25.0f, 23.75f, 0.0f));
+		camera.setTranslation(glm::vec3(0.0f, 0.0f, -3.0f));
+	}
+
+	~VulkanExample()
+	{
+		vkDestroyPipeline(device, pipeline, nullptr);
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+		uniformBuffer.destroy();
+	}
+
+	void loadAssets()
+	{
+		model.loadFromFile(getAssetPath() + "models/chinesedragon.gltf", vulkanDevice, queue, vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::PreMultiplyVertexColors | vkglTF::FileLoadingFlags::FlipY);
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		clearValues[0].color = defaultClearColor;
+		clearValues[1].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.offset.x = 0;
+		renderPassBeginInfo.renderArea.offset.y = 0;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		renderPassBeginInfo.clearValueCount = 2;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			// Set target frame buffer
+			renderPassBeginInfo.framebuffer = frameBuffers[i];
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+			VkRect2D scissor = vks::initializers::rect2D(width, height,	0, 0);
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL);
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
+			model.draw(drawCmdBuffers[i]);
+
+			drawUI(drawCmdBuffers[i]);
+
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void setupDescriptorPool()
+	{
+		std::vector<VkDescriptorPoolSize> poolSizes = {
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1),
+		};
+		VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 2);
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+	}
+
+	void setupDescriptorSetLayout()
+	{
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0),		// Binding 0: Vertex shader uniform buffer
+		};
+		VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout));
+
+		VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1);
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayout));
+	}
+
+	void setupDescriptorSet()
+	{
+		VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1);
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet));
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets = {
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffer.descriptor),	// Binding 0: Vertex shader uniform buffer
+		};
+		vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, nullptr);
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+		VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
+		std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
+		VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
+
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages = {
+			loadShader(getShadersPath() + "screenshot/mesh.vert.spv", VK_SHADER_STAGE_VERTEX_BIT),
+			loadShader(getShadersPath() + "screenshot/mesh.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT),
+		};
+
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass, 0);
+		pipelineCI.pInputAssemblyState = &inputAssemblyState;
+		pipelineCI.pRasterizationState = &rasterizationState;
+		pipelineCI.pColorBlendState = &colorBlendState;
+		pipelineCI.pMultisampleState = &multisampleState;
+		pipelineCI.pViewportState = &viewportState;
+		pipelineCI.pDepthStencilState = &depthStencilState;
+		pipelineCI.pDynamicState = &dynamicState;
+		pipelineCI.stageCount = shaderStages.size();
+		pipelineCI.pStages = shaderStages.data();
+		pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::Color});
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipeline));
+	}
+
+	void prepareUniformBuffers()
+	{
+		// Vertex shader uniform buffer block
+		vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffer,
+			sizeof(uboVS));
+		VK_CHECK_RESULT(uniformBuffer.map());
+		updateUniformBuffers();
+	}
+
+	void updateUniformBuffers()
+	{
+		uboVS.projection = camera.matrices.perspective;
+		uboVS.view = camera.matrices.view;
+		uboVS.model = glm::mat4(1.0f);
+		uniformBuffer.copyTo(&uboVS, sizeof(uboVS));
+	}
+
+	// Take a screenshot from the current swapchain image
+	// This is done using a blit from the swapchain image to a linear image whose memory content is then saved as a ppm image
+	// Getting the image date directly from a swapchain image wouldn't work as they're usually stored in an implementation dependent optimal tiling format
+	// Note: This requires the swapchain images to be created with the VK_IMAGE_USAGE_TRANSFER_SRC_BIT flag (see VulkanSwapChain::create)
+	void saveScreenshot(const char *filename)
+	{
+		screenshotSaved = false;
+		bool supportsBlit = true;
+
+		// Check blit support for source and destination
+		VkFormatProperties formatProps;
+
+		// Check if the device supports blitting from optimal images (the swapchain images are in optimal format)
+		vkGetPhysicalDeviceFormatProperties(physicalDevice, swapChain.colorFormat, &formatProps);
+		if (!(formatProps.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT)) {
+			std::cerr << "Device does not support blitting from optimal tiled images, using copy instead of blit!" << std::endl;
+			supportsBlit = false;
+		}
+
+		// Check if the device supports blitting to linear images
+		vkGetPhysicalDeviceFormatProperties(physicalDevice, VK_FORMAT_R8G8B8A8_UNORM, &formatProps);
+		if (!(formatProps.linearTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT)) {
+			std::cerr << "Device does not support blitting to linear tiled images, using copy instead of blit!" << std::endl;
+			supportsBlit = false;
+		}
+
+		// Source for the copy is the last rendered swapchain image
+		VkImage srcImage = swapChain.images[currentBuffer];
+
+		// Create the linear tiled destination image to copy to and to read the memory from
+		VkImageCreateInfo imageCreateCI(vks::initializers::imageCreateInfo());
+		imageCreateCI.imageType = VK_IMAGE_TYPE_2D;
+		// Note that vkCmdBlitImage (if supported) will also do format conversions if the swapchain color format would differ
+		imageCreateCI.format = VK_FORMAT_R8G8B8A8_UNORM;
+		imageCreateCI.extent.width = width;
+		imageCreateCI.extent.height = height;
+		imageCreateCI.extent.depth = 1;
+		imageCreateCI.arrayLayers = 1;
+		imageCreateCI.mipLevels = 1;
+		imageCreateCI.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+		imageCreateCI.samples = VK_SAMPLE_COUNT_1_BIT;
+		imageCreateCI.tiling = VK_IMAGE_TILING_LINEAR;
+		imageCreateCI.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT;
+		// Create the image
+		VkImage dstImage;
+		VK_CHECK_RESULT(vkCreateImage(device, &imageCreateCI, nullptr, &dstImage));
+		// Create memory to back up the image
+		VkMemoryRequirements memRequirements;
+		VkMemoryAllocateInfo memAllocInfo(vks::initializers::memoryAllocateInfo());
+		VkDeviceMemory dstImageMemory;
+		vkGetImageMemoryRequirements(device, dstImage, &memRequirements);
+		memAllocInfo.allocationSize = memRequirements.size;
+		// Memory must be host visible to copy from
+		memAllocInfo.memoryTypeIndex = vulkanDevice->getMemoryType(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
+		VK_CHECK_RESULT(vkAllocateMemory(device, &memAllocInfo, nullptr, &dstImageMemory));
+		VK_CHECK_RESULT(vkBindImageMemory(device, dstImage, dstImageMemory, 0));
+
+		// Do the actual blit from the swapchain image to our host visible destination image
+		VkCommandBuffer copyCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+
+		// Transition destination image to transfer destination layout
+		vks::tools::insertImageMemoryBarrier(
+			copyCmd,
+			dstImage,
+			0,
+			VK_ACCESS_TRANSFER_WRITE_BIT,
+			VK_IMAGE_LAYOUT_UNDEFINED,
+			VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+			VK_PIPELINE_STAGE_TRANSFER_BIT,
+			VK_PIPELINE_STAGE_TRANSFER_BIT,
+			VkImageSubresourceRange{ VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 });
+
+		// Transition swapchain image from present to transfer source layout
+		vks::tools::insertImageMemoryBarrier(
+			copyCmd,
+			srcImage,
+			VK_ACCESS_MEMORY_READ_BIT,
+			VK_ACCESS_TRANSFER_READ_BIT,
+			VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
+			VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+			VK_PIPELINE_STAGE_TRANSFER_BIT,
+			VK_PIPELINE_STAGE_TRANSFER_BIT,
+			VkImageSubresourceRange{ VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 });
+
+		// If source and destination support blit we'll blit as this also does automatic format conversion (e.g. from BGR to RGB)
+		if (supportsBlit)
+		{
+			// Define the region to blit (we will blit the whole swapchain image)
+			VkOffset3D blitSize;
+			blitSize.x = width;
+			blitSize.y = height;
+			blitSize.z = 1;
+			VkImageBlit imageBlitRegion{};
+			imageBlitRegion.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+			imageBlitRegion.srcSubresource.layerCount = 1;
+			imageBlitRegion.srcOffsets[1] = blitSize;
+			imageBlitRegion.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+			imageBlitRegion.dstSubresource.layerCount = 1;
+			imageBlitRegion.dstOffsets[1] = blitSize;
+
+			// Issue the blit command
+			vkCmdBlitImage(
+				copyCmd,
+				srcImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+				dstImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+				1,
+				&imageBlitRegion,
+				VK_FILTER_NEAREST);
+		}
+		else
+		{
+			// Otherwise use image copy (requires us to manually flip components)
+			VkImageCopy imageCopyRegion{};
+			imageCopyRegion.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+			imageCopyRegion.srcSubresource.layerCount = 1;
+			imageCopyRegion.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+			imageCopyRegion.dstSubresource.layerCount = 1;
+			imageCopyRegion.extent.width = width;
+			imageCopyRegion.extent.height = height;
+			imageCopyRegion.extent.depth = 1;
+
+			// Issue the copy command
+			vkCmdCopyImage(
+				copyCmd,
+				srcImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+				dstImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+				1,
+				&imageCopyRegion);
+		}
+
+		// Transition destination image to general layout, which is the required layout for mapping the image memory later on
+		vks::tools::insertImageMemoryBarrier(
+			copyCmd,
+			dstImage,
+			VK_ACCESS_TRANSFER_WRITE_BIT,
+			VK_ACCESS_MEMORY_READ_BIT,
+			VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+			VK_IMAGE_LAYOUT_GENERAL,
+			VK_PIPELINE_STAGE_TRANSFER_BIT,
+			VK_PIPELINE_STAGE_TRANSFER_BIT,
+			VkImageSubresourceRange{ VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 });
+
+		// Transition back the swap chain image after the blit is done
+		vks::tools::insertImageMemoryBarrier(
+			copyCmd,
+			srcImage,
+			VK_ACCESS_TRANSFER_READ_BIT,
+			VK_ACCESS_MEMORY_READ_BIT,
+			VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+			VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
+			VK_PIPELINE_STAGE_TRANSFER_BIT,
+			VK_PIPELINE_STAGE_TRANSFER_BIT,
+			VkImageSubresourceRange{ VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 });
+
+		vulkanDevice->flushCommandBuffer(copyCmd, queue);
+
+		// Get layout of the image (including row pitch)
+		VkImageSubresource subResource { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0 };
+		VkSubresourceLayout subResourceLayout;
+		vkGetImageSubresourceLayout(device, dstImage, &subResource, &subResourceLayout);
+
+		// Map image memory so we can start copying from it
+		const char* data;
+		vkMapMemory(device, dstImageMemory, 0, VK_WHOLE_SIZE, 0, (void**)&data);
+		data += subResourceLayout.offset;
+
+		std::ofstream file(filename, std::ios::out | std::ios::binary);
+
+		// ppm header
+		file << "P6\n" << width << "\n" << height << "\n" << 255 << "\n";
+
+		// If source is BGR (destination is always RGB) and we can't use blit (which does automatic conversion), we'll have to manually swizzle color components
+		bool colorSwizzle = false;
+		// Check if source is BGR
+		// Note: Not complete, only contains most common and basic BGR surface formats for demonstration purposes
+		if (!supportsBlit)
+		{
+			std::vector<VkFormat> formatsBGR = { VK_FORMAT_B8G8R8A8_SRGB, VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_B8G8R8A8_SNORM };
+			colorSwizzle = (std::find(formatsBGR.begin(), formatsBGR.end(), swapChain.colorFormat) != formatsBGR.end());
+		}
+
+		// ppm binary pixel data
+		for (uint32_t y = 0; y < height; y++)
+		{
+			unsigned int *row = (unsigned int*)data;
+			for (uint32_t x = 0; x < width; x++)
+			{
+				if (colorSwizzle)
+				{
+					file.write((char*)row+2, 1);
+					file.write((char*)row+1, 1);
+					file.write((char*)row, 1);
+				}
+				else
+				{
+					file.write((char*)row, 3);
+				}
+				row++;
+			}
+			data += subResourceLayout.rowPitch;
+		}
+		file.close();
+
+		std::cout << "Screenshot saved to disk" << std::endl;
+
+		// Clean up resources
+		vkUnmapMemory(device, dstImageMemory);
+		vkFreeMemory(device, dstImageMemory, nullptr);
+		vkDestroyImage(device, dstImage, nullptr);
+
+		screenshotSaved = true;
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+
+		VulkanExampleBase::submitFrame();
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		loadAssets();
+		prepareUniformBuffers();
+		setupDescriptorSetLayout();
+		preparePipelines();
+		setupDescriptorPool();
+		setupDescriptorSet();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+	}
+
+	virtual void viewChanged()
+	{
+		updateUniformBuffers();
+	}
+
+	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
+	{
+		if (overlay->header("Functions")) {
+			if (overlay->button("Take screenshot")) {
+				saveScreenshot("screenshot.ppm");
+			}
+			if (screenshotSaved) {
+				overlay->text("Screenshot saved as screenshot.ppm");
+			}
+		}
+	}
+
+};
+
+VULKAN_EXAMPLE_MAIN()
\ No newline at end of file
diff --git a/external/Vulkan/examples/shadowmapping/shadowmapping.cpp b/external/Vulkan/examples/shadowmapping/shadowmapping.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..0592b4fcc1758a1a1a211b98ae393167b92bffc7
--- /dev/null
+++ b/external/Vulkan/examples/shadowmapping/shadowmapping.cpp
@@ -0,0 +1,619 @@
+/*
+* Vulkan Example - Shadow mapping for directional light sources
+*
+* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkanexamplebase.h"
+#include "VulkanglTFModel.h"
+
+#define ENABLE_VALIDATION false
+
+// 16 bits of depth is enough for such a small scene
+#define DEPTH_FORMAT VK_FORMAT_D16_UNORM
+
+// Shadowmap properties
+#if defined(__ANDROID__)
+#define SHADOWMAP_DIM 1024
+#else
+#define SHADOWMAP_DIM 2048
+#endif
+#define DEFAULT_SHADOWMAP_FILTER VK_FILTER_LINEAR
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	bool displayShadowMap = false;
+	bool filterPCF = true;
+
+	// Keep depth range as small as possible
+	// for better shadow map precision
+	float zNear = 1.0f;
+	float zFar = 96.0f;
+
+	// Depth bias (and slope) are used to avoid shadowing artifacts
+	// Constant depth bias factor (always applied)
+	float depthBiasConstant = 1.25f;
+	// Slope depth bias factor, applied depending on polygon's slope
+	float depthBiasSlope = 1.75f;
+
+	glm::vec3 lightPos = glm::vec3();
+	float lightFOV = 45.0f;
+
+	std::vector<vkglTF::Model> scenes;
+	std::vector<std::string> sceneNames;
+	int32_t sceneIndex = 0;
+
+	struct {
+		vks::Buffer scene;
+		vks::Buffer offscreen;
+	} uniformBuffers;
+
+	struct {
+		glm::mat4 projection;
+		glm::mat4 view;
+		glm::mat4 model;
+		glm::mat4 depthBiasMVP;
+		glm::vec3 lightPos;
+	} uboVSscene;
+
+	struct {
+		glm::mat4 depthMVP;
+	} uboOffscreenVS;
+
+	struct {
+		VkPipeline offscreen;
+		VkPipeline sceneShadow;
+		VkPipeline sceneShadowPCF;
+		VkPipeline debug;
+	} pipelines;
+	VkPipelineLayout pipelineLayout;
+
+	struct {
+		VkDescriptorSet offscreen;
+		VkDescriptorSet scene;
+		VkDescriptorSet debug;
+	} descriptorSets;
+
+	VkDescriptorSetLayout descriptorSetLayout;
+
+	// Framebuffer for offscreen rendering
+	struct FrameBufferAttachment {
+		VkImage image;
+		VkDeviceMemory mem;
+		VkImageView view;
+	};
+	struct OffscreenPass {
+		int32_t width, height;
+		VkFramebuffer frameBuffer;
+		FrameBufferAttachment depth;
+		VkRenderPass renderPass;
+		VkSampler depthSampler;
+		VkDescriptorImageInfo descriptor;
+	} offscreenPass;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Projected shadow mapping";
+		camera.type = Camera::CameraType::lookat;
+		camera.setPosition(glm::vec3(0.0f, -0.0f, -20.0f));
+		camera.setRotation(glm::vec3(-15.0f, -390.0f, 0.0f));
+		camera.setPerspective(60.0f, (float)width / (float)height, 1.0f, 256.0f);
+		timerSpeed *= 0.5f;
+	}
+
+	~VulkanExample()
+	{
+		// Clean up used Vulkan resources
+		// Note : Inherited destructor cleans up resources stored in base class
+
+		// Frame buffer
+		vkDestroySampler(device, offscreenPass.depthSampler, nullptr);
+
+		// Depth attachment
+		vkDestroyImageView(device, offscreenPass.depth.view, nullptr);
+		vkDestroyImage(device, offscreenPass.depth.image, nullptr);
+		vkFreeMemory(device, offscreenPass.depth.mem, nullptr);
+
+		vkDestroyFramebuffer(device, offscreenPass.frameBuffer, nullptr);
+
+		vkDestroyRenderPass(device, offscreenPass.renderPass, nullptr);
+
+		vkDestroyPipeline(device, pipelines.debug, nullptr);
+		vkDestroyPipeline(device, pipelines.offscreen, nullptr);
+		vkDestroyPipeline(device, pipelines.sceneShadow, nullptr);
+		vkDestroyPipeline(device, pipelines.sceneShadowPCF, nullptr);
+
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+
+		// Uniform buffers
+		uniformBuffers.offscreen.destroy();
+		uniformBuffers.scene.destroy();
+	}
+
+	// Set up a separate render pass for the offscreen frame buffer
+	// This is necessary as the offscreen frame buffer attachments use formats different to those from the example render pass
+	void prepareOffscreenRenderpass()
+	{
+		VkAttachmentDescription attachmentDescription{};
+		attachmentDescription.format = DEPTH_FORMAT;
+		attachmentDescription.samples = VK_SAMPLE_COUNT_1_BIT;
+		attachmentDescription.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;							// Clear depth at beginning of the render pass
+		attachmentDescription.storeOp = VK_ATTACHMENT_STORE_OP_STORE;						// We will read from depth, so it's important to store the depth attachment results
+		attachmentDescription.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+		attachmentDescription.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+		attachmentDescription.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;					// We don't care about initial layout of the attachment
+		attachmentDescription.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL;// Attachment will be transitioned to shader read at render pass end
+
+		VkAttachmentReference depthReference = {};
+		depthReference.attachment = 0;
+		depthReference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;			// Attachment will be used as depth/stencil during render pass
+
+		VkSubpassDescription subpass = {};
+		subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+		subpass.colorAttachmentCount = 0;													// No color attachments
+		subpass.pDepthStencilAttachment = &depthReference;									// Reference to our depth attachment
+
+		// Use subpass dependencies for layout transitions
+		std::array<VkSubpassDependency, 2> dependencies;
+
+		dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
+		dependencies[0].dstSubpass = 0;
+		dependencies[0].srcStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
+		dependencies[0].dstStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
+		dependencies[0].srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
+		dependencies[0].dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
+		dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+		dependencies[1].srcSubpass = 0;
+		dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
+		dependencies[1].srcStageMask = VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
+		dependencies[1].dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
+		dependencies[1].srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
+		dependencies[1].dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
+		dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+		VkRenderPassCreateInfo renderPassCreateInfo = vks::initializers::renderPassCreateInfo();
+		renderPassCreateInfo.attachmentCount = 1;
+		renderPassCreateInfo.pAttachments = &attachmentDescription;
+		renderPassCreateInfo.subpassCount = 1;
+		renderPassCreateInfo.pSubpasses = &subpass;
+		renderPassCreateInfo.dependencyCount = static_cast<uint32_t>(dependencies.size());
+		renderPassCreateInfo.pDependencies = dependencies.data();
+
+		VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassCreateInfo, nullptr, &offscreenPass.renderPass));
+	}
+
+	// Setup the offscreen framebuffer for rendering the scene from light's point-of-view to
+	// The depth attachment of this framebuffer will then be used to sample from in the fragment shader of the shadowing pass
+	void prepareOffscreenFramebuffer()
+	{
+		offscreenPass.width = SHADOWMAP_DIM;
+		offscreenPass.height = SHADOWMAP_DIM;
+
+		// For shadow mapping we only need a depth attachment
+		VkImageCreateInfo image = vks::initializers::imageCreateInfo();
+		image.imageType = VK_IMAGE_TYPE_2D;
+		image.extent.width = offscreenPass.width;
+		image.extent.height = offscreenPass.height;
+		image.extent.depth = 1;
+		image.mipLevels = 1;
+		image.arrayLayers = 1;
+		image.samples = VK_SAMPLE_COUNT_1_BIT;
+		image.tiling = VK_IMAGE_TILING_OPTIMAL;
+		image.format = DEPTH_FORMAT;																// Depth stencil attachment
+		image.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;		// We will sample directly from the depth attachment for the shadow mapping
+		VK_CHECK_RESULT(vkCreateImage(device, &image, nullptr, &offscreenPass.depth.image));
+
+		VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo();
+		VkMemoryRequirements memReqs;
+		vkGetImageMemoryRequirements(device, offscreenPass.depth.image, &memReqs);
+		memAlloc.allocationSize = memReqs.size;
+		memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+		VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &offscreenPass.depth.mem));
+		VK_CHECK_RESULT(vkBindImageMemory(device, offscreenPass.depth.image, offscreenPass.depth.mem, 0));
+
+		VkImageViewCreateInfo depthStencilView = vks::initializers::imageViewCreateInfo();
+		depthStencilView.viewType = VK_IMAGE_VIEW_TYPE_2D;
+		depthStencilView.format = DEPTH_FORMAT;
+		depthStencilView.subresourceRange = {};
+		depthStencilView.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
+		depthStencilView.subresourceRange.baseMipLevel = 0;
+		depthStencilView.subresourceRange.levelCount = 1;
+		depthStencilView.subresourceRange.baseArrayLayer = 0;
+		depthStencilView.subresourceRange.layerCount = 1;
+		depthStencilView.image = offscreenPass.depth.image;
+		VK_CHECK_RESULT(vkCreateImageView(device, &depthStencilView, nullptr, &offscreenPass.depth.view));
+
+		// Create sampler to sample from to depth attachment
+		// Used to sample in the fragment shader for shadowed rendering
+		VkFilter shadowmap_filter = vks::tools::formatIsFilterable(physicalDevice, DEPTH_FORMAT, VK_IMAGE_TILING_OPTIMAL) ?
+		   DEFAULT_SHADOWMAP_FILTER :
+		   VK_FILTER_NEAREST;
+		VkSamplerCreateInfo sampler = vks::initializers::samplerCreateInfo();
+		sampler.magFilter = shadowmap_filter;
+		sampler.minFilter = shadowmap_filter;
+		sampler.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+		sampler.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+		sampler.addressModeV = sampler.addressModeU;
+		sampler.addressModeW = sampler.addressModeU;
+		sampler.mipLodBias = 0.0f;
+		sampler.maxAnisotropy = 1.0f;
+		sampler.minLod = 0.0f;
+		sampler.maxLod = 1.0f;
+		sampler.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
+		VK_CHECK_RESULT(vkCreateSampler(device, &sampler, nullptr, &offscreenPass.depthSampler));
+
+		prepareOffscreenRenderpass();
+
+		// Create frame buffer
+		VkFramebufferCreateInfo fbufCreateInfo = vks::initializers::framebufferCreateInfo();
+		fbufCreateInfo.renderPass = offscreenPass.renderPass;
+		fbufCreateInfo.attachmentCount = 1;
+		fbufCreateInfo.pAttachments = &offscreenPass.depth.view;
+		fbufCreateInfo.width = offscreenPass.width;
+		fbufCreateInfo.height = offscreenPass.height;
+		fbufCreateInfo.layers = 1;
+
+		VK_CHECK_RESULT(vkCreateFramebuffer(device, &fbufCreateInfo, nullptr, &offscreenPass.frameBuffer));
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		VkViewport viewport;
+		VkRect2D scissor;
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			/*
+				First render pass: Generate shadow map by rendering the scene from light's POV
+			*/
+			{
+				clearValues[0].depthStencil = { 1.0f, 0 };
+
+				VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+				renderPassBeginInfo.renderPass = offscreenPass.renderPass;
+				renderPassBeginInfo.framebuffer = offscreenPass.frameBuffer;
+				renderPassBeginInfo.renderArea.extent.width = offscreenPass.width;
+				renderPassBeginInfo.renderArea.extent.height = offscreenPass.height;
+				renderPassBeginInfo.clearValueCount = 1;
+				renderPassBeginInfo.pClearValues = clearValues;
+
+				vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+				viewport = vks::initializers::viewport((float)offscreenPass.width, (float)offscreenPass.height, 0.0f, 1.0f);
+				vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+				scissor = vks::initializers::rect2D(offscreenPass.width, offscreenPass.height, 0, 0);
+				vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+				// Set depth bias (aka "Polygon offset")
+				// Required to avoid shadow mapping artifacts
+				vkCmdSetDepthBias(
+					drawCmdBuffers[i],
+					depthBiasConstant,
+					0.0f,
+					depthBiasSlope);
+
+				vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.offscreen);
+				vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets.offscreen, 0, nullptr);
+				scenes[sceneIndex].draw(drawCmdBuffers[i]);
+
+				vkCmdEndRenderPass(drawCmdBuffers[i]);
+			}
+
+			/*
+				Note: Explicit synchronization is not required between the render pass, as this is done implicit via sub pass dependencies
+			*/
+
+			/*
+				Second pass: Scene rendering with applied shadow map
+			*/
+
+			{
+				clearValues[0].color = defaultClearColor;
+				clearValues[1].depthStencil = { 1.0f, 0 };
+
+				VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+				renderPassBeginInfo.renderPass = renderPass;
+				renderPassBeginInfo.framebuffer = frameBuffers[i];
+				renderPassBeginInfo.renderArea.extent.width = width;
+				renderPassBeginInfo.renderArea.extent.height = height;
+				renderPassBeginInfo.clearValueCount = 2;
+				renderPassBeginInfo.pClearValues = clearValues;
+
+				vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+				viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+				vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+				scissor = vks::initializers::rect2D(width, height, 0, 0);
+				vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+				// Visualize shadow map
+				if (displayShadowMap) {
+					vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets.debug, 0, nullptr);
+					vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.debug);
+					vkCmdDraw(drawCmdBuffers[i], 3, 1, 0, 0);
+				}
+
+				// 3D scene
+				vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets.scene, 0, nullptr);
+				vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, (filterPCF) ? pipelines.sceneShadowPCF : pipelines.sceneShadow);
+				scenes[sceneIndex].draw(drawCmdBuffers[i]);
+
+				drawUI(drawCmdBuffers[i]);
+
+				vkCmdEndRenderPass(drawCmdBuffers[i]);
+			}
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void loadAssets()
+	{
+		const uint32_t glTFLoadingFlags = vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::PreMultiplyVertexColors | vkglTF::FileLoadingFlags::FlipY;
+		scenes.resize(2);
+		scenes[0].loadFromFile(getAssetPath() + "models/vulkanscene_shadow.gltf", vulkanDevice, queue, glTFLoadingFlags);
+		scenes[1].loadFromFile(getAssetPath() + "models/samplescene.gltf", vulkanDevice, queue, glTFLoadingFlags);
+		sceneNames = {"Vulkan scene", "Teapots and pillars" };
+	}
+
+	void setupDescriptorPool()
+	{
+		std::vector<VkDescriptorPoolSize> poolSizes = {
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 3),
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 3)
+		};
+		VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 3);
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+	}
+
+	void setupDescriptorSetLayout()
+	{
+		// Shared pipeline layout for all pipelines used in this sample
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			// Binding 0 : Vertex shader uniform buffer
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0),
+			// Binding 1 : Fragment shader image sampler (shadow map)
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1)
+		};
+		VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout));
+		VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1);
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayout));
+	}
+
+	void setupDescriptorSets()
+	{
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets;
+
+		// Image descriptor for the shadow map attachment
+		VkDescriptorImageInfo shadowMapDescriptor =
+		    vks::initializers::descriptorImageInfo(
+		        offscreenPass.depthSampler,
+		        offscreenPass.depth.view,
+		        VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL);
+
+		// Debug display
+		VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1);
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.debug));
+		writeDescriptorSets = {
+			// Binding 1 : Fragment shader texture sampler
+		    vks::initializers::writeDescriptorSet(descriptorSets.debug, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &shadowMapDescriptor)
+		};
+		vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, nullptr);
+
+		// Offscreen shadow map generation
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.offscreen));
+		writeDescriptorSets = {
+			// Binding 0 : Vertex shader uniform buffer
+			vks::initializers::writeDescriptorSet(descriptorSets.offscreen, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.offscreen.descriptor),
+		};
+		vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, nullptr);
+
+		// Scene rendering with shadow map applied
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.scene));
+		writeDescriptorSets = {
+			// Binding 0 : Vertex shader uniform buffer
+			vks::initializers::writeDescriptorSet(descriptorSets.scene, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.scene.descriptor),
+			// Binding 1 : Fragment shader shadow sampler
+		    vks::initializers::writeDescriptorSet(descriptorSets.scene, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &shadowMapDescriptor)
+		};
+		vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, nullptr);
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyStateCI = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationStateCI = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendStateCI = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilStateCI = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportStateCI = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+		VkPipelineMultisampleStateCreateInfo multisampleStateCI = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
+		std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
+		VkPipelineDynamicStateCreateInfo dynamicStateCI = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables.data(), dynamicStateEnables.size(), 0);
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass, 0);
+		pipelineCI.pInputAssemblyState = &inputAssemblyStateCI;
+		pipelineCI.pRasterizationState = &rasterizationStateCI;
+		pipelineCI.pColorBlendState = &colorBlendStateCI;
+		pipelineCI.pMultisampleState = &multisampleStateCI;
+		pipelineCI.pViewportState = &viewportStateCI;
+		pipelineCI.pDepthStencilState = &depthStencilStateCI;
+		pipelineCI.pDynamicState = &dynamicStateCI;
+		pipelineCI.stageCount = shaderStages.size();
+		pipelineCI.pStages = shaderStages.data();
+
+		// Shadow mapping debug quad display
+		rasterizationStateCI.cullMode = VK_CULL_MODE_NONE;
+		shaderStages[0] = loadShader(getShadersPath() + "shadowmapping/quad.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "shadowmapping/quad.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		// Empty vertex input state
+		VkPipelineVertexInputStateCreateInfo emptyInputState = vks::initializers::pipelineVertexInputStateCreateInfo();
+		pipelineCI.pVertexInputState = &emptyInputState;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.debug));
+
+		// Scene rendering with shadows applied
+		pipelineCI.pVertexInputState  = vkglTF::Vertex::getPipelineVertexInputState({vkglTF::VertexComponent::Position, vkglTF::VertexComponent::UV, vkglTF::VertexComponent::Color, vkglTF::VertexComponent::Normal});
+		rasterizationStateCI.cullMode = VK_CULL_MODE_BACK_BIT;
+		shaderStages[0] = loadShader(getShadersPath() + "shadowmapping/scene.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "shadowmapping/scene.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		// Use specialization constants to select between horizontal and vertical blur
+		uint32_t enablePCF = 0;
+		VkSpecializationMapEntry specializationMapEntry = vks::initializers::specializationMapEntry(0, 0, sizeof(uint32_t));
+		VkSpecializationInfo specializationInfo = vks::initializers::specializationInfo(1, &specializationMapEntry, sizeof(uint32_t), &enablePCF);
+		shaderStages[1].pSpecializationInfo = &specializationInfo;
+		// No filtering
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.sceneShadow));
+		// PCF filtering
+		enablePCF = 1;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.sceneShadowPCF));
+
+		// Offscreen pipeline (vertex shader only)
+		shaderStages[0] = loadShader(getShadersPath() + "shadowmapping/offscreen.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		pipelineCI.stageCount = 1;
+		// No blend attachment states (no color attachments used)
+		colorBlendStateCI.attachmentCount = 0;
+		// Cull front faces
+		depthStencilStateCI.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL;
+		// Enable depth bias
+		rasterizationStateCI.depthBiasEnable = VK_TRUE;
+		// Add depth bias to dynamic state, so we can change it at runtime
+		dynamicStateEnables.push_back(VK_DYNAMIC_STATE_DEPTH_BIAS);
+		dynamicStateCI =
+			vks::initializers::pipelineDynamicStateCreateInfo(
+				dynamicStateEnables.data(),
+				dynamicStateEnables.size(),
+				0);
+
+		pipelineCI.renderPass = offscreenPass.renderPass;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.offscreen));
+	}
+
+	// Prepare and initialize uniform buffer containing shader uniforms
+	void prepareUniformBuffers()
+	{
+		// Offscreen vertex shader uniform buffer block
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.offscreen,
+			sizeof(uboOffscreenVS)));
+
+		// Scene vertex shader uniform buffer block
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.scene,
+			sizeof(uboVSscene)));
+
+		// Map persistent
+		VK_CHECK_RESULT(uniformBuffers.offscreen.map());
+		VK_CHECK_RESULT(uniformBuffers.scene.map());
+
+		updateLight();
+		updateUniformBufferOffscreen();
+		updateUniformBuffers();
+	}
+
+	void updateLight()
+	{
+		// Animate the light source
+		lightPos.x = cos(glm::radians(timer * 360.0f)) * 40.0f;
+		lightPos.y = -50.0f + sin(glm::radians(timer * 360.0f)) * 20.0f;
+		lightPos.z = 25.0f + sin(glm::radians(timer * 360.0f)) * 5.0f;
+	}
+
+	void updateUniformBuffers()
+	{
+		uboVSscene.projection = camera.matrices.perspective;
+		uboVSscene.view = camera.matrices.view;
+		uboVSscene.model = glm::mat4(1.0f);
+		uboVSscene.lightPos = lightPos;
+		uboVSscene.depthBiasMVP = uboOffscreenVS.depthMVP;
+		memcpy(uniformBuffers.scene.mapped, &uboVSscene, sizeof(uboVSscene));
+	}
+
+	void updateUniformBufferOffscreen()
+	{
+		// Matrix from light's point of view
+		glm::mat4 depthProjectionMatrix = glm::perspective(glm::radians(lightFOV), 1.0f, zNear, zFar);
+		glm::mat4 depthViewMatrix = glm::lookAt(lightPos, glm::vec3(0.0f), glm::vec3(0, 1, 0));
+		glm::mat4 depthModelMatrix = glm::mat4(1.0f);
+
+		uboOffscreenVS.depthMVP = depthProjectionMatrix * depthViewMatrix * depthModelMatrix;
+
+		memcpy(uniformBuffers.offscreen.mapped, &uboOffscreenVS, sizeof(uboOffscreenVS));
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+
+		// Command buffer to be submitted to the queue
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+
+		// Submit to queue
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+
+		VulkanExampleBase::submitFrame();
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		loadAssets();
+		prepareOffscreenFramebuffer();
+		prepareUniformBuffers();
+		setupDescriptorSetLayout();
+		preparePipelines();
+		setupDescriptorPool();
+		setupDescriptorSets();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+		if (!paused || camera.updated)
+		{
+			updateLight();
+			updateUniformBufferOffscreen();
+			updateUniformBuffers();
+		}
+	}
+
+	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
+	{
+		if (overlay->header("Settings")) {
+			if (overlay->comboBox("Scenes", &sceneIndex, sceneNames)) {
+				buildCommandBuffers();
+			}
+			if (overlay->checkBox("Display shadow render target", &displayShadowMap)) {
+				buildCommandBuffers();
+			}
+			if (overlay->checkBox("PCF filtering", &filterPCF)) {
+				buildCommandBuffers();
+			}
+		}
+	}
+};
+
+VULKAN_EXAMPLE_MAIN()
diff --git a/external/Vulkan/examples/shadowmappingcascade/shadowmappingcascade.cpp b/external/Vulkan/examples/shadowmappingcascade/shadowmappingcascade.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b1ff72ca3256c5f73377b87001b2419027a12a7e
--- /dev/null
+++ b/external/Vulkan/examples/shadowmappingcascade/shadowmappingcascade.cpp
@@ -0,0 +1,817 @@
+/*
+	Vulkan Example - Cascaded shadow mapping for directional light sources
+	Copyright by Sascha Willems - www.saschawillems.de
+	This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+/*
+	This example implements projective cascaded shadow mapping. This technique splits up the camera frustum into
+	multiple frustums with each getting its own full-res shadow map, implemented as a layered depth-only image.
+	The shader then selects the proper shadow map layer depending on what split of the frustum the depth value
+	to compare fits into.
+
+	This results in a better shadow map resolution distribution that can be tweaked even further by increasing
+	the number of frustum splits.
+
+	A further optimization could be done using a geometry shader to do a single-pass render for the depth map
+	cascades instead of multiple passes (geometry shaders are not supported on all target devices).
+*/
+
+#include "vulkanexamplebase.h"
+#include "VulkanglTFModel.h"
+
+#define ENABLE_VALIDATION false
+
+#if defined(__ANDROID__)
+#define SHADOWMAP_DIM 2048
+#else
+#define SHADOWMAP_DIM 4096
+#endif
+
+#define SHADOW_MAP_CASCADE_COUNT 4
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	bool displayDepthMap = false;
+	int32_t displayDepthMapCascadeIndex = 0;
+	bool colorCascades = false;
+	bool filterPCF = false;
+
+	float cascadeSplitLambda = 0.95f;
+
+	float zNear = 0.5f;
+	float zFar = 48.0f;
+
+	glm::vec3 lightPos = glm::vec3();
+
+	struct Models {
+		vkglTF::Model terrain;
+		vkglTF::Model tree;
+	} models;
+
+	struct uniformBuffers {
+		vks::Buffer VS;
+		vks::Buffer FS;
+	} uniformBuffers;
+
+	struct UBOVS {
+		glm::mat4 projection;
+		glm::mat4 view;
+		glm::mat4 model;
+		glm::vec3 lightDir;
+	} uboVS;
+
+	struct UBOFS {
+		float cascadeSplits[4];
+		glm::mat4 cascadeViewProjMat[4];
+		glm::mat4 inverseViewMat;
+		glm::vec3 lightDir;
+		float _pad;
+		int32_t colorCascades;
+	} uboFS;
+
+	VkPipelineLayout pipelineLayout;
+	struct Pipelines {
+		VkPipeline debugShadowMap;
+		VkPipeline sceneShadow;
+		VkPipeline sceneShadowPCF;
+	} pipelines;
+
+	struct DescriptorSetLayouts {
+		VkDescriptorSetLayout base;
+	} descriptorSetLayouts;
+	VkDescriptorSet descriptorSet;
+
+	// For simplicity all pipelines use the same push constant block layout
+	struct PushConstBlock {
+		glm::vec4 position;
+		uint32_t cascadeIndex;
+	};
+
+	// Resources of the depth map generation pass
+	struct DepthPass {
+		VkRenderPass renderPass;
+		VkPipelineLayout pipelineLayout;
+		VkPipeline pipeline;
+		vks::Buffer uniformBuffer;
+
+		struct UniformBlock {
+			std::array<glm::mat4, SHADOW_MAP_CASCADE_COUNT> cascadeViewProjMat;
+		} ubo;
+
+	} depthPass;
+
+	// Layered depth image containing the shadow cascade depths
+	struct DepthImage {
+		VkImage image;
+		VkDeviceMemory mem;
+		VkImageView view;
+		VkSampler sampler;
+		void destroy(VkDevice device) {
+			vkDestroyImageView(device, view, nullptr);
+			vkDestroyImage(device, image, nullptr);
+			vkFreeMemory(device, mem, nullptr);
+			vkDestroySampler(device, sampler, nullptr);
+		}
+	} depth;
+
+	// Contains all resources required for a single shadow map cascade
+	struct Cascade {
+		VkFramebuffer frameBuffer;
+		VkDescriptorSet descriptorSet;
+		VkImageView view;
+
+		float splitDepth;
+		glm::mat4 viewProjMatrix;
+
+		void destroy(VkDevice device) {
+			vkDestroyImageView(device, view, nullptr);
+			vkDestroyFramebuffer(device, frameBuffer, nullptr);
+		}
+	};
+	std::array<Cascade, SHADOW_MAP_CASCADE_COUNT> cascades;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Cascaded shadow mapping";
+		timerSpeed *= 0.025f;
+		camera.type = Camera::CameraType::firstperson;
+		camera.movementSpeed = 2.5f;
+		camera.setPerspective(45.0f, (float)width / (float)height, zNear, zFar);
+		camera.setPosition(glm::vec3(-0.12f, 1.14f, -2.25f));
+		camera.setRotation(glm::vec3(-17.0f, 7.0f, 0.0f));
+		timer = 0.2f;
+	}
+
+	~VulkanExample()
+	{
+		for (auto cascade : cascades) {
+			cascade.destroy(device);
+		}
+		depth.destroy(device);
+
+		vkDestroyRenderPass(device, depthPass.renderPass, nullptr);
+
+		vkDestroyPipeline(device, pipelines.debugShadowMap, nullptr);
+		vkDestroyPipeline(device, depthPass.pipeline, nullptr);
+		vkDestroyPipeline(device, pipelines.sceneShadow, nullptr);
+		vkDestroyPipeline(device, pipelines.sceneShadowPCF, nullptr);
+
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+		vkDestroyPipelineLayout(device, depthPass.pipelineLayout, nullptr);
+
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.base, nullptr);
+
+		depthPass.uniformBuffer.destroy();
+		uniformBuffers.VS.destroy();
+		uniformBuffers.FS.destroy();
+	}
+
+	virtual void getEnabledFeatures()
+	{
+		enabledFeatures.samplerAnisotropy = deviceFeatures.samplerAnisotropy;
+		// Depth clamp to avoid near plane clipping
+		enabledFeatures.depthClamp = deviceFeatures.depthClamp;
+	}
+
+	/*
+		Render the example scene with given command buffer, pipeline layout and descriptor set
+		Used by the scene rendering and depth pass generation command buffer
+	*/
+	void renderScene(VkCommandBuffer commandBuffer, VkPipelineLayout pipelineLayout, VkDescriptorSet descriptorSet, uint32_t cascadeIndex = 0) {
+		// We use push constants for passing shadow cascade info to the shaders
+		PushConstBlock pushConstBlock = { glm::vec4(0.0f), cascadeIndex };
+
+		// Set 0 contains the vertex and fragment shader uniform buffers, set 1 for images will be set by the glTF model class at draw time
+		vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr);
+
+		// Floor
+		vkCmdPushConstants(commandBuffer, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(PushConstBlock), &pushConstBlock);
+		models.terrain.draw(commandBuffer, vkglTF::RenderFlags::BindImages, pipelineLayout);
+
+		// Trees
+		const std::vector<glm::vec3> positions = {
+			glm::vec3(0.0f, 0.0f, 0.0f),
+			glm::vec3(1.25f, 0.25f, 1.25f),
+			glm::vec3(-1.25f, -0.2f, 1.25f),
+			glm::vec3(1.25f, 0.1f, -1.25f),
+			glm::vec3(-1.25f, -0.25f, -1.25f),
+		};
+
+		for (auto position : positions) {
+			pushConstBlock.position = glm::vec4(position, 0.0f);
+			vkCmdPushConstants(commandBuffer, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(PushConstBlock), &pushConstBlock);
+			vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr);
+			models.tree.draw(commandBuffer, vkglTF::RenderFlags::BindImages, pipelineLayout);
+		}
+	}
+
+	/*
+		Setup resources used by the depth pass
+		The depth image is layered with each layer storing one shadow map cascade
+	*/
+	void prepareDepthPass()
+	{
+		VkFormat depthFormat = vulkanDevice->getSupportedDepthFormat(true);
+
+		/*
+			Depth map renderpass
+		*/
+
+		VkAttachmentDescription attachmentDescription{};
+		attachmentDescription.format = depthFormat;
+		attachmentDescription.samples = VK_SAMPLE_COUNT_1_BIT;
+		attachmentDescription.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+		attachmentDescription.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+		attachmentDescription.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+		attachmentDescription.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+		attachmentDescription.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+		attachmentDescription.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL;
+
+		VkAttachmentReference depthReference = {};
+		depthReference.attachment = 0;
+		depthReference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+
+		VkSubpassDescription subpass = {};
+		subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+		subpass.colorAttachmentCount = 0;
+		subpass.pDepthStencilAttachment = &depthReference;
+
+		// Use subpass dependencies for layout transitions
+		std::array<VkSubpassDependency, 2> dependencies;
+
+		dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
+		dependencies[0].dstSubpass = 0;
+		dependencies[0].srcStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
+		dependencies[0].dstStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
+		dependencies[0].srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
+		dependencies[0].dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
+		dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+		dependencies[1].srcSubpass = 0;
+		dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
+		dependencies[1].srcStageMask = VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
+		dependencies[1].dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
+		dependencies[1].srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
+		dependencies[1].dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
+		dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+		VkRenderPassCreateInfo renderPassCreateInfo = vks::initializers::renderPassCreateInfo();
+		renderPassCreateInfo.attachmentCount = 1;
+		renderPassCreateInfo.pAttachments = &attachmentDescription;
+		renderPassCreateInfo.subpassCount = 1;
+		renderPassCreateInfo.pSubpasses = &subpass;
+		renderPassCreateInfo.dependencyCount = static_cast<uint32_t>(dependencies.size());
+		renderPassCreateInfo.pDependencies = dependencies.data();
+
+		VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassCreateInfo, nullptr, &depthPass.renderPass));
+
+		/*
+			Layered depth image and views
+		*/
+
+		VkImageCreateInfo imageInfo = vks::initializers::imageCreateInfo();
+		imageInfo.imageType = VK_IMAGE_TYPE_2D;
+		imageInfo.extent.width = SHADOWMAP_DIM;
+		imageInfo.extent.height = SHADOWMAP_DIM;
+		imageInfo.extent.depth = 1;
+		imageInfo.mipLevels = 1;
+		imageInfo.arrayLayers = SHADOW_MAP_CASCADE_COUNT;
+		imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
+		imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
+		imageInfo.format = depthFormat;
+		imageInfo.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
+		VK_CHECK_RESULT(vkCreateImage(device, &imageInfo, nullptr, &depth.image));
+		VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo();
+		VkMemoryRequirements memReqs;
+		vkGetImageMemoryRequirements(device, depth.image, &memReqs);
+		memAlloc.allocationSize = memReqs.size;
+		memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+		VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &depth.mem));
+		VK_CHECK_RESULT(vkBindImageMemory(device, depth.image, depth.mem, 0));
+		// Full depth map view (all layers)
+		VkImageViewCreateInfo viewInfo = vks::initializers::imageViewCreateInfo();
+		viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY;
+		viewInfo.format = depthFormat;
+		viewInfo.subresourceRange = {};
+		viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
+		viewInfo.subresourceRange.baseMipLevel = 0;
+		viewInfo.subresourceRange.levelCount = 1;
+		viewInfo.subresourceRange.baseArrayLayer = 0;
+		viewInfo.subresourceRange.layerCount = SHADOW_MAP_CASCADE_COUNT;
+		viewInfo.image = depth.image;
+		VK_CHECK_RESULT(vkCreateImageView(device, &viewInfo, nullptr, &depth.view));
+
+		// One image and framebuffer per cascade
+		for (uint32_t i = 0; i < SHADOW_MAP_CASCADE_COUNT; i++) {
+			// Image view for this cascade's layer (inside the depth map)
+			// This view is used to render to that specific depth image layer
+			VkImageViewCreateInfo viewInfo = vks::initializers::imageViewCreateInfo();
+			viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY;
+			viewInfo.format = depthFormat;
+			viewInfo.subresourceRange = {};
+			viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
+			viewInfo.subresourceRange.baseMipLevel = 0;
+			viewInfo.subresourceRange.levelCount = 1;
+			viewInfo.subresourceRange.baseArrayLayer = i;
+			viewInfo.subresourceRange.layerCount = 1;
+			viewInfo.image = depth.image;
+			VK_CHECK_RESULT(vkCreateImageView(device, &viewInfo, nullptr, &cascades[i].view));
+			// Framebuffer
+			VkFramebufferCreateInfo framebufferInfo = vks::initializers::framebufferCreateInfo();
+			framebufferInfo.renderPass = depthPass.renderPass;
+			framebufferInfo.attachmentCount = 1;
+			framebufferInfo.pAttachments = &cascades[i].view;
+			framebufferInfo.width = SHADOWMAP_DIM;
+			framebufferInfo.height = SHADOWMAP_DIM;
+			framebufferInfo.layers = 1;
+			VK_CHECK_RESULT(vkCreateFramebuffer(device, &framebufferInfo, nullptr, &cascades[i].frameBuffer));
+		}
+
+		// Shared sampler for cascade depth reads
+		VkSamplerCreateInfo sampler = vks::initializers::samplerCreateInfo();
+		sampler.magFilter = VK_FILTER_LINEAR;
+		sampler.minFilter = VK_FILTER_LINEAR;
+		sampler.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+		sampler.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+		sampler.addressModeV = sampler.addressModeU;
+		sampler.addressModeW = sampler.addressModeU;
+		sampler.mipLodBias = 0.0f;
+		sampler.maxAnisotropy = 1.0f;
+		sampler.minLod = 0.0f;
+		sampler.maxLod = 1.0f;
+		sampler.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
+		VK_CHECK_RESULT(vkCreateSampler(device, &sampler, nullptr, &depth.sampler));
+	}
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkDeviceSize offsets[1] = { 0 };
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); i++) {
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			/*
+				Generate depth map cascades
+
+				Uses multiple passes with each pass rendering the scene to the cascade's depth image layer
+				Could be optimized using a geometry shader (and layered frame buffer) on devices that support geometry shaders
+			*/
+			{
+				VkClearValue clearValues[1];
+				clearValues[0].depthStencil = { 1.0f, 0 };
+
+				VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+				renderPassBeginInfo.renderPass = depthPass.renderPass;
+				renderPassBeginInfo.renderArea.offset.x = 0;
+				renderPassBeginInfo.renderArea.offset.y = 0;
+				renderPassBeginInfo.renderArea.extent.width = SHADOWMAP_DIM;
+				renderPassBeginInfo.renderArea.extent.height = SHADOWMAP_DIM;
+				renderPassBeginInfo.clearValueCount = 1;
+				renderPassBeginInfo.pClearValues = clearValues;
+
+				VkViewport viewport = vks::initializers::viewport((float)SHADOWMAP_DIM, (float)SHADOWMAP_DIM, 0.0f, 1.0f);
+				vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+				VkRect2D scissor = vks::initializers::rect2D(SHADOWMAP_DIM, SHADOWMAP_DIM, 0, 0);
+				vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+				// One pass per cascade
+				// The layer that this pass renders to is defined by the cascade's image view (selected via the cascade's descriptor set)
+				for (uint32_t j = 0; j < SHADOW_MAP_CASCADE_COUNT; j++) {
+					renderPassBeginInfo.framebuffer = cascades[j].frameBuffer;
+					vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+					vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, depthPass.pipeline);
+					renderScene(drawCmdBuffers[i], depthPass.pipelineLayout, cascades[j].descriptorSet, j);
+					vkCmdEndRenderPass(drawCmdBuffers[i]);
+				}
+			}
+
+			/*
+				Note: Explicit synchronization is not required between the render pass, as this is done implicit via sub pass dependencies
+			*/
+
+			/*
+				Scene rendering using depth cascades for shadow mapping
+			*/
+
+			{
+				VkClearValue clearValues[2];
+				clearValues[0].color = { { 0.0f, 0.0f, 0.2f, 1.0f } };
+				clearValues[1].depthStencil = { 1.0f, 0 };
+
+				VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+				renderPassBeginInfo.renderPass = renderPass;
+				renderPassBeginInfo.framebuffer = frameBuffers[i];
+				renderPassBeginInfo.renderArea.offset.x = 0;
+				renderPassBeginInfo.renderArea.offset.y = 0;
+				renderPassBeginInfo.renderArea.extent.width = width;
+				renderPassBeginInfo.renderArea.extent.height = height;
+				renderPassBeginInfo.clearValueCount = 2;
+				renderPassBeginInfo.pClearValues = clearValues;
+
+				vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+				VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+				vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+				VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
+				vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+				// Visualize shadow map cascade
+				if (displayDepthMap) {
+					vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL);
+					vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.debugShadowMap);
+					PushConstBlock pushConstBlock = {};
+					pushConstBlock.cascadeIndex = displayDepthMapCascadeIndex;
+					vkCmdPushConstants(drawCmdBuffers[i], pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(PushConstBlock), &pushConstBlock);
+					vkCmdDraw(drawCmdBuffers[i], 3, 1, 0, 0);
+				}
+
+				// Render shadowed scene
+				vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, (filterPCF) ? pipelines.sceneShadowPCF : pipelines.sceneShadow);
+				renderScene(drawCmdBuffers[i], pipelineLayout, descriptorSet);
+
+				drawUI(drawCmdBuffers[i]);
+
+				vkCmdEndRenderPass(drawCmdBuffers[i]);
+			}
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void loadAssets()
+	{
+		uint32_t glTFLoadingFlags = vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::FlipY;
+		models.terrain.loadFromFile(getAssetPath() + "models/terrain_gridlines.gltf", vulkanDevice, queue, glTFLoadingFlags);
+		models.tree.loadFromFile(getAssetPath() + "models/oaktree.gltf", vulkanDevice, queue, glTFLoadingFlags);
+	}
+
+	void setupLayoutsAndDescriptors()
+	{
+		/*
+			Descriptor pool
+		*/
+		std::vector<VkDescriptorPoolSize> poolSizes = {
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 32),
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 32)
+		};
+		VkDescriptorPoolCreateInfo descriptorPoolInfo =
+			vks::initializers::descriptorPoolCreateInfo(static_cast<uint32_t>(poolSizes.size()), poolSizes.data(), 4 + SHADOW_MAP_CASCADE_COUNT);
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+
+		/*
+			Descriptor set layouts
+		*/
+
+		// Shared matrices and samplers
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0),
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1),
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_FRAGMENT_BIT, 2),
+		};
+		VkDescriptorSetLayoutCreateInfo descriptorLayout =
+			vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayouts.base));
+
+		/*
+			Descriptor sets
+		*/
+
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets;
+
+		VkDescriptorImageInfo depthMapDescriptor =
+			vks::initializers::descriptorImageInfo(depth.sampler, depth.view, VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL);
+
+		VkDescriptorSetAllocateInfo allocInfo =
+			vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.base, 1);
+
+		// Scene rendering / debug display
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet));
+		writeDescriptorSets = {
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.VS.descriptor),
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &depthMapDescriptor),
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2, &uniformBuffers.FS.descriptor),
+		};
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL);
+
+		// Per-cascade descriptor sets
+		// Each descriptor set represents a single layer of the array texture
+		for (uint32_t i = 0; i < SHADOW_MAP_CASCADE_COUNT; i++) {
+			VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &cascades[i].descriptorSet));
+			VkDescriptorImageInfo cascadeImageInfo = vks::initializers::descriptorImageInfo(depth.sampler, depth.view, VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL);
+			writeDescriptorSets = {
+				vks::initializers::writeDescriptorSet(cascades[i].descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &depthPass.uniformBuffer.descriptor),
+				vks::initializers::writeDescriptorSet(cascades[i].descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &cascadeImageInfo)
+			};
+			vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL);
+		}
+
+		/*
+			Pipeline layouts
+		*/
+
+		// Shared pipeline layout (scene and depth map debug display)
+		{
+			VkPushConstantRange pushConstantRange = vks::initializers::pushConstantRange(VK_SHADER_STAGE_VERTEX_BIT, sizeof(PushConstBlock), 0);
+			std::array<VkDescriptorSetLayout, 2> setLayouts = { descriptorSetLayouts.base, vkglTF::descriptorSetLayoutImage };
+			VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(setLayouts.data(), static_cast<uint32_t>(setLayouts.size()));
+			pipelineLayoutCreateInfo.pushConstantRangeCount = 1;
+			pipelineLayoutCreateInfo.pPushConstantRanges = &pushConstantRange;
+			VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout));
+		}
+
+		// Depth pass pipeline layout
+		{
+			VkPushConstantRange pushConstantRange = vks::initializers::pushConstantRange(VK_SHADER_STAGE_VERTEX_BIT, sizeof(PushConstBlock), 0);
+			std::array<VkDescriptorSetLayout, 2> setLayouts = { descriptorSetLayouts.base, vkglTF::descriptorSetLayoutImage };
+			VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(setLayouts.data(), static_cast<uint32_t>(setLayouts.size()));
+			pipelineLayoutCreateInfo.pushConstantRangeCount = 1;
+			pipelineLayoutCreateInfo.pPushConstantRanges = &pushConstantRange;
+			VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &depthPass.pipelineLayout));
+		}
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_CLOCKWISE, 0);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+		VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
+		std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
+		VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass, 0);
+		pipelineCI.pInputAssemblyState = &inputAssemblyState;
+		pipelineCI.pRasterizationState = &rasterizationState;
+		pipelineCI.pColorBlendState = &colorBlendState;
+		pipelineCI.pMultisampleState = &multisampleState;
+		pipelineCI.pViewportState = &viewportState;
+		pipelineCI.pDepthStencilState = &depthStencilState;
+		pipelineCI.pDynamicState = &dynamicState;
+		pipelineCI.stageCount = static_cast<uint32_t>(shaderStages.size());
+		pipelineCI.pStages = shaderStages.data();
+
+		// Shadow map cascade debug quad display
+		rasterizationState.cullMode = VK_CULL_MODE_BACK_BIT;
+		shaderStages[0] = loadShader(getShadersPath() + "shadowmappingcascade/debugshadowmap.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "shadowmappingcascade/debugshadowmap.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		// Empty vertex input state
+		VkPipelineVertexInputStateCreateInfo emptyInputState = vks::initializers::pipelineVertexInputStateCreateInfo();
+		pipelineCI.pVertexInputState = &emptyInputState;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.debugShadowMap));
+
+		pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position, vkglTF::VertexComponent::UV, vkglTF::VertexComponent::Color, vkglTF::VertexComponent::Normal });
+		/*
+			Shadow mapped scene rendering
+		*/
+		rasterizationState.cullMode = VK_CULL_MODE_NONE;
+		shaderStages[0] = loadShader(getShadersPath() + "shadowmappingcascade/scene.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "shadowmappingcascade/scene.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		// Use specialization constants to select between horizontal and vertical blur
+		uint32_t enablePCF = 0;
+		VkSpecializationMapEntry specializationMapEntry = vks::initializers::specializationMapEntry(0, 0, sizeof(uint32_t));
+		VkSpecializationInfo specializationInfo = vks::initializers::specializationInfo(1, &specializationMapEntry, sizeof(uint32_t), &enablePCF);
+		shaderStages[1].pSpecializationInfo = &specializationInfo;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.sceneShadow));
+		enablePCF = 1;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.sceneShadowPCF));
+
+		/*
+			Depth map generation
+		*/
+		shaderStages[0] = loadShader(getShadersPath() + "shadowmappingcascade/depthpass.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "shadowmappingcascade/depthpass.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		// No blend attachment states (no color attachments used)
+		colorBlendState.attachmentCount = 0;
+		depthStencilState.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL;
+		// Enable depth clamp (if available)
+		rasterizationState.depthClampEnable = deviceFeatures.depthClamp;
+		pipelineCI.layout = depthPass.pipelineLayout;
+		pipelineCI.renderPass = depthPass.renderPass;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &depthPass.pipeline));
+	}
+
+	void prepareUniformBuffers()
+	{
+		// Shadow map generation buffer blocks
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&depthPass.uniformBuffer,
+			sizeof(depthPass.ubo)));
+
+		// Scene uniform buffer blocks
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.VS,
+			sizeof(uboVS)));
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.FS,
+			sizeof(uboFS)));
+
+		// Map persistent
+		VK_CHECK_RESULT(depthPass.uniformBuffer.map());
+		VK_CHECK_RESULT(uniformBuffers.VS.map());
+		VK_CHECK_RESULT(uniformBuffers.FS.map());
+
+		updateLight();
+		updateUniformBuffers();
+	}
+
+	/*
+		Calculate frustum split depths and matrices for the shadow map cascades
+		Based on https://johanmedestrom.wordpress.com/2016/03/18/opengl-cascaded-shadow-maps/
+	*/
+	void updateCascades()
+	{
+		float cascadeSplits[SHADOW_MAP_CASCADE_COUNT];
+
+		float nearClip = camera.getNearClip();
+		float farClip = camera.getFarClip();
+		float clipRange = farClip - nearClip;
+
+		float minZ = nearClip;
+		float maxZ = nearClip + clipRange;
+
+		float range = maxZ - minZ;
+		float ratio = maxZ / minZ;
+
+		// Calculate split depths based on view camera frustum
+		// Based on method presented in https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch10.html
+		for (uint32_t i = 0; i < SHADOW_MAP_CASCADE_COUNT; i++) {
+			float p = (i + 1) / static_cast<float>(SHADOW_MAP_CASCADE_COUNT);
+			float log = minZ * std::pow(ratio, p);
+			float uniform = minZ + range * p;
+			float d = cascadeSplitLambda * (log - uniform) + uniform;
+			cascadeSplits[i] = (d - nearClip) / clipRange;
+		}
+
+		// Calculate orthographic projection matrix for each cascade
+		float lastSplitDist = 0.0;
+		for (uint32_t i = 0; i < SHADOW_MAP_CASCADE_COUNT; i++) {
+			float splitDist = cascadeSplits[i];
+
+			glm::vec3 frustumCorners[8] = {
+				glm::vec3(-1.0f,  1.0f, -1.0f),
+				glm::vec3( 1.0f,  1.0f, -1.0f),
+				glm::vec3( 1.0f, -1.0f, -1.0f),
+				glm::vec3(-1.0f, -1.0f, -1.0f),
+				glm::vec3(-1.0f,  1.0f,  1.0f),
+				glm::vec3( 1.0f,  1.0f,  1.0f),
+				glm::vec3( 1.0f, -1.0f,  1.0f),
+				glm::vec3(-1.0f, -1.0f,  1.0f),
+			};
+
+			// Project frustum corners into world space
+			glm::mat4 invCam = glm::inverse(camera.matrices.perspective * camera.matrices.view);
+			for (uint32_t i = 0; i < 8; i++) {
+				glm::vec4 invCorner = invCam * glm::vec4(frustumCorners[i], 1.0f);
+				frustumCorners[i] = invCorner / invCorner.w;
+			}
+
+			for (uint32_t i = 0; i < 4; i++) {
+				glm::vec3 dist = frustumCorners[i + 4] - frustumCorners[i];
+				frustumCorners[i + 4] = frustumCorners[i] + (dist * splitDist);
+				frustumCorners[i] = frustumCorners[i] + (dist * lastSplitDist);
+			}
+
+			// Get frustum center
+			glm::vec3 frustumCenter = glm::vec3(0.0f);
+			for (uint32_t i = 0; i < 8; i++) {
+				frustumCenter += frustumCorners[i];
+			}
+			frustumCenter /= 8.0f;
+
+			float radius = 0.0f;
+			for (uint32_t i = 0; i < 8; i++) {
+				float distance = glm::length(frustumCorners[i] - frustumCenter);
+				radius = glm::max(radius, distance);
+			}
+			radius = std::ceil(radius * 16.0f) / 16.0f;
+
+			glm::vec3 maxExtents = glm::vec3(radius);
+			glm::vec3 minExtents = -maxExtents;
+
+			glm::vec3 lightDir = normalize(-lightPos);
+			glm::mat4 lightViewMatrix = glm::lookAt(frustumCenter - lightDir * -minExtents.z, frustumCenter, glm::vec3(0.0f, 1.0f, 0.0f));
+			glm::mat4 lightOrthoMatrix = glm::ortho(minExtents.x, maxExtents.x, minExtents.y, maxExtents.y, 0.0f, maxExtents.z - minExtents.z);
+
+			// Store split distance and matrix in cascade
+			cascades[i].splitDepth = (camera.getNearClip() + splitDist * clipRange) * -1.0f;
+			cascades[i].viewProjMatrix = lightOrthoMatrix * lightViewMatrix;
+
+			lastSplitDist = cascadeSplits[i];
+		}
+	}
+
+	void updateLight()
+	{
+		float angle = glm::radians(timer * 360.0f);
+		float radius = 20.0f;
+		lightPos = glm::vec3(cos(angle) * radius, -radius, sin(angle) * radius);
+	}
+
+	void updateUniformBuffers()
+	{
+		/*
+			Depth rendering
+		*/
+		for (uint32_t i = 0; i < SHADOW_MAP_CASCADE_COUNT; i++) {
+			depthPass.ubo.cascadeViewProjMat[i] = cascades[i].viewProjMatrix;
+		}
+		memcpy(depthPass.uniformBuffer.mapped, &depthPass.ubo, sizeof(depthPass.ubo));
+
+		/*
+			Scene rendering
+		*/
+		uboVS.projection = camera.matrices.perspective;
+		uboVS.view = camera.matrices.view;
+		uboVS.model = glm::mat4(1.0f);
+
+		uboVS.lightDir = normalize(-lightPos);
+
+		memcpy(uniformBuffers.VS.mapped, &uboVS, sizeof(uboVS));
+
+		for (uint32_t i = 0; i < SHADOW_MAP_CASCADE_COUNT; i++) {
+			uboFS.cascadeSplits[i] = cascades[i].splitDepth;
+			uboFS.cascadeViewProjMat[i] = cascades[i].viewProjMatrix;
+		}
+		uboFS.inverseViewMat = glm::inverse(camera.matrices.view);
+		uboFS.lightDir = normalize(-lightPos);
+		uboFS.colorCascades = colorCascades;
+		memcpy(uniformBuffers.FS.mapped, &uboFS, sizeof(uboFS));
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+		VulkanExampleBase::submitFrame();
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		loadAssets();
+		updateLight();
+		updateCascades();
+		prepareDepthPass();
+		prepareUniformBuffers();
+		setupLayoutsAndDescriptors();
+		preparePipelines();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+		if (!paused || camera.updated) {
+			updateLight();
+			updateCascades();
+			updateUniformBuffers();
+		}
+	}
+
+	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
+	{
+		if (overlay->header("Settings")) {
+			if (overlay->sliderFloat("Split lambda", &cascadeSplitLambda, 0.1f, 1.0f)) {
+				updateCascades();
+				updateUniformBuffers();
+			}
+			if (overlay->checkBox("Color cascades", &colorCascades)) {
+				updateUniformBuffers();
+			}
+			if (overlay->checkBox("Display depth map", &displayDepthMap)) {
+				buildCommandBuffers();
+			}
+			if (displayDepthMap) {
+				if (overlay->sliderInt("Cascade", &displayDepthMapCascadeIndex, 0, SHADOW_MAP_CASCADE_COUNT - 1)) {
+					buildCommandBuffers();
+				}
+			}
+			if (overlay->checkBox("PCF filtering", &filterPCF)) {
+				buildCommandBuffers();
+			}
+		}
+	}
+};
+
+VULKAN_EXAMPLE_MAIN()
diff --git a/external/Vulkan/examples/shadowmappingomni/shadowmappingomni.cpp b/external/Vulkan/examples/shadowmappingomni/shadowmappingomni.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..72e82bd552540cfc91289142c82f1bff61264bd9
--- /dev/null
+++ b/external/Vulkan/examples/shadowmappingomni/shadowmappingomni.cpp
@@ -0,0 +1,813 @@
+/*
+* Vulkan Example - Omni directional shadows using a dynamic cube map
+*
+* Copyright (C) by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkanexamplebase.h"
+#include "VulkanglTFModel.h"
+
+#define ENABLE_VALIDATION false
+
+// Texture properties
+#define TEX_DIM 1024
+#define TEX_FILTER VK_FILTER_LINEAR
+
+// Offscreen frame buffer properties
+#define FB_DIM TEX_DIM
+#define FB_COLOR_FORMAT VK_FORMAT_R32_SFLOAT
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	bool displayCubeMap = false;
+
+	float zNear = 0.1f;
+	float zFar = 1024.0f;
+
+	struct {
+		vkglTF::Model scene;
+		vkglTF::Model debugcube;
+	} models;
+
+	struct {
+		vks::Buffer scene;
+		vks::Buffer offscreen;
+	} uniformBuffers;
+
+	struct {
+		glm::mat4 projection;
+		glm::mat4 model;
+	} uboVSquad;
+
+	glm::vec4 lightPos = glm::vec4(0.0f, -2.5f, 0.0f, 1.0);
+
+	struct UBO {
+		glm::mat4 projection;
+		glm::mat4 view;
+		glm::mat4 model;
+		glm::vec4 lightPos;
+	};
+
+	UBO uboVSscene, uboOffscreenVS;
+
+	struct {
+		VkPipeline scene;
+		VkPipeline offscreen;
+		VkPipeline cubemapDisplay;
+	} pipelines;
+
+	struct {
+		VkPipelineLayout scene;
+		VkPipelineLayout offscreen;
+	} pipelineLayouts;
+
+	struct {
+		VkDescriptorSet scene;
+		VkDescriptorSet offscreen;
+	} descriptorSets;
+
+	VkDescriptorSetLayout descriptorSetLayout;
+
+	vks::Texture shadowCubeMap;
+
+	// Framebuffer for offscreen rendering
+	struct FrameBufferAttachment {
+		VkImage image;
+		VkDeviceMemory mem;
+		VkImageView view;
+	};
+	struct OffscreenPass {
+		int32_t width, height;
+		VkFramebuffer frameBuffer;
+		FrameBufferAttachment color, depth;
+		VkRenderPass renderPass;
+		VkSampler sampler;
+		VkDescriptorImageInfo descriptor;
+	} offscreenPass;
+
+	VkFormat fbDepthFormat;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Point light shadows (cubemap)";
+		camera.type = Camera::CameraType::lookat;
+		camera.setPerspective(45.0f, (float)width / (float)height, zNear, zFar);
+		camera.setRotation(glm::vec3(-20.5f, -673.0f, 0.0f));
+		camera.setPosition(glm::vec3(0.0f, 0.5f, -15.0f));
+		timerSpeed *= 0.5f;
+	}
+
+	~VulkanExample()
+	{
+		// Clean up used Vulkan resources
+		// Note : Inherited destructor cleans up resources stored in base class
+
+		// Cube map
+		vkDestroyImageView(device, shadowCubeMap.view, nullptr);
+		vkDestroyImage(device, shadowCubeMap.image, nullptr);
+		vkDestroySampler(device, shadowCubeMap.sampler, nullptr);
+		vkFreeMemory(device, shadowCubeMap.deviceMemory, nullptr);
+
+		// Frame buffer
+
+		// Color attachment
+		vkDestroyImageView(device, offscreenPass.color.view, nullptr);
+		vkDestroyImage(device, offscreenPass.color.image, nullptr);
+		vkFreeMemory(device, offscreenPass.color.mem, nullptr);
+
+		// Depth attachment
+		vkDestroyImageView(device, offscreenPass.depth.view, nullptr);
+		vkDestroyImage(device, offscreenPass.depth.image, nullptr);
+		vkFreeMemory(device, offscreenPass.depth.mem, nullptr);
+
+		vkDestroyFramebuffer(device, offscreenPass.frameBuffer, nullptr);
+
+		vkDestroyRenderPass(device, offscreenPass.renderPass, nullptr);
+
+		// Pipelines
+		vkDestroyPipeline(device, pipelines.scene, nullptr);
+		vkDestroyPipeline(device, pipelines.offscreen, nullptr);
+		vkDestroyPipeline(device, pipelines.cubemapDisplay, nullptr);
+
+		vkDestroyPipelineLayout(device, pipelineLayouts.scene, nullptr);
+		vkDestroyPipelineLayout(device, pipelineLayouts.offscreen, nullptr);
+
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+
+		// Uniform buffers
+		uniformBuffers.offscreen.destroy();
+		uniformBuffers.scene.destroy();
+	}
+
+	void prepareCubeMap()
+	{
+		shadowCubeMap.width = TEX_DIM;
+		shadowCubeMap.height = TEX_DIM;
+
+		// 32 bit float format for higher precision
+		VkFormat format = VK_FORMAT_R32_SFLOAT;
+
+		// Cube map image description
+		VkImageCreateInfo imageCreateInfo = vks::initializers::imageCreateInfo();
+		imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
+		imageCreateInfo.format = format;
+		imageCreateInfo.extent = { shadowCubeMap.width, shadowCubeMap.height, 1 };
+		imageCreateInfo.mipLevels = 1;
+		imageCreateInfo.arrayLayers = 6;
+		imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
+		imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
+		imageCreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
+		imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+		imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+		imageCreateInfo.flags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;
+
+		VkMemoryAllocateInfo memAllocInfo = vks::initializers::memoryAllocateInfo();
+		VkMemoryRequirements memReqs;
+
+		VkCommandBuffer layoutCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+
+		// Create cube map image
+		VK_CHECK_RESULT(vkCreateImage(device, &imageCreateInfo, nullptr, &shadowCubeMap.image));
+
+		vkGetImageMemoryRequirements(device, shadowCubeMap.image, &memReqs);
+
+		memAllocInfo.allocationSize = memReqs.size;
+		memAllocInfo.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+		VK_CHECK_RESULT(vkAllocateMemory(device, &memAllocInfo, nullptr, &shadowCubeMap.deviceMemory));
+		VK_CHECK_RESULT(vkBindImageMemory(device, shadowCubeMap.image, shadowCubeMap.deviceMemory, 0));
+
+		// Image barrier for optimal image (target)
+		VkImageSubresourceRange subresourceRange = {};
+		subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+		subresourceRange.baseMipLevel = 0;
+		subresourceRange.levelCount = 1;
+		subresourceRange.layerCount = 6;
+		vks::tools::setImageLayout(
+			layoutCmd,
+			shadowCubeMap.image,
+			VK_IMAGE_LAYOUT_UNDEFINED,
+			VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
+			subresourceRange);
+
+		vulkanDevice->flushCommandBuffer(layoutCmd, queue, true);
+
+		// Create sampler
+		VkSamplerCreateInfo sampler = vks::initializers::samplerCreateInfo();
+		sampler.magFilter = TEX_FILTER;
+		sampler.minFilter = TEX_FILTER;
+		sampler.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+		sampler.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
+		sampler.addressModeV = sampler.addressModeU;
+		sampler.addressModeW = sampler.addressModeU;
+		sampler.mipLodBias = 0.0f;
+		sampler.maxAnisotropy = 1.0f;
+		sampler.compareOp = VK_COMPARE_OP_NEVER;
+		sampler.minLod = 0.0f;
+		sampler.maxLod = 1.0f;
+		sampler.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
+		VK_CHECK_RESULT(vkCreateSampler(device, &sampler, nullptr, &shadowCubeMap.sampler));
+
+		// Create image view
+		VkImageViewCreateInfo view = vks::initializers::imageViewCreateInfo();
+		view.image = VK_NULL_HANDLE;
+		view.viewType = VK_IMAGE_VIEW_TYPE_CUBE;
+		view.format = format;
+		view.components = { VK_COMPONENT_SWIZZLE_R };
+		view.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 };
+		view.subresourceRange.layerCount = 6;
+		view.image = shadowCubeMap.image;
+		VK_CHECK_RESULT(vkCreateImageView(device, &view, nullptr, &shadowCubeMap.view));
+	}
+
+	// Prepare a new framebuffer for offscreen rendering
+	// The contents of this framebuffer are then
+	// copied to the different cube map faces
+	void prepareOffscreenFramebuffer()
+	{
+		offscreenPass.width = FB_DIM;
+		offscreenPass.height = FB_DIM;
+
+		VkFormat fbColorFormat = FB_COLOR_FORMAT;
+
+		// Color attachment
+		VkImageCreateInfo imageCreateInfo = vks::initializers::imageCreateInfo();
+		imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
+		imageCreateInfo.format = fbColorFormat;
+		imageCreateInfo.extent.width = offscreenPass.width;
+		imageCreateInfo.extent.height = offscreenPass.height;
+		imageCreateInfo.extent.depth = 1;
+		imageCreateInfo.mipLevels = 1;
+		imageCreateInfo.arrayLayers = 1;
+		imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
+		imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
+		// Image of the framebuffer is blit source
+		imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+		imageCreateInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
+		imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+
+		VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo();
+
+		VkImageViewCreateInfo colorImageView = vks::initializers::imageViewCreateInfo();
+		colorImageView.viewType = VK_IMAGE_VIEW_TYPE_2D;
+		colorImageView.format = fbColorFormat;
+		colorImageView.flags = 0;
+		colorImageView.subresourceRange = {};
+		colorImageView.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+		colorImageView.subresourceRange.baseMipLevel = 0;
+		colorImageView.subresourceRange.levelCount = 1;
+		colorImageView.subresourceRange.baseArrayLayer = 0;
+		colorImageView.subresourceRange.layerCount = 1;
+
+		VkMemoryRequirements memReqs;
+
+		VK_CHECK_RESULT(vkCreateImage(device, &imageCreateInfo, nullptr, &offscreenPass.color.image));
+		vkGetImageMemoryRequirements(device, offscreenPass.color.image, &memReqs);
+		memAlloc.allocationSize = memReqs.size;
+		memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+		VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &offscreenPass.color.mem));
+		VK_CHECK_RESULT(vkBindImageMemory(device, offscreenPass.color.image, offscreenPass.color.mem, 0));
+
+		VkCommandBuffer layoutCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+
+		vks::tools::setImageLayout(
+			layoutCmd,
+			offscreenPass.color.image,
+			VK_IMAGE_ASPECT_COLOR_BIT,
+			VK_IMAGE_LAYOUT_UNDEFINED,
+			VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
+
+		colorImageView.image = offscreenPass.color.image;
+		VK_CHECK_RESULT(vkCreateImageView(device, &colorImageView, nullptr, &offscreenPass.color.view));
+
+		// Depth stencil attachment
+		imageCreateInfo.format = fbDepthFormat;
+		imageCreateInfo.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
+
+		VkImageViewCreateInfo depthStencilView = vks::initializers::imageViewCreateInfo();
+		depthStencilView.viewType = VK_IMAGE_VIEW_TYPE_2D;
+		depthStencilView.format = fbDepthFormat;
+		depthStencilView.flags = 0;
+		depthStencilView.subresourceRange = {};
+		depthStencilView.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
+		depthStencilView.subresourceRange.baseMipLevel = 0;
+		depthStencilView.subresourceRange.levelCount = 1;
+		depthStencilView.subresourceRange.baseArrayLayer = 0;
+		depthStencilView.subresourceRange.layerCount = 1;
+
+		VK_CHECK_RESULT(vkCreateImage(device, &imageCreateInfo, nullptr, &offscreenPass.depth.image));
+		vkGetImageMemoryRequirements(device, offscreenPass.depth.image, &memReqs);
+		memAlloc.allocationSize = memReqs.size;
+		memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+		VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &offscreenPass.depth.mem));
+		VK_CHECK_RESULT(vkBindImageMemory(device, offscreenPass.depth.image, offscreenPass.depth.mem, 0));
+
+		vks::tools::setImageLayout(
+			layoutCmd,
+			offscreenPass.depth.image,
+			VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT,
+			VK_IMAGE_LAYOUT_UNDEFINED,
+			VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
+
+		vulkanDevice->flushCommandBuffer(layoutCmd, queue, true);
+
+		depthStencilView.image = offscreenPass.depth.image;
+		VK_CHECK_RESULT(vkCreateImageView(device, &depthStencilView, nullptr, &offscreenPass.depth.view));
+
+		VkImageView attachments[2];
+		attachments[0] = offscreenPass.color.view;
+		attachments[1] = offscreenPass.depth.view;
+
+		VkFramebufferCreateInfo fbufCreateInfo = vks::initializers::framebufferCreateInfo();
+		fbufCreateInfo.renderPass = offscreenPass.renderPass;
+		fbufCreateInfo.attachmentCount = 2;
+		fbufCreateInfo.pAttachments = attachments;
+		fbufCreateInfo.width = offscreenPass.width;
+		fbufCreateInfo.height = offscreenPass.height;
+		fbufCreateInfo.layers = 1;
+
+		VK_CHECK_RESULT(vkCreateFramebuffer(device, &fbufCreateInfo, nullptr, &offscreenPass.frameBuffer));
+	}
+
+	// Updates a single cube map face
+	// Renders the scene with face's view and does a copy from framebuffer to cube face
+	// Uses push constants for quick update of view matrix for the current cube map face
+	void updateCubeFace(uint32_t faceIndex, VkCommandBuffer commandBuffer)
+	{
+		VkClearValue clearValues[2];
+		clearValues[0].color = { { 0.0f, 0.0f, 0.0f, 1.0f } };
+		clearValues[1].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		// Reuse render pass from example pass
+		renderPassBeginInfo.renderPass = offscreenPass.renderPass;
+		renderPassBeginInfo.framebuffer = offscreenPass.frameBuffer;
+		renderPassBeginInfo.renderArea.extent.width = offscreenPass.width;
+		renderPassBeginInfo.renderArea.extent.height = offscreenPass.height;
+		renderPassBeginInfo.clearValueCount = 2;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		// Update view matrix via push constant
+
+		glm::mat4 viewMatrix = glm::mat4(1.0f);
+		switch (faceIndex)
+		{
+		case 0: // POSITIVE_X
+			viewMatrix = glm::rotate(viewMatrix, glm::radians(90.0f), glm::vec3(0.0f, 1.0f, 0.0f));
+			viewMatrix = glm::rotate(viewMatrix, glm::radians(180.0f), glm::vec3(1.0f, 0.0f, 0.0f));
+			break;
+		case 1:	// NEGATIVE_X
+			viewMatrix = glm::rotate(viewMatrix, glm::radians(-90.0f), glm::vec3(0.0f, 1.0f, 0.0f));
+			viewMatrix = glm::rotate(viewMatrix, glm::radians(180.0f), glm::vec3(1.0f, 0.0f, 0.0f));
+			break;
+		case 2:	// POSITIVE_Y
+			viewMatrix = glm::rotate(viewMatrix, glm::radians(-90.0f), glm::vec3(1.0f, 0.0f, 0.0f));
+			break;
+		case 3:	// NEGATIVE_Y
+			viewMatrix = glm::rotate(viewMatrix, glm::radians(90.0f), glm::vec3(1.0f, 0.0f, 0.0f));
+			break;
+		case 4:	// POSITIVE_Z
+			viewMatrix = glm::rotate(viewMatrix, glm::radians(180.0f), glm::vec3(1.0f, 0.0f, 0.0f));
+			break;
+		case 5:	// NEGATIVE_Z
+			viewMatrix = glm::rotate(viewMatrix, glm::radians(180.0f), glm::vec3(0.0f, 0.0f, 1.0f));
+			break;
+		}
+
+		// Render scene from cube face's point of view
+		vkCmdBeginRenderPass(commandBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+		// Update shader push constant block
+		// Contains current face view matrix
+		vkCmdPushConstants(
+			commandBuffer,
+			pipelineLayouts.offscreen,
+			VK_SHADER_STAGE_VERTEX_BIT,
+			0,
+			sizeof(glm::mat4),
+			&viewMatrix);
+
+		vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.offscreen);
+		vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayouts.offscreen, 0, 1, &descriptorSets.offscreen, 0, NULL);
+		models.scene.draw(commandBuffer);
+
+		vkCmdEndRenderPass(commandBuffer);
+		// Make sure color writes to the framebuffer are finished before using it as transfer source
+		vks::tools::setImageLayout(
+			commandBuffer,
+			offscreenPass.color.image,
+			VK_IMAGE_ASPECT_COLOR_BIT,
+			VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+			VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
+
+		VkImageSubresourceRange cubeFaceSubresourceRange = {};
+		cubeFaceSubresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+		cubeFaceSubresourceRange.baseMipLevel = 0;
+		cubeFaceSubresourceRange.levelCount = 1;
+		cubeFaceSubresourceRange.baseArrayLayer = faceIndex;
+		cubeFaceSubresourceRange.layerCount = 1;
+
+		// Change image layout of one cubemap face to transfer destination
+		vks::tools::setImageLayout(
+			commandBuffer,
+			shadowCubeMap.image,
+			VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
+			VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+			cubeFaceSubresourceRange);
+
+		// Copy region for transfer from framebuffer to cube face
+		VkImageCopy copyRegion = {};
+
+		copyRegion.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+		copyRegion.srcSubresource.baseArrayLayer = 0;
+		copyRegion.srcSubresource.mipLevel = 0;
+		copyRegion.srcSubresource.layerCount = 1;
+		copyRegion.srcOffset = { 0, 0, 0 };
+
+		copyRegion.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+		copyRegion.dstSubresource.baseArrayLayer = faceIndex;
+		copyRegion.dstSubresource.mipLevel = 0;
+		copyRegion.dstSubresource.layerCount = 1;
+		copyRegion.dstOffset = { 0, 0, 0 };
+
+		copyRegion.extent.width = shadowCubeMap.width;
+		copyRegion.extent.height = shadowCubeMap.height;
+		copyRegion.extent.depth = 1;
+
+		// Put image copy into command buffer
+		vkCmdCopyImage(
+			commandBuffer,
+			offscreenPass.color.image,
+			VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+			shadowCubeMap.image,
+			VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+			1,
+			&copyRegion);
+
+		// Transform framebuffer color attachment back
+		vks::tools::setImageLayout(
+			commandBuffer,
+			offscreenPass.color.image,
+			VK_IMAGE_ASPECT_COLOR_BIT,
+			VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+			VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
+
+		// Change image layout of copied face to shader read
+		vks::tools::setImageLayout(
+			commandBuffer,
+			shadowCubeMap.image,
+			VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+			VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
+			cubeFaceSubresourceRange);
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			/*
+				Generate shadow cube maps using one render pass per face
+			*/
+			{
+				VkViewport viewport = vks::initializers::viewport((float)offscreenPass.width, (float)offscreenPass.height, 0.0f, 1.0f);
+				vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+				VkRect2D scissor = vks::initializers::rect2D(offscreenPass.width, offscreenPass.height, 0, 0);
+				vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+				for (uint32_t face = 0; face < 6; face++) {
+					updateCubeFace(face, drawCmdBuffers[i]);
+				}
+			}
+
+			/*
+				Note: Explicit synchronization is not required between the render pass, as this is done implicit via sub pass dependencies
+			*/
+
+			/*
+				Scene rendering with applied shadow map
+			*/
+			{
+				VkClearValue clearValues[2];
+				clearValues[0].color = defaultClearColor;
+				clearValues[1].depthStencil = { 1.0f, 0 };
+
+				VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+				renderPassBeginInfo.renderPass = renderPass;
+				renderPassBeginInfo.framebuffer = frameBuffers[i];
+				renderPassBeginInfo.renderArea.extent.width = width;
+				renderPassBeginInfo.renderArea.extent.height = height;
+				renderPassBeginInfo.clearValueCount = 2;
+				renderPassBeginInfo.pClearValues = clearValues;
+
+				vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+				VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+				vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+				VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
+				vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+				VkDeviceSize offsets[1] = { 0 };
+
+				vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayouts.scene, 0, 1, &descriptorSets.scene, 0, NULL);
+
+				if (displayCubeMap)
+				{
+					// Display all six sides of the shadow cube map
+					// Note: Visualization of the different faces is done in the fragment shader, see cubemapdisplay.frag
+					vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.cubemapDisplay);
+					models.debugcube.draw(drawCmdBuffers[i]);
+				}
+				else
+				{
+					vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.scene);
+					models.scene.draw(drawCmdBuffers[i]);
+				}
+
+				drawUI(drawCmdBuffers[i]);
+
+				vkCmdEndRenderPass(drawCmdBuffers[i]);
+			}
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void loadAssets()
+	{
+		const uint32_t glTFLoadingFlags = vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::PreMultiplyVertexColors | vkglTF::FileLoadingFlags::FlipY;
+		models.debugcube.loadFromFile(getAssetPath() + "models/cube.gltf", vulkanDevice, queue, glTFLoadingFlags);
+		models.scene.loadFromFile(getAssetPath() + "models/shadowscene_fire.gltf", vulkanDevice, queue, glTFLoadingFlags);
+	}
+
+	void setupDescriptorPool()
+	{
+		// Example uses three ubos and two image samplers
+		std::vector<VkDescriptorPoolSize> poolSizes = {
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 3),
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2)
+		};
+		VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes.size(), poolSizes.data(), 3);
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+	}
+
+	void setupDescriptorSetLayout()
+	{
+		// Shared pipeline layout
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			// Binding 0 : Vertex shader uniform buffer
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0),
+			// Binding 1 : Fragment shader image sampler (cube map)
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1)
+		};
+
+		VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings.data(), setLayoutBindings.size());
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout));
+
+		// 3D scene pipeline layout
+		VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1);
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayouts.scene));
+
+		// Offscreen pipeline layout
+		// Push constants for cube map face view matrices
+		VkPushConstantRange pushConstantRange = vks::initializers::pushConstantRange(VK_SHADER_STAGE_VERTEX_BIT, sizeof(glm::mat4), 0);
+		// Push constant ranges are part of the pipeline layout
+		pPipelineLayoutCreateInfo.pushConstantRangeCount = 1;
+		pPipelineLayoutCreateInfo.pPushConstantRanges = &pushConstantRange;
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayouts.offscreen));
+	}
+
+	void setupDescriptorSets()
+	{
+		VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1);
+
+		// 3D scene
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.scene));
+		// Image descriptor for the cube map
+		VkDescriptorImageInfo texDescriptor =
+			vks::initializers::descriptorImageInfo(
+				shadowCubeMap.sampler,
+				shadowCubeMap.view,
+				VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+
+		std::vector<VkWriteDescriptorSet> sceneDescriptorSets = {
+			// Binding 0 : Vertex shader uniform buffer
+			vks::initializers::writeDescriptorSet(descriptorSets.scene, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.scene.descriptor),
+			// Binding 1 : Fragment shader shadow sampler
+			vks::initializers::writeDescriptorSet(descriptorSets.scene, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &texDescriptor)
+		};
+		vkUpdateDescriptorSets(device, sceneDescriptorSets.size(), sceneDescriptorSets.data(), 0, NULL);
+
+		// Offscreen
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.offscreen));
+		std::vector<VkWriteDescriptorSet> offScreenWriteDescriptorSets = {
+			// Binding 0 : Vertex shader uniform buffer
+			vks::initializers::writeDescriptorSet(descriptorSets.offscreen, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.offscreen.descriptor),
+		};
+		vkUpdateDescriptorSets(device, offScreenWriteDescriptorSets.size(), offScreenWriteDescriptorSets.data(), 0, NULL);
+	}
+
+	// Set up a separate render pass for the offscreen frame buffer
+	// This is necessary as the offscreen frame buffer attachments
+	// use formats different to the ones from the visible frame buffer
+	// and at least the depth one may not be compatible
+	void prepareOffscreenRenderpass()
+	{
+		VkAttachmentDescription osAttachments[2] = {};
+
+		// Find a suitable depth format
+		VkBool32 validDepthFormat = vks::tools::getSupportedDepthFormat(physicalDevice, &fbDepthFormat);
+		assert(validDepthFormat);
+
+		osAttachments[0].format = FB_COLOR_FORMAT;
+		osAttachments[0].samples = VK_SAMPLE_COUNT_1_BIT;
+		osAttachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+		osAttachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+		osAttachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+		osAttachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+		osAttachments[0].initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+		osAttachments[0].finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+
+		// Depth attachment
+		osAttachments[1].format = fbDepthFormat;
+		osAttachments[1].samples = VK_SAMPLE_COUNT_1_BIT;
+		osAttachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+		osAttachments[1].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+		osAttachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+		osAttachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+		osAttachments[1].initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+		osAttachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+
+		VkAttachmentReference colorReference = {};
+		colorReference.attachment = 0;
+		colorReference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+
+		VkAttachmentReference depthReference = {};
+		depthReference.attachment = 1;
+		depthReference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+
+		VkSubpassDescription subpass = {};
+		subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+		subpass.colorAttachmentCount = 1;
+		subpass.pColorAttachments = &colorReference;
+		subpass.pDepthStencilAttachment = &depthReference;
+
+		VkRenderPassCreateInfo renderPassCreateInfo = vks::initializers::renderPassCreateInfo();
+		renderPassCreateInfo.attachmentCount = 2;
+		renderPassCreateInfo.pAttachments = osAttachments;
+		renderPassCreateInfo.subpassCount = 1;
+		renderPassCreateInfo.pSubpasses = &subpass;
+
+		VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassCreateInfo, nullptr, &offscreenPass.renderPass));
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+		VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
+		std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
+		VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables.data(), dynamicStateEnables.size(), 0);
+
+		// 3D scene pipeline
+		// Load shaders
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+
+		shaderStages[0] = loadShader(getShadersPath() + "shadowmappingomni/scene.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "shadowmappingomni/scene.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayouts.scene, renderPass, 0);
+		pipelineCI.pInputAssemblyState = &inputAssemblyState;
+		pipelineCI.pRasterizationState = &rasterizationState;
+		pipelineCI.pColorBlendState = &colorBlendState;
+		pipelineCI.pMultisampleState = &multisampleState;
+		pipelineCI.pViewportState = &viewportState;
+		pipelineCI.pDepthStencilState = &depthStencilState;
+		pipelineCI.pDynamicState = &dynamicState;
+		pipelineCI.stageCount = shaderStages.size();
+		pipelineCI.pStages = shaderStages.data();
+		pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Color, vkglTF::VertexComponent::Normal});
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.scene));
+
+		// Offscreen pipeline
+		shaderStages[0] = loadShader(getShadersPath() + "shadowmappingomni/offscreen.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "shadowmappingomni/offscreen.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		pipelineCI.layout = pipelineLayouts.offscreen;
+		pipelineCI.renderPass = offscreenPass.renderPass;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.offscreen));
+
+		// Cube map display pipeline
+		shaderStages[0] = loadShader(getShadersPath() + "shadowmappingomni/cubemapdisplay.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "shadowmappingomni/cubemapdisplay.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		VkPipelineVertexInputStateCreateInfo emptyInputState = vks::initializers::pipelineVertexInputStateCreateInfo();
+		pipelineCI.pVertexInputState = &emptyInputState;
+		pipelineCI.layout = pipelineLayouts.scene;
+		pipelineCI.renderPass = renderPass;
+		rasterizationState.cullMode = VK_CULL_MODE_NONE;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.cubemapDisplay));
+	}
+
+	// Prepare and initialize uniform buffer containing shader uniforms
+	void prepareUniformBuffers()
+	{
+		// Offscreen vertex shader uniform buffer
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.offscreen,
+			sizeof(uboOffscreenVS)));
+
+		// Scene vertex shader uniform buffer
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.scene,
+			sizeof(uboVSscene)));
+
+		// Map persistent
+		VK_CHECK_RESULT(uniformBuffers.offscreen.map());
+		VK_CHECK_RESULT(uniformBuffers.scene.map());
+
+		updateUniformBufferOffscreen();
+		updateUniformBuffers();
+	}
+
+	void updateUniformBuffers()
+	{
+		uboVSscene.projection = camera.matrices.perspective;
+		uboVSscene.view = camera.matrices.view;
+		uboVSscene.model = glm::mat4(1.0f);
+		uboVSscene.lightPos = lightPos;
+		memcpy(uniformBuffers.scene.mapped, &uboVSscene, sizeof(uboVSscene));
+	}
+
+	void updateUniformBufferOffscreen()
+	{
+		lightPos.x = sin(glm::radians(timer * 360.0f)) * 0.15f;
+		lightPos.z = cos(glm::radians(timer * 360.0f)) * 0.15f;
+		uboOffscreenVS.projection = glm::perspective((float)(M_PI / 2.0), 1.0f, zNear, zFar);
+		uboOffscreenVS.view = glm::mat4(1.0f);
+		uboOffscreenVS.model = glm::translate(glm::mat4(1.0f), glm::vec3(-lightPos.x, -lightPos.y, -lightPos.z));
+		uboOffscreenVS.lightPos = lightPos;
+		memcpy(uniformBuffers.offscreen.mapped, &uboOffscreenVS, sizeof(uboOffscreenVS));
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+		VulkanExampleBase::submitFrame();
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		loadAssets();
+		prepareUniformBuffers();
+		prepareCubeMap();
+		setupDescriptorSetLayout();
+		prepareOffscreenRenderpass();
+		preparePipelines();
+		setupDescriptorPool();
+		setupDescriptorSets();
+		prepareOffscreenFramebuffer();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+		if (!paused || camera.updated)
+		{
+			updateUniformBufferOffscreen();
+			updateUniformBuffers();
+		}
+	}
+
+	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
+	{
+		if (overlay->header("Settings")) {
+			if (overlay->checkBox("Display shadow cube render target", &displayCubeMap)) {
+				buildCommandBuffers();
+			}
+		}
+	}
+};
+
+VULKAN_EXAMPLE_MAIN()
diff --git a/external/Vulkan/examples/specializationconstants/specializationconstants.cpp b/external/Vulkan/examples/specializationconstants/specializationconstants.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..0384086da61c5d9c78a0ce6eb19aafe90586f13f
--- /dev/null
+++ b/external/Vulkan/examples/specializationconstants/specializationconstants.cpp
@@ -0,0 +1,329 @@
+/*
+* Vulkan Example - Shader specialization constants
+*
+* For details see https://www.khronos.org/registry/vulkan/specs/misc/GL_KHR_vulkan_glsl.txt
+*
+* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkanexamplebase.h"
+#include "VulkanglTFModel.h"
+
+#define ENABLE_VALIDATION false
+
+class VulkanExample: public VulkanExampleBase
+{
+public:
+	vkglTF::Model scene;
+	vks::Texture2D colormap;
+	vks::Buffer uniformBuffer;
+
+	// Same uniform buffer layout as shader
+	struct UBOVS {
+		glm::mat4 projection;
+		glm::mat4 modelView;
+		glm::vec4 lightPos = glm::vec4(0.0f, -2.0f, 1.0f, 0.0f);
+	} uboVS;
+
+	VkPipelineLayout pipelineLayout;
+	VkDescriptorSet descriptorSet;
+	VkDescriptorSetLayout descriptorSetLayout;
+
+	struct {
+		VkPipeline phong;
+		VkPipeline toon;
+		VkPipeline textured;
+	} pipelines;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Specialization constants";
+		camera.type = Camera::CameraType::lookat;
+		camera.setPerspective(60.0f, ((float)width / 3.0f) / (float)height, 0.1f, 512.0f);
+		camera.setRotation(glm::vec3(-40.0f, -90.0f, 0.0f));
+		camera.setTranslation(glm::vec3(0.0f, 0.0f, -2.0f));
+	}
+
+	~VulkanExample()
+	{
+		vkDestroyPipeline(device, pipelines.phong, nullptr);
+		vkDestroyPipeline(device, pipelines.textured, nullptr);
+		vkDestroyPipeline(device, pipelines.toon, nullptr);
+
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+
+		colormap.destroy();
+		uniformBuffer.destroy();
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		clearValues[0].color = defaultClearColor;
+		clearValues[1].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.offset.x = 0;
+		renderPassBeginInfo.renderArea.offset.y = 0;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		renderPassBeginInfo.clearValueCount = 2;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			// Set target frame buffer
+			renderPassBeginInfo.framebuffer = frameBuffers[i];
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			VkRect2D scissor = vks::initializers::rect2D(width, height,	0, 0);
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL);
+
+			VkDeviceSize offsets[1] = { 0 };
+
+			// Left
+			VkViewport viewport = vks::initializers::viewport((float) width / 3.0f, (float) height, 0.0f, 1.0f);
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.phong);
+			scene.draw(drawCmdBuffers[i]);
+			
+			// Center
+			viewport.x = (float)width / 3.0f;
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.toon);
+			scene.draw(drawCmdBuffers[i]);
+
+			// Right
+			viewport.x = (float)width / 3.0f + (float)width / 3.0f;
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.textured);
+			scene.draw(drawCmdBuffers[i]);
+
+			drawUI(drawCmdBuffers[i]);
+
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void loadAssets()
+	{
+		scene.loadFromFile(getAssetPath() + "models/color_teapot_spheres.gltf", vulkanDevice, queue , vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::PreMultiplyVertexColors | vkglTF::FileLoadingFlags::FlipY);
+		colormap.loadFromFile(getAssetPath() + "textures/metalplate_nomips_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue);
+	}
+
+	void setupDescriptorPool()
+	{
+		std::vector<VkDescriptorPoolSize> poolSizes =
+		{
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1),
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1)
+		};
+
+		VkDescriptorPoolCreateInfo descriptorPoolInfo =
+			vks::initializers::descriptorPoolCreateInfo(
+				static_cast<uint32_t>(poolSizes.size()),
+				poolSizes.data(),
+				1);
+
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+	}
+
+	void setupDescriptorSetLayout()
+	{
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings ={
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0),
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1),
+		};
+
+		VkDescriptorSetLayoutCreateInfo descriptorLayout =
+			vks::initializers::descriptorSetLayoutCreateInfo(
+				setLayoutBindings.data(),
+				static_cast<uint32_t>(setLayoutBindings.size()));
+
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout));
+
+		VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo =
+			vks::initializers::pipelineLayoutCreateInfo(
+				&descriptorSetLayout,
+				1);
+
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayout));
+	}
+
+	void setupDescriptorSet()
+	{
+		VkDescriptorSetAllocateInfo allocInfo =
+			vks::initializers::descriptorSetAllocateInfo(
+				descriptorPool,
+				&descriptorSetLayout,
+				1);
+
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet));
+
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets = {
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffer.descriptor),
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &colormap.descriptor),
+		};
+
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL);
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE, VK_FRONT_FACE_CLOCKWISE, 0);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+		VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
+		std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR, VK_DYNAMIC_STATE_LINE_WIDTH };
+		VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass, 0);
+		pipelineCI.pInputAssemblyState = &inputAssemblyState;
+		pipelineCI.pRasterizationState = &rasterizationState;
+		pipelineCI.pColorBlendState = &colorBlendState;
+		pipelineCI.pMultisampleState = &multisampleState;
+		pipelineCI.pViewportState = &viewportState;
+		pipelineCI.pDepthStencilState = &depthStencilState;
+		pipelineCI.pDynamicState = &dynamicState;
+		pipelineCI.stageCount = static_cast<uint32_t>(shaderStages.size());
+		pipelineCI.pStages = shaderStages.data();
+		pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::UV, vkglTF::VertexComponent::Color });
+
+		// Prepare specialization data
+
+		// Host data to take specialization constants from
+		struct SpecializationData {
+			// Sets the lighting model used in the fragment "uber" shader
+			uint32_t lightingModel;
+			// Parameter for the toon shading part of the fragment shader
+			float toonDesaturationFactor = 0.5f;
+		} specializationData;
+
+		// Each shader constant of a shader stage corresponds to one map entry
+		std::array<VkSpecializationMapEntry, 2> specializationMapEntries;
+		// Shader bindings based on specialization constants are marked by the new "constant_id" layout qualifier:
+		//	layout (constant_id = 0) const int LIGHTING_MODEL = 0;
+		//	layout (constant_id = 1) const float PARAM_TOON_DESATURATION = 0.0f;
+
+		// Map entry for the lighting model to be used by the fragment shader
+		specializationMapEntries[0].constantID = 0;
+		specializationMapEntries[0].size = sizeof(specializationData.lightingModel);
+		specializationMapEntries[0].offset = 0;
+
+		// Map entry for the toon shader parameter
+		specializationMapEntries[1].constantID = 1;
+		specializationMapEntries[1].size = sizeof(specializationData.toonDesaturationFactor);
+		specializationMapEntries[1].offset = offsetof(SpecializationData, toonDesaturationFactor);
+
+		// Prepare specialization info block for the shader stage
+		VkSpecializationInfo specializationInfo{};
+		specializationInfo.dataSize = sizeof(specializationData);
+		specializationInfo.mapEntryCount = static_cast<uint32_t>(specializationMapEntries.size());
+		specializationInfo.pMapEntries = specializationMapEntries.data();
+		specializationInfo.pData = &specializationData;
+
+		// Create pipelines
+		// All pipelines will use the same "uber" shader and specialization constants to change branching and parameters of that shader
+		shaderStages[0] = loadShader(getShadersPath() + "specializationconstants/uber.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "specializationconstants/uber.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		// Specialization info is assigned is part of the shader stage (modul) and must be set after creating the module and before creating the pipeline
+		shaderStages[1].pSpecializationInfo = &specializationInfo;
+
+		// Solid phong shading
+		specializationData.lightingModel = 0;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.phong));
+
+		// Phong and textured
+		specializationData.lightingModel = 1;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.toon));
+
+		// Textured discard
+		specializationData.lightingModel = 2;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.textured));
+	}
+
+	// Prepare and initialize uniform buffer containing shader uniforms
+	void prepareUniformBuffers()
+	{
+		// Create the vertex shader uniform buffer block
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffer,
+			sizeof(uboVS)));
+
+		// Map persistent
+		VK_CHECK_RESULT(uniformBuffer.map());
+
+		updateUniformBuffers();
+	}
+
+	void updateUniformBuffers()
+	{
+		camera.setPerspective(60.0f, ((float)width / 3.0f) / (float)height, 0.1f, 512.0f);
+
+		uboVS.projection = camera.matrices.perspective;
+		uboVS.modelView = camera.matrices.view;
+
+		memcpy(uniformBuffer.mapped, &uboVS, sizeof(uboVS));
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+
+		VulkanExampleBase::submitFrame();
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		loadAssets();
+		prepareUniformBuffers();
+		setupDescriptorSetLayout();
+		preparePipelines();
+		setupDescriptorPool();
+		setupDescriptorSet();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared) {
+			return;
+		}
+		draw();
+		if (camera.updated) {
+			updateUniformBuffers();
+		}
+	}
+
+	virtual void windowResized()
+	{
+		updateUniformBuffers();
+	}
+};
+
+VULKAN_EXAMPLE_MAIN()
\ No newline at end of file
diff --git a/external/Vulkan/examples/sphericalenvmapping/sphericalenvmapping.cpp b/external/Vulkan/examples/sphericalenvmapping/sphericalenvmapping.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..883184aad1e84898663faa12eaf5a5dcd98d1ff1
--- /dev/null
+++ b/external/Vulkan/examples/sphericalenvmapping/sphericalenvmapping.cpp
@@ -0,0 +1,291 @@
+/*
+* Vulkan Example - Spherical Environment Mapping, using different mat caps
+*
+* Use +/-/space toggle through different material captures
+*
+* Based on https://www.clicktorelease.com/blog/creating-spherical-environment-mapping-shader
+*
+* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkanexamplebase.h"
+#include "VulkanglTFModel.h"
+
+#define VERTEX_BUFFER_BIND_ID 0
+#define ENABLE_VALIDATION false
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	vkglTF::Model model;
+	vks::Texture2DArray matCapTextureArray;
+
+	struct UBOVS {
+		glm::mat4 projection;
+		glm::mat4 model;
+		glm::mat4 normal;
+		glm::mat4 view;
+		int32_t texIndex = 0;
+	} uboVS;
+	vks::Buffer uniformBuffer;
+
+	VkPipeline pipeline;
+	VkPipelineLayout pipelineLayout;
+	VkDescriptorSet descriptorSet;
+	VkDescriptorSetLayout descriptorSetLayout;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Spherical Environment Mapping";
+		camera.type = Camera::CameraType::lookat;
+		camera.setPosition(glm::vec3(0.0f, 0.0f, -3.5f));
+		camera.setRotation(glm::vec3(-25.0f, 23.75f, 0.0f));
+		camera.setRotationSpeed(0.75f);
+		camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 256.0f);
+	}
+
+	~VulkanExample()
+	{
+		// Clean up used Vulkan resources
+		// Note : Inherited destructor cleans up resources stored in base class
+		vkDestroyPipeline(device, pipeline, nullptr);
+
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+
+		uniformBuffer.destroy();
+		matCapTextureArray.destroy();
+	}
+
+	void loadAssets()
+	{
+		model.loadFromFile(getAssetPath() + "models/chinesedragon.gltf", vulkanDevice, queue, vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::PreMultiplyVertexColors | vkglTF::FileLoadingFlags::FlipY);
+		// Multiple mat caps are stored in a single texture array so they can easily be switched inside the shader  just by updating the index in a uniform buffer
+		matCapTextureArray.loadFromFile(getAssetPath() + "textures/matcap_array_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue);
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		clearValues[0].color = defaultClearColor;
+		clearValues[1].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.offset.x = 0;
+		renderPassBeginInfo.renderArea.offset.y = 0;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		renderPassBeginInfo.clearValueCount = 2;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			renderPassBeginInfo.framebuffer = frameBuffers[i];
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+			VkRect2D scissor = vks::initializers::rect2D(width, height,	0, 0);
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL);
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
+
+			model.draw(drawCmdBuffers[i]);
+
+			drawUI(drawCmdBuffers[i]);
+
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void setupDescriptorPool()
+	{
+		// Example uses one ubo and one image sampler
+		std::vector<VkDescriptorPoolSize> poolSizes =
+		{
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1),
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1)
+		};
+
+		VkDescriptorPoolCreateInfo descriptorPoolInfo =
+			vks::initializers::descriptorPoolCreateInfo(
+				poolSizes.size(),
+				poolSizes.data(),
+				2);
+
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+	}
+
+	void setupDescriptorSetLayout()
+	{
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings =
+		{
+			// Binding 0 : Vertex shader uniform buffer
+			vks::initializers::descriptorSetLayoutBinding(
+			VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+				VK_SHADER_STAGE_VERTEX_BIT,
+				0),
+			// Binding 1 : Fragment shader color map image sampler
+			vks::initializers::descriptorSetLayoutBinding(
+				VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+				VK_SHADER_STAGE_FRAGMENT_BIT,
+				1)
+		};
+
+		VkDescriptorSetLayoutCreateInfo descriptorLayout =
+			vks::initializers::descriptorSetLayoutCreateInfo(
+				setLayoutBindings.data(),
+				setLayoutBindings.size());
+
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout));
+
+		VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo =
+			vks::initializers::pipelineLayoutCreateInfo(
+				&descriptorSetLayout,
+				1);
+
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayout));
+	}
+
+	void setupDescriptorSet()
+	{
+		VkDescriptorSetAllocateInfo allocInfo =
+			vks::initializers::descriptorSetAllocateInfo(
+				descriptorPool,
+				&descriptorSetLayout,
+				1);
+
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet));
+
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets =
+		{
+			// Binding 0 : Vertex shader uniform buffer
+			vks::initializers::writeDescriptorSet(
+			descriptorSet,
+				VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+				0,
+				&uniformBuffer.descriptor),
+			// Binding 1 : Fragment shader image sampler
+			vks::initializers::writeDescriptorSet(
+				descriptorSet,
+				VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+				1,
+				&matCapTextureArray.descriptor)
+		};
+
+		vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, NULL);
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+		VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
+		std::vector<VkDynamicState> dynamicStateEnables = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
+		VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables.data(), dynamicStateEnables.size(),0);
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass, 0);
+		pipelineCI.pInputAssemblyState = &inputAssemblyState;
+		pipelineCI.pRasterizationState = &rasterizationState;
+		pipelineCI.pColorBlendState = &colorBlendState;
+		pipelineCI.pMultisampleState = &multisampleState;
+		pipelineCI.pViewportState = &viewportState;
+		pipelineCI.pDepthStencilState = &depthStencilState;
+		pipelineCI.pDynamicState = &dynamicState;
+		pipelineCI.stageCount = shaderStages.size();
+		pipelineCI.pStages = shaderStages.data();
+		pipelineCI.pVertexInputState  = vkglTF::Vertex::getPipelineVertexInputState({vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::Color});
+
+		// Spherical environment rendering pipeline
+		shaderStages[0] = loadShader(getShadersPath() + "sphericalenvmapping/sem.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "sphericalenvmapping/sem.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipeline));
+	}
+
+	void prepareUniformBuffers()
+	{
+		// Vertex shader uniform buffer block
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffer,
+			sizeof(uboVS)));
+
+		// Map persistent
+		VK_CHECK_RESULT(uniformBuffer.map());
+
+		updateUniformBuffers();
+	}
+
+	void updateUniformBuffers()
+	{
+		uboVS.projection = camera.matrices.perspective;
+		uboVS.view = camera.matrices.view;
+		uboVS.model = glm::mat4(1.0f);
+		uboVS.normal = glm::inverseTranspose(uboVS.view * uboVS.model);
+		memcpy(uniformBuffer.mapped, &uboVS, sizeof(uboVS));
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+
+		VulkanExampleBase::submitFrame();
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		loadAssets();
+		prepareUniformBuffers();
+		setupDescriptorSetLayout();
+		preparePipelines();
+		setupDescriptorPool();
+		setupDescriptorSet();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+		if (camera.updated) {
+			updateUniformBuffers();
+		}
+	}
+
+	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
+	{
+		if (overlay->header("Settings")) {
+			if (overlay->sliderInt("Material cap", &uboVS.texIndex, 0, matCapTextureArray.layerCount)) {
+				updateUniformBuffers();
+			}
+		}
+	}
+
+};
+
+VULKAN_EXAMPLE_MAIN()
\ No newline at end of file
diff --git a/external/Vulkan/examples/ssao/ssao.cpp b/external/Vulkan/examples/ssao/ssao.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..16481821d15638bfed1cde28a4dbc1497bc1c573
--- /dev/null
+++ b/external/Vulkan/examples/ssao/ssao.cpp
@@ -0,0 +1,966 @@
+/*
+* Vulkan Example - Screen space ambient occlusion example
+*
+* Copyright (C) by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkanexamplebase.h"
+#include "VulkanglTFModel.h"
+
+#define ENABLE_VALIDATION false
+
+#define SSAO_KERNEL_SIZE 32
+#define SSAO_RADIUS 0.3f
+
+#if defined(__ANDROID__)
+#define SSAO_NOISE_DIM 8
+#else
+#define SSAO_NOISE_DIM 4
+#endif
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	struct {
+		vks::Texture2D ssaoNoise;
+	} textures;
+
+	vkglTF::Model scene;
+
+	struct UBOSceneParams {
+		glm::mat4 projection;
+		glm::mat4 model;
+		glm::mat4 view;
+		float nearPlane = 0.1f;
+		float farPlane = 64.0f;
+	} uboSceneParams;
+
+	struct UBOSSAOParams {
+		glm::mat4 projection;
+		int32_t ssao = true;
+		int32_t ssaoOnly = false;
+		int32_t ssaoBlur = true;
+	} uboSSAOParams;
+
+	struct {
+		VkPipeline offscreen;
+		VkPipeline composition;
+		VkPipeline ssao;
+		VkPipeline ssaoBlur;
+	} pipelines;
+
+	struct {
+		VkPipelineLayout gBuffer;
+		VkPipelineLayout ssao;
+		VkPipelineLayout ssaoBlur;
+		VkPipelineLayout composition;
+	} pipelineLayouts;
+
+	struct {
+		const uint32_t count = 5;
+		VkDescriptorSet model;
+		VkDescriptorSet floor;
+		VkDescriptorSet ssao;
+		VkDescriptorSet ssaoBlur;
+		VkDescriptorSet composition;
+	} descriptorSets;
+
+	struct {
+		VkDescriptorSetLayout gBuffer;
+		VkDescriptorSetLayout ssao;
+		VkDescriptorSetLayout ssaoBlur;
+		VkDescriptorSetLayout composition;
+	} descriptorSetLayouts;
+
+	struct {
+		vks::Buffer sceneParams;
+		vks::Buffer ssaoKernel;
+		vks::Buffer ssaoParams;
+	} uniformBuffers;
+
+	// Framebuffer for offscreen rendering
+	struct FrameBufferAttachment {
+		VkImage image;
+		VkDeviceMemory mem;
+		VkImageView view;
+		VkFormat format;
+		void destroy(VkDevice device)
+		{
+			vkDestroyImage(device, image, nullptr);
+			vkDestroyImageView(device, view, nullptr);
+			vkFreeMemory(device, mem, nullptr);
+		}
+	};
+	struct FrameBuffer {
+		int32_t width, height;
+		VkFramebuffer frameBuffer;
+		VkRenderPass renderPass;
+		void setSize(int32_t w, int32_t h)
+		{
+			this->width = w;
+			this->height = h;
+		}
+		void destroy(VkDevice device)
+		{
+			vkDestroyFramebuffer(device, frameBuffer, nullptr);
+			vkDestroyRenderPass(device, renderPass, nullptr);
+		}
+	};
+
+	struct {
+		struct Offscreen : public FrameBuffer {
+			FrameBufferAttachment position, normal, albedo, depth;
+		} offscreen;
+		struct SSAO : public FrameBuffer {
+			FrameBufferAttachment color;
+		} ssao, ssaoBlur;
+	} frameBuffers;
+
+	// One sampler for the frame buffer color attachments
+	VkSampler colorSampler;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Screen space ambient occlusion";
+		camera.type = Camera::CameraType::firstperson;
+#ifndef __ANDROID__
+		camera.rotationSpeed = 0.25f;
+#endif
+		camera.position = { 1.0f, 0.75f, 0.0f };
+		camera.setRotation(glm::vec3(0.0f, 90.0f, 0.0f));
+		camera.setPerspective(60.0f, (float)width / (float)height, uboSceneParams.nearPlane, uboSceneParams.farPlane);
+	}
+
+	~VulkanExample()
+	{
+		vkDestroySampler(device, colorSampler, nullptr);
+
+		// Attachments
+		frameBuffers.offscreen.position.destroy(device);
+		frameBuffers.offscreen.normal.destroy(device);
+		frameBuffers.offscreen.albedo.destroy(device);
+		frameBuffers.offscreen.depth.destroy(device);
+		frameBuffers.ssao.color.destroy(device);
+		frameBuffers.ssaoBlur.color.destroy(device);
+
+		// Framebuffers
+		frameBuffers.offscreen.destroy(device);
+		frameBuffers.ssao.destroy(device);
+		frameBuffers.ssaoBlur.destroy(device);
+
+		vkDestroyPipeline(device, pipelines.offscreen, nullptr);
+		vkDestroyPipeline(device, pipelines.composition, nullptr);
+		vkDestroyPipeline(device, pipelines.ssao, nullptr);
+		vkDestroyPipeline(device, pipelines.ssaoBlur, nullptr);
+
+		vkDestroyPipelineLayout(device, pipelineLayouts.gBuffer, nullptr);
+		vkDestroyPipelineLayout(device, pipelineLayouts.ssao, nullptr);
+		vkDestroyPipelineLayout(device, pipelineLayouts.ssaoBlur, nullptr);
+		vkDestroyPipelineLayout(device, pipelineLayouts.composition, nullptr);
+
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.gBuffer, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.ssao, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.ssaoBlur, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.composition, nullptr);
+
+		// Uniform buffers
+		uniformBuffers.sceneParams.destroy();
+		uniformBuffers.ssaoKernel.destroy();
+		uniformBuffers.ssaoParams.destroy();
+
+		textures.ssaoNoise.destroy();
+	}
+
+	void getEnabledFeatures()
+	{
+		enabledFeatures.samplerAnisotropy = deviceFeatures.samplerAnisotropy;
+	}
+
+	// Create a frame buffer attachment
+	void createAttachment(
+		VkFormat format,
+		VkImageUsageFlagBits usage,
+		FrameBufferAttachment *attachment,
+		uint32_t width,
+		uint32_t height)
+	{
+		VkImageAspectFlags aspectMask = 0;
+		VkImageLayout imageLayout;
+
+		attachment->format = format;
+
+		if (usage & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT)
+		{
+			aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+			imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+		}
+		if (usage & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT)
+		{
+			aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
+			imageLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+		}
+
+		assert(aspectMask > 0);
+
+		VkImageCreateInfo image = vks::initializers::imageCreateInfo();
+		image.imageType = VK_IMAGE_TYPE_2D;
+		image.format = format;
+		image.extent.width = width;
+		image.extent.height = height;
+		image.extent.depth = 1;
+		image.mipLevels = 1;
+		image.arrayLayers = 1;
+		image.samples = VK_SAMPLE_COUNT_1_BIT;
+		image.tiling = VK_IMAGE_TILING_OPTIMAL;
+		image.usage = usage | VK_IMAGE_USAGE_SAMPLED_BIT;
+
+		VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo();
+		VkMemoryRequirements memReqs;
+
+		VK_CHECK_RESULT(vkCreateImage(device, &image, nullptr, &attachment->image));
+		vkGetImageMemoryRequirements(device, attachment->image, &memReqs);
+		memAlloc.allocationSize = memReqs.size;
+		memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+		VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &attachment->mem));
+		VK_CHECK_RESULT(vkBindImageMemory(device, attachment->image, attachment->mem, 0));
+
+		VkImageViewCreateInfo imageView = vks::initializers::imageViewCreateInfo();
+		imageView.viewType = VK_IMAGE_VIEW_TYPE_2D;
+		imageView.format = format;
+		imageView.subresourceRange = {};
+		imageView.subresourceRange.aspectMask = aspectMask;
+		imageView.subresourceRange.baseMipLevel = 0;
+		imageView.subresourceRange.levelCount = 1;
+		imageView.subresourceRange.baseArrayLayer = 0;
+		imageView.subresourceRange.layerCount = 1;
+		imageView.image = attachment->image;
+		VK_CHECK_RESULT(vkCreateImageView(device, &imageView, nullptr, &attachment->view));
+	}
+
+	void prepareOffscreenFramebuffers()
+	{
+		// Attachments
+#if defined(__ANDROID__)
+		const uint32_t ssaoWidth = width / 2;
+		const uint32_t ssaoHeight = height / 2;
+#else
+		const uint32_t ssaoWidth = width;
+		const uint32_t ssaoHeight = height;
+#endif
+
+		frameBuffers.offscreen.setSize(width, height);
+		frameBuffers.ssao.setSize(ssaoWidth, ssaoHeight);
+		frameBuffers.ssaoBlur.setSize(width, height);
+
+		// Find a suitable depth format
+		VkFormat attDepthFormat;
+		VkBool32 validDepthFormat = vks::tools::getSupportedDepthFormat(physicalDevice, &attDepthFormat);
+		assert(validDepthFormat);
+
+		// G-Buffer
+		createAttachment(VK_FORMAT_R32G32B32A32_SFLOAT, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, &frameBuffers.offscreen.position, width, height);	// Position + Depth
+		createAttachment(VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, &frameBuffers.offscreen.normal, width, height);			// Normals
+		createAttachment(VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, &frameBuffers.offscreen.albedo, width, height);			// Albedo (color)
+		createAttachment(attDepthFormat, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, &frameBuffers.offscreen.depth, width, height);			// Depth
+
+		// SSAO
+		createAttachment(VK_FORMAT_R8_UNORM, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, &frameBuffers.ssao.color, ssaoWidth, ssaoHeight);				// Color
+
+		// SSAO blur
+		createAttachment(VK_FORMAT_R8_UNORM, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, &frameBuffers.ssaoBlur.color, width, height);					// Color
+
+		// Render passes
+
+		// G-Buffer creation
+		{
+			std::array<VkAttachmentDescription, 4> attachmentDescs = {};
+
+			// Init attachment properties
+			for (uint32_t i = 0; i < static_cast<uint32_t>(attachmentDescs.size()); i++)
+			{
+				attachmentDescs[i].samples = VK_SAMPLE_COUNT_1_BIT;
+				attachmentDescs[i].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+				attachmentDescs[i].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+				attachmentDescs[i].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+				attachmentDescs[i].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+				attachmentDescs[i].finalLayout = (i == 3) ? VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL : VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+			}
+
+			// Formats
+			attachmentDescs[0].format = frameBuffers.offscreen.position.format;
+			attachmentDescs[1].format = frameBuffers.offscreen.normal.format;
+			attachmentDescs[2].format = frameBuffers.offscreen.albedo.format;
+			attachmentDescs[3].format = frameBuffers.offscreen.depth.format;
+
+			std::vector<VkAttachmentReference> colorReferences;
+			colorReferences.push_back({ 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL });
+			colorReferences.push_back({ 1, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL });
+			colorReferences.push_back({ 2, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL });
+
+			VkAttachmentReference depthReference = {};
+			depthReference.attachment = 3;
+			depthReference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+
+			VkSubpassDescription subpass = {};
+			subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+			subpass.pColorAttachments = colorReferences.data();
+			subpass.colorAttachmentCount = static_cast<uint32_t>(colorReferences.size());
+			subpass.pDepthStencilAttachment = &depthReference;
+
+			// Use subpass dependencies for attachment layout transitions
+			std::array<VkSubpassDependency, 2> dependencies;
+
+			dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
+			dependencies[0].dstSubpass = 0;
+			dependencies[0].srcStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
+			dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+			dependencies[0].srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
+			dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+			dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+			dependencies[1].srcSubpass = 0;
+			dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
+			dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+			dependencies[1].dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
+			dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+			dependencies[1].dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
+			dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+			VkRenderPassCreateInfo renderPassInfo = {};
+			renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
+			renderPassInfo.pAttachments = attachmentDescs.data();
+			renderPassInfo.attachmentCount = static_cast<uint32_t>(attachmentDescs.size());
+			renderPassInfo.subpassCount = 1;
+			renderPassInfo.pSubpasses = &subpass;
+			renderPassInfo.dependencyCount = 2;
+			renderPassInfo.pDependencies = dependencies.data();
+			VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassInfo, nullptr, &frameBuffers.offscreen.renderPass));
+
+			std::array<VkImageView, 4> attachments;
+			attachments[0] = frameBuffers.offscreen.position.view;
+			attachments[1] = frameBuffers.offscreen.normal.view;
+			attachments[2] = frameBuffers.offscreen.albedo.view;
+			attachments[3] = frameBuffers.offscreen.depth.view;
+
+			VkFramebufferCreateInfo fbufCreateInfo = vks::initializers::framebufferCreateInfo();
+			fbufCreateInfo.renderPass = frameBuffers.offscreen.renderPass;
+			fbufCreateInfo.pAttachments = attachments.data();
+			fbufCreateInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
+			fbufCreateInfo.width = frameBuffers.offscreen.width;
+			fbufCreateInfo.height = frameBuffers.offscreen.height;
+			fbufCreateInfo.layers = 1;
+			VK_CHECK_RESULT(vkCreateFramebuffer(device, &fbufCreateInfo, nullptr, &frameBuffers.offscreen.frameBuffer));
+		}
+
+		// SSAO
+		{
+			VkAttachmentDescription attachmentDescription{};
+			attachmentDescription.format = frameBuffers.ssao.color.format;
+			attachmentDescription.samples = VK_SAMPLE_COUNT_1_BIT;
+			attachmentDescription.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+			attachmentDescription.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+			attachmentDescription.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+			attachmentDescription.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+			attachmentDescription.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+			attachmentDescription.finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+
+			VkAttachmentReference colorReference = { 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
+
+			VkSubpassDescription subpass = {};
+			subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+			subpass.pColorAttachments = &colorReference;
+			subpass.colorAttachmentCount = 1;
+
+			std::array<VkSubpassDependency, 2> dependencies;
+
+			dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
+			dependencies[0].dstSubpass = 0;
+			dependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+			dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+			dependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
+			dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+			dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+			dependencies[1].srcSubpass = 0;
+			dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
+			dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+			dependencies[1].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+			dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+			dependencies[1].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
+			dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+			VkRenderPassCreateInfo renderPassInfo = {};
+			renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
+			renderPassInfo.pAttachments = &attachmentDescription;
+			renderPassInfo.attachmentCount = 1;
+			renderPassInfo.subpassCount = 1;
+			renderPassInfo.pSubpasses = &subpass;
+			renderPassInfo.dependencyCount = 2;
+			renderPassInfo.pDependencies = dependencies.data();
+			VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassInfo, nullptr, &frameBuffers.ssao.renderPass));
+
+			VkFramebufferCreateInfo fbufCreateInfo = vks::initializers::framebufferCreateInfo();
+			fbufCreateInfo.renderPass = frameBuffers.ssao.renderPass;
+			fbufCreateInfo.pAttachments = &frameBuffers.ssao.color.view;
+			fbufCreateInfo.attachmentCount = 1;
+			fbufCreateInfo.width = frameBuffers.ssao.width;
+			fbufCreateInfo.height = frameBuffers.ssao.height;
+			fbufCreateInfo.layers = 1;
+			VK_CHECK_RESULT(vkCreateFramebuffer(device, &fbufCreateInfo, nullptr, &frameBuffers.ssao.frameBuffer));
+		}
+
+		// SSAO Blur
+		{
+			VkAttachmentDescription attachmentDescription{};
+			attachmentDescription.format = frameBuffers.ssaoBlur.color.format;
+			attachmentDescription.samples = VK_SAMPLE_COUNT_1_BIT;
+			attachmentDescription.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+			attachmentDescription.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+			attachmentDescription.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+			attachmentDescription.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+			attachmentDescription.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+			attachmentDescription.finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+
+			VkAttachmentReference colorReference = { 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
+
+			VkSubpassDescription subpass = {};
+			subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+			subpass.pColorAttachments = &colorReference;
+			subpass.colorAttachmentCount = 1;
+
+			std::array<VkSubpassDependency, 2> dependencies;
+
+			dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
+			dependencies[0].dstSubpass = 0;
+			dependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+			dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+			dependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
+			dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+			dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+			dependencies[1].srcSubpass = 0;
+			dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
+			dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+			dependencies[1].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+			dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+			dependencies[1].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
+			dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+			VkRenderPassCreateInfo renderPassInfo = {};
+			renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
+			renderPassInfo.pAttachments = &attachmentDescription;
+			renderPassInfo.attachmentCount = 1;
+			renderPassInfo.subpassCount = 1;
+			renderPassInfo.pSubpasses = &subpass;
+			renderPassInfo.dependencyCount = 2;
+			renderPassInfo.pDependencies = dependencies.data();
+			VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassInfo, nullptr, &frameBuffers.ssaoBlur.renderPass));
+
+			VkFramebufferCreateInfo fbufCreateInfo = vks::initializers::framebufferCreateInfo();
+			fbufCreateInfo.renderPass = frameBuffers.ssaoBlur.renderPass;
+			fbufCreateInfo.pAttachments = &frameBuffers.ssaoBlur.color.view;
+			fbufCreateInfo.attachmentCount = 1;
+			fbufCreateInfo.width = frameBuffers.ssaoBlur.width;
+			fbufCreateInfo.height = frameBuffers.ssaoBlur.height;
+			fbufCreateInfo.layers = 1;
+			VK_CHECK_RESULT(vkCreateFramebuffer(device, &fbufCreateInfo, nullptr, &frameBuffers.ssaoBlur.frameBuffer));
+		}
+
+		// Shared sampler used for all color attachments
+		VkSamplerCreateInfo sampler = vks::initializers::samplerCreateInfo();
+		sampler.magFilter = VK_FILTER_NEAREST;
+		sampler.minFilter = VK_FILTER_NEAREST;
+		sampler.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+		sampler.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+		sampler.addressModeV = sampler.addressModeU;
+		sampler.addressModeW = sampler.addressModeU;
+		sampler.mipLodBias = 0.0f;
+		sampler.maxAnisotropy = 1.0f;
+		sampler.minLod = 0.0f;
+		sampler.maxLod = 1.0f;
+		sampler.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
+		VK_CHECK_RESULT(vkCreateSampler(device, &sampler, nullptr, &colorSampler));
+	}
+
+	void loadAssets()
+	{
+		vkglTF::descriptorBindingFlags  = vkglTF::DescriptorBindingFlags::ImageBaseColor;
+		const uint32_t gltfLoadingFlags = vkglTF::FileLoadingFlags::FlipY | vkglTF::FileLoadingFlags::PreTransformVertices;
+		scene.loadFromFile(getAssetPath() + "models/sponza/sponza.gltf", vulkanDevice, queue, gltfLoadingFlags);
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkDeviceSize offsets[1] = { 0 };
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			/*
+				Offscreen SSAO generation
+			*/
+			{
+				// Clear values for all attachments written in the fragment shader
+				std::vector<VkClearValue> clearValues(4);
+				clearValues[0].color = { { 0.0f, 0.0f, 0.0f, 1.0f } };
+				clearValues[1].color = { { 0.0f, 0.0f, 0.0f, 1.0f } };
+				clearValues[2].color = { { 0.0f, 0.0f, 0.0f, 1.0f } };
+				clearValues[3].depthStencil = { 1.0f, 0 };
+
+				VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+				renderPassBeginInfo.renderPass = frameBuffers.offscreen.renderPass;
+				renderPassBeginInfo.framebuffer = frameBuffers.offscreen.frameBuffer;
+				renderPassBeginInfo.renderArea.extent.width = frameBuffers.offscreen.width;
+				renderPassBeginInfo.renderArea.extent.height = frameBuffers.offscreen.height;
+				renderPassBeginInfo.clearValueCount = static_cast<uint32_t>(clearValues.size());
+				renderPassBeginInfo.pClearValues = clearValues.data();
+
+				/*
+					First pass: Fill G-Buffer components (positions+depth, normals, albedo) using MRT
+				*/
+
+				vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+				VkViewport viewport = vks::initializers::viewport((float)frameBuffers.offscreen.width, (float)frameBuffers.offscreen.height, 0.0f, 1.0f);
+				vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+				VkRect2D scissor = vks::initializers::rect2D(frameBuffers.offscreen.width, frameBuffers.offscreen.height, 0, 0);
+				vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+				vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.offscreen);
+
+				vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayouts.gBuffer, 0, 1, &descriptorSets.floor, 0, NULL);
+				scene.draw(drawCmdBuffers[i], vkglTF::RenderFlags::BindImages, pipelineLayouts.gBuffer);
+
+				vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+				/*
+					Second pass: SSAO generation
+				*/
+
+				clearValues[0].color = { { 0.0f, 0.0f, 0.0f, 1.0f } };
+				clearValues[1].depthStencil = { 1.0f, 0 };
+
+				renderPassBeginInfo.framebuffer = frameBuffers.ssao.frameBuffer;
+				renderPassBeginInfo.renderPass = frameBuffers.ssao.renderPass;
+				renderPassBeginInfo.renderArea.extent.width = frameBuffers.ssao.width;
+				renderPassBeginInfo.renderArea.extent.height = frameBuffers.ssao.height;
+				renderPassBeginInfo.clearValueCount = 2;
+				renderPassBeginInfo.pClearValues = clearValues.data();
+
+				vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+				viewport = vks::initializers::viewport((float)frameBuffers.ssao.width, (float)frameBuffers.ssao.height, 0.0f, 1.0f);
+				vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+				scissor = vks::initializers::rect2D(frameBuffers.ssao.width, frameBuffers.ssao.height, 0, 0);
+				vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+				vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayouts.ssao, 0, 1, &descriptorSets.ssao, 0, NULL);
+				vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.ssao);
+				vkCmdDraw(drawCmdBuffers[i], 3, 1, 0, 0);
+
+				vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+				/*
+					Third pass: SSAO blur
+				*/
+
+				renderPassBeginInfo.framebuffer = frameBuffers.ssaoBlur.frameBuffer;
+				renderPassBeginInfo.renderPass = frameBuffers.ssaoBlur.renderPass;
+				renderPassBeginInfo.renderArea.extent.width = frameBuffers.ssaoBlur.width;
+				renderPassBeginInfo.renderArea.extent.height = frameBuffers.ssaoBlur.height;
+
+				vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+				viewport = vks::initializers::viewport((float)frameBuffers.ssaoBlur.width, (float)frameBuffers.ssaoBlur.height, 0.0f, 1.0f);
+				vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+				scissor = vks::initializers::rect2D(frameBuffers.ssaoBlur.width, frameBuffers.ssaoBlur.height, 0, 0);
+				vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+				vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayouts.ssaoBlur, 0, 1, &descriptorSets.ssaoBlur, 0, NULL);
+				vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.ssaoBlur);
+				vkCmdDraw(drawCmdBuffers[i], 3, 1, 0, 0);
+
+				vkCmdEndRenderPass(drawCmdBuffers[i]);
+			}
+
+			/*
+				Note: Explicit synchronization is not required between the render pass, as this is done implicit via sub pass dependencies
+			*/
+
+			/*
+				Final render pass: Scene rendering with applied radial blur
+			*/
+			{
+				std::vector<VkClearValue> clearValues(2);
+				clearValues[0].color = defaultClearColor;
+				clearValues[1].depthStencil = { 1.0f, 0 };
+
+				VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+				renderPassBeginInfo.renderPass = renderPass;
+				renderPassBeginInfo.framebuffer = VulkanExampleBase::frameBuffers[i];
+				renderPassBeginInfo.renderArea.extent.width = width;
+				renderPassBeginInfo.renderArea.extent.height = height;
+				renderPassBeginInfo.clearValueCount = 2;
+				renderPassBeginInfo.pClearValues = clearValues.data();
+
+				vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+				VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+				vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+				VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
+				vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+				vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayouts.composition, 0, 1, &descriptorSets.composition, 0, NULL);
+
+				// Final composition pass
+				vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.composition);
+				vkCmdDraw(drawCmdBuffers[i], 3, 1, 0, 0);
+
+				drawUI(drawCmdBuffers[i]);
+
+				vkCmdEndRenderPass(drawCmdBuffers[i]);
+			}
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void setupDescriptorPool()
+	{
+		std::vector<VkDescriptorPoolSize> poolSizes = {
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 10),
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 12)
+		};
+		VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes,  descriptorSets.count);
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+	}
+
+	void setupLayoutsAndDescriptors()
+	{
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings;
+		VkDescriptorSetLayoutCreateInfo setLayoutCreateInfo;
+		VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo();
+		VkDescriptorSetAllocateInfo descriptorAllocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, nullptr, 1);
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets;
+		std::vector<VkDescriptorImageInfo> imageDescriptors;
+
+		// G-Buffer creation (offscreen scene rendering)
+		setLayoutBindings = {
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0),	// VS + FS Parameter UBO
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1),						// FS Color
+		};
+		setLayoutCreateInfo = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings.data(), static_cast<uint32_t>(setLayoutBindings.size()));
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &setLayoutCreateInfo, nullptr, &descriptorSetLayouts.gBuffer));
+
+		const std::vector<VkDescriptorSetLayout> setLayouts = { descriptorSetLayouts.gBuffer, vkglTF::descriptorSetLayoutImage };
+		pipelineLayoutCreateInfo.pSetLayouts = setLayouts.data();
+		pipelineLayoutCreateInfo.setLayoutCount = 2;
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayouts.gBuffer));
+		descriptorAllocInfo.pSetLayouts = &descriptorSetLayouts.gBuffer;
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &descriptorAllocInfo, &descriptorSets.floor));
+		writeDescriptorSets = {
+			vks::initializers::writeDescriptorSet(descriptorSets.floor, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.sceneParams.descriptor),
+		};
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL);
+		pipelineLayoutCreateInfo.setLayoutCount = 1;
+
+		// SSAO Generation
+		setLayoutBindings = {
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0),						// FS Position+Depth
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1),						// FS Normals
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 2),						// FS SSAO Noise
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_FRAGMENT_BIT, 3),								// FS SSAO Kernel UBO
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_FRAGMENT_BIT, 4),								// FS Params UBO
+		};
+		setLayoutCreateInfo = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings.data(), static_cast<uint32_t>(setLayoutBindings.size()));
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &setLayoutCreateInfo, nullptr, &descriptorSetLayouts.ssao));
+		pipelineLayoutCreateInfo.pSetLayouts = &descriptorSetLayouts.ssao;
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayouts.ssao));
+		descriptorAllocInfo.pSetLayouts = &descriptorSetLayouts.ssao;
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &descriptorAllocInfo, &descriptorSets.ssao));
+		imageDescriptors = {
+			vks::initializers::descriptorImageInfo(colorSampler, frameBuffers.offscreen.position.view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL),
+			vks::initializers::descriptorImageInfo(colorSampler, frameBuffers.offscreen.normal.view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL),
+		};
+		writeDescriptorSets = {
+			vks::initializers::writeDescriptorSet(descriptorSets.ssao, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &imageDescriptors[0]),					// FS Position+Depth
+			vks::initializers::writeDescriptorSet(descriptorSets.ssao, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &imageDescriptors[1]),					// FS Normals
+			vks::initializers::writeDescriptorSet(descriptorSets.ssao, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2, &textures.ssaoNoise.descriptor),		// FS SSAO Noise
+			vks::initializers::writeDescriptorSet(descriptorSets.ssao, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 3, &uniformBuffers.ssaoKernel.descriptor),		// FS SSAO Kernel UBO
+			vks::initializers::writeDescriptorSet(descriptorSets.ssao, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 4, &uniformBuffers.ssaoParams.descriptor),		// FS SSAO Params UBO
+		};
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL);
+
+		// SSAO Blur
+		setLayoutBindings = {
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0),						// FS Sampler SSAO
+		};
+		setLayoutCreateInfo = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings.data(), static_cast<uint32_t>(setLayoutBindings.size()));
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &setLayoutCreateInfo, nullptr, &descriptorSetLayouts.ssaoBlur));
+		pipelineLayoutCreateInfo.pSetLayouts = &descriptorSetLayouts.ssaoBlur;
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayouts.ssaoBlur));
+		descriptorAllocInfo.pSetLayouts = &descriptorSetLayouts.ssaoBlur;
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &descriptorAllocInfo, &descriptorSets.ssaoBlur));
+		imageDescriptors = {
+			vks::initializers::descriptorImageInfo(colorSampler, frameBuffers.ssao.color.view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL),
+		};
+		writeDescriptorSets = {
+			vks::initializers::writeDescriptorSet(descriptorSets.ssaoBlur, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &imageDescriptors[0]),
+		};
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL);
+
+		// Composition
+		setLayoutBindings = {
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0),						// FS Position+Depth
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1),						// FS Normals
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 2),						// FS Albedo
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 3),						// FS SSAO
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 4),						// FS SSAO blurred
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_FRAGMENT_BIT, 5),								// FS Lights UBO
+		};
+		setLayoutCreateInfo = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings.data(), static_cast<uint32_t>(setLayoutBindings.size()));
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &setLayoutCreateInfo, nullptr, &descriptorSetLayouts.composition));
+		pipelineLayoutCreateInfo.pSetLayouts = &descriptorSetLayouts.composition;
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayouts.composition));
+		descriptorAllocInfo.pSetLayouts = &descriptorSetLayouts.composition;
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &descriptorAllocInfo, &descriptorSets.composition));
+		imageDescriptors = {
+			vks::initializers::descriptorImageInfo(colorSampler, frameBuffers.offscreen.position.view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL),
+			vks::initializers::descriptorImageInfo(colorSampler, frameBuffers.offscreen.normal.view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL),
+			vks::initializers::descriptorImageInfo(colorSampler, frameBuffers.offscreen.albedo.view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL),
+			vks::initializers::descriptorImageInfo(colorSampler, frameBuffers.ssao.color.view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL),
+			vks::initializers::descriptorImageInfo(colorSampler, frameBuffers.ssaoBlur.color.view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL),
+		};
+		writeDescriptorSets = {
+			vks::initializers::writeDescriptorSet(descriptorSets.composition, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &imageDescriptors[0]),			// FS Sampler Position+Depth
+			vks::initializers::writeDescriptorSet(descriptorSets.composition, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &imageDescriptors[1]),			// FS Sampler Normals
+			vks::initializers::writeDescriptorSet(descriptorSets.composition, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2, &imageDescriptors[2]),			// FS Sampler Albedo
+			vks::initializers::writeDescriptorSet(descriptorSets.composition, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 3, &imageDescriptors[3]),			// FS Sampler SSAO
+			vks::initializers::writeDescriptorSet(descriptorSets.composition, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 4, &imageDescriptors[4]),			// FS Sampler SSAO blurred
+			vks::initializers::writeDescriptorSet(descriptorSets.composition, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 5, &uniformBuffers.ssaoParams.descriptor),	// FS SSAO Params UBO
+		};
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL);
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+		VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
+		std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
+		VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+
+		VkGraphicsPipelineCreateInfo pipelineCreateInfo = vks::initializers::pipelineCreateInfo( pipelineLayouts.composition, renderPass, 0);
+		pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState;
+		pipelineCreateInfo.pRasterizationState = &rasterizationState;
+		pipelineCreateInfo.pColorBlendState = &colorBlendState;
+		pipelineCreateInfo.pMultisampleState = &multisampleState;
+		pipelineCreateInfo.pViewportState = &viewportState;
+		pipelineCreateInfo.pDepthStencilState = &depthStencilState;
+		pipelineCreateInfo.pDynamicState = &dynamicState;
+		pipelineCreateInfo.stageCount = static_cast<uint32_t>(shaderStages.size());
+		pipelineCreateInfo.pStages = shaderStages.data();
+
+		// Empty vertex input state for fullscreen passes
+		VkPipelineVertexInputStateCreateInfo emptyVertexInputState = vks::initializers::pipelineVertexInputStateCreateInfo();
+		pipelineCreateInfo.pVertexInputState = &emptyVertexInputState;
+		rasterizationState.cullMode = VK_CULL_MODE_FRONT_BIT;
+
+		// Final composition pipeline
+		shaderStages[0] = loadShader(getShadersPath() + "ssao/fullscreen.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "ssao/composition.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipelines.composition));
+
+		// SSAO generation pipeline
+		{
+			pipelineCreateInfo.renderPass = frameBuffers.ssao.renderPass;
+			pipelineCreateInfo.layout = pipelineLayouts.ssao;
+			// SSAO Kernel size and radius are constant for this pipeline, so we set them using specialization constants
+			struct SpecializationData {
+				uint32_t kernelSize = SSAO_KERNEL_SIZE;
+				float radius = SSAO_RADIUS;
+			} specializationData;
+			std::array<VkSpecializationMapEntry, 2> specializationMapEntries = {
+				vks::initializers::specializationMapEntry(0, offsetof(SpecializationData, kernelSize), sizeof(SpecializationData::kernelSize)),
+				vks::initializers::specializationMapEntry(1, offsetof(SpecializationData, radius), sizeof(SpecializationData::radius))
+			};
+			VkSpecializationInfo specializationInfo = vks::initializers::specializationInfo(2, specializationMapEntries.data(), sizeof(specializationData), &specializationData);
+			shaderStages[1] = loadShader(getShadersPath() + "ssao/ssao.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+			shaderStages[1].pSpecializationInfo = &specializationInfo;
+			VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipelines.ssao));
+		}
+
+		// SSAO blur pipeline
+		{
+			pipelineCreateInfo.renderPass = frameBuffers.ssaoBlur.renderPass;
+			pipelineCreateInfo.layout = pipelineLayouts.ssaoBlur;
+			shaderStages[1] = loadShader(getShadersPath() + "ssao/blur.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+			VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipelines.ssaoBlur));
+		}
+
+		// Fill G-Buffer pipeline
+		{
+			// Vertex input state from glTF model loader
+			pipelineCreateInfo.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position, vkglTF::VertexComponent::UV, vkglTF::VertexComponent::Color, vkglTF::VertexComponent::Normal });
+			pipelineCreateInfo.renderPass = frameBuffers.offscreen.renderPass;
+			pipelineCreateInfo.layout = pipelineLayouts.gBuffer;
+			// Blend attachment states required for all color attachments
+			// This is important, as color write mask will otherwise be 0x0 and you
+			// won't see anything rendered to the attachment
+			std::array<VkPipelineColorBlendAttachmentState, 3> blendAttachmentStates = {
+				vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE),
+				vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE),
+				vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE)
+			};
+			colorBlendState.attachmentCount = static_cast<uint32_t>(blendAttachmentStates.size());
+			colorBlendState.pAttachments = blendAttachmentStates.data();
+			rasterizationState.cullMode = VK_CULL_MODE_BACK_BIT;
+			shaderStages[0] = loadShader(getShadersPath() + "ssao/gbuffer.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+			shaderStages[1] = loadShader(getShadersPath() + "ssao/gbuffer.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+			VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipelines.offscreen));
+		}
+	}
+
+	float lerp(float a, float b, float f)
+	{
+		return a + f * (b - a);
+	}
+
+	// Prepare and initialize uniform buffer containing shader uniforms
+	void prepareUniformBuffers()
+	{
+		// Scene matrices
+		vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.sceneParams,
+			sizeof(uboSceneParams));
+
+		// SSAO parameters
+		vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.ssaoParams,
+			sizeof(uboSSAOParams));
+
+		// Update
+		updateUniformBufferMatrices();
+		updateUniformBufferSSAOParams();
+
+		// SSAO
+		std::default_random_engine rndEngine(benchmark.active ? 0 : (unsigned)time(nullptr));
+		std::uniform_real_distribution<float> rndDist(0.0f, 1.0f);
+
+		// Sample kernel
+		std::vector<glm::vec4> ssaoKernel(SSAO_KERNEL_SIZE);
+		for (uint32_t i = 0; i < SSAO_KERNEL_SIZE; ++i)
+		{
+			glm::vec3 sample(rndDist(rndEngine) * 2.0 - 1.0, rndDist(rndEngine) * 2.0 - 1.0, rndDist(rndEngine));
+			sample = glm::normalize(sample);
+			sample *= rndDist(rndEngine);
+			float scale = float(i) / float(SSAO_KERNEL_SIZE);
+			scale = lerp(0.1f, 1.0f, scale * scale);
+			ssaoKernel[i] = glm::vec4(sample * scale, 0.0f);
+		}
+
+		// Upload as UBO
+		vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.ssaoKernel,
+			ssaoKernel.size() * sizeof(glm::vec4),
+			ssaoKernel.data());
+
+		// Random noise
+		std::vector<glm::vec4> ssaoNoise(SSAO_NOISE_DIM * SSAO_NOISE_DIM);
+		for (uint32_t i = 0; i < static_cast<uint32_t>(ssaoNoise.size()); i++)
+		{
+			ssaoNoise[i] = glm::vec4(rndDist(rndEngine) * 2.0f - 1.0f, rndDist(rndEngine) * 2.0f - 1.0f, 0.0f, 0.0f);
+		}
+		// Upload as texture
+		textures.ssaoNoise.fromBuffer(ssaoNoise.data(), ssaoNoise.size() * sizeof(glm::vec4), VK_FORMAT_R32G32B32A32_SFLOAT, SSAO_NOISE_DIM, SSAO_NOISE_DIM, vulkanDevice, queue, VK_FILTER_NEAREST);
+	}
+
+	void updateUniformBufferMatrices()
+	{
+		uboSceneParams.projection = camera.matrices.perspective;
+		uboSceneParams.view = camera.matrices.view;
+		uboSceneParams.model = glm::mat4(1.0f);
+
+		VK_CHECK_RESULT(uniformBuffers.sceneParams.map());
+		uniformBuffers.sceneParams.copyTo(&uboSceneParams, sizeof(uboSceneParams));
+		uniformBuffers.sceneParams.unmap();
+	}
+
+	void updateUniformBufferSSAOParams()
+	{
+		uboSSAOParams.projection = camera.matrices.perspective;
+
+		VK_CHECK_RESULT(uniformBuffers.ssaoParams.map());
+		uniformBuffers.ssaoParams.copyTo(&uboSSAOParams, sizeof(uboSSAOParams));
+		uniformBuffers.ssaoParams.unmap();
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+		VulkanExampleBase::submitFrame();
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		loadAssets();
+		prepareOffscreenFramebuffers();
+		prepareUniformBuffers();
+		setupDescriptorPool();
+		setupLayoutsAndDescriptors();
+		preparePipelines();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared) {
+			return;
+		}
+		draw();
+		if (camera.updated) {
+			updateUniformBufferMatrices();
+			updateUniformBufferSSAOParams();
+		}
+	}
+
+	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
+	{
+		if (overlay->header("Settings")) {
+			if (overlay->checkBox("Enable SSAO", &uboSSAOParams.ssao)) {
+				updateUniformBufferSSAOParams();
+			}
+			if (overlay->checkBox("SSAO blur", &uboSSAOParams.ssaoBlur)) {
+				updateUniformBufferSSAOParams();
+			}
+			if (overlay->checkBox("SSAO pass only", &uboSSAOParams.ssaoOnly)) {
+				updateUniformBufferSSAOParams();
+			}
+		}
+	}
+};
+
+VULKAN_EXAMPLE_MAIN()
diff --git a/external/Vulkan/examples/stencilbuffer/stencilbuffer.cpp b/external/Vulkan/examples/stencilbuffer/stencilbuffer.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..1deb28b413686361b274cf183e1f4bc498aebe3c
--- /dev/null
+++ b/external/Vulkan/examples/stencilbuffer/stencilbuffer.cpp
@@ -0,0 +1,273 @@
+/*
+* Vulkan Example - Rendering outlines using the stencil buffer
+*
+* Copyright (C) 2016-2017 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkanexamplebase.h"
+#include "VulkanglTFModel.h"
+
+#define ENABLE_VALIDATION false
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	vkglTF::Model model;
+
+	struct UBO {
+		glm::mat4 projection;
+		glm::mat4 model;
+		glm::vec4 lightPos = glm::vec4(0.0f, -2.0f, 1.0f, 0.0f);
+		// Vertex shader extrudes model by this value along normals for outlining
+		float outlineWidth = 0.025f;
+	} uboVS;
+
+	vks::Buffer uniformBufferVS;
+
+	struct {
+		VkPipeline stencil;
+		VkPipeline outline;
+	} pipelines;
+
+	VkPipelineLayout pipelineLayout;
+	VkDescriptorSet descriptorSet;
+	VkDescriptorSetLayout descriptorSetLayout;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Stencil buffer outlines";
+		timerSpeed *= 0.25f;
+		camera.type = Camera::CameraType::lookat;
+		camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 512.0f);
+		camera.setRotation(glm::vec3(2.5f, -35.0f, 0.0f));
+		camera.setTranslation(glm::vec3(0.0f, 0.0f, -2.0f));
+	}
+
+	~VulkanExample()
+	{
+		vkDestroyPipeline(device, pipelines.stencil, nullptr);
+		vkDestroyPipeline(device, pipelines.outline, nullptr);
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+		uniformBufferVS.destroy();
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		clearValues[0].color = defaultClearColor;
+		clearValues[1].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.offset.x = 0;
+		renderPassBeginInfo.renderArea.offset.y = 0;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		renderPassBeginInfo.clearValueCount = 2;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			renderPassBeginInfo.framebuffer = frameBuffers[i];
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+			VkRect2D scissor = vks::initializers::rect2D(width, height,	0, 0);
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+			VkDeviceSize offsets[1] = { 0 };
+
+			vkCmdBindVertexBuffers(drawCmdBuffers[i], 0, 1, &model.vertices.buffer, offsets);
+			vkCmdBindIndexBuffer(drawCmdBuffers[i], model.indices.buffer, 0, VK_INDEX_TYPE_UINT32);
+
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL);
+
+			// First pass renders object (toon shaded) and fills stencil buffer
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.stencil);
+			model.draw(drawCmdBuffers[i]);
+
+			// Second pass renders scaled object only where stencil was not set by first pass
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.outline);
+			model.draw(drawCmdBuffers[i]);
+
+			drawUI(drawCmdBuffers[i]);
+
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void loadAssets()
+	{
+		model.loadFromFile(getAssetPath() + "models/venus.gltf", vulkanDevice, queue, vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::FlipY);
+	}
+
+	void setupDescriptorPool()
+	{
+		std::vector<VkDescriptorPoolSize> poolSizes = {
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1),
+		};
+		VkDescriptorPoolCreateInfo descriptorPoolInfo =
+			vks::initializers::descriptorPoolCreateInfo(static_cast<uint32_t>(poolSizes.size()), poolSizes.data(), 1);
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+	}
+
+	void setupDescriptorSetLayout()
+	{
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0)
+		};
+
+		VkDescriptorSetLayoutCreateInfo descriptorLayoutInfo =
+			vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings.data(), 1);
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayoutInfo, nullptr, &descriptorSetLayout));
+
+		VkPipelineLayoutCreateInfo pipelineLayoutInfo =
+			vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1);
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout));
+	}
+
+	void setupDescriptorSet()
+	{
+		VkDescriptorSetAllocateInfo allocInfo =
+			vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1);
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet));
+		std::vector<VkWriteDescriptorSet> modelWriteDescriptorSets = {
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBufferVS.descriptor)
+		};
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(modelWriteDescriptorSets.size()), modelWriteDescriptorSets.data(), 0, NULL);
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_FRONT_BIT, VK_FRONT_FACE_CLOCKWISE, 0);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+		VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
+		std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
+		VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass, 0);
+		pipelineCI.pInputAssemblyState = &inputAssemblyState;
+		pipelineCI.pRasterizationState = &rasterizationState;
+		pipelineCI.pColorBlendState = &colorBlendState;
+		pipelineCI.pMultisampleState = &multisampleState;
+		pipelineCI.pViewportState = &viewportState;
+		pipelineCI.pDepthStencilState = &depthStencilState;
+		pipelineCI.pDynamicState = &dynamicState;
+		pipelineCI.stageCount = static_cast<uint32_t>(shaderStages.size());
+		pipelineCI.pStages = shaderStages.data();
+		pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Color, vkglTF::VertexComponent::Normal });
+
+		// Toon render and stencil fill pass
+		shaderStages[0] = loadShader(getShadersPath() + "stencilbuffer/toon.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "stencilbuffer/toon.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		rasterizationState.cullMode = VK_CULL_MODE_NONE;
+		depthStencilState.stencilTestEnable = VK_TRUE;
+		depthStencilState.back.compareOp = VK_COMPARE_OP_ALWAYS;
+		depthStencilState.back.failOp = VK_STENCIL_OP_REPLACE;
+		depthStencilState.back.depthFailOp = VK_STENCIL_OP_REPLACE;
+		depthStencilState.back.passOp = VK_STENCIL_OP_REPLACE;
+		depthStencilState.back.compareMask = 0xff;
+		depthStencilState.back.writeMask = 0xff;
+		depthStencilState.back.reference = 1;
+		depthStencilState.front = depthStencilState.back;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.stencil));
+		// Outline pass
+		depthStencilState.back.compareOp = VK_COMPARE_OP_NOT_EQUAL;
+		depthStencilState.back.failOp = VK_STENCIL_OP_KEEP;
+		depthStencilState.back.depthFailOp = VK_STENCIL_OP_KEEP;
+		depthStencilState.back.passOp = VK_STENCIL_OP_REPLACE;
+		depthStencilState.front = depthStencilState.back;
+		depthStencilState.depthTestEnable = VK_FALSE;
+		shaderStages[0] = loadShader(getShadersPath() + "stencilbuffer/outline.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "stencilbuffer/outline.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.outline));
+	}
+
+	// Prepare and initialize uniform buffer containing shader uniforms
+	void prepareUniformBuffers()
+	{
+		// Mesh vertex shader uniform buffer block
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBufferVS,
+			sizeof(uboVS)));
+
+		// Map persistent
+		VK_CHECK_RESULT(uniformBufferVS.map());
+
+		updateUniformBuffers();
+	}
+
+	void updateUniformBuffers()
+	{
+		uboVS.projection = camera.matrices.perspective;
+		uboVS.model = camera.matrices.view;
+		memcpy(uniformBufferVS.mapped, &uboVS, sizeof(uboVS));
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+
+		VulkanExampleBase::submitFrame();
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		loadAssets();
+		prepareUniformBuffers();
+		setupDescriptorSetLayout();
+		preparePipelines();
+		setupDescriptorPool();
+		setupDescriptorSet();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+	}
+
+	virtual void viewChanged()
+	{
+		updateUniformBuffers();
+	}
+
+	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
+	{
+		if (overlay->header("Settings")) {
+			if (overlay->inputFloat("Outline width", &uboVS.outlineWidth, 0.05f, 2)) {
+				updateUniformBuffers();
+			}
+		}
+	}
+
+};
+
+VULKAN_EXAMPLE_MAIN()
diff --git a/external/Vulkan/examples/subpasses/subpasses.cpp b/external/Vulkan/examples/subpasses/subpasses.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d1e4ea93386bf29c3c2ee5000ba562be2f83aacf
--- /dev/null
+++ b/external/Vulkan/examples/subpasses/subpasses.cpp
@@ -0,0 +1,922 @@
+/*
+* Vulkan Example - Using subpasses for G-Buffer compositing
+*
+* Copyright (C) 2016-2017 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*
+* Summary:
+* Implements a deferred rendering setup with a forward transparency pass using sub passes
+*
+* Sub passes allow reading from the previous framebuffer (in the same render pass) at
+* the same pixel position.
+*
+* This is a feature that was especially designed for tile-based-renderers
+* (mostly mobile GPUs) and is a new optimization feature in Vulkan for those GPU types.
+*
+*/
+
+#include "vulkanexamplebase.h"
+#include "VulkanglTFModel.h"
+
+#define ENABLE_VALIDATION false
+
+#define NUM_LIGHTS 64
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	struct {
+		vks::Texture2D glass;
+	} textures;
+
+	struct {
+		vkglTF::Model scene;
+		vkglTF::Model transparent;
+	} models;
+
+	struct {
+		glm::mat4 projection;
+		glm::mat4 model;
+		glm::mat4 view;
+	} uboGBuffer;
+
+	struct Light {
+		glm::vec4 position;
+		glm::vec3 color;
+		float radius;
+	};
+
+	struct {
+		glm::vec4 viewPos;
+		Light lights[NUM_LIGHTS];
+	} uboLights;
+
+	struct {
+		vks::Buffer GBuffer;
+		vks::Buffer lights;
+	} uniformBuffers;
+
+	struct {
+		VkPipeline offscreen;
+		VkPipeline composition;
+		VkPipeline transparent;
+	} pipelines;
+
+	struct {
+		VkPipelineLayout offscreen;
+		VkPipelineLayout composition;
+		VkPipelineLayout transparent;
+	} pipelineLayouts;
+
+	struct {
+		VkDescriptorSet scene;
+		VkDescriptorSet composition;
+		VkDescriptorSet transparent;
+	} descriptorSets;
+
+	struct {
+		VkDescriptorSetLayout scene;
+		VkDescriptorSetLayout composition;
+		VkDescriptorSetLayout transparent;
+	} descriptorSetLayouts;
+
+	// G-Buffer framebuffer attachments
+	struct FrameBufferAttachment {
+		VkImage image = VK_NULL_HANDLE;
+		VkDeviceMemory mem = VK_NULL_HANDLE;
+		VkImageView view = VK_NULL_HANDLE;
+		VkFormat format;
+	};
+	struct Attachments {
+		FrameBufferAttachment position, normal, albedo;
+		int32_t width;
+		int32_t height;
+	} attachments;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Subpasses";
+		camera.type = Camera::CameraType::firstperson;
+		camera.movementSpeed = 5.0f;
+#ifndef __ANDROID__
+		camera.rotationSpeed = 0.25f;
+#endif
+		camera.setPosition(glm::vec3(-3.2f, 1.0f, 5.9f));
+		camera.setRotation(glm::vec3(0.5f, 210.05f, 0.0f));
+		camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 256.0f);
+		UIOverlay.subpass = 2;
+	}
+
+	~VulkanExample()
+	{
+		// Clean up used Vulkan resources
+		// Note : Inherited destructor cleans up resources stored in base class
+		vkDestroyPipeline(device, pipelines.offscreen, nullptr);
+		vkDestroyPipeline(device, pipelines.composition, nullptr);
+		vkDestroyPipeline(device, pipelines.transparent, nullptr);
+
+		vkDestroyPipelineLayout(device, pipelineLayouts.offscreen, nullptr);
+		vkDestroyPipelineLayout(device, pipelineLayouts.composition, nullptr);
+		vkDestroyPipelineLayout(device, pipelineLayouts.transparent, nullptr);
+
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.scene, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.composition, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.transparent, nullptr);
+
+		clearAttachment(&attachments.position);
+		clearAttachment(&attachments.normal);
+		clearAttachment(&attachments.albedo);
+
+		textures.glass.destroy();
+		uniformBuffers.GBuffer.destroy();
+		uniformBuffers.lights.destroy();
+	}
+
+	// Enable physical device features required for this example
+	virtual void getEnabledFeatures()
+	{
+		// Enable anisotropic filtering if supported
+		if (deviceFeatures.samplerAnisotropy) {
+			enabledFeatures.samplerAnisotropy = VK_TRUE;
+		}
+	};
+
+	void clearAttachment(FrameBufferAttachment* attachment)
+	{
+		vkDestroyImageView(device, attachment->view, nullptr);
+		vkDestroyImage(device, attachment->image, nullptr);
+		vkFreeMemory(device, attachment->mem, nullptr);
+	}
+
+	// Create a frame buffer attachment
+	void createAttachment(VkFormat format, VkImageUsageFlags usage, FrameBufferAttachment *attachment)
+	{
+		if (attachment->image != VK_NULL_HANDLE) {
+			clearAttachment(attachment);
+		}
+
+		VkImageAspectFlags aspectMask = 0;
+		VkImageLayout imageLayout;
+
+		attachment->format = format;
+
+		if (usage & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT)
+		{
+			aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+			imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+		}
+		if (usage & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT)
+		{
+			aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
+			imageLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+		}
+
+		assert(aspectMask > 0);
+
+		VkImageCreateInfo image = vks::initializers::imageCreateInfo();
+		image.imageType = VK_IMAGE_TYPE_2D;
+		image.format = format;
+		image.extent.width = attachments.width;
+		image.extent.height = attachments.height;
+		image.extent.depth = 1;
+		image.mipLevels = 1;
+		image.arrayLayers = 1;
+		image.samples = VK_SAMPLE_COUNT_1_BIT;
+		image.tiling = VK_IMAGE_TILING_OPTIMAL;
+		// VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT flag is required for input attachments
+		image.usage = usage | VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;
+		image.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+
+		VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo();
+		VkMemoryRequirements memReqs;
+
+		VK_CHECK_RESULT(vkCreateImage(device, &image, nullptr, &attachment->image));
+		vkGetImageMemoryRequirements(device, attachment->image, &memReqs);
+		memAlloc.allocationSize = memReqs.size;
+		memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+		VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &attachment->mem));
+		VK_CHECK_RESULT(vkBindImageMemory(device, attachment->image, attachment->mem, 0));
+
+		VkImageViewCreateInfo imageView = vks::initializers::imageViewCreateInfo();
+		imageView.viewType = VK_IMAGE_VIEW_TYPE_2D;
+		imageView.format = format;
+		imageView.subresourceRange = {};
+		imageView.subresourceRange.aspectMask = aspectMask;
+		imageView.subresourceRange.baseMipLevel = 0;
+		imageView.subresourceRange.levelCount = 1;
+		imageView.subresourceRange.baseArrayLayer = 0;
+		imageView.subresourceRange.layerCount = 1;
+		imageView.image = attachment->image;
+		VK_CHECK_RESULT(vkCreateImageView(device, &imageView, nullptr, &attachment->view));
+	}
+
+	// Create color attachments for the G-Buffer components
+	void createGBufferAttachments()
+	{
+		createAttachment(VK_FORMAT_R16G16B16A16_SFLOAT, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, &attachments.position);	// (World space) Positions
+		createAttachment(VK_FORMAT_R16G16B16A16_SFLOAT, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, &attachments.normal);		// (World space) Normals
+		createAttachment(VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, &attachments.albedo);			// Albedo (color)
+	}
+
+	// Override framebuffer setup from base class, will automatically be called upon setup and if a window is resized
+	void setupFrameBuffer()
+	{
+		// If the window is resized, all the framebuffers/attachments used in our composition passes need to be recreated
+		if (attachments.width != width || attachments.height != height) {
+			attachments.width = width;
+			attachments.height = height;
+			createGBufferAttachments();
+			// Since the framebuffers/attachments are referred in the descriptor sets, these need to be updated too
+			// Composition pass
+			std::vector< VkDescriptorImageInfo> descriptorImageInfos = {
+				vks::initializers::descriptorImageInfo(VK_NULL_HANDLE, attachments.position.view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL),
+				vks::initializers::descriptorImageInfo(VK_NULL_HANDLE, attachments.normal.view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL),
+				vks::initializers::descriptorImageInfo(VK_NULL_HANDLE, attachments.albedo.view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL),
+			};
+			std::vector<VkWriteDescriptorSet> writeDescriptorSets;
+			for (size_t i = 0; i < descriptorImageInfos.size(); i++) {
+				writeDescriptorSets.push_back(vks::initializers::writeDescriptorSet(descriptorSets.composition, VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, i, &descriptorImageInfos[i]));
+			}
+			vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr);
+			// Forward pass
+			writeDescriptorSets = {
+				vks::initializers::writeDescriptorSet(descriptorSets.transparent, VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 1, &descriptorImageInfos[0]),
+			};
+			vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr);
+		}
+
+		VkImageView attachments[5];
+
+		VkFramebufferCreateInfo frameBufferCreateInfo = {};
+		frameBufferCreateInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
+		frameBufferCreateInfo.renderPass = renderPass;
+		frameBufferCreateInfo.attachmentCount = 5;
+		frameBufferCreateInfo.pAttachments = attachments;
+		frameBufferCreateInfo.width = width;
+		frameBufferCreateInfo.height = height;
+		frameBufferCreateInfo.layers = 1;
+
+		// Create frame buffers for every swap chain image
+		frameBuffers.resize(swapChain.imageCount);
+		for (uint32_t i = 0; i < frameBuffers.size(); i++)
+		{
+			attachments[0] = swapChain.buffers[i].view;
+			attachments[1] = this->attachments.position.view;
+			attachments[2] = this->attachments.normal.view;
+			attachments[3] = this->attachments.albedo.view;
+			attachments[4] = depthStencil.view;
+			VK_CHECK_RESULT(vkCreateFramebuffer(device, &frameBufferCreateInfo, nullptr, &frameBuffers[i]));
+		}
+	}
+
+	// Override render pass setup from base class
+	void setupRenderPass()
+	{
+		attachments.width = width;
+		attachments.height = height;
+
+		createGBufferAttachments();
+
+		std::array<VkAttachmentDescription, 5> attachments{};
+		// Color attachment
+		attachments[0].format = swapChain.colorFormat;
+		attachments[0].samples = VK_SAMPLE_COUNT_1_BIT;
+		attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+		attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+		attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+		attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+		attachments[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+		attachments[0].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
+
+		// Deferred attachments
+		// Position
+		attachments[1].format = this->attachments.position.format;
+		attachments[1].samples = VK_SAMPLE_COUNT_1_BIT;
+		attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+		attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+		attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+		attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+		attachments[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+		attachments[1].finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+		// Normals
+		attachments[2].format = this->attachments.normal.format;
+		attachments[2].samples = VK_SAMPLE_COUNT_1_BIT;
+		attachments[2].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+		attachments[2].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+		attachments[2].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+		attachments[2].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+		attachments[2].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+		attachments[2].finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+		// Albedo
+		attachments[3].format = this->attachments.albedo.format;
+		attachments[3].samples = VK_SAMPLE_COUNT_1_BIT;
+		attachments[3].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+		attachments[3].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+		attachments[3].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+		attachments[3].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+		attachments[3].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+		attachments[3].finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+		// Depth attachment
+		attachments[4].format = depthFormat;
+		attachments[4].samples = VK_SAMPLE_COUNT_1_BIT;
+		attachments[4].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+		attachments[4].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+		attachments[4].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+		attachments[4].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+		attachments[4].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+		attachments[4].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+
+		// Three subpasses
+		std::array<VkSubpassDescription,3> subpassDescriptions{};
+
+		// First subpass: Fill G-Buffer components
+		// ----------------------------------------------------------------------------------------
+
+		VkAttachmentReference colorReferences[4];
+		colorReferences[0] = { 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
+		colorReferences[1] = { 1, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
+		colorReferences[2] = { 2, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
+		colorReferences[3] = { 3, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
+		VkAttachmentReference depthReference = { 4, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL };
+
+		subpassDescriptions[0].pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+		subpassDescriptions[0].colorAttachmentCount = 4;
+		subpassDescriptions[0].pColorAttachments = colorReferences;
+		subpassDescriptions[0].pDepthStencilAttachment = &depthReference;
+
+		// Second subpass: Final composition (using G-Buffer components)
+		// ----------------------------------------------------------------------------------------
+
+		VkAttachmentReference colorReference = { 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
+
+		VkAttachmentReference inputReferences[3];
+		inputReferences[0] = { 1, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL };
+		inputReferences[1] = { 2, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL };
+		inputReferences[2] = { 3, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL };
+
+		uint32_t preserveAttachmentIndex = 1;
+
+		subpassDescriptions[1].pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+		subpassDescriptions[1].colorAttachmentCount = 1;
+		subpassDescriptions[1].pColorAttachments = &colorReference;
+		subpassDescriptions[1].pDepthStencilAttachment = &depthReference;
+		// Use the color attachments filled in the first pass as input attachments
+		subpassDescriptions[1].inputAttachmentCount = 3;
+		subpassDescriptions[1].pInputAttachments = inputReferences;
+
+		// Third subpass: Forward transparency
+		// ----------------------------------------------------------------------------------------
+		colorReference = { 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
+
+		inputReferences[0] = { 1, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL };
+
+		subpassDescriptions[2].pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+		subpassDescriptions[2].colorAttachmentCount = 1;
+		subpassDescriptions[2].pColorAttachments = &colorReference;
+		subpassDescriptions[2].pDepthStencilAttachment = &depthReference;
+		// Use the color/depth attachments filled in the first pass as input attachments
+		subpassDescriptions[2].inputAttachmentCount = 1;
+		subpassDescriptions[2].pInputAttachments = inputReferences;
+
+		// Subpass dependencies for layout transitions
+		std::array<VkSubpassDependency, 4> dependencies;
+
+		dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
+		dependencies[0].dstSubpass = 0;
+		dependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+		dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+		dependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
+		dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+		dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+		// This dependency transitions the input attachment from color attachment to shader read
+		dependencies[1].srcSubpass = 0;
+		dependencies[1].dstSubpass = 1;
+		dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+		dependencies[1].dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
+		dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+		dependencies[1].dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
+		dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+		dependencies[2].srcSubpass = 1;
+		dependencies[2].dstSubpass = 2;
+		dependencies[2].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+		dependencies[2].dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
+		dependencies[2].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+		dependencies[2].dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
+		dependencies[2].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+		dependencies[3].srcSubpass = 2;
+		dependencies[3].dstSubpass = VK_SUBPASS_EXTERNAL;
+		dependencies[3].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+		dependencies[3].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+		dependencies[3].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+		dependencies[3].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
+		dependencies[3].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+		VkRenderPassCreateInfo renderPassInfo = {};
+		renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
+		renderPassInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
+		renderPassInfo.pAttachments = attachments.data();
+		renderPassInfo.subpassCount = static_cast<uint32_t>(subpassDescriptions.size());
+		renderPassInfo.pSubpasses = subpassDescriptions.data();
+		renderPassInfo.dependencyCount = static_cast<uint32_t>(dependencies.size());
+		renderPassInfo.pDependencies = dependencies.data();
+
+		VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass));
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[5];
+		clearValues[0].color = { { 0.0f, 0.0f, 0.0f, 0.0f } };
+		clearValues[1].color = { { 0.0f, 0.0f, 0.0f, 0.0f } };
+		clearValues[2].color = { { 0.0f, 0.0f, 0.0f, 0.0f } };
+		clearValues[3].color = { { 0.0f, 0.0f, 0.0f, 0.0f } };
+		clearValues[4].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.offset.x = 0;
+		renderPassBeginInfo.renderArea.offset.y = 0;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		renderPassBeginInfo.clearValueCount = 5;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			// Set target frame buffer
+			renderPassBeginInfo.framebuffer = frameBuffers[i];
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+			VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+			VkDeviceSize offsets[1] = { 0 };
+
+			// First sub pass
+			// Renders the components of the scene to the G-Buffer attachments
+			{
+				vks::debugmarker::beginRegion(drawCmdBuffers[i], "Subpass 0: Deferred G-Buffer creation", glm::vec4(1.0f, 1.0f, 1.0f, 1.0f));
+
+				vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.offscreen);
+				vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayouts.offscreen, 0, 1, &descriptorSets.scene, 0, NULL);
+				models.scene.draw(drawCmdBuffers[i]);
+
+				vks::debugmarker::endRegion(drawCmdBuffers[i]);
+			}
+
+			// Second sub pass
+			// This subpass will use the G-Buffer components that have been filled in the first subpass as input attachment for the final compositing
+			{
+				vks::debugmarker::beginRegion(drawCmdBuffers[i], "Subpass 1: Deferred composition", glm::vec4(1.0f, 1.0f, 1.0f, 1.0f));
+
+				vkCmdNextSubpass(drawCmdBuffers[i], VK_SUBPASS_CONTENTS_INLINE);
+
+				vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.composition);
+				vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayouts.composition, 0, 1, &descriptorSets.composition, 0, NULL);
+				vkCmdDraw(drawCmdBuffers[i], 3, 1, 0, 0);
+
+				vks::debugmarker::endRegion(drawCmdBuffers[i]);
+			}
+
+			// Third subpass
+			// Render transparent geometry using a forward pass that compares against depth generated during G-Buffer fill
+			{
+				vks::debugmarker::beginRegion(drawCmdBuffers[i], "Subpass 2: Forward transparency", glm::vec4(1.0f, 1.0f, 1.0f, 1.0f));
+
+				vkCmdNextSubpass(drawCmdBuffers[i], VK_SUBPASS_CONTENTS_INLINE);
+
+				vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.transparent);
+				vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayouts.transparent, 0, 1, &descriptorSets.transparent, 0, NULL);
+				models.transparent.draw(drawCmdBuffers[i]);
+
+				vks::debugmarker::endRegion(drawCmdBuffers[i]);
+			}
+
+			drawUI(drawCmdBuffers[i]);
+
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void loadAssets()
+	{
+		const uint32_t glTFLoadingFlags = vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::PreMultiplyVertexColors | vkglTF::FileLoadingFlags::FlipY;
+		models.scene.loadFromFile(getAssetPath() + "models/samplebuilding.gltf", vulkanDevice, queue, glTFLoadingFlags);
+		models.transparent.loadFromFile(getAssetPath() + "models/samplebuilding_glass.gltf", vulkanDevice, queue, glTFLoadingFlags);
+		textures.glass.loadFromFile(getAssetPath() + "textures/colored_glass_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue);
+	}
+
+	void setupDescriptorPool()
+	{
+		std::vector<VkDescriptorPoolSize> poolSizes =
+		{
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 4),
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 4),
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 4),
+		};
+
+		VkDescriptorPoolCreateInfo descriptorPoolInfo =
+			vks::initializers::descriptorPoolCreateInfo(
+				static_cast<uint32_t>(poolSizes.size()),
+				poolSizes.data(),
+				4);
+
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+	}
+
+	void setupDescriptorSetLayout()
+	{
+		// Deferred shading layout
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings =
+		{
+			// Binding 0 : Vertex shader uniform buffer
+			vks::initializers::descriptorSetLayoutBinding(
+				VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+				VK_SHADER_STAGE_VERTEX_BIT,
+				0)
+		};
+
+		VkDescriptorSetLayoutCreateInfo descriptorLayout =
+			vks::initializers::descriptorSetLayoutCreateInfo(
+				setLayoutBindings.data(),
+				static_cast<uint32_t>(setLayoutBindings.size()));
+
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayouts.scene));
+
+		VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo =
+			vks::initializers::pipelineLayoutCreateInfo(
+				&descriptorSetLayouts.scene,
+				1);
+
+		// Offscreen (scene) rendering pipeline layout
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayouts.offscreen));
+	}
+
+	void setupDescriptorSet()
+	{
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets;
+
+		VkDescriptorSetAllocateInfo allocInfo =
+			vks::initializers::descriptorSetAllocateInfo(
+				descriptorPool,
+				&descriptorSetLayouts.scene,
+				1);
+
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.scene));
+		writeDescriptorSets =
+		{
+			// Binding 0: Vertex shader uniform buffer
+			vks::initializers::writeDescriptorSet(
+				descriptorSets.scene,
+				VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+				0,
+				&uniformBuffers.GBuffer.descriptor)
+		};
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL);
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+		VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
+		std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
+		VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+
+		// Final fullscreen pass pipeline
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayouts.offscreen, renderPass, 0);
+		pipelineCI.pInputAssemblyState = &inputAssemblyState;
+		pipelineCI.pRasterizationState = &rasterizationState;
+		pipelineCI.pColorBlendState = &colorBlendState;
+		pipelineCI.pMultisampleState = &multisampleState;
+		pipelineCI.pViewportState = &viewportState;
+		pipelineCI.pDepthStencilState = &depthStencilState;
+		pipelineCI.pDynamicState = &dynamicState;
+		pipelineCI.stageCount = static_cast<uint32_t>(shaderStages.size());
+		pipelineCI.pStages = shaderStages.data();
+		pipelineCI.subpass = 0;
+		pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Color, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::UV});
+
+		std::array<VkPipelineColorBlendAttachmentState, 4> blendAttachmentStates = {
+			vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE),
+			vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE),
+			vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE),
+			vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE)
+		};
+
+		colorBlendState.attachmentCount = static_cast<uint32_t>(blendAttachmentStates.size());
+		colorBlendState.pAttachments = blendAttachmentStates.data();
+
+		// Offscreen scene rendering pipeline
+		shaderStages[0] = loadShader(getShadersPath() + "subpasses/gbuffer.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "subpasses/gbuffer.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.offscreen));
+	}
+
+	// Create the Vulkan objects used in the composition pass (descriptor sets, pipelines, etc.)
+	void prepareCompositionPass()
+	{
+		// Descriptor set layout
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings =
+		{
+			// Binding 0: Position input attachment
+			vks::initializers::descriptorSetLayoutBinding(
+				VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT,
+				VK_SHADER_STAGE_FRAGMENT_BIT,
+				0),
+			// Binding 1: Normal input attachment
+			vks::initializers::descriptorSetLayoutBinding(
+				VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT,
+				VK_SHADER_STAGE_FRAGMENT_BIT,
+				1),
+			// Binding 2: Albedo input attachment
+			vks::initializers::descriptorSetLayoutBinding(
+				VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT,
+				VK_SHADER_STAGE_FRAGMENT_BIT,
+				2),
+			// Binding 3: Light positions
+			vks::initializers::descriptorSetLayoutBinding(
+				VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+				VK_SHADER_STAGE_FRAGMENT_BIT,
+				3),
+		};
+
+		VkDescriptorSetLayoutCreateInfo descriptorLayout =
+			vks::initializers::descriptorSetLayoutCreateInfo(
+				setLayoutBindings.data(),
+				static_cast<uint32_t>(setLayoutBindings.size()));
+
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayouts.composition));
+
+		// Pipeline layout
+		VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo =
+			vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayouts.composition, 1);
+
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayouts.composition));
+
+		// Descriptor sets
+		VkDescriptorSetAllocateInfo allocInfo =
+			vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.composition, 1);
+
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.composition));
+
+		// Image descriptors for the offscreen color attachments
+		VkDescriptorImageInfo texDescriptorPosition = vks::initializers::descriptorImageInfo(VK_NULL_HANDLE, attachments.position.view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+		VkDescriptorImageInfo texDescriptorNormal = vks::initializers::descriptorImageInfo(VK_NULL_HANDLE, attachments.normal.view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+		VkDescriptorImageInfo texDescriptorAlbedo = vks::initializers::descriptorImageInfo(VK_NULL_HANDLE, attachments.albedo.view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets = {
+			// Binding 0: Position texture target
+			vks::initializers::writeDescriptorSet(descriptorSets.composition, VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 0, &texDescriptorPosition),
+			// Binding 1: Normals texture target
+			vks::initializers::writeDescriptorSet(descriptorSets.composition, VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 1, &texDescriptorNormal),
+			// Binding 2: Albedo texture target
+			vks::initializers::writeDescriptorSet(descriptorSets.composition, VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 2, &texDescriptorAlbedo),
+			// Binding 4: Fragment shader lights
+			vks::initializers::writeDescriptorSet(descriptorSets.composition, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 3, &uniformBuffers.lights.descriptor),
+		};
+
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL);
+
+		// Pipeline
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1,	&blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+		VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
+		std::vector<VkDynamicState> dynamicStateEnables = {	VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
+		VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+
+		shaderStages[0] = loadShader(getShadersPath() + "subpasses/composition.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "subpasses/composition.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+
+		// Use specialization constants to pass number of lights to the shader
+		VkSpecializationMapEntry specializationEntry{};
+		specializationEntry.constantID = 0;
+		specializationEntry.offset = 0;
+		specializationEntry.size = sizeof(uint32_t);
+
+		uint32_t specializationData = NUM_LIGHTS;
+
+		VkSpecializationInfo specializationInfo;
+		specializationInfo.mapEntryCount = 1;
+		specializationInfo.pMapEntries = &specializationEntry;
+		specializationInfo.dataSize = sizeof(specializationData);
+		specializationInfo.pData = &specializationData;
+
+		shaderStages[1].pSpecializationInfo = &specializationInfo;
+
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayouts.composition, renderPass, 0);
+
+		VkPipelineVertexInputStateCreateInfo emptyInputState{};
+		emptyInputState.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
+
+		pipelineCI.pVertexInputState = &emptyInputState;
+		pipelineCI.pInputAssemblyState = &inputAssemblyState;
+		pipelineCI.pRasterizationState = &rasterizationState;
+		pipelineCI.pColorBlendState = &colorBlendState;
+		pipelineCI.pMultisampleState = &multisampleState;
+		pipelineCI.pViewportState = &viewportState;
+		pipelineCI.pDepthStencilState = &depthStencilState;
+		pipelineCI.pDynamicState = &dynamicState;
+		pipelineCI.stageCount = static_cast<uint32_t>(shaderStages.size());
+		pipelineCI.pStages = shaderStages.data();
+		// Index of the subpass that this pipeline will be used in
+		pipelineCI.subpass = 1;
+
+		depthStencilState.depthWriteEnable = VK_FALSE;
+
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.composition));
+
+		// Transparent (forward) pipeline
+
+		// Descriptor set layout
+		setLayoutBindings = {
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0),
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, VK_SHADER_STAGE_FRAGMENT_BIT, 1),
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 2),
+		};
+
+		descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings.data(), static_cast<uint32_t>(setLayoutBindings.size()));
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayouts.transparent));
+
+		// Pipeline layout
+		pPipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayouts.transparent, 1);
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayouts.transparent));
+
+		// Descriptor sets
+		allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.transparent, 1);
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.transparent));
+
+		writeDescriptorSets = {
+			vks::initializers::writeDescriptorSet(descriptorSets.transparent, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.GBuffer.descriptor),
+			vks::initializers::writeDescriptorSet(descriptorSets.transparent, VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 1, &texDescriptorPosition),
+			vks::initializers::writeDescriptorSet(descriptorSets.transparent, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2, &textures.glass.descriptor),
+		};
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL);
+
+		// Enable blending
+		blendAttachmentState.blendEnable = VK_TRUE;
+		blendAttachmentState.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
+		blendAttachmentState.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
+		blendAttachmentState.colorBlendOp = VK_BLEND_OP_ADD;
+		blendAttachmentState.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
+		blendAttachmentState.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
+		blendAttachmentState.alphaBlendOp = VK_BLEND_OP_ADD;
+		blendAttachmentState.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
+
+		pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Color, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::UV});
+		pipelineCI.layout            = pipelineLayouts.transparent;
+		pipelineCI.subpass           = 2;
+
+		shaderStages[0] = loadShader(getShadersPath() + "subpasses/transparent.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "subpasses/transparent.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.transparent));
+	}
+
+	// Prepare and initialize uniform buffer containing shader uniforms
+	void prepareUniformBuffers()
+	{
+		// Deferred vertex shader
+		vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.GBuffer,
+			sizeof(uboGBuffer));
+
+		// Deferred fragment shader
+		vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.lights,
+			sizeof(uboLights));
+
+		// Update
+		updateUniformBufferDeferredMatrices();
+		updateUniformBufferDeferredLights();
+	}
+
+	void updateUniformBufferDeferredMatrices()
+	{
+		uboGBuffer.projection = camera.matrices.perspective;
+		uboGBuffer.view = camera.matrices.view;
+		uboGBuffer.model = glm::mat4(1.0f);
+
+		VK_CHECK_RESULT(uniformBuffers.GBuffer.map());
+		memcpy(uniformBuffers.GBuffer.mapped, &uboGBuffer, sizeof(uboGBuffer));
+		uniformBuffers.GBuffer.unmap();
+	}
+
+	void initLights()
+	{
+		std::vector<glm::vec3> colors =
+		{
+			glm::vec3(1.0f, 1.0f, 1.0f),
+			glm::vec3(1.0f, 0.0f, 0.0f),
+			glm::vec3(0.0f, 1.0f, 0.0f),
+			glm::vec3(0.0f, 0.0f, 1.0f),
+			glm::vec3(1.0f, 1.0f, 0.0f),
+		};
+
+		std::default_random_engine rndGen(benchmark.active ? 0 : (unsigned)time(nullptr));
+		std::uniform_real_distribution<float> rndDist(-1.0f, 1.0f);
+		std::uniform_int_distribution<uint32_t> rndCol(0, static_cast<uint32_t>(colors.size()-1));
+
+		for (auto& light : uboLights.lights)
+		{
+			light.position = glm::vec4(rndDist(rndGen) * 6.0f, 0.25f + std::abs(rndDist(rndGen)) * 4.0f, rndDist(rndGen) * 6.0f, 1.0f);
+			light.color = colors[rndCol(rndGen)];
+			light.radius = 1.0f + std::abs(rndDist(rndGen));
+		}
+	}
+
+	// Update fragment shader light position uniform block
+	void updateUniformBufferDeferredLights()
+	{
+		// Current view position
+		uboLights.viewPos = glm::vec4(camera.position, 0.0f) * glm::vec4(-1.0f, 1.0f, -1.0f, 1.0f);
+
+		VK_CHECK_RESULT(uniformBuffers.lights.map());
+		memcpy(uniformBuffers.lights.mapped, &uboLights, sizeof(uboLights));
+		uniformBuffers.lights.unmap();
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+
+		// Command buffer to be submitted to the queue
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+
+		// Submit to queue
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+
+		VulkanExampleBase::submitFrame();
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		loadAssets();
+		initLights();
+		prepareUniformBuffers();
+		setupDescriptorSetLayout();
+		preparePipelines();
+		setupDescriptorPool();
+		setupDescriptorSet();
+		prepareCompositionPass();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+		if (camera.updated) {
+			updateUniformBufferDeferredMatrices();
+			updateUniformBufferDeferredLights();
+		}
+	}
+
+	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
+	{
+		if (overlay->header("Subpasses")) {
+			overlay->text("0: Deferred G-Buffer creation");
+			overlay->text("1: Deferred composition");
+			overlay->text("2: Forward transparency");
+		}
+		if (overlay->header("Settings")) {
+			if (overlay->button("Randomize lights")) {
+				initLights();
+				updateUniformBufferDeferredLights();
+			}
+		}
+	}
+};
+
+VULKAN_EXAMPLE_MAIN()
diff --git a/external/Vulkan/examples/terraintessellation/terraintessellation.cpp b/external/Vulkan/examples/terraintessellation/terraintessellation.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..80a79e276f31a1778852aa4ebaa29144e78323d1
--- /dev/null
+++ b/external/Vulkan/examples/terraintessellation/terraintessellation.cpp
@@ -0,0 +1,856 @@
+/*
+* Vulkan Example - Dynamic terrain tessellation
+*
+* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkanexamplebase.h"
+#include "VulkanglTFModel.h"
+#include "frustum.hpp"
+#include <ktx.h>
+#include <ktxvulkan.h>
+
+#define ENABLE_VALIDATION false
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	bool wireframe = false;
+	bool tessellation = true;
+
+	// Holds the buffers for rendering the tessellated terrain
+	struct {
+		struct Vertices {
+			VkBuffer buffer;
+			VkDeviceMemory memory;
+		} vertices;
+		struct Indices {
+			int count;
+			VkBuffer buffer;
+			VkDeviceMemory memory;
+		} indices;
+	} terrain;
+
+	struct {
+		vks::Texture2D heightMap;
+		vks::Texture2D skySphere;
+		vks::Texture2DArray terrainArray;
+	} textures;
+
+	struct {
+		vkglTF::Model skysphere;
+	} models;
+
+	struct {
+		vks::Buffer terrainTessellation;
+		vks::Buffer skysphereVertex;
+	} uniformBuffers;
+
+	// Shared values for tessellation control and evaluation stages
+	struct {
+		glm::mat4 projection;
+		glm::mat4 modelview;
+		glm::vec4 lightPos = glm::vec4(-48.0f, -40.0f, 46.0f, 0.0f);
+		glm::vec4 frustumPlanes[6];
+		float displacementFactor = 32.0f;
+		float tessellationFactor = 0.75f;
+		glm::vec2 viewportDim;
+		// Desired size of tessellated quad patch edge
+		float tessellatedEdgeSize = 20.0f;
+	} uboTess;
+
+	// Skysphere vertex shader stage
+	struct {
+		glm::mat4 mvp;
+	} uboVS;
+
+	struct Pipelines {
+		VkPipeline terrain;
+		VkPipeline wireframe = VK_NULL_HANDLE;
+		VkPipeline skysphere;
+	} pipelines;
+
+	struct {
+		VkDescriptorSetLayout terrain;
+		VkDescriptorSetLayout skysphere;
+	} descriptorSetLayouts;
+
+	struct {
+		VkPipelineLayout terrain;
+		VkPipelineLayout skysphere;
+	} pipelineLayouts;
+
+	struct {
+		VkDescriptorSet terrain;
+		VkDescriptorSet skysphere;
+	} descriptorSets;
+
+	// Pipeline statistics
+	struct {
+		VkBuffer buffer;
+		VkDeviceMemory memory;
+	} queryResult;
+	VkQueryPool queryPool = VK_NULL_HANDLE;
+	uint64_t pipelineStats[2] = { 0 };
+
+	// View frustum passed to tessellation control shader for culling
+	vks::Frustum frustum;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Dynamic terrain tessellation";
+		camera.type = Camera::CameraType::firstperson;
+		camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 512.0f);
+		camera.setRotation(glm::vec3(-12.0f, 159.0f, 0.0f));
+		camera.setTranslation(glm::vec3(18.0f, 22.5f, 57.5f));
+		camera.movementSpeed = 7.5f;
+	}
+
+	~VulkanExample()
+	{
+		// Clean up used Vulkan resources
+		// Note : Inherited destructor cleans up resources stored in base class
+		vkDestroyPipeline(device, pipelines.terrain, nullptr);
+		if (pipelines.wireframe != VK_NULL_HANDLE) {
+			vkDestroyPipeline(device, pipelines.wireframe, nullptr);
+		}
+		vkDestroyPipeline(device, pipelines.skysphere, nullptr);
+
+		vkDestroyPipelineLayout(device, pipelineLayouts.skysphere, nullptr);
+		vkDestroyPipelineLayout(device, pipelineLayouts.terrain, nullptr);
+
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.terrain, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.skysphere, nullptr);
+
+		uniformBuffers.skysphereVertex.destroy();
+		uniformBuffers.terrainTessellation.destroy();
+
+		textures.heightMap.destroy();
+		textures.skySphere.destroy();
+		textures.terrainArray.destroy();
+
+		vkDestroyBuffer(device, terrain.vertices.buffer, nullptr);
+		vkFreeMemory(device, terrain.vertices.memory, nullptr);
+		vkDestroyBuffer(device, terrain.indices.buffer, nullptr);
+		vkFreeMemory(device, terrain.indices.memory, nullptr);
+
+		if (queryPool != VK_NULL_HANDLE) {
+			vkDestroyQueryPool(device, queryPool, nullptr);
+			vkDestroyBuffer(device, queryResult.buffer, nullptr);
+			vkFreeMemory(device, queryResult.memory, nullptr);
+		}
+	}
+
+	// Enable physical device features required for this example
+	virtual void getEnabledFeatures()
+	{
+		// Tessellation shader support is required for this example
+		if (deviceFeatures.tessellationShader) {
+			enabledFeatures.tessellationShader = VK_TRUE;
+		}
+		else {
+			vks::tools::exitFatal("Selected GPU does not support tessellation shaders!", VK_ERROR_FEATURE_NOT_PRESENT);
+		}
+		// Fill mode non solid is required for wireframe display
+		if (deviceFeatures.fillModeNonSolid) {
+			enabledFeatures.fillModeNonSolid = VK_TRUE;
+		};
+		// Pipeline statistics
+		if (deviceFeatures.pipelineStatisticsQuery) {
+			enabledFeatures.pipelineStatisticsQuery = VK_TRUE;
+		};
+		// Enable anisotropic filtering if supported
+		if (deviceFeatures.samplerAnisotropy) {
+			enabledFeatures.samplerAnisotropy = VK_TRUE;
+		}
+		// Enable texture compression
+		if (deviceFeatures.textureCompressionBC) {
+			enabledFeatures.textureCompressionBC = VK_TRUE;
+		}
+		else if (deviceFeatures.textureCompressionASTC_LDR) {
+			enabledFeatures.textureCompressionASTC_LDR = VK_TRUE;
+		}
+		else if (deviceFeatures.textureCompressionETC2) {
+			enabledFeatures.textureCompressionETC2 = VK_TRUE;
+		}
+	}
+
+	// Setup pool and buffer for storing pipeline statistics results
+	void setupQueryResultBuffer()
+	{
+		uint32_t bufSize = 2 * sizeof(uint64_t);
+
+		VkMemoryRequirements memReqs;
+		VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo();
+		VkBufferCreateInfo bufferCreateInfo =
+			vks::initializers::bufferCreateInfo(
+				VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
+				bufSize);
+
+		// Results are saved in a host visible buffer for easy access by the application
+		VK_CHECK_RESULT(vkCreateBuffer(device, &bufferCreateInfo, nullptr, &queryResult.buffer));
+		vkGetBufferMemoryRequirements(device, queryResult.buffer, &memReqs);
+		memAlloc.allocationSize = memReqs.size;
+		memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
+		VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &queryResult.memory));
+		VK_CHECK_RESULT(vkBindBufferMemory(device, queryResult.buffer, queryResult.memory, 0));
+
+		// Create query pool
+		if (deviceFeatures.pipelineStatisticsQuery) {
+			VkQueryPoolCreateInfo queryPoolInfo = {};
+			queryPoolInfo.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO;
+			queryPoolInfo.queryType = VK_QUERY_TYPE_PIPELINE_STATISTICS;
+			queryPoolInfo.pipelineStatistics =
+				VK_QUERY_PIPELINE_STATISTIC_VERTEX_SHADER_INVOCATIONS_BIT |
+				VK_QUERY_PIPELINE_STATISTIC_TESSELLATION_EVALUATION_SHADER_INVOCATIONS_BIT;
+			queryPoolInfo.queryCount = 2;
+			VK_CHECK_RESULT(vkCreateQueryPool(device, &queryPoolInfo, NULL, &queryPool));
+		}
+	}
+
+	// Retrieves the results of the pipeline statistics query submitted to the command buffer
+	void getQueryResults()
+	{
+		// We use vkGetQueryResults to copy the results into a host visible buffer
+		vkGetQueryPoolResults(
+			device,
+			queryPool,
+			0,
+			1,
+			sizeof(pipelineStats),
+			pipelineStats,
+			sizeof(uint64_t),
+			VK_QUERY_RESULT_64_BIT);
+	}
+
+	void loadAssets()
+	{
+		const uint32_t glTFLoadingFlags = vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::PreMultiplyVertexColors | vkglTF::FileLoadingFlags::FlipY;
+		models.skysphere.loadFromFile(getAssetPath() + "models/sphere.gltf", vulkanDevice, queue, glTFLoadingFlags);
+
+		textures.skySphere.loadFromFile(getAssetPath() + "textures/skysphere_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue);
+		// Terrain textures are stored in a texture array with layers corresponding to terrain height
+		textures.terrainArray.loadFromFile(getAssetPath() + "textures/terrain_texturearray_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue);
+
+		// Height data is stored in a one-channel texture
+		textures.heightMap.loadFromFile(getAssetPath() + "textures/terrain_heightmap_r16.ktx", VK_FORMAT_R16_UNORM, vulkanDevice, queue);
+
+		VkSamplerCreateInfo samplerInfo = vks::initializers::samplerCreateInfo();
+
+		// Setup a mirroring sampler for the height map
+		vkDestroySampler(device, textures.heightMap.sampler, nullptr);
+		samplerInfo.magFilter = VK_FILTER_LINEAR;
+		samplerInfo.minFilter = VK_FILTER_LINEAR;
+		samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+		samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
+		samplerInfo.addressModeV = samplerInfo.addressModeU;
+		samplerInfo.addressModeW = samplerInfo.addressModeU;
+		samplerInfo.compareOp = VK_COMPARE_OP_NEVER;
+		samplerInfo.minLod = 0.0f;
+		samplerInfo.maxLod = (float)textures.heightMap.mipLevels;
+		samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
+		VK_CHECK_RESULT(vkCreateSampler(device, &samplerInfo, nullptr, &textures.heightMap.sampler));
+		textures.heightMap.descriptor.sampler = textures.heightMap.sampler;
+
+		// Setup a repeating sampler for the terrain texture layers
+		vkDestroySampler(device, textures.terrainArray.sampler, nullptr);
+		samplerInfo = vks::initializers::samplerCreateInfo();
+		samplerInfo.magFilter = VK_FILTER_LINEAR;
+		samplerInfo.minFilter = VK_FILTER_LINEAR;
+		samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+		samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+		samplerInfo.addressModeV = samplerInfo.addressModeU;
+		samplerInfo.addressModeW = samplerInfo.addressModeU;
+		samplerInfo.compareOp = VK_COMPARE_OP_NEVER;
+		samplerInfo.minLod = 0.0f;
+		samplerInfo.maxLod = (float)textures.terrainArray.mipLevels;
+		samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
+		if (deviceFeatures.samplerAnisotropy)
+		{
+			samplerInfo.maxAnisotropy = 4.0f;
+			samplerInfo.anisotropyEnable = VK_TRUE;
+		}
+		VK_CHECK_RESULT(vkCreateSampler(device, &samplerInfo, nullptr, &textures.terrainArray.sampler));
+		textures.terrainArray.descriptor.sampler = textures.terrainArray.sampler;
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		clearValues[0].color = defaultClearColor;
+		clearValues[1].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.offset.x = 0;
+		renderPassBeginInfo.renderArea.offset.y = 0;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		renderPassBeginInfo.clearValueCount = 2;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			renderPassBeginInfo.framebuffer = frameBuffers[i];
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			if (deviceFeatures.pipelineStatisticsQuery) {
+				vkCmdResetQueryPool(drawCmdBuffers[i], queryPool, 0, 2);
+			}
+
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+			VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+			vkCmdSetLineWidth(drawCmdBuffers[i], 1.0f);
+
+			VkDeviceSize offsets[1] = { 0 };
+
+			// Skysphere
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.skysphere);
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayouts.skysphere, 0, 1, &descriptorSets.skysphere, 0, nullptr);
+			models.skysphere.draw(drawCmdBuffers[i]);
+
+			// Tessellated terrain
+			if (deviceFeatures.pipelineStatisticsQuery) {
+				// Begin pipeline statistics query
+				vkCmdBeginQuery(drawCmdBuffers[i], queryPool, 0, 0);
+			}
+			// Render
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, wireframe ? pipelines.wireframe : pipelines.terrain);
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayouts.terrain, 0, 1, &descriptorSets.terrain, 0, nullptr);
+			vkCmdBindVertexBuffers(drawCmdBuffers[i], 0, 1, &terrain.vertices.buffer, offsets);
+			vkCmdBindIndexBuffer(drawCmdBuffers[i], terrain.indices.buffer, 0, VK_INDEX_TYPE_UINT32);
+			vkCmdDrawIndexed(drawCmdBuffers[i], terrain.indices.count, 1, 0, 0, 0);
+			if (deviceFeatures.pipelineStatisticsQuery) {
+				// End pipeline statistics query
+				vkCmdEndQuery(drawCmdBuffers[i], queryPool, 0);
+			}
+
+			drawUI(drawCmdBuffers[i]);
+
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	// Encapsulate height map data for easy sampling
+	struct HeightMap
+	{
+	private:
+		uint16_t *heightdata;
+		uint32_t dim;
+		uint32_t scale;
+	public:
+#if defined(__ANDROID__)
+		HeightMap(std::string filename, uint32_t patchsize, AAssetManager* assetManager)
+#else
+		HeightMap(std::string filename, uint32_t patchsize)
+#endif
+		{
+			ktxResult result;
+			ktxTexture* ktxTexture;
+#if defined(__ANDROID__)
+			AAsset* asset = AAssetManager_open(androidApp->activity->assetManager, filename.c_str(), AASSET_MODE_STREAMING);
+			assert(asset);
+			size_t size = AAsset_getLength(asset);
+			assert(size > 0);
+			ktx_uint8_t* textureData = new ktx_uint8_t[size];
+			AAsset_read(asset, textureData, size);
+			AAsset_close(asset);
+			result = ktxTexture_CreateFromMemory(textureData, size, KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, &ktxTexture);
+			delete[] textureData;
+
+#else
+			result = ktxTexture_CreateFromNamedFile(filename.c_str(), KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, &ktxTexture);
+#endif
+			assert(result == KTX_SUCCESS);
+			ktx_size_t ktxSize = ktxTexture_GetImageSize(ktxTexture, 0);
+			ktx_uint8_t* ktxImage = ktxTexture_GetData(ktxTexture);
+			dim = ktxTexture->baseWidth;
+			heightdata = new uint16_t[dim * dim];
+			memcpy(heightdata, ktxImage, ktxSize);
+			this->scale = dim / patchsize;
+			ktxTexture_Destroy(ktxTexture);
+		};
+
+		~HeightMap()
+		{
+			delete[] heightdata;
+		}
+
+		float getHeight(uint32_t x, uint32_t y)
+		{
+			glm::ivec2 rpos = glm::ivec2(x, y) * glm::ivec2(scale);
+			rpos.x = std::max(0, std::min(rpos.x, (int)dim-1));
+			rpos.y = std::max(0, std::min(rpos.y, (int)dim-1));
+			rpos /= glm::ivec2(scale);
+			return *(heightdata + (rpos.x + rpos.y * dim) * scale) / 65535.0f;
+		}
+	};
+
+	// Generate a terrain quad patch for feeding to the tessellation control shader
+	void generateTerrain()
+	{
+		#define PATCH_SIZE 64
+		#define UV_SCALE 1.0f
+
+		const uint32_t vertexCount = PATCH_SIZE * PATCH_SIZE;
+		// We use the Vertex definition from the glTF model loader, so we can re-use the vertex input state
+		vkglTF::Vertex *vertices = new vkglTF::Vertex[vertexCount];
+
+		const float wx = 2.0f;
+		const float wy = 2.0f;
+
+		for (auto x = 0; x < PATCH_SIZE; x++)
+		{
+			for (auto y = 0; y < PATCH_SIZE; y++)
+			{
+				uint32_t index = (x + y * PATCH_SIZE);
+				vertices[index].pos[0] = x * wx + wx / 2.0f - (float)PATCH_SIZE * wx / 2.0f;
+				vertices[index].pos[1] = 0.0f;
+				vertices[index].pos[2] = y * wy + wy / 2.0f - (float)PATCH_SIZE * wy / 2.0f;
+				vertices[index].uv = glm::vec2((float)x / PATCH_SIZE, (float)y / PATCH_SIZE) * UV_SCALE;
+			}
+		}
+
+		// Calculate normals from height map using a sobel filter
+#if defined(__ANDROID__)
+		HeightMap heightMap(getAssetPath() + "textures/terrain_heightmap_r16.ktx", PATCH_SIZE, androidApp->activity->assetManager);
+#else
+		HeightMap heightMap(getAssetPath() + "textures/terrain_heightmap_r16.ktx", PATCH_SIZE);
+#endif
+		for (auto x = 0; x < PATCH_SIZE; x++)
+		{
+			for (auto y = 0; y < PATCH_SIZE; y++)
+			{
+				// Get height samples centered around current position
+				float heights[3][3];
+				for (auto hx = -1; hx <= 1; hx++)
+				{
+					for (auto hy = -1; hy <= 1; hy++)
+					{
+						heights[hx+1][hy+1] = heightMap.getHeight(x + hx, y + hy);
+					}
+				}
+
+				// Calculate the normal
+				glm::vec3 normal;
+				// Gx sobel filter
+				normal.x = heights[0][0] - heights[2][0] + 2.0f * heights[0][1] - 2.0f * heights[2][1] + heights[0][2] - heights[2][2];
+				// Gy sobel filter
+				normal.z = heights[0][0] + 2.0f * heights[1][0] + heights[2][0] - heights[0][2] - 2.0f * heights[1][2] - heights[2][2];
+				// Calculate missing up component of the normal using the filtered x and y axis
+				// The first value controls the bump strength
+				normal.y = 0.25f * sqrt( 1.0f - normal.x * normal.x - normal.z * normal.z);
+
+				vertices[x + y * PATCH_SIZE].normal = glm::normalize(normal * glm::vec3(2.0f, 1.0f, 2.0f));
+			}
+		}
+
+		// Indices
+		const uint32_t w = (PATCH_SIZE - 1);
+		const uint32_t indexCount = w * w * 4;
+		uint32_t *indices = new uint32_t[indexCount];
+		for (auto x = 0; x < w; x++)
+		{
+			for (auto y = 0; y < w; y++)
+			{
+				uint32_t index = (x + y * w) * 4;
+				indices[index] = (x + y * PATCH_SIZE);
+				indices[index + 1] = indices[index] + PATCH_SIZE;
+				indices[index + 2] = indices[index + 1] + 1;
+				indices[index + 3] = indices[index] + 1;
+			}
+		}
+		terrain.indices.count = indexCount;
+
+		uint32_t vertexBufferSize = vertexCount * sizeof(vkglTF::Vertex);
+		uint32_t indexBufferSize = indexCount * sizeof(uint32_t);
+
+		struct {
+			VkBuffer buffer;
+			VkDeviceMemory memory;
+		} vertexStaging, indexStaging;
+
+		// Create staging buffers
+
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			vertexBufferSize,
+			&vertexStaging.buffer,
+			&vertexStaging.memory,
+			vertices));
+
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			indexBufferSize,
+			&indexStaging.buffer,
+			&indexStaging.memory,
+			indices));
+
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
+			VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
+			vertexBufferSize,
+			&terrain.vertices.buffer,
+			&terrain.vertices.memory));
+
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
+			VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
+			indexBufferSize,
+			&terrain.indices.buffer,
+			&terrain.indices.memory));
+
+		// Copy from staging buffers
+		VkCommandBuffer copyCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+
+		VkBufferCopy copyRegion = {};
+
+		copyRegion.size = vertexBufferSize;
+		vkCmdCopyBuffer(
+			copyCmd,
+			vertexStaging.buffer,
+			terrain.vertices.buffer,
+			1,
+			&copyRegion);
+
+		copyRegion.size = indexBufferSize;
+		vkCmdCopyBuffer(
+			copyCmd,
+			indexStaging.buffer,
+			terrain.indices.buffer,
+			1,
+			&copyRegion);
+
+		vulkanDevice->flushCommandBuffer(copyCmd, queue, true);
+
+		vkDestroyBuffer(device, vertexStaging.buffer, nullptr);
+		vkFreeMemory(device, vertexStaging.memory, nullptr);
+		vkDestroyBuffer(device, indexStaging.buffer, nullptr);
+		vkFreeMemory(device, indexStaging.memory, nullptr);
+
+		delete[] vertices;
+		delete[] indices;
+	}
+
+	void setupDescriptorPool()
+	{
+		std::vector<VkDescriptorPoolSize> poolSizes =
+		{
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 3),
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 3)
+		};
+
+		VkDescriptorPoolCreateInfo descriptorPoolInfo =
+			vks::initializers::descriptorPoolCreateInfo(
+				static_cast<uint32_t>(poolSizes.size()),
+				poolSizes.data(),
+				2);
+
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+	}
+
+	void setupDescriptorSetLayouts()
+	{
+		VkDescriptorSetLayoutCreateInfo descriptorLayout;
+		VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo;
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings;
+
+		// Terrain
+		setLayoutBindings =
+		{
+			// Binding 0 : Shared Tessellation shader ubo
+			vks::initializers::descriptorSetLayoutBinding(
+				VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+				VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT | VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT,
+				0),
+			// Binding 1 : Height map
+			vks::initializers::descriptorSetLayoutBinding(
+				VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+				VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT | VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
+				1),
+			// Binding 3 : Terrain texture array layers
+			vks::initializers::descriptorSetLayoutBinding(
+				VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+				VK_SHADER_STAGE_FRAGMENT_BIT,
+				2),
+		};
+
+		descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings.data(), static_cast<uint32_t>(setLayoutBindings.size()));
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayouts.terrain));
+		pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayouts.terrain, 1);
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayouts.terrain));
+
+		// Skysphere
+		setLayoutBindings =
+		{
+			// Binding 0 : Vertex shader ubo
+			vks::initializers::descriptorSetLayoutBinding(
+				VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+				VK_SHADER_STAGE_VERTEX_BIT,
+				0),
+			// Binding 1 : Color map
+			vks::initializers::descriptorSetLayoutBinding(
+				VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+				VK_SHADER_STAGE_FRAGMENT_BIT,
+				1),
+		};
+
+		descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings.data(), static_cast<uint32_t>(setLayoutBindings.size()));
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayouts.skysphere));
+		pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayouts.skysphere, 1);
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayouts.skysphere));
+	}
+
+	void setupDescriptorSets()
+	{
+		VkDescriptorSetAllocateInfo allocInfo;
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets;
+
+		// Terrain
+		allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.terrain, 1);
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.terrain));
+
+		writeDescriptorSets =
+		{
+			// Binding 0 : Shared tessellation shader ubo
+			vks::initializers::writeDescriptorSet(
+				descriptorSets.terrain,
+				VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+				0,
+				&uniformBuffers.terrainTessellation.descriptor),
+			// Binding 1 : Displacement map
+			vks::initializers::writeDescriptorSet(
+				descriptorSets.terrain,
+				VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+				1,
+				&textures.heightMap.descriptor),
+			// Binding 2 : Color map (alpha channel)
+			vks::initializers::writeDescriptorSet(
+				descriptorSets.terrain,
+				VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+				2,
+				&textures.terrainArray.descriptor),
+		};
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL);
+
+		// Skysphere
+		allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.skysphere, 1);
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.skysphere));
+
+		writeDescriptorSets =
+		{
+			// Binding 0 : Vertex shader ubo
+			vks::initializers::writeDescriptorSet(
+				descriptorSets.skysphere,
+				VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+				0,
+				&uniformBuffers.skysphereVertex.descriptor),
+			// Binding 1 : Fragment shader color map
+			vks::initializers::writeDescriptorSet(
+				descriptorSets.skysphere,
+				VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+				1,
+				&textures.skySphere.descriptor),
+		};
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL);
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+		VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
+		std::vector<VkDynamicState> dynamicStateEnables = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR, VK_DYNAMIC_STATE_LINE_WIDTH };
+		VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
+		std::array<VkPipelineShaderStageCreateInfo, 4> shaderStages;
+
+		// We render the terrain as a grid of quad patches
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_PATCH_LIST, 0, VK_FALSE);
+		VkPipelineTessellationStateCreateInfo tessellationState = vks::initializers::pipelineTessellationStateCreateInfo(4);
+		// Terrain tessellation pipeline
+		shaderStages[0] = loadShader(getShadersPath() + "terraintessellation/terrain.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "terraintessellation/terrain.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		shaderStages[2] = loadShader(getShadersPath() + "terraintessellation/terrain.tesc.spv", VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT);
+		shaderStages[3] = loadShader(getShadersPath() + "terraintessellation/terrain.tese.spv", VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT);
+
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayouts.terrain, renderPass);
+		pipelineCI.pInputAssemblyState = &inputAssemblyState;
+		pipelineCI.pRasterizationState = &rasterizationState;
+		pipelineCI.pColorBlendState = &colorBlendState;
+		pipelineCI.pMultisampleState = &multisampleState;
+		pipelineCI.pViewportState = &viewportState;
+		pipelineCI.pDepthStencilState = &depthStencilState;
+		pipelineCI.pDynamicState = &dynamicState;
+		pipelineCI.pTessellationState = &tessellationState;
+		pipelineCI.stageCount = static_cast<uint32_t>(shaderStages.size());
+		pipelineCI.pStages = shaderStages.data();
+		pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::UV });
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.terrain));
+
+		// Terrain wireframe pipeline
+		if (deviceFeatures.fillModeNonSolid) {
+			rasterizationState.polygonMode = VK_POLYGON_MODE_LINE;
+			VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.wireframe));
+		};
+
+		// Skysphere pipeline
+		rasterizationState.cullMode = VK_CULL_MODE_FRONT_BIT;
+		rasterizationState.polygonMode = VK_POLYGON_MODE_FILL;
+		// Revert to triangle list topology
+		inputAssemblyState.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
+		// Reset tessellation state
+		pipelineCI.pTessellationState = nullptr;
+		// Don't write to depth buffer
+		depthStencilState.depthWriteEnable = VK_FALSE;
+		pipelineCI.stageCount = 2;
+		pipelineCI.layout = pipelineLayouts.skysphere;
+		shaderStages[0] = loadShader(getShadersPath() + "terraintessellation/skysphere.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "terraintessellation/skysphere.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.skysphere));
+	}
+
+	// Prepare and initialize uniform buffer containing shader uniforms
+	void prepareUniformBuffers()
+	{
+		// Shared tessellation shader stages uniform buffer
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.terrainTessellation,
+			sizeof(uboTess)));
+
+		// Skysphere vertex shader uniform buffer
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.skysphereVertex,
+			sizeof(uboVS)));
+
+		// Map persistent
+		VK_CHECK_RESULT(uniformBuffers.terrainTessellation.map());
+		VK_CHECK_RESULT(uniformBuffers.skysphereVertex.map());
+
+		updateUniformBuffers();
+	}
+
+	void updateUniformBuffers()
+	{
+		// Tessellation
+
+		uboTess.projection = camera.matrices.perspective;
+		uboTess.modelview = camera.matrices.view * glm::mat4(1.0f);
+		uboTess.lightPos.y = -0.5f - uboTess.displacementFactor; // todo: Not uesed yet
+		uboTess.viewportDim = glm::vec2((float)width, (float)height);
+
+		frustum.update(uboTess.projection * uboTess.modelview);
+		memcpy(uboTess.frustumPlanes, frustum.planes.data(), sizeof(glm::vec4) * 6);
+
+		float savedFactor = uboTess.tessellationFactor;
+		if (!tessellation)
+		{
+			// Setting this to zero sets all tessellation factors to 1.0 in the shader
+			uboTess.tessellationFactor = 0.0f;
+		}
+
+		memcpy(uniformBuffers.terrainTessellation.mapped, &uboTess, sizeof(uboTess));
+
+		if (!tessellation)
+		{
+			uboTess.tessellationFactor = savedFactor;
+		}
+
+		// Skysphere vertex shader
+		uboVS.mvp = camera.matrices.perspective * glm::mat4(glm::mat3(camera.matrices.view));
+		memcpy(uniformBuffers.skysphereVertex.mapped, &uboVS, sizeof(uboVS));
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+
+		// Command buffer to be submitted to the queue
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+
+		// Submit to queue
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+
+		if (deviceFeatures.pipelineStatisticsQuery) {
+			// Read query results for displaying in next frame
+			getQueryResults();
+		}
+
+		VulkanExampleBase::submitFrame();
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		loadAssets();
+		generateTerrain();
+		if (deviceFeatures.pipelineStatisticsQuery) {
+			setupQueryResultBuffer();
+		}
+		prepareUniformBuffers();
+		setupDescriptorSetLayouts();
+		preparePipelines();
+		setupDescriptorPool();
+		setupDescriptorSets();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+		if (camera.updated) {
+			updateUniformBuffers();
+		}
+	}
+
+	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
+	{
+		if (overlay->header("Settings")) {
+
+			if (overlay->checkBox("Tessellation", &tessellation)) {
+				updateUniformBuffers();
+			}
+			if (overlay->inputFloat("Factor", &uboTess.tessellationFactor, 0.05f, 2)) {
+				updateUniformBuffers();
+			}
+			if (deviceFeatures.fillModeNonSolid) {
+				if (overlay->checkBox("Wireframe", &wireframe)) {
+					buildCommandBuffers();
+				}
+			}
+		}
+		if (deviceFeatures.pipelineStatisticsQuery) {
+			if (overlay->header("Pipeline statistics")) {
+				overlay->text("VS invocations: %d", pipelineStats[0]);
+				overlay->text("TE invocations: %d", pipelineStats[1]);
+			}
+		}
+	}
+};
+
+VULKAN_EXAMPLE_MAIN()
\ No newline at end of file
diff --git a/external/Vulkan/examples/tessellation/tessellation.cpp b/external/Vulkan/examples/tessellation/tessellation.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..444a07899497b4e99fbda088c2df6bbc15c4827a
--- /dev/null
+++ b/external/Vulkan/examples/tessellation/tessellation.cpp
@@ -0,0 +1,350 @@
+/*
+* Vulkan Example - Tessellation shader PN triangles
+*
+* Based on http://alex.vlachos.com/graphics/CurvedPNTriangles.pdf
+* Shaders based on http://onrendering.blogspot.de/2011/12/tessellation-on-gpu-curved-pn-triangles.html
+*
+* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkanexamplebase.h"
+#include "VulkanglTFModel.h"
+
+#define ENABLE_VALIDATION false
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	bool splitScreen = true;
+	bool wireframe = true;
+
+	vkglTF::Model model;
+
+	struct {
+		vks::Buffer tessControl, tessEval;
+	} uniformBuffers;
+
+	struct UBOTessControl {
+		float tessLevel = 3.0f;
+	} uboTessControl;
+
+	struct UBOTessEval {
+		glm::mat4 projection;
+		glm::mat4 modelView;
+		float tessAlpha = 1.0f;
+	} uboTessEval;
+
+	struct Pipelines {
+		VkPipeline solid;
+		VkPipeline wire = VK_NULL_HANDLE;
+		VkPipeline solidPassThrough;
+		VkPipeline wirePassThrough = VK_NULL_HANDLE;
+	} pipelines;
+
+	VkPipelineLayout pipelineLayout;
+	VkDescriptorSet descriptorSet;
+	VkDescriptorSetLayout descriptorSetLayout;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Tessellation shader (PN Triangles)";
+		camera.type = Camera::CameraType::lookat;
+		camera.setPosition(glm::vec3(0.0f, 0.0f, -4.0f));
+		camera.setRotation(glm::vec3(-350.0f, 60.0f, 0.0f));
+		camera.setPerspective(45.0f, (float)(width * ((splitScreen) ? 0.5f : 1.0f)) / (float)height, 0.1f, 256.0f);
+	}
+
+	~VulkanExample()
+	{
+		// Clean up used Vulkan resources
+		// Note : Inherited destructor cleans up resources stored in base class
+		vkDestroyPipeline(device, pipelines.solid, nullptr);
+		if (pipelines.wire != VK_NULL_HANDLE) {
+			vkDestroyPipeline(device, pipelines.wire, nullptr);
+		};
+		vkDestroyPipeline(device, pipelines.solidPassThrough, nullptr);
+		if (pipelines.wirePassThrough != VK_NULL_HANDLE) {
+			vkDestroyPipeline(device, pipelines.wirePassThrough, nullptr);
+		};
+
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+
+		uniformBuffers.tessControl.destroy();
+		uniformBuffers.tessEval.destroy();
+	}
+
+	// Enable physical device features required for this example
+	virtual void getEnabledFeatures()
+	{
+		// Example uses tessellation shaders
+		if (deviceFeatures.tessellationShader) {
+			enabledFeatures.tessellationShader = VK_TRUE;
+		}
+		else {
+			vks::tools::exitFatal("Selected GPU does not support tessellation shaders!", VK_ERROR_FEATURE_NOT_PRESENT);
+		}
+		// Fill mode non solid is required for wireframe display
+		if (deviceFeatures.fillModeNonSolid) {
+			enabledFeatures.fillModeNonSolid = VK_TRUE;
+		}
+		else {
+			wireframe = false;
+		}
+		if (deviceFeatures.samplerAnisotropy) {
+			enabledFeatures.samplerAnisotropy = VK_TRUE;
+		}
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		clearValues[0].color = { {0.5f, 0.5f, 0.5f, 0.0f} };
+		clearValues[1].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.offset.x = 0;
+		renderPassBeginInfo.renderArea.offset.y = 0;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		renderPassBeginInfo.clearValueCount = 2;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			// Set target frame buffer
+			renderPassBeginInfo.framebuffer = frameBuffers[i];
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			VkViewport viewport = vks::initializers::viewport(splitScreen ? (float)width / 2.0f : (float)width, (float)height, 0.0f, 1.0f);
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+			VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+			vkCmdSetLineWidth(drawCmdBuffers[i], 1.0f);
+
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL);
+
+			if (splitScreen) {
+				vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+				vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, wireframe ? pipelines.wirePassThrough : pipelines.solidPassThrough);
+				model.draw(drawCmdBuffers[i], vkglTF::RenderFlags::BindImages, pipelineLayout);
+				viewport.x = float(width) / 2;
+			}
+
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, wireframe ? pipelines.wire : pipelines.solid);
+			model.draw(drawCmdBuffers[i], vkglTF::RenderFlags::BindImages, pipelineLayout);
+
+			drawUI(drawCmdBuffers[i]);
+
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void loadAssets()
+	{
+		model.loadFromFile(getAssetPath() + "models/deer.gltf", vulkanDevice, queue, vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::FlipY);
+	}
+
+	void setupDescriptorPool()
+	{
+		const std::vector<VkDescriptorPoolSize> poolSizes = {
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2),
+		};
+		VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 1);
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+	}
+
+	void setupDescriptorSetLayout()
+	{
+		const std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			// Binding 0 : Tessellation control shader ubo
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT, 0),
+			// Binding 1 : Tessellation evaluation shader ubo
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT, 1),
+		};
+		VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout));
+
+		// Layout uses set 0 for passing tessellation shader ubos and set 1 for fragment shader images (taken from glTF model)
+		const std::vector<VkDescriptorSetLayout> setLayouts = {
+			descriptorSetLayout,
+			vkglTF::descriptorSetLayoutImage,
+		};
+		VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(setLayouts.data(), 2);
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayout));
+	}
+
+	void setupDescriptorSet()
+	{
+		VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1);
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet));
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets = {
+			// Binding 0 : Tessellation control shader ubo
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.tessControl.descriptor),
+			// Binding 1 : Tessellation evaluation shader ubo
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, &uniformBuffers.tessEval.descriptor),
+		};
+		vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, NULL);
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_PATCH_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+		VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
+		std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR, VK_DYNAMIC_STATE_LINE_WIDTH };
+		VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables, 0);
+		VkPipelineTessellationStateCreateInfo tessellationState = vks::initializers::pipelineTessellationStateCreateInfo(3);
+		std::array<VkPipelineShaderStageCreateInfo, 4> shaderStages;
+
+		// Tessellation pipelines
+		shaderStages[0] = loadShader(getShadersPath() + "tessellation/base.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "tessellation/base.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		shaderStages[2] = loadShader(getShadersPath() + "tessellation/pntriangles.tesc.spv", VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT);
+		shaderStages[3] = loadShader(getShadersPath() + "tessellation/pntriangles.tese.spv", VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT);
+
+		VkGraphicsPipelineCreateInfo pipelineCI =  vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass, 0);
+		pipelineCI.pInputAssemblyState = &inputAssemblyState;
+		pipelineCI.pRasterizationState = &rasterizationState;
+		pipelineCI.pColorBlendState = &colorBlendState;
+		pipelineCI.pMultisampleState = &multisampleState;
+		pipelineCI.pViewportState = &viewportState;
+		pipelineCI.pDepthStencilState = &depthStencilState;
+		pipelineCI.pDynamicState = &dynamicState;
+		pipelineCI.pTessellationState = &tessellationState;
+		pipelineCI.stageCount = shaderStages.size();
+		pipelineCI.pStages = shaderStages.data();
+		pipelineCI.renderPass = renderPass;
+		pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::UV });
+
+		// Tessellation pipelines
+		// Solid
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.solid));
+		// Wireframe
+		if (deviceFeatures.fillModeNonSolid) {
+			rasterizationState.polygonMode = VK_POLYGON_MODE_LINE;
+			VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.wire));
+		}
+
+		// Pass through pipelines
+		// Load pass through tessellation shaders (Vert and frag are reused)
+		shaderStages[2] = loadShader(getShadersPath() + "tessellation/passthrough.tesc.spv", VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT);
+		shaderStages[3] = loadShader(getShadersPath() + "tessellation/passthrough.tese.spv", VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT);
+
+		// Solid
+		rasterizationState.polygonMode = VK_POLYGON_MODE_FILL;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.solidPassThrough));
+		// Wireframe
+		if (deviceFeatures.fillModeNonSolid) {
+			rasterizationState.polygonMode = VK_POLYGON_MODE_LINE;
+			VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.wirePassThrough));
+		}
+	}
+
+	// Prepare and initialize uniform buffer containing shader uniforms
+	void prepareUniformBuffers()
+	{
+		// Tessellation evaluation shader uniform buffer
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.tessEval,
+			sizeof(uboTessEval)));
+
+		// Tessellation control shader uniform buffer
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.tessControl,
+			sizeof(uboTessControl)));
+
+		// Map persistent
+		VK_CHECK_RESULT(uniformBuffers.tessControl.map());
+		VK_CHECK_RESULT(uniformBuffers.tessEval.map());
+
+		updateUniformBuffers();
+	}
+
+	void updateUniformBuffers()
+	{
+		uboTessEval.projection = camera.matrices.perspective;
+		uboTessEval.modelView = camera.matrices.view;
+		// Tessellation evaluation uniform block
+		memcpy(uniformBuffers.tessEval.mapped, &uboTessEval, sizeof(uboTessEval));
+		// Tessellation control uniform block
+		memcpy(uniformBuffers.tessControl.mapped, &uboTessControl, sizeof(uboTessControl));
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+
+		VulkanExampleBase::submitFrame();
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		loadAssets();
+		prepareUniformBuffers();
+		setupDescriptorSetLayout();
+		preparePipelines();
+		setupDescriptorPool();
+		setupDescriptorSet();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+		if (camera.updated) {
+			updateUniformBuffers();
+		}
+	}
+
+	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
+	{
+		if (overlay->header("Settings")) {
+			if (overlay->inputFloat("Tessellation level", &uboTessControl.tessLevel, 0.25f, 2)) {
+				updateUniformBuffers();
+			}
+			if (deviceFeatures.fillModeNonSolid) {
+				if (overlay->checkBox("Wireframe", &wireframe)) {
+					updateUniformBuffers();
+					buildCommandBuffers();
+				}
+				if (overlay->checkBox("Splitscreen", &splitScreen)) {
+					camera.setPerspective(45.0f, (float)(width * ((splitScreen) ? 0.5f : 1.0f)) / (float)height, 0.1f, 256.0f);
+					updateUniformBuffers();
+					buildCommandBuffers();
+				}
+			}
+		}
+	}
+};
+
+VULKAN_EXAMPLE_MAIN()
diff --git a/external/Vulkan/examples/textoverlay/textoverlay.cpp b/external/Vulkan/examples/textoverlay/textoverlay.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..85b70727fa48437ff22628ed6d86a95b42c99916
--- /dev/null
+++ b/external/Vulkan/examples/textoverlay/textoverlay.cpp
@@ -0,0 +1,920 @@
+/*
+* Vulkan Example - Text overlay rendering on-top of an existing scene using a separate render pass
+*
+* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include <sstream>
+#include <iomanip>
+#include "vulkanexamplebase.h"
+#include "VulkanglTFModel.h"
+#include "../external/stb/stb_font_consolas_24_latin1.inl"
+
+#define ENABLE_VALIDATION false
+
+// Max. number of chars the text overlay buffer can hold
+#define TEXTOVERLAY_MAX_CHAR_COUNT 2048
+
+/*
+	Mostly self-contained text overlay class
+*/
+class TextOverlay
+{
+private:
+	vks::VulkanDevice *vulkanDevice;
+
+	VkQueue queue;
+	VkFormat colorFormat;
+	VkFormat depthFormat;
+
+	uint32_t *frameBufferWidth;
+	uint32_t *frameBufferHeight;
+
+	VkSampler sampler;
+	VkImage image;
+	VkImageView view;
+	VkBuffer buffer;
+	VkDeviceMemory memory;
+	VkDeviceMemory imageMemory;
+	VkDescriptorPool descriptorPool;
+	VkDescriptorSetLayout descriptorSetLayout;
+	VkDescriptorSet descriptorSet;
+	VkPipelineLayout pipelineLayout;
+	VkPipelineCache pipelineCache;
+	VkPipeline pipeline;
+	VkRenderPass renderPass;
+	VkCommandPool commandPool;
+	std::vector<VkFramebuffer*> frameBuffers;
+	std::vector<VkPipelineShaderStageCreateInfo> shaderStages;
+
+	// Pointer to mapped vertex buffer
+	glm::vec4 *mapped = nullptr;
+
+	stb_fontchar stbFontData[STB_FONT_consolas_24_latin1_NUM_CHARS];
+	uint32_t numLetters;
+public:
+
+	enum TextAlign { alignLeft, alignCenter, alignRight };
+
+	bool visible = true;
+
+	std::vector<VkCommandBuffer> cmdBuffers;
+
+	TextOverlay(
+		vks::VulkanDevice *vulkanDevice,
+		VkQueue queue,
+		std::vector<VkFramebuffer> &framebuffers,
+		VkFormat colorformat,
+		VkFormat depthformat,
+		uint32_t *framebufferwidth,
+		uint32_t *framebufferheight,
+		std::vector<VkPipelineShaderStageCreateInfo> shaderstages)
+	{
+		this->vulkanDevice = vulkanDevice;
+		this->queue = queue;
+		this->colorFormat = colorformat;
+		this->depthFormat = depthformat;
+
+		this->frameBuffers.resize(framebuffers.size());
+		for (uint32_t i = 0; i < framebuffers.size(); i++)
+		{
+			this->frameBuffers[i] = &framebuffers[i];
+		}
+
+		this->shaderStages = shaderstages;
+
+		this->frameBufferWidth = framebufferwidth;
+		this->frameBufferHeight = framebufferheight;
+
+		cmdBuffers.resize(framebuffers.size());
+		prepareResources();
+		prepareRenderPass();
+		preparePipeline();
+	}
+
+	~TextOverlay()
+	{
+		// Free up all Vulkan resources requested by the text overlay
+		vkDestroySampler(vulkanDevice->logicalDevice, sampler, nullptr);
+		vkDestroyImage(vulkanDevice->logicalDevice, image, nullptr);
+		vkDestroyImageView(vulkanDevice->logicalDevice, view, nullptr);
+		vkDestroyBuffer(vulkanDevice->logicalDevice, buffer, nullptr);
+		vkFreeMemory(vulkanDevice->logicalDevice, memory, nullptr);
+		vkFreeMemory(vulkanDevice->logicalDevice, imageMemory, nullptr);
+		vkDestroyDescriptorSetLayout(vulkanDevice->logicalDevice, descriptorSetLayout, nullptr);
+		vkDestroyDescriptorPool(vulkanDevice->logicalDevice, descriptorPool, nullptr);
+		vkDestroyPipelineLayout(vulkanDevice->logicalDevice, pipelineLayout, nullptr);
+		vkDestroyPipelineCache(vulkanDevice->logicalDevice, pipelineCache, nullptr);
+		vkDestroyPipeline(vulkanDevice->logicalDevice, pipeline, nullptr);
+		vkDestroyRenderPass(vulkanDevice->logicalDevice, renderPass, nullptr);
+		vkDestroyCommandPool(vulkanDevice->logicalDevice, commandPool, nullptr);
+	}
+
+	// Prepare all vulkan resources required to render the font
+	// The text overlay uses separate resources for descriptors (pool, sets, layouts), pipelines and command buffers
+	void prepareResources()
+	{
+		const uint32_t fontWidth = STB_FONT_consolas_24_latin1_BITMAP_WIDTH;
+		const uint32_t fontHeight = STB_FONT_consolas_24_latin1_BITMAP_WIDTH;
+
+		static unsigned char font24pixels[fontWidth][fontHeight];
+		stb_font_consolas_24_latin1(stbFontData, font24pixels, fontHeight);
+
+		// Command buffer
+
+		// Pool
+		VkCommandPoolCreateInfo cmdPoolInfo = {};
+		cmdPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
+		cmdPoolInfo.queueFamilyIndex = vulkanDevice->queueFamilyIndices.graphics;
+		cmdPoolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
+		VK_CHECK_RESULT(vkCreateCommandPool(vulkanDevice->logicalDevice, &cmdPoolInfo, nullptr, &commandPool));
+
+		VkCommandBufferAllocateInfo cmdBufAllocateInfo =
+			vks::initializers::commandBufferAllocateInfo(
+				commandPool,
+				VK_COMMAND_BUFFER_LEVEL_PRIMARY,
+				(uint32_t)cmdBuffers.size());
+
+		VK_CHECK_RESULT(vkAllocateCommandBuffers(vulkanDevice->logicalDevice, &cmdBufAllocateInfo, cmdBuffers.data()));
+
+		// Vertex buffer
+		VkDeviceSize bufferSize = TEXTOVERLAY_MAX_CHAR_COUNT * sizeof(glm::vec4);
+
+		VkBufferCreateInfo bufferInfo = vks::initializers::bufferCreateInfo(VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, bufferSize);
+		VK_CHECK_RESULT(vkCreateBuffer(vulkanDevice->logicalDevice, &bufferInfo, nullptr, &buffer));
+
+		VkMemoryRequirements memReqs;
+		VkMemoryAllocateInfo allocInfo = vks::initializers::memoryAllocateInfo();
+
+		vkGetBufferMemoryRequirements(vulkanDevice->logicalDevice, buffer, &memReqs);
+		allocInfo.allocationSize = memReqs.size;
+		allocInfo.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
+
+		VK_CHECK_RESULT(vkAllocateMemory(vulkanDevice->logicalDevice, &allocInfo, nullptr, &memory));
+		VK_CHECK_RESULT(vkBindBufferMemory(vulkanDevice->logicalDevice, buffer, memory, 0));
+
+		// Font texture
+		VkImageCreateInfo imageInfo = vks::initializers::imageCreateInfo();
+		imageInfo.imageType = VK_IMAGE_TYPE_2D;
+		imageInfo.format = VK_FORMAT_R8_UNORM;
+		imageInfo.extent.width = fontWidth;
+		imageInfo.extent.height = fontHeight;
+		imageInfo.extent.depth = 1;
+		imageInfo.mipLevels = 1;
+		imageInfo.arrayLayers = 1;
+		imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
+		imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
+		imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
+		imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+		imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+
+		VK_CHECK_RESULT(vkCreateImage(vulkanDevice->logicalDevice, &imageInfo, nullptr, &image));
+
+		vkGetImageMemoryRequirements(vulkanDevice->logicalDevice, image, &memReqs);
+		allocInfo.allocationSize = memReqs.size;
+		allocInfo.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+
+		VK_CHECK_RESULT(vkAllocateMemory(vulkanDevice->logicalDevice, &allocInfo, nullptr, &imageMemory));
+		VK_CHECK_RESULT(vkBindImageMemory(vulkanDevice->logicalDevice, image, imageMemory, 0));
+
+		// Staging
+
+		struct {
+			VkDeviceMemory memory;
+			VkBuffer buffer;
+		} stagingBuffer;
+
+		VkBufferCreateInfo bufferCreateInfo = vks::initializers::bufferCreateInfo();
+		bufferCreateInfo.size = allocInfo.allocationSize;
+		bufferCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
+		bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+
+		VK_CHECK_RESULT(vkCreateBuffer(vulkanDevice->logicalDevice, &bufferCreateInfo, nullptr, &stagingBuffer.buffer));
+
+		// Get memory requirements for the staging buffer (alignment, memory type bits)
+		vkGetBufferMemoryRequirements(vulkanDevice->logicalDevice, stagingBuffer.buffer, &memReqs);
+
+		allocInfo.allocationSize = memReqs.size;
+		// Get memory type index for a host visible buffer
+		allocInfo.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
+
+		VK_CHECK_RESULT(vkAllocateMemory(vulkanDevice->logicalDevice, &allocInfo, nullptr, &stagingBuffer.memory));
+		VK_CHECK_RESULT(vkBindBufferMemory(vulkanDevice->logicalDevice, stagingBuffer.buffer, stagingBuffer.memory, 0));
+
+		uint8_t *data;
+		VK_CHECK_RESULT(vkMapMemory(vulkanDevice->logicalDevice, stagingBuffer.memory, 0, allocInfo.allocationSize, 0, (void **)&data));
+		// Size of the font texture is WIDTH * HEIGHT * 1 byte (only one channel)
+		memcpy(data, &font24pixels[0][0], fontWidth * fontHeight);
+		vkUnmapMemory(vulkanDevice->logicalDevice, stagingBuffer.memory);
+
+		// Copy to image
+
+		VkCommandBuffer copyCmd;
+		cmdBufAllocateInfo.commandBufferCount = 1;
+		VK_CHECK_RESULT(vkAllocateCommandBuffers(vulkanDevice->logicalDevice, &cmdBufAllocateInfo, &copyCmd));
+
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+		VK_CHECK_RESULT(vkBeginCommandBuffer(copyCmd, &cmdBufInfo));
+
+		// Prepare for transfer
+		vks::tools::setImageLayout(
+			copyCmd,
+			image,
+			VK_IMAGE_ASPECT_COLOR_BIT,
+			VK_IMAGE_LAYOUT_UNDEFINED,
+			VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
+
+		VkBufferImageCopy bufferCopyRegion = {};
+		bufferCopyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+		bufferCopyRegion.imageSubresource.mipLevel = 0;
+		bufferCopyRegion.imageSubresource.layerCount = 1;
+		bufferCopyRegion.imageExtent.width = fontWidth;
+		bufferCopyRegion.imageExtent.height = fontHeight;
+		bufferCopyRegion.imageExtent.depth = 1;
+
+		vkCmdCopyBufferToImage(
+			copyCmd,
+			stagingBuffer.buffer,
+			image,
+			VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+			1,
+			&bufferCopyRegion
+			);
+
+		// Prepare for shader read
+		vks::tools::setImageLayout(
+			copyCmd,
+			image,
+			VK_IMAGE_ASPECT_COLOR_BIT,
+			VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+			VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+
+		VK_CHECK_RESULT(vkEndCommandBuffer(copyCmd));
+
+		VkSubmitInfo submitInfo = vks::initializers::submitInfo();
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &copyCmd;
+
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+		VK_CHECK_RESULT(vkQueueWaitIdle(queue));
+
+		vkFreeCommandBuffers(vulkanDevice->logicalDevice, commandPool, 1, &copyCmd);
+		vkFreeMemory(vulkanDevice->logicalDevice, stagingBuffer.memory, nullptr);
+		vkDestroyBuffer(vulkanDevice->logicalDevice, stagingBuffer.buffer, nullptr);
+
+		VkImageViewCreateInfo imageViewInfo = vks::initializers::imageViewCreateInfo();
+		imageViewInfo.image = image;
+		imageViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
+		imageViewInfo.format = imageInfo.format;
+		imageViewInfo.components = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B,	VK_COMPONENT_SWIZZLE_A };
+		imageViewInfo.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 };
+		VK_CHECK_RESULT(vkCreateImageView(vulkanDevice->logicalDevice, &imageViewInfo, nullptr, &view));
+
+		// Sampler
+		VkSamplerCreateInfo samplerInfo = vks::initializers::samplerCreateInfo();
+		samplerInfo.magFilter = VK_FILTER_LINEAR;
+		samplerInfo.minFilter = VK_FILTER_LINEAR;
+		samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+		samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+		samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+		samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+		samplerInfo.mipLodBias = 0.0f;
+		samplerInfo.compareOp = VK_COMPARE_OP_NEVER;
+		samplerInfo.minLod = 0.0f;
+		samplerInfo.maxLod = 1.0f;
+		samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
+		VK_CHECK_RESULT(vkCreateSampler(vulkanDevice->logicalDevice, &samplerInfo, nullptr, &sampler));
+
+		// Descriptor
+		// Font uses a separate descriptor pool
+		std::array<VkDescriptorPoolSize, 1> poolSizes;
+		poolSizes[0] = vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1);
+
+		VkDescriptorPoolCreateInfo descriptorPoolInfo =
+			vks::initializers::descriptorPoolCreateInfo(
+				static_cast<uint32_t>(poolSizes.size()),
+				poolSizes.data(),
+				1);
+
+		VK_CHECK_RESULT(vkCreateDescriptorPool(vulkanDevice->logicalDevice, &descriptorPoolInfo, nullptr, &descriptorPool));
+
+		// Descriptor set layout
+		std::array<VkDescriptorSetLayoutBinding, 1> setLayoutBindings;
+		setLayoutBindings[0] = vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0);
+
+		VkDescriptorSetLayoutCreateInfo descriptorSetLayoutInfo =
+			vks::initializers::descriptorSetLayoutCreateInfo(
+				setLayoutBindings.data(),
+				static_cast<uint32_t>(setLayoutBindings.size()));
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(vulkanDevice->logicalDevice, &descriptorSetLayoutInfo, nullptr, &descriptorSetLayout));
+
+		// Pipeline layout
+		VkPipelineLayoutCreateInfo pipelineLayoutInfo =
+			vks::initializers::pipelineLayoutCreateInfo(
+				&descriptorSetLayout,
+				1);
+		VK_CHECK_RESULT(vkCreatePipelineLayout(vulkanDevice->logicalDevice, &pipelineLayoutInfo, nullptr, &pipelineLayout));
+
+		// Descriptor set
+		VkDescriptorSetAllocateInfo descriptorSetAllocInfo =
+			vks::initializers::descriptorSetAllocateInfo(
+				descriptorPool,
+				&descriptorSetLayout,
+				1);
+
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(vulkanDevice->logicalDevice, &descriptorSetAllocInfo, &descriptorSet));
+
+		VkDescriptorImageInfo texDescriptor =
+			vks::initializers::descriptorImageInfo(
+				sampler,
+				view,
+				VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+
+		std::array<VkWriteDescriptorSet, 1> writeDescriptorSets;
+		writeDescriptorSets[0] = vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &texDescriptor);
+		vkUpdateDescriptorSets(vulkanDevice->logicalDevice, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL);
+
+		// Pipeline cache
+		VkPipelineCacheCreateInfo pipelineCacheCreateInfo = {};
+		pipelineCacheCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO;
+		VK_CHECK_RESULT(vkCreatePipelineCache(vulkanDevice->logicalDevice, &pipelineCacheCreateInfo, nullptr, &pipelineCache));
+	}
+
+	// Prepare a separate pipeline for the font rendering decoupled from the main application
+	void preparePipeline()
+	{
+		// Enable blending, using alpha from red channel of the font texture (see text.frag)
+		VkPipelineColorBlendAttachmentState blendAttachmentState{};
+		blendAttachmentState.blendEnable = VK_TRUE;
+		blendAttachmentState.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
+		blendAttachmentState.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
+		blendAttachmentState.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
+		blendAttachmentState.colorBlendOp = VK_BLEND_OP_ADD;
+		blendAttachmentState.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
+		blendAttachmentState.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
+		blendAttachmentState.alphaBlendOp = VK_BLEND_OP_ADD;
+
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_CLOCKWISE, 0);
+		VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+		VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
+		std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
+		VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
+
+		std::array<VkVertexInputBindingDescription, 2> vertexInputBindings = {
+			vks::initializers::vertexInputBindingDescription(0, sizeof(glm::vec4), VK_VERTEX_INPUT_RATE_VERTEX),
+			vks::initializers::vertexInputBindingDescription(1, sizeof(glm::vec4), VK_VERTEX_INPUT_RATE_VERTEX),
+		};
+		std::array<VkVertexInputAttributeDescription, 2> vertexInputAttributes = {
+			vks::initializers::vertexInputAttributeDescription(0, 0, VK_FORMAT_R32G32_SFLOAT, 0),					// Location 0: Position
+			vks::initializers::vertexInputAttributeDescription(1, 1, VK_FORMAT_R32G32_SFLOAT, sizeof(glm::vec2)),	// Location 1: UV
+		};
+
+		VkPipelineVertexInputStateCreateInfo vertexInputState = vks::initializers::pipelineVertexInputStateCreateInfo();
+		vertexInputState.vertexBindingDescriptionCount = static_cast<uint32_t>(vertexInputBindings.size());
+		vertexInputState.pVertexBindingDescriptions = vertexInputBindings.data();
+		vertexInputState.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertexInputAttributes.size());
+		vertexInputState.pVertexAttributeDescriptions = vertexInputAttributes.data();
+
+		VkGraphicsPipelineCreateInfo pipelineCreateInfo = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass, 0);
+		pipelineCreateInfo.pVertexInputState = &vertexInputState;
+		pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState;
+		pipelineCreateInfo.pRasterizationState = &rasterizationState;
+		pipelineCreateInfo.pColorBlendState = &colorBlendState;
+		pipelineCreateInfo.pMultisampleState = &multisampleState;
+		pipelineCreateInfo.pViewportState = &viewportState;
+		pipelineCreateInfo.pDepthStencilState = &depthStencilState;
+		pipelineCreateInfo.pDynamicState = &dynamicState;
+		pipelineCreateInfo.stageCount = static_cast<uint32_t>(shaderStages.size());
+		pipelineCreateInfo.pStages = shaderStages.data();
+
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(vulkanDevice->logicalDevice, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipeline));
+	}
+
+	// Prepare a separate render pass for rendering the text as an overlay
+	void prepareRenderPass()
+	{
+		VkAttachmentDescription attachments[2] = {};
+
+		// Color attachment
+		attachments[0].format = colorFormat;
+		attachments[0].samples = VK_SAMPLE_COUNT_1_BIT;
+		// Don't clear the framebuffer (like the renderpass from the example does)
+		attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
+		attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+		attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+		attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+		attachments[0].initialLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
+		attachments[0].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
+
+		// Depth attachment
+		attachments[1].format = depthFormat;
+		attachments[1].samples = VK_SAMPLE_COUNT_1_BIT;
+		attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+		attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+		attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+		attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+		attachments[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+		attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+
+		VkAttachmentReference colorReference = {};
+		colorReference.attachment = 0;
+		colorReference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+
+		VkAttachmentReference depthReference = {};
+		depthReference.attachment = 1;
+		depthReference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+
+		// Use subpass dependencies for image layout transitions
+		VkSubpassDependency subpassDependencies[2] = {};
+
+		// Transition from final to initial (VK_SUBPASS_EXTERNAL refers to all commands executed outside of the actual renderpass)
+		subpassDependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
+		subpassDependencies[0].dstSubpass = 0;
+		subpassDependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+		subpassDependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+		subpassDependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
+		subpassDependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+		subpassDependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+		// Transition from initial to final
+		subpassDependencies[1].srcSubpass = 0;
+		subpassDependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
+		subpassDependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+		subpassDependencies[1].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+		subpassDependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+		subpassDependencies[1].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
+		subpassDependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+		VkSubpassDescription subpassDescription = {};
+		subpassDescription.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+		subpassDescription.flags = 0;
+		subpassDescription.inputAttachmentCount = 0;
+		subpassDescription.pInputAttachments = NULL;
+		subpassDescription.colorAttachmentCount = 1;
+		subpassDescription.pColorAttachments = &colorReference;
+		subpassDescription.pResolveAttachments = NULL;
+		subpassDescription.pDepthStencilAttachment = &depthReference;
+		subpassDescription.preserveAttachmentCount = 0;
+		subpassDescription.pPreserveAttachments = NULL;
+
+		VkRenderPassCreateInfo renderPassInfo = {};
+		renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
+		renderPassInfo.pNext = NULL;
+		renderPassInfo.attachmentCount = 2;
+		renderPassInfo.pAttachments = attachments;
+		renderPassInfo.subpassCount = 1;
+		renderPassInfo.pSubpasses = &subpassDescription;
+		renderPassInfo.dependencyCount = 2;
+		renderPassInfo.pDependencies = subpassDependencies;
+
+		VK_CHECK_RESULT(vkCreateRenderPass(vulkanDevice->logicalDevice, &renderPassInfo, nullptr, &renderPass));
+	}
+
+	// Map buffer
+	void beginTextUpdate()
+	{
+		VK_CHECK_RESULT(vkMapMemory(vulkanDevice->logicalDevice, memory, 0, VK_WHOLE_SIZE, 0, (void **)&mapped));
+		numLetters = 0;
+	}
+
+	// Add text to the current buffer
+	// todo : drop shadow? color attribute?
+	void addText(std::string text, float x, float y, TextAlign align)
+	{
+		const uint32_t firstChar = STB_FONT_consolas_24_latin1_FIRST_CHAR;
+
+		assert(mapped != nullptr);
+
+		const float charW = 1.5f / *frameBufferWidth;
+		const float charH = 1.5f / *frameBufferHeight;
+
+		float fbW = (float)*frameBufferWidth;
+		float fbH = (float)*frameBufferHeight;
+		x = (x / fbW * 2.0f) - 1.0f;
+		y = (y / fbH * 2.0f) - 1.0f;
+
+		// Calculate text width
+		float textWidth = 0;
+		for (auto letter : text)
+		{
+			stb_fontchar *charData = &stbFontData[(uint32_t)letter - firstChar];
+			textWidth += charData->advance * charW;
+		}
+
+		switch (align)
+		{
+			case alignRight:
+				x -= textWidth;
+				break;
+			case alignCenter:
+				x -= textWidth / 2.0f;
+				break;
+		}
+
+		// Generate a uv mapped quad per char in the new text
+		for (auto letter : text)
+		{
+			stb_fontchar *charData = &stbFontData[(uint32_t)letter - firstChar];
+
+			mapped->x = (x + (float)charData->x0 * charW);
+			mapped->y = (y + (float)charData->y0 * charH);
+			mapped->z = charData->s0;
+			mapped->w = charData->t0;
+			mapped++;
+
+			mapped->x = (x + (float)charData->x1 * charW);
+			mapped->y = (y + (float)charData->y0 * charH);
+			mapped->z = charData->s1;
+			mapped->w = charData->t0;
+			mapped++;
+
+			mapped->x = (x + (float)charData->x0 * charW);
+			mapped->y = (y + (float)charData->y1 * charH);
+			mapped->z = charData->s0;
+			mapped->w = charData->t1;
+			mapped++;
+
+			mapped->x = (x + (float)charData->x1 * charW);
+			mapped->y = (y + (float)charData->y1 * charH);
+			mapped->z = charData->s1;
+			mapped->w = charData->t1;
+			mapped++;
+
+			x += charData->advance * charW;
+
+			numLetters++;
+		}
+	}
+
+	// Unmap buffer and update command buffers
+	void endTextUpdate()
+	{
+		vkUnmapMemory(vulkanDevice->logicalDevice, memory);
+		mapped = nullptr;
+		updateCommandBuffers();
+	}
+
+	// Needs to be called by the application
+	void updateCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		clearValues[1].color = { { 0.0f, 0.0f, 0.0f, 0.0f } };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.extent.width = *frameBufferWidth;
+		renderPassBeginInfo.renderArea.extent.height = *frameBufferHeight;
+		renderPassBeginInfo.clearValueCount = 2;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		for (int32_t i = 0; i < cmdBuffers.size(); ++i)
+		{
+			renderPassBeginInfo.framebuffer = *frameBuffers[i];
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(cmdBuffers[i], &cmdBufInfo));
+
+			vkCmdBeginRenderPass(cmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			VkViewport viewport = vks::initializers::viewport((float)*frameBufferWidth, (float)*frameBufferHeight, 0.0f, 1.0f);
+			vkCmdSetViewport(cmdBuffers[i], 0, 1, &viewport);
+
+			VkRect2D scissor = vks::initializers::rect2D(*frameBufferWidth, *frameBufferHeight, 0, 0);
+			vkCmdSetScissor(cmdBuffers[i], 0, 1, &scissor);
+
+			vkCmdBindPipeline(cmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
+			vkCmdBindDescriptorSets(cmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL);
+
+			VkDeviceSize offsets = 0;
+			vkCmdBindVertexBuffers(cmdBuffers[i], 0, 1, &buffer, &offsets);
+			vkCmdBindVertexBuffers(cmdBuffers[i], 1, 1, &buffer, &offsets);
+			for (uint32_t j = 0; j < numLetters; j++)
+			{
+				vkCmdDraw(cmdBuffers[i], 4, 1, j * 4, 0);
+			}
+
+
+			vkCmdEndRenderPass(cmdBuffers[i]);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(cmdBuffers[i]));
+		}
+	}
+};
+
+/*
+	Vulkan example main class
+*/
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	TextOverlay *textOverlay = nullptr;
+
+	vkglTF::Model model;
+
+	vks::Buffer uniformBuffer;
+
+	struct UBOVS {
+		glm::mat4 projection;
+		glm::mat4 modelView;
+		glm::vec4 lightPos = glm::vec4(0.0f, 0.0f, 0.0f, 1.0f);
+	} uboVS;
+
+	VkPipelineLayout pipelineLayout;
+	VkPipeline pipeline;
+
+	VkDescriptorSetLayout descriptorSetLayout;
+	VkDescriptorSet descriptorSet;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Vulkan Example - Text overlay";
+		camera.type = Camera::CameraType::lookat;
+		camera.setPosition(glm::vec3(0.0f, 0.0f, -2.5f));
+		camera.setRotation(glm::vec3(-25.0f, -0.0f, 0.0f));
+		camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 256.0f);
+		settings.overlay = false;
+	}
+
+	~VulkanExample()
+	{
+		vkDestroyPipeline(device, pipeline, nullptr);
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+		uniformBuffer.destroy();
+		delete(textOverlay);
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[3];
+
+		clearValues[0].color = { { 0.0f, 0.0f, 0.2f, 1.0f } };
+		clearValues[1].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		renderPassBeginInfo.clearValueCount = 2;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			// Set target frame buffer
+			renderPassBeginInfo.framebuffer = frameBuffers[i];
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+			VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr);
+			model.draw(drawCmdBuffers[i]);
+
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+
+		vkQueueWaitIdle(queue);
+	}
+
+	// Update the text buffer displayed by the text overlay
+	void updateTextOverlay(void)
+	{
+		textOverlay->beginTextUpdate();
+
+		textOverlay->addText(title, 5.0f, 5.0f, TextOverlay::alignLeft);
+
+		std::stringstream ss;
+		ss << std::fixed << std::setprecision(2) << (frameTimer * 1000.0f) << "ms (" << lastFPS << " fps)";
+		textOverlay->addText(ss.str(), 5.0f, 25.0f, TextOverlay::alignLeft);
+
+		textOverlay->addText(deviceProperties.deviceName, 5.0f, 45.0f, TextOverlay::alignLeft);
+
+		// Display current model view matrix
+		textOverlay->addText("model view matrix", (float)width, 5.0f, TextOverlay::alignRight);
+
+		for (uint32_t i = 0; i < 4; i++)
+		{
+			ss.str("");
+			ss << std::fixed << std::setprecision(2) << std::showpos;
+			ss << uboVS.modelView[0][i] << " " << uboVS.modelView[1][i] << " " << uboVS.modelView[2][i] << " " << uboVS.modelView[3][i];
+			textOverlay->addText(ss.str(), (float)width, 25.0f + (float)i * 20.0f, TextOverlay::alignRight);
+		}
+
+		glm::vec3 projected = glm::project(glm::vec3(0.0f), uboVS.modelView, uboVS.projection, glm::vec4(0, 0, (float)width, (float)height));
+		textOverlay->addText("A cube", projected.x, projected.y, TextOverlay::alignCenter);
+
+#if defined(__ANDROID__)
+#else
+		textOverlay->addText("Press \"space\" to toggle text overlay", 5.0f, 65.0f, TextOverlay::alignLeft);
+		textOverlay->addText("Hold middle mouse button and drag to move", 5.0f, 85.0f, TextOverlay::alignLeft);
+#endif
+		textOverlay->endTextUpdate();
+	}
+
+	void loadAssets()
+	{
+		const uint32_t glTFLoadingFlags = vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::PreMultiplyVertexColors | vkglTF::FileLoadingFlags::FlipY;
+		model.loadFromFile(getAssetPath() + "models/cube.gltf", vulkanDevice, queue, glTFLoadingFlags);
+	}
+
+	void setupDescriptorPool()
+	{
+		std::vector<VkDescriptorPoolSize> poolSizes = {
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2),
+		};
+
+		VkDescriptorPoolCreateInfo descriptorPoolInfo =
+			vks::initializers::descriptorPoolCreateInfo(poolSizes, 2);
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+	}
+
+	void setupDescriptorSetLayout()
+	{
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			// Binding 0: Vertex shader uniform buffer
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0),
+		};
+		VkDescriptorSetLayoutCreateInfo descriptorLayout =
+			vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout));
+
+		VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo =
+			vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1);
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayout));
+	}
+
+	void setupDescriptorSet()
+	{
+		VkDescriptorSetAllocateInfo allocInfo =
+			vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1);
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet));
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets = {
+			// Binding 0: Vertex shader uniform buffer
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffer.descriptor),
+		};
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL);
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+		VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
+		std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
+		VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass);
+		pipelineCI.pInputAssemblyState = &inputAssemblyState;
+		pipelineCI.pRasterizationState = &rasterizationState;
+		pipelineCI.pColorBlendState = &colorBlendState;
+		pipelineCI.pMultisampleState = &multisampleState;
+		pipelineCI.pViewportState = &viewportState;
+		pipelineCI.pDepthStencilState = &depthStencilState;
+		pipelineCI.pDynamicState = &dynamicState;
+		pipelineCI.stageCount = static_cast<uint32_t>(shaderStages.size());
+		pipelineCI.pStages = shaderStages.data();
+		pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::UV});
+
+		shaderStages[0] = loadShader(getShadersPath() + "textoverlay/mesh.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "textoverlay/mesh.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipeline));
+	}
+
+	// Prepare and initialize uniform buffer containing shader uniforms
+	void prepareUniformBuffers()
+	{
+		// Vertex shader uniform buffer block
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffer,
+			sizeof(uboVS)));
+
+		// Map persistent
+		VK_CHECK_RESULT(uniformBuffer.map());
+
+		updateUniformBuffers();
+	}
+
+	void updateUniformBuffers()
+	{
+		uboVS.projection = camera.matrices.perspective;
+		uboVS.modelView = camera.matrices.view * glm::scale(glm::mat4(1.0f), glm::vec3(0.1f));
+		memcpy(uniformBuffer.mapped, &uboVS, sizeof(uboVS));
+	}
+
+	void prepareTextOverlay()
+	{
+		// Load the text rendering shaders
+		std::vector<VkPipelineShaderStageCreateInfo> shaderStages;
+		shaderStages.push_back(loadShader(getShadersPath() + "textoverlay/text.vert.spv", VK_SHADER_STAGE_VERTEX_BIT));
+		shaderStages.push_back(loadShader(getShadersPath() + "textoverlay/text.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT));
+
+		textOverlay = new TextOverlay(
+			vulkanDevice,
+			queue,
+			frameBuffers,
+			swapChain.colorFormat,
+			depthFormat,
+			&width,
+			&height,
+			shaderStages
+			);
+		updateTextOverlay();
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+
+		std::vector<VkCommandBuffer> commandBuffers = {
+			drawCmdBuffers[currentBuffer]
+		};
+		if (textOverlay->visible) {
+			commandBuffers.push_back(textOverlay->cmdBuffers[currentBuffer]);
+		}
+
+		// Command buffer to be submitted to the queue
+		submitInfo.commandBufferCount = static_cast<uint32_t>(commandBuffers.size());
+		submitInfo.pCommandBuffers = commandBuffers.data();
+
+		// Submit to queue
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+
+		VulkanExampleBase::submitFrame();
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		loadAssets();
+		prepareUniformBuffers();
+		setupDescriptorSetLayout();
+		preparePipelines();
+		setupDescriptorPool();
+		setupDescriptorSet();
+		buildCommandBuffers();
+		prepareTextOverlay();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+		if (frameCounter == 0)
+		{
+			vkDeviceWaitIdle(device);
+			updateTextOverlay();
+		}
+	}
+
+	virtual void viewChanged()
+	{
+		vkDeviceWaitIdle(device);
+		updateUniformBuffers();
+		updateTextOverlay();
+	}
+
+	virtual void windowResized()
+	{
+		updateTextOverlay();
+	}
+
+#if !defined(__ANDROID__)
+	virtual void keyPressed(uint32_t keyCode)
+	{
+		switch (keyCode)
+		{
+		case KEY_KPADD:
+		case KEY_SPACE:
+			textOverlay->visible = !textOverlay->visible;
+		}
+	}
+#endif
+};
+
+VULKAN_EXAMPLE_MAIN()
\ No newline at end of file
diff --git a/external/Vulkan/examples/texture/texture.cpp b/external/Vulkan/examples/texture/texture.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..4cc1f498818cc72a97df473f9bf7acdbde13afcb
--- /dev/null
+++ b/external/Vulkan/examples/texture/texture.cpp
@@ -0,0 +1,813 @@
+/*
+* Vulkan Example - Texture loading (and display) example (including mip maps)
+*
+* Copyright (C) 2016-2017 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkanexamplebase.h"
+#include <ktx.h>
+#include <ktxvulkan.h>
+
+#define VERTEX_BUFFER_BIND_ID 0
+#define ENABLE_VALIDATION false
+
+// Vertex layout for this example
+struct Vertex {
+	float pos[3];
+	float uv[2];
+	float normal[3];
+};
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	// Contains all Vulkan objects that are required to store and use a texture
+	// Note that this repository contains a texture class (VulkanTexture.hpp) that encapsulates texture loading functionality in a class that is used in subsequent demos
+	struct Texture {
+		VkSampler sampler;
+		VkImage image;
+		VkImageLayout imageLayout;
+		VkDeviceMemory deviceMemory;
+		VkImageView view;
+		uint32_t width, height;
+		uint32_t mipLevels;
+	} texture;
+
+	struct {
+		VkPipelineVertexInputStateCreateInfo inputState;
+		std::vector<VkVertexInputBindingDescription> bindingDescriptions;
+		std::vector<VkVertexInputAttributeDescription> attributeDescriptions;
+	} vertices;
+
+	vks::Buffer vertexBuffer;
+	vks::Buffer indexBuffer;
+	uint32_t indexCount;
+
+	vks::Buffer uniformBufferVS;
+
+	struct {
+		glm::mat4 projection;
+		glm::mat4 modelView;
+		glm::vec4 viewPos;
+		float lodBias = 0.0f;
+	} uboVS;
+
+	struct {
+		VkPipeline solid;
+	} pipelines;
+
+	VkPipelineLayout pipelineLayout;
+	VkDescriptorSet descriptorSet;
+	VkDescriptorSetLayout descriptorSetLayout;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Texture loading";
+		camera.type = Camera::CameraType::lookat;
+		camera.setPosition(glm::vec3(0.0f, 0.0f, -2.5f));
+		camera.setRotation(glm::vec3(0.0f, 15.0f, 0.0f));
+		camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 256.0f);
+	}
+
+	~VulkanExample()
+	{
+		// Clean up used Vulkan resources
+		// Note : Inherited destructor cleans up resources stored in base class
+
+		destroyTextureImage(texture);
+
+		vkDestroyPipeline(device, pipelines.solid, nullptr);
+
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+
+		vertexBuffer.destroy();
+		indexBuffer.destroy();
+		uniformBufferVS.destroy();
+	}
+
+	// Enable physical device features required for this example
+	virtual void getEnabledFeatures()
+	{
+		// Enable anisotropic filtering if supported
+		if (deviceFeatures.samplerAnisotropy) {
+			enabledFeatures.samplerAnisotropy = VK_TRUE;
+		};
+	}
+
+	/*
+		Upload texture image data to the GPU
+
+		Vulkan offers two types of image tiling (memory layout):
+
+		Linear tiled images:
+			These are stored as is and can be copied directly to. But due to the linear nature they're not a good match for GPUs and format and feature support is very limited.
+			It's not advised to use linear tiled images for anything else than copying from host to GPU if buffer copies are not an option.
+			Linear tiling is thus only implemented for learning purposes, one should always prefer optimal tiled image.
+
+		Optimal tiled images:
+			These are stored in an implementation specific layout matching the capability of the hardware. They usually support more formats and features and are much faster.
+			Optimal tiled images are stored on the device and not accessible by the host. So they can't be written directly to (like liner tiled images) and always require
+			some sort of data copy, either from a buffer or	a linear tiled image.
+
+		In Short: Always use optimal tiled images for rendering.
+	*/
+	void loadTexture()
+	{
+		// We use the Khronos texture format (https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/)
+		std::string filename = getAssetPath() + "textures/metalplate01_rgba.ktx";
+		// Texture data contains 4 channels (RGBA) with unnormalized 8-bit values, this is the most commonly supported format
+		VkFormat format = VK_FORMAT_R8G8B8A8_UNORM;
+
+		ktxResult result;
+		ktxTexture* ktxTexture;
+
+#if defined(__ANDROID__)
+		// Textures are stored inside the apk on Android (compressed)
+		// So they need to be loaded via the asset manager
+		AAsset* asset = AAssetManager_open(androidApp->activity->assetManager, filename.c_str(), AASSET_MODE_STREAMING);
+		if (!asset) {
+			vks::tools::exitFatal("Could not load texture from " + filename + "\n\nThe file may be part of the additional asset pack.\n\nRun \"download_assets.py\" in the repository root to download the latest version.", -1);
+		}
+		size_t size = AAsset_getLength(asset);
+		assert(size > 0);
+
+		ktx_uint8_t *textureData = new ktx_uint8_t[size];
+		AAsset_read(asset, textureData, size);
+		AAsset_close(asset);
+		result = ktxTexture_CreateFromMemory(textureData, size, KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, &ktxTexture);
+		delete[] textureData;
+#else
+		if (!vks::tools::fileExists(filename)) {
+			vks::tools::exitFatal("Could not load texture from " + filename + "\n\nThe file may be part of the additional asset pack.\n\nRun \"download_assets.py\" in the repository root to download the latest version.", -1);
+		}
+		result = ktxTexture_CreateFromNamedFile(filename.c_str(), KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, &ktxTexture);
+#endif
+		assert(result == KTX_SUCCESS);
+
+		// Get properties required for using and upload texture data from the ktx texture object
+		texture.width = ktxTexture->baseWidth;
+		texture.height = ktxTexture->baseHeight;
+		texture.mipLevels = ktxTexture->numLevels;
+		ktx_uint8_t *ktxTextureData = ktxTexture_GetData(ktxTexture);
+		ktx_size_t ktxTextureSize = ktxTexture_GetSize(ktxTexture);
+
+		// We prefer using staging to copy the texture data to a device local optimal image
+		VkBool32 useStaging = true;
+
+		// Only use linear tiling if forced
+		bool forceLinearTiling = false;
+		if (forceLinearTiling) {
+			// Don't use linear if format is not supported for (linear) shader sampling
+			// Get device properties for the requested texture format
+			VkFormatProperties formatProperties;
+			vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &formatProperties);
+			useStaging = !(formatProperties.linearTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT);
+		}
+
+		VkMemoryAllocateInfo memAllocInfo = vks::initializers::memoryAllocateInfo();
+		VkMemoryRequirements memReqs = {};
+
+		if (useStaging) {
+			// Copy data to an optimal tiled image
+			// This loads the texture data into a host local buffer that is copied to the optimal tiled image on the device
+
+			// Create a host-visible staging buffer that contains the raw image data
+			// This buffer will be the data source for copying texture data to the optimal tiled image on the device
+			VkBuffer stagingBuffer;
+			VkDeviceMemory stagingMemory;
+
+			VkBufferCreateInfo bufferCreateInfo = vks::initializers::bufferCreateInfo();
+			bufferCreateInfo.size = ktxTextureSize;
+			// This buffer is used as a transfer source for the buffer copy
+			bufferCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
+			bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+			VK_CHECK_RESULT(vkCreateBuffer(device, &bufferCreateInfo, nullptr, &stagingBuffer));
+
+			// Get memory requirements for the staging buffer (alignment, memory type bits)
+			vkGetBufferMemoryRequirements(device, stagingBuffer, &memReqs);
+			memAllocInfo.allocationSize = memReqs.size;
+			// Get memory type index for a host visible buffer
+			memAllocInfo.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
+			VK_CHECK_RESULT(vkAllocateMemory(device, &memAllocInfo, nullptr, &stagingMemory));
+			VK_CHECK_RESULT(vkBindBufferMemory(device, stagingBuffer, stagingMemory, 0));
+
+			// Copy texture data into host local staging buffer
+			uint8_t *data;
+			VK_CHECK_RESULT(vkMapMemory(device, stagingMemory, 0, memReqs.size, 0, (void **)&data));
+			memcpy(data, ktxTextureData, ktxTextureSize);
+			vkUnmapMemory(device, stagingMemory);
+
+			// Setup buffer copy regions for each mip level
+			std::vector<VkBufferImageCopy> bufferCopyRegions;
+			uint32_t offset = 0;
+
+			for (uint32_t i = 0; i < texture.mipLevels; i++) {
+				// Calculate offset into staging buffer for the current mip level
+				ktx_size_t offset;
+				KTX_error_code ret = ktxTexture_GetImageOffset(ktxTexture, i, 0, 0, &offset);
+				assert(ret == KTX_SUCCESS);
+				// Setup a buffer image copy structure for the current mip level
+				VkBufferImageCopy bufferCopyRegion = {};
+				bufferCopyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+				bufferCopyRegion.imageSubresource.mipLevel = i;
+				bufferCopyRegion.imageSubresource.baseArrayLayer = 0;
+				bufferCopyRegion.imageSubresource.layerCount = 1;
+				bufferCopyRegion.imageExtent.width = ktxTexture->baseWidth >> i;
+				bufferCopyRegion.imageExtent.height = ktxTexture->baseHeight >> i;
+				bufferCopyRegion.imageExtent.depth = 1;
+				bufferCopyRegion.bufferOffset = offset;
+				bufferCopyRegions.push_back(bufferCopyRegion);
+			}
+
+			// Create optimal tiled target image on the device
+			VkImageCreateInfo imageCreateInfo = vks::initializers::imageCreateInfo();
+			imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
+			imageCreateInfo.format = format;
+			imageCreateInfo.mipLevels = texture.mipLevels;
+			imageCreateInfo.arrayLayers = 1;
+			imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
+			imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
+			imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+			// Set initial layout of the image to undefined
+			imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+			imageCreateInfo.extent = { texture.width, texture.height, 1 };
+			imageCreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
+			VK_CHECK_RESULT(vkCreateImage(device, &imageCreateInfo, nullptr, &texture.image));
+
+			vkGetImageMemoryRequirements(device, texture.image, &memReqs);
+			memAllocInfo.allocationSize = memReqs.size;
+			memAllocInfo.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+			VK_CHECK_RESULT(vkAllocateMemory(device, &memAllocInfo, nullptr, &texture.deviceMemory));
+			VK_CHECK_RESULT(vkBindImageMemory(device, texture.image, texture.deviceMemory, 0));
+
+			VkCommandBuffer copyCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+
+			// Image memory barriers for the texture image
+
+			// The sub resource range describes the regions of the image that will be transitioned using the memory barriers below
+			VkImageSubresourceRange subresourceRange = {};
+			// Image only contains color data
+			subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+			// Start at first mip level
+			subresourceRange.baseMipLevel = 0;
+			// We will transition on all mip levels
+			subresourceRange.levelCount = texture.mipLevels;
+			// The 2D texture only has one layer
+			subresourceRange.layerCount = 1;
+
+			// Transition the texture image layout to transfer target, so we can safely copy our buffer data to it.
+			VkImageMemoryBarrier imageMemoryBarrier = vks::initializers::imageMemoryBarrier();;
+			imageMemoryBarrier.image = texture.image;
+			imageMemoryBarrier.subresourceRange = subresourceRange;
+			imageMemoryBarrier.srcAccessMask = 0;
+			imageMemoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+			imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+			imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+
+			// Insert a memory dependency at the proper pipeline stages that will execute the image layout transition
+			// Source pipeline stage is host write/read execution (VK_PIPELINE_STAGE_HOST_BIT)
+			// Destination pipeline stage is copy command execution (VK_PIPELINE_STAGE_TRANSFER_BIT)
+			vkCmdPipelineBarrier(
+				copyCmd,
+				VK_PIPELINE_STAGE_HOST_BIT,
+				VK_PIPELINE_STAGE_TRANSFER_BIT,
+				0,
+				0, nullptr,
+				0, nullptr,
+				1, &imageMemoryBarrier);
+
+			// Copy mip levels from staging buffer
+			vkCmdCopyBufferToImage(
+				copyCmd,
+				stagingBuffer,
+				texture.image,
+				VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+				static_cast<uint32_t>(bufferCopyRegions.size()),
+				bufferCopyRegions.data());
+
+			// Once the data has been uploaded we transfer to the texture image to the shader read layout, so it can be sampled from
+			imageMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+			imageMemoryBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
+			imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+			imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+
+			// Insert a memory dependency at the proper pipeline stages that will execute the image layout transition
+			// Source pipeline stage is copy command execution (VK_PIPELINE_STAGE_TRANSFER_BIT)
+			// Destination pipeline stage fragment shader access (VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT)
+			vkCmdPipelineBarrier(
+				copyCmd,
+				VK_PIPELINE_STAGE_TRANSFER_BIT,
+				VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
+				0,
+				0, nullptr,
+				0, nullptr,
+				1, &imageMemoryBarrier);
+
+			// Store current layout for later reuse
+			texture.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+
+			vulkanDevice->flushCommandBuffer(copyCmd, queue, true);
+
+			// Clean up staging resources
+			vkFreeMemory(device, stagingMemory, nullptr);
+			vkDestroyBuffer(device, stagingBuffer, nullptr);
+		} else {
+			// Copy data to a linear tiled image
+
+			VkImage mappableImage;
+			VkDeviceMemory mappableMemory;
+
+			// Load mip map level 0 to linear tiling image
+			VkImageCreateInfo imageCreateInfo = vks::initializers::imageCreateInfo();
+			imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
+			imageCreateInfo.format = format;
+			imageCreateInfo.mipLevels = 1;
+			imageCreateInfo.arrayLayers = 1;
+			imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
+			imageCreateInfo.tiling = VK_IMAGE_TILING_LINEAR;
+			imageCreateInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT;
+			imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+			imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
+			imageCreateInfo.extent = { texture.width, texture.height, 1 };
+			VK_CHECK_RESULT(vkCreateImage(device, &imageCreateInfo, nullptr, &mappableImage));
+
+			// Get memory requirements for this image like size and alignment
+			vkGetImageMemoryRequirements(device, mappableImage, &memReqs);
+			// Set memory allocation size to required memory size
+			memAllocInfo.allocationSize = memReqs.size;
+			// Get memory type that can be mapped to host memory
+			memAllocInfo.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
+			VK_CHECK_RESULT(vkAllocateMemory(device, &memAllocInfo, nullptr, &mappableMemory));
+			VK_CHECK_RESULT(vkBindImageMemory(device, mappableImage, mappableMemory, 0));
+
+			// Map image memory
+			void *data;
+			VK_CHECK_RESULT(vkMapMemory(device, mappableMemory, 0, memReqs.size, 0, &data));
+			// Copy image data of the first mip level into memory
+			memcpy(data, ktxTextureData, memReqs.size);
+			vkUnmapMemory(device, mappableMemory);
+
+			// Linear tiled images don't need to be staged and can be directly used as textures
+			texture.image = mappableImage;
+			texture.deviceMemory = mappableMemory;
+			texture.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+
+			// Setup image memory barrier transfer image to shader read layout
+			VkCommandBuffer copyCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+
+			// The sub resource range describes the regions of the image we will be transition
+			VkImageSubresourceRange subresourceRange = {};
+			subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+			subresourceRange.baseMipLevel = 0;
+			subresourceRange.levelCount = 1;
+			subresourceRange.layerCount = 1;
+
+			// Transition the texture image layout to shader read, so it can be sampled from
+			VkImageMemoryBarrier imageMemoryBarrier = vks::initializers::imageMemoryBarrier();;
+			imageMemoryBarrier.image = texture.image;
+			imageMemoryBarrier.subresourceRange = subresourceRange;
+			imageMemoryBarrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT;
+			imageMemoryBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
+			imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
+			imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+
+			// Insert a memory dependency at the proper pipeline stages that will execute the image layout transition
+			// Source pipeline stage is host write/read execution (VK_PIPELINE_STAGE_HOST_BIT)
+			// Destination pipeline stage fragment shader access (VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT)
+			vkCmdPipelineBarrier(
+				copyCmd,
+				VK_PIPELINE_STAGE_HOST_BIT,
+				VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
+				0,
+				0, nullptr,
+				0, nullptr,
+				1, &imageMemoryBarrier);
+
+			vulkanDevice->flushCommandBuffer(copyCmd, queue, true);
+		}
+
+		ktxTexture_Destroy(ktxTexture);
+
+		// Create a texture sampler
+		// In Vulkan textures are accessed by samplers
+		// This separates all the sampling information from the texture data. This means you could have multiple sampler objects for the same texture with different settings
+		// Note: Similar to the samplers available with OpenGL 3.3
+		VkSamplerCreateInfo sampler = vks::initializers::samplerCreateInfo();
+		sampler.magFilter = VK_FILTER_LINEAR;
+		sampler.minFilter = VK_FILTER_LINEAR;
+		sampler.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+		sampler.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+		sampler.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+		sampler.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+		sampler.mipLodBias = 0.0f;
+		sampler.compareOp = VK_COMPARE_OP_NEVER;
+		sampler.minLod = 0.0f;
+		// Set max level-of-detail to mip level count of the texture
+		sampler.maxLod = (useStaging) ? (float)texture.mipLevels : 0.0f;
+		// Enable anisotropic filtering
+		// This feature is optional, so we must check if it's supported on the device
+		if (vulkanDevice->features.samplerAnisotropy) {
+			// Use max. level of anisotropy for this example
+			sampler.maxAnisotropy = vulkanDevice->properties.limits.maxSamplerAnisotropy;
+			sampler.anisotropyEnable = VK_TRUE;
+		} else {
+			// The device does not support anisotropic filtering
+			sampler.maxAnisotropy = 1.0;
+			sampler.anisotropyEnable = VK_FALSE;
+		}
+		sampler.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
+		VK_CHECK_RESULT(vkCreateSampler(device, &sampler, nullptr, &texture.sampler));
+
+		// Create image view
+		// Textures are not directly accessed by the shaders and
+		// are abstracted by image views containing additional
+		// information and sub resource ranges
+		VkImageViewCreateInfo view = vks::initializers::imageViewCreateInfo();
+		view.viewType = VK_IMAGE_VIEW_TYPE_2D;
+		view.format = format;
+		view.components = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A };
+		// The subresource range describes the set of mip levels (and array layers) that can be accessed through this image view
+		// It's possible to create multiple image views for a single image referring to different (and/or overlapping) ranges of the image
+		view.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+		view.subresourceRange.baseMipLevel = 0;
+		view.subresourceRange.baseArrayLayer = 0;
+		view.subresourceRange.layerCount = 1;
+		// Linear tiling usually won't support mip maps
+		// Only set mip map count if optimal tiling is used
+		view.subresourceRange.levelCount = (useStaging) ? texture.mipLevels : 1;
+		// The view will be based on the texture's image
+		view.image = texture.image;
+		VK_CHECK_RESULT(vkCreateImageView(device, &view, nullptr, &texture.view));
+	}
+
+	// Free all Vulkan resources used by a texture object
+	void destroyTextureImage(Texture texture)
+	{
+		vkDestroyImageView(device, texture.view, nullptr);
+		vkDestroyImage(device, texture.image, nullptr);
+		vkDestroySampler(device, texture.sampler, nullptr);
+		vkFreeMemory(device, texture.deviceMemory, nullptr);
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		clearValues[0].color = defaultClearColor;
+		clearValues[1].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.offset.x = 0;
+		renderPassBeginInfo.renderArea.offset.y = 0;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		renderPassBeginInfo.clearValueCount = 2;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			// Set target frame buffer
+			renderPassBeginInfo.framebuffer = frameBuffers[i];
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+			VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL);
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.solid);
+
+			VkDeviceSize offsets[1] = { 0 };
+			vkCmdBindVertexBuffers(drawCmdBuffers[i], VERTEX_BUFFER_BIND_ID, 1, &vertexBuffer.buffer, offsets);
+			vkCmdBindIndexBuffer(drawCmdBuffers[i], indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT32);
+
+			vkCmdDrawIndexed(drawCmdBuffers[i], indexCount, 1, 0, 0, 0);
+
+			drawUI(drawCmdBuffers[i]);
+
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+
+		// Command buffer to be submitted to the queue
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+
+		// Submit to queue
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+
+		VulkanExampleBase::submitFrame();
+	}
+
+	void generateQuad()
+	{
+		// Setup vertices for a single uv-mapped quad made from two triangles
+		std::vector<Vertex> vertices =
+		{
+			{ {  1.0f,  1.0f, 0.0f }, { 1.0f, 1.0f },{ 0.0f, 0.0f, 1.0f } },
+			{ { -1.0f,  1.0f, 0.0f }, { 0.0f, 1.0f },{ 0.0f, 0.0f, 1.0f } },
+			{ { -1.0f, -1.0f, 0.0f }, { 0.0f, 0.0f },{ 0.0f, 0.0f, 1.0f } },
+			{ {  1.0f, -1.0f, 0.0f }, { 1.0f, 0.0f },{ 0.0f, 0.0f, 1.0f } }
+		};
+
+		// Setup indices
+		std::vector<uint32_t> indices = { 0,1,2, 2,3,0 };
+		indexCount = static_cast<uint32_t>(indices.size());
+
+		// Create buffers
+		// For the sake of simplicity we won't stage the vertex data to the gpu memory
+		// Vertex buffer
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&vertexBuffer,
+			vertices.size() * sizeof(Vertex),
+			vertices.data()));
+		// Index buffer
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&indexBuffer,
+			indices.size() * sizeof(uint32_t),
+			indices.data()));
+	}
+
+	void setupVertexDescriptions()
+	{
+		// Binding description
+		vertices.bindingDescriptions.resize(1);
+		vertices.bindingDescriptions[0] =
+			vks::initializers::vertexInputBindingDescription(
+				VERTEX_BUFFER_BIND_ID,
+				sizeof(Vertex),
+				VK_VERTEX_INPUT_RATE_VERTEX);
+
+		// Attribute descriptions
+		// Describes memory layout and shader positions
+		vertices.attributeDescriptions.resize(3);
+		// Location 0 : Position
+		vertices.attributeDescriptions[0] =
+			vks::initializers::vertexInputAttributeDescription(
+				VERTEX_BUFFER_BIND_ID,
+				0,
+				VK_FORMAT_R32G32B32_SFLOAT,
+				offsetof(Vertex, pos));
+		// Location 1 : Texture coordinates
+		vertices.attributeDescriptions[1] =
+			vks::initializers::vertexInputAttributeDescription(
+				VERTEX_BUFFER_BIND_ID,
+				1,
+				VK_FORMAT_R32G32_SFLOAT,
+				offsetof(Vertex, uv));
+		// Location 1 : Vertex normal
+		vertices.attributeDescriptions[2] =
+			vks::initializers::vertexInputAttributeDescription(
+				VERTEX_BUFFER_BIND_ID,
+				2,
+				VK_FORMAT_R32G32B32_SFLOAT,
+				offsetof(Vertex, normal));
+
+		vertices.inputState = vks::initializers::pipelineVertexInputStateCreateInfo();
+		vertices.inputState.vertexBindingDescriptionCount = static_cast<uint32_t>(vertices.bindingDescriptions.size());
+		vertices.inputState.pVertexBindingDescriptions = vertices.bindingDescriptions.data();
+		vertices.inputState.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertices.attributeDescriptions.size());
+		vertices.inputState.pVertexAttributeDescriptions = vertices.attributeDescriptions.data();
+	}
+
+	void setupDescriptorPool()
+	{
+		// Example uses one ubo and one image sampler
+		std::vector<VkDescriptorPoolSize> poolSizes =
+		{
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1),
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1)
+		};
+
+		VkDescriptorPoolCreateInfo descriptorPoolInfo =
+			vks::initializers::descriptorPoolCreateInfo(
+				static_cast<uint32_t>(poolSizes.size()),
+				poolSizes.data(),
+				2);
+
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+	}
+
+	void setupDescriptorSetLayout()
+	{
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings =
+		{
+			// Binding 0 : Vertex shader uniform buffer
+			vks::initializers::descriptorSetLayoutBinding(
+				VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+				VK_SHADER_STAGE_VERTEX_BIT,
+				0),
+			// Binding 1 : Fragment shader image sampler
+			vks::initializers::descriptorSetLayoutBinding(
+				VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+				VK_SHADER_STAGE_FRAGMENT_BIT,
+				1)
+		};
+
+		VkDescriptorSetLayoutCreateInfo descriptorLayout =
+			vks::initializers::descriptorSetLayoutCreateInfo(
+				setLayoutBindings.data(),
+				static_cast<uint32_t>(setLayoutBindings.size()));
+
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout));
+
+		VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo =
+			vks::initializers::pipelineLayoutCreateInfo(
+				&descriptorSetLayout,
+				1);
+
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayout));
+	}
+
+	void setupDescriptorSet()
+	{
+		VkDescriptorSetAllocateInfo allocInfo =
+			vks::initializers::descriptorSetAllocateInfo(
+				descriptorPool,
+				&descriptorSetLayout,
+				1);
+
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet));
+
+		// Setup a descriptor image info for the current texture to be used as a combined image sampler
+		VkDescriptorImageInfo textureDescriptor;
+		textureDescriptor.imageView = texture.view;				// The image's view (images are never directly accessed by the shader, but rather through views defining subresources)
+		textureDescriptor.sampler = texture.sampler;			// The sampler (Telling the pipeline how to sample the texture, including repeat, border, etc.)
+		textureDescriptor.imageLayout = texture.imageLayout;	// The current layout of the image (Note: Should always fit the actual use, e.g. shader read)
+
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets =
+		{
+			// Binding 0 : Vertex shader uniform buffer
+			vks::initializers::writeDescriptorSet(
+				descriptorSet,
+				VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+				0,
+				&uniformBufferVS.descriptor),
+			// Binding 1 : Fragment shader texture sampler
+			//	Fragment shader: layout (binding = 1) uniform sampler2D samplerColor;
+			vks::initializers::writeDescriptorSet(
+				descriptorSet,
+				VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,		// The descriptor set will use a combined image sampler (sampler and image could be split)
+				1,												// Shader binding point 1
+				&textureDescriptor)								// Pointer to the descriptor image for our texture
+		};
+
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL);
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState =
+			vks::initializers::pipelineInputAssemblyStateCreateInfo(
+				VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
+				0,
+				VK_FALSE);
+
+		VkPipelineRasterizationStateCreateInfo rasterizationState =
+			vks::initializers::pipelineRasterizationStateCreateInfo(
+				VK_POLYGON_MODE_FILL,
+				VK_CULL_MODE_NONE,
+				VK_FRONT_FACE_COUNTER_CLOCKWISE,
+				0);
+
+		VkPipelineColorBlendAttachmentState blendAttachmentState =
+			vks::initializers::pipelineColorBlendAttachmentState(
+				0xf,
+				VK_FALSE);
+
+		VkPipelineColorBlendStateCreateInfo colorBlendState =
+			vks::initializers::pipelineColorBlendStateCreateInfo(
+				1,
+				&blendAttachmentState);
+
+		VkPipelineDepthStencilStateCreateInfo depthStencilState =
+			vks::initializers::pipelineDepthStencilStateCreateInfo(
+				VK_TRUE,
+				VK_TRUE,
+				VK_COMPARE_OP_LESS_OR_EQUAL);
+
+		VkPipelineViewportStateCreateInfo viewportState =
+			vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+
+		VkPipelineMultisampleStateCreateInfo multisampleState =
+			vks::initializers::pipelineMultisampleStateCreateInfo(
+				VK_SAMPLE_COUNT_1_BIT,
+				0);
+
+		std::vector<VkDynamicState> dynamicStateEnables = {
+			VK_DYNAMIC_STATE_VIEWPORT,
+			VK_DYNAMIC_STATE_SCISSOR
+		};
+		VkPipelineDynamicStateCreateInfo dynamicState =
+			vks::initializers::pipelineDynamicStateCreateInfo(
+				dynamicStateEnables.data(),
+				static_cast<uint32_t>(dynamicStateEnables.size()),
+				0);
+
+		// Load shaders
+		std::array<VkPipelineShaderStageCreateInfo,2> shaderStages;
+
+		shaderStages[0] = loadShader(getShadersPath() + "texture/texture.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "texture/texture.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+
+		VkGraphicsPipelineCreateInfo pipelineCreateInfo =
+			vks::initializers::pipelineCreateInfo(
+				pipelineLayout,
+				renderPass,
+				0);
+
+		pipelineCreateInfo.pVertexInputState = &vertices.inputState;
+		pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState;
+		pipelineCreateInfo.pRasterizationState = &rasterizationState;
+		pipelineCreateInfo.pColorBlendState = &colorBlendState;
+		pipelineCreateInfo.pMultisampleState = &multisampleState;
+		pipelineCreateInfo.pViewportState = &viewportState;
+		pipelineCreateInfo.pDepthStencilState = &depthStencilState;
+		pipelineCreateInfo.pDynamicState = &dynamicState;
+		pipelineCreateInfo.stageCount = static_cast<uint32_t>(shaderStages.size());
+		pipelineCreateInfo.pStages = shaderStages.data();
+
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipelines.solid));
+	}
+
+	// Prepare and initialize uniform buffer containing shader uniforms
+	void prepareUniformBuffers()
+	{
+		// Vertex shader uniform buffer block
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBufferVS,
+			sizeof(uboVS),
+			&uboVS));
+		VK_CHECK_RESULT(uniformBufferVS.map());
+
+		updateUniformBuffers();
+	}
+
+	void updateUniformBuffers()
+	{
+		uboVS.projection = camera.matrices.perspective;
+		uboVS.modelView = camera.matrices.view;
+		uboVS.viewPos = camera.viewPos;
+		memcpy(uniformBufferVS.mapped, &uboVS, sizeof(uboVS));
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		loadTexture();
+		generateQuad();
+		setupVertexDescriptions();
+		prepareUniformBuffers();
+		setupDescriptorSetLayout();
+		preparePipelines();
+		setupDescriptorPool();
+		setupDescriptorSet();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+	}
+
+	virtual void viewChanged()
+	{
+		updateUniformBuffers();
+	}
+
+	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
+	{
+		if (overlay->header("Settings")) {
+			if (overlay->sliderFloat("LOD bias", &uboVS.lodBias, 0.0f, (float)texture.mipLevels)) {
+				updateUniformBuffers();
+			}
+		}
+	}
+};
+
+VULKAN_EXAMPLE_MAIN()
diff --git a/external/Vulkan/examples/texture3d/texture3d.cpp b/external/Vulkan/examples/texture3d/texture3d.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f2f672e2dfd1bdcc9150b3ea035863895763972c
--- /dev/null
+++ b/external/Vulkan/examples/texture3d/texture3d.cpp
@@ -0,0 +1,786 @@
+/*
+* Vulkan Example - 3D texture loading (and generation using perlin noise) example
+*
+* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkanexamplebase.h"
+
+#define VERTEX_BUFFER_BIND_ID 0
+#define ENABLE_VALIDATION false
+
+// Vertex layout for this example
+struct Vertex {
+	float pos[3];
+	float uv[2];
+	float normal[3];
+};
+
+// Translation of Ken Perlin's JAVA implementation (http://mrl.nyu.edu/~perlin/noise/)
+template <typename T>
+class PerlinNoise
+{
+private:
+	uint32_t permutations[512];
+	T fade(T t)
+	{
+		return t * t * t * (t * (t * (T)6 - (T)15) + (T)10);
+	}
+	T lerp(T t, T a, T b)
+	{
+		return a + t * (b - a);
+	}
+	T grad(int hash, T x, T y, T z)
+	{
+		// Convert LO 4 bits of hash code into 12 gradient directions
+		int h = hash & 15;
+		T u = h < 8 ? x : y;
+		T v = h < 4 ? y : h == 12 || h == 14 ? x : z;
+		return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v);
+	}
+public:
+	PerlinNoise()
+	{
+		// Generate random lookup for permutations containing all numbers from 0..255
+		std::vector<uint8_t> plookup;
+		plookup.resize(256);
+		std::iota(plookup.begin(), plookup.end(), 0);
+		std::default_random_engine rndEngine(std::random_device{}());
+		std::shuffle(plookup.begin(), plookup.end(), rndEngine);
+
+		for (uint32_t i = 0; i < 256; i++)
+		{
+			permutations[i] = permutations[256 + i] = plookup[i];
+		}
+	}
+	T noise(T x, T y, T z)
+	{
+		// Find unit cube that contains point
+		int32_t X = (int32_t)floor(x) & 255;
+		int32_t Y = (int32_t)floor(y) & 255;
+		int32_t Z = (int32_t)floor(z) & 255;
+		// Find relative x,y,z of point in cube
+		x -= floor(x);
+		y -= floor(y);
+		z -= floor(z);
+
+		// Compute fade curves for each of x,y,z
+		T u = fade(x);
+		T v = fade(y);
+		T w = fade(z);
+
+		// Hash coordinates of the 8 cube corners
+		uint32_t A = permutations[X] + Y;
+		uint32_t AA = permutations[A] + Z;
+		uint32_t AB = permutations[A + 1] + Z;
+		uint32_t B = permutations[X + 1] + Y;
+		uint32_t BA = permutations[B] + Z;
+		uint32_t BB = permutations[B + 1] + Z;
+
+		// And add blended results for 8 corners of the cube;
+		T res = lerp(w, lerp(v,
+			lerp(u, grad(permutations[AA], x, y, z), grad(permutations[BA], x - 1, y, z)), lerp(u, grad(permutations[AB], x, y - 1, z), grad(permutations[BB], x - 1, y - 1, z))),
+			lerp(v, lerp(u, grad(permutations[AA + 1], x, y, z - 1), grad(permutations[BA + 1], x - 1, y, z - 1)), lerp(u, grad(permutations[AB + 1], x, y - 1, z - 1), grad(permutations[BB + 1], x - 1, y - 1, z - 1))));
+		return res;
+	}
+};
+
+// Fractal noise generator based on perlin noise above
+template <typename T>
+class FractalNoise
+{
+private:
+	PerlinNoise<float> perlinNoise;
+	uint32_t octaves;
+	T frequency;
+	T amplitude;
+	T persistence;
+public:
+
+	FractalNoise(const PerlinNoise<T> &perlinNoise)
+	{
+		this->perlinNoise = perlinNoise;
+		octaves = 6;
+		persistence = (T)0.5;
+	}
+
+	T noise(T x, T y, T z)
+	{
+		T sum = 0;
+		T frequency = (T)1;
+		T amplitude = (T)1;
+		T max = (T)0;
+		for (uint32_t i = 0; i < octaves; i++)
+		{
+			sum += perlinNoise.noise(x * frequency, y * frequency, z * frequency) * amplitude;
+			max += amplitude;
+			amplitude *= persistence;
+			frequency *= (T)2;
+		}
+
+		sum = sum / max;
+		return (sum + (T)1.0) / (T)2.0;
+	}
+};
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	// Contains all Vulkan objects that are required to store and use a 3D texture
+	struct Texture {
+		VkSampler sampler = VK_NULL_HANDLE;
+		VkImage image = VK_NULL_HANDLE;
+		VkImageLayout imageLayout;
+		VkDeviceMemory deviceMemory = VK_NULL_HANDLE;
+		VkImageView view = VK_NULL_HANDLE;
+		VkDescriptorImageInfo descriptor;
+		VkFormat format;
+		uint32_t width, height, depth;
+		uint32_t mipLevels;
+	} texture;
+
+	struct {
+		VkPipelineVertexInputStateCreateInfo inputState;
+		std::vector<VkVertexInputBindingDescription> inputBinding;
+		std::vector<VkVertexInputAttributeDescription> inputAttributes;
+	} vertices;
+
+	vks::Buffer vertexBuffer;
+	vks::Buffer indexBuffer;
+	uint32_t indexCount;
+
+	vks::Buffer uniformBufferVS;
+
+	struct UboVS {
+		glm::mat4 projection;
+		glm::mat4 modelView;
+		glm::vec4 viewPos;
+		float depth = 0.0f;
+	} uboVS;
+
+	struct {
+		VkPipeline solid;
+	} pipelines;
+
+	VkPipelineLayout pipelineLayout;
+	VkDescriptorSet descriptorSet;
+	VkDescriptorSetLayout descriptorSetLayout;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "3D textures";
+		camera.type = Camera::CameraType::lookat;
+		camera.setPosition(glm::vec3(0.0f, 0.0f, -2.5f));
+		camera.setRotation(glm::vec3(0.0f, 15.0f, 0.0f));
+		camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 256.0f);
+		srand((unsigned int)time(NULL));
+	}
+
+	~VulkanExample()
+	{
+		// Clean up used Vulkan resources
+		// Note : Inherited destructor cleans up resources stored in base class
+
+		destroyTextureImage(texture);
+
+		vkDestroyPipeline(device, pipelines.solid, nullptr);
+
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+
+		vertexBuffer.destroy();
+		indexBuffer.destroy();
+		uniformBufferVS.destroy();
+	}
+
+	// Prepare all Vulkan resources for the 3D texture (including descriptors)
+	// Does not fill the texture with data
+	void prepareNoiseTexture(uint32_t width, uint32_t height, uint32_t depth)
+	{
+		// A 3D texture is described as width x height x depth
+		texture.width = width;
+		texture.height = height;
+		texture.depth = depth;
+		texture.mipLevels = 1;
+		texture.format = VK_FORMAT_R8_UNORM;
+
+		// Format support check
+		// 3D texture support in Vulkan is mandatory (in contrast to OpenGL) so no need to check if it's supported
+		VkFormatProperties formatProperties;
+		vkGetPhysicalDeviceFormatProperties(physicalDevice, texture.format, &formatProperties);
+		// Check if format supports transfer
+		if (!(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_TRANSFER_DST_BIT))
+		{
+			std::cout << "Error: Device does not support flag TRANSFER_DST for selected texture format!" << std::endl;
+			return;
+		}
+		// Check if GPU supports requested 3D texture dimensions
+		uint32_t maxImageDimension3D(vulkanDevice->properties.limits.maxImageDimension3D);
+		if (width > maxImageDimension3D || height > maxImageDimension3D || depth > maxImageDimension3D)
+		{
+			std::cout << "Error: Requested texture dimensions is greater than supported 3D texture dimension!" << std::endl;
+			return;
+		}
+
+		// Create optimal tiled target image
+		VkImageCreateInfo imageCreateInfo = vks::initializers::imageCreateInfo();
+		imageCreateInfo.imageType = VK_IMAGE_TYPE_3D;
+		imageCreateInfo.format = texture.format;
+		imageCreateInfo.mipLevels = texture.mipLevels;
+		imageCreateInfo.arrayLayers = 1;
+		imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
+		imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
+		imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+		imageCreateInfo.extent.width = texture.width;
+		imageCreateInfo.extent.height = texture.height;
+		imageCreateInfo.extent.depth = texture.depth;
+		// Set initial layout of the image to undefined
+		imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+		imageCreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
+		VK_CHECK_RESULT(vkCreateImage(device, &imageCreateInfo, nullptr, &texture.image));
+
+		// Device local memory to back up image
+		VkMemoryAllocateInfo memAllocInfo = vks::initializers::memoryAllocateInfo();
+		VkMemoryRequirements memReqs = {};
+		vkGetImageMemoryRequirements(device, texture.image, &memReqs);
+		memAllocInfo.allocationSize = memReqs.size;
+		memAllocInfo.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+		VK_CHECK_RESULT(vkAllocateMemory(device, &memAllocInfo, nullptr, &texture.deviceMemory));
+		VK_CHECK_RESULT(vkBindImageMemory(device, texture.image, texture.deviceMemory, 0));
+
+		// Create sampler
+		VkSamplerCreateInfo sampler = vks::initializers::samplerCreateInfo();
+		sampler.magFilter = VK_FILTER_LINEAR;
+		sampler.minFilter = VK_FILTER_LINEAR;
+		sampler.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+		sampler.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+		sampler.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+		sampler.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+		sampler.mipLodBias = 0.0f;
+		sampler.compareOp = VK_COMPARE_OP_NEVER;
+		sampler.minLod = 0.0f;
+		sampler.maxLod = 0.0f;
+		sampler.maxAnisotropy = 1.0;
+		sampler.anisotropyEnable = VK_FALSE;
+		sampler.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
+		VK_CHECK_RESULT(vkCreateSampler(device, &sampler, nullptr, &texture.sampler));
+
+		// Create image view
+		VkImageViewCreateInfo view = vks::initializers::imageViewCreateInfo();
+		view.image = texture.image;
+		view.viewType = VK_IMAGE_VIEW_TYPE_3D;
+		view.format = texture.format;
+		view.components = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A };
+		view.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+		view.subresourceRange.baseMipLevel = 0;
+		view.subresourceRange.baseArrayLayer = 0;
+		view.subresourceRange.layerCount = 1;
+		view.subresourceRange.levelCount = 1;
+		VK_CHECK_RESULT(vkCreateImageView(device, &view, nullptr, &texture.view));
+
+		// Fill image descriptor image info to be used descriptor set setup
+		texture.descriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+		texture.descriptor.imageView = texture.view;
+		texture.descriptor.sampler = texture.sampler;
+
+		updateNoiseTexture();
+	}
+
+	// Generate randomized noise and upload it to the 3D texture using staging
+	void updateNoiseTexture()
+	{
+		const uint32_t texMemSize = texture.width * texture.height * texture.depth;
+
+		uint8_t *data = new uint8_t[texMemSize];
+		memset(data, 0, texMemSize);
+
+		// Generate perlin based noise
+		std::cout << "Generating " << texture.width << " x " << texture.height << " x " << texture.depth << " noise texture..." << std::endl;
+
+		auto tStart = std::chrono::high_resolution_clock::now();
+
+		PerlinNoise<float> perlinNoise;
+		FractalNoise<float> fractalNoise(perlinNoise);
+
+		const float noiseScale = static_cast<float>(rand() % 10) + 4.0f;
+
+#pragma omp parallel for
+		for (int32_t z = 0; z < texture.depth; z++)
+		{
+			for (int32_t y = 0; y < texture.height; y++)
+			{
+				for (int32_t x = 0; x < texture.width; x++)
+				{
+					float nx = (float)x / (float)texture.width;
+					float ny = (float)y / (float)texture.height;
+					float nz = (float)z / (float)texture.depth;
+#define FRACTAL
+#ifdef FRACTAL
+					float n = fractalNoise.noise(nx * noiseScale, ny * noiseScale, nz * noiseScale);
+#else
+					float n = 20.0 * perlinNoise.noise(nx, ny, nz);
+#endif
+					n = n - floor(n);
+
+					data[x + y * texture.width + z * texture.width * texture.height] = static_cast<uint8_t>(floor(n * 255));
+				}
+			}
+		}
+
+		auto tEnd = std::chrono::high_resolution_clock::now();
+		auto tDiff = std::chrono::duration<double, std::milli>(tEnd - tStart).count();
+
+		std::cout << "Done in " << tDiff << "ms" << std::endl;
+
+		// Create a host-visible staging buffer that contains the raw image data
+		VkBuffer stagingBuffer;
+		VkDeviceMemory stagingMemory;
+
+		// Buffer object
+		VkBufferCreateInfo bufferCreateInfo = vks::initializers::bufferCreateInfo();
+		bufferCreateInfo.size = texMemSize;
+		bufferCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
+		bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+		VK_CHECK_RESULT(vkCreateBuffer(device, &bufferCreateInfo, nullptr, &stagingBuffer));
+
+		// Allocate host visible memory for data upload
+		VkMemoryAllocateInfo memAllocInfo = vks::initializers::memoryAllocateInfo();
+		VkMemoryRequirements memReqs = {};
+		vkGetBufferMemoryRequirements(device, stagingBuffer, &memReqs);
+		memAllocInfo.allocationSize = memReqs.size;
+		memAllocInfo.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
+		VK_CHECK_RESULT(vkAllocateMemory(device, &memAllocInfo, nullptr, &stagingMemory));
+		VK_CHECK_RESULT(vkBindBufferMemory(device, stagingBuffer, stagingMemory, 0));
+
+		// Copy texture data into staging buffer
+		uint8_t *mapped;
+		VK_CHECK_RESULT(vkMapMemory(device, stagingMemory, 0, memReqs.size, 0, (void **)&mapped));
+		memcpy(mapped, data, texMemSize);
+		vkUnmapMemory(device, stagingMemory);
+
+		VkCommandBuffer copyCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+
+		// The sub resource range describes the regions of the image we will be transitioned
+		VkImageSubresourceRange subresourceRange = {};
+		subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+		subresourceRange.baseMipLevel = 0;
+		subresourceRange.levelCount = 1;
+		subresourceRange.layerCount = 1;
+
+		// Optimal image will be used as destination for the copy, so we must transfer from our
+		// initial undefined image layout to the transfer destination layout
+		vks::tools::setImageLayout(
+			copyCmd,
+			texture.image,
+			VK_IMAGE_LAYOUT_UNDEFINED,
+			VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+			subresourceRange);
+
+		// Copy 3D noise data to texture
+
+		// Setup buffer copy regions
+		VkBufferImageCopy bufferCopyRegion{};
+		bufferCopyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+		bufferCopyRegion.imageSubresource.mipLevel = 0;
+		bufferCopyRegion.imageSubresource.baseArrayLayer = 0;
+		bufferCopyRegion.imageSubresource.layerCount = 1;
+		bufferCopyRegion.imageExtent.width = texture.width;
+		bufferCopyRegion.imageExtent.height = texture.height;
+		bufferCopyRegion.imageExtent.depth = texture.depth;
+
+		vkCmdCopyBufferToImage(
+			copyCmd,
+			stagingBuffer,
+			texture.image,
+			VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+			1,
+			&bufferCopyRegion);
+
+		// Change texture image layout to shader read after all mip levels have been copied
+		texture.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+		vks::tools::setImageLayout(
+			copyCmd,
+			texture.image,
+			VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+			texture.imageLayout,
+			subresourceRange);
+
+		vulkanDevice->flushCommandBuffer(copyCmd, queue, true);
+
+		// Clean up staging resources
+		delete[] data;
+		vkFreeMemory(device, stagingMemory, nullptr);
+		vkDestroyBuffer(device, stagingBuffer, nullptr);
+	}
+
+	// Free all Vulkan resources used a texture object
+	void destroyTextureImage(Texture texture)
+	{
+		if (texture.view != VK_NULL_HANDLE)
+			vkDestroyImageView(device, texture.view, nullptr);
+		if (texture.image != VK_NULL_HANDLE)
+			vkDestroyImage(device, texture.image, nullptr);
+		if (texture.sampler != VK_NULL_HANDLE)
+			vkDestroySampler(device, texture.sampler, nullptr);
+		if (texture.deviceMemory != VK_NULL_HANDLE)
+			vkFreeMemory(device, texture.deviceMemory, nullptr);
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		clearValues[0].color = defaultClearColor;
+		clearValues[1].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.offset.x = 0;
+		renderPassBeginInfo.renderArea.offset.y = 0;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		renderPassBeginInfo.clearValueCount = 2;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			// Set target frame buffer
+			renderPassBeginInfo.framebuffer = frameBuffers[i];
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+			VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL);
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.solid);
+
+			VkDeviceSize offsets[1] = { 0 };
+			vkCmdBindVertexBuffers(drawCmdBuffers[i], VERTEX_BUFFER_BIND_ID, 1, &vertexBuffer.buffer, offsets);
+			vkCmdBindIndexBuffer(drawCmdBuffers[i], indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT32);
+			vkCmdDrawIndexed(drawCmdBuffers[i], indexCount, 1, 0, 0, 0);
+
+			drawUI(drawCmdBuffers[i]);
+
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+
+		// Command buffer to be submitted to the queue
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+
+		// Submit to queue
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+
+		VulkanExampleBase::submitFrame();
+	}
+
+	void generateQuad()
+	{
+		// Setup vertices for a single uv-mapped quad made from two triangles
+		std::vector<Vertex> vertices =
+		{
+			{ {  1.0f,  1.0f, 0.0f }, { 1.0f, 1.0f },{ 0.0f, 0.0f, 1.0f } },
+			{ { -1.0f,  1.0f, 0.0f }, { 0.0f, 1.0f },{ 0.0f, 0.0f, 1.0f } },
+			{ { -1.0f, -1.0f, 0.0f }, { 0.0f, 0.0f },{ 0.0f, 0.0f, 1.0f } },
+			{ {  1.0f, -1.0f, 0.0f }, { 1.0f, 0.0f },{ 0.0f, 0.0f, 1.0f } }
+		};
+
+		// Setup indices
+		std::vector<uint32_t> indices = { 0,1,2, 2,3,0 };
+		indexCount = static_cast<uint32_t>(indices.size());
+
+		// Create buffers
+		// For the sake of simplicity we won't stage the vertex data to the gpu memory
+		// Vertex buffer
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&vertexBuffer,
+			vertices.size() * sizeof(Vertex),
+			vertices.data()));
+		// Index buffer
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&indexBuffer,
+			indices.size() * sizeof(uint32_t),
+			indices.data()));
+	}
+
+	void setupVertexDescriptions()
+	{
+		// Binding description
+		vertices.inputBinding.resize(1);
+		vertices.inputBinding[0] =
+			vks::initializers::vertexInputBindingDescription(
+				VERTEX_BUFFER_BIND_ID,
+				sizeof(Vertex),
+				VK_VERTEX_INPUT_RATE_VERTEX);
+
+		// Attribute descriptions
+		// Describes memory layout and shader positions
+		vertices.inputAttributes.resize(3);
+		// Location 0 : Position
+		vertices.inputAttributes[0] =
+			vks::initializers::vertexInputAttributeDescription(
+				VERTEX_BUFFER_BIND_ID,
+				0,
+				VK_FORMAT_R32G32B32_SFLOAT,
+				offsetof(Vertex, pos));
+		// Location 1 : Texture coordinates
+		vertices.inputAttributes[1] =
+			vks::initializers::vertexInputAttributeDescription(
+				VERTEX_BUFFER_BIND_ID,
+				1,
+				VK_FORMAT_R32G32_SFLOAT,
+				offsetof(Vertex, uv));
+		// Location 1 : Vertex normal
+		vertices.inputAttributes[2] =
+			vks::initializers::vertexInputAttributeDescription(
+				VERTEX_BUFFER_BIND_ID,
+				2,
+				VK_FORMAT_R32G32B32_SFLOAT,
+				offsetof(Vertex, normal));
+
+		vertices.inputState = vks::initializers::pipelineVertexInputStateCreateInfo();
+		vertices.inputState.vertexBindingDescriptionCount = static_cast<uint32_t>(vertices.inputBinding.size());
+		vertices.inputState.pVertexBindingDescriptions = vertices.inputBinding.data();
+		vertices.inputState.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertices.inputAttributes.size());
+		vertices.inputState.pVertexAttributeDescriptions = vertices.inputAttributes.data();
+	}
+
+	void setupDescriptorPool()
+	{
+		// Example uses one ubo and one image sampler
+		std::vector<VkDescriptorPoolSize> poolSizes =
+		{
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1),
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1)
+		};
+
+		VkDescriptorPoolCreateInfo descriptorPoolInfo =
+			vks::initializers::descriptorPoolCreateInfo(
+				static_cast<uint32_t>(poolSizes.size()),
+				poolSizes.data(),
+				2);
+
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+	}
+
+	void setupDescriptorSetLayout()
+	{
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings =
+		{
+			// Binding 0 : Vertex shader uniform buffer
+			vks::initializers::descriptorSetLayoutBinding(
+				VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+				VK_SHADER_STAGE_VERTEX_BIT,
+				0),
+			// Binding 1 : Fragment shader image sampler
+			vks::initializers::descriptorSetLayoutBinding(
+				VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+				VK_SHADER_STAGE_FRAGMENT_BIT,
+				1)
+		};
+
+		VkDescriptorSetLayoutCreateInfo descriptorLayout =
+			vks::initializers::descriptorSetLayoutCreateInfo(
+				setLayoutBindings.data(),
+				static_cast<uint32_t>(setLayoutBindings.size()));
+
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout));
+
+		VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo =
+			vks::initializers::pipelineLayoutCreateInfo(
+				&descriptorSetLayout,
+				1);
+
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayout));
+	}
+
+	void setupDescriptorSet()
+	{
+		VkDescriptorSetAllocateInfo allocInfo =
+			vks::initializers::descriptorSetAllocateInfo(
+				descriptorPool,
+				&descriptorSetLayout,
+				1);
+
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet));
+
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets =
+		{
+			// Binding 0 : Vertex shader uniform buffer
+			vks::initializers::writeDescriptorSet(
+				descriptorSet,
+				VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+				0,
+				&uniformBufferVS.descriptor),
+			// Binding 1 : Fragment shader texture sampler
+			vks::initializers::writeDescriptorSet(
+				descriptorSet,
+				VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+				1,
+				&texture.descriptor)
+		};
+
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL);
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState =
+			vks::initializers::pipelineInputAssemblyStateCreateInfo(
+				VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
+				0,
+				VK_FALSE);
+
+		VkPipelineRasterizationStateCreateInfo rasterizationState =
+			vks::initializers::pipelineRasterizationStateCreateInfo(
+				VK_POLYGON_MODE_FILL,
+				VK_CULL_MODE_NONE,
+				VK_FRONT_FACE_COUNTER_CLOCKWISE,
+				0);
+
+		VkPipelineColorBlendAttachmentState blendAttachmentState =
+			vks::initializers::pipelineColorBlendAttachmentState(
+				0xf,
+				VK_FALSE);
+
+		VkPipelineColorBlendStateCreateInfo colorBlendState =
+			vks::initializers::pipelineColorBlendStateCreateInfo(
+				1,
+				&blendAttachmentState);
+
+		VkPipelineDepthStencilStateCreateInfo depthStencilState =
+			vks::initializers::pipelineDepthStencilStateCreateInfo(
+				VK_TRUE,
+				VK_TRUE,
+				VK_COMPARE_OP_LESS_OR_EQUAL);
+
+		VkPipelineViewportStateCreateInfo viewportState =
+			vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+
+		VkPipelineMultisampleStateCreateInfo multisampleState =
+			vks::initializers::pipelineMultisampleStateCreateInfo(
+				VK_SAMPLE_COUNT_1_BIT,
+				0);
+
+		std::vector<VkDynamicState> dynamicStateEnables = {
+			VK_DYNAMIC_STATE_VIEWPORT,
+			VK_DYNAMIC_STATE_SCISSOR
+		};
+		VkPipelineDynamicStateCreateInfo dynamicState =
+			vks::initializers::pipelineDynamicStateCreateInfo(
+				dynamicStateEnables.data(),
+				static_cast<uint32_t>(dynamicStateEnables.size()),
+				0);
+
+		// Load shaders
+		std::array<VkPipelineShaderStageCreateInfo,2> shaderStages;
+
+		shaderStages[0] = loadShader(getShadersPath() + "texture3d/texture3d.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "texture3d/texture3d.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+
+		VkGraphicsPipelineCreateInfo pipelineCreateInfo =
+			vks::initializers::pipelineCreateInfo(
+				pipelineLayout,
+				renderPass,
+				0);
+
+		pipelineCreateInfo.pVertexInputState = &vertices.inputState;
+		pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState;
+		pipelineCreateInfo.pRasterizationState = &rasterizationState;
+		pipelineCreateInfo.pColorBlendState = &colorBlendState;
+		pipelineCreateInfo.pMultisampleState = &multisampleState;
+		pipelineCreateInfo.pViewportState = &viewportState;
+		pipelineCreateInfo.pDepthStencilState = &depthStencilState;
+		pipelineCreateInfo.pDynamicState = &dynamicState;
+		pipelineCreateInfo.stageCount = static_cast<uint32_t>(shaderStages.size());
+		pipelineCreateInfo.pStages = shaderStages.data();
+
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipelines.solid));
+	}
+
+	// Prepare and initialize uniform buffer containing shader uniforms
+	void prepareUniformBuffers()
+	{
+		// Vertex shader uniform buffer block
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBufferVS,
+			sizeof(uboVS),
+			&uboVS));
+		VK_CHECK_RESULT(uniformBufferVS.map());
+		updateUniformBuffers();
+	}
+
+	void updateUniformBuffers(bool viewchanged = true)
+	{
+		if (viewchanged)
+		{
+			uboVS.projection = camera.matrices.perspective;
+			uboVS.modelView = camera.matrices.view;
+			uboVS.viewPos = camera.viewPos;
+		}
+		else
+		{
+			uboVS.depth += frameTimer * 0.15f;
+			if (uboVS.depth > 1.0f)
+				uboVS.depth = uboVS.depth - 1.0f;
+		}
+		memcpy(uniformBufferVS.mapped, &uboVS, sizeof(uboVS));
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		generateQuad();
+		setupVertexDescriptions();
+		prepareUniformBuffers();
+		prepareNoiseTexture(128, 128, 128);
+		setupDescriptorSetLayout();
+		preparePipelines();
+		setupDescriptorPool();
+		setupDescriptorSet();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+		if (!paused || camera.updated)
+			updateUniformBuffers(camera.updated);
+	}
+
+	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
+	{
+		if (overlay->header("Settings")) {
+			if (overlay->button("Generate new texture")) {
+				updateNoiseTexture();
+			}
+		}
+	}
+};
+
+VULKAN_EXAMPLE_MAIN()
diff --git a/external/Vulkan/examples/texturearray/texturearray.cpp b/external/Vulkan/examples/texturearray/texturearray.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..41cd53920bcf73e0f36b1d7705977ecf70e013a7
--- /dev/null
+++ b/external/Vulkan/examples/texturearray/texturearray.cpp
@@ -0,0 +1,555 @@
+/*
+* Vulkan Example - Texture arrays and instanced rendering
+*
+* Copyright (C) Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkanexamplebase.h"
+#include <ktx.h>
+#include <ktxvulkan.h>
+
+#define ENABLE_VALIDATION false
+
+// Vertex layout for this example
+struct Vertex {
+	float pos[3];
+	float uv[2];
+};
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	// Number of array layers in texture array
+	// Also used as instance count
+	uint32_t layerCount;
+	vks::Texture textureArray;
+
+	vks::Buffer vertexBuffer;
+	vks::Buffer indexBuffer;
+	uint32_t indexCount;
+
+	vks::Buffer uniformBufferVS;
+
+	struct UboInstanceData {
+		// Model matrix
+		glm::mat4 model;
+		// Texture array index
+		// Vec4 due to padding
+		glm::vec4 arrayIndex;
+	};
+
+	struct {
+		// Global matrices
+		struct {
+			glm::mat4 projection;
+			glm::mat4 view;
+		} matrices;
+		// Separate data for each instance
+		UboInstanceData *instance;
+	} uboVS;
+
+
+	VkPipeline pipeline;
+	VkPipelineLayout pipelineLayout;
+	VkDescriptorSet descriptorSet;
+	VkDescriptorSetLayout descriptorSetLayout;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Texture arrays";
+		camera.type = Camera::CameraType::lookat;
+		camera.setPosition(glm::vec3(0.0f, 0.0f, -7.5f));
+		camera.setRotation(glm::vec3(-35.0f, 0.0f, 0.0f));
+		camera.setPerspective(45.0f, (float)width / (float)height, 0.1f, 256.0f);
+	}
+
+	~VulkanExample()
+	{
+		// Clean up used Vulkan resources
+		// Note : Inherited destructor cleans up resources stored in base class
+
+		vkDestroyImageView(device, textureArray.view, nullptr);
+		vkDestroyImage(device, textureArray.image, nullptr);
+		vkDestroySampler(device, textureArray.sampler, nullptr);
+		vkFreeMemory(device, textureArray.deviceMemory, nullptr);
+
+		vkDestroyPipeline(device, pipeline, nullptr);
+
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+
+		vertexBuffer.destroy();
+		indexBuffer.destroy();
+
+		uniformBufferVS.destroy();
+
+		delete[] uboVS.instance;
+	}
+
+	void loadTextureArray(std::string filename, VkFormat format)
+	{
+		ktxResult result;
+		ktxTexture* ktxTexture;
+
+#if defined(__ANDROID__)
+		// Textures are stored inside the apk on Android (compressed)
+		// So they need to be loaded via the asset manager
+		AAsset* asset = AAssetManager_open(androidApp->activity->assetManager, filename.c_str(), AASSET_MODE_STREAMING);
+		if (!asset) {
+			vks::tools::exitFatal("Could not load texture from " + filename + "\n\nThe file may be part of the additional asset pack.\n\nRun \"download_assets.py\" in the repository root to download the latest version.", -1);
+		}
+		size_t size = AAsset_getLength(asset);
+		assert(size > 0);
+
+		ktx_uint8_t *textureData = new ktx_uint8_t[size];
+		AAsset_read(asset, textureData, size);
+		AAsset_close(asset);
+		result = ktxTexture_CreateFromMemory(textureData, size, KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, &ktxTexture);
+		delete[] textureData;
+#else
+		if (!vks::tools::fileExists(filename)) {
+			vks::tools::exitFatal("Could not load texture from " + filename + "\n\nThe file may be part of the additional asset pack.\n\nRun \"download_assets.py\" in the repository root to download the latest version.", -1);
+		}
+		result = ktxTexture_CreateFromNamedFile(filename.c_str(), KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, &ktxTexture);
+#endif
+		assert(result == KTX_SUCCESS);
+
+		// Get properties required for using and upload texture data from the ktx texture object
+		textureArray.width = ktxTexture->baseWidth;
+		textureArray.height = ktxTexture->baseHeight;
+		layerCount = ktxTexture->numLayers;
+		ktx_uint8_t *ktxTextureData = ktxTexture_GetData(ktxTexture);
+		ktx_size_t ktxTextureSize = ktxTexture_GetSize(ktxTexture);
+
+		VkMemoryAllocateInfo memAllocInfo = vks::initializers::memoryAllocateInfo();
+		VkMemoryRequirements memReqs;
+
+		// Create a host-visible staging buffer that contains the raw image data
+		VkBuffer stagingBuffer;
+		VkDeviceMemory stagingMemory;
+
+		VkBufferCreateInfo bufferCreateInfo = vks::initializers::bufferCreateInfo();
+		bufferCreateInfo.size = ktxTextureSize;
+		// This buffer is used as a transfer source for the buffer copy
+		bufferCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
+		bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+
+		VK_CHECK_RESULT(vkCreateBuffer(device, &bufferCreateInfo, nullptr, &stagingBuffer));
+
+		// Get memory requirements for the staging buffer (alignment, memory type bits)
+		vkGetBufferMemoryRequirements(device, stagingBuffer, &memReqs);
+
+		memAllocInfo.allocationSize = memReqs.size;
+		// Get memory type index for a host visible buffer
+		memAllocInfo.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
+
+		VK_CHECK_RESULT(vkAllocateMemory(device, &memAllocInfo, nullptr, &stagingMemory));
+		VK_CHECK_RESULT(vkBindBufferMemory(device, stagingBuffer, stagingMemory, 0));
+
+		// Copy texture data into staging buffer
+		uint8_t *data;
+		VK_CHECK_RESULT(vkMapMemory(device, stagingMemory, 0, memReqs.size, 0, (void **)&data));
+		memcpy(data, ktxTextureData, ktxTextureSize);
+		vkUnmapMemory(device, stagingMemory);
+
+		// Setup buffer copy regions for array layers
+		std::vector<VkBufferImageCopy> bufferCopyRegions;
+
+		// To keep this simple, we will only load layers and no mip level
+		for (uint32_t layer = 0; layer < layerCount; layer++)
+		{
+			// Calculate offset into staging buffer for the current array layer
+			ktx_size_t offset;
+			KTX_error_code ret = ktxTexture_GetImageOffset(ktxTexture, 0, layer, 0, &offset);
+			assert(ret == KTX_SUCCESS);
+			// Setup a buffer image copy structure for the current array layer
+			VkBufferImageCopy bufferCopyRegion = {};
+			bufferCopyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+			bufferCopyRegion.imageSubresource.mipLevel = 0;
+			bufferCopyRegion.imageSubresource.baseArrayLayer = layer;
+			bufferCopyRegion.imageSubresource.layerCount = 1;
+			bufferCopyRegion.imageExtent.width = ktxTexture->baseWidth;
+			bufferCopyRegion.imageExtent.height = ktxTexture->baseHeight;
+			bufferCopyRegion.imageExtent.depth = 1;
+			bufferCopyRegion.bufferOffset = offset;
+			bufferCopyRegions.push_back(bufferCopyRegion);
+		}
+
+		// Create optimal tiled target image
+		VkImageCreateInfo imageCreateInfo = vks::initializers::imageCreateInfo();
+		imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
+		imageCreateInfo.format = format;
+		imageCreateInfo.mipLevels = 1;
+		imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
+		imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
+		imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+		imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+		imageCreateInfo.extent = { textureArray.width, textureArray.height, 1 };
+		imageCreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
+		imageCreateInfo.arrayLayers = layerCount;
+
+		VK_CHECK_RESULT(vkCreateImage(device, &imageCreateInfo, nullptr, &textureArray.image));
+
+		vkGetImageMemoryRequirements(device, textureArray.image, &memReqs);
+
+		memAllocInfo.allocationSize = memReqs.size;
+		memAllocInfo.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+
+		VK_CHECK_RESULT(vkAllocateMemory(device, &memAllocInfo, nullptr, &textureArray.deviceMemory));
+		VK_CHECK_RESULT(vkBindImageMemory(device, textureArray.image, textureArray.deviceMemory, 0));
+
+		VkCommandBuffer copyCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+
+		// Image barrier for optimal image (target)
+		// Set initial layout for all array layers (faces) of the optimal (target) tiled texture
+		VkImageSubresourceRange subresourceRange = {};
+		subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+		subresourceRange.baseMipLevel = 0;
+		subresourceRange.levelCount = 1;
+		subresourceRange.layerCount = layerCount;
+
+		vks::tools::setImageLayout(
+			copyCmd,
+			textureArray.image,
+			VK_IMAGE_LAYOUT_UNDEFINED,
+			VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+			subresourceRange);
+
+		// Copy the cube map faces from the staging buffer to the optimal tiled image
+		vkCmdCopyBufferToImage(
+			copyCmd,
+			stagingBuffer,
+			textureArray.image,
+			VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+			bufferCopyRegions.size(),
+			bufferCopyRegions.data()
+			);
+
+		// Change texture image layout to shader read after all faces have been copied
+		textureArray.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+		vks::tools::setImageLayout(
+			copyCmd,
+			textureArray.image,
+			VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+			textureArray.imageLayout,
+			subresourceRange);
+
+		vulkanDevice->flushCommandBuffer(copyCmd, queue, true);
+
+		// Create sampler
+		VkSamplerCreateInfo sampler = vks::initializers::samplerCreateInfo();
+		sampler.magFilter = VK_FILTER_LINEAR;
+		sampler.minFilter = VK_FILTER_LINEAR;
+		sampler.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+		sampler.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+		sampler.addressModeV = sampler.addressModeU;
+		sampler.addressModeW = sampler.addressModeU;
+		sampler.mipLodBias = 0.0f;
+		sampler.maxAnisotropy = 8;
+		sampler.compareOp = VK_COMPARE_OP_NEVER;
+		sampler.minLod = 0.0f;
+		sampler.maxLod = 0.0f;
+		sampler.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
+		VK_CHECK_RESULT(vkCreateSampler(device, &sampler, nullptr, &textureArray.sampler));
+
+		// Create image view
+		VkImageViewCreateInfo view = vks::initializers::imageViewCreateInfo();
+		view.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY;
+		view.format = format;
+		view.components = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A };
+		view.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 };
+		view.subresourceRange.layerCount = layerCount;
+		view.subresourceRange.levelCount = 1;
+		view.image = textureArray.image;
+		VK_CHECK_RESULT(vkCreateImageView(device, &view, nullptr, &textureArray.view));
+
+		// Clean up staging resources
+		vkFreeMemory(device, stagingMemory, nullptr);
+		vkDestroyBuffer(device, stagingBuffer, nullptr);
+		ktxTexture_Destroy(ktxTexture);
+	}
+
+	void loadAssets()
+	{
+		loadTextureArray(getAssetPath() + "textures/texturearray_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM);
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		clearValues[0].color = defaultClearColor;
+		clearValues[1].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.offset.x = 0;
+		renderPassBeginInfo.renderArea.offset.y = 0;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		renderPassBeginInfo.clearValueCount = 2;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			// Set target frame buffer
+			renderPassBeginInfo.framebuffer = frameBuffers[i];
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+			VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL);
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
+
+			VkDeviceSize offsets[1] = { 0 };
+			vkCmdBindVertexBuffers(drawCmdBuffers[i], 0, 1, &vertexBuffer.buffer, offsets);
+			vkCmdBindIndexBuffer(drawCmdBuffers[i], indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT32);
+
+			vkCmdDrawIndexed(drawCmdBuffers[i], indexCount, layerCount, 0, 0, 0);
+
+			drawUI(drawCmdBuffers[i]);
+
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void generateCube()
+	{
+		std::vector<Vertex> vertices = {
+			{ { -1.0f, -1.0f,  1.0f }, { 0.0f, 0.0f } },
+			{ {  1.0f, -1.0f,  1.0f }, { 1.0f, 0.0f } },
+			{ {  1.0f,  1.0f,  1.0f }, { 1.0f, 1.0f } },
+			{ { -1.0f,  1.0f,  1.0f }, { 0.0f, 1.0f } },
+
+			{ {  1.0f,  1.0f,  1.0f }, { 0.0f, 0.0f } },
+			{ {  1.0f,  1.0f, -1.0f }, { 1.0f, 0.0f } },
+			{ {  1.0f, -1.0f, -1.0f }, { 1.0f, 1.0f } },
+			{ {  1.0f, -1.0f,  1.0f }, { 0.0f, 1.0f } },
+
+			{ { -1.0f, -1.0f, -1.0f }, { 0.0f, 0.0f } },
+			{ {  1.0f, -1.0f, -1.0f }, { 1.0f, 0.0f } },
+			{ {  1.0f,  1.0f, -1.0f }, { 1.0f, 1.0f } },
+			{ { -1.0f,  1.0f, -1.0f }, { 0.0f, 1.0f } },
+
+			{ { -1.0f, -1.0f, -1.0f }, { 0.0f, 0.0f } },
+			{ { -1.0f, -1.0f,  1.0f }, { 1.0f, 0.0f } },
+			{ { -1.0f,  1.0f,  1.0f }, { 1.0f, 1.0f } },
+			{ { -1.0f,  1.0f, -1.0f }, { 0.0f, 1.0f } },
+
+			{ {  1.0f,  1.0f,  1.0f }, { 0.0f, 0.0f } },
+			{ { -1.0f,  1.0f,  1.0f }, { 1.0f, 0.0f } },
+			{ { -1.0f,  1.0f, -1.0f }, { 1.0f, 1.0f } },
+			{ {  1.0f,  1.0f, -1.0f }, { 0.0f, 1.0f } },
+
+			{ { -1.0f, -1.0f, -1.0f }, { 0.0f, 0.0f } },
+			{ {  1.0f, -1.0f, -1.0f }, { 1.0f, 0.0f } },
+			{ {  1.0f, -1.0f,  1.0f }, { 1.0f, 1.0f } },
+			{ { -1.0f, -1.0f,  1.0f }, { 0.0f, 1.0f } },
+		};
+		std::vector<uint32_t> indices = {
+			0,1,2, 0,2,3, 4,5,6,  4,6,7, 8,9,10, 8,10,11, 12,13,14, 12,14,15, 16,17,18, 16,18,19, 20,21,22, 20,22,23
+		};
+
+		indexCount = static_cast<uint32_t>(indices.size());
+
+		// Create buffers
+		// For the sake of simplicity we won't stage the vertex data to the gpu memory
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&vertexBuffer,
+			vertices.size() * sizeof(Vertex),
+			vertices.data()));
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&indexBuffer,
+			indices.size() * sizeof(uint32_t),
+			indices.data()));
+	}
+
+	void setupDescriptorPool()
+	{
+		std::vector<VkDescriptorPoolSize> poolSizes = {
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1),
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1)
+		};
+		VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes.size(), poolSizes.data(), 2);
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+	}
+
+	void setupDescriptorSetLayout()
+	{
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			// Binding 0 : Vertex shader uniform buffer
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0),
+			// Binding 1 : Fragment shader image sampler (texture array)
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1)
+		};
+		VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings.data(), setLayoutBindings.size());
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout));
+
+		VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1);
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout));
+	}
+
+	void setupDescriptorSet()
+	{
+		VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1);
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet));
+
+		// Image descriptor for the texture array
+		VkDescriptorImageInfo textureDescriptor =
+			vks::initializers::descriptorImageInfo(
+				textureArray.sampler,
+				textureArray.view,
+				textureArray.imageLayout);
+
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets = {
+			// Binding 0 : Vertex shader uniform buffer
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBufferVS.descriptor),
+			// Binding 1 : Fragment shader cubemap sampler
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &textureDescriptor)
+		};
+		vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, NULL);
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyStateCI = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationStateCI = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendStateCI = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilStateCI = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportStateCI = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+		VkPipelineMultisampleStateCreateInfo multisampleStateCI = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
+		std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
+		VkPipelineDynamicStateCreateInfo dynamicStateCI = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables.data(), dynamicStateEnables.size(), 0);
+
+		// Vertex bindings and attributes
+		VkVertexInputBindingDescription vertexInputBinding = { 0, sizeof(Vertex), VK_VERTEX_INPUT_RATE_VERTEX };
+		std::vector<VkVertexInputAttributeDescription> vertexInputAttributes = {
+			{ 0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, pos) },
+			{ 1, 0, VK_FORMAT_R32G32_SFLOAT, offsetof(Vertex, uv) },
+		};
+		VkPipelineVertexInputStateCreateInfo vertexInputStateCI = vks::initializers::pipelineVertexInputStateCreateInfo();
+		vertexInputStateCI.vertexBindingDescriptionCount = 1;
+		vertexInputStateCI.pVertexBindingDescriptions = &vertexInputBinding;
+		vertexInputStateCI.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertexInputAttributes.size());
+		vertexInputStateCI.pVertexAttributeDescriptions = vertexInputAttributes.data();
+
+		// Instancing pipeline
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+
+		shaderStages[0] = loadShader(getShadersPath() + "texturearray/instancing.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "texturearray/instancing.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass, 0);
+		pipelineCI.pVertexInputState = &vertexInputStateCI;
+		pipelineCI.pInputAssemblyState = &inputAssemblyStateCI;
+		pipelineCI.pRasterizationState = &rasterizationStateCI;
+		pipelineCI.pColorBlendState = &colorBlendStateCI;
+		pipelineCI.pMultisampleState = &multisampleStateCI;
+		pipelineCI.pViewportState = &viewportStateCI;
+		pipelineCI.pDepthStencilState = &depthStencilStateCI;
+		pipelineCI.pDynamicState = &dynamicStateCI;
+		pipelineCI.stageCount = shaderStages.size();
+		pipelineCI.pStages = shaderStages.data();
+
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipeline));
+	}
+
+	void prepareUniformBuffers()
+	{
+		uboVS.instance = new UboInstanceData[layerCount];
+
+		uint32_t uboSize = sizeof(uboVS.matrices) + (layerCount * sizeof(UboInstanceData));
+
+		// Vertex shader uniform buffer block
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBufferVS,
+			uboSize));
+
+		// Array indices and model matrices are fixed
+		float offset = -1.5f;
+		float center = (layerCount*offset) / 2.0f - (offset * 0.5f);
+		for (uint32_t i = 0; i < layerCount; i++) {
+			// Instance model matrix
+			uboVS.instance[i].model = glm::translate(glm::mat4(1.0f), glm::vec3(i * offset - center, 0.0f, 0.0f));
+			uboVS.instance[i].model = glm::scale(uboVS.instance[i].model, glm::vec3(0.5f));
+			// Instance texture array index
+			uboVS.instance[i].arrayIndex.x = (float)i;
+		}
+
+		// Update instanced part of the uniform buffer
+		uint8_t *pData;
+		uint32_t dataOffset = sizeof(uboVS.matrices);
+		uint32_t dataSize = layerCount * sizeof(UboInstanceData);
+		VK_CHECK_RESULT(vkMapMemory(device, uniformBufferVS.memory, dataOffset, dataSize, 0, (void **)&pData));
+		memcpy(pData, uboVS.instance, dataSize);
+		vkUnmapMemory(device, uniformBufferVS.memory);
+
+		// Map persistent
+		VK_CHECK_RESULT(uniformBufferVS.map());
+
+		updateUniformBuffersCamera();
+	}
+
+	void updateUniformBuffersCamera()
+	{
+		uboVS.matrices.projection = camera.matrices.perspective;
+		uboVS.matrices.view = camera.matrices.view;
+		memcpy(uniformBufferVS.mapped, &uboVS.matrices, sizeof(uboVS.matrices));
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+
+		VulkanExampleBase::submitFrame();
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		loadAssets();
+		generateCube();
+		prepareUniformBuffers();
+		setupDescriptorSetLayout();
+		preparePipelines();
+		setupDescriptorPool();
+		setupDescriptorSet();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+		if (camera.updated)
+			updateUniformBuffersCamera();
+	}
+
+};
+
+VULKAN_EXAMPLE_MAIN()
\ No newline at end of file
diff --git a/external/Vulkan/examples/texturecubemap/texturecubemap.cpp b/external/Vulkan/examples/texturecubemap/texturecubemap.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..33358541adc2f72fa9d22bbd8ec85d20a96783f8
--- /dev/null
+++ b/external/Vulkan/examples/texturecubemap/texturecubemap.cpp
@@ -0,0 +1,594 @@
+/*
+* Vulkan Example - Cube map texture loading and displaying
+*
+* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkanexamplebase.h"
+#include "VulkanglTFModel.h"
+#include <ktx.h>
+#include <ktxvulkan.h>
+
+#define ENABLE_VALIDATION false
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	bool displaySkybox = true;
+
+	vks::Texture cubeMap;
+
+	struct Meshes {
+		vkglTF::Model skybox;
+		std::vector<vkglTF::Model> objects;
+		int32_t objectIndex = 0;
+	} models;
+
+	struct {
+		vks::Buffer object;
+		vks::Buffer skybox;
+	} uniformBuffers;
+
+	struct UBOVS {
+		glm::mat4 projection;
+		glm::mat4 modelView;
+		glm::mat4 inverseModelview;
+		float lodBias = 0.0f;
+	} uboVS;
+
+	struct {
+		VkPipeline skybox;
+		VkPipeline reflect;
+	} pipelines;
+
+	struct {
+		VkDescriptorSet object;
+		VkDescriptorSet skybox;
+	} descriptorSets;
+
+	VkPipelineLayout pipelineLayout;
+	VkDescriptorSetLayout descriptorSetLayout;
+
+	std::vector<std::string> objectNames;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Cube map textures";
+		camera.type = Camera::CameraType::lookat;
+		camera.setPosition(glm::vec3(0.0f, 0.0f, -4.0f));
+		camera.setRotation(glm::vec3(0.0f));
+		camera.setRotationSpeed(0.25f);
+		camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 256.0f);
+	}
+
+	~VulkanExample()
+	{
+		// Clean up used Vulkan resources
+		// Note : Inherited destructor cleans up resources stored in base class
+
+		// Clean up texture resources
+		vkDestroyImageView(device, cubeMap.view, nullptr);
+		vkDestroyImage(device, cubeMap.image, nullptr);
+		vkDestroySampler(device, cubeMap.sampler, nullptr);
+		vkFreeMemory(device, cubeMap.deviceMemory, nullptr);
+
+		vkDestroyPipeline(device, pipelines.skybox, nullptr);
+		vkDestroyPipeline(device, pipelines.reflect, nullptr);
+
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+
+		uniformBuffers.object.destroy();
+		uniformBuffers.skybox.destroy();
+	}
+
+	// Enable physical device features required for this example
+	virtual void getEnabledFeatures()
+	{
+		if (deviceFeatures.samplerAnisotropy) {
+			enabledFeatures.samplerAnisotropy = VK_TRUE;
+		}
+	}
+
+	void loadCubemap(std::string filename, VkFormat format, bool forceLinearTiling)
+	{
+		ktxResult result;
+		ktxTexture* ktxTexture;
+
+#if defined(__ANDROID__)
+		// Textures are stored inside the apk on Android (compressed)
+		// So they need to be loaded via the asset manager
+		AAsset* asset = AAssetManager_open(androidApp->activity->assetManager, filename.c_str(), AASSET_MODE_STREAMING);
+		if (!asset) {
+			vks::tools::exitFatal("Could not load texture from " + filename + "\n\nThe file may be part of the additional asset pack.\n\nRun \"download_assets.py\" in the repository root to download the latest version.", -1);
+		}
+		size_t size = AAsset_getLength(asset);
+		assert(size > 0);
+
+		ktx_uint8_t *textureData = new ktx_uint8_t[size];
+		AAsset_read(asset, textureData, size);
+		AAsset_close(asset);
+		result = ktxTexture_CreateFromMemory(textureData, size, KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, &ktxTexture);
+		delete[] textureData;
+#else
+		if (!vks::tools::fileExists(filename)) {
+			vks::tools::exitFatal("Could not load texture from " + filename + "\n\nThe file may be part of the additional asset pack.\n\nRun \"download_assets.py\" in the repository root to download the latest version.", -1);
+		}
+		result = ktxTexture_CreateFromNamedFile(filename.c_str(), KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, &ktxTexture);
+#endif
+		assert(result == KTX_SUCCESS);
+
+		// Get properties required for using and upload texture data from the ktx texture object
+		cubeMap.width = ktxTexture->baseWidth;
+		cubeMap.height = ktxTexture->baseHeight;
+		cubeMap.mipLevels = ktxTexture->numLevels;
+		ktx_uint8_t *ktxTextureData = ktxTexture_GetData(ktxTexture);
+		ktx_size_t ktxTextureSize = ktxTexture_GetSize(ktxTexture);
+
+		VkMemoryAllocateInfo memAllocInfo = vks::initializers::memoryAllocateInfo();
+		VkMemoryRequirements memReqs;
+
+		// Create a host-visible staging buffer that contains the raw image data
+		VkBuffer stagingBuffer;
+		VkDeviceMemory stagingMemory;
+
+		VkBufferCreateInfo bufferCreateInfo = vks::initializers::bufferCreateInfo();
+		bufferCreateInfo.size = ktxTextureSize;
+		// This buffer is used as a transfer source for the buffer copy
+		bufferCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
+		bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+
+		VK_CHECK_RESULT(vkCreateBuffer(device, &bufferCreateInfo, nullptr, &stagingBuffer));
+
+		// Get memory requirements for the staging buffer (alignment, memory type bits)
+		vkGetBufferMemoryRequirements(device, stagingBuffer, &memReqs);
+		memAllocInfo.allocationSize = memReqs.size;
+		// Get memory type index for a host visible buffer
+		memAllocInfo.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
+		VK_CHECK_RESULT(vkAllocateMemory(device, &memAllocInfo, nullptr, &stagingMemory));
+		VK_CHECK_RESULT(vkBindBufferMemory(device, stagingBuffer, stagingMemory, 0));
+
+		// Copy texture data into staging buffer
+		uint8_t *data;
+		VK_CHECK_RESULT(vkMapMemory(device, stagingMemory, 0, memReqs.size, 0, (void **)&data));
+		memcpy(data, ktxTextureData, ktxTextureSize);
+		vkUnmapMemory(device, stagingMemory);
+
+		// Create optimal tiled target image
+		VkImageCreateInfo imageCreateInfo = vks::initializers::imageCreateInfo();
+		imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
+		imageCreateInfo.format = format;
+		imageCreateInfo.mipLevels = cubeMap.mipLevels;
+		imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
+		imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
+		imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+		imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+		imageCreateInfo.extent = { cubeMap.width, cubeMap.height, 1 };
+		imageCreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
+		// Cube faces count as array layers in Vulkan
+		imageCreateInfo.arrayLayers = 6;
+		// This flag is required for cube map images
+		imageCreateInfo.flags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;
+
+		VK_CHECK_RESULT(vkCreateImage(device, &imageCreateInfo, nullptr, &cubeMap.image));
+
+		vkGetImageMemoryRequirements(device, cubeMap.image, &memReqs);
+
+		memAllocInfo.allocationSize = memReqs.size;
+		memAllocInfo.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+
+		VK_CHECK_RESULT(vkAllocateMemory(device, &memAllocInfo, nullptr, &cubeMap.deviceMemory));
+		VK_CHECK_RESULT(vkBindImageMemory(device, cubeMap.image, cubeMap.deviceMemory, 0));
+
+		VkCommandBuffer copyCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+
+		// Setup buffer copy regions for each face including all of its miplevels
+		std::vector<VkBufferImageCopy> bufferCopyRegions;
+		uint32_t offset = 0;
+
+		for (uint32_t face = 0; face < 6; face++)
+		{
+			for (uint32_t level = 0; level < cubeMap.mipLevels; level++)
+			{
+				// Calculate offset into staging buffer for the current mip level and face
+				ktx_size_t offset;
+				KTX_error_code ret = ktxTexture_GetImageOffset(ktxTexture, level, 0, face, &offset);
+				assert(ret == KTX_SUCCESS);
+				VkBufferImageCopy bufferCopyRegion = {};
+				bufferCopyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+				bufferCopyRegion.imageSubresource.mipLevel = level;
+				bufferCopyRegion.imageSubresource.baseArrayLayer = face;
+				bufferCopyRegion.imageSubresource.layerCount = 1;
+				bufferCopyRegion.imageExtent.width = ktxTexture->baseWidth >> level;
+				bufferCopyRegion.imageExtent.height = ktxTexture->baseHeight >> level;
+				bufferCopyRegion.imageExtent.depth = 1;
+				bufferCopyRegion.bufferOffset = offset;
+				bufferCopyRegions.push_back(bufferCopyRegion);
+			}
+		}
+
+		// Image barrier for optimal image (target)
+		// Set initial layout for all array layers (faces) of the optimal (target) tiled texture
+		VkImageSubresourceRange subresourceRange = {};
+		subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+		subresourceRange.baseMipLevel = 0;
+		subresourceRange.levelCount = cubeMap.mipLevels;
+		subresourceRange.layerCount = 6;
+
+		vks::tools::setImageLayout(
+			copyCmd,
+			cubeMap.image,
+			VK_IMAGE_LAYOUT_UNDEFINED,
+			VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+			subresourceRange);
+
+		// Copy the cube map faces from the staging buffer to the optimal tiled image
+		vkCmdCopyBufferToImage(
+			copyCmd,
+			stagingBuffer,
+			cubeMap.image,
+			VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+			static_cast<uint32_t>(bufferCopyRegions.size()),
+			bufferCopyRegions.data()
+			);
+
+		// Change texture image layout to shader read after all faces have been copied
+		cubeMap.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+		vks::tools::setImageLayout(
+			copyCmd,
+			cubeMap.image,
+			VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+			cubeMap.imageLayout,
+			subresourceRange);
+
+		vulkanDevice->flushCommandBuffer(copyCmd, queue, true);
+
+		// Create sampler
+		VkSamplerCreateInfo sampler = vks::initializers::samplerCreateInfo();
+		sampler.magFilter = VK_FILTER_LINEAR;
+		sampler.minFilter = VK_FILTER_LINEAR;
+		sampler.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+		sampler.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+		sampler.addressModeV = sampler.addressModeU;
+		sampler.addressModeW = sampler.addressModeU;
+		sampler.mipLodBias = 0.0f;
+		sampler.compareOp = VK_COMPARE_OP_NEVER;
+		sampler.minLod = 0.0f;
+		sampler.maxLod = cubeMap.mipLevels;
+		sampler.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
+		sampler.maxAnisotropy = 1.0f;
+		if (vulkanDevice->features.samplerAnisotropy)
+		{
+			sampler.maxAnisotropy = vulkanDevice->properties.limits.maxSamplerAnisotropy;
+			sampler.anisotropyEnable = VK_TRUE;
+		}
+		VK_CHECK_RESULT(vkCreateSampler(device, &sampler, nullptr, &cubeMap.sampler));
+
+		// Create image view
+		VkImageViewCreateInfo view = vks::initializers::imageViewCreateInfo();
+		// Cube map view type
+		view.viewType = VK_IMAGE_VIEW_TYPE_CUBE;
+		view.format = format;
+		view.components = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A };
+		view.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 };
+		// 6 array layers (faces)
+		view.subresourceRange.layerCount = 6;
+		// Set number of mip levels
+		view.subresourceRange.levelCount = cubeMap.mipLevels;
+		view.image = cubeMap.image;
+		VK_CHECK_RESULT(vkCreateImageView(device, &view, nullptr, &cubeMap.view));
+
+		// Clean up staging resources
+		vkFreeMemory(device, stagingMemory, nullptr);
+		vkDestroyBuffer(device, stagingBuffer, nullptr);
+		ktxTexture_Destroy(ktxTexture);
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		clearValues[0].color = defaultClearColor;
+		clearValues[1].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.offset.x = 0;
+		renderPassBeginInfo.renderArea.offset.y = 0;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		renderPassBeginInfo.clearValueCount = 2;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			// Set target frame buffer
+			renderPassBeginInfo.framebuffer = frameBuffers[i];
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			VkViewport viewport = vks::initializers::viewport((float)width,	(float)height, 0.0f, 1.0f);
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+			VkRect2D scissor = vks::initializers::rect2D(width,	height,	0, 0);
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+			VkDeviceSize offsets[1] = { 0 };
+
+			// Skybox
+			if (displaySkybox)
+			{
+				vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets.skybox, 0, NULL);
+				vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.skybox);
+				models.skybox.draw(drawCmdBuffers[i]);
+			}
+
+			// 3D object
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets.object, 0, NULL);
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.reflect);
+			models.objects[models.objectIndex].draw(drawCmdBuffers[i]);
+
+			drawUI(drawCmdBuffers[i]);
+
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void loadAssets()
+	{
+		uint32_t glTFLoadingFlags = vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::FlipY;
+		// Skybox
+		models.skybox.loadFromFile(getAssetPath() + "models/cube.gltf", vulkanDevice, queue, glTFLoadingFlags);
+		// Objects
+		std::vector<std::string> filenames = { "sphere.gltf", "teapot.gltf", "torusknot.gltf", "venus.gltf" };
+		objectNames = { "Sphere", "Teapot", "Torusknot", "Venus" };
+		models.objects.resize(filenames.size());
+		for (size_t i = 0; i < filenames.size(); i++) {
+			models.objects[i].loadFromFile(getAssetPath() + "models/" + filenames[i], vulkanDevice, queue, glTFLoadingFlags);
+		}
+		// Cubemap texture
+		const bool forceLinearTiling = false;
+		loadCubemap(getAssetPath() + "textures/cubemap_yokohama_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, forceLinearTiling);
+	}
+
+	void setupDescriptorPool()
+	{
+		std::vector<VkDescriptorPoolSize> poolSizes =
+		{
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2),
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2)
+		};
+
+		VkDescriptorPoolCreateInfo descriptorPoolInfo =
+			vks::initializers::descriptorPoolCreateInfo(
+				poolSizes.size(),
+				poolSizes.data(),
+				2);
+
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+	}
+
+	void setupDescriptorSetLayout()
+	{
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings =
+		{
+			// Binding 0 : Uniform buffer
+			vks::initializers::descriptorSetLayoutBinding(
+				VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+				VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
+				0),
+			// Binding 1 : Fragment shader image sampler
+			vks::initializers::descriptorSetLayoutBinding(
+				VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+				VK_SHADER_STAGE_FRAGMENT_BIT,
+				1)
+		};
+
+		VkDescriptorSetLayoutCreateInfo descriptorLayout =
+			vks::initializers::descriptorSetLayoutCreateInfo(
+				setLayoutBindings.data(),
+				setLayoutBindings.size());
+
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout));
+
+		VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo =
+			vks::initializers::pipelineLayoutCreateInfo(
+				&descriptorSetLayout,
+				1);
+
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayout));
+	}
+
+	void setupDescriptorSets()
+	{
+		// Image descriptor for the cube map texture
+		VkDescriptorImageInfo textureDescriptor =
+			vks::initializers::descriptorImageInfo(
+				cubeMap.sampler,
+				cubeMap.view,
+				cubeMap.imageLayout);
+
+		VkDescriptorSetAllocateInfo allocInfo =
+			vks::initializers::descriptorSetAllocateInfo(
+				descriptorPool,
+				&descriptorSetLayout,
+				1);
+
+		// 3D object descriptor set
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.object));
+
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets =
+		{
+			// Binding 0 : Vertex shader uniform buffer
+			vks::initializers::writeDescriptorSet(
+				descriptorSets.object,
+				VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+				0,
+				&uniformBuffers.object.descriptor),
+			// Binding 1 : Fragment shader cubemap sampler
+			vks::initializers::writeDescriptorSet(
+				descriptorSets.object,
+				VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+				1,
+				&textureDescriptor)
+		};
+		vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, NULL);
+
+		// Sky box descriptor set
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.skybox));
+
+		writeDescriptorSets =
+		{
+			// Binding 0 : Vertex shader uniform buffer
+			vks::initializers::writeDescriptorSet(
+				descriptorSets.skybox,
+				VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+				0,
+				&uniformBuffers.skybox.descriptor),
+			// Binding 1 : Fragment shader cubemap sampler
+			vks::initializers::writeDescriptorSet(
+				descriptorSets.skybox,
+				VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+				1,
+				&textureDescriptor)
+		};
+		vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, NULL);
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_FALSE, VK_FALSE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+		VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
+		std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
+		VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass, 0);
+		pipelineCI.pInputAssemblyState = &inputAssemblyState;
+		pipelineCI.pRasterizationState = &rasterizationState;
+		pipelineCI.pColorBlendState = &colorBlendState;
+		pipelineCI.pMultisampleState = &multisampleState;
+		pipelineCI.pViewportState = &viewportState;
+		pipelineCI.pDepthStencilState = &depthStencilState;
+		pipelineCI.pDynamicState = &dynamicState;
+		pipelineCI.stageCount = shaderStages.size();
+		pipelineCI.pStages = shaderStages.data();
+		pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal });
+
+		// Skybox pipeline (background cube)
+		shaderStages[0] = loadShader(getShadersPath() + "texturecubemap/skybox.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "texturecubemap/skybox.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		rasterizationState.cullMode = VK_CULL_MODE_FRONT_BIT;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.skybox));
+
+		// Cube map reflect pipeline
+		shaderStages[0] = loadShader(getShadersPath() + "texturecubemap/reflect.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "texturecubemap/reflect.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		// Enable depth test and write
+		depthStencilState.depthWriteEnable = VK_TRUE;
+		depthStencilState.depthTestEnable = VK_TRUE;
+		rasterizationState.cullMode = VK_CULL_MODE_BACK_BIT;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.reflect));
+	}
+
+	// Prepare and initialize uniform buffer containing shader uniforms
+	void prepareUniformBuffers()
+	{
+		// Object vertex shader uniform buffer
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.object,
+			sizeof(uboVS)));
+
+		// Skybox vertex shader uniform buffer
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.skybox,
+			sizeof(uboVS)));
+
+		// Map persistent
+		VK_CHECK_RESULT(uniformBuffers.object.map());
+		VK_CHECK_RESULT(uniformBuffers.skybox.map());
+
+		updateUniformBuffers();
+	}
+
+	void updateUniformBuffers()
+	{
+		// 3D object
+		uboVS.projection = camera.matrices.perspective;
+		uboVS.modelView = camera.matrices.view;
+		uboVS.inverseModelview = glm::inverse(camera.matrices.view);
+		memcpy(uniformBuffers.object.mapped, &uboVS, sizeof(uboVS));
+		// Skybox
+		uboVS.modelView = camera.matrices.view;
+		// Cancel out translation
+		uboVS.modelView[3] = glm::vec4(0.0f, 0.0f, 0.0f, 1.0f);
+		memcpy(uniformBuffers.skybox.mapped, &uboVS, sizeof(uboVS));
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+		VulkanExampleBase::submitFrame();
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		loadAssets();
+		prepareUniformBuffers();
+		setupDescriptorSetLayout();
+		preparePipelines();
+		setupDescriptorPool();
+		setupDescriptorSets();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+	}
+
+	virtual void viewChanged()
+	{
+		updateUniformBuffers();
+	}
+
+	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
+	{
+		if (overlay->header("Settings")) {
+			if (overlay->sliderFloat("LOD bias", &uboVS.lodBias, 0.0f, (float)cubeMap.mipLevels)) {
+				updateUniformBuffers();
+			}
+			if (overlay->comboBox("Object type", &models.objectIndex, objectNames)) {
+				buildCommandBuffers();
+			}
+			if (overlay->checkBox("Skybox", &displaySkybox)) {
+				buildCommandBuffers();
+			}
+		}
+	}
+};
+
+VULKAN_EXAMPLE_MAIN()
\ No newline at end of file
diff --git a/external/Vulkan/examples/texturecubemaparray/texturecubemaparray.cpp b/external/Vulkan/examples/texturecubemaparray/texturecubemaparray.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..6d9c549470b76a9cafe39d89d96982ca41d65bb0
--- /dev/null
+++ b/external/Vulkan/examples/texturecubemaparray/texturecubemaparray.cpp
@@ -0,0 +1,551 @@
+/*
+* Vulkan Example - Cube map array texture loading and displaying
+*
+* Copyright (C) 2020 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkanexamplebase.h"
+#include "VulkanglTFModel.h"
+#include <ktx.h>
+#include <ktxvulkan.h>
+
+#define ENABLE_VALIDATION false
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	bool displaySkybox = true;
+
+	vks::Texture cubeMapArray;
+
+	struct Meshes {
+		vkglTF::Model skybox;
+		std::vector<vkglTF::Model> objects;
+		int32_t objectIndex = 0;
+	} models;
+
+	struct {
+		vks::Buffer object;
+		vks::Buffer skybox;
+	} uniformBuffers;
+
+	struct ShaderData {
+		glm::mat4 projection;
+		glm::mat4 modelView;
+		glm::mat4 inverseModelview;
+		float lodBias = 0.0f;
+		int cubeMapIndex = 1;
+	} shaderData;
+
+	struct {
+		VkPipeline skybox;
+		VkPipeline reflect;
+	} pipelines;
+
+	struct {
+		VkDescriptorSet object;
+		VkDescriptorSet skybox;
+	} descriptorSets;
+
+	VkPipelineLayout pipelineLayout;
+	VkDescriptorSetLayout descriptorSetLayout;
+
+	std::vector<std::string> objectNames;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Cube map textures";
+		camera.type = Camera::CameraType::lookat;
+		camera.setPosition(glm::vec3(0.0f, 0.0f, -4.0f));
+		camera.setRotationSpeed(0.25f);
+		camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 256.0f);
+	}
+
+	~VulkanExample()
+	{
+		// Clean up texture resources
+		vkDestroyImageView(device, cubeMapArray.view, nullptr);
+		vkDestroyImage(device, cubeMapArray.image, nullptr);
+		vkDestroySampler(device, cubeMapArray.sampler, nullptr);
+		vkFreeMemory(device, cubeMapArray.deviceMemory, nullptr);
+
+		vkDestroyPipeline(device, pipelines.skybox, nullptr);
+		vkDestroyPipeline(device, pipelines.reflect, nullptr);
+
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+
+		uniformBuffers.object.destroy();
+		uniformBuffers.skybox.destroy();
+	}
+
+	// Enable physical device features required for this example
+	virtual void getEnabledFeatures()
+	{
+		if (deviceFeatures.imageCubeArray) {
+			enabledFeatures.imageCubeArray = VK_TRUE;
+		} else {
+			vks::tools::exitFatal("Selected GPU does not support cube map arrays!", VK_ERROR_FEATURE_NOT_PRESENT);
+		}
+		enabledFeatures.imageCubeArray = VK_TRUE;
+		if (deviceFeatures.samplerAnisotropy) {
+			enabledFeatures.samplerAnisotropy = VK_TRUE;
+		}
+	};
+
+	void loadCubemapArray(std::string filename, VkFormat format, bool forceLinearTiling)
+	{
+		ktxResult result;
+		ktxTexture* ktxTexture;
+
+#if defined(__ANDROID__)
+		// Textures are stored inside the apk on Android (compressed)
+		// So they need to be loaded via the asset manager
+		AAsset* asset = AAssetManager_open(androidApp->activity->assetManager, filename.c_str(), AASSET_MODE_STREAMING);
+		if (!asset) {
+			vks::tools::exitFatal("Could not load texture from " + filename + "\n\nThe file may be part of the additional asset pack.\n\nRun \"download_assets.py\" in the repository root to download the latest version.", -1);
+		}
+		size_t size = AAsset_getLength(asset);
+		assert(size > 0);
+
+		ktx_uint8_t *textureData = new ktx_uint8_t[size];
+		AAsset_read(asset, textureData, size);
+		AAsset_close(asset);
+		result = ktxTexture_CreateFromMemory(textureData, size, KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, &ktxTexture);
+		delete[] textureData;
+#else
+		if (!vks::tools::fileExists(filename)) {
+			vks::tools::exitFatal("Could not load texture from " + filename + "\n\nThe file may be part of the additional asset pack.\n\nRun \"download_assets.py\" in the repository root to download the latest version.", -1);
+		}
+		result = ktxTexture_CreateFromNamedFile(filename.c_str(), KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, &ktxTexture);
+#endif
+		assert(result == KTX_SUCCESS);
+
+		// Get properties required for using and upload texture data from the ktx texture object
+		cubeMapArray.width = ktxTexture->baseWidth;
+		cubeMapArray.height = ktxTexture->baseHeight;
+		cubeMapArray.mipLevels = ktxTexture->numLevels;
+		cubeMapArray.layerCount = ktxTexture->numLayers;
+		ktx_uint8_t *ktxTextureData = ktxTexture_GetData(ktxTexture);
+		ktx_size_t ktxTextureSize = ktxTexture_GetSize(ktxTexture);
+
+		vks::Buffer sourceData;
+
+		// Create a host-visible source buffer that contains the raw image data
+		VkBufferCreateInfo bufferCreateInfo = vks::initializers::bufferCreateInfo();
+		bufferCreateInfo.size = ktxTextureSize;
+		bufferCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
+		bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+		VK_CHECK_RESULT(vkCreateBuffer(device, &bufferCreateInfo, nullptr, &sourceData.buffer));
+
+		// Get memory requirements for the source buffer (alignment, memory type bits)
+		VkMemoryRequirements memReqs;
+		vkGetBufferMemoryRequirements(device, sourceData.buffer, &memReqs);
+		VkMemoryAllocateInfo memAllocInfo = vks::initializers::memoryAllocateInfo();
+		memAllocInfo.allocationSize = memReqs.size;
+		// Get memory type index for a host visible buffer
+		memAllocInfo.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
+		VK_CHECK_RESULT(vkAllocateMemory(device, &memAllocInfo, nullptr, &sourceData.memory));
+		VK_CHECK_RESULT(vkBindBufferMemory(device, sourceData.buffer, sourceData.memory, 0));
+
+		// Copy the ktx image data into the source buffer
+		uint8_t *data;
+		VK_CHECK_RESULT(vkMapMemory(device, sourceData.memory, 0, memReqs.size, 0, (void **)&data));
+		memcpy(data, ktxTextureData, ktxTextureSize);
+		vkUnmapMemory(device, sourceData.memory);
+
+		// Create optimal tiled target image
+		VkImageCreateInfo imageCreateInfo = vks::initializers::imageCreateInfo();
+		imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
+		imageCreateInfo.format = format;
+		imageCreateInfo.mipLevels = cubeMapArray.mipLevels;
+		imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
+		imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
+		imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+		imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+		imageCreateInfo.extent = { cubeMapArray.width, cubeMapArray.height, 1 };
+		imageCreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
+		// Cube faces count as array layers in Vulkan
+		imageCreateInfo.arrayLayers = 6 * cubeMapArray.layerCount;
+		// This flag is required for cube map images
+		imageCreateInfo.flags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;
+		VK_CHECK_RESULT(vkCreateImage(device, &imageCreateInfo, nullptr, &cubeMapArray.image));
+
+		// Allocate memory for the cube map array image
+		vkGetImageMemoryRequirements(device, cubeMapArray.image, &memReqs);
+		memAllocInfo.allocationSize = memReqs.size;
+		memAllocInfo.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+		VK_CHECK_RESULT(vkAllocateMemory(device, &memAllocInfo, nullptr, &cubeMapArray.deviceMemory));
+		VK_CHECK_RESULT(vkBindImageMemory(device, cubeMapArray.image, cubeMapArray.deviceMemory, 0));
+
+		/*
+			We now copy the parts that make up the cube map array to our image via a command buffer
+			Cube map arrays in ktx are stored with a layout like this:
+			- Mip Level 0
+				- Layer 0 (= Cube map 0)
+					- Face +X
+					- Face -X
+					- Face +Y
+					- Face -Y
+					- Face +Z
+					- Face -Z
+				- Layer 1 (= Cube map 1)
+					- Face +X
+					...
+			- Mip Level 1
+				- Layer 0 (= Cube map 0)
+					- Face +X
+					...
+				- Layer 1 (= Cube map 1)
+					- Face +X
+					...
+		*/
+
+		VkCommandBuffer copyCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+
+		// Setup buffer copy regions for each face including all of its miplevels
+		std::vector<VkBufferImageCopy> bufferCopyRegions;
+		uint32_t offset = 0;
+		for (uint32_t face = 0; face < 6; face++) {
+			for (uint32_t layer = 0; layer < ktxTexture->numLayers; layer++) {
+				for (uint32_t level = 0; level < ktxTexture->numLevels; level++) {
+					ktx_size_t offset;
+					KTX_error_code ret = ktxTexture_GetImageOffset(ktxTexture, level, layer, face, &offset);
+					assert(ret == KTX_SUCCESS);
+					VkBufferImageCopy bufferCopyRegion = {};
+					bufferCopyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+					bufferCopyRegion.imageSubresource.mipLevel = level;
+					bufferCopyRegion.imageSubresource.baseArrayLayer = layer * 6 + face;
+					bufferCopyRegion.imageSubresource.layerCount = 1;
+					bufferCopyRegion.imageExtent.width = ktxTexture->baseWidth >> level;
+					bufferCopyRegion.imageExtent.height = ktxTexture->baseHeight >> level;
+					bufferCopyRegion.imageExtent.depth = 1;
+					bufferCopyRegion.bufferOffset = offset;
+					bufferCopyRegions.push_back(bufferCopyRegion);
+				}
+			}
+		}
+
+		VkImageSubresourceRange subresourceRange = {};
+		subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+		subresourceRange.baseMipLevel = 0;
+		subresourceRange.levelCount = cubeMapArray.mipLevels;
+		subresourceRange.layerCount = 6 * cubeMapArray.layerCount;
+
+		// Transition target image to accept the writes from our buffer to image copies
+		vks::tools::setImageLayout(copyCmd, cubeMapArray.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, subresourceRange);
+
+		// Copy the cube map array buffer parts from the staging buffer to the optimal tiled image
+		vkCmdCopyBufferToImage(
+			copyCmd,
+			sourceData.buffer,
+			cubeMapArray.image,
+			VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+			static_cast<uint32_t>(bufferCopyRegions.size()),
+			bufferCopyRegions.data()
+			);
+
+		// Transition image to shader read layout
+		cubeMapArray.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+		vks::tools::setImageLayout(copyCmd, cubeMapArray.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, cubeMapArray.imageLayout, subresourceRange);
+
+		vulkanDevice->flushCommandBuffer(copyCmd, queue, true);
+
+		// Create sampler
+		VkSamplerCreateInfo sampler = vks::initializers::samplerCreateInfo();
+		sampler.magFilter = VK_FILTER_LINEAR;
+		sampler.minFilter = VK_FILTER_LINEAR;
+		sampler.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+		sampler.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+		sampler.addressModeV = sampler.addressModeU;
+		sampler.addressModeW = sampler.addressModeU;
+		sampler.mipLodBias = 0.0f;
+		sampler.compareOp = VK_COMPARE_OP_NEVER;
+		sampler.minLod = 0.0f;
+		sampler.maxLod = static_cast<float>(cubeMapArray.mipLevels);
+		sampler.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
+		sampler.maxAnisotropy = 1.0f;
+		if (vulkanDevice->features.samplerAnisotropy)
+		{
+			sampler.maxAnisotropy = vulkanDevice->properties.limits.maxSamplerAnisotropy;
+			sampler.anisotropyEnable = VK_TRUE;
+		}
+		VK_CHECK_RESULT(vkCreateSampler(device, &sampler, nullptr, &cubeMapArray.sampler));
+
+		// Create the image view for a cube map array
+		VkImageViewCreateInfo view = vks::initializers::imageViewCreateInfo();
+		view.viewType = VK_IMAGE_VIEW_TYPE_CUBE_ARRAY;
+		view.format = format;
+		view.components = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A };
+		view.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 };
+		view.subresourceRange.layerCount = 6 * cubeMapArray.layerCount;
+		view.subresourceRange.levelCount = cubeMapArray.mipLevels;
+		view.image = cubeMapArray.image;
+		VK_CHECK_RESULT(vkCreateImageView(device, &view, nullptr, &cubeMapArray.view));
+
+		// Clean up staging resources
+		vkFreeMemory(device, sourceData.memory, nullptr);
+		vkDestroyBuffer(device, sourceData.buffer, nullptr);
+		ktxTexture_Destroy(ktxTexture);
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		clearValues[0].color = defaultClearColor;
+		clearValues[1].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.offset.x = 0;
+		renderPassBeginInfo.renderArea.offset.y = 0;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		renderPassBeginInfo.clearValueCount = 2;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			// Set target frame buffer
+			renderPassBeginInfo.framebuffer = frameBuffers[i];
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			VkViewport viewport = vks::initializers::viewport((float)width,	(float)height, 0.0f, 1.0f);
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+			VkRect2D scissor = vks::initializers::rect2D(width,	height,	0, 0);
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+			VkDeviceSize offsets[1] = { 0 };
+
+			// Skybox
+			if (displaySkybox)
+			{
+				vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets.skybox, 0, NULL);
+				vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.skybox);
+				models.skybox.draw(drawCmdBuffers[i]);
+			}
+
+			// 3D object
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets.object, 0, NULL);
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.reflect);
+			models.objects[models.objectIndex].draw(drawCmdBuffers[i]);
+
+			drawUI(drawCmdBuffers[i]);
+
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void loadAssets()
+	{
+		uint32_t glTFLoadingFlags = vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::FlipY;
+		// Skybox
+		models.skybox.loadFromFile(getAssetPath() + "models/cube.gltf", vulkanDevice, queue, glTFLoadingFlags);
+		// Objects
+		std::vector<std::string> filenames = { "sphere.gltf", "teapot.gltf", "torusknot.gltf", "venus.gltf" };
+		objectNames = { "Sphere", "Teapot", "Torusknot", "Venus" };
+		models.objects.resize(filenames.size());
+		for (size_t i = 0; i < filenames.size(); i++) {
+			models.objects[i].loadFromFile(getAssetPath() + "models/" + filenames[i], vulkanDevice, queue, glTFLoadingFlags);
+		}
+		// Load the cube map array from a ktx texture file
+		loadCubemapArray(getAssetPath() + "textures/cubemap_array.ktx", VK_FORMAT_R8G8B8A8_UNORM, false);
+	}
+
+	void setupDescriptorPool()
+	{
+		const std::vector<VkDescriptorPoolSize> poolSizes = {
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2),
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2)
+		};
+		const VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 2);
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+	}
+
+	void setupDescriptorSetLayout()
+	{
+		const std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			// Binding 0 : Uniform buffer
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0),
+			// Binding 1 : Fragment shader image sampler
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1)
+		};
+
+		const VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout));
+
+		const VkPipelineLayoutCreateInfo pipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1);
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelineLayout));
+	}
+
+	void setupDescriptorSets()
+	{
+		// Image descriptor for the cube map texture
+		VkDescriptorImageInfo textureDescriptor = vks::initializers::descriptorImageInfo(cubeMapArray.sampler, cubeMapArray.view, cubeMapArray.imageLayout);
+		VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1);
+
+		// 3D object descriptor set
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.object));
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets =
+		{
+			// Binding 0 : Vertex shader uniform buffer
+			vks::initializers::writeDescriptorSet(descriptorSets.object, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.object.descriptor),
+			// Binding 1 : Fragment shader cubemap sampler
+			vks::initializers::writeDescriptorSet(descriptorSets.object, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &textureDescriptor)
+		};
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr);
+
+		// Sky box descriptor set
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.skybox));
+		writeDescriptorSets =
+		{
+			// Binding 0 : Vertex shader uniform buffer
+			vks::initializers::writeDescriptorSet(descriptorSets.skybox, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.skybox.descriptor),
+			// Binding 1 : Fragment shader cubemap sampler
+			vks::initializers::writeDescriptorSet(descriptorSets.skybox, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &textureDescriptor)
+		};
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr);
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_FALSE, VK_FALSE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+		VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
+		std::vector<VkDynamicState> dynamicStateEnables = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
+		VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass, 0);
+		pipelineCI.pInputAssemblyState = &inputAssemblyState;
+		pipelineCI.pRasterizationState = &rasterizationState;
+		pipelineCI.pColorBlendState = &colorBlendState;
+		pipelineCI.pMultisampleState = &multisampleState;
+		pipelineCI.pViewportState = &viewportState;
+		pipelineCI.pDepthStencilState = &depthStencilState;
+		pipelineCI.pDynamicState = &dynamicState;
+		pipelineCI.stageCount = static_cast<uint32_t>(shaderStages.size());
+		pipelineCI.pStages = shaderStages.data();
+		pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal });
+
+		// Skybox pipeline (background cube)
+		shaderStages[0] = loadShader(getShadersPath() + "texturecubemaparray/skybox.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "texturecubemaparray/skybox.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		rasterizationState.cullMode = VK_CULL_MODE_FRONT_BIT;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.skybox));
+
+		// Cube map reflect pipeline
+		shaderStages[0] = loadShader(getShadersPath() + "texturecubemaparray/reflect.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "texturecubemaparray/reflect.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		// Enable depth test and write
+		depthStencilState.depthWriteEnable = VK_TRUE;
+		depthStencilState.depthTestEnable = VK_TRUE;
+		// Flip cull mode
+		rasterizationState.cullMode = VK_CULL_MODE_BACK_BIT;
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.reflect));
+	}
+
+	void prepareUniformBuffers()
+	{
+		// Object vertex shader uniform buffer
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.object,
+			sizeof(ShaderData)));
+
+		// Skybox vertex shader uniform buffer
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBuffers.skybox,
+			sizeof(ShaderData)));
+
+		// Map persistent
+		VK_CHECK_RESULT(uniformBuffers.object.map());
+		VK_CHECK_RESULT(uniformBuffers.skybox.map());
+
+		updateUniformBuffers();
+	}
+
+	void updateUniformBuffers()
+	{
+		// 3D object
+		shaderData.projection = camera.matrices.perspective;
+		shaderData.modelView = camera.matrices.view;
+		shaderData.inverseModelview = glm::inverse(camera.matrices.view);
+		memcpy(uniformBuffers.object.mapped, &shaderData, sizeof(ShaderData));
+
+		// Skybox
+		shaderData.modelView = camera.matrices.view;
+		// Cancel out translation
+		shaderData.modelView[3] = glm::vec4(0.0f, 0.0f, 0.0f, 1.0f);
+		memcpy(uniformBuffers.skybox.mapped, &shaderData, sizeof(ShaderData));
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+		VulkanExampleBase::submitFrame();
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		loadAssets();
+		prepareUniformBuffers();
+		setupDescriptorSetLayout();
+		preparePipelines();
+		setupDescriptorPool();
+		setupDescriptorSets();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+		if (camera.updated) {
+			updateUniformBuffers();
+		}
+	}
+
+	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
+	{
+		if (overlay->header("Settings")) {
+			if (overlay->sliderInt("Cube map", &shaderData.cubeMapIndex, 0, cubeMapArray.layerCount - 1)) {
+				updateUniformBuffers();
+			}
+			if (overlay->sliderFloat("LOD bias", &shaderData.lodBias, 0.0f, (float)cubeMapArray.mipLevels)) {
+				updateUniformBuffers();
+			}
+			if (overlay->comboBox("Object type", &models.objectIndex, objectNames)) {
+				buildCommandBuffers();
+			}
+			if (overlay->checkBox("Skybox", &displaySkybox)) {
+				buildCommandBuffers();
+			}
+		}
+	}
+};
+
+VULKAN_EXAMPLE_MAIN()
\ No newline at end of file
diff --git a/external/Vulkan/examples/texturemipmapgen/README.md b/external/Vulkan/examples/texturemipmapgen/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..cd7d351810a5ed194886b0a6267230ca22a3c61e
--- /dev/null
+++ b/external/Vulkan/examples/texturemipmapgen/README.md
@@ -0,0 +1,202 @@
+# Run-time mip-map generation
+
+<img src="../../screenshots/texture_mipmap_gen.jpg" height="256px">
+
+## Synopsis
+
+Generates a complete texture mip-chain at runtime from a base image using image blits and proper image barriers.
+
+## Requirements
+To downsample from one mip level to the next, we will be using [```vkCmdBlitImage```](https://www.khronos.org/registry/vulkan/specs/1.0/man/html/vkCmdBlitImage.html). This requires the format used to support the ```BLIT_SRC_BIT``` and the  ```BLIT_DST_BIT``` flags. If these are not supported, the image format can't be used to blit and you'd either have to choose a different format or use e.g. a compute shader to generate mip levels. The example uses the ```VK_FORMAT_R8G8B8A8_UNORM``` that should support these flags on most implementations.
+
+***Note:*** Use [```vkGetPhysicalDeviceFormatProperties```](https://www.khronos.org/registry/vulkan/specs/1.0/man/html/vkGetPhysicalDeviceFormatProperties.html) to check if the format supports the blit flags first. 
+
+## Description
+
+This examples demonstrates how to generate a complete texture mip-chain at runtime instead of loading offline generated mip-maps from a texture file.
+
+While usually not applied for textures stored on the disk (that usually have the mips generated offline and stored in the file, see [basic texture mapping example](../texture)) this technique is used textures are generated at runtime, e.g. when doing dynamic cubemaps or other render-to-texture effects.
+
+Having mip-maps for runtime generated textures offers lots of benefits, both in terms of image stability and performance. Without mip mapping the image will become noisy, especially with high frequency textures (and texture components like specular) and using mip mapping will result in higher performance due to caching.
+
+Though this example only generates one mip-chain for a single texture at the beginning this technique can also be used during normal frame rendering to generate mip-chains for dynamic textures. 
+
+Some GPUs also offer ```asynchronous transfer queues``` (check for queue families with only the ?  ```VK_QUEUE_TRANSFER_BIT``` set) that may be used to speed up such operations.  
+
+## Points of interest
+
+### Image setup
+Even though we'll only upload the first mip level initially, we create the image with number of desired mip levels. The following formula is used to calculate the number of mip levels based on the max. image extent:
+
+```cpp
+texture.mipLevels = floor(log2(std::max(texture.width, texture.height))) + 1;
+```
+
+This is then passed to the image creat info:
+
+```cpp
+VkImageCreateInfo imageCreateInfo = vkTools::initializers::imageCreateInfo();
+imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
+imageCreateInfo.format = format;
+imageCreateInfo.mipLevels = texture.mipLevels;
+...
+```
+
+Setting the number of desired mip levels is necessary as this is used for allocating the right amount of memory for the image (```vkAllocateMemory```). 
+
+### Upload base mip level
+
+Before generating the mip-chain we need to copy the image data loaded from disk into the newly generated image. This image will be the base for our mip-chain:
+
+```cpp
+VkBufferImageCopy bufferCopyRegion = {};
+bufferCopyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+bufferCopyRegion.imageSubresource.mipLevel = 0;
+bufferCopyRegion.imageExtent.width = texture.width;
+bufferCopyRegion.imageExtent.height = texture.height;
+bufferCopyRegion.imageExtent.depth = 1;
+
+vkCmdCopyBufferToImage(copyCmd, stagingBuffer, texture.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &bufferCopyRegion);
+```
+
+### Prepare base mip level
+As we are going to blit ***from*** the base mip-level just uploaded we also need to set insert an image memory barrier that sets the image layout to ```TRANSFER_SRC``` for the base mip level:
+
+```cpp
+VkImageSubresourceRange subresourceRange = {};
+subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+subresourceRange.levelCount = 1;
+subresourceRange.layerCount = 1;
+
+vks::tools::insertImageMemoryBarrier(
+  copyCmd,
+  texture.image,
+  VK_ACCESS_TRANSFER_WRITE_BIT,
+  VK_ACCESS_TRANSFER_READ_BIT,
+  VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+  VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+  VK_PIPELINE_STAGE_TRANSFER_BIT,
+  VK_PIPELINE_STAGE_TRANSFER_BIT,
+  subresourceRange);
+```
+
+### Generating the mip-chain
+There are two different ways of generating the mip-chain. The first one is to blit down the whole mip-chain from level n-1 to n, the other way would be to always use the base image and blit down from that to all levels. This example uses the first one.
+
+***Note:*** Blitting (same for copying) images is done inside of a command buffer that has to be submitted and as such has to be synchronized before using the new image with e.g. a ```vkFence```. 
+
+We simply loop over all remaining mip levels (level 0 was loaded from disk) and prepare a ```VkImageBlit``` structure for each blit from mip level i-1 to level i.
+
+First the source for out blit. This is the previous mip level. The dimensions of the blit source are specified by srcOffset:
+```cpp
+for (int32_t i = 1; i < texture.mipLevels; i++)
+{
+  VkImageBlit imageBlit{};				
+
+  // Source
+  imageBlit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+  imageBlit.srcSubresource.layerCount = 1;
+  imageBlit.srcSubresource.mipLevel = i-1;
+  imageBlit.srcOffsets[1].x = int32_t(texture.width >> (i - 1));
+  imageBlit.srcOffsets[1].y = int32_t(texture.height >> (i - 1));
+  imageBlit.srcOffsets[1].z = 1;
+```
+Setup for the destination mip level (1), with the dimensions for the blit destination specified in dstOffsets[1]:
+```cpp
+  // Destination
+  imageBlit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+  imageBlit.dstSubresource.layerCount = 1;
+  imageBlit.dstSubresource.mipLevel = i;
+  imageBlit.dstOffsets[1].x = int32_t(texture.width >> i);
+  imageBlit.dstOffsets[1].y = int32_t(texture.height >> i);
+  imageBlit.dstOffsets[1].z = 1;
+```
+
+Before we can blit to this mip level, we need to transition its image layout to ```TRANSFER_DST```:
+```cpp
+  VkImageSubresourceRange mipSubRange = {};
+  mipSubRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+  mipSubRange.baseMipLevel = i;
+  mipSubRange.levelCount = 1;
+  mipSubRange.layerCount = 1;
+
+  // Prepare current mip level as image blit destination
+  vks::tools::insertImageMemoryBarrier(
+    blitCmd,
+    texture.image,
+    0,
+    VK_ACCESS_TRANSFER_WRITE_BIT,
+    VK_IMAGE_LAYOUT_UNDEFINED,
+    VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+    VK_PIPELINE_STAGE_TRANSFER_BIT,
+    VK_PIPELINE_STAGE_TRANSFER_BIT,
+    mipSubRange);
+``` 
+Note that we set the ```baseMipLevel``` member of the subresource range so the image memory barrier will only affect the one mip level we want to copy to.
+
+Now that the mip level we want to copy from and the one we'll copy to have are in the proper layout (transfer source and destination) we can issue the [```vkCmdBlitImage```](https://www.khronos.org/registry/vulkan/specs/1.0/man/html/vkCmdBlitImage.html) to copy from mip level (i-1) to mip level (i):
+
+```cpp
+  vkCmdBlitImage(
+    blitCmd,
+    texture.image,
+    VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+    texture.image,
+    VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+    1,
+    &imageBlit,
+    VK_FILTER_LINEAR);
+```
+```vkCmdBlitImage``` does the (down) scaling from mip level (i-1) to mip level (i) using a linear filter.
+
+After the blit is done we can use this mip level as a base for the next level, so we transition the layout from ```TRANSFER_DST_OPTIMAL``` to ```TRANSFER_SRC_OPTIMAL``` so we can use this level as transfer source for the next level:
+
+```cpp
+  // Prepare current mip level as image blit source for next level
+  vks::tools::insertImageMemoryBarrier(
+    copyCmd,
+    texture.image,
+    VK_ACCESS_TRANSFER_WRITE_BIT,
+    VK_ACCESS_TRANSFER_READ_BIT,
+    VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+    VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+    VK_PIPELINE_STAGE_TRANSFER_BIT,
+    VK_PIPELINE_STAGE_TRANSFER_BIT,
+    mipSubRange);
+}
+```
+
+### Final image layout transitions
+Once the loop is done we need to transition all mip levels of the image to their actual usage layout, which is ```SHADER_READ``` for this example. Note that after the loop all levels will be in the ```TRANSER_SRC``` layout allowing us to transfer the whole image at once:
+
+```cpp
+  subresourceRange.levelCount = texture.mipLevels;
+  vks::tools::insertImageMemoryBarrier(
+    copyCmd,
+    texture.image,
+    VK_ACCESS_TRANSFER_READ_BIT,
+    VK_ACCESS_SHADER_READ_BIT,
+    VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+    VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
+    VK_PIPELINE_STAGE_TRANSFER_BIT,
+    VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
+    subresourceRange);
+```  
+
+Submitting that command buffer will result in an image with a complete mip-chain and all mip levels being transitioned to the proper image layout for shader reads.
+
+### Image View creation
+The Image View also requires information about how many Mip Levels are used. This is specified in the ```VkImageViewCreateInfo.subresourceRange.levelCount``` field.
+
+```cpp
+  VkImageViewCreateInfo view = vks::initializers::imageViewCreateInfo();
+  view.image = texture.image;
+  view.viewType = VK_IMAGE_VIEW_TYPE_2D;
+  view.format = format;
+  view.components = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A };
+  view.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+  view.subresourceRange.baseMipLevel = 0;
+  view.subresourceRange.baseArrayLayer = 0;
+  view.subresourceRange.layerCount = 1;
+  view.subresourceRange.levelCount = texture.mipLevels;
+```  
\ No newline at end of file
diff --git a/external/Vulkan/examples/texturemipmapgen/texturemipmapgen.cpp b/external/Vulkan/examples/texturemipmapgen/texturemipmapgen.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..1fae3773ea5755c893a9c0d63a9cf925da8ad218
--- /dev/null
+++ b/external/Vulkan/examples/texturemipmapgen/texturemipmapgen.cpp
@@ -0,0 +1,584 @@
+/*
+* Vulkan Example - Runtime mip map generation
+*
+* Copyright (C) by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkanexamplebase.h"
+#include "VulkanglTFModel.h"
+#include <ktx.h>
+#include <ktxvulkan.h>
+
+#define ENABLE_VALIDATION false
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	struct Texture {
+		VkImage image;
+		VkDeviceMemory deviceMemory;
+		VkImageView view;
+		uint32_t width, height;
+		uint32_t mipLevels;
+	} texture;
+
+	// To demonstrate mip mapping and filtering this example uses separate samplers
+	std::vector<std::string> samplerNames{ "No mip maps" , "Mip maps (bilinear)" , "Mip maps (anisotropic)" };
+	std::vector<VkSampler> samplers;
+
+	vkglTF::Model model;
+
+	vks::Buffer uniformBufferVS;
+
+	struct uboVS {
+		glm::mat4 projection;
+		glm::mat4 view;
+		glm::mat4 model;
+		glm::vec4 viewPos;
+		float lodBias = 0.0f;
+		int32_t samplerIndex = 2;
+	} uboVS;
+
+	VkPipeline pipeline;
+	VkPipelineLayout pipelineLayout;
+	VkDescriptorSet descriptorSet;
+	VkDescriptorSetLayout descriptorSetLayout;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Runtime mip map generation";
+		camera.type = Camera::CameraType::firstperson;
+		camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 1024.0f);
+		camera.setRotation(glm::vec3(0.0f, 90.0f, 0.0f));
+		camera.setTranslation(glm::vec3(40.75f, 0.0f, 0.0f));
+		camera.movementSpeed = 2.5f;
+		camera.rotationSpeed = 0.5f;
+		timerSpeed *= 0.05f;
+	}
+
+	~VulkanExample()
+	{
+		destroyTextureImage(texture);
+		vkDestroyPipeline(device, pipeline, nullptr);
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+		uniformBufferVS.destroy();
+		for (auto sampler : samplers)
+		{
+			vkDestroySampler(device, sampler, nullptr);
+		}
+	}
+
+	virtual void getEnabledFeatures()
+	{
+		if (deviceFeatures.samplerAnisotropy) {
+			enabledFeatures.samplerAnisotropy = VK_TRUE;
+		}
+	}
+
+	void loadTexture(std::string filename, VkFormat format, bool forceLinearTiling)
+	{
+		ktxResult result;
+		ktxTexture* ktxTexture;
+
+#if defined(__ANDROID__)
+		// Textures are stored inside the apk on Android (compressed)
+		// So they need to be loaded via the asset manager
+		AAsset* asset = AAssetManager_open(androidApp->activity->assetManager, filename.c_str(), AASSET_MODE_STREAMING);
+		if (!asset) {
+			vks::tools::exitFatal("Could not load texture from " + filename + "\n\nThe file may be part of the additional asset pack.\n\nRun \"download_assets.py\" in the repository root to download the latest version.", -1);
+		}
+		size_t size = AAsset_getLength(asset);
+		assert(size > 0);
+
+		ktx_uint8_t *textureData = new ktx_uint8_t[size];
+		AAsset_read(asset, textureData, size);
+		AAsset_close(asset);
+		result = ktxTexture_CreateFromMemory(textureData, size, KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, &ktxTexture);
+		delete[] textureData;
+#else
+		if (!vks::tools::fileExists(filename)) {
+			vks::tools::exitFatal("Could not load texture from " + filename + "\n\nThe file may be part of the additional asset pack.\n\nRun \"download_assets.py\" in the repository root to download the latest version.", -1);
+		}
+		result = ktxTexture_CreateFromNamedFile(filename.c_str(), KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, &ktxTexture);
+#endif
+		assert(result == KTX_SUCCESS);
+
+		texture.width = ktxTexture->baseWidth;
+		texture.height = ktxTexture->baseHeight;
+		ktx_uint8_t *ktxTextureData = ktxTexture_GetData(ktxTexture);
+		ktx_size_t ktxTextureSize = ktxTexture_GetImageSize(ktxTexture, 0);
+
+		// calculate num of mip maps
+		// numLevels = 1 + floor(log2(max(w, h, d)))
+		// Calculated as log2(max(width, height, depth))c + 1 (see specs)
+		texture.mipLevels = floor(log2(std::max(texture.width, texture.height))) + 1;
+
+		// Get device properties for the requested texture format
+		VkFormatProperties formatProperties;
+		vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &formatProperties);
+		// Mip-chain generation requires support for blit source and destination
+		assert(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT);
+		assert(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT);
+
+		VkMemoryAllocateInfo memAllocInfo = vks::initializers::memoryAllocateInfo();
+		VkMemoryRequirements memReqs = {};
+
+		// Create a host-visible staging buffer that contains the raw image data
+		VkBuffer stagingBuffer;
+		VkDeviceMemory stagingMemory;
+
+		VkBufferCreateInfo bufferCreateInfo = vks::initializers::bufferCreateInfo();
+		bufferCreateInfo.size = ktxTextureSize;
+		// This buffer is used as a transfer source for the buffer copy
+		bufferCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
+		bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+		VK_CHECK_RESULT(vkCreateBuffer(device, &bufferCreateInfo, nullptr, &stagingBuffer));
+		vkGetBufferMemoryRequirements(device, stagingBuffer, &memReqs);
+		memAllocInfo.allocationSize = memReqs.size;
+		memAllocInfo.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
+		VK_CHECK_RESULT(vkAllocateMemory(device, &memAllocInfo, nullptr, &stagingMemory));
+		VK_CHECK_RESULT(vkBindBufferMemory(device, stagingBuffer, stagingMemory, 0));
+
+		// Copy texture data into staging buffer
+		uint8_t *data;
+		VK_CHECK_RESULT(vkMapMemory(device, stagingMemory, 0, memReqs.size, 0, (void **)&data));
+		memcpy(data, ktxTextureData, ktxTextureSize);
+		vkUnmapMemory(device, stagingMemory);
+
+		// Create optimal tiled target image
+		VkImageCreateInfo imageCreateInfo = vks::initializers::imageCreateInfo();
+		imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
+		imageCreateInfo.format = format;
+		imageCreateInfo.mipLevels = texture.mipLevels;
+		imageCreateInfo.arrayLayers = 1;
+		imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
+		imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
+		imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+		imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+		imageCreateInfo.extent = { texture.width, texture.height, 1 };
+		imageCreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
+		VK_CHECK_RESULT(vkCreateImage(device, &imageCreateInfo, nullptr, &texture.image));
+		vkGetImageMemoryRequirements(device, texture.image, &memReqs);
+		memAllocInfo.allocationSize = memReqs.size;
+		memAllocInfo.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+		VK_CHECK_RESULT(vkAllocateMemory(device, &memAllocInfo, nullptr, &texture.deviceMemory));
+		VK_CHECK_RESULT(vkBindImageMemory(device, texture.image, texture.deviceMemory, 0));
+
+		VkCommandBuffer copyCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+
+		VkImageSubresourceRange subresourceRange = {};
+		subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+		subresourceRange.levelCount = 1;
+		subresourceRange.layerCount = 1;
+
+		// Optimal image will be used as destination for the copy, so we must transfer from our initial undefined image layout to the transfer destination layout
+		vks::tools::insertImageMemoryBarrier(
+			copyCmd,
+			texture.image,
+			0,
+			VK_ACCESS_TRANSFER_WRITE_BIT,
+			VK_IMAGE_LAYOUT_UNDEFINED,
+			VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+			VK_PIPELINE_STAGE_TRANSFER_BIT,
+			VK_PIPELINE_STAGE_TRANSFER_BIT,
+			subresourceRange);
+
+		// Copy the first mip of the chain, remaining mips will be generated
+		VkBufferImageCopy bufferCopyRegion = {};
+		bufferCopyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+		bufferCopyRegion.imageSubresource.mipLevel = 0;
+		bufferCopyRegion.imageSubresource.baseArrayLayer = 0;
+		bufferCopyRegion.imageSubresource.layerCount = 1;
+		bufferCopyRegion.imageExtent.width = texture.width;
+		bufferCopyRegion.imageExtent.height = texture.height;
+		bufferCopyRegion.imageExtent.depth = 1;
+
+		vkCmdCopyBufferToImage(copyCmd, stagingBuffer, texture.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &bufferCopyRegion);
+
+		// Transition first mip level to transfer source for read during blit
+		vks::tools::insertImageMemoryBarrier(
+			copyCmd,
+			texture.image,
+			VK_ACCESS_TRANSFER_WRITE_BIT,
+			VK_ACCESS_TRANSFER_READ_BIT,
+			VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+			VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+			VK_PIPELINE_STAGE_TRANSFER_BIT,
+			VK_PIPELINE_STAGE_TRANSFER_BIT,
+			subresourceRange);
+
+		vulkanDevice->flushCommandBuffer(copyCmd, queue, true);
+
+		// Clean up staging resources
+		vkFreeMemory(device, stagingMemory, nullptr);
+		vkDestroyBuffer(device, stagingBuffer, nullptr);
+		ktxTexture_Destroy(ktxTexture);
+
+		// Generate the mip chain
+		// ---------------------------------------------------------------
+		// We copy down the whole mip chain doing a blit from mip-1 to mip
+		// An alternative way would be to always blit from the first mip level and sample that one down
+		VkCommandBuffer blitCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+
+		// Copy down mips from n-1 to n
+		for (int32_t i = 1; i < texture.mipLevels; i++)
+		{
+			VkImageBlit imageBlit{};
+
+			// Source
+			imageBlit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+			imageBlit.srcSubresource.layerCount = 1;
+			imageBlit.srcSubresource.mipLevel = i-1;
+			imageBlit.srcOffsets[1].x = int32_t(texture.width >> (i - 1));
+			imageBlit.srcOffsets[1].y = int32_t(texture.height >> (i - 1));
+			imageBlit.srcOffsets[1].z = 1;
+
+			// Destination
+			imageBlit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+			imageBlit.dstSubresource.layerCount = 1;
+			imageBlit.dstSubresource.mipLevel = i;
+			imageBlit.dstOffsets[1].x = int32_t(texture.width >> i);
+			imageBlit.dstOffsets[1].y = int32_t(texture.height >> i);
+			imageBlit.dstOffsets[1].z = 1;
+
+			VkImageSubresourceRange mipSubRange = {};
+			mipSubRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+			mipSubRange.baseMipLevel = i;
+			mipSubRange.levelCount = 1;
+			mipSubRange.layerCount = 1;
+
+			// Prepare current mip level as image blit destination
+			vks::tools::insertImageMemoryBarrier(
+				blitCmd,
+				texture.image,
+				0,
+				VK_ACCESS_TRANSFER_WRITE_BIT,
+				VK_IMAGE_LAYOUT_UNDEFINED,
+				VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+				VK_PIPELINE_STAGE_TRANSFER_BIT,
+				VK_PIPELINE_STAGE_TRANSFER_BIT,
+				mipSubRange);
+
+			// Blit from previous level
+			vkCmdBlitImage(
+				blitCmd,
+				texture.image,
+				VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+				texture.image,
+				VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+				1,
+				&imageBlit,
+				VK_FILTER_LINEAR);
+
+			// Prepare current mip level as image blit source for next level
+			vks::tools::insertImageMemoryBarrier(
+				blitCmd,
+				texture.image,
+				VK_ACCESS_TRANSFER_WRITE_BIT,
+				VK_ACCESS_TRANSFER_READ_BIT,
+				VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+				VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+				VK_PIPELINE_STAGE_TRANSFER_BIT,
+				VK_PIPELINE_STAGE_TRANSFER_BIT,
+				mipSubRange);
+		}
+
+		// After the loop, all mip layers are in TRANSFER_SRC layout, so transition all to SHADER_READ
+		subresourceRange.levelCount = texture.mipLevels;
+		vks::tools::insertImageMemoryBarrier(
+			blitCmd,
+			texture.image,
+			VK_ACCESS_TRANSFER_READ_BIT,
+			VK_ACCESS_SHADER_READ_BIT,
+			VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+			VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
+			VK_PIPELINE_STAGE_TRANSFER_BIT,
+			VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
+			subresourceRange);
+
+		vulkanDevice->flushCommandBuffer(blitCmd, queue, true);
+		// ---------------------------------------------------------------
+
+		// Create samplers
+		samplers.resize(3);
+		VkSamplerCreateInfo sampler = vks::initializers::samplerCreateInfo();
+		sampler.magFilter = VK_FILTER_LINEAR;
+		sampler.minFilter = VK_FILTER_LINEAR;
+		sampler.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+		sampler.addressModeU = VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
+		sampler.addressModeV = VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
+		sampler.addressModeW = VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
+		sampler.mipLodBias = 0.0f;
+		sampler.compareOp = VK_COMPARE_OP_NEVER;
+		sampler.minLod = 0.0f;
+		sampler.maxLod = 0.0f;
+		sampler.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
+		sampler.maxAnisotropy = 1.0;
+		sampler.anisotropyEnable = VK_FALSE;
+
+		// Without mip mapping
+		VK_CHECK_RESULT(vkCreateSampler(device, &sampler, nullptr, &samplers[0]));
+
+		// With mip mapping
+		sampler.maxLod = (float)texture.mipLevels;
+		VK_CHECK_RESULT(vkCreateSampler(device, &sampler, nullptr, &samplers[1]));
+
+		// With mip mapping and anisotropic filtering
+		if (vulkanDevice->features.samplerAnisotropy)
+		{
+			sampler.maxAnisotropy = vulkanDevice->properties.limits.maxSamplerAnisotropy;
+			sampler.anisotropyEnable = VK_TRUE;
+		}
+		VK_CHECK_RESULT(vkCreateSampler(device, &sampler, nullptr, &samplers[2]));
+
+		// Create image view
+		VkImageViewCreateInfo view = vks::initializers::imageViewCreateInfo();
+		view.image = texture.image;
+		view.viewType = VK_IMAGE_VIEW_TYPE_2D;
+		view.format = format;
+		view.components = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A };
+		view.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+		view.subresourceRange.baseMipLevel = 0;
+		view.subresourceRange.baseArrayLayer = 0;
+		view.subresourceRange.layerCount = 1;
+		view.subresourceRange.levelCount = texture.mipLevels;
+		VK_CHECK_RESULT(vkCreateImageView(device, &view, nullptr, &texture.view));
+	}
+
+	// Free all Vulkan resources used a texture object
+	void destroyTextureImage(Texture texture)
+	{
+		vkDestroyImageView(device, texture.view, nullptr);
+		vkDestroyImage(device, texture.image, nullptr);
+		vkFreeMemory(device, texture.deviceMemory, nullptr);
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		clearValues[0].color = defaultClearColor;
+		clearValues[1].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.offset.x = 0;
+		renderPassBeginInfo.renderArea.offset.y = 0;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		renderPassBeginInfo.clearValueCount = 2;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			// Set target frame buffer
+			renderPassBeginInfo.framebuffer = frameBuffers[i];
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+			VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL);
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
+
+			model.draw(drawCmdBuffers[i]);
+
+			drawUI(drawCmdBuffers[i]);
+
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+
+		// Command buffer to be submitted to the queue
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+
+		// Submit to queue
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+
+		VulkanExampleBase::submitFrame();
+	}
+
+	void loadAssets()
+	{
+		model.loadFromFile(getAssetPath() + "models/tunnel_cylinder.gltf", vulkanDevice, queue, vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::FlipY);
+		loadTexture(getAssetPath() + "textures/metalplate_nomips_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, false);
+	}
+
+	void setupDescriptorPool()
+	{
+		std::vector<VkDescriptorPoolSize> poolSizes =
+		{
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1),	// Vertex shader UBO
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1),		// Sampled image
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_SAMPLER, 3),			// 3 samplers (array)
+		};
+
+		VkDescriptorPoolCreateInfo descriptorPoolInfo =
+			vks::initializers::descriptorPoolCreateInfo(
+				static_cast<uint32_t>(poolSizes.size()),
+				poolSizes.data(),
+				1);
+
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+	}
+
+	void setupDescriptorSetLayout()
+	{
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			// Binding 0: Vertex shader uniform buffer
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0),
+			// Binding 1: Sampled image
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, VK_SHADER_STAGE_FRAGMENT_BIT, 1),
+			// Binding 2: Sampler array (3 descriptors)
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 2, 3),
+		};
+
+		VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout));
+
+		VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1);
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayout));
+	}
+
+	void setupDescriptorSet()
+	{
+		VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout,1);
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet));
+
+		VkDescriptorImageInfo textureDescriptor = vks::initializers::descriptorImageInfo(VK_NULL_HANDLE, texture.view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets = {
+
+			// Binding 0: Vertex shader uniform buffer
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBufferVS.descriptor),
+			// Binding 1: Sampled image
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1, &textureDescriptor)
+		};
+
+		// Binding 2: Sampler array
+		std::vector<VkDescriptorImageInfo> samplerDescriptors;
+		for (auto i = 0; i < samplers.size(); i++)
+		{
+			samplerDescriptors.push_back(vks::initializers::descriptorImageInfo(samplers[i], VK_NULL_HANDLE, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL));
+		}
+		VkWriteDescriptorSet samplerDescriptorWrite{};
+		samplerDescriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+		samplerDescriptorWrite.dstSet = descriptorSet;
+		samplerDescriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER;
+		samplerDescriptorWrite.descriptorCount = static_cast<uint32_t>(samplerDescriptors.size());
+		samplerDescriptorWrite.pImageInfo = samplerDescriptors.data();
+		samplerDescriptorWrite.dstBinding = 2;
+		samplerDescriptorWrite.dstArrayElement = 0;
+		writeDescriptorSets.push_back(samplerDescriptorWrite);
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL);
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+		VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
+		std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
+		VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
+		std::array<VkPipelineShaderStageCreateInfo,2> shaderStages;
+
+		shaderStages[0] = loadShader(getShadersPath() + "texturemipmapgen/texture.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "texturemipmapgen/texture.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass, 0);
+		pipelineCI.pInputAssemblyState = &inputAssemblyState;
+		pipelineCI.pRasterizationState = &rasterizationState;
+		pipelineCI.pColorBlendState = &colorBlendState;
+		pipelineCI.pMultisampleState = &multisampleState;
+		pipelineCI.pViewportState = &viewportState;
+		pipelineCI.pDepthStencilState = &depthStencilState;
+		pipelineCI.pDynamicState = &dynamicState;
+		pipelineCI.stageCount = static_cast<uint32_t>(shaderStages.size());
+		pipelineCI.pStages = shaderStages.data();
+		pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position, vkglTF::VertexComponent::UV, vkglTF::VertexComponent::Normal });
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipeline));
+	}
+
+	// Prepare and initialize uniform buffer containing shader uniforms
+	void prepareUniformBuffers()
+	{
+		// Vertex shader uniform buffer block
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBufferVS,
+			sizeof(uboVS),
+			&uboVS));
+
+		updateUniformBuffers();
+	}
+
+	void updateUniformBuffers()
+	{
+		uboVS.projection = camera.matrices.perspective;
+		uboVS.view = camera.matrices.view;
+		uboVS.model = glm::rotate(glm::mat4(1.0f), glm::radians(timer * 360.0f), glm::vec3(1.0f, 0.0f, 0.0f));
+		uboVS.viewPos = glm::vec4(camera.position, 0.0f) * glm::vec4(-1.0f);
+		VK_CHECK_RESULT(uniformBufferVS.map());
+		memcpy(uniformBufferVS.mapped, &uboVS, sizeof(uboVS));
+		uniformBufferVS.unmap();
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		loadAssets();
+		prepareUniformBuffers();
+		setupDescriptorSetLayout();
+		preparePipelines();
+		setupDescriptorPool();
+		setupDescriptorSet();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+		if (!paused || camera.updated)
+		{
+			updateUniformBuffers();
+		}
+	}
+
+	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
+	{
+		if (overlay->header("Settings")) {
+			if (overlay->sliderFloat("LOD bias", &uboVS.lodBias, 0.0f, (float)texture.mipLevels)) {
+				updateUniformBuffers();
+			}
+			if (overlay->comboBox("Sampler type", &uboVS.samplerIndex, samplerNames)) {
+				updateUniformBuffers();
+			}
+		}
+	}
+};
+
+VULKAN_EXAMPLE_MAIN()
diff --git a/external/Vulkan/examples/texturesparseresidency/texturesparseresidency.cpp b/external/Vulkan/examples/texturesparseresidency/texturesparseresidency.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..0875a6ecfd25d3b926bea884575a50c189605e0a
--- /dev/null
+++ b/external/Vulkan/examples/texturesparseresidency/texturesparseresidency.cpp
@@ -0,0 +1,922 @@
+/*
+* Vulkan Example - Sparse texture residency example
+*
+* Copyright (C) 2016-2021 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+/*
+* Note : This sample is work-in-progress and works basically, but it's not yet finished
+*/
+
+#include "texturesparseresidency.h"
+
+/*
+	Virtual texture page 
+	Contains all functions and objects for a single page of a virtual texture
+ */
+
+VirtualTexturePage::VirtualTexturePage()
+{
+	// Pages are initially not backed up by memory (non-resident)
+	imageMemoryBind.memory = VK_NULL_HANDLE;
+}
+
+bool VirtualTexturePage::resident()
+{
+	return (imageMemoryBind.memory != VK_NULL_HANDLE);
+}
+
+// Allocate Vulkan memory for the virtual page
+bool VirtualTexturePage::allocate(VkDevice device, uint32_t memoryTypeIndex)
+{
+	if (imageMemoryBind.memory != VK_NULL_HANDLE)
+	{
+		return false;
+	};
+
+	imageMemoryBind = {};
+
+	VkMemoryAllocateInfo allocInfo = vks::initializers::memoryAllocateInfo();
+	allocInfo.allocationSize = size;
+	allocInfo.memoryTypeIndex = memoryTypeIndex;
+	VK_CHECK_RESULT(vkAllocateMemory(device, &allocInfo, nullptr, &imageMemoryBind.memory));
+
+	VkImageSubresource subResource{};
+	subResource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+	subResource.mipLevel = mipLevel;
+	subResource.arrayLayer = layer;
+
+	// Sparse image memory binding
+	imageMemoryBind.subresource = subResource;
+	imageMemoryBind.extent = extent;
+	imageMemoryBind.offset = offset;
+	return true;
+}
+
+// Release Vulkan memory allocated for this page
+bool VirtualTexturePage::release(VkDevice device)
+{
+	del= false;
+	if (imageMemoryBind.memory != VK_NULL_HANDLE)
+	{
+		vkFreeMemory(device, imageMemoryBind.memory, nullptr);
+		imageMemoryBind.memory = VK_NULL_HANDLE;
+		return true;
+	}
+	return false;
+}
+
+/*
+	Virtual texture 
+	Contains the virtual pages and memory binding information for a whole virtual texture
+ */
+
+VirtualTexturePage* VirtualTexture::addPage(VkOffset3D offset, VkExtent3D extent, const VkDeviceSize size, const uint32_t mipLevel, uint32_t layer)
+{
+	VirtualTexturePage newPage{};
+	newPage.offset = offset;
+	newPage.extent = extent;
+	newPage.size = size;
+	newPage.mipLevel = mipLevel;
+	newPage.layer = layer;
+	newPage.index = static_cast<uint32_t>(pages.size());
+	newPage.imageMemoryBind = {};
+	newPage.imageMemoryBind.offset = offset;
+	newPage.imageMemoryBind.extent = extent;
+	newPage.del = false;
+	pages.push_back(newPage);
+	return &pages.back();
+}
+
+// Call before sparse binding to update memory bind list etc.
+void VirtualTexture::updateSparseBindInfo(std::vector<VirtualTexturePage> &bindingChangedPages, bool del)
+{
+	// Update list of memory-backed sparse image memory binds
+	//sparseImageMemoryBinds.resize(pages.size());
+	sparseImageMemoryBinds.clear();
+	for (auto page : bindingChangedPages)
+	{
+		sparseImageMemoryBinds.push_back(page.imageMemoryBind);
+		if (del)
+		{
+			sparseImageMemoryBinds[sparseImageMemoryBinds.size() - 1].memory = VK_NULL_HANDLE;
+		}
+	}
+	// Update sparse bind info
+	bindSparseInfo = vks::initializers::bindSparseInfo();
+	// todo: Semaphore for queue submission
+	// bindSparseInfo.signalSemaphoreCount = 1;
+	// bindSparseInfo.pSignalSemaphores = &bindSparseSemaphore;
+
+	// Image memory binds
+	imageMemoryBindInfo = {};
+	imageMemoryBindInfo.image = image;
+	imageMemoryBindInfo.bindCount = static_cast<uint32_t>(sparseImageMemoryBinds.size());
+	imageMemoryBindInfo.pBinds = sparseImageMemoryBinds.data();
+	bindSparseInfo.imageBindCount = (imageMemoryBindInfo.bindCount > 0) ? 1 : 0;
+	bindSparseInfo.pImageBinds = &imageMemoryBindInfo;
+
+	// Opaque image memory binds for the mip tail
+	opaqueMemoryBindInfo.image = image;
+	opaqueMemoryBindInfo.bindCount = static_cast<uint32_t>(opaqueMemoryBinds.size());
+	opaqueMemoryBindInfo.pBinds = opaqueMemoryBinds.data();
+	bindSparseInfo.imageOpaqueBindCount = (opaqueMemoryBindInfo.bindCount > 0) ? 1 : 0;
+	bindSparseInfo.pImageOpaqueBinds = &opaqueMemoryBindInfo;
+}
+
+// Release all Vulkan resources
+void VirtualTexture::destroy()
+{
+	for (auto page : pages)
+	{
+		page.release(device);
+	}
+	for (auto bind : opaqueMemoryBinds)
+	{
+		vkFreeMemory(device, bind.memory, nullptr);
+	}
+	// Clean up mip tail
+	if (mipTailimageMemoryBind.memory != VK_NULL_HANDLE) {
+		vkFreeMemory(device, mipTailimageMemoryBind.memory, nullptr);
+	}
+}
+
+/*
+	Vulkan Example class
+*/
+VulkanExample::VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+{
+	title = "Sparse texture residency";
+	std::cout.imbue(std::locale(""));
+	camera.type = Camera::CameraType::lookat;
+	camera.setPosition(glm::vec3(0.0f, 0.0f, -12.0f));
+	camera.setRotation(glm::vec3(-90.0f, 0.0f, 0.0f));
+	camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 256.0f);
+}
+
+VulkanExample::~VulkanExample()
+{
+	// Clean up used Vulkan resources
+	// Note : Inherited destructor cleans up resources stored in base class
+	destroyTextureImage(texture);
+	vkDestroySemaphore(device, bindSparseSemaphore, nullptr);
+	vkDestroyPipeline(device, pipeline, nullptr);
+	vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+	vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+	uniformBufferVS.destroy();
+}
+
+void VulkanExample::getEnabledFeatures()
+{
+	if (deviceFeatures.sparseBinding && deviceFeatures.sparseResidencyImage2D) {
+		enabledFeatures.shaderResourceResidency = VK_TRUE;
+		enabledFeatures.shaderResourceMinLod = VK_TRUE;
+		enabledFeatures.sparseBinding = VK_TRUE;
+		enabledFeatures.sparseResidencyImage2D = VK_TRUE;
+	}
+	else {
+		std::cout << "Sparse binding not supported" << std::endl;
+	}
+}
+
+glm::uvec3 VulkanExample::alignedDivision(const VkExtent3D& extent, const VkExtent3D& granularity)
+{
+	glm::uvec3 res;
+	res.x = extent.width / granularity.width + ((extent.width % granularity.width) ? 1u : 0u);
+	res.y = extent.height / granularity.height + ((extent.height % granularity.height) ? 1u : 0u);
+	res.z = extent.depth / granularity.depth + ((extent.depth % granularity.depth) ? 1u : 0u);
+	return res;
+}
+
+void VulkanExample::prepareSparseTexture(uint32_t width, uint32_t height, uint32_t layerCount, VkFormat format)
+{
+	texture.device = vulkanDevice->logicalDevice;
+	texture.width = width;
+	texture.height = height;
+	texture.mipLevels = static_cast<uint32_t>(floor(log2(std::max(width, height))) + 1);
+	texture.layerCount = layerCount;
+	texture.format = format;
+
+	texture.subRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, texture.mipLevels, 0, 1 };
+	// Get device properties for the requested texture format
+	VkFormatProperties formatProperties;
+	vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &formatProperties);
+
+	const VkImageType imageType = VK_IMAGE_TYPE_2D;
+	const VkSampleCountFlagBits sampleCount = VK_SAMPLE_COUNT_1_BIT;
+	const VkImageUsageFlags imageUsage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
+	const VkImageTiling imageTiling = VK_IMAGE_TILING_OPTIMAL;
+
+	// Get sparse image properties
+	std::vector<VkSparseImageFormatProperties> sparseProperties;
+	// Sparse properties count for the desired format
+	uint32_t sparsePropertiesCount;
+	vkGetPhysicalDeviceSparseImageFormatProperties(physicalDevice, format, imageType, sampleCount, imageUsage, imageTiling, &sparsePropertiesCount, nullptr);
+	// Check if sparse is supported for this format
+	if (sparsePropertiesCount == 0)
+	{
+		std::cout << "Error: Requested format does not support sparse features!" << std::endl;
+		return;
+	}
+
+	// Get actual image format properties
+	sparseProperties.resize(sparsePropertiesCount);
+	vkGetPhysicalDeviceSparseImageFormatProperties(physicalDevice, format, imageType, sampleCount, imageUsage, imageTiling, &sparsePropertiesCount, sparseProperties.data());
+
+	std::cout << "Sparse image format properties: " << sparsePropertiesCount << std::endl;
+	for (auto props : sparseProperties)
+	{
+		std::cout << "\t Image granularity: w = " << props.imageGranularity.width << " h = " << props.imageGranularity.height << " d = " << props.imageGranularity.depth << std::endl;
+		std::cout << "\t Aspect mask: " << props.aspectMask << std::endl;
+		std::cout << "\t Flags: " << props.flags << std::endl;
+	}
+
+	// Create sparse image
+	VkImageCreateInfo sparseImageCreateInfo = vks::initializers::imageCreateInfo();
+	sparseImageCreateInfo.imageType = imageType;
+	sparseImageCreateInfo.format = texture.format;
+	sparseImageCreateInfo.mipLevels = texture.mipLevels;
+	sparseImageCreateInfo.arrayLayers = texture.layerCount;
+	sparseImageCreateInfo.samples = sampleCount;
+	sparseImageCreateInfo.tiling = imageTiling;
+	sparseImageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+	sparseImageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+	sparseImageCreateInfo.extent = { texture.width, texture.height, 1 };
+	sparseImageCreateInfo.usage = imageUsage;
+	sparseImageCreateInfo.flags = VK_IMAGE_CREATE_SPARSE_BINDING_BIT | VK_IMAGE_CREATE_SPARSE_RESIDENCY_BIT;
+	VK_CHECK_RESULT(vkCreateImage(device, &sparseImageCreateInfo, nullptr, &texture.image));
+
+	VkCommandBuffer copyCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+	vks::tools::setImageLayout(copyCmd, texture.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, texture.subRange);
+	vulkanDevice->flushCommandBuffer(copyCmd, queue);
+
+	// Get memory requirements
+	VkMemoryRequirements sparseImageMemoryReqs;
+	// Sparse image memory requirement counts
+	vkGetImageMemoryRequirements(device, texture.image, &sparseImageMemoryReqs);
+
+	std::cout << "Image memory requirements:" << std::endl;
+	std::cout << "\t Size: " << sparseImageMemoryReqs.size << std::endl;
+	std::cout << "\t Alignment: " << sparseImageMemoryReqs.alignment << std::endl;
+
+	// Check requested image size against hardware sparse limit
+	if (sparseImageMemoryReqs.size > vulkanDevice->properties.limits.sparseAddressSpaceSize)
+	{
+		std::cout << "Error: Requested sparse image size exceeds supports sparse address space size!" << std::endl;
+		return;
+	};
+
+	// Get sparse memory requirements
+	// Count
+	uint32_t sparseMemoryReqsCount = 32;
+	std::vector<VkSparseImageMemoryRequirements> sparseMemoryReqs(sparseMemoryReqsCount);
+	vkGetImageSparseMemoryRequirements(device, texture.image, &sparseMemoryReqsCount, sparseMemoryReqs.data());
+	if (sparseMemoryReqsCount == 0)
+	{
+		std::cout << "Error: No memory requirements for the sparse image!" << std::endl;
+		return;
+	}
+	sparseMemoryReqs.resize(sparseMemoryReqsCount);
+	// Get actual requirements
+	vkGetImageSparseMemoryRequirements(device, texture.image, &sparseMemoryReqsCount, sparseMemoryReqs.data());
+
+	std::cout << "Sparse image memory requirements: " << sparseMemoryReqsCount << std::endl;
+	for (auto reqs : sparseMemoryReqs)
+	{
+		std::cout << "\t Image granularity: w = " << reqs.formatProperties.imageGranularity.width << " h = " << reqs.formatProperties.imageGranularity.height << " d = " << reqs.formatProperties.imageGranularity.depth << std::endl;
+		std::cout << "\t Mip tail first LOD: " << reqs.imageMipTailFirstLod << std::endl;
+		std::cout << "\t Mip tail size: " << reqs.imageMipTailSize << std::endl;
+		std::cout << "\t Mip tail offset: " << reqs.imageMipTailOffset << std::endl;
+		std::cout << "\t Mip tail stride: " << reqs.imageMipTailStride << std::endl;
+		//todo:multiple reqs
+		texture.mipTailStart = reqs.imageMipTailFirstLod;
+	}
+
+	// Get sparse image requirements for the color aspect
+	VkSparseImageMemoryRequirements sparseMemoryReq;
+	bool colorAspectFound = false;
+	for (auto reqs : sparseMemoryReqs)
+	{
+		if (reqs.formatProperties.aspectMask & VK_IMAGE_ASPECT_COLOR_BIT)
+		{
+			sparseMemoryReq = reqs;
+			colorAspectFound = true;
+			break;
+		}
+	}
+	if (!colorAspectFound)
+	{
+		std::cout << "Error: Could not find sparse image memory requirements for color aspect bit!" << std::endl;
+		return;
+	}
+
+	// @todo: proper comment
+	// Calculate number of required sparse memory bindings by alignment
+	assert((sparseImageMemoryReqs.size % sparseImageMemoryReqs.alignment) == 0);
+	texture.memoryTypeIndex = vulkanDevice->getMemoryType(sparseImageMemoryReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+
+	// Get sparse bindings
+	uint32_t sparseBindsCount = static_cast<uint32_t>(sparseImageMemoryReqs.size / sparseImageMemoryReqs.alignment);
+	std::vector<VkSparseMemoryBind>	sparseMemoryBinds(sparseBindsCount);
+
+	texture.sparseImageMemoryRequirements = sparseMemoryReq;
+
+	// The mip tail contains all mip levels > sparseMemoryReq.imageMipTailFirstLod
+	// Check if the format has a single mip tail for all layers or one mip tail for each layer
+	// @todo: Comment
+	texture.mipTailInfo.singleMipTail = sparseMemoryReq.formatProperties.flags & VK_SPARSE_IMAGE_FORMAT_SINGLE_MIPTAIL_BIT;
+	texture.mipTailInfo.alingedMipSize = sparseMemoryReq.formatProperties.flags & VK_SPARSE_IMAGE_FORMAT_ALIGNED_MIP_SIZE_BIT;
+
+	// Sparse bindings for each mip level of all layers outside of the mip tail
+	for (uint32_t layer = 0; layer < texture.layerCount; layer++)
+	{
+		// sparseMemoryReq.imageMipTailFirstLod is the first mip level that's stored inside the mip tail
+		for (uint32_t mipLevel = 0; mipLevel < sparseMemoryReq.imageMipTailFirstLod; mipLevel++)
+		{
+			VkExtent3D extent;
+			extent.width = std::max(sparseImageCreateInfo.extent.width >> mipLevel, 1u);
+			extent.height = std::max(sparseImageCreateInfo.extent.height >> mipLevel, 1u);
+			extent.depth = std::max(sparseImageCreateInfo.extent.depth >> mipLevel, 1u);
+
+			VkImageSubresource subResource{};
+			subResource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+			subResource.mipLevel = mipLevel;
+			subResource.arrayLayer = layer;
+
+			// Aligned sizes by image granularity
+			VkExtent3D imageGranularity = sparseMemoryReq.formatProperties.imageGranularity;
+			glm::uvec3 sparseBindCounts = alignedDivision(extent, imageGranularity);
+			glm::uvec3 lastBlockExtent;
+			lastBlockExtent.x = (extent.width % imageGranularity.width) ? extent.width % imageGranularity.width : imageGranularity.width;
+			lastBlockExtent.y = (extent.height % imageGranularity.height) ? extent.height % imageGranularity.height : imageGranularity.height;
+			lastBlockExtent.z = (extent.depth % imageGranularity.depth) ? extent.depth % imageGranularity.depth : imageGranularity.depth;
+
+			// @todo: Comment
+			uint32_t index = 0;
+			for (uint32_t z = 0; z < sparseBindCounts.z; z++)
+			{
+				for (uint32_t y = 0; y < sparseBindCounts.y; y++)
+				{
+					for (uint32_t x = 0; x < sparseBindCounts.x; x++)
+					{
+						// Offset
+						VkOffset3D offset;
+						offset.x = x * imageGranularity.width;
+						offset.y = y * imageGranularity.height;
+						offset.z = z * imageGranularity.depth;
+						// Size of the page
+						VkExtent3D extent;
+						extent.width = (x == sparseBindCounts.x - 1) ? lastBlockExtent.x : imageGranularity.width;
+						extent.height = (y == sparseBindCounts.y - 1) ? lastBlockExtent.y : imageGranularity.height;
+						extent.depth = (z == sparseBindCounts.z - 1) ? lastBlockExtent.z : imageGranularity.depth;
+
+						// Add new virtual page
+						VirtualTexturePage* newPage = texture.addPage(offset, extent, sparseImageMemoryReqs.alignment, mipLevel, layer);
+						newPage->imageMemoryBind.subresource = subResource;
+
+						index++;
+					}
+				}
+			}
+		}
+
+		// @todo: proper comment
+		// @todo: store in mip tail and properly release
+		// @todo: Only one block for single mip tail
+		if ((!texture.mipTailInfo.singleMipTail) && (sparseMemoryReq.imageMipTailFirstLod < texture.mipLevels))
+		{
+			// Allocate memory for the mip tail
+			VkMemoryAllocateInfo allocInfo = vks::initializers::memoryAllocateInfo();
+			allocInfo.allocationSize = sparseMemoryReq.imageMipTailSize;
+			allocInfo.memoryTypeIndex = texture.memoryTypeIndex;
+
+			VkDeviceMemory deviceMemory;
+			VK_CHECK_RESULT(vkAllocateMemory(device, &allocInfo, nullptr, &deviceMemory));
+
+			// (Opaque) sparse memory binding
+			VkSparseMemoryBind sparseMemoryBind{};
+			sparseMemoryBind.resourceOffset = sparseMemoryReq.imageMipTailOffset + layer * sparseMemoryReq.imageMipTailStride;
+			sparseMemoryBind.size = sparseMemoryReq.imageMipTailSize;
+			sparseMemoryBind.memory = deviceMemory;
+
+			texture.opaqueMemoryBinds.push_back(sparseMemoryBind);
+		}
+	} // end layers and mips
+
+	std::cout << "Texture info:" << std::endl;
+	std::cout << "\tDim: " << texture.width << " x " << texture.height << std::endl;
+	std::cout << "\tVirtual pages: " << texture.pages.size() << std::endl;
+
+	// Check if format has one mip tail for all layers
+	if ((sparseMemoryReq.formatProperties.flags & VK_SPARSE_IMAGE_FORMAT_SINGLE_MIPTAIL_BIT) && (sparseMemoryReq.imageMipTailFirstLod < texture.mipLevels))
+	{
+		// Allocate memory for the mip tail
+		VkMemoryAllocateInfo allocInfo = vks::initializers::memoryAllocateInfo();
+		allocInfo.allocationSize = sparseMemoryReq.imageMipTailSize;
+		allocInfo.memoryTypeIndex = texture.memoryTypeIndex;
+
+		VkDeviceMemory deviceMemory;
+		VK_CHECK_RESULT(vkAllocateMemory(device, &allocInfo, nullptr, &deviceMemory));
+
+		// (Opaque) sparse memory binding
+		VkSparseMemoryBind sparseMemoryBind{};
+		sparseMemoryBind.resourceOffset = sparseMemoryReq.imageMipTailOffset;
+		sparseMemoryBind.size = sparseMemoryReq.imageMipTailSize;
+		sparseMemoryBind.memory = deviceMemory;
+
+		texture.opaqueMemoryBinds.push_back(sparseMemoryBind);
+	}
+
+	// Create signal semaphore for sparse binding
+	VkSemaphoreCreateInfo semaphoreCreateInfo = vks::initializers::semaphoreCreateInfo();
+	VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &bindSparseSemaphore));
+
+	// Prepare bind sparse info for reuse in queue submission
+	texture.updateSparseBindInfo(texture.pages);
+
+	// Bind to queue
+	// todo: in draw?
+	vkQueueBindSparse(queue, 1, &texture.bindSparseInfo, VK_NULL_HANDLE);
+	//todo: use sparse bind semaphore
+	vkQueueWaitIdle(queue);
+
+	// Create sampler
+	VkSamplerCreateInfo sampler = vks::initializers::samplerCreateInfo();
+	sampler.magFilter = VK_FILTER_LINEAR;
+	sampler.minFilter = VK_FILTER_LINEAR;
+	sampler.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
+	sampler.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+	sampler.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+	sampler.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+	sampler.mipLodBias = 0.0f;
+	sampler.compareOp = VK_COMPARE_OP_NEVER;
+	sampler.minLod = 0.0f;
+	sampler.maxLod = static_cast<float>(texture.mipLevels);
+	sampler.maxAnisotropy = vulkanDevice->features.samplerAnisotropy ? vulkanDevice->properties.limits.maxSamplerAnisotropy : 1.0f;
+	sampler.anisotropyEnable = false;
+	sampler.borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK;
+	VK_CHECK_RESULT(vkCreateSampler(device, &sampler, nullptr, &texture.sampler));
+
+	// Create image view
+	VkImageViewCreateInfo view = vks::initializers::imageViewCreateInfo();
+	view.image = VK_NULL_HANDLE;
+	view.viewType = VK_IMAGE_VIEW_TYPE_2D;
+	view.format = format;
+	view.components = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A };
+	view.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+	view.subresourceRange.baseMipLevel = 0;
+	view.subresourceRange.baseArrayLayer = 0;
+	view.subresourceRange.layerCount = 1;
+	view.subresourceRange.levelCount = texture.mipLevels;
+	view.image = texture.image;
+	VK_CHECK_RESULT(vkCreateImageView(device, &view, nullptr, &texture.view));
+
+	// Fill image descriptor image info that can be used during the descriptor set setup
+	texture.descriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+	texture.descriptor.imageView = texture.view;
+	texture.descriptor.sampler = texture.sampler;
+}
+
+// Free all Vulkan resources used a texture object
+void VulkanExample::destroyTextureImage(SparseTexture texture)
+{
+	vkDestroyImageView(device, texture.view, nullptr);
+	vkDestroyImage(device, texture.image, nullptr);
+	vkDestroySampler(device, texture.sampler, nullptr);
+	texture.destroy();
+}
+
+void VulkanExample::buildCommandBuffers()
+{
+	VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+	VkClearValue clearValues[2];
+	clearValues[0].color = defaultClearColor;
+	clearValues[1].depthStencil = { 1.0f, 0 };
+
+	VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+	renderPassBeginInfo.renderPass = renderPass;
+	renderPassBeginInfo.renderArea.offset.x = 0;
+	renderPassBeginInfo.renderArea.offset.y = 0;
+	renderPassBeginInfo.renderArea.extent.width = width;
+	renderPassBeginInfo.renderArea.extent.height = height;
+	renderPassBeginInfo.clearValueCount = 2;
+	renderPassBeginInfo.pClearValues = clearValues;
+
+	for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+	{
+		renderPassBeginInfo.framebuffer = frameBuffers[i];
+
+		VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+		vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+		VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+		vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+		VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
+		vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+		vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL);
+		vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
+		plane.draw(drawCmdBuffers[i]);
+
+		drawUI(drawCmdBuffers[i]);
+
+		vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+		VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+	}
+}
+
+void VulkanExample::draw()
+{
+	VulkanExampleBase::prepareFrame();
+	submitInfo.commandBufferCount = 1;
+	submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+	VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+	VulkanExampleBase::submitFrame();
+}
+
+void VulkanExample::loadAssets()
+{
+	const uint32_t glTFLoadingFlags = vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::PreMultiplyVertexColors | vkglTF::FileLoadingFlags::FlipY;
+	plane.loadFromFile(getAssetPath() + "models/plane.gltf", vulkanDevice, queue, glTFLoadingFlags);
+}
+
+void VulkanExample::setupDescriptorPool()
+{
+	// Example uses one ubo and one image sampler
+	std::vector<VkDescriptorPoolSize> poolSizes =
+	{
+		vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1),
+		vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1)
+	};
+
+	VkDescriptorPoolCreateInfo descriptorPoolInfo =
+		vks::initializers::descriptorPoolCreateInfo(
+			static_cast<uint32_t>(poolSizes.size()),
+			poolSizes.data(),
+			2);
+
+	VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+}
+
+void VulkanExample::setupDescriptorSetLayout()
+{
+	std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings =
+	{
+		// Binding 0 : Vertex shader uniform buffer
+		vks::initializers::descriptorSetLayoutBinding(
+			VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+			VK_SHADER_STAGE_VERTEX_BIT,
+			0),
+		// Binding 1 : Fragment shader image sampler
+		vks::initializers::descriptorSetLayoutBinding(
+			VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+			VK_SHADER_STAGE_FRAGMENT_BIT,
+			1)
+	};
+
+	VkDescriptorSetLayoutCreateInfo descriptorLayout =
+		vks::initializers::descriptorSetLayoutCreateInfo(
+			setLayoutBindings.data(),
+			static_cast<uint32_t>(setLayoutBindings.size()));
+
+	VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout));
+
+	VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo =
+		vks::initializers::pipelineLayoutCreateInfo(
+			&descriptorSetLayout,
+			1);
+
+	VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayout));
+}
+
+void VulkanExample::setupDescriptorSet()
+{
+	VkDescriptorSetAllocateInfo allocInfo =
+		vks::initializers::descriptorSetAllocateInfo(
+			descriptorPool,
+			&descriptorSetLayout,
+			1);
+
+	VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet));
+
+	std::vector<VkWriteDescriptorSet> writeDescriptorSets =
+	{
+		// Binding 0 : Vertex shader uniform buffer
+		vks::initializers::writeDescriptorSet(
+			descriptorSet,
+			VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+			0,
+			&uniformBufferVS.descriptor),
+		// Binding 1 : Fragment shader texture sampler
+		vks::initializers::writeDescriptorSet(
+			descriptorSet,
+			VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+			1,
+			&texture.descriptor)
+	};
+
+	vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL);
+}
+
+void VulkanExample::preparePipelines()
+{
+	VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+	VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
+	VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+	VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+	VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+	VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+	VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
+	std::vector<VkDynamicState> dynamicStateEnables = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
+	VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
+	std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+
+	VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo( pipelineLayout, renderPass);
+	pipelineCI.pInputAssemblyState = &inputAssemblyState;
+	pipelineCI.pRasterizationState = &rasterizationState;
+	pipelineCI.pColorBlendState = &colorBlendState;
+	pipelineCI.pMultisampleState = &multisampleState;
+	pipelineCI.pViewportState = &viewportState;
+	pipelineCI.pDepthStencilState = &depthStencilState;
+	pipelineCI.pDynamicState = &dynamicState;
+	pipelineCI.stageCount = static_cast<uint32_t>(shaderStages.size());
+	pipelineCI.pStages = shaderStages.data();
+	pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::UV });
+
+	shaderStages[0] = loadShader(getShadersPath() + "texturesparseresidency/sparseresidency.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+	shaderStages[1] = loadShader(getShadersPath() + "texturesparseresidency/sparseresidency.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+	VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipeline));
+}
+
+// Prepare and initialize uniform buffer containing shader uniforms
+void VulkanExample::prepareUniformBuffers()
+{
+	// Vertex shader uniform buffer block
+	VK_CHECK_RESULT(vulkanDevice->createBuffer(
+		VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+		VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+		&uniformBufferVS,
+		sizeof(uboVS),
+		&uboVS));
+
+	updateUniformBuffers();
+}
+
+void VulkanExample::updateUniformBuffers()
+{
+	uboVS.projection = camera.matrices.perspective;
+	uboVS.model = camera.matrices.view;
+	uboVS.viewPos = camera.viewPos;
+
+	VK_CHECK_RESULT(uniformBufferVS.map());
+	memcpy(uniformBufferVS.mapped, &uboVS, sizeof(uboVS));
+	uniformBufferVS.unmap();
+}
+
+void VulkanExample::prepare()
+{
+	VulkanExampleBase::prepare();
+	// Check if the GPU supports sparse residency for 2D images
+	if (!vulkanDevice->features.sparseResidencyImage2D) {
+		vks::tools::exitFatal("Device does not support sparse residency for 2D images!", VK_ERROR_FEATURE_NOT_PRESENT);
+	}
+	loadAssets();
+	prepareUniformBuffers();
+	// Create a virtual texture with max. possible dimension (does not take up any VRAM yet)
+	prepareSparseTexture(4096, 4096, 1, VK_FORMAT_R8G8B8A8_UNORM);
+	setupDescriptorSetLayout();
+	preparePipelines();
+	setupDescriptorPool();
+	setupDescriptorSet();
+	buildCommandBuffers();
+	prepared = true;
+}
+
+void VulkanExample::render()
+{
+	if (!prepared)
+		return;
+	draw();
+	if (camera.updated) {
+		updateUniformBuffers();
+	}
+}
+
+// Fills a buffer with random colors
+void VulkanExample::randomPattern(uint8_t* buffer, uint32_t width, uint32_t height)
+{
+	std::random_device rd;
+	std::mt19937 rndEngine(rd());
+	std::uniform_int_distribution<uint32_t> rndDist(0, 255);
+	uint8_t rndVal[4] = { 0, 0, 0, 0 };
+	while (rndVal[0] + rndVal[1] + rndVal[2] < 10) {
+		rndVal[0] = (uint8_t)rndDist(rndEngine);
+		rndVal[1] = (uint8_t)rndDist(rndEngine);
+		rndVal[2] = (uint8_t)rndDist(rndEngine);
+	}
+	rndVal[3] = 255;
+	for (uint32_t y = 0; y < height; y++) {
+		for (uint32_t x = 0; x < width; x++) {
+			for (uint32_t c = 0; c < 4; c++, ++buffer) {
+				*buffer = rndVal[c];
+			}
+		}
+	}
+}
+
+void VulkanExample::uploadContent(VirtualTexturePage page, VkImage image)
+{
+	// Generate some random image data and upload as a buffer
+	const size_t bufferSize = 4 * page.extent.width * page.extent.height;
+
+	vks::Buffer imageBuffer;
+	VK_CHECK_RESULT(vulkanDevice->createBuffer(
+		VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
+		VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+		&imageBuffer,
+		bufferSize));
+	imageBuffer.map();
+
+	uint8_t* data = (uint8_t*)imageBuffer.mapped;
+	randomPattern(data, page.extent.height, page.extent.width);
+
+	VkCommandBuffer copyCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+	vks::tools::setImageLayout(copyCmd, image, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, texture.subRange, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
+	VkBufferImageCopy region{};
+	region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+	region.imageSubresource.layerCount = 1;
+	region.imageSubresource.mipLevel = page.mipLevel;
+	region.imageOffset = page.offset;
+	region.imageExtent = page.extent;
+	vkCmdCopyBufferToImage(copyCmd, imageBuffer.buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
+	vks::tools::setImageLayout(copyCmd, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, texture.subRange, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);
+	vulkanDevice->flushCommandBuffer(copyCmd, queue);
+
+	imageBuffer.destroy();
+}
+
+void VulkanExample::fillRandomPages()
+{
+	vkDeviceWaitIdle(device);
+
+	std::default_random_engine rndEngine(std::random_device{}());
+	std::uniform_real_distribution<float> rndDist(0.0f, 1.0f);
+
+	std::vector<VirtualTexturePage> updatedPages;
+	std::vector<VirtualTexturePage> bindingChangedPages;
+	for (auto& page : texture.pages) {
+		if (rndDist(rndEngine) < 0.5f) {
+			continue;
+		}
+		if (page.allocate(device, texture.memoryTypeIndex))
+		{
+			bindingChangedPages.push_back(page);
+		}
+		updatedPages.push_back(page);
+	}
+
+	// Update sparse queue binding
+	texture.updateSparseBindInfo(bindingChangedPages);
+	VkFenceCreateInfo fenceInfo = vks::initializers::fenceCreateInfo(VK_FLAGS_NONE);
+	VkFence fence;
+	VK_CHECK_RESULT(vkCreateFence(device, &fenceInfo, nullptr, &fence));
+	vkQueueBindSparse(queue, 1, &texture.bindSparseInfo, fence);
+	vkWaitForFences(device, 1, &fence, VK_TRUE, UINT64_MAX);
+	vkDestroyFence(device, fence, nullptr);
+
+	for (auto &page: updatedPages) {
+		uploadContent(page, texture.image);
+	}
+}
+
+void VulkanExample::fillMipTail()
+{
+	// Clean up previous mip tail memory allocation
+	if (texture.mipTailimageMemoryBind.memory != VK_NULL_HANDLE) {
+		vkFreeMemory(device, texture.mipTailimageMemoryBind.memory, nullptr);
+	}
+
+	//@todo: WIP
+	VkDeviceSize imageMipTailSize = texture.sparseImageMemoryRequirements.imageMipTailSize;
+	VkDeviceSize imageMipTailOffset = texture.sparseImageMemoryRequirements.imageMipTailOffset;
+	// Stride between memory bindings for each mip level if not single mip tail (VK_SPARSE_IMAGE_FORMAT_SINGLE_MIPTAIL_BIT not set)
+	VkDeviceSize imageMipTailStride = texture.sparseImageMemoryRequirements.imageMipTailStride;
+
+	VkMemoryAllocateInfo allocInfo = vks::initializers::memoryAllocateInfo();
+	allocInfo.allocationSize = imageMipTailSize;
+	allocInfo.memoryTypeIndex = texture.memoryTypeIndex;
+	VK_CHECK_RESULT(vkAllocateMemory(device, &allocInfo, nullptr, &texture.mipTailimageMemoryBind.memory));
+
+	uint32_t mipLevel = texture.sparseImageMemoryRequirements.imageMipTailFirstLod;
+	uint32_t width = std::max(texture.width >> texture.sparseImageMemoryRequirements.imageMipTailFirstLod, 1u);
+	uint32_t height = std::max(texture.height >> texture.sparseImageMemoryRequirements.imageMipTailFirstLod, 1u);
+	uint32_t depth = 1;
+
+	for (uint32_t i = texture.mipTailStart; i < texture.mipLevels; i++) {
+
+		const uint32_t width = std::max(texture.width >> i, 1u);
+		const uint32_t height = std::max(texture.height >> i, 1u);
+
+		// Generate some random image data and upload as a buffer
+		const size_t bufferSize = 4 * width * height;
+
+		vks::Buffer imageBuffer;
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&imageBuffer,
+			bufferSize));
+		imageBuffer.map();
+
+		// Fill buffer with random colors
+		std::random_device rd;
+		std::mt19937 rndEngine(rd());
+		std::uniform_int_distribution<uint32_t> rndDist(0, 255);
+		uint8_t* data = (uint8_t*)imageBuffer.mapped;
+		randomPattern(data, width, height);
+
+		VkCommandBuffer copyCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+		vks::tools::setImageLayout(copyCmd, texture.image, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, texture.subRange, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
+		VkBufferImageCopy region{};
+		region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+		region.imageSubresource.layerCount = 1;
+		region.imageSubresource.mipLevel = i;
+		region.imageOffset = {};
+		region.imageExtent = { width, height, 1 };
+		vkCmdCopyBufferToImage(copyCmd, imageBuffer.buffer, texture.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
+		vks::tools::setImageLayout(copyCmd, texture.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, texture.subRange, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);
+		vulkanDevice->flushCommandBuffer(copyCmd, queue);
+
+		imageBuffer.destroy();
+	}
+}
+
+void VulkanExample::flushRandomPages()
+{
+	vkDeviceWaitIdle(device);
+
+	std::default_random_engine rndEngine(std::random_device{}());
+	std::uniform_real_distribution<float> rndDist(0.0f, 1.0f);
+
+	std::vector<VirtualTexturePage> updatedPages;
+	std::vector<VirtualTexturePage> bindingChangedPages;
+	for (auto& page : texture.pages)
+	{
+		if (rndDist(rndEngine) < 0.5f) {
+			continue;
+		}
+		if (page.imageMemoryBind.memory != VK_NULL_HANDLE){
+			page.del = true;
+			bindingChangedPages.push_back(page);
+		}
+	}
+
+	// Update sparse queue binding
+	texture.updateSparseBindInfo(bindingChangedPages, true);
+	VkFenceCreateInfo fenceInfo = vks::initializers::fenceCreateInfo(VK_FLAGS_NONE);
+	VkFence fence;
+	VK_CHECK_RESULT(vkCreateFence(device, &fenceInfo, nullptr, &fence));
+	vkQueueBindSparse(queue, 1, &texture.bindSparseInfo, fence);
+	vkWaitForFences(device, 1, &fence, VK_TRUE, UINT64_MAX);
+	vkDestroyFence(device, fence, nullptr);
+	for (auto& page : texture.pages)
+	{
+		if (page.del)
+		{
+			page.release(device);
+		}
+	}
+}
+
+void VulkanExample::OnUpdateUIOverlay(vks::UIOverlay* overlay)
+{
+	if (overlay->header("Settings")) {
+		if (overlay->sliderFloat("LOD bias", &uboVS.lodBias, -(float)texture.mipLevels, (float)texture.mipLevels)) {
+			updateUniformBuffers();
+		}
+		if (overlay->button("Fill random pages")) {
+			fillRandomPages();
+		}
+		if (overlay->button("Flush random pages")) {
+			flushRandomPages();
+		}
+		if (overlay->button("Fill mip tail")) {
+			fillMipTail();
+		}
+	}
+	if (overlay->header("Statistics")) {
+		uint32_t respages = 0;
+		std::for_each(texture.pages.begin(), texture.pages.end(), [&respages](VirtualTexturePage page) { respages += (page.resident()) ? 1 : 0; });
+		overlay->text("Resident pages: %d of %d", respages, static_cast<uint32_t>(texture.pages.size()));
+		overlay->text("Mip tail starts at: %d", texture.mipTailStart);
+	}
+
+}
+
+VULKAN_EXAMPLE_MAIN()
diff --git a/external/Vulkan/examples/texturesparseresidency/texturesparseresidency.h b/external/Vulkan/examples/texturesparseresidency/texturesparseresidency.h
new file mode 100644
index 0000000000000000000000000000000000000000..e061dc9e7916e7034adad8241f477f4875fafd8d
--- /dev/null
+++ b/external/Vulkan/examples/texturesparseresidency/texturesparseresidency.h
@@ -0,0 +1,124 @@
+/*
+* Vulkan Example - Sparse texture residency example
+*
+* Copyright (C) 2016-2020 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+/*
+* Note : This sample is work-in-progress and works basically, but it's not yet finished
+*/
+
+#include "vulkanexamplebase.h"
+#include "VulkanglTFModel.h"
+
+#define ENABLE_VALIDATION false
+
+// Virtual texture page as a part of the partially resident texture
+// Contains memory bindings, offsets and status information
+struct VirtualTexturePage
+{
+	VkOffset3D offset;
+	VkExtent3D extent;
+	VkSparseImageMemoryBind imageMemoryBind;							// Sparse image memory bind for this page
+	VkDeviceSize size;													// Page (memory) size in bytes
+	uint32_t mipLevel;													// Mip level that this page belongs to
+	uint32_t layer;														// Array layer that this page belongs to
+	uint32_t index;
+    bool del;
+
+	VirtualTexturePage();
+	bool resident();
+	bool allocate(VkDevice device, uint32_t memoryTypeIndex);
+	bool release(VkDevice device);
+};
+
+// Virtual texture object containing all pages
+struct VirtualTexture
+{
+	VkDevice device;
+	VkImage image;														// Texture image handle
+	VkBindSparseInfo bindSparseInfo;									// Sparse queue binding information
+	std::vector<VirtualTexturePage> pages;								// Contains all virtual pages of the texture
+	std::vector<VkSparseImageMemoryBind> sparseImageMemoryBinds;		// Sparse image memory bindings of all memory-backed virtual tables
+	std::vector<VkSparseMemoryBind>	opaqueMemoryBinds;					// Sparse opaque memory bindings for the mip tail (if present)
+	VkSparseImageMemoryBindInfo imageMemoryBindInfo;					// Sparse image memory bind info
+	VkSparseImageOpaqueMemoryBindInfo opaqueMemoryBindInfo;				// Sparse image opaque memory bind info (mip tail)
+	uint32_t mipTailStart;												// First mip level in mip tail
+	VkSparseImageMemoryRequirements sparseImageMemoryRequirements;		// @todo: Comment
+	uint32_t memoryTypeIndex;											// @todo: Comment
+
+	VkSparseImageMemoryBind mipTailimageMemoryBind{};
+
+	// @todo: comment
+	struct MipTailInfo {
+		bool singleMipTail;
+		bool alingedMipSize;
+	} mipTailInfo;
+
+	VirtualTexturePage *addPage(VkOffset3D offset, VkExtent3D extent, const VkDeviceSize size, const uint32_t mipLevel, uint32_t layer);
+	void updateSparseBindInfo(std::vector<VirtualTexturePage> &bindingChangedPages, bool del = false);
+	// @todo: replace with dtor?
+	void destroy();
+};
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	//todo: comments
+	struct SparseTexture : VirtualTexture {
+		VkSampler sampler;
+		VkImageLayout imageLayout;
+		VkImageView view;
+		VkDescriptorImageInfo descriptor;
+		VkFormat format;
+		uint32_t width, height;
+		uint32_t mipLevels;
+		uint32_t layerCount;
+        VkImageSubresourceRange subRange;
+	} texture;
+
+	vkglTF::Model plane;
+
+	struct UboVS {
+		glm::mat4 projection;
+		glm::mat4 model;
+		glm::vec4 viewPos;
+		float lodBias = 0.0f;
+	} uboVS;
+	vks::Buffer uniformBufferVS;
+
+	VkPipeline pipeline;
+	VkPipelineLayout pipelineLayout;
+	VkDescriptorSet descriptorSet;
+	VkDescriptorSetLayout descriptorSetLayout;
+
+	//todo: comment
+	VkSemaphore bindSparseSemaphore = VK_NULL_HANDLE;
+
+	VulkanExample();
+	~VulkanExample();
+	virtual void getEnabledFeatures();
+	glm::uvec3 alignedDivision(const VkExtent3D& extent, const VkExtent3D& granularity);
+	void randomPattern(uint8_t* buffer, uint32_t width, uint32_t height);
+	void prepareSparseTexture(uint32_t width, uint32_t height, uint32_t layerCount, VkFormat format);
+	// @todo: move to dtor of texture
+	void destroyTextureImage(SparseTexture texture);
+	void buildCommandBuffers();
+	void draw();
+	void loadAssets();
+	void setupDescriptorPool();
+	void setupDescriptorSetLayout();
+	void setupDescriptorSet();
+	void preparePipelines();
+	void prepareUniformBuffers();
+	void updateUniformBuffers();
+	void prepare();
+	virtual void render();
+	void uploadContent(VirtualTexturePage page, VkImage image);
+	void fillRandomPages();
+	void fillMipTail();
+	void flushRandomPages();
+	virtual void OnUpdateUIOverlay(vks::UIOverlay* overlay);
+};
diff --git a/external/Vulkan/examples/triangle/triangle.cpp b/external/Vulkan/examples/triangle/triangle.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..8ca17c327c1802c586e55468e2457827fd076fdb
--- /dev/null
+++ b/external/Vulkan/examples/triangle/triangle.cpp
@@ -0,0 +1,1232 @@
+/*
+* Vulkan Example - Basic indexed triangle rendering
+*
+* Note:
+*	This is a "pedal to the metal" example to show off how to get Vulkan up and displaying something
+*	Contrary to the other examples, this one won't make use of helper functions or initializers
+*	Except in a few cases (swap chain setup e.g.)
+*
+* Copyright (C) 2016-2017 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <fstream>
+#include <vector>
+#include <exception>
+
+#define GLM_FORCE_RADIANS
+#define GLM_FORCE_DEPTH_ZERO_TO_ONE
+#include <glm/glm.hpp>
+#include <glm/gtc/matrix_transform.hpp>
+
+#include <vulkan/vulkan.h>
+#include "vulkanexamplebase.h"
+
+// Set to "true" to enable Vulkan's validation layers (see vulkandebug.cpp for details)
+#define ENABLE_VALIDATION false
+// Set to "true" to use staging buffers for uploading vertex and index data to device local memory
+// See "prepareVertices" for details on what's staging and on why to use it
+#define USE_STAGING true
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	// Vertex layout used in this example
+	struct Vertex {
+		float position[3];
+		float color[3];
+	};
+
+	// Vertex buffer and attributes
+	struct {
+		VkDeviceMemory memory; // Handle to the device memory for this buffer
+		VkBuffer buffer;       // Handle to the Vulkan buffer object that the memory is bound to
+	} vertices;
+
+	// Index buffer
+	struct {
+		VkDeviceMemory memory;
+		VkBuffer buffer;
+		uint32_t count;
+	} indices;
+
+	// Uniform buffer block object
+	struct {
+		VkDeviceMemory memory;
+		VkBuffer buffer;
+		VkDescriptorBufferInfo descriptor;
+	}  uniformBufferVS;
+
+	// For simplicity we use the same uniform block layout as in the shader:
+	//
+	//	layout(set = 0, binding = 0) uniform UBO
+	//	{
+	//		mat4 projectionMatrix;
+	//		mat4 modelMatrix;
+	//		mat4 viewMatrix;
+	//	} ubo;
+	//
+	// This way we can just memcopy the ubo data to the ubo
+	// Note: You should use data types that align with the GPU in order to avoid manual padding (vec4, mat4)
+	struct {
+		glm::mat4 projectionMatrix;
+		glm::mat4 modelMatrix;
+		glm::mat4 viewMatrix;
+	} uboVS;
+
+	// The pipeline layout is used by a pipeline to access the descriptor sets
+	// It defines interface (without binding any actual data) between the shader stages used by the pipeline and the shader resources
+	// A pipeline layout can be shared among multiple pipelines as long as their interfaces match
+	VkPipelineLayout pipelineLayout;
+
+	// Pipelines (often called "pipeline state objects") are used to bake all states that affect a pipeline
+	// While in OpenGL every state can be changed at (almost) any time, Vulkan requires to layout the graphics (and compute) pipeline states upfront
+	// So for each combination of non-dynamic pipeline states you need a new pipeline (there are a few exceptions to this not discussed here)
+	// Even though this adds a new dimension of planning ahead, it's a great opportunity for performance optimizations by the driver
+	VkPipeline pipeline;
+
+	// The descriptor set layout describes the shader binding layout (without actually referencing descriptor)
+	// Like the pipeline layout it's pretty much a blueprint and can be used with different descriptor sets as long as their layout matches
+	VkDescriptorSetLayout descriptorSetLayout;
+
+	// The descriptor set stores the resources bound to the binding points in a shader
+	// It connects the binding points of the different shaders with the buffers and images used for those bindings
+	VkDescriptorSet descriptorSet;
+
+
+	// Synchronization primitives
+	// Synchronization is an important concept of Vulkan that OpenGL mostly hid away. Getting this right is crucial to using Vulkan.
+
+	// Semaphores
+	// Used to coordinate operations within the graphics queue and ensure correct command ordering
+	VkSemaphore presentCompleteSemaphore;
+	VkSemaphore renderCompleteSemaphore;
+
+	// Fences
+	// Used to check the completion of queue operations (e.g. command buffer execution)
+	std::vector<VkFence> waitFences;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Vulkan Example - Basic indexed triangle";
+		// To keep things simple, we don't use the UI overlay
+		settings.overlay = false;
+		// Setup a default look-at camera
+		camera.type = Camera::CameraType::lookat;
+		camera.setPosition(glm::vec3(0.0f, 0.0f, -2.5f));
+		camera.setRotation(glm::vec3(0.0f));
+		camera.setPerspective(60.0f, (float)width / (float)height, 1.0f, 256.0f);
+		// Values not set here are initialized in the base class constructor
+	}
+
+	~VulkanExample()
+	{
+		// Clean up used Vulkan resources
+		// Note: Inherited destructor cleans up resources stored in base class
+		vkDestroyPipeline(device, pipeline, nullptr);
+
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+
+		vkDestroyBuffer(device, vertices.buffer, nullptr);
+		vkFreeMemory(device, vertices.memory, nullptr);
+
+		vkDestroyBuffer(device, indices.buffer, nullptr);
+		vkFreeMemory(device, indices.memory, nullptr);
+
+		vkDestroyBuffer(device, uniformBufferVS.buffer, nullptr);
+		vkFreeMemory(device, uniformBufferVS.memory, nullptr);
+
+		vkDestroySemaphore(device, presentCompleteSemaphore, nullptr);
+		vkDestroySemaphore(device, renderCompleteSemaphore, nullptr);
+
+		for (auto& fence : waitFences)
+		{
+			vkDestroyFence(device, fence, nullptr);
+		}
+	}
+
+	// This function is used to request a device memory type that supports all the property flags we request (e.g. device local, host visible)
+	// Upon success it will return the index of the memory type that fits our requested memory properties
+	// This is necessary as implementations can offer an arbitrary number of memory types with different
+	// memory properties.
+	// You can check http://vulkan.gpuinfo.org/ for details on different memory configurations
+	uint32_t getMemoryTypeIndex(uint32_t typeBits, VkMemoryPropertyFlags properties)
+	{
+		// Iterate over all memory types available for the device used in this example
+		for (uint32_t i = 0; i < deviceMemoryProperties.memoryTypeCount; i++)
+		{
+			if ((typeBits & 1) == 1)
+			{
+				if ((deviceMemoryProperties.memoryTypes[i].propertyFlags & properties) == properties)
+				{
+					return i;
+				}
+			}
+			typeBits >>= 1;
+		}
+
+		throw "Could not find a suitable memory type!";
+	}
+
+	// Create the Vulkan synchronization primitives used in this example
+	void prepareSynchronizationPrimitives()
+	{
+		// Semaphores (Used for correct command ordering)
+		VkSemaphoreCreateInfo semaphoreCreateInfo = {};
+		semaphoreCreateInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
+		semaphoreCreateInfo.pNext = nullptr;
+
+		// Semaphore used to ensure that image presentation is complete before starting to submit again
+		VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &presentCompleteSemaphore));
+
+		// Semaphore used to ensure that all commands submitted have been finished before submitting the image to the queue
+		VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &renderCompleteSemaphore));
+
+		// Fences (Used to check draw command buffer completion)
+		VkFenceCreateInfo fenceCreateInfo = {};
+		fenceCreateInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
+		// Create in signaled state so we don't wait on first render of each command buffer
+		fenceCreateInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
+		waitFences.resize(drawCmdBuffers.size());
+		for (auto& fence : waitFences)
+		{
+			VK_CHECK_RESULT(vkCreateFence(device, &fenceCreateInfo, nullptr, &fence));
+		}
+	}
+
+	// Get a new command buffer from the command pool
+	// If begin is true, the command buffer is also started so we can start adding commands
+	VkCommandBuffer getCommandBuffer(bool begin)
+	{
+		VkCommandBuffer cmdBuffer;
+
+		VkCommandBufferAllocateInfo cmdBufAllocateInfo = {};
+		cmdBufAllocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
+		cmdBufAllocateInfo.commandPool = cmdPool;
+		cmdBufAllocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
+		cmdBufAllocateInfo.commandBufferCount = 1;
+
+		VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, &cmdBuffer));
+
+		// If requested, also start the new command buffer
+		if (begin)
+		{
+			VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+			VK_CHECK_RESULT(vkBeginCommandBuffer(cmdBuffer, &cmdBufInfo));
+		}
+
+		return cmdBuffer;
+	}
+
+	// End the command buffer and submit it to the queue
+	// Uses a fence to ensure command buffer has finished executing before deleting it
+	void flushCommandBuffer(VkCommandBuffer commandBuffer)
+	{
+		assert(commandBuffer != VK_NULL_HANDLE);
+
+		VK_CHECK_RESULT(vkEndCommandBuffer(commandBuffer));
+
+		VkSubmitInfo submitInfo = {};
+		submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &commandBuffer;
+
+		// Create fence to ensure that the command buffer has finished executing
+		VkFenceCreateInfo fenceCreateInfo = {};
+		fenceCreateInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
+		fenceCreateInfo.flags = 0;
+		VkFence fence;
+		VK_CHECK_RESULT(vkCreateFence(device, &fenceCreateInfo, nullptr, &fence));
+
+		// Submit to the queue
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, fence));
+		// Wait for the fence to signal that command buffer has finished executing
+		VK_CHECK_RESULT(vkWaitForFences(device, 1, &fence, VK_TRUE, DEFAULT_FENCE_TIMEOUT));
+
+		vkDestroyFence(device, fence, nullptr);
+		vkFreeCommandBuffers(device, cmdPool, 1, &commandBuffer);
+	}
+
+	// Build separate command buffers for every framebuffer image
+	// Unlike in OpenGL all rendering commands are recorded once into command buffers that are then resubmitted to the queue
+	// This allows to generate work upfront and from multiple threads, one of the biggest advantages of Vulkan
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = {};
+		cmdBufInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
+		cmdBufInfo.pNext = nullptr;
+
+		// Set clear values for all framebuffer attachments with loadOp set to clear
+		// We use two attachments (color and depth) that are cleared at the start of the subpass and as such we need to set clear values for both
+		VkClearValue clearValues[2];
+		clearValues[0].color = { { 0.0f, 0.0f, 0.2f, 1.0f } };
+		clearValues[1].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = {};
+		renderPassBeginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
+		renderPassBeginInfo.pNext = nullptr;
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.offset.x = 0;
+		renderPassBeginInfo.renderArea.offset.y = 0;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		renderPassBeginInfo.clearValueCount = 2;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			// Set target frame buffer
+			renderPassBeginInfo.framebuffer = frameBuffers[i];
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			// Start the first sub pass specified in our default render pass setup by the base class
+			// This will clear the color and depth attachment
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			// Update dynamic viewport state
+			VkViewport viewport = {};
+			viewport.height = (float)height;
+			viewport.width = (float)width;
+			viewport.minDepth = (float) 0.0f;
+			viewport.maxDepth = (float) 1.0f;
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+			// Update dynamic scissor state
+			VkRect2D scissor = {};
+			scissor.extent.width = width;
+			scissor.extent.height = height;
+			scissor.offset.x = 0;
+			scissor.offset.y = 0;
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+			// Bind descriptor sets describing shader binding points
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr);
+
+			// Bind the rendering pipeline
+			// The pipeline (state object) contains all states of the rendering pipeline, binding it will set all the states specified at pipeline creation time
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
+
+			// Bind triangle vertex buffer (contains position and colors)
+			VkDeviceSize offsets[1] = { 0 };
+			vkCmdBindVertexBuffers(drawCmdBuffers[i], 0, 1, &vertices.buffer, offsets);
+
+			// Bind triangle index buffer
+			vkCmdBindIndexBuffer(drawCmdBuffers[i], indices.buffer, 0, VK_INDEX_TYPE_UINT32);
+
+			// Draw indexed triangle
+			vkCmdDrawIndexed(drawCmdBuffers[i], indices.count, 1, 0, 0, 1);
+
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			// Ending the render pass will add an implicit barrier transitioning the frame buffer color attachment to
+			// VK_IMAGE_LAYOUT_PRESENT_SRC_KHR for presenting it to the windowing system
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void draw()
+	{
+		// Get next image in the swap chain (back/front buffer)
+		VK_CHECK_RESULT(swapChain.acquireNextImage(presentCompleteSemaphore, &currentBuffer));
+
+		// Use a fence to wait until the command buffer has finished execution before using it again
+		VK_CHECK_RESULT(vkWaitForFences(device, 1, &waitFences[currentBuffer], VK_TRUE, UINT64_MAX));
+		VK_CHECK_RESULT(vkResetFences(device, 1, &waitFences[currentBuffer]));
+
+		// Pipeline stage at which the queue submission will wait (via pWaitSemaphores)
+		VkPipelineStageFlags waitStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+		// The submit info structure specifies a command buffer queue submission batch
+		VkSubmitInfo submitInfo = {};
+		submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
+		submitInfo.pWaitDstStageMask = &waitStageMask;               // Pointer to the list of pipeline stages that the semaphore waits will occur at
+		submitInfo.pWaitSemaphores = &presentCompleteSemaphore;      // Semaphore(s) to wait upon before the submitted command buffer starts executing
+		submitInfo.waitSemaphoreCount = 1;                           // One wait semaphore
+		submitInfo.pSignalSemaphores = &renderCompleteSemaphore;     // Semaphore(s) to be signaled when command buffers have completed
+		submitInfo.signalSemaphoreCount = 1;                         // One signal semaphore
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer]; // Command buffers(s) to execute in this batch (submission)
+		submitInfo.commandBufferCount = 1;                           // One command buffer
+
+		// Submit to the graphics queue passing a wait fence
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, waitFences[currentBuffer]));
+
+		// Present the current buffer to the swap chain
+		// Pass the semaphore signaled by the command buffer submission from the submit info as the wait semaphore for swap chain presentation
+		// This ensures that the image is not presented to the windowing system until all commands have been submitted
+		VkResult present = swapChain.queuePresent(queue, currentBuffer, renderCompleteSemaphore);
+		if (!((present == VK_SUCCESS) || (present == VK_SUBOPTIMAL_KHR))) {
+			VK_CHECK_RESULT(present);
+		}
+
+	}
+
+	// Prepare vertex and index buffers for an indexed triangle
+	// Also uploads them to device local memory using staging and initializes vertex input and attribute binding to match the vertex shader
+	void prepareVertices(bool useStagingBuffers)
+	{
+		// A note on memory management in Vulkan in general:
+		//	This is a very complex topic and while it's fine for an example application to small individual memory allocations that is not
+		//	what should be done a real-world application, where you should allocate large chunks of memory at once instead.
+
+		// Setup vertices
+		std::vector<Vertex> vertexBuffer =
+		{
+			{ {  1.0f,  1.0f, 0.0f }, { 1.0f, 0.0f, 0.0f } },
+			{ { -1.0f,  1.0f, 0.0f }, { 0.0f, 1.0f, 0.0f } },
+			{ {  0.0f, -1.0f, 0.0f }, { 0.0f, 0.0f, 1.0f } }
+		};
+		uint32_t vertexBufferSize = static_cast<uint32_t>(vertexBuffer.size()) * sizeof(Vertex);
+
+		// Setup indices
+		std::vector<uint32_t> indexBuffer = { 0, 1, 2 };
+		indices.count = static_cast<uint32_t>(indexBuffer.size());
+		uint32_t indexBufferSize = indices.count * sizeof(uint32_t);
+
+		VkMemoryAllocateInfo memAlloc = {};
+		memAlloc.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+		VkMemoryRequirements memReqs;
+
+		void *data;
+
+		if (useStagingBuffers)
+		{
+			// Static data like vertex and index buffer should be stored on the device memory
+			// for optimal (and fastest) access by the GPU
+			//
+			// To achieve this we use so-called "staging buffers" :
+			// - Create a buffer that's visible to the host (and can be mapped)
+			// - Copy the data to this buffer
+			// - Create another buffer that's local on the device (VRAM) with the same size
+			// - Copy the data from the host to the device using a command buffer
+			// - Delete the host visible (staging) buffer
+			// - Use the device local buffers for rendering
+
+			struct StagingBuffer {
+				VkDeviceMemory memory;
+				VkBuffer buffer;
+			};
+
+			struct {
+				StagingBuffer vertices;
+				StagingBuffer indices;
+			} stagingBuffers;
+
+			// Vertex buffer
+			VkBufferCreateInfo vertexBufferInfo = {};
+			vertexBufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+			vertexBufferInfo.size = vertexBufferSize;
+			// Buffer is used as the copy source
+			vertexBufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
+			// Create a host-visible buffer to copy the vertex data to (staging buffer)
+			VK_CHECK_RESULT(vkCreateBuffer(device, &vertexBufferInfo, nullptr, &stagingBuffers.vertices.buffer));
+			vkGetBufferMemoryRequirements(device, stagingBuffers.vertices.buffer, &memReqs);
+			memAlloc.allocationSize = memReqs.size;
+			// Request a host visible memory type that can be used to copy our data do
+			// Also request it to be coherent, so that writes are visible to the GPU right after unmapping the buffer
+			memAlloc.memoryTypeIndex = getMemoryTypeIndex(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
+			VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &stagingBuffers.vertices.memory));
+			// Map and copy
+			VK_CHECK_RESULT(vkMapMemory(device, stagingBuffers.vertices.memory, 0, memAlloc.allocationSize, 0, &data));
+			memcpy(data, vertexBuffer.data(), vertexBufferSize);
+			vkUnmapMemory(device, stagingBuffers.vertices.memory);
+			VK_CHECK_RESULT(vkBindBufferMemory(device, stagingBuffers.vertices.buffer, stagingBuffers.vertices.memory, 0));
+
+			// Create a device local buffer to which the (host local) vertex data will be copied and which will be used for rendering
+			vertexBufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
+			VK_CHECK_RESULT(vkCreateBuffer(device, &vertexBufferInfo, nullptr, &vertices.buffer));
+			vkGetBufferMemoryRequirements(device, vertices.buffer, &memReqs);
+			memAlloc.allocationSize = memReqs.size;
+			memAlloc.memoryTypeIndex = getMemoryTypeIndex(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+			VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &vertices.memory));
+			VK_CHECK_RESULT(vkBindBufferMemory(device, vertices.buffer, vertices.memory, 0));
+
+			// Index buffer
+			VkBufferCreateInfo indexbufferInfo = {};
+			indexbufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+			indexbufferInfo.size = indexBufferSize;
+			indexbufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
+			// Copy index data to a buffer visible to the host (staging buffer)
+			VK_CHECK_RESULT(vkCreateBuffer(device, &indexbufferInfo, nullptr, &stagingBuffers.indices.buffer));
+			vkGetBufferMemoryRequirements(device, stagingBuffers.indices.buffer, &memReqs);
+			memAlloc.allocationSize = memReqs.size;
+			memAlloc.memoryTypeIndex = getMemoryTypeIndex(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
+			VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &stagingBuffers.indices.memory));
+			VK_CHECK_RESULT(vkMapMemory(device, stagingBuffers.indices.memory, 0, indexBufferSize, 0, &data));
+			memcpy(data, indexBuffer.data(), indexBufferSize);
+			vkUnmapMemory(device, stagingBuffers.indices.memory);
+			VK_CHECK_RESULT(vkBindBufferMemory(device, stagingBuffers.indices.buffer, stagingBuffers.indices.memory, 0));
+
+			// Create destination buffer with device only visibility
+			indexbufferInfo.usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
+			VK_CHECK_RESULT(vkCreateBuffer(device, &indexbufferInfo, nullptr, &indices.buffer));
+			vkGetBufferMemoryRequirements(device, indices.buffer, &memReqs);
+			memAlloc.allocationSize = memReqs.size;
+			memAlloc.memoryTypeIndex = getMemoryTypeIndex(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+			VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &indices.memory));
+			VK_CHECK_RESULT(vkBindBufferMemory(device, indices.buffer, indices.memory, 0));
+
+			// Buffer copies have to be submitted to a queue, so we need a command buffer for them
+			// Note: Some devices offer a dedicated transfer queue (with only the transfer bit set) that may be faster when doing lots of copies
+			VkCommandBuffer copyCmd = getCommandBuffer(true);
+
+			// Put buffer region copies into command buffer
+			VkBufferCopy copyRegion = {};
+
+			// Vertex buffer
+			copyRegion.size = vertexBufferSize;
+			vkCmdCopyBuffer(copyCmd, stagingBuffers.vertices.buffer, vertices.buffer, 1, &copyRegion);
+			// Index buffer
+			copyRegion.size = indexBufferSize;
+			vkCmdCopyBuffer(copyCmd, stagingBuffers.indices.buffer, indices.buffer,	1, &copyRegion);
+
+			// Flushing the command buffer will also submit it to the queue and uses a fence to ensure that all commands have been executed before returning
+			flushCommandBuffer(copyCmd);
+
+			// Destroy staging buffers
+			// Note: Staging buffer must not be deleted before the copies have been submitted and executed
+			vkDestroyBuffer(device, stagingBuffers.vertices.buffer, nullptr);
+			vkFreeMemory(device, stagingBuffers.vertices.memory, nullptr);
+			vkDestroyBuffer(device, stagingBuffers.indices.buffer, nullptr);
+			vkFreeMemory(device, stagingBuffers.indices.memory, nullptr);
+		}
+		else
+		{
+			// Don't use staging
+			// Create host-visible buffers only and use these for rendering. This is not advised and will usually result in lower rendering performance
+
+			// Vertex buffer
+			VkBufferCreateInfo vertexBufferInfo = {};
+			vertexBufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+			vertexBufferInfo.size = vertexBufferSize;
+			vertexBufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
+
+			// Copy vertex data to a buffer visible to the host
+			VK_CHECK_RESULT(vkCreateBuffer(device, &vertexBufferInfo, nullptr, &vertices.buffer));
+			vkGetBufferMemoryRequirements(device, vertices.buffer, &memReqs);
+			memAlloc.allocationSize = memReqs.size;
+			// VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT is host visible memory, and VK_MEMORY_PROPERTY_HOST_COHERENT_BIT makes sure writes are directly visible
+			memAlloc.memoryTypeIndex = getMemoryTypeIndex(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
+			VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &vertices.memory));
+			VK_CHECK_RESULT(vkMapMemory(device, vertices.memory, 0, memAlloc.allocationSize, 0, &data));
+			memcpy(data, vertexBuffer.data(), vertexBufferSize);
+			vkUnmapMemory(device, vertices.memory);
+			VK_CHECK_RESULT(vkBindBufferMemory(device, vertices.buffer, vertices.memory, 0));
+
+			// Index buffer
+			VkBufferCreateInfo indexbufferInfo = {};
+			indexbufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+			indexbufferInfo.size = indexBufferSize;
+			indexbufferInfo.usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT;
+
+			// Copy index data to a buffer visible to the host
+			VK_CHECK_RESULT(vkCreateBuffer(device, &indexbufferInfo, nullptr, &indices.buffer));
+			vkGetBufferMemoryRequirements(device, indices.buffer, &memReqs);
+			memAlloc.allocationSize = memReqs.size;
+			memAlloc.memoryTypeIndex = getMemoryTypeIndex(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
+			VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &indices.memory));
+			VK_CHECK_RESULT(vkMapMemory(device, indices.memory, 0, indexBufferSize, 0, &data));
+			memcpy(data, indexBuffer.data(), indexBufferSize);
+			vkUnmapMemory(device, indices.memory);
+			VK_CHECK_RESULT(vkBindBufferMemory(device, indices.buffer, indices.memory, 0));
+		}
+	}
+
+	void setupDescriptorPool()
+	{
+		// We need to tell the API the number of max. requested descriptors per type
+		VkDescriptorPoolSize typeCounts[1];
+		// This example only uses one descriptor type (uniform buffer) and only requests one descriptor of this type
+		typeCounts[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+		typeCounts[0].descriptorCount = 1;
+		// For additional types you need to add new entries in the type count list
+		// E.g. for two combined image samplers :
+		// typeCounts[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+		// typeCounts[1].descriptorCount = 2;
+
+		// Create the global descriptor pool
+		// All descriptors used in this example are allocated from this pool
+		VkDescriptorPoolCreateInfo descriptorPoolInfo = {};
+		descriptorPoolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
+		descriptorPoolInfo.pNext = nullptr;
+		descriptorPoolInfo.poolSizeCount = 1;
+		descriptorPoolInfo.pPoolSizes = typeCounts;
+		// Set the max. number of descriptor sets that can be requested from this pool (requesting beyond this limit will result in an error)
+		descriptorPoolInfo.maxSets = 1;
+
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+	}
+
+	void setupDescriptorSetLayout()
+	{
+		// Setup layout of descriptors used in this example
+		// Basically connects the different shader stages to descriptors for binding uniform buffers, image samplers, etc.
+		// So every shader binding should map to one descriptor set layout binding
+
+		// Binding 0: Uniform buffer (Vertex shader)
+		VkDescriptorSetLayoutBinding layoutBinding = {};
+		layoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+		layoutBinding.descriptorCount = 1;
+		layoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
+		layoutBinding.pImmutableSamplers = nullptr;
+
+		VkDescriptorSetLayoutCreateInfo descriptorLayout = {};
+		descriptorLayout.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
+		descriptorLayout.pNext = nullptr;
+		descriptorLayout.bindingCount = 1;
+		descriptorLayout.pBindings = &layoutBinding;
+
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout));
+
+		// Create the pipeline layout that is used to generate the rendering pipelines that are based on this descriptor set layout
+		// In a more complex scenario you would have different pipeline layouts for different descriptor set layouts that could be reused
+		VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo = {};
+		pPipelineLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
+		pPipelineLayoutCreateInfo.pNext = nullptr;
+		pPipelineLayoutCreateInfo.setLayoutCount = 1;
+		pPipelineLayoutCreateInfo.pSetLayouts = &descriptorSetLayout;
+
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayout));
+	}
+
+	void setupDescriptorSet()
+	{
+		// Allocate a new descriptor set from the global descriptor pool
+		VkDescriptorSetAllocateInfo allocInfo = {};
+		allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
+		allocInfo.descriptorPool = descriptorPool;
+		allocInfo.descriptorSetCount = 1;
+		allocInfo.pSetLayouts = &descriptorSetLayout;
+
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet));
+
+		// Update the descriptor set determining the shader binding points
+		// For every binding point used in a shader there needs to be one
+		// descriptor set matching that binding point
+
+		VkWriteDescriptorSet writeDescriptorSet = {};
+
+		// Binding 0 : Uniform buffer
+		writeDescriptorSet.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+		writeDescriptorSet.dstSet = descriptorSet;
+		writeDescriptorSet.descriptorCount = 1;
+		writeDescriptorSet.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+		writeDescriptorSet.pBufferInfo = &uniformBufferVS.descriptor;
+		// Binds this uniform buffer to binding point 0
+		writeDescriptorSet.dstBinding = 0;
+
+		vkUpdateDescriptorSets(device, 1, &writeDescriptorSet, 0, nullptr);
+	}
+
+	// Create the depth (and stencil) buffer attachments used by our framebuffers
+	// Note: Override of virtual function in the base class and called from within VulkanExampleBase::prepare
+	void setupDepthStencil()
+	{
+		// Create an optimal image used as the depth stencil attachment
+		VkImageCreateInfo image = {};
+		image.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
+		image.imageType = VK_IMAGE_TYPE_2D;
+		image.format = depthFormat;
+		// Use example's height and width
+		image.extent = { width, height, 1 };
+		image.mipLevels = 1;
+		image.arrayLayers = 1;
+		image.samples = VK_SAMPLE_COUNT_1_BIT;
+		image.tiling = VK_IMAGE_TILING_OPTIMAL;
+		image.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
+		image.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+		VK_CHECK_RESULT(vkCreateImage(device, &image, nullptr, &depthStencil.image));
+
+		// Allocate memory for the image (device local) and bind it to our image
+		VkMemoryAllocateInfo memAlloc = {};
+		memAlloc.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+		VkMemoryRequirements memReqs;
+		vkGetImageMemoryRequirements(device, depthStencil.image, &memReqs);
+		memAlloc.allocationSize = memReqs.size;
+		memAlloc.memoryTypeIndex = getMemoryTypeIndex(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+		VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &depthStencil.mem));
+		VK_CHECK_RESULT(vkBindImageMemory(device, depthStencil.image, depthStencil.mem, 0));
+
+		// Create a view for the depth stencil image
+		// Images aren't directly accessed in Vulkan, but rather through views described by a subresource range
+		// This allows for multiple views of one image with differing ranges (e.g. for different layers)
+		VkImageViewCreateInfo depthStencilView = {};
+		depthStencilView.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+		depthStencilView.viewType = VK_IMAGE_VIEW_TYPE_2D;
+		depthStencilView.format = depthFormat;
+		depthStencilView.subresourceRange = {};
+		depthStencilView.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
+		depthStencilView.subresourceRange.baseMipLevel = 0;
+		depthStencilView.subresourceRange.levelCount = 1;
+		depthStencilView.subresourceRange.baseArrayLayer = 0;
+		depthStencilView.subresourceRange.layerCount = 1;
+		depthStencilView.image = depthStencil.image;
+		VK_CHECK_RESULT(vkCreateImageView(device, &depthStencilView, nullptr, &depthStencil.view));
+	}
+
+	// Create a frame buffer for each swap chain image
+	// Note: Override of virtual function in the base class and called from within VulkanExampleBase::prepare
+	void setupFrameBuffer()
+	{
+		// Create a frame buffer for every image in the swapchain
+		frameBuffers.resize(swapChain.imageCount);
+		for (size_t i = 0; i < frameBuffers.size(); i++)
+		{
+			std::array<VkImageView, 2> attachments;
+			attachments[0] = swapChain.buffers[i].view; // Color attachment is the view of the swapchain image
+			attachments[1] = depthStencil.view;         // Depth/Stencil attachment is the same for all frame buffers
+
+			VkFramebufferCreateInfo frameBufferCreateInfo = {};
+			frameBufferCreateInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
+			// All frame buffers use the same renderpass setup
+			frameBufferCreateInfo.renderPass = renderPass;
+			frameBufferCreateInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
+			frameBufferCreateInfo.pAttachments = attachments.data();
+			frameBufferCreateInfo.width = width;
+			frameBufferCreateInfo.height = height;
+			frameBufferCreateInfo.layers = 1;
+			// Create the framebuffer
+			VK_CHECK_RESULT(vkCreateFramebuffer(device, &frameBufferCreateInfo, nullptr, &frameBuffers[i]));
+		}
+	}
+
+	// Render pass setup
+	// Render passes are a new concept in Vulkan. They describe the attachments used during rendering and may contain multiple subpasses with attachment dependencies
+	// This allows the driver to know up-front what the rendering will look like and is a good opportunity to optimize especially on tile-based renderers (with multiple subpasses)
+	// Using sub pass dependencies also adds implicit layout transitions for the attachment used, so we don't need to add explicit image memory barriers to transform them
+	// Note: Override of virtual function in the base class and called from within VulkanExampleBase::prepare
+	void setupRenderPass()
+	{
+		// This example will use a single render pass with one subpass
+
+		// Descriptors for the attachments used by this renderpass
+		std::array<VkAttachmentDescription, 2> attachments = {};
+
+		// Color attachment
+		attachments[0].format = swapChain.colorFormat;                                  // Use the color format selected by the swapchain
+		attachments[0].samples = VK_SAMPLE_COUNT_1_BIT;                                 // We don't use multi sampling in this example
+		attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;                            // Clear this attachment at the start of the render pass
+		attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;                          // Keep its contents after the render pass is finished (for displaying it)
+		attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;                 // We don't use stencil, so don't care for load
+		attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;               // Same for store
+		attachments[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;                       // Layout at render pass start. Initial doesn't matter, so we use undefined
+		attachments[0].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;                   // Layout to which the attachment is transitioned when the render pass is finished
+		                                                                                // As we want to present the color buffer to the swapchain, we transition to PRESENT_KHR
+		// Depth attachment
+		attachments[1].format = depthFormat;                                           // A proper depth format is selected in the example base
+		attachments[1].samples = VK_SAMPLE_COUNT_1_BIT;
+		attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;                           // Clear depth at start of first subpass
+		attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;                     // We don't need depth after render pass has finished (DONT_CARE may result in better performance)
+		attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;                // No stencil
+		attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;              // No Stencil
+		attachments[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;                      // Layout at render pass start. Initial doesn't matter, so we use undefined
+		attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; // Transition to depth/stencil attachment
+
+		// Setup attachment references
+		VkAttachmentReference colorReference = {};
+		colorReference.attachment = 0;                                    // Attachment 0 is color
+		colorReference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; // Attachment layout used as color during the subpass
+
+		VkAttachmentReference depthReference = {};
+		depthReference.attachment = 1;                                            // Attachment 1 is color
+		depthReference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; // Attachment used as depth/stencil used during the subpass
+
+		// Setup a single subpass reference
+		VkSubpassDescription subpassDescription = {};
+		subpassDescription.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+		subpassDescription.colorAttachmentCount = 1;                            // Subpass uses one color attachment
+		subpassDescription.pColorAttachments = &colorReference;                 // Reference to the color attachment in slot 0
+		subpassDescription.pDepthStencilAttachment = &depthReference;           // Reference to the depth attachment in slot 1
+		subpassDescription.inputAttachmentCount = 0;                            // Input attachments can be used to sample from contents of a previous subpass
+		subpassDescription.pInputAttachments = nullptr;                         // (Input attachments not used by this example)
+		subpassDescription.preserveAttachmentCount = 0;                         // Preserved attachments can be used to loop (and preserve) attachments through subpasses
+		subpassDescription.pPreserveAttachments = nullptr;                      // (Preserve attachments not used by this example)
+		subpassDescription.pResolveAttachments = nullptr;                       // Resolve attachments are resolved at the end of a sub pass and can be used for e.g. multi sampling
+
+		// Setup subpass dependencies
+		// These will add the implicit attachment layout transitions specified by the attachment descriptions
+		// The actual usage layout is preserved through the layout specified in the attachment reference
+		// Each subpass dependency will introduce a memory and execution dependency between the source and dest subpass described by
+		// srcStageMask, dstStageMask, srcAccessMask, dstAccessMask (and dependencyFlags is set)
+		// Note: VK_SUBPASS_EXTERNAL is a special constant that refers to all commands executed outside of the actual renderpass)
+		std::array<VkSubpassDependency, 2> dependencies;
+
+		// First dependency at the start of the renderpass
+		// Does the transition from final to initial layout
+		dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;                             // Producer of the dependency
+		dependencies[0].dstSubpass = 0;                                               // Consumer is our single subpass that will wait for the execution dependency
+		dependencies[0].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; // Match our pWaitDstStageMask when we vkQueueSubmit
+		dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; // is a loadOp stage for color attachments
+		dependencies[0].srcAccessMask = 0;                                            // semaphore wait already does memory dependency for us
+		dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;         // is a loadOp CLEAR access mask for color attachments
+		dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+		// Second dependency at the end the renderpass
+		// Does the transition from the initial to the final layout
+		// Technically this is the same as the implicit subpass dependency, but we are gonna state it explicitly here
+		dependencies[1].srcSubpass = 0;                                               // Producer of the dependency is our single subpass
+		dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;                             // Consumer are all commands outside of the renderpass
+		dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; // is a storeOp stage for color attachments
+		dependencies[1].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;          // Do not block any subsequent work
+		dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;         // is a storeOp `STORE` access mask for color attachments
+		dependencies[1].dstAccessMask = 0;
+		dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
+
+		// Create the actual renderpass
+		VkRenderPassCreateInfo renderPassInfo = {};
+		renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
+		renderPassInfo.attachmentCount = static_cast<uint32_t>(attachments.size());  // Number of attachments used by this render pass
+		renderPassInfo.pAttachments = attachments.data();                            // Descriptions of the attachments used by the render pass
+		renderPassInfo.subpassCount = 1;                                             // We only use one subpass in this example
+		renderPassInfo.pSubpasses = &subpassDescription;                             // Description of that subpass
+		renderPassInfo.dependencyCount = static_cast<uint32_t>(dependencies.size()); // Number of subpass dependencies
+		renderPassInfo.pDependencies = dependencies.data();                          // Subpass dependencies used by the render pass
+
+		VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass));
+	}
+
+	// Vulkan loads its shaders from an immediate binary representation called SPIR-V
+	// Shaders are compiled offline from e.g. GLSL using the reference glslang compiler
+	// This function loads such a shader from a binary file and returns a shader module structure
+	VkShaderModule loadSPIRVShader(std::string filename)
+	{
+		size_t shaderSize;
+		char* shaderCode = NULL;
+
+#if defined(__ANDROID__)
+		// Load shader from compressed asset
+		AAsset* asset = AAssetManager_open(androidApp->activity->assetManager, filename.c_str(), AASSET_MODE_STREAMING);
+		assert(asset);
+		shaderSize = AAsset_getLength(asset);
+		assert(shaderSize > 0);
+
+		shaderCode = new char[shaderSize];
+		AAsset_read(asset, shaderCode, shaderSize);
+		AAsset_close(asset);
+#else
+		std::ifstream is(filename, std::ios::binary | std::ios::in | std::ios::ate);
+
+		if (is.is_open())
+		{
+			shaderSize = is.tellg();
+			is.seekg(0, std::ios::beg);
+			// Copy file contents into a buffer
+			shaderCode = new char[shaderSize];
+			is.read(shaderCode, shaderSize);
+			is.close();
+			assert(shaderSize > 0);
+		}
+#endif
+		if (shaderCode)
+		{
+			// Create a new shader module that will be used for pipeline creation
+			VkShaderModuleCreateInfo moduleCreateInfo{};
+			moduleCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
+			moduleCreateInfo.codeSize = shaderSize;
+			moduleCreateInfo.pCode = (uint32_t*)shaderCode;
+
+			VkShaderModule shaderModule;
+			VK_CHECK_RESULT(vkCreateShaderModule(device, &moduleCreateInfo, NULL, &shaderModule));
+
+			delete[] shaderCode;
+
+			return shaderModule;
+		}
+		else
+		{
+			std::cerr << "Error: Could not open shader file \"" << filename << "\"" << std::endl;
+			return VK_NULL_HANDLE;
+		}
+	}
+
+	void preparePipelines()
+	{
+		// Create the graphics pipeline used in this example
+		// Vulkan uses the concept of rendering pipelines to encapsulate fixed states, replacing OpenGL's complex state machine
+		// A pipeline is then stored and hashed on the GPU making pipeline changes very fast
+		// Note: There are still a few dynamic states that are not directly part of the pipeline (but the info that they are used is)
+
+		VkGraphicsPipelineCreateInfo pipelineCreateInfo = {};
+		pipelineCreateInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
+		// The layout used for this pipeline (can be shared among multiple pipelines using the same layout)
+		pipelineCreateInfo.layout = pipelineLayout;
+		// Renderpass this pipeline is attached to
+		pipelineCreateInfo.renderPass = renderPass;
+
+		// Construct the different states making up the pipeline
+
+		// Input assembly state describes how primitives are assembled
+		// This pipeline will assemble vertex data as a triangle lists (though we only use one triangle)
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = {};
+		inputAssemblyState.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
+		inputAssemblyState.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
+
+		// Rasterization state
+		VkPipelineRasterizationStateCreateInfo rasterizationState = {};
+		rasterizationState.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
+		rasterizationState.polygonMode = VK_POLYGON_MODE_FILL;
+		rasterizationState.cullMode = VK_CULL_MODE_NONE;
+		rasterizationState.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
+		rasterizationState.depthClampEnable = VK_FALSE;
+		rasterizationState.rasterizerDiscardEnable = VK_FALSE;
+		rasterizationState.depthBiasEnable = VK_FALSE;
+		rasterizationState.lineWidth = 1.0f;
+
+		// Color blend state describes how blend factors are calculated (if used)
+		// We need one blend attachment state per color attachment (even if blending is not used)
+		VkPipelineColorBlendAttachmentState blendAttachmentState[1] = {};
+		blendAttachmentState[0].colorWriteMask = 0xf;
+		blendAttachmentState[0].blendEnable = VK_FALSE;
+		VkPipelineColorBlendStateCreateInfo colorBlendState = {};
+		colorBlendState.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
+		colorBlendState.attachmentCount = 1;
+		colorBlendState.pAttachments = blendAttachmentState;
+
+		// Viewport state sets the number of viewports and scissor used in this pipeline
+		// Note: This is actually overridden by the dynamic states (see below)
+		VkPipelineViewportStateCreateInfo viewportState = {};
+		viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
+		viewportState.viewportCount = 1;
+		viewportState.scissorCount = 1;
+
+		// Enable dynamic states
+		// Most states are baked into the pipeline, but there are still a few dynamic states that can be changed within a command buffer
+		// To be able to change these we need do specify which dynamic states will be changed using this pipeline. Their actual states are set later on in the command buffer.
+		// For this example we will set the viewport and scissor using dynamic states
+		std::vector<VkDynamicState> dynamicStateEnables;
+		dynamicStateEnables.push_back(VK_DYNAMIC_STATE_VIEWPORT);
+		dynamicStateEnables.push_back(VK_DYNAMIC_STATE_SCISSOR);
+		VkPipelineDynamicStateCreateInfo dynamicState = {};
+		dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
+		dynamicState.pDynamicStates = dynamicStateEnables.data();
+		dynamicState.dynamicStateCount = static_cast<uint32_t>(dynamicStateEnables.size());
+
+		// Depth and stencil state containing depth and stencil compare and test operations
+		// We only use depth tests and want depth tests and writes to be enabled and compare with less or equal
+		VkPipelineDepthStencilStateCreateInfo depthStencilState = {};
+		depthStencilState.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
+		depthStencilState.depthTestEnable = VK_TRUE;
+		depthStencilState.depthWriteEnable = VK_TRUE;
+		depthStencilState.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL;
+		depthStencilState.depthBoundsTestEnable = VK_FALSE;
+		depthStencilState.back.failOp = VK_STENCIL_OP_KEEP;
+		depthStencilState.back.passOp = VK_STENCIL_OP_KEEP;
+		depthStencilState.back.compareOp = VK_COMPARE_OP_ALWAYS;
+		depthStencilState.stencilTestEnable = VK_FALSE;
+		depthStencilState.front = depthStencilState.back;
+
+		// Multi sampling state
+		// This example does not make use of multi sampling (for anti-aliasing), the state must still be set and passed to the pipeline
+		VkPipelineMultisampleStateCreateInfo multisampleState = {};
+		multisampleState.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
+		multisampleState.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
+		multisampleState.pSampleMask = nullptr;
+
+		// Vertex input descriptions
+		// Specifies the vertex input parameters for a pipeline
+
+		// Vertex input binding
+		// This example uses a single vertex input binding at binding point 0 (see vkCmdBindVertexBuffers)
+		VkVertexInputBindingDescription vertexInputBinding = {};
+		vertexInputBinding.binding = 0;
+		vertexInputBinding.stride = sizeof(Vertex);
+		vertexInputBinding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
+
+		// Input attribute bindings describe shader attribute locations and memory layouts
+		std::array<VkVertexInputAttributeDescription, 2> vertexInputAttributs;
+		// These match the following shader layout (see triangle.vert):
+		//	layout (location = 0) in vec3 inPos;
+		//	layout (location = 1) in vec3 inColor;
+		// Attribute location 0: Position
+		vertexInputAttributs[0].binding = 0;
+		vertexInputAttributs[0].location = 0;
+		// Position attribute is three 32 bit signed (SFLOAT) floats (R32 G32 B32)
+		vertexInputAttributs[0].format = VK_FORMAT_R32G32B32_SFLOAT;
+		vertexInputAttributs[0].offset = offsetof(Vertex, position);
+		// Attribute location 1: Color
+		vertexInputAttributs[1].binding = 0;
+		vertexInputAttributs[1].location = 1;
+		// Color attribute is three 32 bit signed (SFLOAT) floats (R32 G32 B32)
+		vertexInputAttributs[1].format = VK_FORMAT_R32G32B32_SFLOAT;
+		vertexInputAttributs[1].offset = offsetof(Vertex, color);
+
+		// Vertex input state used for pipeline creation
+		VkPipelineVertexInputStateCreateInfo vertexInputState = {};
+		vertexInputState.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
+		vertexInputState.vertexBindingDescriptionCount = 1;
+		vertexInputState.pVertexBindingDescriptions = &vertexInputBinding;
+		vertexInputState.vertexAttributeDescriptionCount = 2;
+		vertexInputState.pVertexAttributeDescriptions = vertexInputAttributs.data();
+
+		// Shaders
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages{};
+
+		// Vertex shader
+		shaderStages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+		// Set pipeline stage for this shader
+		shaderStages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
+		// Load binary SPIR-V shader
+		shaderStages[0].module = loadSPIRVShader(getShadersPath() + "triangle/triangle.vert.spv");
+		// Main entry point for the shader
+		shaderStages[0].pName = "main";
+		assert(shaderStages[0].module != VK_NULL_HANDLE);
+
+		// Fragment shader
+		shaderStages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+		// Set pipeline stage for this shader
+		shaderStages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
+		// Load binary SPIR-V shader
+		shaderStages[1].module = loadSPIRVShader(getShadersPath() + "triangle/triangle.frag.spv");
+		// Main entry point for the shader
+		shaderStages[1].pName = "main";
+		assert(shaderStages[1].module != VK_NULL_HANDLE);
+
+		// Set pipeline shader stage info
+		pipelineCreateInfo.stageCount = static_cast<uint32_t>(shaderStages.size());
+		pipelineCreateInfo.pStages = shaderStages.data();
+
+		// Assign the pipeline states to the pipeline creation info structure
+		pipelineCreateInfo.pVertexInputState = &vertexInputState;
+		pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState;
+		pipelineCreateInfo.pRasterizationState = &rasterizationState;
+		pipelineCreateInfo.pColorBlendState = &colorBlendState;
+		pipelineCreateInfo.pMultisampleState = &multisampleState;
+		pipelineCreateInfo.pViewportState = &viewportState;
+		pipelineCreateInfo.pDepthStencilState = &depthStencilState;
+		pipelineCreateInfo.pDynamicState = &dynamicState;
+
+		// Create rendering pipeline using the specified states
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipeline));
+
+		// Shader modules are no longer needed once the graphics pipeline has been created
+		vkDestroyShaderModule(device, shaderStages[0].module, nullptr);
+		vkDestroyShaderModule(device, shaderStages[1].module, nullptr);
+	}
+
+	void prepareUniformBuffers()
+	{
+		// Prepare and initialize a uniform buffer block containing shader uniforms
+		// Single uniforms like in OpenGL are no longer present in Vulkan. All Shader uniforms are passed via uniform buffer blocks
+		VkMemoryRequirements memReqs;
+
+		// Vertex shader uniform buffer block
+		VkBufferCreateInfo bufferInfo = {};
+		VkMemoryAllocateInfo allocInfo = {};
+		allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+		allocInfo.pNext = nullptr;
+		allocInfo.allocationSize = 0;
+		allocInfo.memoryTypeIndex = 0;
+
+		bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+		bufferInfo.size = sizeof(uboVS);
+		// This buffer will be used as a uniform buffer
+		bufferInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
+
+		// Create a new buffer
+		VK_CHECK_RESULT(vkCreateBuffer(device, &bufferInfo, nullptr, &uniformBufferVS.buffer));
+		// Get memory requirements including size, alignment and memory type
+		vkGetBufferMemoryRequirements(device, uniformBufferVS.buffer, &memReqs);
+		allocInfo.allocationSize = memReqs.size;
+		// Get the memory type index that supports host visible memory access
+		// Most implementations offer multiple memory types and selecting the correct one to allocate memory from is crucial
+		// We also want the buffer to be host coherent so we don't have to flush (or sync after every update.
+		// Note: This may affect performance so you might not want to do this in a real world application that updates buffers on a regular base
+		allocInfo.memoryTypeIndex = getMemoryTypeIndex(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
+		// Allocate memory for the uniform buffer
+		VK_CHECK_RESULT(vkAllocateMemory(device, &allocInfo, nullptr, &(uniformBufferVS.memory)));
+		// Bind memory to buffer
+		VK_CHECK_RESULT(vkBindBufferMemory(device, uniformBufferVS.buffer, uniformBufferVS.memory, 0));
+
+		// Store information in the uniform's descriptor that is used by the descriptor set
+		uniformBufferVS.descriptor.buffer = uniformBufferVS.buffer;
+		uniformBufferVS.descriptor.offset = 0;
+		uniformBufferVS.descriptor.range = sizeof(uboVS);
+
+		updateUniformBuffers();
+	}
+
+	void updateUniformBuffers()
+	{
+		// Pass matrices to the shaders
+		uboVS.projectionMatrix = camera.matrices.perspective;
+		uboVS.viewMatrix = camera.matrices.view;
+		uboVS.modelMatrix = glm::mat4(1.0f);
+
+		// Map uniform buffer and update it
+		uint8_t *pData;
+		VK_CHECK_RESULT(vkMapMemory(device, uniformBufferVS.memory, 0, sizeof(uboVS), 0, (void **)&pData));
+		memcpy(pData, &uboVS, sizeof(uboVS));
+		// Unmap after data has been copied
+		// Note: Since we requested a host coherent memory type for the uniform buffer, the write is instantly visible to the GPU
+		vkUnmapMemory(device, uniformBufferVS.memory);
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		prepareSynchronizationPrimitives();
+		prepareVertices(USE_STAGING);
+		prepareUniformBuffers();
+		setupDescriptorSetLayout();
+		preparePipelines();
+		setupDescriptorPool();
+		setupDescriptorSet();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+	}
+
+	virtual void viewChanged()
+	{
+		// This function is called by the base example class each time the view is changed by user input
+		updateUniformBuffers();
+	}
+};
+
+// OS specific macros for the example main entry points
+// Most of the code base is shared for the different supported operating systems, but stuff like message handling differs
+
+#if defined(_WIN32)
+// Windows entry point
+VulkanExample *vulkanExample;
+LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+	if (vulkanExample != NULL)
+	{
+		vulkanExample->handleMessages(hWnd, uMsg, wParam, lParam);
+	}
+	return (DefWindowProc(hWnd, uMsg, wParam, lParam));
+}
+int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR pCmdLine, int nCmdShow)
+{
+	for (size_t i = 0; i < __argc; i++) { VulkanExample::args.push_back(__argv[i]); };
+	vulkanExample = new VulkanExample();
+	vulkanExample->initVulkan();
+	vulkanExample->setupWindow(hInstance, WndProc);
+	vulkanExample->prepare();
+	vulkanExample->renderLoop();
+	delete(vulkanExample);
+	return 0;
+}
+
+#elif defined(__ANDROID__)
+// Android entry point
+VulkanExample *vulkanExample;
+void android_main(android_app* state)
+{
+	vulkanExample = new VulkanExample();
+	state->userData = vulkanExample;
+	state->onAppCmd = VulkanExample::handleAppCommand;
+	state->onInputEvent = VulkanExample::handleAppInput;
+	androidApp = state;
+	vulkanExample->renderLoop();
+	delete(vulkanExample);
+}
+#elif defined(_DIRECT2DISPLAY)
+
+// Linux entry point with direct to display wsi
+// Direct to Displays (D2D) is used on embedded platforms
+VulkanExample *vulkanExample;
+static void handleEvent()
+{
+}
+int main(const int argc, const char *argv[])
+{
+	for (size_t i = 0; i < argc; i++) { VulkanExample::args.push_back(argv[i]); };
+	vulkanExample = new VulkanExample();
+	vulkanExample->initVulkan();
+	vulkanExample->prepare();
+	vulkanExample->renderLoop();
+	delete(vulkanExample);
+	return 0;
+}
+#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT)
+VulkanExample *vulkanExample;
+static void handleEvent(const DFBWindowEvent *event)
+{
+	if (vulkanExample != NULL)
+	{
+		vulkanExample->handleEvent(event);
+	}
+}
+int main(const int argc, const char *argv[])
+{
+	for (size_t i = 0; i < argc; i++) { VulkanExample::args.push_back(argv[i]); };
+	vulkanExample = new VulkanExample();
+	vulkanExample->initVulkan();
+	vulkanExample->setupWindow();
+	vulkanExample->prepare();
+	vulkanExample->renderLoop();
+	delete(vulkanExample);
+	return 0;
+}
+#elif defined(VK_USE_PLATFORM_WAYLAND_KHR)
+VulkanExample *vulkanExample;
+int main(const int argc, const char *argv[])
+{
+	for (size_t i = 0; i < argc; i++) { VulkanExample::args.push_back(argv[i]); };
+	vulkanExample = new VulkanExample();
+	vulkanExample->initVulkan();
+	vulkanExample->setupWindow();
+	vulkanExample->prepare();
+	vulkanExample->renderLoop();
+	delete(vulkanExample);
+	return 0;
+}
+#elif defined(__linux__) || defined(__FreeBSD__)
+
+// Linux entry point
+VulkanExample *vulkanExample;
+#if defined(VK_USE_PLATFORM_XCB_KHR)
+static void handleEvent(const xcb_generic_event_t *event)
+{
+	if (vulkanExample != NULL)
+	{
+		vulkanExample->handleEvent(event);
+	}
+}
+#else
+static void handleEvent()
+{
+}
+#endif
+int main(const int argc, const char *argv[])
+{
+	for (size_t i = 0; i < argc; i++) { VulkanExample::args.push_back(argv[i]); };
+	vulkanExample = new VulkanExample();
+	vulkanExample->initVulkan();
+	vulkanExample->setupWindow();
+	vulkanExample->prepare();
+	vulkanExample->renderLoop();
+	delete(vulkanExample);
+	return 0;
+}
+#elif (defined(VK_USE_PLATFORM_MACOS_MVK) && defined(VK_EXAMPLE_XCODE_GENERATED))
+VulkanExample *vulkanExample;
+int main(const int argc, const char *argv[])
+{
+	@autoreleasepool
+	{
+		for (size_t i = 0; i < argc; i++) { VulkanExample::args.push_back(argv[i]); };
+		vulkanExample = new VulkanExample();
+		vulkanExample->initVulkan();
+		vulkanExample->setupWindow(nullptr);
+		vulkanExample->prepare();
+		vulkanExample->renderLoop();
+		delete(vulkanExample);
+	}
+	return 0;
+}
+#endif
diff --git a/external/Vulkan/examples/variablerateshading/variablerateshading.cpp b/external/Vulkan/examples/variablerateshading/variablerateshading.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f367fce928864935aa5cf026249415959e4c5331
--- /dev/null
+++ b/external/Vulkan/examples/variablerateshading/variablerateshading.cpp
@@ -0,0 +1,438 @@
+/*
+* Vulkan Example - Variable rate shading
+*
+* Copyright (C) 2020 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "variablerateshading.h"
+
+VulkanExample::VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+{
+	title = "Variable rate shading";
+	apiVersion = VK_API_VERSION_1_1;
+	camera.type = Camera::CameraType::firstperson;
+	camera.flipY = true;
+	camera.setPosition(glm::vec3(0.0f, 1.0f, 0.0f));
+	camera.setRotation(glm::vec3(0.0f, -90.0f, 0.0f));
+	camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 256.0f);
+	camera.setRotationSpeed(0.25f);
+	enabledInstanceExtensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
+	enabledDeviceExtensions.push_back(VK_NV_SHADING_RATE_IMAGE_EXTENSION_NAME);
+}
+
+VulkanExample::~VulkanExample()
+{
+	vkDestroyPipeline(device, basePipelines.masked, nullptr);
+	vkDestroyPipeline(device, basePipelines.opaque, nullptr);
+	vkDestroyPipeline(device, shadingRatePipelines.masked, nullptr);
+	vkDestroyPipeline(device, shadingRatePipelines.opaque, nullptr);
+	vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+	vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+	vkDestroyImageView(device, shadingRateImage.view, nullptr);
+	vkDestroyImage(device, shadingRateImage.image, nullptr);
+	vkFreeMemory(device, shadingRateImage.memory, nullptr);
+	shaderData.buffer.destroy();
+}
+
+void VulkanExample::getEnabledFeatures()
+{
+	enabledFeatures.samplerAnisotropy = deviceFeatures.samplerAnisotropy;
+	// POI
+	enabledPhysicalDeviceShadingRateImageFeaturesNV = {};
+	enabledPhysicalDeviceShadingRateImageFeaturesNV.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADING_RATE_IMAGE_FEATURES_NV;
+	enabledPhysicalDeviceShadingRateImageFeaturesNV.shadingRateImage = VK_TRUE;
+	deviceCreatepNextChain = &enabledPhysicalDeviceShadingRateImageFeaturesNV;
+}
+
+/*
+	If the window has been resized, we need to recreate the shading rate image
+*/
+void VulkanExample::handleResize()
+{
+	// Delete allocated resources
+	vkDestroyImageView(device, shadingRateImage.view, nullptr);
+	vkDestroyImage(device, shadingRateImage.image, nullptr);
+	vkFreeMemory(device, shadingRateImage.memory, nullptr);
+	// Recreate image
+	prepareShadingRateImage();
+}
+
+void VulkanExample::buildCommandBuffers()
+{
+	if (resized)
+	{
+		handleResize();
+	}
+
+	VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+	VkClearValue clearValues[2];
+	clearValues[0].color = defaultClearColor;
+	clearValues[0].color = { { 0.25f, 0.25f, 0.25f, 1.0f } };;
+	clearValues[1].depthStencil = { 1.0f, 0 };
+
+	VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+	renderPassBeginInfo.renderPass = renderPass;
+	renderPassBeginInfo.renderArea.offset.x = 0;
+	renderPassBeginInfo.renderArea.offset.y = 0;
+	renderPassBeginInfo.renderArea.extent.width = width;
+	renderPassBeginInfo.renderArea.extent.height = height;
+	renderPassBeginInfo.clearValueCount = 2;
+	renderPassBeginInfo.pClearValues = clearValues;
+
+	const VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+	const VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
+
+	for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+	{
+		renderPassBeginInfo.framebuffer = frameBuffers[i];
+		VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+		vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+		vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+		vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+		vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr);
+
+		// POI: Bind the image that contains the shading rate patterns
+		if (enableShadingRate) {
+			vkCmdBindShadingRateImageNV(drawCmdBuffers[i], shadingRateImage.view, VK_IMAGE_LAYOUT_SHADING_RATE_OPTIMAL_NV);
+		};
+
+		// Render the scene
+		Pipelines& pipelines = enableShadingRate ? shadingRatePipelines : basePipelines;
+		vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.opaque);
+		scene.draw(drawCmdBuffers[i], vkglTF::RenderFlags::BindImages | vkglTF::RenderFlags::RenderOpaqueNodes, pipelineLayout);
+		vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.masked);
+		scene.draw(drawCmdBuffers[i], vkglTF::RenderFlags::BindImages | vkglTF::RenderFlags::RenderAlphaMaskedNodes, pipelineLayout);
+
+		drawUI(drawCmdBuffers[i]);
+		vkCmdEndRenderPass(drawCmdBuffers[i]);
+		VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+	}
+}
+
+void VulkanExample::loadAssets()
+{
+	vkglTF::descriptorBindingFlags = vkglTF::DescriptorBindingFlags::ImageBaseColor | vkglTF::DescriptorBindingFlags::ImageNormalMap;
+	scene.loadFromFile(getAssetPath() + "models/sponza/sponza.gltf", vulkanDevice, queue, vkglTF::FileLoadingFlags::PreTransformVertices);
+}
+
+void VulkanExample::setupDescriptors()
+{
+	// Pool
+	const std::vector<VkDescriptorPoolSize> poolSizes = {
+		vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1),
+	};
+	VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 1);
+	VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+
+	// Descriptor set layout
+	const std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+		vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0),
+	};
+	VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
+	VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout));
+
+	// Pipeline layout
+	const std::vector<VkDescriptorSetLayout> setLayouts = {
+		descriptorSetLayout,
+		vkglTF::descriptorSetLayoutImage,
+	};
+	VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(setLayouts.data(), 2);
+	VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayout));
+
+	// Descriptor set
+	VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1);
+	VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet));
+	std::vector<VkWriteDescriptorSet> writeDescriptorSets = {
+		vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &shaderData.buffer.descriptor),
+	};
+	vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr);
+}
+
+// [POI]
+void VulkanExample::prepareShadingRateImage()
+{
+	// Shading rate image size depends on shading rate texel size
+	// For each texel in the target image, there is a corresponding shading texel size width x height block in the shading rate image
+	VkExtent3D imageExtent{};
+	imageExtent.width = static_cast<uint32_t>(ceil(width / (float)physicalDeviceShadingRateImagePropertiesNV.shadingRateTexelSize.width));
+	imageExtent.height = static_cast<uint32_t>(ceil(height / (float)physicalDeviceShadingRateImagePropertiesNV.shadingRateTexelSize.height));
+	imageExtent.depth = 1;
+
+	VkImageCreateInfo imageCI{};
+	imageCI.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
+	imageCI.imageType = VK_IMAGE_TYPE_2D;
+	imageCI.format = VK_FORMAT_R8_UINT;
+	imageCI.extent = imageExtent;
+	imageCI.mipLevels = 1;
+	imageCI.arrayLayers = 1;
+	imageCI.samples = VK_SAMPLE_COUNT_1_BIT;
+	imageCI.tiling = VK_IMAGE_TILING_OPTIMAL;
+	imageCI.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+	imageCI.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+	imageCI.usage = VK_IMAGE_USAGE_SHADING_RATE_IMAGE_BIT_NV | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
+	VK_CHECK_RESULT(vkCreateImage(device, &imageCI, nullptr, &shadingRateImage.image));
+	VkMemoryRequirements memReqs{};
+	vkGetImageMemoryRequirements(device, shadingRateImage.image, &memReqs);
+	
+	VkDeviceSize bufferSize = imageExtent.width * imageExtent.height * sizeof(uint8_t);
+
+	VkMemoryAllocateInfo memAllloc{};
+	memAllloc.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+	memAllloc.allocationSize = memReqs.size;
+	memAllloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+	VK_CHECK_RESULT(vkAllocateMemory(device, &memAllloc, nullptr, &shadingRateImage.memory));
+	VK_CHECK_RESULT(vkBindImageMemory(device, shadingRateImage.image, shadingRateImage.memory, 0));
+
+	VkImageViewCreateInfo imageViewCI{};
+	imageViewCI.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+	imageViewCI.viewType = VK_IMAGE_VIEW_TYPE_2D;
+	imageViewCI.image = shadingRateImage.image;
+	imageViewCI.format = VK_FORMAT_R8_UINT;
+	imageViewCI.subresourceRange.baseMipLevel = 0;
+	imageViewCI.subresourceRange.levelCount = 1;
+	imageViewCI.subresourceRange.baseArrayLayer = 0;
+	imageViewCI.subresourceRange.layerCount = 1;
+	imageViewCI.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+	VK_CHECK_RESULT(vkCreateImageView(device, &imageViewCI, nullptr, &shadingRateImage.view));
+
+	// Populate with lowest possible shading rate pattern
+	uint8_t val = VK_SHADING_RATE_PALETTE_ENTRY_1_INVOCATION_PER_4X4_PIXELS_NV;
+	uint8_t* shadingRatePatternData = new uint8_t[bufferSize];
+	memset(shadingRatePatternData, val, bufferSize);
+
+	// Create a circular pattern with decreasing sampling rates outwards (max. range, pattern)
+	std::map<float, VkShadingRatePaletteEntryNV> patternLookup = {
+		{ 8.0f, VK_SHADING_RATE_PALETTE_ENTRY_1_INVOCATION_PER_PIXEL_NV },
+		{ 12.0f, VK_SHADING_RATE_PALETTE_ENTRY_1_INVOCATION_PER_2X1_PIXELS_NV },
+		{ 16.0f, VK_SHADING_RATE_PALETTE_ENTRY_1_INVOCATION_PER_1X2_PIXELS_NV },
+		{ 18.0f, VK_SHADING_RATE_PALETTE_ENTRY_1_INVOCATION_PER_2X2_PIXELS_NV },
+		{ 20.0f, VK_SHADING_RATE_PALETTE_ENTRY_1_INVOCATION_PER_4X2_PIXELS_NV },
+		{ 24.0f, VK_SHADING_RATE_PALETTE_ENTRY_1_INVOCATION_PER_2X4_PIXELS_NV }
+	};
+
+	uint8_t* ptrData = shadingRatePatternData;
+	for (uint32_t y = 0; y < imageExtent.height; y++) {
+		for (uint32_t x = 0; x < imageExtent.width; x++) {
+			const float deltaX = (float)imageExtent.width / 2.0f - (float)x;
+			const float deltaY = ((float)imageExtent.height / 2.0f - (float)y) * ((float)width / (float)height);
+			const float dist = std::sqrt(deltaX * deltaX + deltaY * deltaY);
+			for (auto pattern : patternLookup) {
+				if (dist < pattern.first) {
+					*ptrData = pattern.second;
+					break;
+				}
+			}
+			ptrData++;
+		}
+	}
+
+	VkBuffer stagingBuffer;
+	VkDeviceMemory stagingMemory;
+
+	VkBufferCreateInfo bufferCreateInfo{};
+	bufferCreateInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+	bufferCreateInfo.size = bufferSize;
+	bufferCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
+	bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+	VK_CHECK_RESULT(vkCreateBuffer(device, &bufferCreateInfo, nullptr, &stagingBuffer));
+	VkMemoryAllocateInfo memAllocInfo{};
+	memAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+	memReqs = {};
+	vkGetBufferMemoryRequirements(device, stagingBuffer, &memReqs);
+	memAllocInfo.allocationSize = memReqs.size;
+	memAllocInfo.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
+	VK_CHECK_RESULT(vkAllocateMemory(device, &memAllocInfo, nullptr, &stagingMemory));
+	VK_CHECK_RESULT(vkBindBufferMemory(device, stagingBuffer, stagingMemory, 0));
+
+	uint8_t* mapped;
+	VK_CHECK_RESULT(vkMapMemory(device, stagingMemory, 0, memReqs.size, 0, (void**)&mapped));
+	memcpy(mapped, shadingRatePatternData, bufferSize);
+	vkUnmapMemory(device, stagingMemory);
+
+	delete[] shadingRatePatternData;
+
+	// Upload
+	VkCommandBuffer copyCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
+	VkImageSubresourceRange subresourceRange = {};
+	subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+	subresourceRange.levelCount = 1;
+	subresourceRange.layerCount = 1;
+	{
+		VkImageMemoryBarrier imageMemoryBarrier{};
+		imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+		imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+		imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+		imageMemoryBarrier.srcAccessMask = 0;
+		imageMemoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+		imageMemoryBarrier.image = shadingRateImage.image;
+		imageMemoryBarrier.subresourceRange = subresourceRange;
+		vkCmdPipelineBarrier(copyCmd, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &imageMemoryBarrier);
+	}
+	VkBufferImageCopy bufferCopyRegion{};
+	bufferCopyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+	bufferCopyRegion.imageSubresource.layerCount = 1;
+	bufferCopyRegion.imageExtent.width = imageExtent.width;
+	bufferCopyRegion.imageExtent.height = imageExtent.height;
+	bufferCopyRegion.imageExtent.depth = 1;
+	vkCmdCopyBufferToImage(copyCmd, stagingBuffer, shadingRateImage.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &bufferCopyRegion);
+	{
+		VkImageMemoryBarrier imageMemoryBarrier{};
+		imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+		imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+		imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_SHADING_RATE_OPTIMAL_NV;
+		imageMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+		imageMemoryBarrier.dstAccessMask = 0;
+		imageMemoryBarrier.image = shadingRateImage.image;
+		imageMemoryBarrier.subresourceRange = subresourceRange;
+		vkCmdPipelineBarrier(copyCmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, nullptr, 0, nullptr, 1, &imageMemoryBarrier);
+	}
+	vulkanDevice->flushCommandBuffer(copyCmd, queue, true);
+
+	vkFreeMemory(device, stagingMemory, nullptr);
+	vkDestroyBuffer(device, stagingBuffer, nullptr);
+}
+
+void VulkanExample::preparePipelines()
+{
+	VkPipelineInputAssemblyStateCreateInfo inputAssemblyStateCI = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+	VkPipelineRasterizationStateCreateInfo rasterizationStateCI = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
+	VkPipelineColorBlendAttachmentState blendAttachmentStateCI = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+	VkPipelineColorBlendStateCreateInfo colorBlendStateCI = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentStateCI);
+	VkPipelineDepthStencilStateCreateInfo depthStencilStateCI = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+	VkPipelineViewportStateCreateInfo viewportStateCI = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+	VkPipelineMultisampleStateCreateInfo multisampleStateCI = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
+	const std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
+	VkPipelineDynamicStateCreateInfo dynamicStateCI = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables.data(), static_cast<uint32_t>(dynamicStateEnables.size()), 0);
+	std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+
+	VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass, 0);
+	pipelineCI.pInputAssemblyState = &inputAssemblyStateCI;
+	pipelineCI.pRasterizationState = &rasterizationStateCI;
+	pipelineCI.pColorBlendState = &colorBlendStateCI;
+	pipelineCI.pMultisampleState = &multisampleStateCI;
+	pipelineCI.pViewportState = &viewportStateCI;
+	pipelineCI.pDepthStencilState = &depthStencilStateCI;
+	pipelineCI.pDynamicState = &dynamicStateCI;
+	pipelineCI.stageCount = static_cast<uint32_t>(shaderStages.size());
+	pipelineCI.pStages = shaderStages.data();
+	pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::UV, vkglTF::VertexComponent::Color, vkglTF::VertexComponent::Tangent });
+
+	shaderStages[0] = loadShader(getShadersPath() + "variablerateshading/scene.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+	shaderStages[1] = loadShader(getShadersPath() + "variablerateshading/scene.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+
+	// Properties for alpha masked materials will be passed via specialization constants
+	struct SpecializationData {
+		bool alphaMask;
+		float alphaMaskCutoff;
+	} specializationData;
+	specializationData.alphaMask = false;
+	specializationData.alphaMaskCutoff = 0.5f;
+	const std::vector<VkSpecializationMapEntry> specializationMapEntries = {
+		vks::initializers::specializationMapEntry(0, offsetof(SpecializationData, alphaMask), sizeof(SpecializationData::alphaMask)),
+		vks::initializers::specializationMapEntry(1, offsetof(SpecializationData, alphaMaskCutoff), sizeof(SpecializationData::alphaMaskCutoff)),
+	};
+	VkSpecializationInfo specializationInfo = vks::initializers::specializationInfo(specializationMapEntries, sizeof(specializationData), &specializationData);
+	shaderStages[1].pSpecializationInfo = &specializationInfo;
+
+	// Create pipeline without shading rate 
+	VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &basePipelines.opaque));
+	specializationData.alphaMask = true;
+	rasterizationStateCI.cullMode = VK_CULL_MODE_NONE;
+	VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &basePipelines.masked));
+	rasterizationStateCI.cullMode = VK_CULL_MODE_BACK_BIT;
+	specializationData.alphaMask = false;
+
+	// Create pipeline with shading rate enabled
+	// [POI] Possible per-Viewport shading rate palette entries
+	const std::vector<VkShadingRatePaletteEntryNV> shadingRatePaletteEntries = {
+		VK_SHADING_RATE_PALETTE_ENTRY_NO_INVOCATIONS_NV,
+		VK_SHADING_RATE_PALETTE_ENTRY_16_INVOCATIONS_PER_PIXEL_NV,
+		VK_SHADING_RATE_PALETTE_ENTRY_8_INVOCATIONS_PER_PIXEL_NV,
+		VK_SHADING_RATE_PALETTE_ENTRY_4_INVOCATIONS_PER_PIXEL_NV,
+		VK_SHADING_RATE_PALETTE_ENTRY_2_INVOCATIONS_PER_PIXEL_NV,
+		VK_SHADING_RATE_PALETTE_ENTRY_1_INVOCATION_PER_PIXEL_NV,
+		VK_SHADING_RATE_PALETTE_ENTRY_1_INVOCATION_PER_2X1_PIXELS_NV,
+		VK_SHADING_RATE_PALETTE_ENTRY_1_INVOCATION_PER_1X2_PIXELS_NV,
+		VK_SHADING_RATE_PALETTE_ENTRY_1_INVOCATION_PER_2X2_PIXELS_NV,
+		VK_SHADING_RATE_PALETTE_ENTRY_1_INVOCATION_PER_4X2_PIXELS_NV,
+		VK_SHADING_RATE_PALETTE_ENTRY_1_INVOCATION_PER_2X4_PIXELS_NV,
+		VK_SHADING_RATE_PALETTE_ENTRY_1_INVOCATION_PER_4X4_PIXELS_NV,
+	};
+	VkShadingRatePaletteNV shadingRatePalette{};
+	shadingRatePalette.shadingRatePaletteEntryCount = static_cast<uint32_t>(shadingRatePaletteEntries.size());
+	shadingRatePalette.pShadingRatePaletteEntries = shadingRatePaletteEntries.data();
+	VkPipelineViewportShadingRateImageStateCreateInfoNV pipelineViewportShadingRateImageStateCI{};
+	pipelineViewportShadingRateImageStateCI.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_SHADING_RATE_IMAGE_STATE_CREATE_INFO_NV;
+	pipelineViewportShadingRateImageStateCI.shadingRateImageEnable = VK_TRUE;
+	pipelineViewportShadingRateImageStateCI.viewportCount = 1;
+	pipelineViewportShadingRateImageStateCI.pShadingRatePalettes = &shadingRatePalette;
+	viewportStateCI.pNext = &pipelineViewportShadingRateImageStateCI;
+	VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &shadingRatePipelines.opaque));
+	specializationData.alphaMask = true;
+	rasterizationStateCI.cullMode = VK_CULL_MODE_NONE;
+	VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &shadingRatePipelines.masked));
+}
+
+void VulkanExample::prepareUniformBuffers()
+{
+	VK_CHECK_RESULT(vulkanDevice->createBuffer(
+		VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+		VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+		&shaderData.buffer,
+		sizeof(shaderData.values)));
+	VK_CHECK_RESULT(shaderData.buffer.map());
+	updateUniformBuffers();
+}
+
+void VulkanExample::updateUniformBuffers()
+{
+	shaderData.values.projection = camera.matrices.perspective;
+	shaderData.values.view = camera.matrices.view;
+	shaderData.values.viewPos = camera.viewPos;
+	shaderData.values.colorShadingRate = colorShadingRate;
+	memcpy(shaderData.buffer.mapped, &shaderData.values, sizeof(shaderData.values));
+}
+
+void VulkanExample::prepare()
+{
+	VulkanExampleBase::prepare();
+	loadAssets();
+	
+	// [POI]
+	vkCmdBindShadingRateImageNV = reinterpret_cast<PFN_vkCmdBindShadingRateImageNV>(vkGetDeviceProcAddr(device, "vkCmdBindShadingRateImageNV"));
+	physicalDeviceShadingRateImagePropertiesNV.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADING_RATE_IMAGE_PROPERTIES_NV;
+	VkPhysicalDeviceProperties2 deviceProperties2{};
+	deviceProperties2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
+	deviceProperties2.pNext = &physicalDeviceShadingRateImagePropertiesNV;
+	vkGetPhysicalDeviceProperties2(physicalDevice, &deviceProperties2);
+
+	prepareShadingRateImage();
+	prepareUniformBuffers();
+	setupDescriptors();
+	preparePipelines();
+	buildCommandBuffers();
+	prepared = true;
+}
+
+void VulkanExample::render()
+{
+	renderFrame();
+	if (camera.updated) {
+		updateUniformBuffers();
+	}
+}
+
+void VulkanExample::OnUpdateUIOverlay(vks::UIOverlay* overlay)
+{
+	if (overlay->checkBox("Enable shading rate", &enableShadingRate)) {
+		buildCommandBuffers();
+	}
+	if (overlay->checkBox("Color shading rates", &colorShadingRate)) {
+		updateUniformBuffers();
+	}
+}
+
+VULKAN_EXAMPLE_MAIN()
diff --git a/external/Vulkan/examples/variablerateshading/variablerateshading.h b/external/Vulkan/examples/variablerateshading/variablerateshading.h
new file mode 100644
index 0000000000000000000000000000000000000000..8b7095f03e3bfbb22d5c53d2e16987ea34127802
--- /dev/null
+++ b/external/Vulkan/examples/variablerateshading/variablerateshading.h
@@ -0,0 +1,71 @@
+/*
+* Vulkan Example - Variable rate shading
+*
+* Copyright (C) 2020 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkanexamplebase.h"
+#include "VulkanglTFModel.h"
+
+#define ENABLE_VALIDATION false
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	vkglTF::Model scene;
+
+	struct ShadingRateImage {
+		VkImage image;
+		VkDeviceMemory memory;
+		VkImageView view;
+	} shadingRateImage;
+
+	bool enableShadingRate = true;
+	bool colorShadingRate = false;
+
+	struct ShaderData {
+		vks::Buffer buffer;
+		struct Values {
+			glm::mat4 projection;
+			glm::mat4 view;
+			glm::mat4 model = glm::mat4(1.0f);
+			glm::vec4 lightPos = glm::vec4(0.0f, 2.5f, 0.0f, 1.0f);
+			glm::vec4 viewPos;
+			int32_t colorShadingRate;
+		} values;
+	} shaderData;
+
+	struct Pipelines {
+		VkPipeline opaque;
+		VkPipeline masked;
+	};
+
+	Pipelines basePipelines;
+	Pipelines shadingRatePipelines;
+
+	VkPipelineLayout pipelineLayout;
+	VkDescriptorSet descriptorSet;
+	VkDescriptorSetLayout descriptorSetLayout;
+
+	VkPhysicalDeviceShadingRateImagePropertiesNV physicalDeviceShadingRateImagePropertiesNV{};
+	VkPhysicalDeviceShadingRateImageFeaturesNV enabledPhysicalDeviceShadingRateImageFeaturesNV{};
+	PFN_vkCmdBindShadingRateImageNV vkCmdBindShadingRateImageNV;
+
+	VulkanExample();
+	~VulkanExample();
+	virtual void getEnabledFeatures();
+	void handleResize();
+	void buildCommandBuffers();
+	void loadglTFFile(std::string filename);
+	void loadAssets();
+	void prepareShadingRateImage();
+	void setupDescriptors();
+	void preparePipelines();
+	void prepareUniformBuffers();
+	void updateUniformBuffers();
+	void prepare();
+	virtual void render();
+	virtual void OnUpdateUIOverlay(vks::UIOverlay* overlay);
+};
\ No newline at end of file
diff --git a/external/Vulkan/examples/viewportarray/viewportarray.cpp b/external/Vulkan/examples/viewportarray/viewportarray.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..9bbc6b524666f8ae3c7fde3fbe65e5d1f13f8062
--- /dev/null
+++ b/external/Vulkan/examples/viewportarray/viewportarray.cpp
@@ -0,0 +1,327 @@
+/*
+* Vulkan Example - Viewport array with single pass rendering using geometry shaders
+*
+* Copyright (C) 2017 by Sascha Willems - www.saschawillems.de
+*
+* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+*/
+
+#include "vulkanexamplebase.h"
+#include "VulkanglTFModel.h"
+
+#define ENABLE_VALIDATION false
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	vkglTF::Model scene;
+
+	struct UBOGS {
+		glm::mat4 projection[2];
+		glm::mat4 modelview[2];
+		glm::vec4 lightPos = glm::vec4(-2.5f, -3.5f, 0.0f, 1.0f);
+	} uboGS;
+
+	vks::Buffer uniformBufferGS;
+
+	VkPipeline pipeline;
+	VkPipelineLayout pipelineLayout;
+	VkDescriptorSet descriptorSet;
+	VkDescriptorSetLayout descriptorSetLayout;
+
+	// Camera and view properties
+	float eyeSeparation = 0.08f;
+	const float focalLength = 0.5f;
+	const float fov = 90.0f;
+	const float zNear = 0.1f;
+	const float zFar = 256.0f;
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Viewport arrays";
+		camera.type = Camera::CameraType::firstperson;
+		camera.setRotation(glm::vec3(0.0f, 90.0f, 0.0f));
+		camera.setTranslation(glm::vec3(7.0f, 3.2f, 0.0f));
+		camera.setMovementSpeed(5.0f);
+	}
+
+	~VulkanExample()
+	{
+		vkDestroyPipeline(device, pipeline, nullptr);
+
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+
+		uniformBufferGS.destroy();
+	}
+
+	// Enable physical device features required for this example
+	virtual void getEnabledFeatures()
+	{
+		// Geometry shader support is required for this example
+		if (deviceFeatures.geometryShader) {
+			enabledFeatures.geometryShader = VK_TRUE;
+		}
+		else {
+			vks::tools::exitFatal("Selected GPU does not support geometry shaders!", VK_ERROR_FEATURE_NOT_PRESENT);
+		}
+		// Multiple viewports must be supported
+		if (deviceFeatures.multiViewport) {
+			enabledFeatures.multiViewport = VK_TRUE;
+		}
+		else {
+			vks::tools::exitFatal("Selected GPU does not support multi viewports!", VK_ERROR_FEATURE_NOT_PRESENT);
+		}
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		clearValues[0].color = defaultClearColor;
+		clearValues[1].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.offset.x = 0;
+		renderPassBeginInfo.renderArea.offset.y = 0;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		renderPassBeginInfo.clearValueCount = 2;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			// Set target frame buffer
+			renderPassBeginInfo.framebuffer = frameBuffers[i];
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			VkViewport viewports[2];
+			// Left
+			viewports[0] = { 0, 0, (float)width / 2.0f, (float)height, 0.0, 1.0f };
+			// Right
+			viewports[1] = { (float)width / 2.0f, 0, (float)width / 2.0f, (float)height, 0.0, 1.0f };
+
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 2, viewports);
+
+			VkRect2D scissorRects[2] = {
+				vks::initializers::rect2D(width/2, height, 0, 0),
+				vks::initializers::rect2D(width/2, height, width / 2, 0),
+			};
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 2, scissorRects);
+
+			vkCmdSetLineWidth(drawCmdBuffers[i], 1.0f);
+
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr);
+			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
+			scene.draw(drawCmdBuffers[i]);
+
+			drawUI(drawCmdBuffers[i]);
+
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void loadAssets()
+	{
+		scene.loadFromFile(getAssetPath() + "models/sampleroom.gltf", vulkanDevice, queue, vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::PreMultiplyVertexColors | vkglTF::FileLoadingFlags::FlipY);
+	}
+
+	void setupDescriptorPool()
+	{
+		// Example uses two ubos
+		std::vector<VkDescriptorPoolSize> poolSizes = {
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1),
+		};
+
+		VkDescriptorPoolCreateInfo descriptorPoolInfo =
+			vks::initializers::descriptorPoolCreateInfo(static_cast<uint32_t>(poolSizes.size()), poolSizes.data(), 1);
+
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+	}
+
+	void setupDescriptorSetLayout()
+	{
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
+			vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_GEOMETRY_BIT, 0)	// Binding 1: Geometry shader ubo
+		};
+
+		VkDescriptorSetLayoutCreateInfo descriptorLayout =
+			vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
+
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout));
+
+		VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo =
+			vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1);
+
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayout));
+	}
+
+	void setupDescriptorSet()
+	{
+		VkDescriptorSetAllocateInfo allocInfo =
+			vks::initializers::descriptorSetAllocateInfo(
+				descriptorPool,
+				&descriptorSetLayout,
+				1);
+
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet));
+
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets = {
+			vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBufferGS.descriptor),	// Binding 0 :Geometry shader ubo
+		};
+
+		vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr);
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		// We use two viewports
+		VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(2, 2, 0);
+		VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT);
+		std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR, VK_DYNAMIC_STATE_LINE_WIDTH };
+		VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
+		std::array<VkPipelineShaderStageCreateInfo, 3> shaderStages;
+
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass);
+		pipelineCI.pInputAssemblyState = &inputAssemblyState;
+		pipelineCI.pRasterizationState = &rasterizationState;
+		pipelineCI.pColorBlendState = &colorBlendState;
+		pipelineCI.pMultisampleState = &multisampleState;
+		pipelineCI.pViewportState = &viewportState;
+		pipelineCI.pDepthStencilState = &depthStencilState;
+		pipelineCI.pDynamicState = &dynamicState;
+		pipelineCI.stageCount = static_cast<uint32_t>(shaderStages.size());
+		pipelineCI.pStages = shaderStages.data();
+		pipelineCI.renderPass = renderPass;
+		pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::Color});
+
+		shaderStages[0] = loadShader(getShadersPath() + "viewportarray/scene.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "viewportarray/scene.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		// A geometry shader is used to output geometry to multiple viewports in one single pass
+		// See the "invocations" decorator of the layout input in the shader
+		shaderStages[2] = loadShader(getShadersPath() + "viewportarray/multiview.geom.spv", VK_SHADER_STAGE_GEOMETRY_BIT);
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipeline));
+	}
+
+	// Prepare and initialize uniform buffer containing shader uniforms
+	void prepareUniformBuffers()
+	{
+		// Geometry shader uniform buffer block
+		VK_CHECK_RESULT(vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformBufferGS,
+			sizeof(uboGS)));
+
+		// Map persistent
+		VK_CHECK_RESULT(uniformBufferGS.map());
+
+		updateUniformBuffers();
+	}
+
+	void updateUniformBuffers()
+	{
+		// Geometry shader matrices for the two viewports
+		// See http://paulbourke.net/stereographics/stereorender/
+
+		// Calculate some variables
+		float aspectRatio = (float)(width * 0.5f) / (float)height;
+		float wd2 = zNear * tan(glm::radians(fov / 2.0f));
+		float ndfl = zNear / focalLength;
+		float left, right;
+		float top = wd2;
+		float bottom = -wd2;
+
+		glm::vec3 camFront;
+		camFront.x = -cos(glm::radians(camera.rotation.x)) * sin(glm::radians(camera.rotation.y));
+		camFront.y = sin(glm::radians(camera.rotation.x));
+		camFront.z = cos(glm::radians(camera.rotation.x)) * cos(glm::radians(camera.rotation.y));
+		camFront = glm::normalize(camFront);
+		glm::vec3 camRight = glm::normalize(glm::cross(camFront, glm::vec3(0.0f, 1.0f, 0.0f)));
+
+		glm::mat4 rotM = glm::mat4(1.0f);
+		glm::mat4 transM;
+
+		rotM = glm::rotate(rotM, glm::radians(camera.rotation.x), glm::vec3(1.0f, 0.0f, 0.0f));
+		rotM = glm::rotate(rotM, glm::radians(camera.rotation.y), glm::vec3(0.0f, 1.0f, 0.0f));
+		rotM = glm::rotate(rotM, glm::radians(camera.rotation.z), glm::vec3(0.0f, 0.0f, 1.0f));
+
+		// Left eye
+		left = -aspectRatio * wd2 + 0.5f * eyeSeparation * ndfl;
+		right = aspectRatio * wd2 + 0.5f * eyeSeparation * ndfl;
+
+		transM = glm::translate(glm::mat4(1.0f), camera.position - camRight * (eyeSeparation / 2.0f));
+
+		uboGS.projection[0] = glm::frustum(left, right, bottom, top, zNear, zFar);
+		uboGS.modelview[0] = rotM * transM;
+
+		// Right eye
+		left = -aspectRatio * wd2 - 0.5f * eyeSeparation * ndfl;
+		right = aspectRatio * wd2 - 0.5f * eyeSeparation * ndfl;
+
+		transM = glm::translate(glm::mat4(1.0f), camera.position + camRight * (eyeSeparation / 2.0f));
+
+		uboGS.projection[1] = glm::frustum(left, right, bottom, top, zNear, zFar);
+		uboGS.modelview[1] = rotM * transM;
+
+		memcpy(uniformBufferGS.mapped, &uboGS, sizeof(uboGS));
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+		VulkanExampleBase::submitFrame();
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		loadAssets();
+		prepareUniformBuffers();
+		setupDescriptorSetLayout();
+		preparePipelines();
+		setupDescriptorPool();
+		setupDescriptorSet();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+	}
+
+	virtual void viewChanged()
+	{
+		updateUniformBuffers();
+	}
+
+	virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
+	{
+		if (overlay->header("Settings")) {
+			if (overlay->sliderFloat("Eye separation", &eyeSeparation, -1.0f, 1.0f)) {
+				updateUniformBuffers();
+			}
+		}
+	}
+
+};
+
+VULKAN_EXAMPLE_MAIN()
\ No newline at end of file
diff --git a/external/Vulkan/examples/vulkanscene/vulkanscene.cpp b/external/Vulkan/examples/vulkanscene/vulkanscene.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ce1d4417f891e509703221d011a910e6abb00a9e
--- /dev/null
+++ b/external/Vulkan/examples/vulkanscene/vulkanscene.cpp
@@ -0,0 +1,335 @@
+/*
+* Vulkan Demo Scene
+*
+* Don't take this a an example, it's more of a personal playground
+*
+* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
+*
+* Note : Different license than the other examples!
+*
+* This code is licensed under the Mozilla Public License Version 2.0 (http://opensource.org/licenses/MPL-2.0)
+*/
+
+#include "vulkanexamplebase.h"
+#include "VulkanglTFModel.h"
+
+#define ENABLE_VALIDATION false
+
+class VulkanExample : public VulkanExampleBase
+{
+public:
+	struct DemoModel
+	{
+		vkglTF::Model* glTF;
+		VkPipeline *pipeline;
+	};
+	std::vector<DemoModel> demoModels;
+
+	struct {
+		vks::Buffer meshVS;
+	} uniformData;
+
+	struct {
+		glm::mat4 projection;
+		glm::mat4 model;
+		glm::mat4 normal;
+		glm::mat4 view;
+		glm::vec4 lightPos;
+	} uboVS;
+
+	struct
+	{
+		vks::TextureCubeMap skybox;
+	} textures;
+
+	struct {
+		VkPipeline logos;
+		VkPipeline models;
+		VkPipeline skybox;
+	} pipelines;
+
+	VkPipelineLayout pipelineLayout;
+	VkDescriptorSet descriptorSet;
+	VkDescriptorSetLayout descriptorSetLayout;
+
+	glm::vec4 lightPos = glm::vec4(1.0f, 4.0f, 0.0f, 0.0f);
+
+	VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
+	{
+		title = "Vulkan Demo Scene - (c) by Sascha Willems";
+		camera.type = Camera::CameraType::lookat;
+		//camera.flipY = true;
+		camera.setPosition(glm::vec3(0.0f, 0.0f, -3.75f));
+		camera.setRotation(glm::vec3(15.0f, 0.0f, 0.0f));
+		camera.setRotationSpeed(0.5f);
+		camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 256.0f);
+	}
+
+	~VulkanExample()
+	{
+		// Clean up used Vulkan resources
+		// Note : Inherited destructor cleans up resources stored in base class
+		vkDestroyPipeline(device, pipelines.logos, nullptr);
+		vkDestroyPipeline(device, pipelines.models, nullptr);
+		vkDestroyPipeline(device, pipelines.skybox, nullptr);
+
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+
+		for (auto demoModel : demoModels) {
+			delete demoModel.glTF;
+		}
+
+		uniformData.meshVS.destroy();
+		textures.skybox.destroy();
+	}
+
+	void loadAssets()
+	{
+		// Models
+		std::vector<std::string> modelFiles = { "vulkanscenelogos.gltf", "vulkanscenebackground.gltf", "vulkanscenemodels.gltf", "cube.gltf" };
+		std::vector<VkPipeline*> modelPipelines = { &pipelines.logos, &pipelines.models, &pipelines.models, &pipelines.skybox };
+		for (auto i = 0; i < modelFiles.size(); i++) {
+			DemoModel model;
+			const uint32_t glTFLoadingFlags = vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::PreMultiplyVertexColors | vkglTF::FileLoadingFlags::FlipY;
+			model.pipeline = modelPipelines[i];
+			model.glTF = new vkglTF::Model();
+			model.glTF->loadFromFile(getAssetPath() + "models/" + modelFiles[i], vulkanDevice, queue, glTFLoadingFlags);
+			demoModels.push_back(model);
+		}
+		// Textures
+		textures.skybox.loadFromFile(getAssetPath() + "textures/cubemap_vulkan.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue);
+	}
+
+	void buildCommandBuffers()
+	{
+		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+
+		VkClearValue clearValues[2];
+		clearValues[0].color = defaultClearColor;
+		clearValues[1].depthStencil = { 1.0f, 0 };
+
+		VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
+		renderPassBeginInfo.renderPass = renderPass;
+		renderPassBeginInfo.renderArea.offset.x = 0;
+		renderPassBeginInfo.renderArea.offset.y = 0;
+		renderPassBeginInfo.renderArea.extent.width = width;
+		renderPassBeginInfo.renderArea.extent.height = height;
+		renderPassBeginInfo.clearValueCount = 2;
+		renderPassBeginInfo.pClearValues = clearValues;
+
+		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
+		{
+			renderPassBeginInfo.framebuffer = frameBuffers[i];
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
+
+			vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
+			vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
+
+			VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
+			vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
+
+			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL);
+
+			for (auto model : demoModels) {
+				vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, *model.pipeline);
+				model.glTF->draw(drawCmdBuffers[i]);
+			}
+
+			drawUI(drawCmdBuffers[i]);
+
+			vkCmdEndRenderPass(drawCmdBuffers[i]);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
+		}
+	}
+
+	void setupDescriptorPool()
+	{
+		// Example uses one ubo and one image sampler
+		std::vector<VkDescriptorPoolSize> poolSizes =
+		{
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2),
+			vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1)
+		};
+
+		VkDescriptorPoolCreateInfo descriptorPoolInfo =
+			vks::initializers::descriptorPoolCreateInfo(
+				poolSizes.size(),
+				poolSizes.data(),
+				2);
+
+		VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
+	}
+
+	void setupDescriptorSetLayout()
+	{
+		std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings =
+		{
+			// Binding 0 : Vertex shader uniform buffer
+			vks::initializers::descriptorSetLayoutBinding(
+				VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+				VK_SHADER_STAGE_VERTEX_BIT,
+				0),
+			// Binding 1 : Fragment shader color map image sampler
+			vks::initializers::descriptorSetLayoutBinding(
+				VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+				VK_SHADER_STAGE_FRAGMENT_BIT,
+				1)
+		};
+
+		VkDescriptorSetLayoutCreateInfo descriptorLayout =
+			vks::initializers::descriptorSetLayoutCreateInfo(
+				setLayoutBindings.data(),
+				setLayoutBindings.size());
+
+		VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout));
+
+		VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo =
+			vks::initializers::pipelineLayoutCreateInfo(
+				&descriptorSetLayout,
+				1);
+
+		VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayout));
+	}
+
+	void setupDescriptorSet()
+	{
+		VkDescriptorSetAllocateInfo allocInfo =
+			vks::initializers::descriptorSetAllocateInfo(
+				descriptorPool,
+				&descriptorSetLayout,
+				1);
+
+		VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet));
+
+		// Cube map image descriptor
+		VkDescriptorImageInfo texDescriptorCubeMap =
+			vks::initializers::descriptorImageInfo(
+				textures.skybox.sampler,
+				textures.skybox.view,
+				VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+
+		std::vector<VkWriteDescriptorSet> writeDescriptorSets =
+		{
+			// Binding 0 : Vertex shader uniform buffer
+			vks::initializers::writeDescriptorSet(
+				descriptorSet,
+				VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+				0,
+				&uniformData.meshVS.descriptor),
+			// Binding 1 : Fragment shader image sampler
+			vks::initializers::writeDescriptorSet(
+				descriptorSet,
+				VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
+				1,
+				&texDescriptorCubeMap)
+		};
+
+		vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, NULL);
+	}
+
+	void preparePipelines()
+	{
+		VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
+		VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE,0);
+		VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
+		VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
+		VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
+		VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
+		VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
+		std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
+		VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables, 0);
+		std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
+		VkGraphicsPipelineCreateInfo pipelineCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass, 0);
+		pipelineCI.pInputAssemblyState = &inputAssemblyState;
+		pipelineCI.pRasterizationState = &rasterizationState;
+		pipelineCI.pColorBlendState = &colorBlendState;
+		pipelineCI.pMultisampleState = &multisampleState;
+		pipelineCI.pViewportState = &viewportState;
+		pipelineCI.pDepthStencilState = &depthStencilState;
+		pipelineCI.pDynamicState = &dynamicState;
+		pipelineCI.stageCount = shaderStages.size();
+		pipelineCI.pStages = shaderStages.data();
+		pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::UV, vkglTF::VertexComponent::Color });;
+
+		// Default mesh rendering pipeline
+		shaderStages[0] = loadShader(getShadersPath() + "vulkanscene/mesh.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "vulkanscene/mesh.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.models));
+
+		// Pipeline for the logos
+		shaderStages[0] = loadShader(getShadersPath() + "vulkanscene/logo.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "vulkanscene/logo.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.logos));
+
+		// Pipeline for the sky sphere
+		rasterizationState.cullMode = VK_CULL_MODE_FRONT_BIT;
+		depthStencilState.depthWriteEnable = VK_FALSE;
+		shaderStages[0] = loadShader(getShadersPath() + "vulkanscene/skybox.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
+		shaderStages[1] = loadShader(getShadersPath() + "vulkanscene/skybox.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
+		VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.skybox));
+	}
+
+	// Prepare and initialize uniform buffer containing shader uniforms
+	void prepareUniformBuffers()
+	{
+		vulkanDevice->createBuffer(
+			VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+			VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+			&uniformData.meshVS,
+			sizeof(uboVS));
+		VK_CHECK_RESULT(uniformData.meshVS.map());
+		updateUniformBuffers();
+	}
+
+	void updateUniformBuffers()
+	{
+		uboVS.projection = camera.matrices.perspective;
+		uboVS.view = camera.matrices.view;
+		uboVS.model = glm::mat4(1.0f);
+		uboVS.normal = glm::inverseTranspose(uboVS.view * uboVS.model);
+		uboVS.lightPos = lightPos;
+		memcpy(uniformData.meshVS.mapped, &uboVS, sizeof(uboVS));
+	}
+
+	void draw()
+	{
+		VulkanExampleBase::prepareFrame();
+		submitInfo.commandBufferCount = 1;
+		submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
+		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
+		VulkanExampleBase::submitFrame();
+	}
+
+	void prepare()
+	{
+		VulkanExampleBase::prepare();
+		loadAssets();
+		prepareUniformBuffers();
+		setupDescriptorSetLayout();
+		preparePipelines();
+		setupDescriptorPool();
+		setupDescriptorSet();
+		buildCommandBuffers();
+		prepared = true;
+	}
+
+	virtual void render()
+	{
+		if (!prepared)
+			return;
+		draw();
+	}
+
+	virtual void viewChanged()
+	{
+		updateUniformBuffers();
+	}
+
+};
+
+VULKAN_EXAMPLE_MAIN()
\ No newline at end of file
diff --git a/external/Vulkan/external/glm b/external/Vulkan/external/glm
new file mode 160000
index 0000000000000000000000000000000000000000..1ad55c5016339b83b7eec98c31007e0aee57d2bf
--- /dev/null
+++ b/external/Vulkan/external/glm
@@ -0,0 +1 @@
+Subproject commit 1ad55c5016339b83b7eec98c31007e0aee57d2bf
diff --git a/external/Vulkan/external/imgui/LICENSE.txt b/external/Vulkan/external/imgui/LICENSE.txt
new file mode 100644
index 0000000000000000000000000000000000000000..21b6ee7e2a45c29709bacb429dfecebee270839d
--- /dev/null
+++ b/external/Vulkan/external/imgui/LICENSE.txt
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014-2018 Omar Cornut
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/external/Vulkan/external/imgui/imconfig.h b/external/Vulkan/external/imgui/imconfig.h
new file mode 100644
index 0000000000000000000000000000000000000000..50b7f471ce35aec1c7dfaf957ca69a2374c391c3
--- /dev/null
+++ b/external/Vulkan/external/imgui/imconfig.h
@@ -0,0 +1,72 @@
+//-----------------------------------------------------------------------------
+// COMPILE-TIME OPTIONS FOR DEAR IMGUI
+// Runtime options (clipboard callbacks, enabling various features, etc.) can generally be set via the ImGuiIO structure.
+// You can use ImGui::SetAllocatorFunctions() before calling ImGui::CreateContext() to rewire memory allocation functions.
+//-----------------------------------------------------------------------------
+// A) You may edit imconfig.h (and not overwrite it when updating imgui, or maintain a patch/branch with your modifications to imconfig.h)
+// B) or add configuration directives in your own file and compile with #define IMGUI_USER_CONFIG "myfilename.h"
+// If you do so you need to make sure that configuration settings are defined consistently _everywhere_ dear imgui is used, which include
+// the imgui*.cpp files but also _any_ of your code that uses imgui. This is because some compile-time options have an affect on data structures.
+// Defining those options in imconfig.h will ensure every compilation unit gets to see the same data structure layouts.
+// Call IMGUI_CHECKVERSION() from your .cpp files to verify that the data structures your files are using are matching the ones imgui.cpp is using.
+//-----------------------------------------------------------------------------
+
+#pragma once
+
+//---- Define assertion handler. Defaults to calling assert().
+//#define IM_ASSERT(_EXPR)  MyAssert(_EXPR)
+//#define IM_ASSERT(_EXPR)  ((void)(_EXPR))     // Disable asserts
+
+//---- Define attributes of all API symbols declarations, e.g. for DLL under Windows.
+//#define IMGUI_API __declspec( dllexport )
+//#define IMGUI_API __declspec( dllimport )
+
+//---- Don't define obsolete functions/enums names. Consider enabling from time to time after updating to avoid using soon-to-be obsolete function/names.
+//#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS
+
+//---- Don't implement demo windows functionality (ShowDemoWindow()/ShowStyleEditor()/ShowUserGuide() methods will be empty)
+//---- It is very strongly recommended to NOT disable the demo windows during development. Please read the comments in imgui_demo.cpp.
+//#define IMGUI_DISABLE_DEMO_WINDOWS
+
+//---- Don't implement some functions to reduce linkage requirements.
+//#define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS   // [Win32] Don't implement default clipboard handler. Won't use and link with OpenClipboard/GetClipboardData/CloseClipboard etc.
+//#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS         // [Win32] Don't implement default IME handler. Won't use and link with ImmGetContext/ImmSetCompositionWindow.
+//#define IMGUI_DISABLE_FORMAT_STRING_FUNCTIONS             // Don't implement ImFormatString/ImFormatStringV so you can implement them yourself if you don't want to link with vsnprintf.
+//#define IMGUI_DISABLE_MATH_FUNCTIONS                      // Don't implement ImFabs/ImSqrt/ImPow/ImFmod/ImCos/ImSin/ImAcos/ImAtan2 wrapper so you can implement them yourself. Declare your prototypes in imconfig.h.
+//#define IMGUI_DISABLE_DEFAULT_ALLOCATORS                  // Don't implement default allocators calling malloc()/free() to avoid linking with them. You will need to call ImGui::SetAllocatorFunctions().
+
+//---- Include imgui_user.h at the end of imgui.h as a convenience
+//#define IMGUI_INCLUDE_IMGUI_USER_H
+
+//---- Pack colors to BGRA8 instead of RGBA8 (to avoid converting from one to another)
+//#define IMGUI_USE_BGRA_PACKED_COLOR
+
+//---- Avoid multiple STB libraries implementations, or redefine path/filenames to prioritize another version
+// By default the embedded implementations are declared static and not available outside of imgui cpp files.
+//#define IMGUI_STB_TRUETYPE_FILENAME   "my_folder/stb_truetype.h"
+//#define IMGUI_STB_RECT_PACK_FILENAME  "my_folder/stb_rect_pack.h"
+//#define IMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION
+//#define IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION
+
+//---- Define constructor and implicit cast operators to convert back<>forth between your math types and ImVec2/ImVec4.
+// This will be inlined as part of ImVec2 and ImVec4 class declarations.
+/*
+#define IM_VEC2_CLASS_EXTRA                                                 \
+        ImVec2(const MyVec2& f) { x = f.x; y = f.y; }                       \
+        operator MyVec2() const { return MyVec2(x,y); }
+
+#define IM_VEC4_CLASS_EXTRA                                                 \
+        ImVec4(const MyVec4& f) { x = f.x; y = f.y; z = f.z; w = f.w; }     \
+        operator MyVec4() const { return MyVec4(x,y,z,w); }
+*/
+
+//---- Use 32-bit vertex indices (default is 16-bit) to allow meshes with more than 64K vertices. Render function needs to support it.
+//#define ImDrawIdx unsigned int
+
+//---- Tip: You can add extra functions within the ImGui:: namespace, here or in your own headers files.
+/*
+namespace ImGui
+{
+    void MyFunction(const char* name, const MyMatrix44& v);
+}
+*/
diff --git a/external/Vulkan/external/imgui/imgui.cpp b/external/Vulkan/external/imgui/imgui.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e6a9bc1afe701da70d2cf8561bcd256c15c66f98
--- /dev/null
+++ b/external/Vulkan/external/imgui/imgui.cpp
@@ -0,0 +1,8970 @@
+// dear imgui, v1.65
+// (main code and documentation)
+
+// Call and read ImGui::ShowDemoWindow() in imgui_demo.cpp for demo code.
+// Newcomers, read 'Programmer guide' below for notes on how to setup Dear ImGui in your codebase.
+// Get latest version at https://github.com/ocornut/imgui
+// Releases change-log at https://github.com/ocornut/imgui/releases
+// Technical Support for Getting Started https://discourse.dearimgui.org/c/getting-started
+// Gallery (please post your screenshots/video there!): https://github.com/ocornut/imgui/issues/1269
+// Developed by Omar Cornut and every direct or indirect contributors to the GitHub.
+// This library is free but I need your support to sustain development and maintenance.
+// If you work for a company, please consider financial support, see README. For individuals: https://www.patreon.com/imgui
+
+// It is recommended that you don't modify imgui.cpp! It will become difficult for you to update the library.
+// Note that 'ImGui::' being a namespace, you can add functions into the namespace from your own source files, without
+// modifying imgui.h or imgui.cpp. You may include imgui_internal.h to access internal data structures, but it doesn't
+// come with any guarantee of forward compatibility. Discussing your changes on the GitHub Issue Tracker may lead you
+// to a better solution or official support for them.
+
+/*
+
+Index of this file:
+
+DOCUMENTATION
+
+- MISSION STATEMENT
+- END-USER GUIDE
+- PROGRAMMER GUIDE (read me!)
+  - Read first
+  - How to update to a newer version of Dear ImGui
+  - Getting started with integrating Dear ImGui in your code/engine
+  - This is how a simple application may look like (2 variations)
+  - This is how a simple rendering function may look like
+  - Using gamepad/keyboard navigation controls
+- API BREAKING CHANGES (read me when you update!)
+- FREQUENTLY ASKED QUESTIONS (FAQ), TIPS
+  - How can I tell whether to dispatch mouse/keyboard to imgui or to my application?
+  - How can I display an image? What is ImTextureID, how does it works?
+  - How can I have multiple widgets with the same label or without a label? A primer on labels and the ID Stack.
+  - How can I use my own math types instead of ImVec2/ImVec4? 
+  - How can I load a different font than the default?
+  - How can I easily use icons in my application?
+  - How can I load multiple fonts?
+  - How can I display and input non-latin characters such as Chinese, Japanese, Korean, Cyrillic?
+  - How can I use the drawing facilities without an ImGui window? (using ImDrawList API)
+  - I integrated Dear ImGui in my engine and the text or lines are blurry..
+  - I integrated Dear ImGui in my engine and some elements are clipping or disappearing when I move windows around..
+  - How can I help?
+
+CODE 
+(search for "[SECTION]" in the code to find them)
+
+// [SECTION] FORWARD DECLARATIONS
+// [SECTION] CONTEXT AND MEMORY ALLOCATORS
+// [SECTION] MAIN USER FACING STRUCTURES (ImGuiStyle, ImGuiIO)
+// [SECTION] MISC HELPER/UTILITIES (Maths, String, Format, Hash, File functions)
+// [SECTION] MISC HELPER/UTILITIES (ImText* functions)
+// [SECTION] MISC HELPER/UTILITIES (Color functions)
+// [SECTION] ImGuiStorage
+// [SECTION] ImGuiTextFilter
+// [SECTION] ImGuiTextBuffer
+// [SECTION] ImGuiListClipper
+// [SECTION] RENDER HELPERS
+// [SECTION] MAIN CODE (most of the code! lots of stuff, needs tidying up!)
+// [SECTION] TOOLTIPS
+// [SECTION] POPUPS
+// [SECTION] KEYBOARD/GAMEPAD NAVIGATION
+// [SECTION] COLUMNS
+// [SECTION] DRAG AND DROP
+// [SECTION] LOGGING/CAPTURING
+// [SECTION] SETTINGS
+// [SECTION] PLATFORM DEPENDENT HELPERS
+// [SECTION] METRICS/DEBUG WINDOW
+
+*/
+
+//-----------------------------------------------------------------------------
+// DOCUMENTATION
+//-----------------------------------------------------------------------------
+
+/*
+
+ MISSION STATEMENT
+ =================
+
+ - Easy to use to create code-driven and data-driven tools
+ - Easy to use to create ad hoc short-lived tools and long-lived, more elaborate tools
+ - Easy to hack and improve
+ - Minimize screen real-estate usage
+ - Minimize setup and maintenance
+ - Minimize state storage on user side
+ - Portable, minimize dependencies, run on target (consoles, phones, etc.)
+ - Efficient runtime and memory consumption (NB- we do allocate when "growing" content e.g. creating a window,
+   opening a tree node for the first time, etc. but a typical frame should not allocate anything)
+
+ Designed for developers and content-creators, not the typical end-user! Some of the weaknesses includes:
+ - Doesn't look fancy, doesn't animate
+ - Limited layout features, intricate layouts are typically crafted in code
+
+
+ END-USER GUIDE
+ ==============
+
+ - Double-click on title bar to collapse window.
+ - Click upper right corner to close a window, available when 'bool* p_open' is passed to ImGui::Begin().
+ - Click and drag on lower right corner to resize window (double-click to auto fit window to its contents).
+ - Click and drag on any empty space to move window.
+ - TAB/SHIFT+TAB to cycle through keyboard editable fields.
+ - CTRL+Click on a slider or drag box to input value as text.
+ - Use mouse wheel to scroll.
+ - Text editor:
+   - Hold SHIFT or use mouse to select text.
+   - CTRL+Left/Right to word jump.
+   - CTRL+Shift+Left/Right to select words.
+   - CTRL+A our Double-Click to select all.
+   - CTRL+X,CTRL+C,CTRL+V to use OS clipboard/
+   - CTRL+Z,CTRL+Y to undo/redo.
+   - ESCAPE to revert text to its original value.
+   - You can apply arithmetic operators +,*,/ on numerical values. Use +- to subtract (because - would set a negative value!)
+   - Controls are automatically adjusted for OSX to match standard OSX text editing operations.
+ - General Keyboard controls: enable with ImGuiConfigFlags_NavEnableKeyboard.
+ - General Gamepad controls: enable with ImGuiConfigFlags_NavEnableGamepad. See suggested mappings in imgui.h ImGuiNavInput_ + download PNG/PSD at http://goo.gl/9LgVZW
+
+
+ PROGRAMMER GUIDE
+ ================
+
+ READ FIRST
+
+ - Read the FAQ below this section!
+ - Your code creates the UI, if your code doesn't run the UI is gone! The UI can be highly dynamic, there are no construction
+   or destruction steps, less data retention on your side, less state duplication, less state synchronization, less bugs.
+ - Call and read ImGui::ShowDemoWindow() for demo code demonstrating most features.
+ - You can learn about immediate-mode gui principles at http://www.johno.se/book/imgui.html or watch http://mollyrocket.com/861
+
+ HOW TO UPDATE TO A NEWER VERSION OF DEAR IMGUI
+
+ - Overwrite all the sources files except for imconfig.h (if you have made modification to your copy of imconfig.h)
+ - Read the "API BREAKING CHANGES" section (below). This is where we list occasional API breaking changes.
+   If a function/type has been renamed / or marked obsolete, try to fix the name in your code before it is permanently removed
+   from the public API. If you have a problem with a missing function/symbols, search for its name in the code, there will
+   likely be a comment about it. Please report any issue to the GitHub page!
+ - Try to keep your copy of dear imgui reasonably up to date.
+
+ GETTING STARTED WITH INTEGRATING DEAR IMGUI IN YOUR CODE/ENGINE
+
+ - Run and study the examples and demo to get acquainted with the library.
+ - Add the Dear ImGui source files to your projects or using your preferred build system.
+   It is recommended you build the .cpp files as part of your project and not as a library.
+ - You can later customize the imconfig.h file to tweak some compilation time behavior, such as integrating imgui types with your own maths types.
+ - You may be able to grab and copy a ready made imgui_impl_*** file from the examples/ folder.
+ - When using Dear ImGui, your programming IDE is your friend: follow the declaration of variables, functions and types to find comments about them.
+ - Dear ImGui never touches or knows about your GPU state. The only function that knows about GPU is the draw function that you provide.
+   Effectively it means you can create widgets at any time in your code, regardless of considerations of being in "update" vs "render"
+   phases of your own application. All rendering informatioe are stored into command-lists that you will retrieve after calling ImGui::Render().
+ - Refer to the bindings and demo applications in the examples/ folder for instruction on how to setup your code.
+
+ THIS IS HOW A SIMPLE APPLICATION MAY LOOK LIKE
+ EXHIBIT 1: USING THE EXAMPLE BINDINGS (imgui_impl_XXX.cpp files from the examples/ folder)
+
+     // Application init: create a dear imgui context, setup some options, load fonts
+     ImGui::CreateContext();
+     ImGuiIO& io = ImGui::GetIO();
+     // TODO: Set optional io.ConfigFlags values, e.g. 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard' to enable keyboard controls
+     // TODO: Fill optional fields of the io structure later.
+     // TODO: Load TTF/OTF fonts if you don't want to use the default font.
+
+     // Initialize helper Platform and Renderer bindings (here we are using imgui_impl_win32 and imgui_impl_dx11)
+     ImGui_ImplWin32_Init(hwnd);
+     ImGui_ImplDX11_Init(g_pd3dDevice, g_pd3dDeviceContext);
+
+     // Application main loop
+     while (true)
+     {
+         // Feed inputs to dear imgui, start new frame
+         ImGui_ImplDX11_NewFrame();
+         ImGui_ImplWin32_NewFrame();
+         ImGui::NewFrame();
+
+         // Any application code here
+         ImGui::Text("Hello, world!");
+
+         // Render dear imgui into screen
+         ImGui::Render();
+         ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());
+         g_pSwapChain->Present(1, 0);
+     }
+     
+     // Shutdown
+     ImGui_ImplDX11_Shutdown();
+     ImGui_ImplWin32_Shutdown();
+     ImGui::DestroyContext();
+
+ THIS IS HOW A SIMPLE APPLICATION MAY LOOK LIKE
+ EXHIBIT 2: IMPLEMENTING CUSTOM BINDING / CUSTOM ENGINE
+
+     // Application init: create a dear imgui context, setup some options, load fonts
+     ImGui::CreateContext();
+     ImGuiIO& io = ImGui::GetIO();
+     // TODO: Set optional io.ConfigFlags values, e.g. 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard' to enable keyboard controls
+     // TODO: Fill optional fields of the io structure later.
+     // TODO: Load TTF/OTF fonts if you don't want to use the default font.
+
+     // Build and load the texture atlas into a texture
+     // (In the examples/ app this is usually done within the ImGui_ImplXXX_Init() function from one of the demo Renderer)
+     int width, height;
+     unsigned char* pixels = NULL;
+     io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
+
+     // At this point you've got the texture data and you need to upload that your your graphic system:
+     // After we have created the texture, store its pointer/identifier (_in whichever format your engine uses_) in 'io.Fonts->TexID'.
+     // This will be passed back to your via the renderer. Basically ImTextureID == void*. Read FAQ below for details about ImTextureID.
+     MyTexture* texture = MyEngine::CreateTextureFromMemoryPixels(pixels, width, height, TEXTURE_TYPE_RGBA32)
+     io.Fonts->TexID = (void*)texture;
+
+     // Application main loop
+     while (true)
+     {
+        // Setup low-level inputs, e.g. on Win32: calling GetKeyboardState(), or write to those fields from your Windows message handlers, etc.
+        // (In the examples/ app this is usually done within the ImGui_ImplXXX_NewFrame() function from one of the demo Platform bindings)
+        io.DeltaTime = 1.0f/60.0f;              // set the time elapsed since the previous frame (in seconds)
+        io.DisplaySize.x = 1920.0f;             // set the current display width
+        io.DisplaySize.y = 1280.0f;             // set the current display height here
+        io.MousePos = my_mouse_pos;             // set the mouse position
+        io.MouseDown[0] = my_mouse_buttons[0];  // set the mouse button states
+        io.MouseDown[1] = my_mouse_buttons[1];
+
+        // Call NewFrame(), after this point you can use ImGui::* functions anytime
+        // (So you want to try calling NewFrame() as early as you can in your mainloop to be able to use imgui everywhere)
+        ImGui::NewFrame();
+
+        // Most of your application code here
+        ImGui::Text("Hello, world!");
+        MyGameUpdate(); // may use any ImGui functions, e.g. ImGui::Begin("My window"); ImGui::Text("Hello, world!"); ImGui::End();
+        MyGameRender(); // may use any ImGui functions as well!
+
+        // Render imgui, swap buffers
+        // (You want to try calling EndFrame/Render as late as you can, to be able to use imgui in your own game rendering code)
+        ImGui::EndFrame();
+        ImGui::Render();
+        ImDrawData* draw_data = ImGui::GetDrawData();
+        MyImGuiRenderFunction(draw_data);
+        SwapBuffers();
+     }
+
+     // Shutdown
+     ImGui::DestroyContext();
+
+ THIS HOW A SIMPLE RENDERING FUNCTION MAY LOOK LIKE
+
+    void void MyImGuiRenderFunction(ImDrawData* draw_data)
+    {
+       // TODO: Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled
+       // TODO: Setup viewport using draw_data->DisplaySize
+       // TODO: Setup orthographic projection matrix cover draw_data->DisplayPos to draw_data->DisplayPos + draw_data->DisplaySize
+       // TODO: Setup shader: vertex { float2 pos, float2 uv, u32 color }, fragment shader sample color from 1 texture, multiply by vertex color.
+       for (int n = 0; n < draw_data->CmdListsCount; n++)
+       {
+          const ImDrawVert* vtx_buffer = cmd_list->VtxBuffer.Data;  // vertex buffer generated by ImGui
+          const ImDrawIdx* idx_buffer = cmd_list->IdxBuffer.Data;   // index buffer generated by ImGui
+          for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)
+          {
+             const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
+             if (pcmd->UserCallback)
+             {
+                 pcmd->UserCallback(cmd_list, pcmd);
+             }
+             else
+             {
+                 // The texture for the draw call is specified by pcmd->TextureId.
+                 // The vast majority of draw calls with use the imgui texture atlas, which value you have set yourself during initialization.
+                 MyEngineBindTexture(pcmd->TextureId);
+
+                 // We are using scissoring to clip some objects. All low-level graphics API should supports it.
+                 // - If your engine doesn't support scissoring yet, you may ignore this at first. You will get some small glitches
+                 //   (some elements visible outside their bounds) but you can fix that once everywhere else works!
+                 // - Clipping coordinates are provided in imgui coordinates space (from draw_data->DisplayPos to draw_data->DisplayPos + draw_data->DisplaySize)
+                 //   In a single viewport application, draw_data->DisplayPos will always be (0,0) and draw_data->DisplaySize will always be == io.DisplaySize.
+                 //   However, in the interest of supporting multi-viewport applications in the future, always subtract draw_data->DisplayPos from
+                 //   clipping bounds to convert them to your viewport space.
+                 // - Note that pcmd->ClipRect contains Min+Max bounds. Some graphics API may use Min+Max, other may use Min+Size (size being Max-Min)
+                 ImVec2 pos = draw_data->DisplayPos;
+                 MyEngineScissor((int)(pcmd->ClipRect.x - pos.x), (int)(pcmd->ClipRect.y - pos.y), (int)(pcmd->ClipRect.z - pos.x), (int)(pcmd->ClipRect.w - pos.y));
+
+                 // Render 'pcmd->ElemCount/3' indexed triangles.
+                 // By default the indices ImDrawIdx are 16-bits, you can change them to 32-bits if your engine doesn't support 16-bits indices.
+                 MyEngineDrawIndexedTriangles(pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer, vtx_buffer);
+             }
+             idx_buffer += pcmd->ElemCount;
+          }
+       }
+    }
+
+ - The examples/ folders contains many functional implementation of the pseudo-code above.
+ - When calling NewFrame(), the 'io.WantCaptureMouse', 'io.WantCaptureKeyboard' and 'io.WantTextInput' flags are updated.
+   They tell you if ImGui intends to use your inputs. When a flag is set you want to hide the corresponding inputs from the rest of your application.
+   In both cases you need to pass on the inputs to imgui. Read the FAQ below for more information about those flags.
+ - Please read the FAQ above. Amusingly, it is called a FAQ because people frequently have the same issues!
+
+ USING GAMEPAD/KEYBOARD NAVIGATION CONTROLS
+
+ - The gamepad/keyboard navigation is fairly functional and keeps being improved. 
+ - You can ask questions and report issues at https://github.com/ocornut/imgui/issues/787
+ - The initial focus was to support game controllers, but keyboard is becoming increasingly and decently usable.
+ - Gamepad:
+    - Set io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad to enable.
+    - Backend: Set io.BackendFlags |= ImGuiBackendFlags_HasGamepad + fill the io.NavInputs[] fields before calling NewFrame().
+      Note that io.NavInputs[] is cleared by EndFrame().
+    - See 'enum ImGuiNavInput_' in imgui.h for a description of inputs. For each entry of io.NavInputs[], set the following values:
+         0.0f= not held. 1.0f= fully held. Pass intermediate 0.0f..1.0f values for analog triggers/sticks.
+    - We uses a simple >0.0f test for activation testing, and won't attempt to test for a dead-zone.
+      Your code will probably need to transform your raw inputs (such as e.g. remapping your 0.2..0.9 raw input range to 0.0..1.0 imgui range, etc.).
+    - You can download PNG/PSD files depicting the gamepad controls for common controllers at: http://goo.gl/9LgVZW.
+    - If you need to share inputs between your game and the imgui parts, the easiest approach is to go all-or-nothing, with a buttons combo
+      to toggle the target. Please reach out if you think the game vs navigation input sharing could be improved.
+ - Keyboard:
+    - Set io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard to enable.
+      NewFrame() will automatically fill io.NavInputs[] based on your io.KeysDown[] + io.KeyMap[] arrays.
+    - When keyboard navigation is active (io.NavActive + ImGuiConfigFlags_NavEnableKeyboard), the io.WantCaptureKeyboard flag
+      will be set. For more advanced uses, you may want to read from:
+       - io.NavActive: true when a window is focused and it doesn't have the ImGuiWindowFlags_NoNavInputs flag set.
+       - io.NavVisible: true when the navigation cursor is visible (and usually goes false when mouse is used).
+       - or query focus information with e.g. IsWindowFocused(ImGuiFocusedFlags_AnyWindow), IsItemFocused() etc. functions.
+      Please reach out if you think the game vs navigation input sharing could be improved.
+ - Mouse:
+    - PS4 users: Consider emulating a mouse cursor with DualShock4 touch pad or a spare analog stick as a mouse-emulation fallback.
+    - Consoles/Tablet/Phone users: Consider using a Synergy 1.x server (on your PC) + uSynergy.c (on your console/tablet/phone app) to share your PC mouse/keyboard.
+    - On a TV/console system where readability may be lower or mouse inputs may be awkward, you may want to set the ImGuiConfigFlags_NavEnableSetMousePos flag.
+      Enabling ImGuiConfigFlags_NavEnableSetMousePos + ImGuiBackendFlags_HasSetMousePos instructs dear imgui to move your mouse cursor along with navigation movements.
+      When enabled, the NewFrame() function may alter 'io.MousePos' and set 'io.WantSetMousePos' to notify you that it wants the mouse cursor to be moved.
+      When that happens your back-end NEEDS to move the OS or underlying mouse cursor on the next frame. Some of the binding in examples/ do that.
+      (If you set the NavEnableSetMousePos flag but don't honor 'io.WantSetMousePos' properly, imgui will misbehave as it will see your mouse as moving back and forth!)
+      (In a setup when you may not have easy control over the mouse cursor, e.g. uSynergy.c doesn't expose moving remote mouse cursor, you may want
+       to set a boolean to ignore your other external mouse positions until the external source is moved again.)
+
+
+ API BREAKING CHANGES
+ ====================
+
+ Occasionally introducing changes that are breaking the API. We try to make the breakage minor and easy to fix.
+ Below is a change-log of API breaking changes only. If you are using one of the functions listed, expect to have to fix some code.
+ When you are not sure about a old symbol or function name, try using the Search/Find function of your IDE to look for comments or references in all imgui files.
+ You can read releases logs https://github.com/ocornut/imgui/releases for more details.
+
+ - 2018/09/06 (1.65) - renamed stb_truetype.h to imstb_truetype.h, stb_textedit.h to imstb_textedit.h, and stb_rect_pack.h to imstb_rectpack.h. 
+                       If you were conveniently using the imgui copy of those STB headers in your project you will have to update your include paths.
+ - 2018/09/05 (1.65) - renamed io.OptCursorBlink/io.ConfigCursorBlink to io.ConfigInputTextCursorBlink. (#1427)
+ - 2018/08/31 (1.64) - added imgui_widgets.cpp file, extracted and moved widgets code out of imgui.cpp into imgui_widgets.cpp. Re-ordered some of the code remaining in imgui.cpp.
+                       NONE OF THE FUNCTIONS HAVE CHANGED. THE CODE IS SEMANTICALLY 100% IDENTICAL, BUT _EVERY_ FUNCTION HAS BEEN MOVED.
+                       Because of this, any local modifications to imgui.cpp will likely conflict when you update. Read docs/CHANGELOG.txt for suggestions.
+ - 2018/08/22 (1.63) - renamed IsItemDeactivatedAfterChange() to IsItemDeactivatedAfterEdit() for consistency with new IsItemEdited() API. Kept redirection function (will obsolete soonish as IsItemDeactivatedAfterChange() is very recent).
+ - 2018/08/21 (1.63) - renamed ImGuiTextEditCallback to ImGuiInputTextCallback, ImGuiTextEditCallbackData to ImGuiInputTextCallbackData for consistency. Kept redirection types (will obsolete).
+ - 2018/08/21 (1.63) - removed ImGuiInputTextCallbackData::ReadOnly since it is a duplication of (ImGuiInputTextCallbackData::Flags & ImGuiInputTextFlags_ReadOnly).
+ - 2018/08/01 (1.63) - removed per-window ImGuiWindowFlags_ResizeFromAnySide beta flag in favor of a global io.ConfigResizeWindowsFromEdges to enable the feature.
+ - 2018/08/01 (1.63) - renamed io.OptCursorBlink to io.ConfigCursorBlink [-> io.ConfigInputTextCursorBlink in 1.65], io.OptMacOSXBehaviors to ConfigMacOSXBehaviors for consistency.
+ - 2018/07/22 (1.63) - changed ImGui::GetTime() return value from float to double to avoid accumulating floating point imprecisions over time.
+ - 2018/07/08 (1.63) - style: renamed ImGuiCol_ModalWindowDarkening to ImGuiCol_ModalWindowDimBg for consistency with other features. Kept redirection enum (will obsolete).
+ - 2018/06/06 (1.62) - renamed GetGlyphRangesChinese() to GetGlyphRangesChineseFull() to distinguish other variants and discourage using the full set.
+ - 2018/06/06 (1.62) - TreeNodeEx()/TreeNodeBehavior(): the ImGuiTreeNodeFlags_CollapsingHeader helper now include the ImGuiTreeNodeFlags_NoTreePushOnOpen flag. See Changelog for details. 
+ - 2018/05/03 (1.61) - DragInt(): the default compile-time format string has been changed from "%.0f" to "%d", as we are not using integers internally any more.
+                       If you used DragInt() with custom format strings, make sure you change them to use %d or an integer-compatible format.
+                       To honor backward-compatibility, the DragInt() code will currently parse and modify format strings to replace %*f with %d, giving time to users to upgrade their code.
+                       If you have IMGUI_DISABLE_OBSOLETE_FUNCTIONS enabled, the code will instead assert! You may run a reg-exp search on your codebase for e.g. "DragInt.*%f" to help you find them.
+ - 2018/04/28 (1.61) - obsoleted InputFloat() functions taking an optional "int decimal_precision" in favor of an equivalent and more flexible "const char* format",
+                       consistent with other functions. Kept redirection functions (will obsolete).
+ - 2018/04/09 (1.61) - IM_DELETE() helper function added in 1.60 doesn't clear the input _pointer_ reference, more consistent with expectation and allows passing r-value.
+ - 2018/03/20 (1.60) - renamed io.WantMoveMouse to io.WantSetMousePos for consistency and ease of understanding (was added in 1.52, _not_ used by core and only honored by some binding ahead of merging the Nav branch).
+ - 2018/03/12 (1.60) - removed ImGuiCol_CloseButton, ImGuiCol_CloseButtonActive, ImGuiCol_CloseButtonHovered as the closing cross uses regular button colors now.
+ - 2018/03/08 (1.60) - changed ImFont::DisplayOffset.y to default to 0 instead of +1. Fixed rounding of Ascent/Descent to match TrueType renderer. If you were adding or subtracting to ImFont::DisplayOffset check if your fonts are correctly aligned vertically.
+ - 2018/03/03 (1.60) - renamed ImGuiStyleVar_Count_ to ImGuiStyleVar_COUNT and ImGuiMouseCursor_Count_ to ImGuiMouseCursor_COUNT for consistency with other public enums.
+ - 2018/02/18 (1.60) - BeginDragDropSource(): temporarily removed the optional mouse_button=0 parameter because it is not really usable in many situations at the moment.
+ - 2018/02/16 (1.60) - obsoleted the io.RenderDrawListsFn callback, you can call your graphics engine render function after ImGui::Render(). Use ImGui::GetDrawData() to retrieve the ImDrawData* to display.
+ - 2018/02/07 (1.60) - reorganized context handling to be more explicit,
+                       - YOU NOW NEED TO CALL ImGui::CreateContext() AT THE BEGINNING OF YOUR APP, AND CALL ImGui::DestroyContext() AT THE END.
+                       - removed Shutdown() function, as DestroyContext() serve this purpose.
+                       - you may pass a ImFontAtlas* pointer to CreateContext() to share a font atlas between contexts. Otherwise CreateContext() will create its own font atlas instance.
+                       - removed allocator parameters from CreateContext(), they are now setup with SetAllocatorFunctions(), and shared by all contexts.
+                       - removed the default global context and font atlas instance, which were confusing for users of DLL reloading and users of multiple contexts.
+ - 2018/01/31 (1.60) - moved sample TTF files from extra_fonts/ to misc/fonts/. If you loaded files directly from the imgui repo you may need to update your paths.
+ - 2018/01/11 (1.60) - obsoleted IsAnyWindowHovered() in favor of IsWindowHovered(ImGuiHoveredFlags_AnyWindow). Kept redirection function (will obsolete).
+ - 2018/01/11 (1.60) - obsoleted IsAnyWindowFocused() in favor of IsWindowFocused(ImGuiFocusedFlags_AnyWindow). Kept redirection function (will obsolete).
+ - 2018/01/03 (1.60) - renamed ImGuiSizeConstraintCallback to ImGuiSizeCallback, ImGuiSizeConstraintCallbackData to ImGuiSizeCallbackData.
+ - 2017/12/29 (1.60) - removed CalcItemRectClosestPoint() which was weird and not really used by anyone except demo code. If you need it it's easy to replicate on your side.
+ - 2017/12/24 (1.53) - renamed the emblematic ShowTestWindow() function to ShowDemoWindow(). Kept redirection function (will obsolete).
+ - 2017/12/21 (1.53) - ImDrawList: renamed style.AntiAliasedShapes to style.AntiAliasedFill for consistency and as a way to explicitly break code that manipulate those flag at runtime. You can now manipulate ImDrawList::Flags
+ - 2017/12/21 (1.53) - ImDrawList: removed 'bool anti_aliased = true' final parameter of ImDrawList::AddPolyline() and ImDrawList::AddConvexPolyFilled(). Prefer manipulating ImDrawList::Flags if you need to toggle them during the frame.
+ - 2017/12/14 (1.53) - using the ImGuiWindowFlags_NoScrollWithMouse flag on a child window forwards the mouse wheel event to the parent window, unless either ImGuiWindowFlags_NoInputs or ImGuiWindowFlags_NoScrollbar are also set.
+ - 2017/12/13 (1.53) - renamed GetItemsLineHeightWithSpacing() to GetFrameHeightWithSpacing(). Kept redirection function (will obsolete).
+ - 2017/12/13 (1.53) - obsoleted IsRootWindowFocused() in favor of using IsWindowFocused(ImGuiFocusedFlags_RootWindow). Kept redirection function (will obsolete).
+                     - obsoleted IsRootWindowOrAnyChildFocused() in favor of using IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows). Kept redirection function (will obsolete).
+ - 2017/12/12 (1.53) - renamed ImGuiTreeNodeFlags_AllowOverlapMode to ImGuiTreeNodeFlags_AllowItemOverlap. Kept redirection enum (will obsolete).
+ - 2017/12/10 (1.53) - removed SetNextWindowContentWidth(), prefer using SetNextWindowContentSize(). Kept redirection function (will obsolete).
+ - 2017/11/27 (1.53) - renamed ImGuiTextBuffer::append() helper to appendf(), appendv() to appendfv(). If you copied the 'Log' demo in your code, it uses appendv() so that needs to be renamed.
+ - 2017/11/18 (1.53) - Style, Begin: removed ImGuiWindowFlags_ShowBorders window flag. Borders are now fully set up in the ImGuiStyle structure (see e.g. style.FrameBorderSize, style.WindowBorderSize). Use ImGui::ShowStyleEditor() to look them up.
+                       Please note that the style system will keep evolving (hopefully stabilizing in Q1 2018), and so custom styles will probably subtly break over time. It is recommended you use the StyleColorsClassic(), StyleColorsDark(), StyleColorsLight() functions.
+ - 2017/11/18 (1.53) - Style: removed ImGuiCol_ComboBg in favor of combo boxes using ImGuiCol_PopupBg for consistency.
+ - 2017/11/18 (1.53) - Style: renamed ImGuiCol_ChildWindowBg to ImGuiCol_ChildBg.
+ - 2017/11/18 (1.53) - Style: renamed style.ChildWindowRounding to style.ChildRounding, ImGuiStyleVar_ChildWindowRounding to ImGuiStyleVar_ChildRounding.
+ - 2017/11/02 (1.53) - obsoleted IsRootWindowOrAnyChildHovered() in favor of using IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows);
+ - 2017/10/24 (1.52) - renamed IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCS/IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCS to IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS/IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS for consistency.
+ - 2017/10/20 (1.52) - changed IsWindowHovered() default parameters behavior to return false if an item is active in another window (e.g. click-dragging item from another window to this window). You can use the newly introduced IsWindowHovered() flags to requests this specific behavior if you need it.
+ - 2017/10/20 (1.52) - marked IsItemHoveredRect()/IsMouseHoveringWindow() as obsolete, in favor of using the newly introduced flags for IsItemHovered() and IsWindowHovered(). See https://github.com/ocornut/imgui/issues/1382 for details.
+                       removed the IsItemRectHovered()/IsWindowRectHovered() names introduced in 1.51 since they were merely more consistent names for the two functions we are now obsoleting.
+ - 2017/10/17 (1.52) - marked the old 5-parameters version of Begin() as obsolete (still available). Use SetNextWindowSize()+Begin() instead!
+ - 2017/10/11 (1.52) - renamed AlignFirstTextHeightToWidgets() to AlignTextToFramePadding(). Kept inline redirection function (will obsolete).
+ - 2017/09/25 (1.52) - removed SetNextWindowPosCenter() because SetNextWindowPos() now has the optional pivot information to do the same and more. Kept redirection function (will obsolete).
+ - 2017/08/25 (1.52) - io.MousePos needs to be set to ImVec2(-FLT_MAX,-FLT_MAX) when mouse is unavailable/missing. Previously ImVec2(-1,-1) was enough but we now accept negative mouse coordinates. In your binding if you need to support unavailable mouse, make sure to replace "io.MousePos = ImVec2(-1,-1)" with "io.MousePos = ImVec2(-FLT_MAX,-FLT_MAX)".
+ - 2017/08/22 (1.51) - renamed IsItemHoveredRect() to IsItemRectHovered(). Kept inline redirection function (will obsolete). -> (1.52) use IsItemHovered(ImGuiHoveredFlags_RectOnly)!
+                     - renamed IsMouseHoveringAnyWindow() to IsAnyWindowHovered() for consistency. Kept inline redirection function (will obsolete).
+                     - renamed IsMouseHoveringWindow() to IsWindowRectHovered() for consistency. Kept inline redirection function (will obsolete).
+ - 2017/08/20 (1.51) - renamed GetStyleColName() to GetStyleColorName() for consistency.
+ - 2017/08/20 (1.51) - added PushStyleColor(ImGuiCol idx, ImU32 col) overload, which _might_ cause an "ambiguous call" compilation error if you are using ImColor() with implicit cast. Cast to ImU32 or ImVec4 explicily to fix.
+ - 2017/08/15 (1.51) - marked the weird IMGUI_ONCE_UPON_A_FRAME helper macro as obsolete. prefer using the more explicit ImGuiOnceUponAFrame.
+ - 2017/08/15 (1.51) - changed parameter order for BeginPopupContextWindow() from (const char*,int buttons,bool also_over_items) to (const char*,int buttons,bool also_over_items). Note that most calls relied on default parameters completely.
+ - 2017/08/13 (1.51) - renamed ImGuiCol_Columns*** to ImGuiCol_Separator***. Kept redirection enums (will obsolete).
+ - 2017/08/11 (1.51) - renamed ImGuiSetCond_*** types and flags to ImGuiCond_***. Kept redirection enums (will obsolete).
+ - 2017/08/09 (1.51) - removed ValueColor() helpers, they are equivalent to calling Text(label) + SameLine() + ColorButton().
+ - 2017/08/08 (1.51) - removed ColorEditMode() and ImGuiColorEditMode in favor of ImGuiColorEditFlags and parameters to the various Color*() functions. The SetColorEditOptions() allows to initialize default but the user can still change them with right-click context menu.
+                     - changed prototype of 'ColorEdit4(const char* label, float col[4], bool show_alpha = true)' to 'ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags = 0)', where passing flags = 0x01 is a safe no-op (hello dodgy backward compatibility!). - check and run the demo window, under "Color/Picker Widgets", to understand the various new options.
+                     - changed prototype of rarely used 'ColorButton(ImVec4 col, bool small_height = false, bool outline_border = true)' to 'ColorButton(const char* desc_id, ImVec4 col, ImGuiColorEditFlags flags = 0, ImVec2 size = ImVec2(0,0))'
+ - 2017/07/20 (1.51) - removed IsPosHoveringAnyWindow(ImVec2), which was partly broken and misleading. ASSERT + redirect user to io.WantCaptureMouse
+ - 2017/05/26 (1.50) - removed ImFontConfig::MergeGlyphCenterV in favor of a more multipurpose ImFontConfig::GlyphOffset.
+ - 2017/05/01 (1.50) - renamed ImDrawList::PathFill() (rarely used directly) to ImDrawList::PathFillConvex() for clarity.
+ - 2016/11/06 (1.50) - BeginChild(const char*) now applies the stack id to the provided label, consistently with other functions as it should always have been. It shouldn't affect you unless (extremely unlikely) you were appending multiple times to a same child from different locations of the stack id. If that's the case, generate an id with GetId() and use it instead of passing string to BeginChild().
+ - 2016/10/15 (1.50) - avoid 'void* user_data' parameter to io.SetClipboardTextFn/io.GetClipboardTextFn pointers. We pass io.ClipboardUserData to it.
+ - 2016/09/25 (1.50) - style.WindowTitleAlign is now a ImVec2 (ImGuiAlign enum was removed). set to (0.5f,0.5f) for horizontal+vertical centering, (0.0f,0.0f) for upper-left, etc.
+ - 2016/07/30 (1.50) - SameLine(x) with x>0.0f is now relative to left of column/group if any, and not always to left of window. This was sort of always the intent and hopefully breakage should be minimal.
+ - 2016/05/12 (1.49) - title bar (using ImGuiCol_TitleBg/ImGuiCol_TitleBgActive colors) isn't rendered over a window background (ImGuiCol_WindowBg color) anymore.
+                       If your TitleBg/TitleBgActive alpha was 1.0f or you are using the default theme it will not affect you.
+                       If your TitleBg/TitleBgActive alpha was <1.0f you need to tweak your custom theme to readjust for the fact that we don't draw a WindowBg background behind the title bar.
+                       This helper function will convert an old TitleBg/TitleBgActive color into a new one with the same visual output, given the OLD color and the OLD WindowBg color.
+                           ImVec4 ConvertTitleBgCol(const ImVec4& win_bg_col, const ImVec4& title_bg_col)
+                           {
+                               float new_a = 1.0f - ((1.0f - win_bg_col.w) * (1.0f - title_bg_col.w)), k = title_bg_col.w / new_a;
+                               return ImVec4((win_bg_col.x * win_bg_col.w + title_bg_col.x) * k, (win_bg_col.y * win_bg_col.w + title_bg_col.y) * k, (win_bg_col.z * win_bg_col.w + title_bg_col.z) * k, new_a);
+                           }
+                       If this is confusing, pick the RGB value from title bar from an old screenshot and apply this as TitleBg/TitleBgActive. Or you may just create TitleBgActive from a tweaked TitleBg color.
+ - 2016/05/07 (1.49) - removed confusing set of GetInternalState(), GetInternalStateSize(), SetInternalState() functions. Now using CreateContext(), DestroyContext(), GetCurrentContext(), SetCurrentContext().
+ - 2016/05/02 (1.49) - renamed SetNextTreeNodeOpened() to SetNextTreeNodeOpen(), no redirection.
+ - 2016/05/01 (1.49) - obsoleted old signature of CollapsingHeader(const char* label, const char* str_id = NULL, bool display_frame = true, bool default_open = false) as extra parameters were badly designed and rarely used. You can replace the "default_open = true" flag in new API with CollapsingHeader(label, ImGuiTreeNodeFlags_DefaultOpen).
+ - 2016/04/26 (1.49) - changed ImDrawList::PushClipRect(ImVec4 rect) to ImDrawList::PushClipRect(Imvec2 min,ImVec2 max,bool intersect_with_current_clip_rect=false). Note that higher-level ImGui::PushClipRect() is preferable because it will clip at logic/widget level, whereas ImDrawList::PushClipRect() only affect your renderer.
+ - 2016/04/03 (1.48) - removed style.WindowFillAlphaDefault setting which was redundant. Bake default BG alpha inside style.Colors[ImGuiCol_WindowBg] and all other Bg color values. (ref github issue #337).
+ - 2016/04/03 (1.48) - renamed ImGuiCol_TooltipBg to ImGuiCol_PopupBg, used by popups/menus and tooltips. popups/menus were previously using ImGuiCol_WindowBg. (ref github issue #337)
+ - 2016/03/21 (1.48) - renamed GetWindowFont() to GetFont(), GetWindowFontSize() to GetFontSize(). Kept inline redirection function (will obsolete).
+ - 2016/03/02 (1.48) - InputText() completion/history/always callbacks: if you modify the text buffer manually (without using DeleteChars()/InsertChars() helper) you need to maintain the BufTextLen field. added an assert.
+ - 2016/01/23 (1.48) - fixed not honoring exact width passed to PushItemWidth(), previously it would add extra FramePadding.x*2 over that width. if you had manual pixel-perfect alignment in place it might affect you.
+ - 2015/12/27 (1.48) - fixed ImDrawList::AddRect() which used to render a rectangle 1 px too large on each axis.
+ - 2015/12/04 (1.47) - renamed Color() helpers to ValueColor() - dangerously named, rarely used and probably to be made obsolete.
+ - 2015/08/29 (1.45) - with the addition of horizontal scrollbar we made various fixes to inconsistencies with dealing with cursor position.
+                       GetCursorPos()/SetCursorPos() functions now include the scrolled amount. It shouldn't affect the majority of users, but take note that SetCursorPosX(100.0f) puts you at +100 from the starting x position which may include scrolling, not at +100 from the window left side.
+                       GetContentRegionMax()/GetWindowContentRegionMin()/GetWindowContentRegionMax() functions allow include the scrolled amount. Typically those were used in cases where no scrolling would happen so it may not be a problem, but watch out!
+ - 2015/08/29 (1.45) - renamed style.ScrollbarWidth to style.ScrollbarSize
+ - 2015/08/05 (1.44) - split imgui.cpp into extra files: imgui_demo.cpp imgui_draw.cpp imgui_internal.h that you need to add to your project.
+ - 2015/07/18 (1.44) - fixed angles in ImDrawList::PathArcTo(), PathArcToFast() (introduced in 1.43) being off by an extra PI for no justifiable reason
+ - 2015/07/14 (1.43) - add new ImFontAtlas::AddFont() API. For the old AddFont***, moved the 'font_no' parameter of ImFontAtlas::AddFont** functions to the ImFontConfig structure.
+                       you need to render your textured triangles with bilinear filtering to benefit from sub-pixel positioning of text.
+ - 2015/07/08 (1.43) - switched rendering data to use indexed rendering. this is saving a fair amount of CPU/GPU and enables us to get anti-aliasing for a marginal cost.
+                       this necessary change will break your rendering function! the fix should be very easy. sorry for that :(
+                     - if you are using a vanilla copy of one of the imgui_impl_XXXX.cpp provided in the example, you just need to update your copy and you can ignore the rest.
+                     - the signature of the io.RenderDrawListsFn handler has changed!
+                       old: ImGui_XXXX_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_count)
+                       new: ImGui_XXXX_RenderDrawLists(ImDrawData* draw_data).
+                         argument:   'cmd_lists' becomes 'draw_data->CmdLists', 'cmd_lists_count' becomes 'draw_data->CmdListsCount'
+                         ImDrawList: 'commands' becomes 'CmdBuffer', 'vtx_buffer' becomes 'VtxBuffer', 'IdxBuffer' is new.
+                         ImDrawCmd:  'vtx_count' becomes 'ElemCount', 'clip_rect' becomes 'ClipRect', 'user_callback' becomes 'UserCallback', 'texture_id' becomes 'TextureId'.
+                     - each ImDrawList now contains both a vertex buffer and an index buffer. For each command, render ElemCount/3 triangles using indices from the index buffer.
+                     - if you REALLY cannot render indexed primitives, you can call the draw_data->DeIndexAllBuffers() method to de-index the buffers. This is slow and a waste of CPU/GPU. Prefer using indexed rendering!
+                     - refer to code in the examples/ folder or ask on the GitHub if you are unsure of how to upgrade. please upgrade!
+ - 2015/07/10 (1.43) - changed SameLine() parameters from int to float.
+ - 2015/07/02 (1.42) - renamed SetScrollPosHere() to SetScrollFromCursorPos(). Kept inline redirection function (will obsolete).
+ - 2015/07/02 (1.42) - renamed GetScrollPosY() to GetScrollY(). Necessary to reduce confusion along with other scrolling functions, because positions (e.g. cursor position) are not equivalent to scrolling amount.
+ - 2015/06/14 (1.41) - changed ImageButton() default bg_col parameter from (0,0,0,1) (black) to (0,0,0,0) (transparent) - makes a difference when texture have transparence
+ - 2015/06/14 (1.41) - changed Selectable() API from (label, selected, size) to (label, selected, flags, size). Size override should have been rarely be used. Sorry!
+ - 2015/05/31 (1.40) - renamed GetWindowCollapsed() to IsWindowCollapsed() for consistency. Kept inline redirection function (will obsolete).
+ - 2015/05/31 (1.40) - renamed IsRectClipped() to IsRectVisible() for consistency. Note that return value is opposite! Kept inline redirection function (will obsolete).
+ - 2015/05/27 (1.40) - removed the third 'repeat_if_held' parameter from Button() - sorry! it was rarely used and inconsistent. Use PushButtonRepeat(true) / PopButtonRepeat() to enable repeat on desired buttons.
+ - 2015/05/11 (1.40) - changed BeginPopup() API, takes a string identifier instead of a bool. ImGui needs to manage the open/closed state of popups. Call OpenPopup() to actually set the "open" state of a popup. BeginPopup() returns true if the popup is opened.
+ - 2015/05/03 (1.40) - removed style.AutoFitPadding, using style.WindowPadding makes more sense (the default values were already the same).
+ - 2015/04/13 (1.38) - renamed IsClipped() to IsRectClipped(). Kept inline redirection function until 1.50.
+ - 2015/04/09 (1.38) - renamed ImDrawList::AddArc() to ImDrawList::AddArcFast() for compatibility with future API
+ - 2015/04/03 (1.38) - removed ImGuiCol_CheckHovered, ImGuiCol_CheckActive, replaced with the more general ImGuiCol_FrameBgHovered, ImGuiCol_FrameBgActive.
+ - 2014/04/03 (1.38) - removed support for passing -FLT_MAX..+FLT_MAX as the range for a SliderFloat(). Use DragFloat() or Inputfloat() instead.
+ - 2015/03/17 (1.36) - renamed GetItemBoxMin()/GetItemBoxMax()/IsMouseHoveringBox() to GetItemRectMin()/GetItemRectMax()/IsMouseHoveringRect(). Kept inline redirection function until 1.50.
+ - 2015/03/15 (1.36) - renamed style.TreeNodeSpacing to style.IndentSpacing, ImGuiStyleVar_TreeNodeSpacing to ImGuiStyleVar_IndentSpacing
+ - 2015/03/13 (1.36) - renamed GetWindowIsFocused() to IsWindowFocused(). Kept inline redirection function until 1.50.
+ - 2015/03/08 (1.35) - renamed style.ScrollBarWidth to style.ScrollbarWidth (casing)
+ - 2015/02/27 (1.34) - renamed OpenNextNode(bool) to SetNextTreeNodeOpened(bool, ImGuiSetCond). Kept inline redirection function until 1.50.
+ - 2015/02/27 (1.34) - renamed ImGuiSetCondition_*** to ImGuiSetCond_***, and _FirstUseThisSession becomes _Once.
+ - 2015/02/11 (1.32) - changed text input callback ImGuiTextEditCallback return type from void-->int. reserved for future use, return 0 for now.
+ - 2015/02/10 (1.32) - renamed GetItemWidth() to CalcItemWidth() to clarify its evolving behavior
+ - 2015/02/08 (1.31) - renamed GetTextLineSpacing() to GetTextLineHeightWithSpacing()
+ - 2015/02/01 (1.31) - removed IO.MemReallocFn (unused)
+ - 2015/01/19 (1.30) - renamed ImGuiStorage::GetIntPtr()/GetFloatPtr() to GetIntRef()/GetIntRef() because Ptr was conflicting with actual pointer storage functions.
+ - 2015/01/11 (1.30) - big font/image API change! now loads TTF file. allow for multiple fonts. no need for a PNG loader.
+              (1.30) - removed GetDefaultFontData(). uses io.Fonts->GetTextureData*() API to retrieve uncompressed pixels.
+                       font init:  const void* png_data; unsigned int png_size; ImGui::GetDefaultFontData(NULL, NULL, &png_data, &png_size); <..Upload texture to GPU..>
+                       became:     unsigned char* pixels; int width, height; io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); <..Upload texture to GPU>; io.Fonts->TexId = YourTextureIdentifier;
+                       you now more flexibility to load multiple TTF fonts and manage the texture buffer for internal needs.
+                       it is now recommended that you sample the font texture with bilinear interpolation.
+              (1.30) - added texture identifier in ImDrawCmd passed to your render function (we can now render images). make sure to set io.Fonts->TexID.
+              (1.30) - removed IO.PixelCenterOffset (unnecessary, can be handled in user projection matrix)
+              (1.30) - removed ImGui::IsItemFocused() in favor of ImGui::IsItemActive() which handles all widgets
+ - 2014/12/10 (1.18) - removed SetNewWindowDefaultPos() in favor of new generic API SetNextWindowPos(pos, ImGuiSetCondition_FirstUseEver)
+ - 2014/11/28 (1.17) - moved IO.Font*** options to inside the IO.Font-> structure (FontYOffset, FontTexUvForWhite, FontBaseScale, FontFallbackGlyph)
+ - 2014/11/26 (1.17) - reworked syntax of IMGUI_ONCE_UPON_A_FRAME helper macro to increase compiler compatibility
+ - 2014/11/07 (1.15) - renamed IsHovered() to IsItemHovered()
+ - 2014/10/02 (1.14) - renamed IMGUI_INCLUDE_IMGUI_USER_CPP to IMGUI_INCLUDE_IMGUI_USER_INL and imgui_user.cpp to imgui_user.inl (more IDE friendly)
+ - 2014/09/25 (1.13) - removed 'text_end' parameter from IO.SetClipboardTextFn (the string is now always zero-terminated for simplicity)
+ - 2014/09/24 (1.12) - renamed SetFontScale() to SetWindowFontScale()
+ - 2014/09/24 (1.12) - moved IM_MALLOC/IM_REALLOC/IM_FREE preprocessor defines to IO.MemAllocFn/IO.MemReallocFn/IO.MemFreeFn
+ - 2014/08/30 (1.09) - removed IO.FontHeight (now computed automatically)
+ - 2014/08/30 (1.09) - moved IMGUI_FONT_TEX_UV_FOR_WHITE preprocessor define to IO.FontTexUvForWhite
+ - 2014/08/28 (1.09) - changed the behavior of IO.PixelCenterOffset following various rendering fixes
+
+
+ FREQUENTLY ASKED QUESTIONS (FAQ), TIPS
+ ======================================
+
+ Q: How can I tell whether to dispatch mouse/keyboard to imgui or to my application?
+ A: You can read the 'io.WantCaptureMouse', 'io.WantCaptureKeyboard' and 'io.WantTextInput' flags from the ImGuiIO structure (e.g. if (ImGui::GetIO().WantCaptureMouse) { ... } )
+    - When 'io.WantCaptureMouse' is set, imgui wants to use your mouse state, and you may want to discard/hide the inputs from the rest of your application.
+    - When 'io.WantCaptureKeyboard' is set, imgui wants to use your keyboard state, and you may want to discard/hide the inputs from the rest of your application.
+    - When 'io.WantTextInput' is set to may want to notify your OS to popup an on-screen keyboard, if available (e.g. on a mobile phone, or console OS).
+    Note: you should always pass your mouse/keyboard inputs to imgui, even when the io.WantCaptureXXX flag are set false.
+     This is because imgui needs to detect that you clicked in the void to unfocus its windows.
+    Note: The 'io.WantCaptureMouse' is more accurate that any attempt to "check if the mouse is hovering a window" (don't do that!).
+     It handle mouse dragging correctly (both dragging that started over your application or over an imgui window) and handle e.g. modal windows blocking inputs.
+     Those flags are updated by ImGui::NewFrame(). Preferably read the flags after calling NewFrame() if you can afford it, but reading them before is also
+     perfectly fine, as the bool toggle fairly rarely. If you have on a touch device, you might find use for an early call to UpdateHoveredWindowAndCaptureFlags().
+    Note: Text input widget releases focus on "Return KeyDown", so the subsequent "Return KeyUp" event that your application receive will typically
+     have 'io.WantCaptureKeyboard=false'. Depending on your application logic it may or not be inconvenient. You might want to track which key-downs
+     were targeted for Dear ImGui, e.g. with an array of bool, and filter out the corresponding key-ups.)
+
+ Q: How can I display an image? What is ImTextureID, how does it works?
+ A: Short explanation:
+    - You may use functions such as ImGui::Image(), ImGui::ImageButton() or lower-level ImDrawList::AddImage() to emit draw calls that will use your own textures.
+    - Actual textures are identified in a way that is up to the user/engine.
+    - Loading image files from the disk and turning them into a texture is not within the scope of Dear ImGui (for a good reason). 
+      Please read documentations or tutorials on your graphics API to understand how to display textures on the screen before moving onward.
+
+    Long explanation:
+    - Dear ImGui's job is to create "meshes", defined in a renderer-agnostic format made of draw commands and vertices.
+      At the end of the frame those meshes (ImDrawList) will be displayed by your rendering function. They are made up of textured polygons and the code
+      to render them is generally fairly short (a few dozen lines). In the examples/ folder we provide functions for popular graphics API (OpenGL, DirectX, etc.).
+    - Each rendering function decides on a data type to represent "textures". The concept of what is a "texture" is entirely tied to your underlying engine/graphics API.
+      We carry the information to identify a "texture" in the ImTextureID type. 
+      ImTextureID is nothing more that a void*, aka 4/8 bytes worth of data: just enough to store 1 pointer or 1 integer of your choice.
+      Dear ImGui doesn't know or understand what you are storing in ImTextureID, it merely pass ImTextureID values until they reach your rendering function.
+    - In the examples/ bindings, for each graphics API binding we decided on a type that is likely to be a good representation for specifying 
+      an image from the end-user perspective. This is what the _examples_ rendering functions are using:
+
+         OpenGL:     ImTextureID = GLuint                       (see ImGui_ImplGlfwGL3_RenderDrawData() function in imgui_impl_glfw_gl3.cpp)
+         DirectX9:   ImTextureID = LPDIRECT3DTEXTURE9           (see ImGui_ImplDX9_RenderDrawData()     function in imgui_impl_dx9.cpp)
+         DirectX11:  ImTextureID = ID3D11ShaderResourceView*    (see ImGui_ImplDX11_RenderDrawData()    function in imgui_impl_dx11.cpp)
+         DirectX12:  ImTextureID = D3D12_GPU_DESCRIPTOR_HANDLE  (see ImGui_ImplDX12_RenderDrawData()    function in imgui_impl_dx12.cpp)
+
+      For example, in the OpenGL example binding we store raw OpenGL texture identifier (GLuint) inside ImTextureID. 
+      Whereas in the DirectX11 example binding we store a pointer to ID3D11ShaderResourceView inside ImTextureID, which is a higher-level structure 
+      tying together both the texture and information about its format and how to read it.
+    - If you have a custom engine built over e.g. OpenGL, instead of passing GLuint around you may decide to use a high-level data type to carry information about
+      the texture as well as how to display it (shaders, etc.). The decision of what to use as ImTextureID can always be made better knowing how your codebase
+      is designed. If your engine has high-level data types for "textures" and "material" then you may want to use them.
+      If you are starting with OpenGL or DirectX or Vulkan and haven't built much of a rendering engine over them, keeping the default ImTextureID 
+      representation suggested by the example bindings is probably the best choice.
+      (Advanced users may also decide to keep a low-level type in ImTextureID, and use ImDrawList callback and pass information to their renderer)
+
+    User code may do:
+
+        // Cast our texture type to ImTextureID / void*
+        MyTexture* texture = g_CoffeeTableTexture;
+        ImGui::Image((void*)texture, ImVec2(texture->Width, texture->Height)); 
+
+    The renderer function called after ImGui::Render() will receive that same value that the user code passed:
+
+        // Cast ImTextureID / void* stored in the draw command as our texture type
+        MyTexture* texture = (MyTexture*)pcmd->TextureId;
+        MyEngineBindTexture2D(texture);
+
+    Once you understand this design you will understand that loading image files and turning them into displayable textures is not within the scope of Dear ImGui.
+    This is by design and is actually a good thing, because it means your code has full control over your data types and how you display them.
+    If you want to display an image file (e.g. PNG file) into the screen, please refer to documentation and tutorials for the graphics API you are using.
+
+    Here's a simplified OpenGL example using stb_image.h:
+
+        // Use stb_image.h to load a PNG from disk and turn it into raw RGBA pixel data:
+        #define STB_IMAGE_IMPLEMENTATION
+        #include <stb_image.h>
+        [...]
+        int my_image_width, my_image_height;
+        unsigned char* my_image_data = stbi_load("my_image.png", &my_image_width, &my_image_height, NULL, 4);
+
+        // Turn the RGBA pixel data into an OpenGL texture:
+        GLuint my_opengl_texture;
+        glGenTextures(1, &my_opengl_texture);
+        glBindTexture(GL_TEXTURE_2D, my_opengl_texture);
+        glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
+        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image_width, image_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image_data);
+
+        // Now that we have an OpenGL texture, assuming our imgui rendering function (imgui_impl_xxx.cpp file) takes GLuint as ImTextureID, we can display it:
+        ImGui::Image((void*)(intptr_t)my_opengl_texture, ImVec2(my_image_width, my_image_height));
+
+    C/C++ tip: a void* is pointer-sized storage. You may safely store any pointer or integer into it by casting your value to ImTexture / void*, and vice-versa. 
+    Because both end-points (user code and rendering function) are under your control, you know exactly what is stored inside the ImTexture / void*.
+    Examples:
+
+        GLuint my_tex = XXX;
+        void* my_void_ptr;
+        my_void_ptr = (void*)(intptr_t)my_tex;                  // cast a GLuint into a void* (we don't take its address! we literally store the value inside the pointer)
+        my_tex = (GLuint)(intptr_t)my_void_ptr;                 // cast a void* into a GLuint
+
+        ID3D11ShaderResourceView* my_dx11_srv = XXX;
+        void* my_void_ptr;
+        my_void_ptr = (void*)my_dx11_srv;                       // cast a ID3D11ShaderResourceView* into an opaque void*
+        my_dx11_srv = (ID3D11ShaderResourceView*)my_void_ptr;   // cast a void* into a ID3D11ShaderResourceView*
+
+    Finally, you may call ImGui::ShowMetricsWindow() to explore/visualize/understand how the ImDrawList are generated.
+
+ Q: How can I have multiple widgets with the same label or without a label?
+ Q: I have multiple widgets with the same label, and only the first one works. Why is that?
+ A: A primer on labels and the ID Stack...
+
+    Dear ImGui internally need to uniquely identify UI elements.
+    Elements that are typically not clickable (such as calls to the Text functions) don't need an ID.
+    Interactive widgets (such as calls to Button buttons) need a unique ID. 
+    Unique ID are used internally to track active widgets and occasionally associate state to widgets.
+    Unique ID are implicitly built from the hash of multiple elements that identify the "path" to the UI element.
+
+   - Unique ID are often derived from a string label:
+
+       Button("OK");          // Label = "OK",     ID = hash of (..., "OK")
+       Button("Cancel");      // Label = "Cancel", ID = hash of (..., "Cancel")
+
+   - ID are uniquely scoped within windows, tree nodes, etc. which all pushes to the ID stack. Having
+     two buttons labeled "OK" in different windows or different tree locations is fine.
+     We used "..." above to signify whatever was already pushed to the ID stack previously:
+
+       Begin("MyWindow");
+       Button("OK");          // Label = "OK",     ID = hash of ("MyWindow", "OK")
+       End();
+
+   - If you have a same ID twice in the same location, you'll have a conflict:
+
+       Button("OK");
+       Button("OK");          // ID collision! Interacting with either button will trigger the first one.
+
+     Fear not! this is easy to solve and there are many ways to solve it!
+
+   - Solving ID conflict in a simple/local context:
+     When passing a label you can optionally specify extra ID information within string itself.
+     Use "##" to pass a complement to the ID that won't be visible to the end-user.
+     This helps solving the simple collision cases when you know e.g. at compilation time which items
+     are going to be created:
+
+       Begin("MyWindow");
+       Button("Play");        // Label = "Play",   ID = hash of ("MyWindow", "Play")
+       Button("Play##foo1");  // Label = "Play",   ID = hash of ("MyWindow", "Play##foo1")  // Different from above
+       Button("Play##foo2");  // Label = "Play",   ID = hash of ("MyWindow", "Play##foo2")  // Different from above
+       End();
+
+   - If you want to completely hide the label, but still need an ID:
+
+       Checkbox("##On", &b);  // Label = "",       ID = hash of (..., "##On")   // No visible label!
+
+   - Occasionally/rarely you might want change a label while preserving a constant ID. This allows
+     you to animate labels. For example you may want to include varying information in a window title bar,
+     but windows are uniquely identified by their ID. Use "###" to pass a label that isn't part of ID:
+
+       Button("Hello###ID";   // Label = "Hello",  ID = hash of (..., "ID")
+       Button("World###ID";   // Label = "World",  ID = hash of (..., "ID")     // Same as above, even though the label looks different
+
+       sprintf(buf, "My game (%f FPS)###MyGame", fps);
+       Begin(buf);            // Variable label,   ID = hash of "MyGame"
+
+   - Solving ID conflict in a more general manner:
+     Use PushID() / PopID() to create scopes and manipulate the ID stack, as to avoid ID conflicts
+     within the same window. This is the most convenient way of distinguishing ID when iterating and
+     creating many UI elements programmatically.
+     You can push a pointer, a string or an integer value into the ID stack.
+     Remember that ID are formed from the concatenation of _everything_ in the ID stack!
+
+       Begin("Window");
+       for (int i = 0; i < 100; i++)
+       {
+         PushID(i);         // Push i to the id tack
+         Button("Click");   // Label = "Click",  ID = Hash of ("Window", i, "Click")
+         PopID();
+       }
+       for (int i = 0; i < 100; i++)
+       {
+         MyObject* obj = Objects[i];
+         PushID(obj);
+         Button("Click");   // Label = "Click",  ID = Hash of ("Window", obj pointer, "Click")
+         PopID();
+       }
+       for (int i = 0; i < 100; i++)
+       {
+         MyObject* obj = Objects[i];
+         PushID(obj->Name);
+         Button("Click");   // Label = "Click",  ID = Hash of ("Window", obj->Name, "Click")
+         PopID();
+       }
+       End();
+
+   - More example showing that you can stack multiple prefixes into the ID stack:
+
+       Button("Click");     // Label = "Click",  ID = hash of (..., "Click")
+       PushID("node");
+       Button("Click");     // Label = "Click",  ID = hash of (..., "node", "Click")
+         PushID(my_ptr);
+           Button("Click"); // Label = "Click",  ID = hash of (..., "node", my_ptr, "Click")
+         PopID();
+       PopID();
+
+   - Tree nodes implicitly creates a scope for you by calling PushID().
+
+       Button("Click");     // Label = "Click",  ID = hash of (..., "Click")
+       if (TreeNode("node"))
+       {
+         Button("Click");   // Label = "Click",  ID = hash of (..., "node", "Click")
+         TreePop();
+       }
+
+   - When working with trees, ID are used to preserve the open/close state of each tree node.
+     Depending on your use cases you may want to use strings, indices or pointers as ID.
+      e.g. when following a single pointer that may change over time, using a static string as ID
+       will preserve your node open/closed state when the targeted object change.
+      e.g. when displaying a list of objects, using indices or pointers as ID will preserve the
+       node open/closed state differently. See what makes more sense in your situation!
+
+ Q: How can I use my own math types instead of ImVec2/ImVec4? 
+ A: You can edit imconfig.h and setup the IM_VEC2_CLASS_EXTRA/IM_VEC4_CLASS_EXTRA macros to add implicit type conversions.
+    This way you'll be able to use your own types everywhere, e.g. passsing glm::vec2 to ImGui functions instead of ImVec2.
+
+ Q: How can I load a different font than the default?
+ A: Use the font atlas to load the TTF/OTF file you want:
+      ImGuiIO& io = ImGui::GetIO();
+      io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels);
+      io.Fonts->GetTexDataAsRGBA32() or GetTexDataAsAlpha8()
+    (default is ProggyClean.ttf, rendered at size 13, embedded in dear imgui's source code)
+
+    New programmers: remember that in C/C++ and most programming languages if you want to use a
+    backslash \ within a string literal, you need to write it double backslash "\\":
+      io.Fonts->AddFontFromFileTTF("MyDataFolder\MyFontFile.ttf", size_in_pixels);   // WRONG (you are escape the M here!)
+      io.Fonts->AddFontFromFileTTF("MyDataFolder\\MyFontFile.ttf", size_in_pixels);  // CORRECT
+      io.Fonts->AddFontFromFileTTF("MyDataFolder/MyFontFile.ttf", size_in_pixels);   // ALSO CORRECT
+
+ Q: How can I easily use icons in my application?
+ A: The most convenient and practical way is to merge an icon font such as FontAwesome inside you
+    main font. Then you can refer to icons within your strings. Read 'How can I load multiple fonts?'
+    and the file 'misc/fonts/README.txt' for instructions and useful header files.
+
+ Q: How can I load multiple fonts?
+ A: Use the font atlas to pack them into a single texture:
+    (Read misc/fonts/README.txt and the code in ImFontAtlas for more details.)
+
+      ImGuiIO& io = ImGui::GetIO();
+      ImFont* font0 = io.Fonts->AddFontDefault();
+      ImFont* font1 = io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels);
+      ImFont* font2 = io.Fonts->AddFontFromFileTTF("myfontfile2.ttf", size_in_pixels);
+      io.Fonts->GetTexDataAsRGBA32() or GetTexDataAsAlpha8()
+      // the first loaded font gets used by default
+      // use ImGui::PushFont()/ImGui::PopFont() to change the font at runtime
+
+      // Options
+      ImFontConfig config;
+      config.OversampleH = 3;
+      config.OversampleV = 1;
+      config.GlyphOffset.y -= 2.0f;      // Move everything by 2 pixels up
+      config.GlyphExtraSpacing.x = 1.0f; // Increase spacing between characters
+      io.Fonts->LoadFromFileTTF("myfontfile.ttf", size_pixels, &config);
+
+      // Combine multiple fonts into one (e.g. for icon fonts)
+      ImWchar ranges[] = { 0xf000, 0xf3ff, 0 };
+      ImFontConfig config;
+      config.MergeMode = true;
+      io.Fonts->AddFontDefault();
+      io.Fonts->LoadFromFileTTF("fontawesome-webfont.ttf", 16.0f, &config, ranges); // Merge icon font
+      io.Fonts->LoadFromFileTTF("myfontfile.ttf", size_pixels, NULL, &config, io.Fonts->GetGlyphRangesJapanese()); // Merge japanese glyphs
+
+ Q: How can I display and input non-Latin characters such as Chinese, Japanese, Korean, Cyrillic?
+ A: When loading a font, pass custom Unicode ranges to specify the glyphs to load.
+
+      // Add default Japanese ranges
+      io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels, NULL, io.Fonts->GetGlyphRangesJapanese());
+
+      // Or create your own custom ranges (e.g. for a game you can feed your entire game script and only build the characters the game need)
+      ImVector<ImWchar> ranges;
+      ImFontAtlas::GlyphRangesBuilder builder;
+      builder.AddText("Hello world");                        // Add a string (here "Hello world" contains 7 unique characters)
+      builder.AddChar(0x7262);                               // Add a specific character
+      builder.AddRanges(io.Fonts->GetGlyphRangesJapanese()); // Add one of the default ranges
+      builder.BuildRanges(&ranges);                          // Build the final result (ordered ranges with all the unique characters submitted)
+      io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels, NULL, ranges.Data);
+
+    All your strings needs to use UTF-8 encoding. In C++11 you can encode a string literal in UTF-8
+    by using the u8"hello" syntax. Specifying literal in your source code using a local code page
+    (such as CP-923 for Japanese or CP-1251 for Cyrillic) will NOT work!
+    Otherwise you can convert yourself to UTF-8 or load text data from file already saved as UTF-8.
+
+    Text input: it is up to your application to pass the right character code by calling io.AddInputCharacter(). 
+    The applications in examples/ are doing that. 
+    Windows: you can use the WM_CHAR or WM_UNICHAR or WM_IME_CHAR message (depending if your app is built using Unicode or MultiByte mode).
+    You may also use MultiByteToWideChar() or ToUnicode() to retrieve Unicode codepoints from MultiByte characters or keyboard state.
+    Windows: if your language is relying on an Input Method Editor (IME), you copy the HWND of your window to io.ImeWindowHandle in order for 
+    the default implementation of io.ImeSetInputScreenPosFn() to set your Microsoft IME position correctly.
+
+ Q: How can I use the drawing facilities without an ImGui window? (using ImDrawList API)
+ A: - You can create a dummy window. Call SetNextWindowBgAlpha(0.0f), call Begin() with NoTitleBar|NoResize|NoMove|NoScrollbar|NoSavedSettings|NoInputs flags.
+      Then you can retrieve the ImDrawList* via GetWindowDrawList() and draw to it in any way you like.
+    - You can call ImGui::GetOverlayDrawList() and use this draw list to display contents over every other imgui windows.
+    - You can create your own ImDrawList instance. You'll need to initialize them ImGui::GetDrawListSharedData(), or create your own ImDrawListSharedData.
+
+ Q: I integrated Dear ImGui in my engine and the text or lines are blurry..
+ A: In your Render function, try translating your projection matrix by (0.5f,0.5f) or (0.375f,0.375f).
+    Also make sure your orthographic projection matrix and io.DisplaySize matches your actual framebuffer dimension.
+
+ Q: I integrated Dear ImGui in my engine and some elements are clipping or disappearing when I move windows around..
+ A: You are probably mishandling the clipping rectangles in your render function.
+    Rectangles provided by ImGui are defined as (x1=left,y1=top,x2=right,y2=bottom) and NOT as (x1,y1,width,height).
+
+ Q: How can I help?
+ A: - If you are experienced with Dear ImGui and C++, look at the github issues, or docs/TODO.txt and see how you want/can help!
+    - Convince your company to fund development time! Individual users: you can also become a Patron (patreon.com/imgui) or donate on PayPal! See README.
+    - Disclose your usage of dear imgui via a dev blog post, a tweet, a screenshot, a mention somewhere etc.
+      You may post screenshot or links in the gallery threads (github.com/ocornut/imgui/issues/1269). Visuals are ideal as they inspire other programmers.
+      But even without visuals, disclosing your use of dear imgui help the library grow credibility, and help other teams and programmers with taking decisions.
+    - If you have issues or if you need to hack into the library, even if you don't expect any support it is useful that you share your issues (on github or privately).
+
+ - tip: you can call Begin() multiple times with the same name during the same frame, it will keep appending to the same window.
+        this is also useful to set yourself in the context of another window (to get/set other settings)
+ - tip: you can create widgets without a Begin()/End() block, they will go in an implicit window called "Debug".
+ - tip: the ImGuiOnceUponAFrame helper will allow run the block of code only once a frame. You can use it to quickly add custom UI in the middle
+        of a deep nested inner loop in your code.
+ - tip: you can call Render() multiple times (e.g for VR renders).
+ - tip: call and read the ShowDemoWindow() code in imgui_demo.cpp for more example of how to use ImGui!
+
+*/
+
+#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
+#define _CRT_SECURE_NO_WARNINGS
+#endif
+
+#include "imgui.h"
+#ifndef IMGUI_DEFINE_MATH_OPERATORS
+#define IMGUI_DEFINE_MATH_OPERATORS
+#endif
+#include "imgui_internal.h"
+
+#include <ctype.h>      // toupper, isprint
+#include <stdio.h>      // vsnprintf, sscanf, printf
+#if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier
+#include <stddef.h>     // intptr_t
+#else
+#include <stdint.h>     // intptr_t
+#endif
+
+#define IMGUI_DEBUG_NAV_SCORING     0
+#define IMGUI_DEBUG_NAV_RECTS       0
+
+// Visual Studio warnings
+#ifdef _MSC_VER
+#pragma warning (disable: 4127) // condition expression is constant
+#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen
+#endif
+
+// Clang/GCC warnings with -Weverything
+#ifdef __clang__
+#pragma clang diagnostic ignored "-Wunknown-pragmas"        // warning : unknown warning group '-Wformat-pedantic *'        // not all warnings are known by all clang versions.. so ignoring warnings triggers new warnings on some configuration. great!
+#pragma clang diagnostic ignored "-Wold-style-cast"         // warning : use of old-style cast                              // yes, they are more terse.
+#pragma clang diagnostic ignored "-Wfloat-equal"            // warning : comparing floating point with == or != is unsafe   // storing and comparing against same constants (typically 0.0f) is ok.
+#pragma clang diagnostic ignored "-Wformat-nonliteral"      // warning : format string is not a string literal              // passing non-literal to vsnformat(). yes, user passing incorrect format strings can crash the code.
+#pragma clang diagnostic ignored "-Wexit-time-destructors"  // warning : declaration requires an exit-time destructor       // exit-time destruction order is undefined. if MemFree() leads to users code that has been disabled before exit it might cause problems. ImGui coding style welcomes static/globals.
+#pragma clang diagnostic ignored "-Wglobal-constructors"    // warning : declaration requires a global destructor           // similar to above, not sure what the exact difference it.
+#pragma clang diagnostic ignored "-Wsign-conversion"        // warning : implicit conversion changes signedness             //
+#pragma clang diagnostic ignored "-Wformat-pedantic"        // warning : format specifies type 'void *' but the argument has type 'xxxx *' // unreasonable, would lead to casting every %p arg to void*. probably enabled by -pedantic.
+#pragma clang diagnostic ignored "-Wint-to-void-pointer-cast" // warning : cast to 'void *' from smaller integer type 'int'
+#elif defined(__GNUC__)
+#pragma GCC diagnostic ignored "-Wunused-function"          // warning: 'xxxx' defined but not used
+#pragma GCC diagnostic ignored "-Wint-to-pointer-cast"      // warning: cast to pointer from integer of different size
+#pragma GCC diagnostic ignored "-Wformat"                   // warning: format '%p' expects argument of type 'void*', but argument 6 has type 'ImGuiWindow*'
+#pragma GCC diagnostic ignored "-Wdouble-promotion"         // warning: implicit conversion from 'float' to 'double' when passing argument to function
+#pragma GCC diagnostic ignored "-Wconversion"               // warning: conversion to 'xxxx' from 'xxxx' may alter its value
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"        // warning: format not a string literal, format string not checked
+#pragma GCC diagnostic ignored "-Wstrict-overflow"          // warning: assuming signed overflow does not occur when assuming that (X - c) > X is always false
+#if __GNUC__ >= 8
+#pragma GCC diagnostic ignored "-Wclass-memaccess"          // warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead
+#endif
+#endif
+
+// When using CTRL+TAB (or Gamepad Square+L/R) we delay the visual a little in order to reduce visual noise doing a fast switch.
+static const float NAV_WINDOWING_HIGHLIGHT_DELAY   = 0.20f; // Time before the highlight and screen dimming starts fading in
+static const float NAV_WINDOWING_LIST_APPEAR_DELAY = 0.15f; // Time before the window list starts to appear
+
+//-------------------------------------------------------------------------
+// [SECTION] FORWARD DECLARATIONS
+//-------------------------------------------------------------------------
+
+static void             SetCurrentWindow(ImGuiWindow* window);
+static void             SetWindowPos(ImGuiWindow* window, const ImVec2& pos, ImGuiCond cond);
+static void             SetWindowSize(ImGuiWindow* window, const ImVec2& size, ImGuiCond cond);
+static void             SetWindowCollapsed(ImGuiWindow* window, bool collapsed, ImGuiCond cond);
+static void             FindHoveredWindow();
+static ImGuiWindow*     CreateNewWindow(const char* name, ImVec2 size, ImGuiWindowFlags flags);
+static void             CheckStacksSize(ImGuiWindow* window, bool write);
+static ImVec2           CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window, bool snap_on_edges);
+
+static void             AddDrawListToDrawData(ImVector<ImDrawList*>* out_list, ImDrawList* draw_list);
+static void             AddWindowToDrawData(ImVector<ImDrawList*>* out_list, ImGuiWindow* window);
+static void             AddWindowToSortedBuffer(ImVector<ImGuiWindow*>* out_sorted_windows, ImGuiWindow* window);
+
+static ImRect           GetViewportRect();
+
+// Settings
+static void*            SettingsHandlerWindow_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name);
+static void             SettingsHandlerWindow_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, void* entry, const char* line);
+static void             SettingsHandlerWindow_WriteAll(ImGuiContext* imgui_ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf);
+
+// Platform Dependents default implementation for IO functions
+static const char*      GetClipboardTextFn_DefaultImpl(void* user_data);
+static void             SetClipboardTextFn_DefaultImpl(void* user_data, const char* text);
+static void             ImeSetInputScreenPosFn_DefaultImpl(int x, int y);
+
+namespace ImGui
+{
+static bool             BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, bool border, ImGuiWindowFlags flags);
+
+// Navigation
+static void             NavUpdate();
+static void             NavUpdateWindowing();
+static void             NavUpdateWindowingList();
+static void             NavUpdateMoveResult();
+static float            NavUpdatePageUpPageDown(int allowed_dir_flags);
+static inline void      NavUpdateAnyRequestFlag();
+static void             NavProcessItem(ImGuiWindow* window, const ImRect& nav_bb, const ImGuiID id);
+static ImVec2           NavCalcPreferredRefPos();
+static void             NavSaveLastChildNavWindow(ImGuiWindow* nav_window);
+static ImGuiWindow*     NavRestoreLastChildNavWindow(ImGuiWindow* window);
+
+// Misc
+static void             UpdateMouseInputs();
+static void             UpdateMouseWheel();
+static void             UpdateManualResize(ImGuiWindow* window, const ImVec2& size_auto_fit, int* border_held, int resize_grip_count, ImU32 resize_grip_col[4]);
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] CONTEXT AND MEMORY ALLOCATORS
+//-----------------------------------------------------------------------------
+
+// Current context pointer. Implicitly used by all ImGui functions. Always assumed to be != NULL.
+// CreateContext() will automatically set this pointer if it is NULL. Change to a different context by calling ImGui::SetCurrentContext().
+// If you use DLL hotreloading you might need to call SetCurrentContext() after reloading code from this file.
+// ImGui functions are not thread-safe because of this pointer. If you want thread-safety to allow N threads to access N different contexts, you can:
+// - Change this variable to use thread local storage. You may #define GImGui in imconfig.h for that purpose. Future development aim to make this context pointer explicit to all calls. Also read https://github.com/ocornut/imgui/issues/586
+// - Having multiple instances of the ImGui code compiled inside different namespace (easiest/safest, if you have a finite number of contexts)
+#ifndef GImGui
+ImGuiContext*   GImGui = NULL;
+#endif
+
+// Memory Allocator functions. Use SetAllocatorFunctions() to change them.
+// If you use DLL hotreloading you might need to call SetAllocatorFunctions() after reloading code from this file.
+// Otherwise, you probably don't want to modify them mid-program, and if you use global/static e.g. ImVector<> instances you may need to keep them accessible during program destruction.
+#ifndef IMGUI_DISABLE_DEFAULT_ALLOCATORS
+static void*   MallocWrapper(size_t size, void* user_data)    { (void)user_data; return malloc(size); }
+static void    FreeWrapper(void* ptr, void* user_data)        { (void)user_data; free(ptr); }
+#else
+static void*   MallocWrapper(size_t size, void* user_data)    { (void)user_data; (void)size; IM_ASSERT(0); return NULL; }
+static void    FreeWrapper(void* ptr, void* user_data)        { (void)user_data; (void)ptr; IM_ASSERT(0); }
+#endif
+
+static void*  (*GImAllocatorAllocFunc)(size_t size, void* user_data) = MallocWrapper;
+static void   (*GImAllocatorFreeFunc)(void* ptr, void* user_data) = FreeWrapper;
+static void*    GImAllocatorUserData = NULL;
+
+//-----------------------------------------------------------------------------
+// [SECTION] MAIN USER FACING STRUCTURES (ImGuiStyle, ImGuiIO)
+//-----------------------------------------------------------------------------
+
+ImGuiStyle::ImGuiStyle()
+{
+    Alpha                   = 1.0f;             // Global alpha applies to everything in ImGui
+    WindowPadding           = ImVec2(8,8);      // Padding within a window
+    WindowRounding          = 7.0f;             // Radius of window corners rounding. Set to 0.0f to have rectangular windows
+    WindowBorderSize        = 1.0f;             // Thickness of border around windows. Generally set to 0.0f or 1.0f. Other values not well tested.
+    WindowMinSize           = ImVec2(32,32);    // Minimum window size
+    WindowTitleAlign        = ImVec2(0.0f,0.5f);// Alignment for title bar text
+    ChildRounding           = 0.0f;             // Radius of child window corners rounding. Set to 0.0f to have rectangular child windows
+    ChildBorderSize         = 1.0f;             // Thickness of border around child windows. Generally set to 0.0f or 1.0f. Other values not well tested.
+    PopupRounding           = 0.0f;             // Radius of popup window corners rounding. Set to 0.0f to have rectangular child windows
+    PopupBorderSize         = 1.0f;             // Thickness of border around popup or tooltip windows. Generally set to 0.0f or 1.0f. Other values not well tested.
+    FramePadding            = ImVec2(4,3);      // Padding within a framed rectangle (used by most widgets)
+    FrameRounding           = 0.0f;             // Radius of frame corners rounding. Set to 0.0f to have rectangular frames (used by most widgets).
+    FrameBorderSize         = 0.0f;             // Thickness of border around frames. Generally set to 0.0f or 1.0f. Other values not well tested.
+    ItemSpacing             = ImVec2(8,4);      // Horizontal and vertical spacing between widgets/lines
+    ItemInnerSpacing        = ImVec2(4,4);      // Horizontal and vertical spacing between within elements of a composed widget (e.g. a slider and its label)
+    TouchExtraPadding       = ImVec2(0,0);      // Expand reactive bounding box for touch-based system where touch position is not accurate enough. Unfortunately we don't sort widgets so priority on overlap will always be given to the first widget. So don't grow this too much!
+    IndentSpacing           = 21.0f;            // Horizontal spacing when e.g. entering a tree node. Generally == (FontSize + FramePadding.x*2).
+    ColumnsMinSpacing       = 6.0f;             // Minimum horizontal spacing between two columns
+    ScrollbarSize           = 16.0f;            // Width of the vertical scrollbar, Height of the horizontal scrollbar
+    ScrollbarRounding       = 9.0f;             // Radius of grab corners rounding for scrollbar
+    GrabMinSize             = 10.0f;            // Minimum width/height of a grab box for slider/scrollbar
+    GrabRounding            = 0.0f;             // Radius of grabs corners rounding. Set to 0.0f to have rectangular slider grabs.
+    ButtonTextAlign         = ImVec2(0.5f,0.5f);// Alignment of button text when button is larger than text.
+    DisplayWindowPadding    = ImVec2(20,20);    // Window position are clamped to be visible within the display area by at least this amount. Only applies to regular windows.
+    DisplaySafeAreaPadding  = ImVec2(3,3);      // If you cannot see the edge of your screen (e.g. on a TV) increase the safe area padding. Covers popups/tooltips as well regular windows.
+    MouseCursorScale        = 1.0f;             // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). May be removed later.
+    AntiAliasedLines        = true;             // Enable anti-aliasing on lines/borders. Disable if you are really short on CPU/GPU.
+    AntiAliasedFill         = true;             // Enable anti-aliasing on filled shapes (rounded rectangles, circles, etc.)
+    CurveTessellationTol    = 1.25f;            // Tessellation tolerance when using PathBezierCurveTo() without a specific number of segments. Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality.
+
+    // Default theme
+    ImGui::StyleColorsDark(this);
+}
+
+// To scale your entire UI (e.g. if you want your app to use High DPI or generally be DPI aware) you may use this helper function. Scaling the fonts is done separately and is up to you.
+// Important: This operation is lossy because we round all sizes to integer. If you need to change your scale multiples, call this over a freshly initialized ImGuiStyle structure rather than scaling multiple times.
+void ImGuiStyle::ScaleAllSizes(float scale_factor)
+{
+    WindowPadding = ImFloor(WindowPadding * scale_factor);
+    WindowRounding = ImFloor(WindowRounding * scale_factor);
+    WindowMinSize = ImFloor(WindowMinSize * scale_factor);
+    ChildRounding = ImFloor(ChildRounding * scale_factor);
+    PopupRounding = ImFloor(PopupRounding * scale_factor);
+    FramePadding = ImFloor(FramePadding * scale_factor);
+    FrameRounding = ImFloor(FrameRounding * scale_factor);
+    ItemSpacing = ImFloor(ItemSpacing * scale_factor);
+    ItemInnerSpacing = ImFloor(ItemInnerSpacing * scale_factor);
+    TouchExtraPadding = ImFloor(TouchExtraPadding * scale_factor);
+    IndentSpacing = ImFloor(IndentSpacing * scale_factor);
+    ColumnsMinSpacing = ImFloor(ColumnsMinSpacing * scale_factor);
+    ScrollbarSize = ImFloor(ScrollbarSize * scale_factor);
+    ScrollbarRounding = ImFloor(ScrollbarRounding * scale_factor);
+    GrabMinSize = ImFloor(GrabMinSize * scale_factor);
+    GrabRounding = ImFloor(GrabRounding * scale_factor);
+    DisplayWindowPadding = ImFloor(DisplayWindowPadding * scale_factor);
+    DisplaySafeAreaPadding = ImFloor(DisplaySafeAreaPadding * scale_factor);
+    MouseCursorScale = ImFloor(MouseCursorScale * scale_factor);
+}
+
+ImGuiIO::ImGuiIO()
+{
+    // Most fields are initialized with zero
+    memset(this, 0, sizeof(*this));
+
+    // Settings
+    ConfigFlags = 0x00;
+    BackendFlags = 0x00;
+    DisplaySize = ImVec2(-1.0f, -1.0f);
+    DeltaTime = 1.0f/60.0f;
+    IniSavingRate = 5.0f;
+    IniFilename = "imgui.ini";
+    LogFilename = "imgui_log.txt";
+    MouseDoubleClickTime = 0.30f;
+    MouseDoubleClickMaxDist = 6.0f;
+    for (int i = 0; i < ImGuiKey_COUNT; i++)
+        KeyMap[i] = -1;
+    KeyRepeatDelay = 0.250f;
+    KeyRepeatRate = 0.050f;
+    UserData = NULL;
+
+    Fonts = NULL;
+    FontGlobalScale = 1.0f;
+    FontDefault = NULL;
+    FontAllowUserScaling = false;
+    DisplayFramebufferScale = ImVec2(1.0f, 1.0f);
+    DisplayVisibleMin = DisplayVisibleMax = ImVec2(0.0f, 0.0f);
+
+    // Miscellaneous configuration options
+#ifdef __APPLE__
+    ConfigMacOSXBehaviors = true;  // Set Mac OS X style defaults based on __APPLE__ compile time flag
+#else
+    ConfigMacOSXBehaviors = false;
+#endif
+    ConfigInputTextCursorBlink = true;
+    ConfigResizeWindowsFromEdges = false;
+
+    // Settings (User Functions)
+    GetClipboardTextFn = GetClipboardTextFn_DefaultImpl;   // Platform dependent default implementations
+    SetClipboardTextFn = SetClipboardTextFn_DefaultImpl;
+    ClipboardUserData = NULL;
+    ImeSetInputScreenPosFn = ImeSetInputScreenPosFn_DefaultImpl;
+    ImeWindowHandle = NULL;
+
+#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
+    RenderDrawListsFn = NULL;
+#endif
+
+    // Input (NB: we already have memset zero the entire structure)
+    MousePos = ImVec2(-FLT_MAX, -FLT_MAX);
+    MousePosPrev = ImVec2(-FLT_MAX, -FLT_MAX);
+    MouseDragThreshold = 6.0f;
+    for (int i = 0; i < IM_ARRAYSIZE(MouseDownDuration); i++) MouseDownDuration[i] = MouseDownDurationPrev[i] = -1.0f;
+    for (int i = 0; i < IM_ARRAYSIZE(KeysDownDuration); i++) KeysDownDuration[i]  = KeysDownDurationPrev[i] = -1.0f;
+    for (int i = 0; i < IM_ARRAYSIZE(NavInputsDownDuration); i++) NavInputsDownDuration[i] = -1.0f;
+}
+
+// Pass in translated ASCII characters for text input.
+// - with glfw you can get those from the callback set in glfwSetCharCallback()
+// - on Windows you can get those using ToAscii+keyboard state, or via the WM_CHAR message
+void ImGuiIO::AddInputCharacter(ImWchar c)
+{
+    const int n = ImStrlenW(InputCharacters);
+    if (n + 1 < IM_ARRAYSIZE(InputCharacters))
+    {
+        InputCharacters[n] = c;
+        InputCharacters[n+1] = '\0';
+    }
+}
+
+void ImGuiIO::AddInputCharactersUTF8(const char* utf8_chars)
+{
+    // We can't pass more wchars than ImGuiIO::InputCharacters[] can hold so don't convert more
+    const int wchars_buf_len = sizeof(ImGuiIO::InputCharacters) / sizeof(ImWchar);
+    ImWchar wchars[wchars_buf_len];
+    ImTextStrFromUtf8(wchars, wchars_buf_len, utf8_chars, NULL);
+    for (int i = 0; i < wchars_buf_len && wchars[i] != 0; i++)
+        AddInputCharacter(wchars[i]);
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] MISC HELPER/UTILITIES (Maths, String, Format, Hash, File functions)
+//-----------------------------------------------------------------------------
+
+ImVec2 ImLineClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& p)
+{
+    ImVec2 ap = p - a;
+    ImVec2 ab_dir = b - a;
+    float dot = ap.x * ab_dir.x + ap.y * ab_dir.y;
+    if (dot < 0.0f)
+        return a;
+    float ab_len_sqr = ab_dir.x * ab_dir.x + ab_dir.y * ab_dir.y;
+    if (dot > ab_len_sqr)
+        return b;
+    return a + ab_dir * dot / ab_len_sqr;
+}
+
+bool ImTriangleContainsPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p)
+{
+    bool b1 = ((p.x - b.x) * (a.y - b.y) - (p.y - b.y) * (a.x - b.x)) < 0.0f;
+    bool b2 = ((p.x - c.x) * (b.y - c.y) - (p.y - c.y) * (b.x - c.x)) < 0.0f;
+    bool b3 = ((p.x - a.x) * (c.y - a.y) - (p.y - a.y) * (c.x - a.x)) < 0.0f;
+    return ((b1 == b2) && (b2 == b3));
+}
+
+void ImTriangleBarycentricCoords(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p, float& out_u, float& out_v, float& out_w)
+{
+    ImVec2 v0 = b - a;
+    ImVec2 v1 = c - a;
+    ImVec2 v2 = p - a;
+    const float denom = v0.x * v1.y - v1.x * v0.y;
+    out_v = (v2.x * v1.y - v1.x * v2.y) / denom;
+    out_w = (v0.x * v2.y - v2.x * v0.y) / denom;
+    out_u = 1.0f - out_v - out_w;
+}
+
+ImVec2 ImTriangleClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p)
+{
+    ImVec2 proj_ab = ImLineClosestPoint(a, b, p);
+    ImVec2 proj_bc = ImLineClosestPoint(b, c, p);
+    ImVec2 proj_ca = ImLineClosestPoint(c, a, p);
+    float dist2_ab = ImLengthSqr(p - proj_ab);
+    float dist2_bc = ImLengthSqr(p - proj_bc);
+    float dist2_ca = ImLengthSqr(p - proj_ca);
+    float m = ImMin(dist2_ab, ImMin(dist2_bc, dist2_ca));
+    if (m == dist2_ab)
+        return proj_ab;
+    if (m == dist2_bc)
+        return proj_bc;
+    return proj_ca;
+}
+
+int ImStricmp(const char* str1, const char* str2)
+{
+    int d;
+    while ((d = toupper(*str2) - toupper(*str1)) == 0 && *str1) { str1++; str2++; }
+    return d;
+}
+
+int ImStrnicmp(const char* str1, const char* str2, size_t count)
+{
+    int d = 0;
+    while (count > 0 && (d = toupper(*str2) - toupper(*str1)) == 0 && *str1) { str1++; str2++; count--; }
+    return d;
+}
+
+void ImStrncpy(char* dst, const char* src, size_t count)
+{
+    if (count < 1) return;
+    strncpy(dst, src, count);
+    dst[count-1] = 0;
+}
+
+char* ImStrdup(const char *str)
+{
+    size_t len = strlen(str) + 1;
+    void* buf = ImGui::MemAlloc(len);
+    return (char*)memcpy(buf, (const void*)str, len);
+}
+
+const char* ImStrchrRange(const char* str, const char* str_end, char c)
+{
+    for ( ; str < str_end; str++)
+        if (*str == c)
+            return str;
+    return NULL;
+}
+
+int ImStrlenW(const ImWchar* str)
+{
+    int n = 0;
+    while (*str++) n++;
+    return n;
+}
+
+const ImWchar* ImStrbolW(const ImWchar* buf_mid_line, const ImWchar* buf_begin) // find beginning-of-line
+{
+    while (buf_mid_line > buf_begin && buf_mid_line[-1] != '\n')
+        buf_mid_line--;
+    return buf_mid_line;
+}
+
+const char* ImStristr(const char* haystack, const char* haystack_end, const char* needle, const char* needle_end)
+{
+    if (!needle_end)
+        needle_end = needle + strlen(needle);
+
+    const char un0 = (char)toupper(*needle);
+    while ((!haystack_end && *haystack) || (haystack_end && haystack < haystack_end))
+    {
+        if (toupper(*haystack) == un0)
+        {
+            const char* b = needle + 1;
+            for (const char* a = haystack + 1; b < needle_end; a++, b++)
+                if (toupper(*a) != toupper(*b))
+                    break;
+            if (b == needle_end)
+                return haystack;
+        }
+        haystack++;
+    }
+    return NULL;
+}
+
+// Trim str by offsetting contents when there's leading data + writing a \0 at the trailing position. We use this in situation where the cost is negligible.
+void ImStrTrimBlanks(char* buf)
+{
+    char* p = buf;
+    while (p[0] == ' ' || p[0] == '\t')     // Leading blanks
+        p++;
+    char* p_start = p;
+    while (*p != 0)                         // Find end of string
+        p++;
+    while (p > p_start && (p[-1] == ' ' || p[-1] == '\t'))  // Trailing blanks
+        p--;
+    if (p_start != buf)                     // Copy memory if we had leading blanks
+        memmove(buf, p_start, p - p_start);
+    buf[p - p_start] = 0;                   // Zero terminate
+}
+
+// A) MSVC version appears to return -1 on overflow, whereas glibc appears to return total count (which may be >= buf_size).
+// Ideally we would test for only one of those limits at runtime depending on the behavior the vsnprintf(), but trying to deduct it at compile time sounds like a pandora can of worm.
+// B) When buf==NULL vsnprintf() will return the output size.
+#ifndef IMGUI_DISABLE_FORMAT_STRING_FUNCTIONS
+
+#if defined(_MSC_VER) && !defined(vsnprintf)
+#define vsnprintf _vsnprintf
+#endif
+
+int ImFormatString(char* buf, size_t buf_size, const char* fmt, ...)
+{
+    va_list args;
+    va_start(args, fmt);
+    int w = vsnprintf(buf, buf_size, fmt, args);
+    va_end(args);
+    if (buf == NULL)
+        return w;
+    if (w == -1 || w >= (int)buf_size)
+        w = (int)buf_size - 1;
+    buf[w] = 0;
+    return w;
+}
+
+int ImFormatStringV(char* buf, size_t buf_size, const char* fmt, va_list args)
+{
+    int w = vsnprintf(buf, buf_size, fmt, args);
+    if (buf == NULL)
+        return w;
+    if (w == -1 || w >= (int)buf_size)
+        w = (int)buf_size - 1;
+    buf[w] = 0;
+    return w;
+}
+#endif // #ifdef IMGUI_DISABLE_FORMAT_STRING_FUNCTIONS
+
+// Pass data_size==0 for zero-terminated strings
+// FIXME-OPT: Replace with e.g. FNV1a hash? CRC32 pretty much randomly access 1KB. Need to do proper measurements.
+ImU32 ImHash(const void* data, int data_size, ImU32 seed)
+{
+    static ImU32 crc32_lut[256] = { 0 };
+    if (!crc32_lut[1])
+    {
+        const ImU32 polynomial = 0xEDB88320;
+        for (ImU32 i = 0; i < 256; i++)
+        {
+            ImU32 crc = i;
+            for (ImU32 j = 0; j < 8; j++)
+                crc = (crc >> 1) ^ (ImU32(-int(crc & 1)) & polynomial);
+            crc32_lut[i] = crc;
+        }
+    }
+
+    seed = ~seed;
+    ImU32 crc = seed;
+    const unsigned char* current = (const unsigned char*)data;
+
+    if (data_size > 0)
+    {
+        // Known size
+        while (data_size--)
+            crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ *current++];
+    }
+    else
+    {
+        // Zero-terminated string
+        while (unsigned char c = *current++)
+        {
+            // We support a syntax of "label###id" where only "###id" is included in the hash, and only "label" gets displayed.
+            // Because this syntax is rarely used we are optimizing for the common case.
+            // - If we reach ### in the string we discard the hash so far and reset to the seed.
+            // - We don't do 'current += 2; continue;' after handling ### to keep the code smaller.
+            if (c == '#' && current[0] == '#' && current[1] == '#')
+                crc = seed;
+            crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ c];
+        }
+    }
+    return ~crc;
+}
+
+FILE* ImFileOpen(const char* filename, const char* mode)
+{
+#if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__GNUC__)
+    // We need a fopen() wrapper because MSVC/Windows fopen doesn't handle UTF-8 filenames. Converting both strings from UTF-8 to wchar format (using a single allocation, because we can)
+    const int filename_wsize = ImTextCountCharsFromUtf8(filename, NULL) + 1;
+    const int mode_wsize = ImTextCountCharsFromUtf8(mode, NULL) + 1;
+    ImVector<ImWchar> buf;
+    buf.resize(filename_wsize + mode_wsize);
+    ImTextStrFromUtf8(&buf[0], filename_wsize, filename, NULL);
+    ImTextStrFromUtf8(&buf[filename_wsize], mode_wsize, mode, NULL);
+    return _wfopen((wchar_t*)&buf[0], (wchar_t*)&buf[filename_wsize]);
+#else
+    return fopen(filename, mode);
+#endif
+}
+
+// Load file content into memory
+// Memory allocated with ImGui::MemAlloc(), must be freed by user using ImGui::MemFree()
+void* ImFileLoadToMemory(const char* filename, const char* file_open_mode, size_t* out_file_size, int padding_bytes)
+{
+    IM_ASSERT(filename && file_open_mode);
+    if (out_file_size)
+        *out_file_size = 0;
+
+    FILE* f;
+    if ((f = ImFileOpen(filename, file_open_mode)) == NULL)
+        return NULL;
+
+    long file_size_signed;
+    if (fseek(f, 0, SEEK_END) || (file_size_signed = ftell(f)) == -1 || fseek(f, 0, SEEK_SET))
+    {
+        fclose(f);
+        return NULL;
+    }
+
+    size_t file_size = (size_t)file_size_signed;
+    void* file_data = ImGui::MemAlloc(file_size + padding_bytes);
+    if (file_data == NULL)
+    {
+        fclose(f);
+        return NULL;
+    }
+    if (fread(file_data, 1, file_size, f) != file_size)
+    {
+        fclose(f);
+        ImGui::MemFree(file_data);
+        return NULL;
+    }
+    if (padding_bytes > 0)
+        memset((void*)(((char*)file_data) + file_size), 0, (size_t)padding_bytes);
+
+    fclose(f);
+    if (out_file_size)
+        *out_file_size = file_size;
+
+    return file_data;
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] MISC HELPERS/UTILITIES (ImText* functions)
+//-----------------------------------------------------------------------------
+
+// Convert UTF-8 to 32-bits character, process single character input.
+// Based on stb_from_utf8() from github.com/nothings/stb/
+// We handle UTF-8 decoding error by skipping forward.
+int ImTextCharFromUtf8(unsigned int* out_char, const char* in_text, const char* in_text_end)
+{
+    unsigned int c = (unsigned int)-1;
+    const unsigned char* str = (const unsigned char*)in_text;
+    if (!(*str & 0x80))
+    {
+        c = (unsigned int)(*str++);
+        *out_char = c;
+        return 1;
+    }
+    if ((*str & 0xe0) == 0xc0)
+    {
+        *out_char = 0xFFFD; // will be invalid but not end of string
+        if (in_text_end && in_text_end - (const char*)str < 2) return 1;
+        if (*str < 0xc2) return 2;
+        c = (unsigned int)((*str++ & 0x1f) << 6);
+        if ((*str & 0xc0) != 0x80) return 2;
+        c += (*str++ & 0x3f);
+        *out_char = c;
+        return 2;
+    }
+    if ((*str & 0xf0) == 0xe0)
+    {
+        *out_char = 0xFFFD; // will be invalid but not end of string
+        if (in_text_end && in_text_end - (const char*)str < 3) return 1;
+        if (*str == 0xe0 && (str[1] < 0xa0 || str[1] > 0xbf)) return 3;
+        if (*str == 0xed && str[1] > 0x9f) return 3; // str[1] < 0x80 is checked below
+        c = (unsigned int)((*str++ & 0x0f) << 12);
+        if ((*str & 0xc0) != 0x80) return 3;
+        c += (unsigned int)((*str++ & 0x3f) << 6);
+        if ((*str & 0xc0) != 0x80) return 3;
+        c += (*str++ & 0x3f);
+        *out_char = c;
+        return 3;
+    }
+    if ((*str & 0xf8) == 0xf0)
+    {
+        *out_char = 0xFFFD; // will be invalid but not end of string
+        if (in_text_end && in_text_end - (const char*)str < 4) return 1;
+        if (*str > 0xf4) return 4;
+        if (*str == 0xf0 && (str[1] < 0x90 || str[1] > 0xbf)) return 4;
+        if (*str == 0xf4 && str[1] > 0x8f) return 4; // str[1] < 0x80 is checked below
+        c = (unsigned int)((*str++ & 0x07) << 18);
+        if ((*str & 0xc0) != 0x80) return 4;
+        c += (unsigned int)((*str++ & 0x3f) << 12);
+        if ((*str & 0xc0) != 0x80) return 4;
+        c += (unsigned int)((*str++ & 0x3f) << 6);
+        if ((*str & 0xc0) != 0x80) return 4;
+        c += (*str++ & 0x3f);
+        // utf-8 encodings of values used in surrogate pairs are invalid
+        if ((c & 0xFFFFF800) == 0xD800) return 4;
+        *out_char = c;
+        return 4;
+    }
+    *out_char = 0;
+    return 0;
+}
+
+int ImTextStrFromUtf8(ImWchar* buf, int buf_size, const char* in_text, const char* in_text_end, const char** in_text_remaining)
+{
+    ImWchar* buf_out = buf;
+    ImWchar* buf_end = buf + buf_size;
+    while (buf_out < buf_end-1 && (!in_text_end || in_text < in_text_end) && *in_text)
+    {
+        unsigned int c;
+        in_text += ImTextCharFromUtf8(&c, in_text, in_text_end);
+        if (c == 0)
+            break;
+        if (c < 0x10000)    // FIXME: Losing characters that don't fit in 2 bytes
+            *buf_out++ = (ImWchar)c;
+    }
+    *buf_out = 0;
+    if (in_text_remaining)
+        *in_text_remaining = in_text;
+    return (int)(buf_out - buf);
+}
+
+int ImTextCountCharsFromUtf8(const char* in_text, const char* in_text_end)
+{
+    int char_count = 0;
+    while ((!in_text_end || in_text < in_text_end) && *in_text)
+    {
+        unsigned int c;
+        in_text += ImTextCharFromUtf8(&c, in_text, in_text_end);
+        if (c == 0)
+            break;
+        if (c < 0x10000)
+            char_count++;
+    }
+    return char_count;
+}
+
+// Based on stb_to_utf8() from github.com/nothings/stb/
+static inline int ImTextCharToUtf8(char* buf, int buf_size, unsigned int c)
+{
+    if (c < 0x80)
+    {
+        buf[0] = (char)c;
+        return 1;
+    }
+    if (c < 0x800)
+    {
+        if (buf_size < 2) return 0;
+        buf[0] = (char)(0xc0 + (c >> 6));
+        buf[1] = (char)(0x80 + (c & 0x3f));
+        return 2;
+    }
+    if (c >= 0xdc00 && c < 0xe000)
+    {
+        return 0;
+    }
+    if (c >= 0xd800 && c < 0xdc00)
+    {
+        if (buf_size < 4) return 0;
+        buf[0] = (char)(0xf0 + (c >> 18));
+        buf[1] = (char)(0x80 + ((c >> 12) & 0x3f));
+        buf[2] = (char)(0x80 + ((c >> 6) & 0x3f));
+        buf[3] = (char)(0x80 + ((c ) & 0x3f));
+        return 4;
+    }
+    //else if (c < 0x10000)
+    {
+        if (buf_size < 3) return 0;
+        buf[0] = (char)(0xe0 + (c >> 12));
+        buf[1] = (char)(0x80 + ((c>> 6) & 0x3f));
+        buf[2] = (char)(0x80 + ((c ) & 0x3f));
+        return 3;
+    }
+}
+
+// Not optimal but we very rarely use this function.
+int ImTextCountUtf8BytesFromChar(const char* in_text, const char* in_text_end)
+{
+    unsigned int dummy = 0;
+    return ImTextCharFromUtf8(&dummy, in_text, in_text_end);
+}
+
+static inline int ImTextCountUtf8BytesFromChar(unsigned int c)
+{
+    if (c < 0x80) return 1;
+    if (c < 0x800) return 2;
+    if (c >= 0xdc00 && c < 0xe000) return 0;
+    if (c >= 0xd800 && c < 0xdc00) return 4;
+    return 3;
+}
+
+int ImTextStrToUtf8(char* buf, int buf_size, const ImWchar* in_text, const ImWchar* in_text_end)
+{
+    char* buf_out = buf;
+    const char* buf_end = buf + buf_size;
+    while (buf_out < buf_end-1 && (!in_text_end || in_text < in_text_end) && *in_text)
+    {
+        unsigned int c = (unsigned int)(*in_text++);
+        if (c < 0x80)
+            *buf_out++ = (char)c;
+        else
+            buf_out += ImTextCharToUtf8(buf_out, (int)(buf_end-buf_out-1), c);
+    }
+    *buf_out = 0;
+    return (int)(buf_out - buf);
+}
+
+int ImTextCountUtf8BytesFromStr(const ImWchar* in_text, const ImWchar* in_text_end)
+{
+    int bytes_count = 0;
+    while ((!in_text_end || in_text < in_text_end) && *in_text)
+    {
+        unsigned int c = (unsigned int)(*in_text++);
+        if (c < 0x80)
+            bytes_count++;
+        else
+            bytes_count += ImTextCountUtf8BytesFromChar(c);
+    }
+    return bytes_count;
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] MISC HELPER/UTILTIES (Color functions)
+// Note: The Convert functions are early design which are not consistent with other API.
+//-----------------------------------------------------------------------------
+
+ImVec4 ImGui::ColorConvertU32ToFloat4(ImU32 in)
+{
+    float s = 1.0f/255.0f;
+    return ImVec4(
+        ((in >> IM_COL32_R_SHIFT) & 0xFF) * s,
+        ((in >> IM_COL32_G_SHIFT) & 0xFF) * s,
+        ((in >> IM_COL32_B_SHIFT) & 0xFF) * s,
+        ((in >> IM_COL32_A_SHIFT) & 0xFF) * s);
+}
+
+ImU32 ImGui::ColorConvertFloat4ToU32(const ImVec4& in)
+{
+    ImU32 out;
+    out  = ((ImU32)IM_F32_TO_INT8_SAT(in.x)) << IM_COL32_R_SHIFT;
+    out |= ((ImU32)IM_F32_TO_INT8_SAT(in.y)) << IM_COL32_G_SHIFT;
+    out |= ((ImU32)IM_F32_TO_INT8_SAT(in.z)) << IM_COL32_B_SHIFT;
+    out |= ((ImU32)IM_F32_TO_INT8_SAT(in.w)) << IM_COL32_A_SHIFT;
+    return out;
+}
+
+// Convert rgb floats ([0-1],[0-1],[0-1]) to hsv floats ([0-1],[0-1],[0-1]), from Foley & van Dam p592
+// Optimized http://lolengine.net/blog/2013/01/13/fast-rgb-to-hsv
+void ImGui::ColorConvertRGBtoHSV(float r, float g, float b, float& out_h, float& out_s, float& out_v)
+{
+    float K = 0.f;
+    if (g < b)
+    {
+        ImSwap(g, b);
+        K = -1.f;
+    }
+    if (r < g)
+    {
+        ImSwap(r, g);
+        K = -2.f / 6.f - K;
+    }
+
+    const float chroma = r - (g < b ? g : b);
+    out_h = ImFabs(K + (g - b) / (6.f * chroma + 1e-20f));
+    out_s = chroma / (r + 1e-20f);
+    out_v = r;
+}
+
+// Convert hsv floats ([0-1],[0-1],[0-1]) to rgb floats ([0-1],[0-1],[0-1]), from Foley & van Dam p593
+// also http://en.wikipedia.org/wiki/HSL_and_HSV
+void ImGui::ColorConvertHSVtoRGB(float h, float s, float v, float& out_r, float& out_g, float& out_b)
+{
+    if (s == 0.0f)
+    {
+        // gray
+        out_r = out_g = out_b = v;
+        return;
+    }
+
+    h = ImFmod(h, 1.0f) / (60.0f/360.0f);
+    int   i = (int)h;
+    float f = h - (float)i;
+    float p = v * (1.0f - s);
+    float q = v * (1.0f - s * f);
+    float t = v * (1.0f - s * (1.0f - f));
+
+    switch (i)
+    {
+    case 0: out_r = v; out_g = t; out_b = p; break;
+    case 1: out_r = q; out_g = v; out_b = p; break;
+    case 2: out_r = p; out_g = v; out_b = t; break;
+    case 3: out_r = p; out_g = q; out_b = v; break;
+    case 4: out_r = t; out_g = p; out_b = v; break;
+    case 5: default: out_r = v; out_g = p; out_b = q; break;
+    }
+}
+
+ImU32 ImGui::GetColorU32(ImGuiCol idx, float alpha_mul)
+{
+    ImGuiStyle& style = GImGui->Style;
+    ImVec4 c = style.Colors[idx];
+    c.w *= style.Alpha * alpha_mul;
+    return ColorConvertFloat4ToU32(c);
+}
+
+ImU32 ImGui::GetColorU32(const ImVec4& col)
+{
+    ImGuiStyle& style = GImGui->Style;
+    ImVec4 c = col;
+    c.w *= style.Alpha;
+    return ColorConvertFloat4ToU32(c);
+}
+
+const ImVec4& ImGui::GetStyleColorVec4(ImGuiCol idx)
+{
+    ImGuiStyle& style = GImGui->Style;
+    return style.Colors[idx];
+}
+
+ImU32 ImGui::GetColorU32(ImU32 col)
+{
+    float style_alpha = GImGui->Style.Alpha;
+    if (style_alpha >= 1.0f)
+        return col;
+    ImU32 a = (col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT;
+    a = (ImU32)(a * style_alpha); // We don't need to clamp 0..255 because Style.Alpha is in 0..1 range.
+    return (col & ~IM_COL32_A_MASK) | (a << IM_COL32_A_SHIFT);
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] ImGuiStorage
+// Helper: Key->value storage
+//-----------------------------------------------------------------------------
+
+// std::lower_bound but without the bullshit
+static ImVector<ImGuiStorage::Pair>::iterator LowerBound(ImVector<ImGuiStorage::Pair>& data, ImGuiID key)
+{
+    ImVector<ImGuiStorage::Pair>::iterator first = data.begin();
+    ImVector<ImGuiStorage::Pair>::iterator last = data.end();
+    size_t count = (size_t)(last - first);
+    while (count > 0)
+    {
+        size_t count2 = count >> 1;
+        ImVector<ImGuiStorage::Pair>::iterator mid = first + count2;
+        if (mid->key < key)
+        {
+            first = ++mid;
+            count -= count2 + 1;
+        }
+        else
+        {
+            count = count2;
+        }
+    }
+    return first;
+}
+
+// For quicker full rebuild of a storage (instead of an incremental one), you may add all your contents and then sort once.
+void ImGuiStorage::BuildSortByKey()
+{
+    struct StaticFunc
+    {
+        static int IMGUI_CDECL PairCompareByID(const void* lhs, const void* rhs)
+        {
+            // We can't just do a subtraction because qsort uses signed integers and subtracting our ID doesn't play well with that.
+            if (((const Pair*)lhs)->key > ((const Pair*)rhs)->key) return +1;
+            if (((const Pair*)lhs)->key < ((const Pair*)rhs)->key) return -1;
+            return 0;
+        }
+    };
+    if (Data.Size > 1)
+        ImQsort(Data.Data, (size_t)Data.Size, sizeof(Pair), StaticFunc::PairCompareByID);
+}
+
+int ImGuiStorage::GetInt(ImGuiID key, int default_val) const
+{
+    ImVector<Pair>::iterator it = LowerBound(const_cast<ImVector<ImGuiStorage::Pair>&>(Data), key);
+    if (it == Data.end() || it->key != key)
+        return default_val;
+    return it->val_i;
+}
+
+bool ImGuiStorage::GetBool(ImGuiID key, bool default_val) const
+{
+    return GetInt(key, default_val ? 1 : 0) != 0;
+}
+
+float ImGuiStorage::GetFloat(ImGuiID key, float default_val) const
+{
+    ImVector<Pair>::iterator it = LowerBound(const_cast<ImVector<ImGuiStorage::Pair>&>(Data), key);
+    if (it == Data.end() || it->key != key)
+        return default_val;
+    return it->val_f;
+}
+
+void* ImGuiStorage::GetVoidPtr(ImGuiID key) const
+{
+    ImVector<Pair>::iterator it = LowerBound(const_cast<ImVector<ImGuiStorage::Pair>&>(Data), key);
+    if (it == Data.end() || it->key != key)
+        return NULL;
+    return it->val_p;
+}
+
+// References are only valid until a new value is added to the storage. Calling a Set***() function or a Get***Ref() function invalidates the pointer.
+int* ImGuiStorage::GetIntRef(ImGuiID key, int default_val)
+{
+    ImVector<Pair>::iterator it = LowerBound(Data, key);
+    if (it == Data.end() || it->key != key)
+        it = Data.insert(it, Pair(key, default_val));
+    return &it->val_i;
+}
+
+bool* ImGuiStorage::GetBoolRef(ImGuiID key, bool default_val)
+{
+    return (bool*)GetIntRef(key, default_val ? 1 : 0);
+}
+
+float* ImGuiStorage::GetFloatRef(ImGuiID key, float default_val)
+{
+    ImVector<Pair>::iterator it = LowerBound(Data, key);
+    if (it == Data.end() || it->key != key)
+        it = Data.insert(it, Pair(key, default_val));
+    return &it->val_f;
+}
+
+void** ImGuiStorage::GetVoidPtrRef(ImGuiID key, void* default_val)
+{
+    ImVector<Pair>::iterator it = LowerBound(Data, key);
+    if (it == Data.end() || it->key != key)
+        it = Data.insert(it, Pair(key, default_val));
+    return &it->val_p;
+}
+
+// FIXME-OPT: Need a way to reuse the result of lower_bound when doing GetInt()/SetInt() - not too bad because it only happens on explicit interaction (maximum one a frame)
+void ImGuiStorage::SetInt(ImGuiID key, int val)
+{
+    ImVector<Pair>::iterator it = LowerBound(Data, key);
+    if (it == Data.end() || it->key != key)
+    {
+        Data.insert(it, Pair(key, val));
+        return;
+    }
+    it->val_i = val;
+}
+
+void ImGuiStorage::SetBool(ImGuiID key, bool val)
+{
+    SetInt(key, val ? 1 : 0);
+}
+
+void ImGuiStorage::SetFloat(ImGuiID key, float val)
+{
+    ImVector<Pair>::iterator it = LowerBound(Data, key);
+    if (it == Data.end() || it->key != key)
+    {
+        Data.insert(it, Pair(key, val));
+        return;
+    }
+    it->val_f = val;
+}
+
+void ImGuiStorage::SetVoidPtr(ImGuiID key, void* val)
+{
+    ImVector<Pair>::iterator it = LowerBound(Data, key);
+    if (it == Data.end() || it->key != key)
+    {
+        Data.insert(it, Pair(key, val));
+        return;
+    }
+    it->val_p = val;
+}
+
+void ImGuiStorage::SetAllInt(int v)
+{
+    for (int i = 0; i < Data.Size; i++)
+        Data[i].val_i = v;
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] ImGuiTextFilter
+//-----------------------------------------------------------------------------
+
+// Helper: Parse and apply text filters. In format "aaaaa[,bbbb][,ccccc]"
+ImGuiTextFilter::ImGuiTextFilter(const char* default_filter)
+{
+    if (default_filter)
+    {
+        ImStrncpy(InputBuf, default_filter, IM_ARRAYSIZE(InputBuf));
+        Build();
+    }
+    else
+    {
+        InputBuf[0] = 0;
+        CountGrep = 0;
+    }
+}
+
+bool ImGuiTextFilter::Draw(const char* label, float width)
+{
+    if (width != 0.0f)
+        ImGui::PushItemWidth(width);
+    bool value_changed = ImGui::InputText(label, InputBuf, IM_ARRAYSIZE(InputBuf));
+    if (width != 0.0f)
+        ImGui::PopItemWidth();
+    if (value_changed)
+        Build();
+    return value_changed;
+}
+
+void ImGuiTextFilter::TextRange::split(char separator, ImVector<TextRange>* out) const
+{
+    out->resize(0);
+    const char* wb = b;
+    const char* we = wb;
+    while (we < e)
+    {
+        if (*we == separator)
+        {
+            out->push_back(TextRange(wb, we));
+            wb = we + 1;
+        }
+        we++;
+    }
+    if (wb != we)
+        out->push_back(TextRange(wb, we));
+}
+
+void ImGuiTextFilter::Build()
+{
+    Filters.resize(0);
+    TextRange input_range(InputBuf, InputBuf+strlen(InputBuf));
+    input_range.split(',', &Filters);
+
+    CountGrep = 0;
+    for (int i = 0; i != Filters.Size; i++)
+    {
+        TextRange& f = Filters[i];
+        while (f.b < f.e && ImCharIsBlankA(f.b[0]))
+            f.b++;
+        while (f.e > f.b && ImCharIsBlankA(f.e[-1]))
+            f.e--;
+        if (f.empty())
+            continue;
+        if (Filters[i].b[0] != '-')
+            CountGrep += 1;
+    }
+}
+
+bool ImGuiTextFilter::PassFilter(const char* text, const char* text_end) const
+{
+    if (Filters.empty())
+        return true;
+
+    if (text == NULL)
+        text = "";
+
+    for (int i = 0; i != Filters.Size; i++)
+    {
+        const TextRange& f = Filters[i];
+        if (f.empty())
+            continue;
+        if (f.b[0] == '-')
+        {
+            // Subtract
+            if (ImStristr(text, text_end, f.begin()+1, f.end()) != NULL)
+                return false;
+        }
+        else
+        {
+            // Grep
+            if (ImStristr(text, text_end, f.begin(), f.end()) != NULL)
+                return true;
+        }
+    }
+
+    // Implicit * grep
+    if (CountGrep == 0)
+        return true;
+
+    return false;
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] ImGuiTextBuffer
+//-----------------------------------------------------------------------------
+
+// On some platform vsnprintf() takes va_list by reference and modifies it.
+// va_copy is the 'correct' way to copy a va_list but Visual Studio prior to 2013 doesn't have it.
+#ifndef va_copy
+#if defined(__GNUC__) || defined(__clang__)
+#define va_copy(dest, src) __builtin_va_copy(dest, src)
+#else
+#define va_copy(dest, src) (dest = src)
+#endif
+#endif
+
+// Helper: Text buffer for logging/accumulating text
+void ImGuiTextBuffer::appendfv(const char* fmt, va_list args)
+{
+    va_list args_copy;
+    va_copy(args_copy, args);
+
+    int len = ImFormatStringV(NULL, 0, fmt, args);         // FIXME-OPT: could do a first pass write attempt, likely successful on first pass.
+    if (len <= 0)
+    {
+        va_end(args_copy);
+        return;
+    }
+
+    const int write_off = Buf.Size;
+    const int needed_sz = write_off + len;
+    if (write_off + len >= Buf.Capacity)
+    {
+        int double_capacity = Buf.Capacity * 2;
+        Buf.reserve(needed_sz > double_capacity ? needed_sz : double_capacity);
+    }
+
+    Buf.resize(needed_sz);
+    ImFormatStringV(&Buf[write_off - 1], (size_t)len + 1, fmt, args_copy);
+    va_end(args_copy);
+}
+
+void ImGuiTextBuffer::appendf(const char* fmt, ...)
+{
+    va_list args;
+    va_start(args, fmt);
+    appendfv(fmt, args);
+    va_end(args);
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] ImGuiListClipper
+// This is currently not as flexible/powerful as it should be, needs some rework (see TODO)
+//-----------------------------------------------------------------------------
+
+static void SetCursorPosYAndSetupDummyPrevLine(float pos_y, float line_height)
+{
+    // Set cursor position and a few other things so that SetScrollHere() and Columns() can work when seeking cursor.
+    // FIXME: It is problematic that we have to do that here, because custom/equivalent end-user code would stumble on the same issue.
+    // The clipper should probably have a 4th step to display the last item in a regular manner.
+    ImGui::SetCursorPosY(pos_y);
+    ImGuiWindow* window = ImGui::GetCurrentWindow();
+    window->DC.CursorPosPrevLine.y = window->DC.CursorPos.y - line_height;      // Setting those fields so that SetScrollHere() can properly function after the end of our clipper usage.
+    window->DC.PrevLineSize.y = (line_height - GImGui->Style.ItemSpacing.y);    // If we end up needing more accurate data (to e.g. use SameLine) we may as well make the clipper have a fourth step to let user process and display the last item in their list.
+    if (window->DC.ColumnsSet)
+        window->DC.ColumnsSet->LineMinY = window->DC.CursorPos.y;           // Setting this so that cell Y position are set properly
+}
+
+// Use case A: Begin() called from constructor with items_height<0, then called again from Sync() in StepNo 1
+// Use case B: Begin() called from constructor with items_height>0
+// FIXME-LEGACY: Ideally we should remove the Begin/End functions but they are part of the legacy API we still support. This is why some of the code in Step() calling Begin() and reassign some fields, spaghetti style.
+void ImGuiListClipper::Begin(int count, float items_height)
+{
+    StartPosY = ImGui::GetCursorPosY();
+    ItemsHeight = items_height;
+    ItemsCount = count;
+    StepNo = 0;
+    DisplayEnd = DisplayStart = -1;
+    if (ItemsHeight > 0.0f)
+    {
+        ImGui::CalcListClipping(ItemsCount, ItemsHeight, &DisplayStart, &DisplayEnd); // calculate how many to clip/display
+        if (DisplayStart > 0)
+            SetCursorPosYAndSetupDummyPrevLine(StartPosY + DisplayStart * ItemsHeight, ItemsHeight); // advance cursor
+        StepNo = 2;
+    }
+}
+
+void ImGuiListClipper::End()
+{
+    if (ItemsCount < 0)
+        return;
+    // In theory here we should assert that ImGui::GetCursorPosY() == StartPosY + DisplayEnd * ItemsHeight, but it feels saner to just seek at the end and not assert/crash the user.
+    if (ItemsCount < INT_MAX)
+        SetCursorPosYAndSetupDummyPrevLine(StartPosY + ItemsCount * ItemsHeight, ItemsHeight); // advance cursor
+    ItemsCount = -1;
+    StepNo = 3;
+}
+
+bool ImGuiListClipper::Step()
+{
+    if (ItemsCount == 0 || ImGui::GetCurrentWindowRead()->SkipItems)
+    {
+        ItemsCount = -1;
+        return false;
+    }
+    if (StepNo == 0) // Step 0: the clipper let you process the first element, regardless of it being visible or not, so we can measure the element height.
+    {
+        DisplayStart = 0;
+        DisplayEnd = 1;
+        StartPosY = ImGui::GetCursorPosY();
+        StepNo = 1;
+        return true;
+    }
+    if (StepNo == 1) // Step 1: the clipper infer height from first element, calculate the actual range of elements to display, and position the cursor before the first element.
+    {
+        if (ItemsCount == 1) { ItemsCount = -1; return false; }
+        float items_height = ImGui::GetCursorPosY() - StartPosY;
+        IM_ASSERT(items_height > 0.0f);   // If this triggers, it means Item 0 hasn't moved the cursor vertically
+        Begin(ItemsCount-1, items_height);
+        DisplayStart++;
+        DisplayEnd++;
+        StepNo = 3;
+        return true;
+    }
+    if (StepNo == 2) // Step 2: dummy step only required if an explicit items_height was passed to constructor or Begin() and user still call Step(). Does nothing and switch to Step 3.
+    {
+        IM_ASSERT(DisplayStart >= 0 && DisplayEnd >= 0);
+        StepNo = 3;
+        return true;
+    }
+    if (StepNo == 3) // Step 3: the clipper validate that we have reached the expected Y position (corresponding to element DisplayEnd), advance the cursor to the end of the list and then returns 'false' to end the loop.
+        End();
+    return false;
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] RENDER HELPERS
+// Those (internal) functions are currently quite a legacy mess - their signature and behavior will change.
+// Also see imgui_draw.cpp for some more which have been reworked to not rely on ImGui:: state.
+//-----------------------------------------------------------------------------
+
+const char* ImGui::FindRenderedTextEnd(const char* text, const char* text_end)
+{
+    const char* text_display_end = text;
+    if (!text_end)
+        text_end = (const char*)-1;
+
+    while (text_display_end < text_end && *text_display_end != '\0' && (text_display_end[0] != '#' || text_display_end[1] != '#'))
+        text_display_end++;
+    return text_display_end;
+}
+
+// Internal ImGui functions to render text
+// RenderText***() functions calls ImDrawList::AddText() calls ImBitmapFont::RenderText()
+void ImGui::RenderText(ImVec2 pos, const char* text, const char* text_end, bool hide_text_after_hash)
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiWindow* window = g.CurrentWindow;
+
+    // Hide anything after a '##' string
+    const char* text_display_end;
+    if (hide_text_after_hash)
+    {
+        text_display_end = FindRenderedTextEnd(text, text_end);
+    }
+    else
+    {
+        if (!text_end)
+            text_end = text + strlen(text); // FIXME-OPT
+        text_display_end = text_end;
+    }
+
+    if (text != text_display_end)
+    {
+        window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_display_end);
+        if (g.LogEnabled)
+            LogRenderedText(&pos, text, text_display_end);
+    }
+}
+
+void ImGui::RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end, float wrap_width)
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiWindow* window = g.CurrentWindow;
+
+    if (!text_end)
+        text_end = text + strlen(text); // FIXME-OPT
+
+    if (text != text_end)
+    {
+        window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_end, wrap_width);
+        if (g.LogEnabled)
+            LogRenderedText(&pos, text, text_end);
+    }
+}
+
+// Default clip_rect uses (pos_min,pos_max)
+// Handle clipping on CPU immediately (vs typically let the GPU clip the triangles that are overlapping the clipping rectangle edges)
+void ImGui::RenderTextClipped(const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_end, const ImVec2* text_size_if_known, const ImVec2& align, const ImRect* clip_rect)
+{
+    // Hide anything after a '##' string
+    const char* text_display_end = FindRenderedTextEnd(text, text_end);
+    const int text_len = (int)(text_display_end - text);
+    if (text_len == 0)
+        return;
+
+    ImGuiContext& g = *GImGui;
+    ImGuiWindow* window = g.CurrentWindow;
+
+    // Perform CPU side clipping for single clipped element to avoid using scissor state
+    ImVec2 pos = pos_min;
+    const ImVec2 text_size = text_size_if_known ? *text_size_if_known : CalcTextSize(text, text_display_end, false, 0.0f);
+
+    const ImVec2* clip_min = clip_rect ? &clip_rect->Min : &pos_min;
+    const ImVec2* clip_max = clip_rect ? &clip_rect->Max : &pos_max;
+    bool need_clipping = (pos.x + text_size.x >= clip_max->x) || (pos.y + text_size.y >= clip_max->y);
+    if (clip_rect) // If we had no explicit clipping rectangle then pos==clip_min
+        need_clipping |= (pos.x < clip_min->x) || (pos.y < clip_min->y);
+
+    // Align whole block. We should defer that to the better rendering function when we'll have support for individual line alignment.
+    if (align.x > 0.0f) pos.x = ImMax(pos.x, pos.x + (pos_max.x - pos.x - text_size.x) * align.x);
+    if (align.y > 0.0f) pos.y = ImMax(pos.y, pos.y + (pos_max.y - pos.y - text_size.y) * align.y);
+
+    // Render
+    if (need_clipping)
+    {
+        ImVec4 fine_clip_rect(clip_min->x, clip_min->y, clip_max->x, clip_max->y);
+        window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_display_end, 0.0f, &fine_clip_rect);
+    }
+    else
+    {
+        window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_display_end, 0.0f, NULL);
+    }
+    if (g.LogEnabled)
+        LogRenderedText(&pos, text, text_display_end);
+}
+
+// Render a rectangle shaped with optional rounding and borders
+void ImGui::RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool border, float rounding)
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiWindow* window = g.CurrentWindow;
+    window->DrawList->AddRectFilled(p_min, p_max, fill_col, rounding);
+    const float border_size = g.Style.FrameBorderSize;
+    if (border && border_size > 0.0f)
+    {
+        window->DrawList->AddRect(p_min+ImVec2(1,1), p_max+ImVec2(1,1), GetColorU32(ImGuiCol_BorderShadow), rounding, ImDrawCornerFlags_All, border_size);
+        window->DrawList->AddRect(p_min, p_max, GetColorU32(ImGuiCol_Border), rounding, ImDrawCornerFlags_All, border_size);
+    }
+}
+
+void ImGui::RenderFrameBorder(ImVec2 p_min, ImVec2 p_max, float rounding)
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiWindow* window = g.CurrentWindow;
+    const float border_size = g.Style.FrameBorderSize;
+    if (border_size > 0.0f)
+    {
+        window->DrawList->AddRect(p_min+ImVec2(1,1), p_max+ImVec2(1,1), GetColorU32(ImGuiCol_BorderShadow), rounding, ImDrawCornerFlags_All, border_size);
+        window->DrawList->AddRect(p_min, p_max, GetColorU32(ImGuiCol_Border), rounding, ImDrawCornerFlags_All, border_size);
+    }
+}
+
+// Render an arrow aimed to be aligned with text (p_min is a position in the same space text would be positioned). To e.g. denote expanded/collapsed state
+void ImGui::RenderArrow(ImVec2 p_min, ImGuiDir dir, float scale)
+{
+    ImGuiContext& g = *GImGui;
+
+    const float h = g.FontSize * 1.00f;
+    float r = h * 0.40f * scale;
+    ImVec2 center = p_min + ImVec2(h * 0.50f, h * 0.50f * scale);
+
+    ImVec2 a, b, c;
+    switch (dir)
+    {
+    case ImGuiDir_Up:
+    case ImGuiDir_Down:
+        if (dir == ImGuiDir_Up) r = -r;
+        a = ImVec2(+0.000f,+0.750f) * r;
+        b = ImVec2(-0.866f,-0.750f) * r;
+        c = ImVec2(+0.866f,-0.750f) * r;
+        break;
+    case ImGuiDir_Left:
+    case ImGuiDir_Right:
+        if (dir == ImGuiDir_Left) r = -r;
+        a = ImVec2(+0.750f,+0.000f) * r;
+        b = ImVec2(-0.750f,+0.866f) * r;
+        c = ImVec2(-0.750f,-0.866f) * r;
+        break;
+    case ImGuiDir_None:
+    case ImGuiDir_COUNT:
+        IM_ASSERT(0);
+        break;
+    }
+
+    g.CurrentWindow->DrawList->AddTriangleFilled(center + a, center + b, center + c, GetColorU32(ImGuiCol_Text));
+}
+
+void ImGui::RenderBullet(ImVec2 pos)
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiWindow* window = g.CurrentWindow;
+    window->DrawList->AddCircleFilled(pos, g.FontSize*0.20f, GetColorU32(ImGuiCol_Text), 8);
+}
+
+void ImGui::RenderCheckMark(ImVec2 pos, ImU32 col, float sz)
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiWindow* window = g.CurrentWindow;
+
+    float thickness = ImMax(sz / 5.0f, 1.0f);
+    sz -= thickness*0.5f;
+    pos += ImVec2(thickness*0.25f, thickness*0.25f);
+
+    float third = sz / 3.0f;
+    float bx = pos.x + third;
+    float by = pos.y + sz - third*0.5f;
+    window->DrawList->PathLineTo(ImVec2(bx - third, by - third));
+    window->DrawList->PathLineTo(ImVec2(bx, by));
+    window->DrawList->PathLineTo(ImVec2(bx + third*2, by - third*2));
+    window->DrawList->PathStroke(col, false, thickness);
+}
+
+void ImGui::RenderNavHighlight(const ImRect& bb, ImGuiID id, ImGuiNavHighlightFlags flags)
+{
+    ImGuiContext& g = *GImGui;
+    if (id != g.NavId)
+        return;
+    if (g.NavDisableHighlight && !(flags & ImGuiNavHighlightFlags_AlwaysDraw))
+        return;
+    ImGuiWindow* window = ImGui::GetCurrentWindow();
+    if (window->DC.NavHideHighlightOneFrame)
+        return;
+
+    float rounding = (flags & ImGuiNavHighlightFlags_NoRounding) ? 0.0f : g.Style.FrameRounding;
+    ImRect display_rect = bb;
+    display_rect.ClipWith(window->ClipRect);
+    if (flags & ImGuiNavHighlightFlags_TypeDefault)
+    {
+        const float THICKNESS = 2.0f;
+        const float DISTANCE = 3.0f + THICKNESS * 0.5f;
+        display_rect.Expand(ImVec2(DISTANCE,DISTANCE));
+        bool fully_visible = window->ClipRect.Contains(display_rect);
+        if (!fully_visible)
+            window->DrawList->PushClipRect(display_rect.Min, display_rect.Max);
+        window->DrawList->AddRect(display_rect.Min + ImVec2(THICKNESS*0.5f,THICKNESS*0.5f), display_rect.Max - ImVec2(THICKNESS*0.5f,THICKNESS*0.5f), GetColorU32(ImGuiCol_NavHighlight), rounding, ImDrawCornerFlags_All, THICKNESS);
+        if (!fully_visible)
+            window->DrawList->PopClipRect();
+    }
+    if (flags & ImGuiNavHighlightFlags_TypeThin)
+    {
+        window->DrawList->AddRect(display_rect.Min, display_rect.Max, GetColorU32(ImGuiCol_NavHighlight), rounding, ~0, 1.0f);
+    }
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] MAIN CODE (most of the code! lots of stuff, needs tidying up!)
+//-----------------------------------------------------------------------------
+
+// ImGuiWindow is mostly a dumb struct. It merely has a constructor and a few helper methods
+ImGuiWindow::ImGuiWindow(ImGuiContext* context, const char* name)
+    : DrawListInst(&context->DrawListSharedData)
+{
+    Name = ImStrdup(name);
+    ID = ImHash(name, 0);
+    IDStack.push_back(ID);
+    Flags = 0;
+    Pos = ImVec2(0.0f, 0.0f);
+    Size = SizeFull = ImVec2(0.0f, 0.0f);
+    SizeContents = SizeContentsExplicit = ImVec2(0.0f, 0.0f);
+    WindowPadding = ImVec2(0.0f, 0.0f);
+    WindowRounding = 0.0f;
+    WindowBorderSize = 0.0f;
+    MoveId = GetID("#MOVE");
+    ChildId = 0;
+    Scroll = ImVec2(0.0f, 0.0f);
+    ScrollTarget = ImVec2(FLT_MAX, FLT_MAX);
+    ScrollTargetCenterRatio = ImVec2(0.5f, 0.5f);
+    ScrollbarSizes = ImVec2(0.0f, 0.0f);
+    ScrollbarX = ScrollbarY = false;
+    Active = WasActive = false;
+    WriteAccessed = false;
+    Collapsed = false;
+    WantCollapseToggle = false;
+    SkipItems = false;
+    Appearing = false;
+    Hidden = false;
+    HasCloseButton = false;
+    BeginCount = 0;
+    BeginOrderWithinParent = -1;
+    BeginOrderWithinContext = -1;
+    PopupId = 0;
+    AutoFitFramesX = AutoFitFramesY = -1;
+    AutoFitOnlyGrows = false;
+    AutoFitChildAxises = 0x00;
+    AutoPosLastDirection = ImGuiDir_None;
+    HiddenFramesRegular = HiddenFramesForResize = 0;
+    SetWindowPosAllowFlags = SetWindowSizeAllowFlags = SetWindowCollapsedAllowFlags = ImGuiCond_Always | ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing;
+    SetWindowPosVal = SetWindowPosPivot = ImVec2(FLT_MAX, FLT_MAX);
+
+    LastFrameActive = -1;
+    ItemWidthDefault = 0.0f;
+    FontWindowScale = 1.0f;
+    SettingsIdx = -1;
+
+    DrawList = &DrawListInst;
+    DrawList->_OwnerName = Name;
+    ParentWindow = NULL;
+    RootWindow = NULL;
+    RootWindowForTitleBarHighlight = NULL;
+    RootWindowForNav = NULL;
+
+    NavLastIds[0] = NavLastIds[1] = 0;
+    NavRectRel[0] = NavRectRel[1] = ImRect();
+    NavLastChildNavWindow = NULL;
+
+    FocusIdxAllCounter = FocusIdxTabCounter = -1;
+    FocusIdxAllRequestCurrent = FocusIdxTabRequestCurrent = INT_MAX;
+    FocusIdxAllRequestNext = FocusIdxTabRequestNext = INT_MAX;
+}
+
+ImGuiWindow::~ImGuiWindow()
+{
+    IM_ASSERT(DrawList == &DrawListInst);
+    IM_DELETE(Name);
+    for (int i = 0; i != ColumnsStorage.Size; i++)
+        ColumnsStorage[i].~ImGuiColumnsSet();
+}
+
+ImGuiID ImGuiWindow::GetID(const char* str, const char* str_end)
+{
+    ImGuiID seed = IDStack.back();
+    ImGuiID id = ImHash(str, str_end ? (int)(str_end - str) : 0, seed);
+    ImGui::KeepAliveID(id);
+    return id;
+}
+
+ImGuiID ImGuiWindow::GetID(const void* ptr)
+{
+    ImGuiID seed = IDStack.back();
+    ImGuiID id = ImHash(&ptr, sizeof(void*), seed);
+    ImGui::KeepAliveID(id);
+    return id;
+}
+
+ImGuiID ImGuiWindow::GetIDNoKeepAlive(const char* str, const char* str_end)
+{
+    ImGuiID seed = IDStack.back();
+    return ImHash(str, str_end ? (int)(str_end - str) : 0, seed);
+}
+
+ImGuiID ImGuiWindow::GetIDNoKeepAlive(const void* ptr)
+{
+    ImGuiID seed = IDStack.back();
+    return ImHash(&ptr, sizeof(void*), seed);
+}
+
+// This is only used in rare/specific situations to manufacture an ID out of nowhere.
+ImGuiID ImGuiWindow::GetIDFromRectangle(const ImRect& r_abs)
+{
+    ImGuiID seed = IDStack.back();
+    const int r_rel[4] = { (int)(r_abs.Min.x - Pos.x), (int)(r_abs.Min.y - Pos.y), (int)(r_abs.Max.x - Pos.x), (int)(r_abs.Max.y - Pos.y) };
+    ImGuiID id = ImHash(&r_rel, sizeof(r_rel), seed);
+    ImGui::KeepAliveID(id);
+    return id;
+}
+
+static void SetCurrentWindow(ImGuiWindow* window)
+{
+    ImGuiContext& g = *GImGui;
+    g.CurrentWindow = window;
+    if (window)
+        g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize();
+}
+
+void ImGui::SetNavID(ImGuiID id, int nav_layer)
+{
+    ImGuiContext& g = *GImGui;
+    IM_ASSERT(g.NavWindow);
+    IM_ASSERT(nav_layer == 0 || nav_layer == 1);
+    g.NavId = id;
+    g.NavWindow->NavLastIds[nav_layer] = id;
+}
+
+void ImGui::SetNavIDWithRectRel(ImGuiID id, int nav_layer, const ImRect& rect_rel)
+{
+    ImGuiContext& g = *GImGui;
+    SetNavID(id, nav_layer);
+    g.NavWindow->NavRectRel[nav_layer] = rect_rel;
+    g.NavMousePosDirty = true;
+    g.NavDisableHighlight = false;
+    g.NavDisableMouseHover = true;
+}
+
+void ImGui::SetActiveID(ImGuiID id, ImGuiWindow* window)
+{
+    ImGuiContext& g = *GImGui;
+    g.ActiveIdIsJustActivated = (g.ActiveId != id);
+    if (g.ActiveIdIsJustActivated)
+    {
+        g.ActiveIdTimer = 0.0f;
+        g.ActiveIdHasBeenEdited = false;
+        if (id != 0)
+        {
+            g.LastActiveId = id;
+            g.LastActiveIdTimer = 0.0f;
+        }
+    }
+    g.ActiveId = id;
+    g.ActiveIdAllowNavDirFlags = 0;
+    g.ActiveIdAllowOverlap = false;
+    g.ActiveIdWindow = window;
+    if (id)
+    {
+        g.ActiveIdIsAlive = id;
+        g.ActiveIdSource = (g.NavActivateId == id || g.NavInputId == id || g.NavJustTabbedId == id || g.NavJustMovedToId == id) ? ImGuiInputSource_Nav : ImGuiInputSource_Mouse;
+    }
+}
+
+void ImGui::SetFocusID(ImGuiID id, ImGuiWindow* window)
+{
+    ImGuiContext& g = *GImGui;
+    IM_ASSERT(id != 0);
+
+    // Assume that SetFocusID() is called in the context where its NavLayer is the current layer, which is the case everywhere we call it.
+    const int nav_layer = window->DC.NavLayerCurrent;
+    if (g.NavWindow != window)
+        g.NavInitRequest = false;
+    g.NavId = id;
+    g.NavWindow = window;
+    g.NavLayer = nav_layer;
+    window->NavLastIds[nav_layer] = id;
+    if (window->DC.LastItemId == id)
+        window->NavRectRel[nav_layer] = ImRect(window->DC.LastItemRect.Min - window->Pos, window->DC.LastItemRect.Max - window->Pos);
+
+    if (g.ActiveIdSource == ImGuiInputSource_Nav)
+        g.NavDisableMouseHover = true;
+    else
+        g.NavDisableHighlight = true;
+}
+
+void ImGui::ClearActiveID()
+{
+    SetActiveID(0, NULL);
+}
+
+void ImGui::SetHoveredID(ImGuiID id)
+{
+    ImGuiContext& g = *GImGui;
+    g.HoveredId = id;
+    g.HoveredIdAllowOverlap = false;
+    if (id != 0 && g.HoveredIdPreviousFrame != id)
+        g.HoveredIdTimer = 0.0f;
+}
+
+ImGuiID ImGui::GetHoveredID()
+{
+    ImGuiContext& g = *GImGui;
+    return g.HoveredId ? g.HoveredId : g.HoveredIdPreviousFrame;
+}
+
+void ImGui::KeepAliveID(ImGuiID id)
+{
+    ImGuiContext& g = *GImGui;
+    if (g.ActiveId == id)
+        g.ActiveIdIsAlive = id;
+    if (g.ActiveIdPreviousFrame == id)
+        g.ActiveIdPreviousFrameIsAlive = true;
+}
+
+void ImGui::MarkItemEdited(ImGuiID id)
+{
+    // This marking is solely to be able to provide info for IsItemDeactivatedAfterEdit().
+    // ActiveId might have been released by the time we call this (as in the typical press/release button behavior) but still need need to fill the data.
+    (void)id; // Avoid unused variable warnings when asserts are compiled out.
+    ImGuiContext& g = *GImGui;
+    IM_ASSERT(g.ActiveId == id || g.ActiveId == 0 || g.DragDropActive);
+    //IM_ASSERT(g.CurrentWindow->DC.LastItemId == id);
+    g.ActiveIdHasBeenEdited = true;
+    g.CurrentWindow->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_Edited;
+}
+
+static inline bool IsWindowContentHoverable(ImGuiWindow* window, ImGuiHoveredFlags flags)
+{
+    // An active popup disable hovering on other windows (apart from its own children)
+    // FIXME-OPT: This could be cached/stored within the window.
+    ImGuiContext& g = *GImGui;
+    if (g.NavWindow)
+        if (ImGuiWindow* focused_root_window = g.NavWindow->RootWindow)
+            if (focused_root_window->WasActive && focused_root_window != window->RootWindow)
+            {
+                // For the purpose of those flags we differentiate "standard popup" from "modal popup"
+                // NB: The order of those two tests is important because Modal windows are also Popups.
+                if (focused_root_window->Flags & ImGuiWindowFlags_Modal)
+                    return false;
+                if ((focused_root_window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiHoveredFlags_AllowWhenBlockedByPopup))
+                    return false;
+            }
+
+    return true;
+}
+
+// Advance cursor given item size for layout.
+void ImGui::ItemSize(const ImVec2& size, float text_offset_y)
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiWindow* window = g.CurrentWindow;
+    if (window->SkipItems)
+        return;
+
+    // Always align ourselves on pixel boundaries
+    const float line_height = ImMax(window->DC.CurrentLineSize.y, size.y);
+    const float text_base_offset = ImMax(window->DC.CurrentLineTextBaseOffset, text_offset_y);
+    //if (g.IO.KeyAlt) window->DrawList->AddRect(window->DC.CursorPos, window->DC.CursorPos + ImVec2(size.x, line_height), IM_COL32(255,0,0,200)); // [DEBUG]
+    window->DC.CursorPosPrevLine = ImVec2(window->DC.CursorPos.x + size.x, window->DC.CursorPos.y);
+    window->DC.CursorPos = ImVec2((float)(int)(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x), (float)(int)(window->DC.CursorPos.y + line_height + g.Style.ItemSpacing.y));
+    window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, window->DC.CursorPosPrevLine.x);
+    window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, window->DC.CursorPos.y - g.Style.ItemSpacing.y);
+    //if (g.IO.KeyAlt) window->DrawList->AddCircle(window->DC.CursorMaxPos, 3.0f, IM_COL32(255,0,0,255), 4); // [DEBUG]
+
+    window->DC.PrevLineSize.y = line_height;
+    window->DC.PrevLineTextBaseOffset = text_base_offset;
+    window->DC.CurrentLineSize.y = window->DC.CurrentLineTextBaseOffset = 0.0f;
+
+    // Horizontal layout mode
+    if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
+        SameLine();
+}
+
+void ImGui::ItemSize(const ImRect& bb, float text_offset_y)
+{
+    ItemSize(bb.GetSize(), text_offset_y);
+}
+
+// Declare item bounding box for clipping and interaction.
+// Note that the size can be different than the one provided to ItemSize(). Typically, widgets that spread over available surface
+// declare their minimum size requirement to ItemSize() and then use a larger region for drawing/interaction, which is passed to ItemAdd().
+bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg)
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiWindow* window = g.CurrentWindow;
+
+    if (id != 0)
+    {
+        // Navigation processing runs prior to clipping early-out
+        //  (a) So that NavInitRequest can be honored, for newly opened windows to select a default widget
+        //  (b) So that we can scroll up/down past clipped items. This adds a small O(N) cost to regular navigation requests unfortunately, but it is still limited to one window.
+        //      it may not scale very well for windows with ten of thousands of item, but at least NavMoveRequest is only set on user interaction, aka maximum once a frame.
+        //      We could early out with "if (is_clipped && !g.NavInitRequest) return false;" but when we wouldn't be able to reach unclipped widgets. This would work if user had explicit scrolling control (e.g. mapped on a stick)
+        window->DC.NavLayerActiveMaskNext |= window->DC.NavLayerCurrentMask;
+        if (g.NavId == id || g.NavAnyRequest)
+            if (g.NavWindow->RootWindowForNav == window->RootWindowForNav)
+                if (window == g.NavWindow || ((window->Flags | g.NavWindow->Flags) & ImGuiWindowFlags_NavFlattened))
+                    NavProcessItem(window, nav_bb_arg ? *nav_bb_arg : bb, id);
+    }
+
+    window->DC.LastItemId = id;
+    window->DC.LastItemRect = bb;
+    window->DC.LastItemStatusFlags = 0;
+
+    // Clipping test
+    const bool is_clipped = IsClippedEx(bb, id, false);
+    if (is_clipped)
+        return false;
+    //if (g.IO.KeyAlt) window->DrawList->AddRect(bb.Min, bb.Max, IM_COL32(255,255,0,120)); // [DEBUG]
+
+    // We need to calculate this now to take account of the current clipping rectangle (as items like Selectable may change them)
+    if (IsMouseHoveringRect(bb.Min, bb.Max))
+        window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_HoveredRect;
+    return true;
+}
+
+// This is roughly matching the behavior of internal-facing ItemHoverable()
+// - we allow hovering to be true when ActiveId==window->MoveID, so that clicking on non-interactive items such as a Text() item still returns true with IsItemHovered()
+// - this should work even for non-interactive items that have no ID, so we cannot use LastItemId
+bool ImGui::IsItemHovered(ImGuiHoveredFlags flags)
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiWindow* window = g.CurrentWindow;
+    if (g.NavDisableMouseHover && !g.NavDisableHighlight)
+        return IsItemFocused();
+
+    // Test for bounding box overlap, as updated as ItemAdd()
+    if (!(window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect))
+        return false;
+    IM_ASSERT((flags & (ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows)) == 0);   // Flags not supported by this function
+
+    // Test if we are hovering the right window (our window could be behind another window)
+    // [2017/10/16] Reverted commit 344d48be3 and testing RootWindow instead. I believe it is correct to NOT test for RootWindow but this leaves us unable to use IsItemHovered() after EndChild() itself.
+    // Until a solution is found I believe reverting to the test from 2017/09/27 is safe since this was the test that has been running for a long while.
+    //if (g.HoveredWindow != window)
+    //    return false;
+    if (g.HoveredRootWindow != window->RootWindow && !(flags & ImGuiHoveredFlags_AllowWhenOverlapped))
+        return false;
+
+    // Test if another item is active (e.g. being dragged)
+    if (!(flags & ImGuiHoveredFlags_AllowWhenBlockedByActiveItem))
+        if (g.ActiveId != 0 && g.ActiveId != window->DC.LastItemId && !g.ActiveIdAllowOverlap && g.ActiveId != window->MoveId)
+            return false;
+
+    // Test if interactions on this window are blocked by an active popup or modal
+    if (!IsWindowContentHoverable(window, flags))
+        return false;
+
+    // Test if the item is disabled
+    if ((window->DC.ItemFlags & ImGuiItemFlags_Disabled) && !(flags & ImGuiHoveredFlags_AllowWhenDisabled))
+        return false;
+
+    // Special handling for the 1st item after Begin() which represent the title bar. When the window is collapsed (SkipItems==true) that last item will never be overwritten so we need to detect tht case.
+    if (window->DC.LastItemId == window->MoveId && window->WriteAccessed)
+        return false;
+    return true;
+}
+
+// Internal facing ItemHoverable() used when submitting widgets. Differs slightly from IsItemHovered().
+bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id)
+{
+    ImGuiContext& g = *GImGui;
+    if (g.HoveredId != 0 && g.HoveredId != id && !g.HoveredIdAllowOverlap)
+        return false;
+
+    ImGuiWindow* window = g.CurrentWindow;
+    if (g.HoveredWindow != window)
+        return false;
+    if (g.ActiveId != 0 && g.ActiveId != id && !g.ActiveIdAllowOverlap)
+        return false;
+    if (!IsMouseHoveringRect(bb.Min, bb.Max))
+        return false;
+    if (g.NavDisableMouseHover || !IsWindowContentHoverable(window, ImGuiHoveredFlags_None))
+        return false;
+    if (window->DC.ItemFlags & ImGuiItemFlags_Disabled)
+        return false;
+
+    SetHoveredID(id);
+    return true;
+}
+
+bool ImGui::IsClippedEx(const ImRect& bb, ImGuiID id, bool clip_even_when_logged)
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiWindow* window = g.CurrentWindow;
+    if (!bb.Overlaps(window->ClipRect))
+        if (id == 0 || id != g.ActiveId)
+            if (clip_even_when_logged || !g.LogEnabled)
+                return true;
+    return false;
+}
+
+bool ImGui::FocusableItemRegister(ImGuiWindow* window, ImGuiID id, bool tab_stop)
+{
+    ImGuiContext& g = *GImGui;
+
+    const bool allow_keyboard_focus = (window->DC.ItemFlags & (ImGuiItemFlags_AllowKeyboardFocus | ImGuiItemFlags_Disabled)) == ImGuiItemFlags_AllowKeyboardFocus;
+    window->FocusIdxAllCounter++;
+    if (allow_keyboard_focus)
+        window->FocusIdxTabCounter++;
+
+    // Process keyboard input at this point: TAB/Shift-TAB to tab out of the currently focused item.
+    // Note that we can always TAB out of a widget that doesn't allow tabbing in.
+    if (tab_stop && (g.ActiveId == id) && window->FocusIdxAllRequestNext == INT_MAX && window->FocusIdxTabRequestNext == INT_MAX && !g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_Tab))
+        window->FocusIdxTabRequestNext = window->FocusIdxTabCounter + (g.IO.KeyShift ? (allow_keyboard_focus ? -1 : 0) : +1); // Modulo on index will be applied at the end of frame once we've got the total counter of items.
+
+    if (window->FocusIdxAllCounter == window->FocusIdxAllRequestCurrent)
+        return true;
+    if (allow_keyboard_focus && window->FocusIdxTabCounter == window->FocusIdxTabRequestCurrent)
+    {
+        g.NavJustTabbedId = id;
+        return true;
+    }
+
+    return false;
+}
+
+void ImGui::FocusableItemUnregister(ImGuiWindow* window)
+{
+    window->FocusIdxAllCounter--;
+    window->FocusIdxTabCounter--;
+}
+
+ImVec2 ImGui::CalcItemSize(ImVec2 size, float default_x, float default_y)
+{
+    ImGuiContext& g = *GImGui;
+    ImVec2 content_max;
+    if (size.x < 0.0f || size.y < 0.0f)
+        content_max = g.CurrentWindow->Pos + GetContentRegionMax();
+    if (size.x <= 0.0f)
+        size.x = (size.x == 0.0f) ? default_x : ImMax(content_max.x - g.CurrentWindow->DC.CursorPos.x, 4.0f) + size.x;
+    if (size.y <= 0.0f)
+        size.y = (size.y == 0.0f) ? default_y : ImMax(content_max.y - g.CurrentWindow->DC.CursorPos.y, 4.0f) + size.y;
+    return size;
+}
+
+float ImGui::CalcWrapWidthForPos(const ImVec2& pos, float wrap_pos_x)
+{
+    if (wrap_pos_x < 0.0f)
+        return 0.0f;
+
+    ImGuiWindow* window = GetCurrentWindowRead();
+    if (wrap_pos_x == 0.0f)
+        wrap_pos_x = GetContentRegionMax().x + window->Pos.x;
+    else if (wrap_pos_x > 0.0f)
+        wrap_pos_x += window->Pos.x - window->Scroll.x; // wrap_pos_x is provided is window local space
+
+    return ImMax(wrap_pos_x - pos.x, 1.0f);
+}
+
+void* ImGui::MemAlloc(size_t size)
+{
+    if (ImGuiContext* ctx = GImGui)
+        ctx->IO.MetricsActiveAllocations++;
+    return GImAllocatorAllocFunc(size, GImAllocatorUserData);
+}
+
+void ImGui::MemFree(void* ptr)
+{
+    if (ptr) 
+        if (ImGuiContext* ctx = GImGui)
+            ctx->IO.MetricsActiveAllocations--;
+    return GImAllocatorFreeFunc(ptr, GImAllocatorUserData);
+}
+
+const char* ImGui::GetClipboardText()
+{
+    return GImGui->IO.GetClipboardTextFn ? GImGui->IO.GetClipboardTextFn(GImGui->IO.ClipboardUserData) : "";
+}
+
+void ImGui::SetClipboardText(const char* text)
+{
+    if (GImGui->IO.SetClipboardTextFn)
+        GImGui->IO.SetClipboardTextFn(GImGui->IO.ClipboardUserData, text);
+}
+
+const char* ImGui::GetVersion()
+{
+    return IMGUI_VERSION;
+}
+
+// Internal state access - if you want to share ImGui state between modules (e.g. DLL) or allocate it yourself
+// Note that we still point to some static data and members (such as GFontAtlas), so the state instance you end up using will point to the static data within its module
+ImGuiContext* ImGui::GetCurrentContext()
+{
+    return GImGui;
+}
+
+void ImGui::SetCurrentContext(ImGuiContext* ctx)
+{
+#ifdef IMGUI_SET_CURRENT_CONTEXT_FUNC
+    IMGUI_SET_CURRENT_CONTEXT_FUNC(ctx); // For custom thread-based hackery you may want to have control over this.
+#else
+    GImGui = ctx;
+#endif
+}
+
+// Helper function to verify that the type sizes are matching between the calling file's compilation unit and imgui.cpp's compilation unit
+// If the user has inconsistent compilation settings, imgui configuration #define, packing pragma, etc. you may see different structures from what imgui.cpp sees which is highly problematic.
+bool ImGui::DebugCheckVersionAndDataLayout(const char* version, size_t sz_io, size_t sz_style, size_t sz_vec2, size_t sz_vec4, size_t sz_vert)
+{
+    bool error = false;
+    if (strcmp(version, IMGUI_VERSION)!=0) { error = true; IM_ASSERT(strcmp(version,IMGUI_VERSION)==0 && "Mismatched version string!");  }
+    if (sz_io    != sizeof(ImGuiIO))       { error = true; IM_ASSERT(sz_io    == sizeof(ImGuiIO)      && "Mismatched struct layout!"); }
+    if (sz_style != sizeof(ImGuiStyle))    { error = true; IM_ASSERT(sz_style == sizeof(ImGuiStyle)   && "Mismatched struct layout!"); }
+    if (sz_vec2  != sizeof(ImVec2))        { error = true; IM_ASSERT(sz_vec2  == sizeof(ImVec2)       && "Mismatched struct layout!"); }
+    if (sz_vec4  != sizeof(ImVec4))        { error = true; IM_ASSERT(sz_vec4  == sizeof(ImVec4)       && "Mismatched struct layout!"); }
+    if (sz_vert  != sizeof(ImDrawVert))    { error = true; IM_ASSERT(sz_vert  == sizeof(ImDrawVert)   && "Mismatched struct layout!"); }
+    return !error;
+}
+
+void ImGui::SetAllocatorFunctions(void* (*alloc_func)(size_t sz, void* user_data), void(*free_func)(void* ptr, void* user_data), void* user_data)
+{
+    GImAllocatorAllocFunc = alloc_func;
+    GImAllocatorFreeFunc = free_func;
+    GImAllocatorUserData = user_data;
+}
+
+ImGuiContext* ImGui::CreateContext(ImFontAtlas* shared_font_atlas)
+{
+    ImGuiContext* ctx = IM_NEW(ImGuiContext)(shared_font_atlas);
+    if (GImGui == NULL)
+        SetCurrentContext(ctx);
+    Initialize(ctx);
+    return ctx;
+}
+
+void ImGui::DestroyContext(ImGuiContext* ctx)
+{
+    if (ctx == NULL)
+        ctx = GImGui;
+    Shutdown(ctx);
+    if (GImGui == ctx)
+        SetCurrentContext(NULL);
+    IM_DELETE(ctx);
+}
+
+ImGuiIO& ImGui::GetIO()
+{
+    IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() or ImGui::SetCurrentContext()?");
+    return GImGui->IO;
+}
+
+ImGuiStyle& ImGui::GetStyle()
+{
+    IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() or ImGui::SetCurrentContext()?");
+    return GImGui->Style;
+}
+
+// Same value as passed to the old io.RenderDrawListsFn function. Valid after Render() and until the next call to NewFrame()
+ImDrawData* ImGui::GetDrawData()
+{
+    ImGuiContext& g = *GImGui;
+    return g.DrawData.Valid ? &g.DrawData : NULL;
+}
+
+double ImGui::GetTime()
+{
+    return GImGui->Time;
+}
+
+int ImGui::GetFrameCount()
+{
+    return GImGui->FrameCount;
+}
+
+ImDrawList* ImGui::GetOverlayDrawList()
+{
+    return &GImGui->OverlayDrawList;
+}
+
+ImDrawListSharedData* ImGui::GetDrawListSharedData()
+{
+    return &GImGui->DrawListSharedData;
+}
+
+void ImGui::StartMouseMovingWindow(ImGuiWindow* window)
+{
+    // Set ActiveId even if the _NoMove flag is set. Without it, dragging away from a window with _NoMove would activate hover on other windows.
+    ImGuiContext& g = *GImGui;
+    FocusWindow(window);
+    SetActiveID(window->MoveId, window);
+    g.NavDisableHighlight = true;
+    g.ActiveIdClickOffset = g.IO.MousePos - window->RootWindow->Pos;
+    if (!(window->Flags & ImGuiWindowFlags_NoMove) && !(window->RootWindow->Flags & ImGuiWindowFlags_NoMove))
+        g.MovingWindow = window;
+}
+
+// Handle mouse moving window
+// Note: moving window with the navigation keys (Square + d-pad / CTRL+TAB + Arrows) are processed in NavUpdateWindowing()
+void ImGui::UpdateMouseMovingWindow()
+{
+    ImGuiContext& g = *GImGui;
+    if (g.MovingWindow != NULL)
+    {
+        // We actually want to move the root window. g.MovingWindow == window we clicked on (could be a child window).
+        // We track it to preserve Focus and so that generally ActiveIdWindow == MovingWindow and ActiveId == MovingWindow->MoveId for consistency.
+        KeepAliveID(g.ActiveId);
+        IM_ASSERT(g.MovingWindow && g.MovingWindow->RootWindow);
+        ImGuiWindow* moving_window = g.MovingWindow->RootWindow;
+        if (g.IO.MouseDown[0] && IsMousePosValid(&g.IO.MousePos))
+        {
+            ImVec2 pos = g.IO.MousePos - g.ActiveIdClickOffset;
+            if (moving_window->Pos.x != pos.x || moving_window->Pos.y != pos.y)
+            {
+                MarkIniSettingsDirty(moving_window);
+                SetWindowPos(moving_window, pos, ImGuiCond_Always);
+            }
+            FocusWindow(g.MovingWindow);
+        }
+        else
+        {
+            ClearActiveID();
+            g.MovingWindow = NULL;
+        }
+    }
+    else
+    {
+        // When clicking/dragging from a window that has the _NoMove flag, we still set the ActiveId in order to prevent hovering others.
+        if (g.ActiveIdWindow && g.ActiveIdWindow->MoveId == g.ActiveId)
+        {
+            KeepAliveID(g.ActiveId);
+            if (!g.IO.MouseDown[0])
+                ClearActiveID();
+        }
+    }
+}
+
+static bool IsWindowActiveAndVisible(ImGuiWindow* window)
+{
+    return (window->Active) && (!window->Hidden);
+}
+
+static void ImGui::UpdateMouseInputs()
+{
+    ImGuiContext& g = *GImGui;
+
+    // If mouse just appeared or disappeared (usually denoted by -FLT_MAX components) we cancel out movement in MouseDelta
+    if (IsMousePosValid(&g.IO.MousePos) && IsMousePosValid(&g.IO.MousePosPrev))
+        g.IO.MouseDelta = g.IO.MousePos - g.IO.MousePosPrev;
+    else
+        g.IO.MouseDelta = ImVec2(0.0f, 0.0f);
+    if (g.IO.MouseDelta.x != 0.0f || g.IO.MouseDelta.y != 0.0f)
+        g.NavDisableMouseHover = false;
+
+    g.IO.MousePosPrev = g.IO.MousePos;
+    for (int i = 0; i < IM_ARRAYSIZE(g.IO.MouseDown); i++)
+    {
+        g.IO.MouseClicked[i] = g.IO.MouseDown[i] && g.IO.MouseDownDuration[i] < 0.0f;
+        g.IO.MouseReleased[i] = !g.IO.MouseDown[i] && g.IO.MouseDownDuration[i] >= 0.0f;
+        g.IO.MouseDownDurationPrev[i] = g.IO.MouseDownDuration[i];
+        g.IO.MouseDownDuration[i] = g.IO.MouseDown[i] ? (g.IO.MouseDownDuration[i] < 0.0f ? 0.0f : g.IO.MouseDownDuration[i] + g.IO.DeltaTime) : -1.0f;
+        g.IO.MouseDoubleClicked[i] = false;
+        if (g.IO.MouseClicked[i])
+        {
+            if ((float)(g.Time - g.IO.MouseClickedTime[i]) < g.IO.MouseDoubleClickTime)
+            {
+                ImVec2 delta_from_click_pos = IsMousePosValid(&g.IO.MousePos) ? (g.IO.MousePos - g.IO.MouseClickedPos[i]) : ImVec2(0.0f, 0.0f);
+                if (ImLengthSqr(delta_from_click_pos) < g.IO.MouseDoubleClickMaxDist * g.IO.MouseDoubleClickMaxDist)
+                    g.IO.MouseDoubleClicked[i] = true;
+                g.IO.MouseClickedTime[i] = -FLT_MAX;    // so the third click isn't turned into a double-click
+            }
+            else
+            {
+                g.IO.MouseClickedTime[i] = g.Time;
+            }
+            g.IO.MouseClickedPos[i] = g.IO.MousePos;
+            g.IO.MouseDragMaxDistanceAbs[i] = ImVec2(0.0f, 0.0f);
+            g.IO.MouseDragMaxDistanceSqr[i] = 0.0f;
+        }
+        else if (g.IO.MouseDown[i])
+        {
+            // Maintain the maximum distance we reaching from the initial click position, which is used with dragging threshold
+            ImVec2 delta_from_click_pos = IsMousePosValid(&g.IO.MousePos) ? (g.IO.MousePos - g.IO.MouseClickedPos[i]) : ImVec2(0.0f, 0.0f);
+            g.IO.MouseDragMaxDistanceSqr[i] = ImMax(g.IO.MouseDragMaxDistanceSqr[i], ImLengthSqr(delta_from_click_pos));
+            g.IO.MouseDragMaxDistanceAbs[i].x = ImMax(g.IO.MouseDragMaxDistanceAbs[i].x, delta_from_click_pos.x < 0.0f ? -delta_from_click_pos.x : delta_from_click_pos.x);
+            g.IO.MouseDragMaxDistanceAbs[i].y = ImMax(g.IO.MouseDragMaxDistanceAbs[i].y, delta_from_click_pos.y < 0.0f ? -delta_from_click_pos.y : delta_from_click_pos.y);
+        }
+        if (g.IO.MouseClicked[i]) // Clicking any mouse button reactivate mouse hovering which may have been deactivated by gamepad/keyboard navigation
+            g.NavDisableMouseHover = false;
+    }
+}
+
+void ImGui::UpdateMouseWheel()
+{
+    ImGuiContext& g = *GImGui;
+    if (!g.HoveredWindow || g.HoveredWindow->Collapsed)
+        return;
+    if (g.IO.MouseWheel == 0.0f && g.IO.MouseWheelH == 0.0f)
+        return;
+
+    // If a child window has the ImGuiWindowFlags_NoScrollWithMouse flag, we give a chance to scroll its parent (unless either ImGuiWindowFlags_NoInputs or ImGuiWindowFlags_NoScrollbar are also set).
+    ImGuiWindow* window = g.HoveredWindow;
+    ImGuiWindow* scroll_window = window;
+    while ((scroll_window->Flags & ImGuiWindowFlags_ChildWindow) && (scroll_window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(scroll_window->Flags & ImGuiWindowFlags_NoScrollbar) && !(scroll_window->Flags & ImGuiWindowFlags_NoInputs) && scroll_window->ParentWindow)
+        scroll_window = scroll_window->ParentWindow;
+    const bool scroll_allowed = !(scroll_window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(scroll_window->Flags & ImGuiWindowFlags_NoInputs);
+
+    if (g.IO.MouseWheel != 0.0f)
+    {
+        if (g.IO.KeyCtrl && g.IO.FontAllowUserScaling)
+        {
+            // Zoom / Scale window
+            const float new_font_scale = ImClamp(window->FontWindowScale + g.IO.MouseWheel * 0.10f, 0.50f, 2.50f);
+            const float scale = new_font_scale / window->FontWindowScale;
+            window->FontWindowScale = new_font_scale;
+
+            const ImVec2 offset = window->Size * (1.0f - scale) * (g.IO.MousePos - window->Pos) / window->Size;
+            window->Pos += offset;
+            window->Size *= scale;
+            window->SizeFull *= scale;
+        }
+        else if (!g.IO.KeyCtrl && scroll_allowed)
+        {
+            // Mouse wheel vertical scrolling
+            float scroll_amount = 5 * scroll_window->CalcFontSize();
+            scroll_amount = (float)(int)ImMin(scroll_amount, (scroll_window->ContentsRegionRect.GetHeight() + scroll_window->WindowPadding.y * 2.0f) * 0.67f);
+            SetWindowScrollY(scroll_window, scroll_window->Scroll.y - g.IO.MouseWheel * scroll_amount);
+        }
+    }
+    if (g.IO.MouseWheelH != 0.0f && scroll_allowed && !g.IO.KeyCtrl)
+    {
+        // Mouse wheel horizontal scrolling (for hardware that supports it)
+        float scroll_amount = scroll_window->CalcFontSize();
+        SetWindowScrollX(scroll_window, scroll_window->Scroll.x - g.IO.MouseWheelH * scroll_amount);
+    }
+}
+
+// The reason this is exposed in imgui_internal.h is: on touch-based system that don't have hovering, we want to dispatch inputs to the right target (imgui vs imgui+app)
+void ImGui::UpdateHoveredWindowAndCaptureFlags()
+{
+    ImGuiContext& g = *GImGui;
+
+    // Find the window hovered by mouse:
+    // - Child windows can extend beyond the limit of their parent so we need to derive HoveredRootWindow from HoveredWindow.
+    // - When moving a window we can skip the search, which also conveniently bypasses the fact that window->WindowRectClipped is lagging as this point of the frame.
+    // - We also support the moved window toggling the NoInputs flag after moving has started in order to be able to detect windows below it, which is useful for e.g. docking mechanisms.
+    FindHoveredWindow();
+
+    // Modal windows prevents cursor from hovering behind them.
+    ImGuiWindow* modal_window = GetFrontMostPopupModal();
+    if (modal_window)
+        if (g.HoveredRootWindow && !IsWindowChildOf(g.HoveredRootWindow, modal_window))
+            g.HoveredRootWindow = g.HoveredWindow = NULL;
+
+    // Disabled mouse?
+    if (g.IO.ConfigFlags & ImGuiConfigFlags_NoMouse)
+        g.HoveredWindow = g.HoveredRootWindow = NULL;
+
+    // We track click ownership. When clicked outside of a window the click is owned by the application and won't report hovering nor request capture even while dragging over our windows afterward.
+    int mouse_earliest_button_down = -1;
+    bool mouse_any_down = false;
+    for (int i = 0; i < IM_ARRAYSIZE(g.IO.MouseDown); i++)
+    {
+        if (g.IO.MouseClicked[i])
+            g.IO.MouseDownOwned[i] = (g.HoveredWindow != NULL) || (!g.OpenPopupStack.empty());
+        mouse_any_down |= g.IO.MouseDown[i];
+        if (g.IO.MouseDown[i])
+            if (mouse_earliest_button_down == -1 || g.IO.MouseClickedTime[i] < g.IO.MouseClickedTime[mouse_earliest_button_down])
+                mouse_earliest_button_down = i;
+    }
+    const bool mouse_avail_to_imgui = (mouse_earliest_button_down == -1) || g.IO.MouseDownOwned[mouse_earliest_button_down];
+
+    // If mouse was first clicked outside of ImGui bounds we also cancel out hovering.
+    // FIXME: For patterns of drag and drop across OS windows, we may need to rework/remove this test (first committed 311c0ca9 on 2015/02)
+    const bool mouse_dragging_extern_payload = g.DragDropActive && (g.DragDropSourceFlags & ImGuiDragDropFlags_SourceExtern) != 0;
+    if (!mouse_avail_to_imgui && !mouse_dragging_extern_payload)
+        g.HoveredWindow = g.HoveredRootWindow = NULL;
+
+    // Update io.WantCaptureMouse for the user application (true = dispatch mouse info to imgui, false = dispatch mouse info to imgui + app)
+    if (g.WantCaptureMouseNextFrame != -1)
+        g.IO.WantCaptureMouse = (g.WantCaptureMouseNextFrame != 0);
+    else
+        g.IO.WantCaptureMouse = (mouse_avail_to_imgui && (g.HoveredWindow != NULL || mouse_any_down)) || (!g.OpenPopupStack.empty());
+
+    // Update io.WantCaptureKeyboard for the user application (true = dispatch keyboard info to imgui, false = dispatch keyboard info to imgui + app)
+    if (g.WantCaptureKeyboardNextFrame != -1)
+        g.IO.WantCaptureKeyboard = (g.WantCaptureKeyboardNextFrame != 0);
+    else
+        g.IO.WantCaptureKeyboard = (g.ActiveId != 0) || (modal_window != NULL);
+    if (g.IO.NavActive && (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) && !(g.IO.ConfigFlags & ImGuiConfigFlags_NavNoCaptureKeyboard))
+        g.IO.WantCaptureKeyboard = true;
+
+    // Update io.WantTextInput flag, this is to allow systems without a keyboard (e.g. mobile, hand-held) to show a software keyboard if possible
+    g.IO.WantTextInput = (g.WantTextInputNextFrame != -1) ? (g.WantTextInputNextFrame != 0) : false;
+}
+
+void ImGui::NewFrame()
+{
+    IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() or ImGui::SetCurrentContext()?");
+    ImGuiContext& g = *GImGui;
+
+    // Check user data
+    // (We pass an error message in the assert expression to make it visible to programmers who are not using a debugger, as most assert handlers display their argument)
+    IM_ASSERT(g.Initialized);
+    IM_ASSERT(g.IO.DeltaTime >= 0.0f                                    && "Need a positive DeltaTime (zero is tolerated but will cause some timing issues)");
+    IM_ASSERT(g.IO.DisplaySize.x >= 0.0f && g.IO.DisplaySize.y >= 0.0f  && "Invalid DisplaySize value");
+    IM_ASSERT(g.IO.Fonts->Fonts.Size > 0                                && "Font Atlas not built. Did you call io.Fonts->GetTexDataAsRGBA32() / GetTexDataAsAlpha8() ?");
+    IM_ASSERT(g.IO.Fonts->Fonts[0]->IsLoaded()                          && "Font Atlas not built. Did you call io.Fonts->GetTexDataAsRGBA32() / GetTexDataAsAlpha8() ?");
+    IM_ASSERT(g.Style.CurveTessellationTol > 0.0f                       && "Invalid style setting");
+    IM_ASSERT(g.Style.Alpha >= 0.0f && g.Style.Alpha <= 1.0f            && "Invalid style setting. Alpha cannot be negative (allows us to avoid a few clamps in color computations)");
+    IM_ASSERT((g.FrameCount == 0 || g.FrameCountEnded == g.FrameCount)  && "Forgot to call Render() or EndFrame() at the end of the previous frame?");
+    for (int n = 0; n < ImGuiKey_COUNT; n++)
+        IM_ASSERT(g.IO.KeyMap[n] >= -1 && g.IO.KeyMap[n] < IM_ARRAYSIZE(g.IO.KeysDown) && "io.KeyMap[] contains an out of bound value (need to be 0..512, or -1 for unmapped key)");
+
+    // Perform simple check: required key mapping (we intentionally do NOT check all keys to not pressure user into setting up everything, but Space is required and was only recently added in 1.60 WIP)
+    if (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard)
+        IM_ASSERT(g.IO.KeyMap[ImGuiKey_Space] != -1 && "ImGuiKey_Space is not mapped, required for keyboard navigation.");
+
+    // Perform simple check: the beta io.ConfigResizeWindowsFromEdges option requires back-end to honor mouse cursor changes and set the ImGuiBackendFlags_HasMouseCursors flag accordingly.
+    if (g.IO.ConfigResizeWindowsFromEdges && !(g.IO.BackendFlags & ImGuiBackendFlags_HasMouseCursors))
+        g.IO.ConfigResizeWindowsFromEdges = false;
+
+    // Load settings on first frame (if not explicitly loaded manually before)
+    if (!g.SettingsLoaded)
+    {
+        IM_ASSERT(g.SettingsWindows.empty());
+        if (g.IO.IniFilename)
+            LoadIniSettingsFromDisk(g.IO.IniFilename);
+        g.SettingsLoaded = true;
+    }
+
+    // Save settings (with a delay after the last modification, so we don't spam disk too much)
+    if (g.SettingsDirtyTimer > 0.0f)
+    {
+        g.SettingsDirtyTimer -= g.IO.DeltaTime;
+        if (g.SettingsDirtyTimer <= 0.0f)
+        {
+            if (g.IO.IniFilename != NULL)
+                SaveIniSettingsToDisk(g.IO.IniFilename);
+            else
+                g.IO.WantSaveIniSettings = true;  // Let user know they can call SaveIniSettingsToMemory(). user will need to clear io.WantSaveIniSettings themselves.
+            g.SettingsDirtyTimer = 0.0f;
+        }
+    }
+
+    g.Time += g.IO.DeltaTime;
+    g.FrameScopeActive = true;
+    g.FrameCount += 1;
+    g.TooltipOverrideCount = 0;
+    g.WindowsActiveCount = 0;
+
+    // Setup current font and draw list
+    g.IO.Fonts->Locked = true;
+    SetCurrentFont(GetDefaultFont());
+    IM_ASSERT(g.Font->IsLoaded());
+    g.DrawListSharedData.ClipRectFullscreen = ImVec4(0.0f, 0.0f, g.IO.DisplaySize.x, g.IO.DisplaySize.y);
+    g.DrawListSharedData.CurveTessellationTol = g.Style.CurveTessellationTol;
+
+    g.OverlayDrawList.Clear();
+    g.OverlayDrawList.PushTextureID(g.IO.Fonts->TexID);
+    g.OverlayDrawList.PushClipRectFullScreen();
+    g.OverlayDrawList.Flags = (g.Style.AntiAliasedLines ? ImDrawListFlags_AntiAliasedLines : 0) | (g.Style.AntiAliasedFill ? ImDrawListFlags_AntiAliasedFill : 0);
+
+    // Mark rendering data as invalid to prevent user who may have a handle on it to use it
+    g.DrawData.Clear();
+
+    // Drag and drop keep the source ID alive so even if the source disappear our state is consistent
+    if (g.DragDropActive && g.DragDropPayload.SourceId == g.ActiveId)
+        KeepAliveID(g.DragDropPayload.SourceId);
+
+    // Clear reference to active widget if the widget isn't alive anymore
+    if (!g.HoveredIdPreviousFrame)
+        g.HoveredIdTimer = 0.0f;
+    if (g.HoveredId)
+        g.HoveredIdTimer += g.IO.DeltaTime;
+    g.HoveredIdPreviousFrame = g.HoveredId;
+    g.HoveredId = 0;
+    g.HoveredIdAllowOverlap = false;
+    if (g.ActiveIdIsAlive != g.ActiveId && g.ActiveIdPreviousFrame == g.ActiveId && g.ActiveId != 0)
+        ClearActiveID();
+    if (g.ActiveId)
+        g.ActiveIdTimer += g.IO.DeltaTime;
+    g.LastActiveIdTimer += g.IO.DeltaTime;
+    g.ActiveIdPreviousFrame = g.ActiveId;
+    g.ActiveIdPreviousFrameWindow = g.ActiveIdWindow;
+    g.ActiveIdPreviousFrameHasBeenEdited = g.ActiveIdHasBeenEdited;
+    g.ActiveIdIsAlive = 0;
+    g.ActiveIdPreviousFrameIsAlive = false;
+    g.ActiveIdIsJustActivated = false;
+    if (g.ScalarAsInputTextId && g.ActiveId != g.ScalarAsInputTextId)
+        g.ScalarAsInputTextId = 0;
+
+    // Drag and drop
+    g.DragDropAcceptIdPrev = g.DragDropAcceptIdCurr;
+    g.DragDropAcceptIdCurr = 0;
+    g.DragDropAcceptIdCurrRectSurface = FLT_MAX;
+    g.DragDropWithinSourceOrTarget = false;
+
+    // Update keyboard input state
+    memcpy(g.IO.KeysDownDurationPrev, g.IO.KeysDownDuration, sizeof(g.IO.KeysDownDuration));
+    for (int i = 0; i < IM_ARRAYSIZE(g.IO.KeysDown); i++)
+        g.IO.KeysDownDuration[i] = g.IO.KeysDown[i] ? (g.IO.KeysDownDuration[i] < 0.0f ? 0.0f : g.IO.KeysDownDuration[i] + g.IO.DeltaTime) : -1.0f;
+
+    // Update gamepad/keyboard directional navigation
+    NavUpdate();
+
+    // Update mouse input state
+    UpdateMouseInputs();
+
+    // Calculate frame-rate for the user, as a purely luxurious feature
+    g.FramerateSecPerFrameAccum += g.IO.DeltaTime - g.FramerateSecPerFrame[g.FramerateSecPerFrameIdx];
+    g.FramerateSecPerFrame[g.FramerateSecPerFrameIdx] = g.IO.DeltaTime;
+    g.FramerateSecPerFrameIdx = (g.FramerateSecPerFrameIdx + 1) % IM_ARRAYSIZE(g.FramerateSecPerFrame);
+    g.IO.Framerate = (g.FramerateSecPerFrameAccum > 0.0f) ? (1.0f / (g.FramerateSecPerFrameAccum / (float)IM_ARRAYSIZE(g.FramerateSecPerFrame))) : FLT_MAX;
+
+    // Handle user moving window with mouse (at the beginning of the frame to avoid input lag or sheering)
+    UpdateMouseMovingWindow();
+    UpdateHoveredWindowAndCaptureFlags();
+
+    // Background darkening/whitening
+    if (GetFrontMostPopupModal() != NULL || (g.NavWindowingTarget != NULL && g.NavWindowingHighlightAlpha > 0.0f))
+        g.DimBgRatio = ImMin(g.DimBgRatio + g.IO.DeltaTime * 6.0f, 1.0f);
+    else
+        g.DimBgRatio = ImMax(g.DimBgRatio - g.IO.DeltaTime * 10.0f, 0.0f);
+
+    g.MouseCursor = ImGuiMouseCursor_Arrow;
+    g.WantCaptureMouseNextFrame = g.WantCaptureKeyboardNextFrame = g.WantTextInputNextFrame = -1;
+    g.PlatformImePos = ImVec2(1.0f, 1.0f); // OS Input Method Editor showing on top-left of our window by default
+
+    // Mouse wheel scrolling, scale
+    UpdateMouseWheel();
+
+    // Pressing TAB activate widget focus
+    if (g.ActiveId == 0 && g.NavWindow != NULL && g.NavWindow->Active && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs) && !g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_Tab, false))
+    {
+        if (g.NavId != 0 && g.NavIdTabCounter != INT_MAX)
+            g.NavWindow->FocusIdxTabRequestNext = g.NavIdTabCounter + 1 + (g.IO.KeyShift ? -1 : 1);
+        else
+            g.NavWindow->FocusIdxTabRequestNext = g.IO.KeyShift ? -1 : 0;
+    }
+    g.NavIdTabCounter = INT_MAX;
+
+    // Mark all windows as not visible
+    for (int i = 0; i != g.Windows.Size; i++)
+    {
+        ImGuiWindow* window = g.Windows[i];
+        window->WasActive = window->Active;
+        window->Active = false;
+        window->WriteAccessed = false;
+    }
+
+    // Closing the focused window restore focus to the first active root window in descending z-order
+    if (g.NavWindow && !g.NavWindow->WasActive)
+        FocusFrontMostActiveWindowIgnoringOne(NULL);
+
+    // No window should be open at the beginning of the frame.
+    // But in order to allow the user to call NewFrame() multiple times without calling Render(), we are doing an explicit clear.
+    g.CurrentWindowStack.resize(0);
+    g.CurrentPopupStack.resize(0);
+    ClosePopupsOverWindow(g.NavWindow);
+
+    // Create implicit window - we will only render it if the user has added something to it.
+    // We don't use "Debug" to avoid colliding with user trying to create a "Debug" window with custom flags.
+    SetNextWindowSize(ImVec2(400,400), ImGuiCond_FirstUseEver);
+    Begin("Debug##Default");
+}
+
+void ImGui::Initialize(ImGuiContext* context)
+{
+    ImGuiContext& g = *context;
+    IM_ASSERT(!g.Initialized && !g.SettingsLoaded);
+
+    // Add .ini handle for ImGuiWindow type
+    ImGuiSettingsHandler ini_handler;
+    ini_handler.TypeName = "Window";
+    ini_handler.TypeHash = ImHash("Window", 0, 0);
+    ini_handler.ReadOpenFn = SettingsHandlerWindow_ReadOpen;
+    ini_handler.ReadLineFn = SettingsHandlerWindow_ReadLine;
+    ini_handler.WriteAllFn = SettingsHandlerWindow_WriteAll;
+    g.SettingsHandlers.push_front(ini_handler);
+
+    g.Initialized = true;
+}
+
+// This function is merely here to free heap allocations.
+void ImGui::Shutdown(ImGuiContext* context)
+{
+    // The fonts atlas can be used prior to calling NewFrame(), so we clear it even if g.Initialized is FALSE (which would happen if we never called NewFrame)
+    ImGuiContext& g = *context;
+    if (g.IO.Fonts && g.FontAtlasOwnedByContext)
+        IM_DELETE(g.IO.Fonts);
+    g.IO.Fonts = NULL;
+
+    // Cleanup of other data are conditional on actually having initialized ImGui.
+    if (!g.Initialized)
+        return;
+
+    // Save settings (unless we haven't attempted to load them: CreateContext/DestroyContext without a call to NewFrame shouldn't save an empty file)
+    if (g.SettingsLoaded && g.IO.IniFilename != NULL)
+        SaveIniSettingsToDisk(g.IO.IniFilename);
+
+    // Clear everything else
+    for (int i = 0; i < g.Windows.Size; i++)
+        IM_DELETE(g.Windows[i]);
+    g.Windows.clear();
+    g.WindowsSortBuffer.clear();
+    g.CurrentWindow = NULL;
+    g.CurrentWindowStack.clear();
+    g.WindowsById.Clear();
+    g.NavWindow = NULL;
+    g.HoveredWindow = NULL;
+    g.HoveredRootWindow = NULL;
+    g.ActiveIdWindow = g.ActiveIdPreviousFrameWindow = NULL;
+    g.MovingWindow = NULL;
+    g.ColorModifiers.clear();
+    g.StyleModifiers.clear();
+    g.FontStack.clear();
+    g.OpenPopupStack.clear();
+    g.CurrentPopupStack.clear();
+    g.DrawDataBuilder.ClearFreeMemory();
+    g.OverlayDrawList.ClearFreeMemory();
+    g.PrivateClipboard.clear();
+    g.InputTextState.TextW.clear();
+    g.InputTextState.InitialText.clear();
+    g.InputTextState.TempBuffer.clear();
+
+    for (int i = 0; i < g.SettingsWindows.Size; i++)
+        IM_DELETE(g.SettingsWindows[i].Name);
+    g.SettingsWindows.clear();
+    g.SettingsHandlers.clear();
+
+    if (g.LogFile && g.LogFile != stdout)
+    {
+        fclose(g.LogFile);
+        g.LogFile = NULL;
+    }
+    g.LogClipboard.clear();
+
+    g.Initialized = false;
+}
+
+// FIXME: Add a more explicit sort order in the window structure.
+static int IMGUI_CDECL ChildWindowComparer(const void* lhs, const void* rhs)
+{
+    const ImGuiWindow* const a = *(const ImGuiWindow* const *)lhs;
+    const ImGuiWindow* const b = *(const ImGuiWindow* const *)rhs;
+    if (int d = (a->Flags & ImGuiWindowFlags_Popup) - (b->Flags & ImGuiWindowFlags_Popup))
+        return d;
+    if (int d = (a->Flags & ImGuiWindowFlags_Tooltip) - (b->Flags & ImGuiWindowFlags_Tooltip))
+        return d;
+    return (a->BeginOrderWithinParent - b->BeginOrderWithinParent);
+}
+
+static void AddWindowToSortedBuffer(ImVector<ImGuiWindow*>* out_sorted_windows, ImGuiWindow* window)
+{
+    out_sorted_windows->push_back(window);
+    if (window->Active)
+    {
+        int count = window->DC.ChildWindows.Size;
+        if (count > 1)
+            ImQsort(window->DC.ChildWindows.begin(), (size_t)count, sizeof(ImGuiWindow*), ChildWindowComparer);
+        for (int i = 0; i < count; i++)
+        {
+            ImGuiWindow* child = window->DC.ChildWindows[i];
+            if (child->Active)
+                AddWindowToSortedBuffer(out_sorted_windows, child);
+        }
+    }
+}
+
+static void AddDrawListToDrawData(ImVector<ImDrawList*>* out_list, ImDrawList* draw_list)
+{
+    if (draw_list->CmdBuffer.empty())
+        return;
+
+    // Remove trailing command if unused
+    ImDrawCmd& last_cmd = draw_list->CmdBuffer.back();
+    if (last_cmd.ElemCount == 0 && last_cmd.UserCallback == NULL)
+    {
+        draw_list->CmdBuffer.pop_back();
+        if (draw_list->CmdBuffer.empty())
+            return;
+    }
+
+    // Draw list sanity check. Detect mismatch between PrimReserve() calls and incrementing _VtxCurrentIdx, _VtxWritePtr etc. May trigger for you if you are using PrimXXX functions incorrectly.
+    IM_ASSERT(draw_list->VtxBuffer.Size == 0 || draw_list->_VtxWritePtr == draw_list->VtxBuffer.Data + draw_list->VtxBuffer.Size);
+    IM_ASSERT(draw_list->IdxBuffer.Size == 0 || draw_list->_IdxWritePtr == draw_list->IdxBuffer.Data + draw_list->IdxBuffer.Size);
+    IM_ASSERT((int)draw_list->_VtxCurrentIdx == draw_list->VtxBuffer.Size);
+
+    // Check that draw_list doesn't use more vertices than indexable (default ImDrawIdx = unsigned short = 2 bytes = 64K vertices per ImDrawList = per window)
+    // If this assert triggers because you are drawing lots of stuff manually:
+    // A) Make sure you are coarse clipping, because ImDrawList let all your vertices pass. You can use the Metrics window to inspect draw list contents.
+    // B) If you need/want meshes with more than 64K vertices, uncomment the '#define ImDrawIdx unsigned int' line in imconfig.h to set the index size to 4 bytes.
+    //    You'll need to handle the 4-bytes indices to your renderer. For example, the OpenGL example code detect index size at compile-time by doing:
+    //      glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer_offset);
+    //    Your own engine or render API may use different parameters or function calls to specify index sizes. 2 and 4 bytes indices are generally supported by most API.
+    // C) If for some reason you cannot use 4 bytes indices or don't want to, a workaround is to call BeginChild()/EndChild() before reaching the 64K limit to split your draw commands in multiple draw lists.
+    if (sizeof(ImDrawIdx) == 2)
+        IM_ASSERT(draw_list->_VtxCurrentIdx < (1 << 16) && "Too many vertices in ImDrawList using 16-bit indices. Read comment above");
+
+    out_list->push_back(draw_list);
+}
+
+static void AddWindowToDrawData(ImVector<ImDrawList*>* out_render_list, ImGuiWindow* window)
+{
+    ImGuiContext& g = *GImGui;
+    g.IO.MetricsRenderWindows++;
+    AddDrawListToDrawData(out_render_list, window->DrawList);
+    for (int i = 0; i < window->DC.ChildWindows.Size; i++)
+    {
+        ImGuiWindow* child = window->DC.ChildWindows[i];
+        if (IsWindowActiveAndVisible(child)) // clipped children may have been marked not active
+            AddWindowToDrawData(out_render_list, child);
+    }
+}
+
+static void AddWindowToDrawDataSelectLayer(ImGuiWindow* window)
+{
+    ImGuiContext& g = *GImGui;
+    if (window->Flags & ImGuiWindowFlags_Tooltip)
+        AddWindowToDrawData(&g.DrawDataBuilder.Layers[1], window);
+    else
+        AddWindowToDrawData(&g.DrawDataBuilder.Layers[0], window);
+}
+
+void ImDrawDataBuilder::FlattenIntoSingleLayer()
+{
+    int n = Layers[0].Size;
+    int size = n;
+    for (int i = 1; i < IM_ARRAYSIZE(Layers); i++)
+        size += Layers[i].Size;
+    Layers[0].resize(size);
+    for (int layer_n = 1; layer_n < IM_ARRAYSIZE(Layers); layer_n++)
+    {
+        ImVector<ImDrawList*>& layer = Layers[layer_n];
+        if (layer.empty())
+            continue;
+        memcpy(&Layers[0][n], &layer[0], layer.Size * sizeof(ImDrawList*));
+        n += layer.Size;
+        layer.resize(0);
+    }
+}
+
+static void SetupDrawData(ImVector<ImDrawList*>* draw_lists, ImDrawData* draw_data)
+{
+    ImGuiIO& io = ImGui::GetIO();
+    draw_data->Valid = true;
+    draw_data->CmdLists = (draw_lists->Size > 0) ? draw_lists->Data : NULL;
+    draw_data->CmdListsCount = draw_lists->Size;
+    draw_data->TotalVtxCount = draw_data->TotalIdxCount = 0;
+    draw_data->DisplayPos = ImVec2(0.0f, 0.0f);
+    draw_data->DisplaySize = io.DisplaySize;
+    for (int n = 0; n < draw_lists->Size; n++)
+    {
+        draw_data->TotalVtxCount += draw_lists->Data[n]->VtxBuffer.Size;
+        draw_data->TotalIdxCount += draw_lists->Data[n]->IdxBuffer.Size;
+    }
+}
+
+// When using this function it is sane to ensure that float are perfectly rounded to integer values, to that e.g. (int)(max.x-min.x) in user's render produce correct result.
+void ImGui::PushClipRect(const ImVec2& clip_rect_min, const ImVec2& clip_rect_max, bool intersect_with_current_clip_rect)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    window->DrawList->PushClipRect(clip_rect_min, clip_rect_max, intersect_with_current_clip_rect);
+    window->ClipRect = window->DrawList->_ClipRectStack.back();
+}
+
+void ImGui::PopClipRect()
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    window->DrawList->PopClipRect();
+    window->ClipRect = window->DrawList->_ClipRectStack.back();
+}
+
+// This is normally called by Render(). You may want to call it directly if you want to avoid calling Render() but the gain will be very minimal.
+void ImGui::EndFrame()
+{
+    ImGuiContext& g = *GImGui;
+    IM_ASSERT(g.Initialized);
+    if (g.FrameCountEnded == g.FrameCount)          // Don't process EndFrame() multiple times.
+        return;
+    IM_ASSERT(g.FrameScopeActive && "Forgot to call ImGui::NewFrame()");
+
+    // Notify OS when our Input Method Editor cursor has moved (e.g. CJK inputs using Microsoft IME)
+    if (g.IO.ImeSetInputScreenPosFn && ImLengthSqr(g.PlatformImeLastPos - g.PlatformImePos) > 0.0001f)
+    {
+        g.IO.ImeSetInputScreenPosFn((int)g.PlatformImePos.x, (int)g.PlatformImePos.y);
+        g.PlatformImeLastPos = g.PlatformImePos;
+    }
+
+    // Hide implicit "Debug" window if it hasn't been used
+    IM_ASSERT(g.CurrentWindowStack.Size == 1);    // Mismatched Begin()/End() calls, did you forget to call end on g.CurrentWindow->Name?
+    if (g.CurrentWindow && !g.CurrentWindow->WriteAccessed)
+        g.CurrentWindow->Active = false;
+    End();
+
+    // Show CTRL+TAB list
+    if (g.NavWindowingTarget)
+        NavUpdateWindowingList();
+
+    // Drag and Drop: Elapse payload (if delivered, or if source stops being submitted)
+    if (g.DragDropActive)
+    {
+        bool is_delivered = g.DragDropPayload.Delivery;
+        bool is_elapsed = (g.DragDropPayload.DataFrameCount + 1 < g.FrameCount) && ((g.DragDropSourceFlags & ImGuiDragDropFlags_SourceAutoExpirePayload) || !IsMouseDown(g.DragDropMouseButton));
+        if (is_delivered || is_elapsed)
+            ClearDragDrop();
+    }
+
+    // Drag and Drop: Fallback for source tooltip. This is not ideal but better than nothing.
+    if (g.DragDropActive && g.DragDropSourceFrameCount < g.FrameCount)
+    {
+        g.DragDropWithinSourceOrTarget = true;
+        SetTooltip("...");
+        g.DragDropWithinSourceOrTarget = false;
+    }
+
+    // Initiate moving window
+    if (g.ActiveId == 0 && g.HoveredId == 0)
+    {
+        if (!g.NavWindow || !g.NavWindow->Appearing) // Unless we just made a window/popup appear
+        {
+            // Click to focus window and start moving (after we're done with all our widgets)
+            if (g.IO.MouseClicked[0])
+            {
+                if (g.HoveredRootWindow != NULL)
+                    StartMouseMovingWindow(g.HoveredWindow);
+                else if (g.NavWindow != NULL && GetFrontMostPopupModal() == NULL)
+                    FocusWindow(NULL);  // Clicking on void disable focus
+            }
+
+            // With right mouse button we close popups without changing focus
+            // (The left mouse button path calls FocusWindow which will lead NewFrame->ClosePopupsOverWindow to trigger)
+            if (g.IO.MouseClicked[1])
+            {
+                // Find the top-most window between HoveredWindow and the front most Modal Window.
+                // This is where we can trim the popup stack.
+                ImGuiWindow* modal = GetFrontMostPopupModal();
+                bool hovered_window_above_modal = false;
+                if (modal == NULL)
+                    hovered_window_above_modal = true;
+                for (int i = g.Windows.Size - 1; i >= 0 && hovered_window_above_modal == false; i--)
+                {
+                    ImGuiWindow* window = g.Windows[i];
+                    if (window == modal)
+                        break;
+                    if (window == g.HoveredWindow)
+                        hovered_window_above_modal = true;
+                }
+                ClosePopupsOverWindow(hovered_window_above_modal ? g.HoveredWindow : modal);
+            }
+        }
+    }
+
+    // Sort the window list so that all child windows are after their parent
+    // We cannot do that on FocusWindow() because childs may not exist yet
+    g.WindowsSortBuffer.resize(0);
+    g.WindowsSortBuffer.reserve(g.Windows.Size);
+    for (int i = 0; i != g.Windows.Size; i++)
+    {
+        ImGuiWindow* window = g.Windows[i];
+        if (window->Active && (window->Flags & ImGuiWindowFlags_ChildWindow))       // if a child is active its parent will add it
+            continue;
+        AddWindowToSortedBuffer(&g.WindowsSortBuffer, window);
+    }
+
+    IM_ASSERT(g.Windows.Size == g.WindowsSortBuffer.Size);  // we done something wrong
+    g.Windows.swap(g.WindowsSortBuffer);
+    g.IO.MetricsActiveWindows = g.WindowsActiveCount;
+
+    // Unlock font atlas
+    g.IO.Fonts->Locked = false;
+
+    // Clear Input data for next frame
+    g.IO.MouseWheel = g.IO.MouseWheelH = 0.0f;
+    memset(g.IO.InputCharacters, 0, sizeof(g.IO.InputCharacters));
+    memset(g.IO.NavInputs, 0, sizeof(g.IO.NavInputs));
+
+    g.FrameScopeActive = false;
+    g.FrameCountEnded = g.FrameCount;
+}
+
+void ImGui::Render()
+{
+    ImGuiContext& g = *GImGui;
+    IM_ASSERT(g.Initialized);
+
+    if (g.FrameCountEnded != g.FrameCount)
+        ImGui::EndFrame();
+    g.FrameCountRendered = g.FrameCount;
+
+    // Gather ImDrawList to render (for each active window)
+    g.IO.MetricsRenderVertices = g.IO.MetricsRenderIndices = g.IO.MetricsRenderWindows = 0;
+    g.DrawDataBuilder.Clear();
+    ImGuiWindow* windows_to_render_front_most[2];
+    windows_to_render_front_most[0] = (g.NavWindowingTarget && !(g.NavWindowingTarget->Flags & ImGuiWindowFlags_NoBringToFrontOnFocus)) ? g.NavWindowingTarget->RootWindow : NULL;
+    windows_to_render_front_most[1] = g.NavWindowingTarget ? g.NavWindowingList : NULL;
+    for (int n = 0; n != g.Windows.Size; n++)
+    {
+        ImGuiWindow* window = g.Windows[n];
+        if (IsWindowActiveAndVisible(window) && (window->Flags & ImGuiWindowFlags_ChildWindow) == 0 && window != windows_to_render_front_most[0] && window != windows_to_render_front_most[1])
+            AddWindowToDrawDataSelectLayer(window);
+    }
+    for (int n = 0; n < IM_ARRAYSIZE(windows_to_render_front_most); n++)
+        if (windows_to_render_front_most[n] && IsWindowActiveAndVisible(windows_to_render_front_most[n])) // NavWindowingTarget is always temporarily displayed as the front-most window
+            AddWindowToDrawDataSelectLayer(windows_to_render_front_most[n]);
+    g.DrawDataBuilder.FlattenIntoSingleLayer();
+
+    // Draw software mouse cursor if requested
+    if (g.IO.MouseDrawCursor)
+        RenderMouseCursor(&g.OverlayDrawList, g.IO.MousePos, g.Style.MouseCursorScale, g.MouseCursor);
+
+    if (!g.OverlayDrawList.VtxBuffer.empty())
+        AddDrawListToDrawData(&g.DrawDataBuilder.Layers[0], &g.OverlayDrawList);
+
+    // Setup ImDrawData structure for end-user
+    SetupDrawData(&g.DrawDataBuilder.Layers[0], &g.DrawData);
+    g.IO.MetricsRenderVertices = g.DrawData.TotalVtxCount;
+    g.IO.MetricsRenderIndices = g.DrawData.TotalIdxCount;
+
+    // Render. If user hasn't set a callback then they may retrieve the draw data via GetDrawData()
+#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
+    if (g.DrawData.CmdListsCount > 0 && g.IO.RenderDrawListsFn != NULL)
+        g.IO.RenderDrawListsFn(&g.DrawData);
+#endif
+}
+
+// Calculate text size. Text can be multi-line. Optionally ignore text after a ## marker.
+// CalcTextSize("") should return ImVec2(0.0f, GImGui->FontSize)
+ImVec2 ImGui::CalcTextSize(const char* text, const char* text_end, bool hide_text_after_double_hash, float wrap_width)
+{
+    ImGuiContext& g = *GImGui;
+
+    const char* text_display_end;
+    if (hide_text_after_double_hash)
+        text_display_end = FindRenderedTextEnd(text, text_end);      // Hide anything after a '##' string
+    else
+        text_display_end = text_end;
+
+    ImFont* font = g.Font;
+    const float font_size = g.FontSize;
+    if (text == text_display_end)
+        return ImVec2(0.0f, font_size);
+    ImVec2 text_size = font->CalcTextSizeA(font_size, FLT_MAX, wrap_width, text, text_display_end, NULL);
+
+    // Cancel out character spacing for the last character of a line (it is baked into glyph->AdvanceX field)
+    const float font_scale = font_size / font->FontSize;
+    const float character_spacing_x = 1.0f * font_scale;
+    if (text_size.x > 0.0f)
+        text_size.x -= character_spacing_x;
+    text_size.x = (float)(int)(text_size.x + 0.95f);
+
+    return text_size;
+}
+
+// Helper to calculate coarse clipping of large list of evenly sized items.
+// NB: Prefer using the ImGuiListClipper higher-level helper if you can! Read comments and instructions there on how those use this sort of pattern.
+// NB: 'items_count' is only used to clamp the result, if you don't know your count you can use INT_MAX
+void ImGui::CalcListClipping(int items_count, float items_height, int* out_items_display_start, int* out_items_display_end)
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiWindow* window = g.CurrentWindow;
+    if (g.LogEnabled)
+    {
+        // If logging is active, do not perform any clipping
+        *out_items_display_start = 0;
+        *out_items_display_end = items_count;
+        return;
+    }
+    if (window->SkipItems)
+    {
+        *out_items_display_start = *out_items_display_end = 0;
+        return;
+    }
+
+    // We create the union of the ClipRect and the NavScoringRect which at worst should be 1 page away from ClipRect
+    ImRect unclipped_rect = window->ClipRect;
+    if (g.NavMoveRequest)
+        unclipped_rect.Add(g.NavScoringRectScreen);
+
+    const ImVec2 pos = window->DC.CursorPos;
+    int start = (int)((unclipped_rect.Min.y - pos.y) / items_height);
+    int end = (int)((unclipped_rect.Max.y - pos.y) / items_height);
+
+    // When performing a navigation request, ensure we have one item extra in the direction we are moving to
+    if (g.NavMoveRequest && g.NavMoveClipDir == ImGuiDir_Up)
+        start--;
+    if (g.NavMoveRequest && g.NavMoveClipDir == ImGuiDir_Down)
+        end++;
+
+    start = ImClamp(start, 0, items_count);
+    end = ImClamp(end + 1, start, items_count);
+    *out_items_display_start = start;
+    *out_items_display_end = end;
+}
+
+// Find window given position, search front-to-back
+// FIXME: Note that we have an inconsequential lag here: OuterRectClipped is updated in Begin(), so windows moved programatically 
+// with SetWindowPos() and not SetNextWindowPos() will have that rectangle lagging by a frame at the time FindHoveredWindow() is 
+// called, aka before the next Begin(). Moving window isn't affected.
+static void FindHoveredWindow()
+{
+    ImGuiContext& g = *GImGui;
+
+    ImGuiWindow* hovered_window = NULL;
+    if (g.MovingWindow && !(g.MovingWindow->Flags & ImGuiWindowFlags_NoInputs))
+        hovered_window = g.MovingWindow;
+
+    for (int i = g.Windows.Size - 1; i >= 0 && hovered_window == NULL; i--)
+    {
+        ImGuiWindow* window = g.Windows[i];
+        if (!window->Active || window->Hidden)
+            continue;
+        if (window->Flags & ImGuiWindowFlags_NoInputs)
+            continue;
+
+        // Using the clipped AABB, a child window will typically be clipped by its parent (not always)
+        ImRect bb(window->OuterRectClipped.Min - g.Style.TouchExtraPadding, window->OuterRectClipped.Max + g.Style.TouchExtraPadding);
+        if (bb.Contains(g.IO.MousePos))
+        {
+            if (hovered_window == NULL)
+                hovered_window = window;
+            if (hovered_window)
+                break;
+        }
+    }
+
+    g.HoveredWindow = hovered_window;
+    g.HoveredRootWindow = g.HoveredWindow ? g.HoveredWindow->RootWindow : NULL;
+
+}
+
+// Test if mouse cursor is hovering given rectangle
+// NB- Rectangle is clipped by our current clip setting
+// NB- Expand the rectangle to be generous on imprecise inputs systems (g.Style.TouchExtraPadding)
+bool ImGui::IsMouseHoveringRect(const ImVec2& r_min, const ImVec2& r_max, bool clip)
+{
+    ImGuiContext& g = *GImGui;
+
+    // Clip
+    ImRect rect_clipped(r_min, r_max);
+    if (clip)
+        rect_clipped.ClipWith(g.CurrentWindow->ClipRect);
+
+    // Expand for touch input
+    const ImRect rect_for_touch(rect_clipped.Min - g.Style.TouchExtraPadding, rect_clipped.Max + g.Style.TouchExtraPadding);
+    if (!rect_for_touch.Contains(g.IO.MousePos))
+        return false;
+    return true;
+}
+
+int ImGui::GetKeyIndex(ImGuiKey imgui_key)
+{
+    IM_ASSERT(imgui_key >= 0 && imgui_key < ImGuiKey_COUNT);
+    return GImGui->IO.KeyMap[imgui_key];
+}
+
+// Note that imgui doesn't know the semantic of each entry of io.KeysDown[]. Use your own indices/enums according to how your back-end/engine stored them into io.KeysDown[]!
+bool ImGui::IsKeyDown(int user_key_index)
+{
+    if (user_key_index < 0) return false;
+    IM_ASSERT(user_key_index >= 0 && user_key_index < IM_ARRAYSIZE(GImGui->IO.KeysDown));
+    return GImGui->IO.KeysDown[user_key_index];
+}
+
+int ImGui::CalcTypematicPressedRepeatAmount(float t, float t_prev, float repeat_delay, float repeat_rate)
+{
+    if (t == 0.0f)
+        return 1;
+    if (t <= repeat_delay || repeat_rate <= 0.0f)
+        return 0;
+    const int count = (int)((t - repeat_delay) / repeat_rate) - (int)((t_prev - repeat_delay) / repeat_rate);
+    return (count > 0) ? count : 0;
+}
+
+int ImGui::GetKeyPressedAmount(int key_index, float repeat_delay, float repeat_rate)
+{
+    ImGuiContext& g = *GImGui;
+    if (key_index < 0) return false;
+    IM_ASSERT(key_index >= 0 && key_index < IM_ARRAYSIZE(g.IO.KeysDown));
+    const float t = g.IO.KeysDownDuration[key_index];
+    return CalcTypematicPressedRepeatAmount(t, t - g.IO.DeltaTime, repeat_delay, repeat_rate);
+}
+
+bool ImGui::IsKeyPressed(int user_key_index, bool repeat)
+{
+    ImGuiContext& g = *GImGui;
+    if (user_key_index < 0) return false;
+    IM_ASSERT(user_key_index >= 0 && user_key_index < IM_ARRAYSIZE(g.IO.KeysDown));
+    const float t = g.IO.KeysDownDuration[user_key_index];
+    if (t == 0.0f)
+        return true;
+    if (repeat && t > g.IO.KeyRepeatDelay)
+        return GetKeyPressedAmount(user_key_index, g.IO.KeyRepeatDelay, g.IO.KeyRepeatRate) > 0;
+    return false;
+}
+
+bool ImGui::IsKeyReleased(int user_key_index)
+{
+    ImGuiContext& g = *GImGui;
+    if (user_key_index < 0) return false;
+    IM_ASSERT(user_key_index >= 0 && user_key_index < IM_ARRAYSIZE(g.IO.KeysDown));
+    return g.IO.KeysDownDurationPrev[user_key_index] >= 0.0f && !g.IO.KeysDown[user_key_index];
+}
+
+bool ImGui::IsMouseDown(int button)
+{
+    ImGuiContext& g = *GImGui;
+    IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
+    return g.IO.MouseDown[button];
+}
+
+bool ImGui::IsAnyMouseDown()
+{
+    ImGuiContext& g = *GImGui;
+    for (int n = 0; n < IM_ARRAYSIZE(g.IO.MouseDown); n++)
+        if (g.IO.MouseDown[n])
+            return true;
+    return false;
+}
+
+bool ImGui::IsMouseClicked(int button, bool repeat)
+{
+    ImGuiContext& g = *GImGui;
+    IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
+    const float t = g.IO.MouseDownDuration[button];
+    if (t == 0.0f)
+        return true;
+
+    if (repeat && t > g.IO.KeyRepeatDelay)
+    {
+        float delay = g.IO.KeyRepeatDelay, rate = g.IO.KeyRepeatRate;
+        if ((ImFmod(t - delay, rate) > rate*0.5f) != (ImFmod(t - delay - g.IO.DeltaTime, rate) > rate*0.5f))
+            return true;
+    }
+
+    return false;
+}
+
+bool ImGui::IsMouseReleased(int button)
+{
+    ImGuiContext& g = *GImGui;
+    IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
+    return g.IO.MouseReleased[button];
+}
+
+bool ImGui::IsMouseDoubleClicked(int button)
+{
+    ImGuiContext& g = *GImGui;
+    IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
+    return g.IO.MouseDoubleClicked[button];
+}
+
+bool ImGui::IsMouseDragging(int button, float lock_threshold)
+{
+    ImGuiContext& g = *GImGui;
+    IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
+    if (!g.IO.MouseDown[button])
+        return false;
+    if (lock_threshold < 0.0f)
+        lock_threshold = g.IO.MouseDragThreshold;
+    return g.IO.MouseDragMaxDistanceSqr[button] >= lock_threshold * lock_threshold;
+}
+
+ImVec2 ImGui::GetMousePos()
+{
+    return GImGui->IO.MousePos;
+}
+
+// NB: prefer to call right after BeginPopup(). At the time Selectable/MenuItem is activated, the popup is already closed!
+ImVec2 ImGui::GetMousePosOnOpeningCurrentPopup()
+{
+    ImGuiContext& g = *GImGui;
+    if (g.CurrentPopupStack.Size > 0)
+        return g.OpenPopupStack[g.CurrentPopupStack.Size-1].OpenMousePos;
+    return g.IO.MousePos;
+}
+
+// We typically use ImVec2(-FLT_MAX,-FLT_MAX) to denote an invalid mouse position
+bool ImGui::IsMousePosValid(const ImVec2* mouse_pos)
+{
+    if (mouse_pos == NULL)
+        mouse_pos = &GImGui->IO.MousePos;
+    const float MOUSE_INVALID = -256000.0f;
+    return mouse_pos->x >= MOUSE_INVALID && mouse_pos->y >= MOUSE_INVALID;
+}
+
+// NB: This is only valid if IsMousePosValid(). Back-ends in theory should always keep mouse position valid when dragging even outside the client window.
+ImVec2 ImGui::GetMouseDragDelta(int button, float lock_threshold)
+{
+    ImGuiContext& g = *GImGui;
+    IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
+    if (lock_threshold < 0.0f)
+        lock_threshold = g.IO.MouseDragThreshold;
+    if (g.IO.MouseDown[button])
+        if (g.IO.MouseDragMaxDistanceSqr[button] >= lock_threshold * lock_threshold)
+            return g.IO.MousePos - g.IO.MouseClickedPos[button];     // Assume we can only get active with left-mouse button (at the moment).
+    return ImVec2(0.0f, 0.0f);
+}
+
+void ImGui::ResetMouseDragDelta(int button)
+{
+    ImGuiContext& g = *GImGui;
+    IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
+    // NB: We don't need to reset g.IO.MouseDragMaxDistanceSqr
+    g.IO.MouseClickedPos[button] = g.IO.MousePos;
+}
+
+ImGuiMouseCursor ImGui::GetMouseCursor()
+{
+    return GImGui->MouseCursor;
+}
+
+void ImGui::SetMouseCursor(ImGuiMouseCursor cursor_type)
+{
+    GImGui->MouseCursor = cursor_type;
+}
+
+void ImGui::CaptureKeyboardFromApp(bool capture)
+{
+    GImGui->WantCaptureKeyboardNextFrame = capture ? 1 : 0;
+}
+
+void ImGui::CaptureMouseFromApp(bool capture)
+{
+    GImGui->WantCaptureMouseNextFrame = capture ? 1 : 0;
+}
+
+bool ImGui::IsItemActive()
+{
+    ImGuiContext& g = *GImGui;
+    if (g.ActiveId)
+    {
+        ImGuiWindow* window = g.CurrentWindow;
+        return g.ActiveId == window->DC.LastItemId;
+    }
+    return false;
+}
+
+bool ImGui::IsItemDeactivated()
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiWindow* window = g.CurrentWindow;
+    return (g.ActiveIdPreviousFrame == window->DC.LastItemId && g.ActiveIdPreviousFrame != 0 && g.ActiveId != window->DC.LastItemId);
+}
+
+bool ImGui::IsItemDeactivatedAfterEdit()
+{
+    ImGuiContext& g = *GImGui;
+    return IsItemDeactivated() && (g.ActiveIdPreviousFrameHasBeenEdited || (g.ActiveId == 0 && g.ActiveIdHasBeenEdited));
+}
+
+bool ImGui::IsItemFocused()
+{
+    ImGuiContext& g = *GImGui;
+    return g.NavId && !g.NavDisableHighlight && g.NavId == g.CurrentWindow->DC.LastItemId;
+}
+
+bool ImGui::IsItemClicked(int mouse_button)
+{
+    return IsMouseClicked(mouse_button) && IsItemHovered(ImGuiHoveredFlags_None);
+}
+
+bool ImGui::IsAnyItemHovered()
+{
+    ImGuiContext& g = *GImGui;
+    return g.HoveredId != 0 || g.HoveredIdPreviousFrame != 0;
+}
+
+bool ImGui::IsAnyItemActive()
+{
+    ImGuiContext& g = *GImGui;
+    return g.ActiveId != 0;
+}
+
+bool ImGui::IsAnyItemFocused()
+{
+    ImGuiContext& g = *GImGui;
+    return g.NavId != 0 && !g.NavDisableHighlight;
+}
+
+bool ImGui::IsItemVisible()
+{
+    ImGuiWindow* window = GetCurrentWindowRead();
+    return window->ClipRect.Overlaps(window->DC.LastItemRect);
+}
+
+bool ImGui::IsItemEdited()
+{
+    ImGuiWindow* window = GetCurrentWindowRead();
+    return (window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_Edited) != 0;
+}
+
+// Allow last item to be overlapped by a subsequent item. Both may be activated during the same frame before the later one takes priority.
+void ImGui::SetItemAllowOverlap()
+{
+    ImGuiContext& g = *GImGui;
+    if (g.HoveredId == g.CurrentWindow->DC.LastItemId)
+        g.HoveredIdAllowOverlap = true;
+    if (g.ActiveId == g.CurrentWindow->DC.LastItemId)
+        g.ActiveIdAllowOverlap = true;
+}
+
+ImVec2 ImGui::GetItemRectMin()
+{
+    ImGuiWindow* window = GetCurrentWindowRead();
+    return window->DC.LastItemRect.Min;
+}
+
+ImVec2 ImGui::GetItemRectMax()
+{
+    ImGuiWindow* window = GetCurrentWindowRead();
+    return window->DC.LastItemRect.Max;
+}
+
+ImVec2 ImGui::GetItemRectSize()
+{
+    ImGuiWindow* window = GetCurrentWindowRead();
+    return window->DC.LastItemRect.GetSize();
+}
+
+static ImRect GetViewportRect()
+{
+    ImGuiContext& g = *GImGui;
+    if (g.IO.DisplayVisibleMin.x != g.IO.DisplayVisibleMax.x && g.IO.DisplayVisibleMin.y != g.IO.DisplayVisibleMax.y)
+        return ImRect(g.IO.DisplayVisibleMin, g.IO.DisplayVisibleMax);
+    return ImRect(0.0f, 0.0f, g.IO.DisplaySize.x, g.IO.DisplaySize.y);
+}
+
+static bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, bool border, ImGuiWindowFlags flags)
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiWindow* parent_window = g.CurrentWindow;
+
+    flags |= ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_ChildWindow;
+    flags |= (parent_window->Flags & ImGuiWindowFlags_NoMove);  // Inherit the NoMove flag
+
+    // Size
+    const ImVec2 content_avail = GetContentRegionAvail();
+    ImVec2 size = ImFloor(size_arg);
+    const int auto_fit_axises = ((size.x == 0.0f) ? (1 << ImGuiAxis_X) : 0x00) | ((size.y == 0.0f) ? (1 << ImGuiAxis_Y) : 0x00);
+    if (size.x <= 0.0f)
+        size.x = ImMax(content_avail.x + size.x, 4.0f); // Arbitrary minimum child size (0.0f causing too much issues)
+    if (size.y <= 0.0f)
+        size.y = ImMax(content_avail.y + size.y, 4.0f);
+    SetNextWindowSize(size);
+
+    // Name
+    char title[256];
+    if (name)
+        ImFormatString(title, IM_ARRAYSIZE(title), "%s/%s", parent_window->Name, name);
+    else
+        ImFormatString(title, IM_ARRAYSIZE(title), "%s/%08X", parent_window->Name, id);
+
+    const float backup_border_size = g.Style.ChildBorderSize;
+    if (!border)
+        g.Style.ChildBorderSize = 0.0f;
+    bool ret = Begin(title, NULL, flags);
+    g.Style.ChildBorderSize = backup_border_size;
+
+    ImGuiWindow* child_window = g.CurrentWindow;
+    child_window->ChildId = id;
+    child_window->AutoFitChildAxises = auto_fit_axises;
+
+    // Process navigation-in immediately so NavInit can run on first frame
+    if (g.NavActivateId == id && !(flags & ImGuiWindowFlags_NavFlattened) && (child_window->DC.NavLayerActiveMask != 0 || child_window->DC.NavHasScroll))
+    {
+        FocusWindow(child_window);
+        NavInitWindow(child_window, false);
+        SetActiveID(id+1, child_window); // Steal ActiveId with a dummy id so that key-press won't activate child item
+        g.ActiveIdSource = ImGuiInputSource_Nav;
+    }
+    return ret;
+}
+
+bool ImGui::BeginChild(const char* str_id, const ImVec2& size_arg, bool border, ImGuiWindowFlags extra_flags)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    return BeginChildEx(str_id, window->GetID(str_id), size_arg, border, extra_flags);
+}
+
+bool ImGui::BeginChild(ImGuiID id, const ImVec2& size_arg, bool border, ImGuiWindowFlags extra_flags)
+{
+    IM_ASSERT(id != 0);
+    return BeginChildEx(NULL, id, size_arg, border, extra_flags);
+}
+
+void ImGui::EndChild()
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiWindow* window = g.CurrentWindow;
+
+    IM_ASSERT(window->Flags & ImGuiWindowFlags_ChildWindow);   // Mismatched BeginChild()/EndChild() callss
+    if (window->BeginCount > 1)
+    {
+        End();
+    }
+    else
+    {
+        ImVec2 sz = window->Size;
+        if (window->AutoFitChildAxises & (1 << ImGuiAxis_X)) // Arbitrary minimum zero-ish child size of 4.0f causes less trouble than a 0.0f
+            sz.x = ImMax(4.0f, sz.x);
+        if (window->AutoFitChildAxises & (1 << ImGuiAxis_Y))
+            sz.y = ImMax(4.0f, sz.y);
+        End();
+
+        ImGuiWindow* parent_window = g.CurrentWindow;
+        ImRect bb(parent_window->DC.CursorPos, parent_window->DC.CursorPos + sz);
+        ItemSize(sz);
+        if ((window->DC.NavLayerActiveMask != 0 || window->DC.NavHasScroll) && !(window->Flags & ImGuiWindowFlags_NavFlattened))
+        {
+            ItemAdd(bb, window->ChildId);
+            RenderNavHighlight(bb, window->ChildId);
+
+            // When browsing a window that has no activable items (scroll only) we keep a highlight on the child
+            if (window->DC.NavLayerActiveMask == 0 && window == g.NavWindow)
+                RenderNavHighlight(ImRect(bb.Min - ImVec2(2,2), bb.Max + ImVec2(2,2)), g.NavId, ImGuiNavHighlightFlags_TypeThin);
+        }
+        else
+        {
+            // Not navigable into
+            ItemAdd(bb, 0);
+        }
+    }
+}
+
+// Helper to create a child window / scrolling region that looks like a normal widget frame.
+bool ImGui::BeginChildFrame(ImGuiID id, const ImVec2& size, ImGuiWindowFlags extra_flags)
+{
+    ImGuiContext& g = *GImGui;
+    const ImGuiStyle& style = g.Style;
+    PushStyleColor(ImGuiCol_ChildBg, style.Colors[ImGuiCol_FrameBg]);
+    PushStyleVar(ImGuiStyleVar_ChildRounding, style.FrameRounding);
+    PushStyleVar(ImGuiStyleVar_ChildBorderSize, style.FrameBorderSize);
+    PushStyleVar(ImGuiStyleVar_WindowPadding, style.FramePadding);
+    bool ret = BeginChild(id, size, true, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysUseWindowPadding | extra_flags);
+    PopStyleVar(3);
+    PopStyleColor();
+    return ret;
+}
+
+void ImGui::EndChildFrame()
+{
+    EndChild();
+}
+
+// Save and compare stack sizes on Begin()/End() to detect usage errors
+static void CheckStacksSize(ImGuiWindow* window, bool write)
+{
+    // NOT checking: DC.ItemWidth, DC.AllowKeyboardFocus, DC.ButtonRepeat, DC.TextWrapPos (per window) to allow user to conveniently push once and not pop (they are cleared on Begin)
+    ImGuiContext& g = *GImGui;
+    int* p_backup = &window->DC.StackSizesBackup[0];
+    { int current = window->IDStack.Size;       if (write) *p_backup = current; else IM_ASSERT(*p_backup == current && "PushID/PopID or TreeNode/TreePop Mismatch!");   p_backup++; }    // Too few or too many PopID()/TreePop()
+    { int current = window->DC.GroupStack.Size; if (write) *p_backup = current; else IM_ASSERT(*p_backup == current && "BeginGroup/EndGroup Mismatch!");                p_backup++; }    // Too few or too many EndGroup()
+    { int current = g.CurrentPopupStack.Size;   if (write) *p_backup = current; else IM_ASSERT(*p_backup == current && "BeginMenu/EndMenu or BeginPopup/EndPopup Mismatch"); p_backup++;}// Too few or too many EndMenu()/EndPopup()
+    // For color, style and font stacks there is an incentive to use Push/Begin/Pop/.../End patterns, so we relax our checks a little to allow them.
+    { int current = g.ColorModifiers.Size;      if (write) *p_backup = current; else IM_ASSERT(*p_backup >= current && "PushStyleColor/PopStyleColor Mismatch!");       p_backup++; }    // Too few or too many PopStyleColor()
+    { int current = g.StyleModifiers.Size;      if (write) *p_backup = current; else IM_ASSERT(*p_backup >= current && "PushStyleVar/PopStyleVar Mismatch!");           p_backup++; }    // Too few or too many PopStyleVar()
+    { int current = g.FontStack.Size;           if (write) *p_backup = current; else IM_ASSERT(*p_backup >= current && "PushFont/PopFont Mismatch!");                   p_backup++; }    // Too few or too many PopFont()
+    IM_ASSERT(p_backup == window->DC.StackSizesBackup + IM_ARRAYSIZE(window->DC.StackSizesBackup));
+}
+
+static void SetWindowConditionAllowFlags(ImGuiWindow* window, ImGuiCond flags, bool enabled)
+{
+    window->SetWindowPosAllowFlags       = enabled ? (window->SetWindowPosAllowFlags       | flags) : (window->SetWindowPosAllowFlags       & ~flags);
+    window->SetWindowSizeAllowFlags      = enabled ? (window->SetWindowSizeAllowFlags      | flags) : (window->SetWindowSizeAllowFlags      & ~flags);
+    window->SetWindowCollapsedAllowFlags = enabled ? (window->SetWindowCollapsedAllowFlags | flags) : (window->SetWindowCollapsedAllowFlags & ~flags);
+}
+
+ImGuiWindow* ImGui::FindWindowByName(const char* name)
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiID id = ImHash(name, 0);
+    return (ImGuiWindow*)g.WindowsById.GetVoidPtr(id);
+}
+
+static ImGuiWindow* CreateNewWindow(const char* name, ImVec2 size, ImGuiWindowFlags flags)
+{
+    ImGuiContext& g = *GImGui;
+
+    // Create window the first time
+    ImGuiWindow* window = IM_NEW(ImGuiWindow)(&g, name);
+    window->Flags = flags;
+    g.WindowsById.SetVoidPtr(window->ID, window);
+
+    // Default/arbitrary window position. Use SetNextWindowPos() with the appropriate condition flag to change the initial position of a window.
+    window->Pos = ImVec2(60, 60);
+
+    // User can disable loading and saving of settings. Tooltip and child windows also don't store settings.
+    if (!(flags & ImGuiWindowFlags_NoSavedSettings))
+        if (ImGuiWindowSettings* settings = ImGui::FindWindowSettings(window->ID))
+        {
+            // Retrieve settings from .ini file
+            window->SettingsIdx = g.SettingsWindows.index_from_pointer(settings);
+            SetWindowConditionAllowFlags(window, ImGuiCond_FirstUseEver, false);
+            window->Pos = ImFloor(settings->Pos);
+            window->Collapsed = settings->Collapsed;
+            if (ImLengthSqr(settings->Size) > 0.00001f)
+                size = ImFloor(settings->Size);
+        }
+    window->Size = window->SizeFull = window->SizeFullAtLastBegin = size;
+    window->DC.CursorMaxPos = window->Pos; // So first call to CalcSizeContents() doesn't return crazy values
+
+    if ((flags & ImGuiWindowFlags_AlwaysAutoResize) != 0)
+    {
+        window->AutoFitFramesX = window->AutoFitFramesY = 2;
+        window->AutoFitOnlyGrows = false;
+    }
+    else
+    {
+        if (window->Size.x <= 0.0f)
+            window->AutoFitFramesX = 2;
+        if (window->Size.y <= 0.0f)
+            window->AutoFitFramesY = 2;
+        window->AutoFitOnlyGrows = (window->AutoFitFramesX > 0) || (window->AutoFitFramesY > 0);
+    }
+
+    if (flags & ImGuiWindowFlags_NoBringToFrontOnFocus)
+        g.Windows.insert(g.Windows.begin(), window); // Quite slow but rare and only once
+    else
+        g.Windows.push_back(window);
+    return window;
+}
+
+static ImVec2 CalcSizeAfterConstraint(ImGuiWindow* window, ImVec2 new_size)
+{
+    ImGuiContext& g = *GImGui;
+    if (g.NextWindowData.SizeConstraintCond != 0)
+    {
+        // Using -1,-1 on either X/Y axis to preserve the current size.
+        ImRect cr = g.NextWindowData.SizeConstraintRect;
+        new_size.x = (cr.Min.x >= 0 && cr.Max.x >= 0) ? ImClamp(new_size.x, cr.Min.x, cr.Max.x) : window->SizeFull.x;
+        new_size.y = (cr.Min.y >= 0 && cr.Max.y >= 0) ? ImClamp(new_size.y, cr.Min.y, cr.Max.y) : window->SizeFull.y;
+        if (g.NextWindowData.SizeCallback)
+        {
+            ImGuiSizeCallbackData data;
+            data.UserData = g.NextWindowData.SizeCallbackUserData;
+            data.Pos = window->Pos;
+            data.CurrentSize = window->SizeFull;
+            data.DesiredSize = new_size;
+            g.NextWindowData.SizeCallback(&data);
+            new_size = data.DesiredSize;
+        }
+    }
+
+    // Minimum size
+    if (!(window->Flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_AlwaysAutoResize)))
+    {
+        new_size = ImMax(new_size, g.Style.WindowMinSize);
+        new_size.y = ImMax(new_size.y, window->TitleBarHeight() + window->MenuBarHeight() + ImMax(0.0f, g.Style.WindowRounding - 1.0f)); // Reduce artifacts with very small windows
+    }
+    return new_size;
+}
+
+static ImVec2 CalcSizeContents(ImGuiWindow* window)
+{
+    ImVec2 sz;
+    sz.x = (float)(int)((window->SizeContentsExplicit.x != 0.0f) ? window->SizeContentsExplicit.x : (window->DC.CursorMaxPos.x - window->Pos.x + window->Scroll.x));
+    sz.y = (float)(int)((window->SizeContentsExplicit.y != 0.0f) ? window->SizeContentsExplicit.y : (window->DC.CursorMaxPos.y - window->Pos.y + window->Scroll.y));
+    return sz + window->WindowPadding;
+}
+
+static ImVec2 CalcSizeAutoFit(ImGuiWindow* window, const ImVec2& size_contents)
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiStyle& style = g.Style;
+    if (window->Flags & ImGuiWindowFlags_Tooltip)
+    {
+        // Tooltip always resize
+        return size_contents;
+    }
+    else
+    {
+        // When the window cannot fit all contents (either because of constraints, either because screen is too small): we are growing the size on the other axis to compensate for expected scrollbar. FIXME: Might turn bigger than DisplaySize-WindowPadding.
+        const bool is_popup = (window->Flags & ImGuiWindowFlags_Popup) != 0;
+        const bool is_menu = (window->Flags & ImGuiWindowFlags_ChildMenu) != 0;
+        ImVec2 size_min = style.WindowMinSize;
+        if (is_popup || is_menu) // Popups and menus bypass style.WindowMinSize by default, but we give then a non-zero minimum size to facilitate understanding problematic cases (e.g. empty popups)
+            size_min = ImMin(size_min, ImVec2(4.0f, 4.0f));
+        ImVec2 size_auto_fit = ImClamp(size_contents, size_min, ImMax(size_min, g.IO.DisplaySize - style.DisplaySafeAreaPadding * 2.0f));
+        ImVec2 size_auto_fit_after_constraint = CalcSizeAfterConstraint(window, size_auto_fit);
+        if (size_auto_fit_after_constraint.x < size_contents.x && !(window->Flags & ImGuiWindowFlags_NoScrollbar) && (window->Flags & ImGuiWindowFlags_HorizontalScrollbar))
+            size_auto_fit.y += style.ScrollbarSize;
+        if (size_auto_fit_after_constraint.y < size_contents.y && !(window->Flags & ImGuiWindowFlags_NoScrollbar))
+            size_auto_fit.x += style.ScrollbarSize;
+        return size_auto_fit;
+    }
+}
+
+ImVec2 ImGui::CalcWindowExpectedSize(ImGuiWindow* window)
+{
+    ImVec2 size_contents = CalcSizeContents(window);
+    return CalcSizeAfterConstraint(window, CalcSizeAutoFit(window, size_contents));
+}
+
+static float GetScrollMaxX(ImGuiWindow* window)
+{
+    return ImMax(0.0f, window->SizeContents.x - (window->SizeFull.x - window->ScrollbarSizes.x));
+}
+
+static float GetScrollMaxY(ImGuiWindow* window)
+{
+    return ImMax(0.0f, window->SizeContents.y - (window->SizeFull.y - window->ScrollbarSizes.y));
+}
+
+static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window, bool snap_on_edges)
+{
+    ImGuiContext& g = *GImGui;
+    ImVec2 scroll = window->Scroll;
+    if (window->ScrollTarget.x < FLT_MAX)
+    {
+        float cr_x = window->ScrollTargetCenterRatio.x;
+        scroll.x = window->ScrollTarget.x - cr_x * (window->SizeFull.x - window->ScrollbarSizes.x);
+    }
+    if (window->ScrollTarget.y < FLT_MAX)
+    {
+        // 'snap_on_edges' allows for a discontinuity at the edge of scrolling limits to take account of WindowPadding so that scrolling to make the last item visible scroll far enough to see the padding.
+        float cr_y = window->ScrollTargetCenterRatio.y;
+        float target_y = window->ScrollTarget.y;
+        if (snap_on_edges && cr_y <= 0.0f && target_y <= window->WindowPadding.y)
+            target_y = 0.0f;
+        if (snap_on_edges && cr_y >= 1.0f && target_y >= window->SizeContents.y - window->WindowPadding.y + g.Style.ItemSpacing.y)
+            target_y = window->SizeContents.y;
+        scroll.y = target_y - (1.0f - cr_y) * (window->TitleBarHeight() + window->MenuBarHeight()) - cr_y * (window->SizeFull.y - window->ScrollbarSizes.y);
+    }
+    scroll = ImMax(scroll, ImVec2(0.0f, 0.0f));
+    if (!window->Collapsed && !window->SkipItems)
+    {
+        scroll.x = ImMin(scroll.x, GetScrollMaxX(window));
+        scroll.y = ImMin(scroll.y, GetScrollMaxY(window));
+    }
+    return scroll;
+}
+
+static ImGuiCol GetWindowBgColorIdxFromFlags(ImGuiWindowFlags flags)
+{
+    if (flags & (ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_Popup))
+        return ImGuiCol_PopupBg;
+    if (flags & ImGuiWindowFlags_ChildWindow)
+        return ImGuiCol_ChildBg;
+    return ImGuiCol_WindowBg;
+}
+
+static void CalcResizePosSizeFromAnyCorner(ImGuiWindow* window, const ImVec2& corner_target, const ImVec2& corner_norm, ImVec2* out_pos, ImVec2* out_size)
+{
+    ImVec2 pos_min = ImLerp(corner_target, window->Pos, corner_norm);                // Expected window upper-left
+    ImVec2 pos_max = ImLerp(window->Pos + window->Size, corner_target, corner_norm); // Expected window lower-right
+    ImVec2 size_expected = pos_max - pos_min;
+    ImVec2 size_constrained = CalcSizeAfterConstraint(window, size_expected);
+    *out_pos = pos_min;
+    if (corner_norm.x == 0.0f)
+        out_pos->x -= (size_constrained.x - size_expected.x);
+    if (corner_norm.y == 0.0f)
+        out_pos->y -= (size_constrained.y - size_expected.y);
+    *out_size = size_constrained;
+}
+
+struct ImGuiResizeGripDef
+{
+    ImVec2  CornerPos;
+    ImVec2  InnerDir;
+    int     AngleMin12, AngleMax12;
+};
+
+const ImGuiResizeGripDef resize_grip_def[4] =
+{
+    { ImVec2(1,1), ImVec2(-1,-1), 0, 3 }, // Lower right
+    { ImVec2(0,1), ImVec2(+1,-1), 3, 6 }, // Lower left
+    { ImVec2(0,0), ImVec2(+1,+1), 6, 9 }, // Upper left
+    { ImVec2(1,0), ImVec2(-1,+1), 9,12 }, // Upper right
+};
+
+static ImRect GetResizeBorderRect(ImGuiWindow* window, int border_n, float perp_padding, float thickness)
+{
+    ImRect rect = window->Rect();
+    if (thickness == 0.0f) rect.Max -= ImVec2(1,1);
+    if (border_n == 0) return ImRect(rect.Min.x + perp_padding, rect.Min.y,                rect.Max.x - perp_padding, rect.Min.y + thickness);
+    if (border_n == 1) return ImRect(rect.Max.x - thickness,    rect.Min.y + perp_padding, rect.Max.x,                rect.Max.y - perp_padding);
+    if (border_n == 2) return ImRect(rect.Min.x + perp_padding, rect.Max.y - thickness,    rect.Max.x - perp_padding, rect.Max.y);
+    if (border_n == 3) return ImRect(rect.Min.x,                rect.Min.y + perp_padding, rect.Min.x + thickness,    rect.Max.y - perp_padding);
+    IM_ASSERT(0);
+    return ImRect();
+}
+
+// Handle resize for: Resize Grips, Borders, Gamepad
+static void ImGui::UpdateManualResize(ImGuiWindow* window, const ImVec2& size_auto_fit, int* border_held, int resize_grip_count, ImU32 resize_grip_col[4])
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiWindowFlags flags = window->Flags;
+    if ((flags & ImGuiWindowFlags_NoResize) || (flags & ImGuiWindowFlags_AlwaysAutoResize) || window->AutoFitFramesX > 0 || window->AutoFitFramesY > 0)
+        return;
+
+    const int resize_border_count = g.IO.ConfigResizeWindowsFromEdges ? 4 : 0;
+    const float grip_draw_size = (float)(int)ImMax(g.FontSize * 1.35f, window->WindowRounding + 1.0f + g.FontSize * 0.2f);
+    const float grip_hover_size = (float)(int)(grip_draw_size * 0.75f);
+
+    ImVec2 pos_target(FLT_MAX, FLT_MAX);
+    ImVec2 size_target(FLT_MAX, FLT_MAX);
+
+    // Manual resize grips
+    PushID("#RESIZE");
+    for (int resize_grip_n = 0; resize_grip_n < resize_grip_count; resize_grip_n++)
+    {
+        const ImGuiResizeGripDef& grip = resize_grip_def[resize_grip_n];
+        const ImVec2 corner = ImLerp(window->Pos, window->Pos + window->Size, grip.CornerPos);
+
+        // Using the FlattenChilds button flag we make the resize button accessible even if we are hovering over a child window
+        ImRect resize_rect(corner, corner + grip.InnerDir * grip_hover_size);
+        if (resize_rect.Min.x > resize_rect.Max.x) ImSwap(resize_rect.Min.x, resize_rect.Max.x);
+        if (resize_rect.Min.y > resize_rect.Max.y) ImSwap(resize_rect.Min.y, resize_rect.Max.y);
+        bool hovered, held;
+        ButtonBehavior(resize_rect, window->GetID((void*)(intptr_t)resize_grip_n), &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_NoNavFocus);
+        if (hovered || held)
+            g.MouseCursor = (resize_grip_n & 1) ? ImGuiMouseCursor_ResizeNESW : ImGuiMouseCursor_ResizeNWSE;
+
+        if (held && g.IO.MouseDoubleClicked[0] && resize_grip_n == 0)
+        {
+            // Manual auto-fit when double-clicking
+            size_target = CalcSizeAfterConstraint(window, size_auto_fit);
+            ClearActiveID();
+        }
+        else if (held)
+        {
+            // Resize from any of the four corners
+            // We don't use an incremental MouseDelta but rather compute an absolute target size based on mouse position
+            ImVec2 corner_target = g.IO.MousePos - g.ActiveIdClickOffset + resize_rect.GetSize() * grip.CornerPos; // Corner of the window corresponding to our corner grip
+            CalcResizePosSizeFromAnyCorner(window, corner_target, grip.CornerPos, &pos_target, &size_target);
+        }
+        if (resize_grip_n == 0 || held || hovered)
+            resize_grip_col[resize_grip_n] = GetColorU32(held ? ImGuiCol_ResizeGripActive : hovered ? ImGuiCol_ResizeGripHovered : ImGuiCol_ResizeGrip);
+    }
+    for (int border_n = 0; border_n < resize_border_count; border_n++)
+    {
+        const float BORDER_SIZE = 5.0f;          // FIXME: Only works _inside_ window because of HoveredWindow check.
+        const float BORDER_APPEAR_TIMER = 0.05f; // Reduce visual noise
+        bool hovered, held;
+        ImRect border_rect = GetResizeBorderRect(window, border_n, grip_hover_size, BORDER_SIZE);
+        ButtonBehavior(border_rect, window->GetID((void*)(intptr_t)(border_n + 4)), &hovered, &held, ImGuiButtonFlags_FlattenChildren);
+        if ((hovered && g.HoveredIdTimer > BORDER_APPEAR_TIMER) || held)
+        {
+            g.MouseCursor = (border_n & 1) ? ImGuiMouseCursor_ResizeEW : ImGuiMouseCursor_ResizeNS;
+            if (held) *border_held = border_n;
+        }
+        if (held)
+        {
+            ImVec2 border_target = window->Pos;
+            ImVec2 border_posn;
+            if (border_n == 0) { border_posn = ImVec2(0, 0); border_target.y = (g.IO.MousePos.y - g.ActiveIdClickOffset.y); }
+            if (border_n == 1) { border_posn = ImVec2(1, 0); border_target.x = (g.IO.MousePos.x - g.ActiveIdClickOffset.x + BORDER_SIZE); }
+            if (border_n == 2) { border_posn = ImVec2(0, 1); border_target.y = (g.IO.MousePos.y - g.ActiveIdClickOffset.y + BORDER_SIZE); }
+            if (border_n == 3) { border_posn = ImVec2(0, 0); border_target.x = (g.IO.MousePos.x - g.ActiveIdClickOffset.x); }
+            CalcResizePosSizeFromAnyCorner(window, border_target, border_posn, &pos_target, &size_target);
+        }
+    }
+    PopID();
+
+    // Navigation resize (keyboard/gamepad)
+    if (g.NavWindowingTarget && g.NavWindowingTarget->RootWindow == window)
+    {
+        ImVec2 nav_resize_delta;
+        if (g.NavInputSource == ImGuiInputSource_NavKeyboard && g.IO.KeyShift)
+            nav_resize_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard, ImGuiInputReadMode_Down);
+        if (g.NavInputSource == ImGuiInputSource_NavGamepad)
+            nav_resize_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_Down);
+        if (nav_resize_delta.x != 0.0f || nav_resize_delta.y != 0.0f)
+        {
+            const float NAV_RESIZE_SPEED = 600.0f;
+            nav_resize_delta *= ImFloor(NAV_RESIZE_SPEED * g.IO.DeltaTime * ImMin(g.IO.DisplayFramebufferScale.x, g.IO.DisplayFramebufferScale.y));
+            g.NavWindowingToggleLayer = false;
+            g.NavDisableMouseHover = true;
+            resize_grip_col[0] = GetColorU32(ImGuiCol_ResizeGripActive);
+            // FIXME-NAV: Should store and accumulate into a separate size buffer to handle sizing constraints properly, right now a constraint will make us stuck.
+            size_target = CalcSizeAfterConstraint(window, window->SizeFull + nav_resize_delta);
+        }
+    }
+
+    // Apply back modified position/size to window
+    if (size_target.x != FLT_MAX)
+    {
+        window->SizeFull = size_target;
+        MarkIniSettingsDirty(window);
+    }
+    if (pos_target.x != FLT_MAX)
+    {
+        window->Pos = ImFloor(pos_target);
+        MarkIniSettingsDirty(window);
+    }
+
+    window->Size = window->SizeFull;
+}
+
+void ImGui::UpdateWindowParentAndRootLinks(ImGuiWindow* window, ImGuiWindowFlags flags, ImGuiWindow* parent_window)
+{
+    window->ParentWindow = parent_window;
+    window->RootWindow = window->RootWindowForTitleBarHighlight = window->RootWindowForNav = window;
+    if (parent_window && (flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Tooltip))
+        window->RootWindow = parent_window->RootWindow;
+    if (parent_window && !(flags & ImGuiWindowFlags_Modal) && (flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup)))
+        window->RootWindowForTitleBarHighlight = parent_window->RootWindowForTitleBarHighlight;
+    while (window->RootWindowForNav->Flags & ImGuiWindowFlags_NavFlattened)
+        window->RootWindowForNav = window->RootWindowForNav->ParentWindow;
+}
+
+// Push a new ImGui window to add widgets to.
+// - A default window called "Debug" is automatically stacked at the beginning of every frame so you can use widgets without explicitly calling a Begin/End pair.
+// - Begin/End can be called multiple times during the frame with the same window name to append content.
+// - The window name is used as a unique identifier to preserve window information across frames (and save rudimentary information to the .ini file).
+//   You can use the "##" or "###" markers to use the same label with different id, or same id with different label. See documentation at the top of this file.
+// - Return false when window is collapsed, so you can early out in your code. You always need to call ImGui::End() even if false is returned.
+// - Passing 'bool* p_open' displays a Close button on the upper-right corner of the window, the pointed value will be set to false when the button is pressed.
+bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
+{
+    ImGuiContext& g = *GImGui;
+    const ImGuiStyle& style = g.Style;
+    IM_ASSERT(name != NULL);                        // Window name required
+    IM_ASSERT(g.FrameScopeActive);                  // Forgot to call ImGui::NewFrame()
+    IM_ASSERT(g.FrameCountEnded != g.FrameCount);   // Called ImGui::Render() or ImGui::EndFrame() and haven't called ImGui::NewFrame() again yet
+
+    // Find or create
+    ImGuiWindow* window = FindWindowByName(name);
+    const bool window_just_created = (window == NULL);
+    if (window_just_created)
+    {
+        ImVec2 size_on_first_use = (g.NextWindowData.SizeCond != 0) ? g.NextWindowData.SizeVal : ImVec2(0.0f, 0.0f); // Any condition flag will do since we are creating a new window here.
+        window = CreateNewWindow(name, size_on_first_use, flags);
+    }
+
+    // Automatically disable manual moving/resizing when NoInputs is set
+    if (flags & ImGuiWindowFlags_NoInputs)
+        flags |= ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize;
+
+    if (flags & ImGuiWindowFlags_NavFlattened)
+        IM_ASSERT(flags & ImGuiWindowFlags_ChildWindow);
+
+    const int current_frame = g.FrameCount;
+    const bool first_begin_of_the_frame = (window->LastFrameActive != current_frame);
+    if (first_begin_of_the_frame)
+        window->Flags = (ImGuiWindowFlags)flags;
+    else
+        flags = window->Flags;
+
+    // Parent window is latched only on the first call to Begin() of the frame, so further append-calls can be done from a different window stack
+    ImGuiWindow* parent_window_in_stack = g.CurrentWindowStack.empty() ? NULL : g.CurrentWindowStack.back();
+    ImGuiWindow* parent_window = first_begin_of_the_frame ? ((flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup)) ? parent_window_in_stack : NULL) : window->ParentWindow;
+    IM_ASSERT(parent_window != NULL || !(flags & ImGuiWindowFlags_ChildWindow));
+    window->HasCloseButton = (p_open != NULL);
+
+    // Update the Appearing flag
+    bool window_just_activated_by_user = (window->LastFrameActive < current_frame - 1);   // Not using !WasActive because the implicit "Debug" window would always toggle off->on
+    const bool window_just_appearing_after_hidden_for_resize = (window->HiddenFramesForResize > 0);
+    if (flags & ImGuiWindowFlags_Popup)
+    {
+        ImGuiPopupRef& popup_ref = g.OpenPopupStack[g.CurrentPopupStack.Size];
+        window_just_activated_by_user |= (window->PopupId != popup_ref.PopupId); // We recycle popups so treat window as activated if popup id changed
+        window_just_activated_by_user |= (window != popup_ref.Window);
+    }
+    window->Appearing = (window_just_activated_by_user || window_just_appearing_after_hidden_for_resize);
+    if (window->Appearing)
+        SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, true);
+
+    // Add to stack
+    g.CurrentWindowStack.push_back(window);
+    SetCurrentWindow(window);
+    CheckStacksSize(window, true);
+    if (flags & ImGuiWindowFlags_Popup)
+    {
+        ImGuiPopupRef& popup_ref = g.OpenPopupStack[g.CurrentPopupStack.Size];
+        popup_ref.Window = window;
+        g.CurrentPopupStack.push_back(popup_ref);
+        window->PopupId = popup_ref.PopupId;
+    }
+
+    if (window_just_appearing_after_hidden_for_resize && !(flags & ImGuiWindowFlags_ChildWindow))
+        window->NavLastIds[0] = 0;
+
+    // Process SetNextWindow***() calls
+    bool window_pos_set_by_api = false;
+    bool window_size_x_set_by_api = false, window_size_y_set_by_api = false;
+    if (g.NextWindowData.PosCond)
+    {
+        window_pos_set_by_api = (window->SetWindowPosAllowFlags & g.NextWindowData.PosCond) != 0;
+        if (window_pos_set_by_api && ImLengthSqr(g.NextWindowData.PosPivotVal) > 0.00001f)
+        {
+            // May be processed on the next frame if this is our first frame and we are measuring size
+            // FIXME: Look into removing the branch so everything can go through this same code path for consistency.
+            window->SetWindowPosVal = g.NextWindowData.PosVal;
+            window->SetWindowPosPivot = g.NextWindowData.PosPivotVal;
+            window->SetWindowPosAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing);
+        }
+        else
+        {
+            SetWindowPos(window, g.NextWindowData.PosVal, g.NextWindowData.PosCond);
+        }
+    }
+    if (g.NextWindowData.SizeCond)
+    {
+        window_size_x_set_by_api = (window->SetWindowSizeAllowFlags & g.NextWindowData.SizeCond) != 0 && (g.NextWindowData.SizeVal.x > 0.0f);
+        window_size_y_set_by_api = (window->SetWindowSizeAllowFlags & g.NextWindowData.SizeCond) != 0 && (g.NextWindowData.SizeVal.y > 0.0f);
+        SetWindowSize(window, g.NextWindowData.SizeVal, g.NextWindowData.SizeCond);
+    }
+    if (g.NextWindowData.ContentSizeCond)
+    {
+        // Adjust passed "client size" to become a "window size"
+        window->SizeContentsExplicit = g.NextWindowData.ContentSizeVal;
+        if (window->SizeContentsExplicit.y != 0.0f)
+            window->SizeContentsExplicit.y += window->TitleBarHeight() + window->MenuBarHeight();
+    }
+    else if (first_begin_of_the_frame)
+    {
+        window->SizeContentsExplicit = ImVec2(0.0f, 0.0f);
+    }
+    if (g.NextWindowData.CollapsedCond)
+        SetWindowCollapsed(window, g.NextWindowData.CollapsedVal, g.NextWindowData.CollapsedCond);
+    if (g.NextWindowData.FocusCond)
+        FocusWindow(window);
+    if (window->Appearing)
+        SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, false);
+
+    // When reusing window again multiple times a frame, just append content (don't need to setup again)
+    if (first_begin_of_the_frame)
+    {
+        // Initialize
+        const bool window_is_child_tooltip = (flags & ImGuiWindowFlags_ChildWindow) && (flags & ImGuiWindowFlags_Tooltip); // FIXME-WIP: Undocumented behavior of Child+Tooltip for pinned tooltip (#1345)
+        UpdateWindowParentAndRootLinks(window, flags, parent_window);
+
+        window->Active = true;
+        window->BeginOrderWithinParent = 0;
+        window->BeginOrderWithinContext = g.WindowsActiveCount++;
+        window->BeginCount = 0;
+        window->ClipRect = ImVec4(-FLT_MAX,-FLT_MAX,+FLT_MAX,+FLT_MAX);
+        window->LastFrameActive = current_frame;
+        window->IDStack.resize(1);
+
+        // UPDATE CONTENTS SIZE, UPDATE HIDDEN STATUS
+
+        // Update contents size from last frame for auto-fitting (or use explicit size)
+        window->SizeContents = CalcSizeContents(window);
+        if (window->HiddenFramesRegular > 0)
+            window->HiddenFramesRegular--;
+        if (window->HiddenFramesForResize > 0)
+            window->HiddenFramesForResize--;
+
+        // Hide new windows for one frame until they calculate their size
+        if (window_just_created && (!window_size_x_set_by_api || !window_size_y_set_by_api))
+            window->HiddenFramesForResize = 1;
+
+        // Hide popup/tooltip window when re-opening while we measure size (because we recycle the windows)
+        // We reset Size/SizeContents for reappearing popups/tooltips early in this function, so further code won't be tempted to use the old size.
+        if (window_just_activated_by_user && (flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) != 0)
+        {
+            window->HiddenFramesForResize = 1;
+            if (flags & ImGuiWindowFlags_AlwaysAutoResize)
+            {
+                if (!window_size_x_set_by_api)
+                    window->Size.x = window->SizeFull.x = 0.f;
+                if (!window_size_y_set_by_api)
+                    window->Size.y = window->SizeFull.y = 0.f;
+                window->SizeContents = ImVec2(0.f, 0.f);
+            }
+        }
+
+        SetCurrentWindow(window);
+
+        // Lock border size and padding for the frame (so that altering them doesn't cause inconsistencies)
+        window->WindowBorderSize = (flags & ImGuiWindowFlags_ChildWindow) ? style.ChildBorderSize : ((flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) && !(flags & ImGuiWindowFlags_Modal)) ? style.PopupBorderSize : style.WindowBorderSize;
+        window->WindowPadding = style.WindowPadding;
+        if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & (ImGuiWindowFlags_AlwaysUseWindowPadding | ImGuiWindowFlags_Popup)) && window->WindowBorderSize == 0.0f)
+            window->WindowPadding = ImVec2(0.0f, (flags & ImGuiWindowFlags_MenuBar) ? style.WindowPadding.y : 0.0f);
+        window->DC.MenuBarOffset.x = ImMax(ImMax(window->WindowPadding.x, style.ItemSpacing.x), g.NextWindowData.MenuBarOffsetMinVal.x);
+        window->DC.MenuBarOffset.y = g.NextWindowData.MenuBarOffsetMinVal.y;
+
+        // Collapse window by double-clicking on title bar
+        // At this point we don't have a clipping rectangle setup yet, so we can use the title bar area for hit detection and drawing
+        if (!(flags & ImGuiWindowFlags_NoTitleBar) && !(flags & ImGuiWindowFlags_NoCollapse))
+        {
+            // We don't use a regular button+id to test for double-click on title bar (mostly due to legacy reason, could be fixed), so verify that we don't have items over the title bar.
+            ImRect title_bar_rect = window->TitleBarRect();
+            if (g.HoveredWindow == window && g.HoveredId == 0 && g.HoveredIdPreviousFrame == 0 && IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max) && g.IO.MouseDoubleClicked[0])
+                window->WantCollapseToggle = true;
+            if (window->WantCollapseToggle)
+            {
+                window->Collapsed = !window->Collapsed;
+                MarkIniSettingsDirty(window);
+                FocusWindow(window);
+            }
+        }
+        else
+        {
+            window->Collapsed = false;
+        }
+        window->WantCollapseToggle = false;
+
+        // SIZE
+
+        // Calculate auto-fit size, handle automatic resize
+        const ImVec2 size_auto_fit = CalcSizeAutoFit(window, window->SizeContents);
+        ImVec2 size_full_modified(FLT_MAX, FLT_MAX);
+        if ((flags & ImGuiWindowFlags_AlwaysAutoResize) && !window->Collapsed)
+        {
+            // Using SetNextWindowSize() overrides ImGuiWindowFlags_AlwaysAutoResize, so it can be used on tooltips/popups, etc.
+            if (!window_size_x_set_by_api)
+                window->SizeFull.x = size_full_modified.x = size_auto_fit.x;
+            if (!window_size_y_set_by_api)
+                window->SizeFull.y = size_full_modified.y = size_auto_fit.y;
+        }
+        else if (window->AutoFitFramesX > 0 || window->AutoFitFramesY > 0)
+        {
+            // Auto-fit may only grow window during the first few frames
+            // We still process initial auto-fit on collapsed windows to get a window width, but otherwise don't honor ImGuiWindowFlags_AlwaysAutoResize when collapsed.
+            if (!window_size_x_set_by_api && window->AutoFitFramesX > 0)
+                window->SizeFull.x = size_full_modified.x = window->AutoFitOnlyGrows ? ImMax(window->SizeFull.x, size_auto_fit.x) : size_auto_fit.x;
+            if (!window_size_y_set_by_api && window->AutoFitFramesY > 0)
+                window->SizeFull.y = size_full_modified.y = window->AutoFitOnlyGrows ? ImMax(window->SizeFull.y, size_auto_fit.y) : size_auto_fit.y;
+            if (!window->Collapsed)
+                MarkIniSettingsDirty(window);
+        }
+
+        // Apply minimum/maximum window size constraints and final size
+        window->SizeFull = CalcSizeAfterConstraint(window, window->SizeFull);
+        window->Size = window->Collapsed && !(flags & ImGuiWindowFlags_ChildWindow) ? window->TitleBarRect().GetSize() : window->SizeFull;
+
+        // SCROLLBAR STATUS
+
+        // Update scrollbar status (based on the Size that was effective during last frame or the auto-resized Size).
+        if (!window->Collapsed)
+        {
+            // When reading the current size we need to read it after size constraints have been applied
+            float size_x_for_scrollbars = size_full_modified.x != FLT_MAX ? window->SizeFull.x : window->SizeFullAtLastBegin.x;
+            float size_y_for_scrollbars = size_full_modified.y != FLT_MAX ? window->SizeFull.y : window->SizeFullAtLastBegin.y;
+            window->ScrollbarY = (flags & ImGuiWindowFlags_AlwaysVerticalScrollbar) || ((window->SizeContents.y > size_y_for_scrollbars) && !(flags & ImGuiWindowFlags_NoScrollbar));
+            window->ScrollbarX = (flags & ImGuiWindowFlags_AlwaysHorizontalScrollbar) || ((window->SizeContents.x > size_x_for_scrollbars - (window->ScrollbarY ? style.ScrollbarSize : 0.0f)) && !(flags & ImGuiWindowFlags_NoScrollbar) && (flags & ImGuiWindowFlags_HorizontalScrollbar));
+            if (window->ScrollbarX && !window->ScrollbarY)
+                window->ScrollbarY = (window->SizeContents.y > size_y_for_scrollbars - style.ScrollbarSize) && !(flags & ImGuiWindowFlags_NoScrollbar);
+            window->ScrollbarSizes = ImVec2(window->ScrollbarY ? style.ScrollbarSize : 0.0f, window->ScrollbarX ? style.ScrollbarSize : 0.0f);
+        }
+
+        // POSITION
+
+        // Popup latch its initial position, will position itself when it appears next frame
+        if (window_just_activated_by_user)
+        {
+            window->AutoPosLastDirection = ImGuiDir_None;
+            if ((flags & ImGuiWindowFlags_Popup) != 0 && !window_pos_set_by_api)
+                window->Pos = g.CurrentPopupStack.back().OpenPopupPos;
+        }
+
+        // Position child window
+        if (flags & ImGuiWindowFlags_ChildWindow)
+        {
+            window->BeginOrderWithinParent = parent_window->DC.ChildWindows.Size;
+            parent_window->DC.ChildWindows.push_back(window);
+            if (!(flags & ImGuiWindowFlags_Popup) && !window_pos_set_by_api && !window_is_child_tooltip)
+                window->Pos = parent_window->DC.CursorPos;
+        }
+
+        const bool window_pos_with_pivot = (window->SetWindowPosVal.x != FLT_MAX && window->HiddenFramesForResize == 0);
+        if (window_pos_with_pivot)
+            SetWindowPos(window, ImMax(style.DisplaySafeAreaPadding, window->SetWindowPosVal - window->SizeFull * window->SetWindowPosPivot), 0); // Position given a pivot (e.g. for centering)
+        else if ((flags & ImGuiWindowFlags_ChildMenu) != 0)
+            window->Pos = FindBestWindowPosForPopup(window);
+        else if ((flags & ImGuiWindowFlags_Popup) != 0 && !window_pos_set_by_api && window_just_appearing_after_hidden_for_resize)
+            window->Pos = FindBestWindowPosForPopup(window);
+        else if ((flags & ImGuiWindowFlags_Tooltip) != 0 && !window_pos_set_by_api && !window_is_child_tooltip)
+            window->Pos = FindBestWindowPosForPopup(window);
+
+        // Clamp position so it stays visible
+        if (!(flags & ImGuiWindowFlags_ChildWindow))
+        {
+            if (!window_pos_set_by_api && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0 && g.IO.DisplaySize.x > 0.0f && g.IO.DisplaySize.y > 0.0f) // Ignore zero-sized display explicitly to avoid losing positions if a window manager reports zero-sized window when initializing or minimizing.
+            {
+                ImVec2 padding = ImMax(style.DisplayWindowPadding, style.DisplaySafeAreaPadding);
+                window->Pos = ImMax(window->Pos + window->Size, padding) - window->Size;
+                window->Pos = ImMin(window->Pos, g.IO.DisplaySize - padding);
+            }
+        }
+        window->Pos = ImFloor(window->Pos);
+
+        // Lock window rounding for the frame (so that altering them doesn't cause inconsistencies)
+        window->WindowRounding = (flags & ImGuiWindowFlags_ChildWindow) ? style.ChildRounding : ((flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiWindowFlags_Modal)) ? style.PopupRounding : style.WindowRounding;
+
+        // Prepare for item focus requests
+        window->FocusIdxAllRequestCurrent = (window->FocusIdxAllRequestNext == INT_MAX || window->FocusIdxAllCounter == -1) ? INT_MAX : (window->FocusIdxAllRequestNext + (window->FocusIdxAllCounter+1)) % (window->FocusIdxAllCounter+1);
+        window->FocusIdxTabRequestCurrent = (window->FocusIdxTabRequestNext == INT_MAX || window->FocusIdxTabCounter == -1) ? INT_MAX : (window->FocusIdxTabRequestNext + (window->FocusIdxTabCounter+1)) % (window->FocusIdxTabCounter+1);
+        window->FocusIdxAllCounter = window->FocusIdxTabCounter = -1;
+        window->FocusIdxAllRequestNext = window->FocusIdxTabRequestNext = INT_MAX;
+
+        // Apply scrolling
+        window->Scroll = CalcNextScrollFromScrollTargetAndClamp(window, true);
+        window->ScrollTarget = ImVec2(FLT_MAX, FLT_MAX);
+
+        // Apply window focus (new and reactivated windows are moved to front)
+        bool want_focus = false;
+        if (window_just_activated_by_user && !(flags & ImGuiWindowFlags_NoFocusOnAppearing))
+            if (!(flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Tooltip)) || (flags & ImGuiWindowFlags_Popup))
+                want_focus = true;
+
+        // Handle manual resize: Resize Grips, Borders, Gamepad
+        int border_held = -1;
+        ImU32 resize_grip_col[4] = { 0 };
+        const int resize_grip_count = g.IO.ConfigResizeWindowsFromEdges ? 2 : 1; // 4
+        const float grip_draw_size = (float)(int)ImMax(g.FontSize * 1.35f, window->WindowRounding + 1.0f + g.FontSize * 0.2f);
+        if (!window->Collapsed)
+            UpdateManualResize(window, size_auto_fit, &border_held, resize_grip_count, &resize_grip_col[0]);
+
+        // Default item width. Make it proportional to window size if window manually resizes
+        if (window->Size.x > 0.0f && !(flags & ImGuiWindowFlags_Tooltip) && !(flags & ImGuiWindowFlags_AlwaysAutoResize))
+            window->ItemWidthDefault = (float)(int)(window->Size.x * 0.65f);
+        else
+            window->ItemWidthDefault = (float)(int)(g.FontSize * 16.0f);
+
+        // DRAWING
+
+        // Setup draw list and outer clipping rectangle
+        window->DrawList->Clear();
+        window->DrawList->Flags = (g.Style.AntiAliasedLines ? ImDrawListFlags_AntiAliasedLines : 0) | (g.Style.AntiAliasedFill ? ImDrawListFlags_AntiAliasedFill : 0);
+        window->DrawList->PushTextureID(g.Font->ContainerAtlas->TexID);
+        ImRect viewport_rect(GetViewportRect());
+        if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup) && !window_is_child_tooltip)
+            PushClipRect(parent_window->ClipRect.Min, parent_window->ClipRect.Max, true);
+        else
+            PushClipRect(viewport_rect.Min, viewport_rect.Max, true);
+
+        // Draw modal window background (darkens what is behind them, all viewports)
+        const bool dim_bg_for_modal = (flags & ImGuiWindowFlags_Modal) && window == GetFrontMostPopupModal() && window->HiddenFramesForResize <= 0;
+        const bool dim_bg_for_window_list = g.NavWindowingTargetAnim && (window == g.NavWindowingTargetAnim->RootWindow);
+        if (dim_bg_for_modal || dim_bg_for_window_list)
+        {
+            const ImU32 dim_bg_col = GetColorU32(dim_bg_for_modal ? ImGuiCol_ModalWindowDimBg : ImGuiCol_NavWindowingDimBg, g.DimBgRatio);
+            window->DrawList->AddRectFilled(viewport_rect.Min, viewport_rect.Max, dim_bg_col);
+        }
+
+        // Draw navigation selection/windowing rectangle background
+        if (dim_bg_for_window_list && window == g.NavWindowingTargetAnim)
+        {
+            ImRect bb = window->Rect();
+            bb.Expand(g.FontSize);
+            if (!bb.Contains(viewport_rect)) // Avoid drawing if the window covers all the viewport anyway
+                window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_NavWindowingHighlight, g.NavWindowingHighlightAlpha * 0.25f), g.Style.WindowRounding);
+        }
+
+        // Draw window + handle manual resize
+        const float window_rounding = window->WindowRounding;
+        const float window_border_size = window->WindowBorderSize;
+        const ImGuiWindow* window_to_highlight = g.NavWindowingTarget ? g.NavWindowingTarget : g.NavWindow;
+        const bool title_bar_is_highlight = want_focus || (window_to_highlight && window->RootWindowForTitleBarHighlight == window_to_highlight->RootWindowForTitleBarHighlight);
+        const ImRect title_bar_rect = window->TitleBarRect();
+        if (window->Collapsed)
+        {
+            // Title bar only
+            float backup_border_size = style.FrameBorderSize;
+            g.Style.FrameBorderSize = window->WindowBorderSize;
+            ImU32 title_bar_col = GetColorU32((title_bar_is_highlight && !g.NavDisableHighlight) ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBgCollapsed);
+            RenderFrame(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, true, window_rounding);
+            g.Style.FrameBorderSize = backup_border_size;
+        }
+        else
+        {
+            // Window background
+            ImU32 bg_col = GetColorU32(GetWindowBgColorIdxFromFlags(flags));
+            if (g.NextWindowData.BgAlphaCond != 0)
+            {
+                bg_col = (bg_col & ~IM_COL32_A_MASK) | (IM_F32_TO_INT8_SAT(g.NextWindowData.BgAlphaVal) << IM_COL32_A_SHIFT);
+                g.NextWindowData.BgAlphaCond = 0;
+            }
+            window->DrawList->AddRectFilled(window->Pos + ImVec2(0, window->TitleBarHeight()), window->Pos + window->Size, bg_col, window_rounding, (flags & ImGuiWindowFlags_NoTitleBar) ? ImDrawCornerFlags_All : ImDrawCornerFlags_Bot);
+
+            // Title bar
+            ImU32 title_bar_col = GetColorU32(window->Collapsed ? ImGuiCol_TitleBgCollapsed : title_bar_is_highlight ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBg);
+            if (!(flags & ImGuiWindowFlags_NoTitleBar))
+                window->DrawList->AddRectFilled(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, window_rounding, ImDrawCornerFlags_Top);
+
+            // Menu bar
+            if (flags & ImGuiWindowFlags_MenuBar)
+            {
+                ImRect menu_bar_rect = window->MenuBarRect();
+                menu_bar_rect.ClipWith(window->Rect());  // Soft clipping, in particular child window don't have minimum size covering the menu bar so this is useful for them.
+                window->DrawList->AddRectFilled(menu_bar_rect.Min, menu_bar_rect.Max, GetColorU32(ImGuiCol_MenuBarBg), (flags & ImGuiWindowFlags_NoTitleBar) ? window_rounding : 0.0f, ImDrawCornerFlags_Top);
+                if (style.FrameBorderSize > 0.0f && menu_bar_rect.Max.y < window->Pos.y + window->Size.y)
+                    window->DrawList->AddLine(menu_bar_rect.GetBL(), menu_bar_rect.GetBR(), GetColorU32(ImGuiCol_Border), style.FrameBorderSize);
+            }
+
+            // Scrollbars
+            if (window->ScrollbarX)
+                Scrollbar(ImGuiLayoutType_Horizontal);
+            if (window->ScrollbarY)
+                Scrollbar(ImGuiLayoutType_Vertical);
+
+            // Render resize grips (after their input handling so we don't have a frame of latency)
+            if (!(flags & ImGuiWindowFlags_NoResize))
+            {
+                for (int resize_grip_n = 0; resize_grip_n < resize_grip_count; resize_grip_n++)
+                {
+                    const ImGuiResizeGripDef& grip = resize_grip_def[resize_grip_n];
+                    const ImVec2 corner = ImLerp(window->Pos, window->Pos + window->Size, grip.CornerPos);
+                    window->DrawList->PathLineTo(corner + grip.InnerDir * ((resize_grip_n & 1) ? ImVec2(window_border_size, grip_draw_size) : ImVec2(grip_draw_size, window_border_size)));
+                    window->DrawList->PathLineTo(corner + grip.InnerDir * ((resize_grip_n & 1) ? ImVec2(grip_draw_size, window_border_size) : ImVec2(window_border_size, grip_draw_size)));
+                    window->DrawList->PathArcToFast(ImVec2(corner.x + grip.InnerDir.x * (window_rounding + window_border_size), corner.y + grip.InnerDir.y * (window_rounding + window_border_size)), window_rounding, grip.AngleMin12, grip.AngleMax12);
+                    window->DrawList->PathFillConvex(resize_grip_col[resize_grip_n]);
+                }
+            }
+
+            // Borders
+            if (window_border_size > 0.0f)
+                window->DrawList->AddRect(window->Pos, window->Pos + window->Size, GetColorU32(ImGuiCol_Border), window_rounding, ImDrawCornerFlags_All, window_border_size);
+            if (border_held != -1)
+            {
+                ImRect border = GetResizeBorderRect(window, border_held, grip_draw_size, 0.0f);
+                window->DrawList->AddLine(border.Min, border.Max, GetColorU32(ImGuiCol_SeparatorActive), ImMax(1.0f, window_border_size));
+            }
+            if (style.FrameBorderSize > 0 && !(flags & ImGuiWindowFlags_NoTitleBar))
+                window->DrawList->AddLine(title_bar_rect.GetBL() + ImVec2(style.WindowBorderSize, -1), title_bar_rect.GetBR() + ImVec2(-style.WindowBorderSize, -1), GetColorU32(ImGuiCol_Border), style.FrameBorderSize);
+        }
+
+        // Draw navigation selection/windowing rectangle border
+        if (g.NavWindowingTargetAnim == window)
+        {
+            float rounding = ImMax(window->WindowRounding, g.Style.WindowRounding);
+            ImRect bb = window->Rect();
+            bb.Expand(g.FontSize);
+            if (bb.Contains(viewport_rect)) // If a window fits the entire viewport, adjust its highlight inward
+            {
+                bb.Expand(-g.FontSize - 1.0f);
+                rounding = window->WindowRounding;
+            }
+            window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_NavWindowingHighlight, g.NavWindowingHighlightAlpha), rounding, ~0, 3.0f);
+        }
+
+        // Store a backup of SizeFull which we will use next frame to decide if we need scrollbars.
+        window->SizeFullAtLastBegin = window->SizeFull;
+
+        // Update various regions. Variables they depends on are set above in this function.
+        // FIXME: window->ContentsRegionRect.Max is currently very misleading / partly faulty, but some BeginChild() patterns relies on it.
+        window->ContentsRegionRect.Min.x = window->Pos.x - window->Scroll.x + window->WindowPadding.x;
+        window->ContentsRegionRect.Min.y = window->Pos.y - window->Scroll.y + window->WindowPadding.y + window->TitleBarHeight() + window->MenuBarHeight();
+        window->ContentsRegionRect.Max.x = window->Pos.x - window->Scroll.x - window->WindowPadding.x + (window->SizeContentsExplicit.x != 0.0f ? window->SizeContentsExplicit.x : (window->Size.x - window->ScrollbarSizes.x));
+        window->ContentsRegionRect.Max.y = window->Pos.y - window->Scroll.y - window->WindowPadding.y + (window->SizeContentsExplicit.y != 0.0f ? window->SizeContentsExplicit.y : (window->Size.y - window->ScrollbarSizes.y));
+
+        // Setup drawing context
+        // (NB: That term "drawing context / DC" lost its meaning a long time ago. Initially was meant to hold transient data only. Nowadays difference between window-> and window->DC-> is dubious.)
+        window->DC.Indent.x = 0.0f + window->WindowPadding.x - window->Scroll.x;
+        window->DC.GroupOffset.x = 0.0f;
+        window->DC.ColumnsOffset.x = 0.0f;
+        window->DC.CursorStartPos = window->Pos + ImVec2(window->DC.Indent.x + window->DC.ColumnsOffset.x, window->TitleBarHeight() + window->MenuBarHeight() + window->WindowPadding.y - window->Scroll.y);
+        window->DC.CursorPos = window->DC.CursorStartPos;
+        window->DC.CursorPosPrevLine = window->DC.CursorPos;
+        window->DC.CursorMaxPos = window->DC.CursorStartPos;
+        window->DC.CurrentLineSize = window->DC.PrevLineSize = ImVec2(0.0f, 0.0f);
+        window->DC.CurrentLineTextBaseOffset = window->DC.PrevLineTextBaseOffset = 0.0f;
+        window->DC.NavHideHighlightOneFrame = false;
+        window->DC.NavHasScroll = (GetScrollMaxY() > 0.0f);
+        window->DC.NavLayerActiveMask = window->DC.NavLayerActiveMaskNext;
+        window->DC.NavLayerActiveMaskNext = 0x00;
+        window->DC.MenuBarAppending = false;
+        window->DC.LogLinePosY = window->DC.CursorPos.y - 9999.0f;
+        window->DC.ChildWindows.resize(0);
+        window->DC.LayoutType = ImGuiLayoutType_Vertical;
+        window->DC.ParentLayoutType = parent_window ? parent_window->DC.LayoutType : ImGuiLayoutType_Vertical;
+        window->DC.ItemFlags = parent_window ? parent_window->DC.ItemFlags : ImGuiItemFlags_Default_;
+        window->DC.ItemWidth = window->ItemWidthDefault;
+        window->DC.TextWrapPos = -1.0f; // disabled
+        window->DC.ItemFlagsStack.resize(0);
+        window->DC.ItemWidthStack.resize(0);
+        window->DC.TextWrapPosStack.resize(0);
+        window->DC.ColumnsSet = NULL;
+        window->DC.TreeDepth = 0;
+        window->DC.TreeDepthMayJumpToParentOnPop = 0x00;
+        window->DC.StateStorage = &window->StateStorage;
+        window->DC.GroupStack.resize(0);
+        window->MenuColumns.Update(3, style.ItemSpacing.x, window_just_activated_by_user);
+
+        if ((flags & ImGuiWindowFlags_ChildWindow) && (window->DC.ItemFlags != parent_window->DC.ItemFlags))
+        {
+            window->DC.ItemFlags = parent_window->DC.ItemFlags;
+            window->DC.ItemFlagsStack.push_back(window->DC.ItemFlags);
+        }
+
+        if (window->AutoFitFramesX > 0)
+            window->AutoFitFramesX--;
+        if (window->AutoFitFramesY > 0)
+            window->AutoFitFramesY--;
+
+        // Apply focus (we need to call FocusWindow() AFTER setting DC.CursorStartPos so our initial navigation reference rectangle can start around there)
+        if (want_focus)
+        {
+            FocusWindow(window);
+            NavInitWindow(window, false);
+        }
+
+        // Title bar
+        if (!(flags & ImGuiWindowFlags_NoTitleBar))
+        {
+            // Close & collapse button are on layer 1 (same as menus) and don't default focus
+            const ImGuiItemFlags item_flags_backup = window->DC.ItemFlags;
+            window->DC.ItemFlags |= ImGuiItemFlags_NoNavDefaultFocus;
+            window->DC.NavLayerCurrent++;
+            window->DC.NavLayerCurrentMask <<= 1;
+
+            // Collapse button
+            if (!(flags & ImGuiWindowFlags_NoCollapse))
+                if (CollapseButton(window->GetID("#COLLAPSE"), window->Pos))
+                    window->WantCollapseToggle = true; // Defer collapsing to next frame as we are too far in the Begin() function
+
+            // Close button
+            if (p_open != NULL)
+            {
+                const float pad = style.FramePadding.y;
+                const float rad = g.FontSize * 0.5f;
+                if (CloseButton(window->GetID("#CLOSE"), window->Rect().GetTR() + ImVec2(-pad - rad, pad + rad), rad + 1))
+                    *p_open = false;
+            }
+
+            window->DC.NavLayerCurrent--;
+            window->DC.NavLayerCurrentMask >>= 1;
+            window->DC.ItemFlags = item_flags_backup;
+
+            // Title text (FIXME: refactor text alignment facilities along with RenderText helpers, this is too much code for what it does.)
+            ImVec2 text_size = CalcTextSize(name, NULL, true);
+            ImRect text_r = title_bar_rect;
+            float pad_left = (flags & ImGuiWindowFlags_NoCollapse) ? style.FramePadding.x : (style.FramePadding.x + g.FontSize + style.ItemInnerSpacing.x);
+            float pad_right = (p_open == NULL)                     ? style.FramePadding.x : (style.FramePadding.x + g.FontSize + style.ItemInnerSpacing.x);
+            if (style.WindowTitleAlign.x > 0.0f)
+                pad_right = ImLerp(pad_right, pad_left, style.WindowTitleAlign.x);
+            text_r.Min.x += pad_left;
+            text_r.Max.x -= pad_right;
+            ImRect clip_rect = text_r;
+            clip_rect.Max.x = window->Pos.x + window->Size.x - (p_open ? title_bar_rect.GetHeight() - 3 : style.FramePadding.x); // Match the size of CloseButton()
+            RenderTextClipped(text_r.Min, text_r.Max, name, NULL, &text_size, style.WindowTitleAlign, &clip_rect);
+        }
+
+        // Save clipped aabb so we can access it in constant-time in FindHoveredWindow()
+        window->OuterRectClipped = window->Rect();
+        window->OuterRectClipped.ClipWith(window->ClipRect);
+
+        // Pressing CTRL+C while holding on a window copy its content to the clipboard
+        // This works but 1. doesn't handle multiple Begin/End pairs, 2. recursing into another Begin/End pair - so we need to work that out and add better logging scope.
+        // Maybe we can support CTRL+C on every element?
+        /*
+        if (g.ActiveId == move_id)
+            if (g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_C))
+                ImGui::LogToClipboard();
+        */
+
+        // Inner rectangle
+        // We set this up after processing the resize grip so that our clip rectangle doesn't lag by a frame
+        // Note that if our window is collapsed we will end up with an inverted (~null) clipping rectangle which is the correct behavior.
+        window->InnerMainRect.Min.x = title_bar_rect.Min.x + window->WindowBorderSize;
+        window->InnerMainRect.Min.y = title_bar_rect.Max.y + window->MenuBarHeight() + (((flags & ImGuiWindowFlags_MenuBar) || !(flags & ImGuiWindowFlags_NoTitleBar)) ? style.FrameBorderSize : window->WindowBorderSize);
+        window->InnerMainRect.Max.x = window->Pos.x + window->Size.x - window->ScrollbarSizes.x - window->WindowBorderSize;
+        window->InnerMainRect.Max.y = window->Pos.y + window->Size.y - window->ScrollbarSizes.y - window->WindowBorderSize;
+        //window->DrawList->AddRect(window->InnerRect.Min, window->InnerRect.Max, IM_COL32_WHITE);
+
+        // Inner clipping rectangle
+        // Force round operator last to ensure that e.g. (int)(max.x-min.x) in user's render code produce correct result.
+        window->InnerClipRect.Min.x = ImFloor(0.5f + window->InnerMainRect.Min.x + ImMax(0.0f, ImFloor(window->WindowPadding.x*0.5f - window->WindowBorderSize)));
+        window->InnerClipRect.Min.y = ImFloor(0.5f + window->InnerMainRect.Min.y);
+        window->InnerClipRect.Max.x = ImFloor(0.5f + window->InnerMainRect.Max.x - ImMax(0.0f, ImFloor(window->WindowPadding.x*0.5f - window->WindowBorderSize)));
+        window->InnerClipRect.Max.y = ImFloor(0.5f + window->InnerMainRect.Max.y);
+
+        // After Begin() we fill the last item / hovered data based on title bar data. It is a standard behavior (to allow creation of context menus on title bar only, etc.).
+        window->DC.LastItemId = window->MoveId;
+        window->DC.LastItemStatusFlags = IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max, false) ? ImGuiItemStatusFlags_HoveredRect : 0;
+        window->DC.LastItemRect = title_bar_rect;
+    }
+
+    PushClipRect(window->InnerClipRect.Min, window->InnerClipRect.Max, true);
+
+    // Clear 'accessed' flag last thing (After PushClipRect which will set the flag. We want the flag to stay false when the default "Debug" window is unused)
+    if (first_begin_of_the_frame)
+        window->WriteAccessed = false;
+
+    window->BeginCount++;
+    g.NextWindowData.Clear();
+
+    if (flags & ImGuiWindowFlags_ChildWindow)
+    {
+        // Child window can be out of sight and have "negative" clip windows.
+        // Mark them as collapsed so commands are skipped earlier (we can't manually collapse them because they have no title bar).
+        IM_ASSERT((flags & ImGuiWindowFlags_NoTitleBar) != 0);
+
+        if (!(flags & ImGuiWindowFlags_AlwaysAutoResize) && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0)
+            if (window->OuterRectClipped.Min.x >= window->OuterRectClipped.Max.x || window->OuterRectClipped.Min.y >= window->OuterRectClipped.Max.y)
+                window->HiddenFramesRegular = 1;
+
+        // Completely hide along with parent or if parent is collapsed
+        if (parent_window && (parent_window->Collapsed || parent_window->Hidden))
+            window->HiddenFramesRegular = 1;
+    }
+
+    // Don't render if style alpha is 0.0 at the time of Begin(). This is arbitrary and inconsistent but has been there for a long while (may remove at some point)
+    if (style.Alpha <= 0.0f)
+        window->HiddenFramesRegular = 1;
+
+    // Update the Hidden flag
+    window->Hidden = (window->HiddenFramesRegular > 0) || (window->HiddenFramesForResize);
+
+    // Return false if we don't intend to display anything to allow user to perform an early out optimization
+    window->SkipItems = (window->Collapsed || !window->Active || window->Hidden) && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0 && window->HiddenFramesForResize <= 0;
+
+    return !window->SkipItems;
+}
+
+// Old Begin() API with 5 parameters, avoid calling this version directly! Use SetNextWindowSize()/SetNextWindowBgAlpha() + Begin() instead.
+#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
+bool ImGui::Begin(const char* name, bool* p_open, const ImVec2& size_first_use, float bg_alpha_override, ImGuiWindowFlags flags)
+{
+    // Old API feature: we could pass the initial window size as a parameter. This was misleading because it only had an effect if the window didn't have data in the .ini file.
+    if (size_first_use.x != 0.0f || size_first_use.y != 0.0f)
+        ImGui::SetNextWindowSize(size_first_use, ImGuiCond_FirstUseEver);
+
+    // Old API feature: override the window background alpha with a parameter.
+    if (bg_alpha_override >= 0.0f)
+        ImGui::SetNextWindowBgAlpha(bg_alpha_override);
+
+    return ImGui::Begin(name, p_open, flags);
+}
+#endif // IMGUI_DISABLE_OBSOLETE_FUNCTIONS
+
+void ImGui::End()
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiWindow* window = g.CurrentWindow;
+
+    if (window->DC.ColumnsSet != NULL)
+        EndColumns();
+    PopClipRect();   // Inner window clip rectangle
+
+    // Stop logging
+    if (!(window->Flags & ImGuiWindowFlags_ChildWindow))    // FIXME: add more options for scope of logging
+        LogFinish();
+
+    // Pop from window stack
+    g.CurrentWindowStack.pop_back();
+    if (window->Flags & ImGuiWindowFlags_Popup)
+        g.CurrentPopupStack.pop_back();
+    CheckStacksSize(window, false);
+    SetCurrentWindow(g.CurrentWindowStack.empty() ? NULL : g.CurrentWindowStack.back());
+}
+
+void ImGui::BringWindowToFront(ImGuiWindow* window)
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiWindow* current_front_window = g.Windows.back();
+    if (current_front_window == window || current_front_window->RootWindow == window)
+        return;
+    for (int i = g.Windows.Size - 2; i >= 0; i--) // We can ignore the front most window
+        if (g.Windows[i] == window)
+        {
+            g.Windows.erase(g.Windows.Data + i);
+            g.Windows.push_back(window);
+            break;
+        }
+}
+
+void ImGui::BringWindowToBack(ImGuiWindow* window)
+{
+    ImGuiContext& g = *GImGui;
+    if (g.Windows[0] == window)
+        return;
+    for (int i = 0; i < g.Windows.Size; i++)
+        if (g.Windows[i] == window)
+        {
+            memmove(&g.Windows[1], &g.Windows[0], (size_t)i * sizeof(ImGuiWindow*));
+            g.Windows[0] = window;
+            break;
+        }
+}
+
+// Moving window to front of display and set focus (which happens to be back of our sorted list)
+void ImGui::FocusWindow(ImGuiWindow* window)
+{
+    ImGuiContext& g = *GImGui;
+
+    if (g.NavWindow != window)
+    {
+        g.NavWindow = window;
+        if (window && g.NavDisableMouseHover)
+            g.NavMousePosDirty = true;
+        g.NavInitRequest = false;
+        g.NavId = window ? window->NavLastIds[0] : 0; // Restore NavId
+        g.NavIdIsAlive = false;
+        g.NavLayer = 0;
+        //printf("[%05d] FocusWindow(\"%s\")\n", g.FrameCount, window ? window->Name : NULL);
+    }
+
+    // Passing NULL allow to disable keyboard focus
+    if (!window)
+        return;
+
+    // Move the root window to the top of the pile
+    if (window->RootWindow)
+        window = window->RootWindow;
+
+    // Steal focus on active widgets
+    if (window->Flags & ImGuiWindowFlags_Popup) // FIXME: This statement should be unnecessary. Need further testing before removing it..
+        if (g.ActiveId != 0 && g.ActiveIdWindow && g.ActiveIdWindow->RootWindow != window)
+            ClearActiveID();
+
+    // Bring to front
+    if (!(window->Flags & ImGuiWindowFlags_NoBringToFrontOnFocus))
+        BringWindowToFront(window);
+}
+
+void ImGui::FocusFrontMostActiveWindowIgnoringOne(ImGuiWindow* ignore_window)
+{
+    ImGuiContext& g = *GImGui;
+    for (int i = g.Windows.Size - 1; i >= 0; i--)
+        if (g.Windows[i] != ignore_window && g.Windows[i]->WasActive && !(g.Windows[i]->Flags & ImGuiWindowFlags_ChildWindow))
+        {
+            ImGuiWindow* focus_window = NavRestoreLastChildNavWindow(g.Windows[i]);
+            FocusWindow(focus_window);
+            return;
+        }
+}
+
+void ImGui::PushItemWidth(float item_width)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    window->DC.ItemWidth = (item_width == 0.0f ? window->ItemWidthDefault : item_width);
+    window->DC.ItemWidthStack.push_back(window->DC.ItemWidth);
+}
+
+void ImGui::PushMultiItemsWidths(int components, float w_full)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    const ImGuiStyle& style = GImGui->Style;
+    if (w_full <= 0.0f)
+        w_full = CalcItemWidth();
+    const float w_item_one  = ImMax(1.0f, (float)(int)((w_full - (style.ItemInnerSpacing.x) * (components-1)) / (float)components));
+    const float w_item_last = ImMax(1.0f, (float)(int)(w_full - (w_item_one + style.ItemInnerSpacing.x) * (components-1)));
+    window->DC.ItemWidthStack.push_back(w_item_last);
+    for (int i = 0; i < components-1; i++)
+        window->DC.ItemWidthStack.push_back(w_item_one);
+    window->DC.ItemWidth = window->DC.ItemWidthStack.back();
+}
+
+void ImGui::PopItemWidth()
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    window->DC.ItemWidthStack.pop_back();
+    window->DC.ItemWidth = window->DC.ItemWidthStack.empty() ? window->ItemWidthDefault : window->DC.ItemWidthStack.back();
+}
+
+float ImGui::CalcItemWidth()
+{
+    ImGuiWindow* window = GetCurrentWindowRead();
+    float w = window->DC.ItemWidth;
+    if (w < 0.0f)
+    {
+        // Align to a right-side limit. We include 1 frame padding in the calculation because this is how the width is always used (we add 2 frame padding to it), but we could move that responsibility to the widget as well.
+        float width_to_right_edge = GetContentRegionAvail().x;
+        w = ImMax(1.0f, width_to_right_edge + w);
+    }
+    w = (float)(int)w;
+    return w;
+}
+
+void ImGui::SetCurrentFont(ImFont* font)
+{
+    ImGuiContext& g = *GImGui;
+    IM_ASSERT(font && font->IsLoaded());    // Font Atlas not created. Did you call io.Fonts->GetTexDataAsRGBA32 / GetTexDataAsAlpha8 ?
+    IM_ASSERT(font->Scale > 0.0f);
+    g.Font = font;
+    g.FontBaseSize = g.IO.FontGlobalScale * g.Font->FontSize * g.Font->Scale;
+    g.FontSize = g.CurrentWindow ? g.CurrentWindow->CalcFontSize() : 0.0f;
+
+    ImFontAtlas* atlas = g.Font->ContainerAtlas;
+    g.DrawListSharedData.TexUvWhitePixel = atlas->TexUvWhitePixel;
+    g.DrawListSharedData.Font = g.Font;
+    g.DrawListSharedData.FontSize = g.FontSize;
+}
+
+void ImGui::PushFont(ImFont* font)
+{
+    ImGuiContext& g = *GImGui;
+    if (!font)
+        font = GetDefaultFont();
+    SetCurrentFont(font);
+    g.FontStack.push_back(font);
+    g.CurrentWindow->DrawList->PushTextureID(font->ContainerAtlas->TexID);
+}
+
+void  ImGui::PopFont()
+{
+    ImGuiContext& g = *GImGui;
+    g.CurrentWindow->DrawList->PopTextureID();
+    g.FontStack.pop_back();
+    SetCurrentFont(g.FontStack.empty() ? GetDefaultFont() : g.FontStack.back());
+}
+
+void ImGui::PushItemFlag(ImGuiItemFlags option, bool enabled)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    if (enabled)
+        window->DC.ItemFlags |= option;
+    else
+        window->DC.ItemFlags &= ~option;
+    window->DC.ItemFlagsStack.push_back(window->DC.ItemFlags);
+}
+
+void ImGui::PopItemFlag()
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    window->DC.ItemFlagsStack.pop_back();
+    window->DC.ItemFlags = window->DC.ItemFlagsStack.empty() ? ImGuiItemFlags_Default_ : window->DC.ItemFlagsStack.back();
+}
+
+void ImGui::PushAllowKeyboardFocus(bool allow_keyboard_focus)
+{
+    PushItemFlag(ImGuiItemFlags_AllowKeyboardFocus, allow_keyboard_focus);
+}
+
+void ImGui::PopAllowKeyboardFocus()
+{
+    PopItemFlag();
+}
+
+void ImGui::PushButtonRepeat(bool repeat)
+{
+    PushItemFlag(ImGuiItemFlags_ButtonRepeat, repeat);
+}
+
+void ImGui::PopButtonRepeat()
+{
+    PopItemFlag();
+}
+
+void ImGui::PushTextWrapPos(float wrap_pos_x)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    window->DC.TextWrapPos = wrap_pos_x;
+    window->DC.TextWrapPosStack.push_back(wrap_pos_x);
+}
+
+void ImGui::PopTextWrapPos()
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    window->DC.TextWrapPosStack.pop_back();
+    window->DC.TextWrapPos = window->DC.TextWrapPosStack.empty() ? -1.0f : window->DC.TextWrapPosStack.back();
+}
+
+// FIXME: This may incur a round-trip (if the end user got their data from a float4) but eventually we aim to store the in-flight colors as ImU32
+void ImGui::PushStyleColor(ImGuiCol idx, ImU32 col)
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiColorMod backup;
+    backup.Col = idx;
+    backup.BackupValue = g.Style.Colors[idx];
+    g.ColorModifiers.push_back(backup);
+    g.Style.Colors[idx] = ColorConvertU32ToFloat4(col);
+}
+
+void ImGui::PushStyleColor(ImGuiCol idx, const ImVec4& col)
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiColorMod backup;
+    backup.Col = idx;
+    backup.BackupValue = g.Style.Colors[idx];
+    g.ColorModifiers.push_back(backup);
+    g.Style.Colors[idx] = col;
+}
+
+void ImGui::PopStyleColor(int count)
+{
+    ImGuiContext& g = *GImGui;
+    while (count > 0)
+    {
+        ImGuiColorMod& backup = g.ColorModifiers.back();
+        g.Style.Colors[backup.Col] = backup.BackupValue;
+        g.ColorModifiers.pop_back();
+        count--;
+    }
+}
+
+struct ImGuiStyleVarInfo
+{
+    ImGuiDataType   Type;
+    ImU32           Count;
+    ImU32           Offset;
+    void*           GetVarPtr(ImGuiStyle* style) const { return (void*)((unsigned char*)style + Offset); }
+};
+
+static const ImGuiStyleVarInfo GStyleVarInfo[] =
+{
+    { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, Alpha) },              // ImGuiStyleVar_Alpha
+    { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowPadding) },      // ImGuiStyleVar_WindowPadding
+    { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowRounding) },     // ImGuiStyleVar_WindowRounding
+    { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowBorderSize) },   // ImGuiStyleVar_WindowBorderSize
+    { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowMinSize) },      // ImGuiStyleVar_WindowMinSize
+    { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowTitleAlign) },   // ImGuiStyleVar_WindowTitleAlign
+    { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, ChildRounding) },      // ImGuiStyleVar_ChildRounding
+    { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, ChildBorderSize) },    // ImGuiStyleVar_ChildBorderSize
+    { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, PopupRounding) },      // ImGuiStyleVar_PopupRounding
+    { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, PopupBorderSize) },    // ImGuiStyleVar_PopupBorderSize
+    { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, FramePadding) },       // ImGuiStyleVar_FramePadding
+    { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, FrameRounding) },      // ImGuiStyleVar_FrameRounding
+    { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, FrameBorderSize) },    // ImGuiStyleVar_FrameBorderSize
+    { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, ItemSpacing) },        // ImGuiStyleVar_ItemSpacing
+    { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, ItemInnerSpacing) },   // ImGuiStyleVar_ItemInnerSpacing
+    { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, IndentSpacing) },      // ImGuiStyleVar_IndentSpacing
+    { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, ScrollbarSize) },      // ImGuiStyleVar_ScrollbarSize
+    { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, ScrollbarRounding) },  // ImGuiStyleVar_ScrollbarRounding
+    { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, GrabMinSize) },        // ImGuiStyleVar_GrabMinSize
+    { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, GrabRounding) },       // ImGuiStyleVar_GrabRounding
+    { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, ButtonTextAlign) },    // ImGuiStyleVar_ButtonTextAlign
+};
+
+static const ImGuiStyleVarInfo* GetStyleVarInfo(ImGuiStyleVar idx)
+{
+    IM_ASSERT(idx >= 0 && idx < ImGuiStyleVar_COUNT);
+    IM_ASSERT(IM_ARRAYSIZE(GStyleVarInfo) == ImGuiStyleVar_COUNT);
+    return &GStyleVarInfo[idx];
+}
+
+void ImGui::PushStyleVar(ImGuiStyleVar idx, float val)
+{
+    const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(idx);
+    if (var_info->Type == ImGuiDataType_Float && var_info->Count == 1)
+    {
+        ImGuiContext& g = *GImGui;
+        float* pvar = (float*)var_info->GetVarPtr(&g.Style);
+        g.StyleModifiers.push_back(ImGuiStyleMod(idx, *pvar));
+        *pvar = val;
+        return;
+    }
+    IM_ASSERT(0); // Called function with wrong-type? Variable is not a float.
+}
+
+void ImGui::PushStyleVar(ImGuiStyleVar idx, const ImVec2& val)
+{
+    const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(idx);
+    if (var_info->Type == ImGuiDataType_Float && var_info->Count == 2)
+    {
+        ImGuiContext& g = *GImGui;
+        ImVec2* pvar = (ImVec2*)var_info->GetVarPtr(&g.Style);
+        g.StyleModifiers.push_back(ImGuiStyleMod(idx, *pvar));
+        *pvar = val;
+        return;
+    }
+    IM_ASSERT(0); // Called function with wrong-type? Variable is not a ImVec2.
+}
+
+void ImGui::PopStyleVar(int count)
+{
+    ImGuiContext& g = *GImGui;
+    while (count > 0)
+    {
+        // We avoid a generic memcpy(data, &backup.Backup.., GDataTypeSize[info->Type] * info->Count), the overhead in Debug is not worth it.
+        ImGuiStyleMod& backup = g.StyleModifiers.back();
+        const ImGuiStyleVarInfo* info = GetStyleVarInfo(backup.VarIdx);
+        void* data = info->GetVarPtr(&g.Style);
+        if (info->Type == ImGuiDataType_Float && info->Count == 1)      { ((float*)data)[0] = backup.BackupFloat[0]; }
+        else if (info->Type == ImGuiDataType_Float && info->Count == 2) { ((float*)data)[0] = backup.BackupFloat[0]; ((float*)data)[1] = backup.BackupFloat[1]; }
+        g.StyleModifiers.pop_back();
+        count--;
+    }
+}
+
+const char* ImGui::GetStyleColorName(ImGuiCol idx)
+{
+    // Create switch-case from enum with regexp: ImGuiCol_{.*}, --> case ImGuiCol_\1: return "\1";
+    switch (idx)
+    {
+    case ImGuiCol_Text: return "Text";
+    case ImGuiCol_TextDisabled: return "TextDisabled";
+    case ImGuiCol_WindowBg: return "WindowBg";
+    case ImGuiCol_ChildBg: return "ChildBg";
+    case ImGuiCol_PopupBg: return "PopupBg";
+    case ImGuiCol_Border: return "Border";
+    case ImGuiCol_BorderShadow: return "BorderShadow";
+    case ImGuiCol_FrameBg: return "FrameBg";
+    case ImGuiCol_FrameBgHovered: return "FrameBgHovered";
+    case ImGuiCol_FrameBgActive: return "FrameBgActive";
+    case ImGuiCol_TitleBg: return "TitleBg";
+    case ImGuiCol_TitleBgActive: return "TitleBgActive";
+    case ImGuiCol_TitleBgCollapsed: return "TitleBgCollapsed";
+    case ImGuiCol_MenuBarBg: return "MenuBarBg";
+    case ImGuiCol_ScrollbarBg: return "ScrollbarBg";
+    case ImGuiCol_ScrollbarGrab: return "ScrollbarGrab";
+    case ImGuiCol_ScrollbarGrabHovered: return "ScrollbarGrabHovered";
+    case ImGuiCol_ScrollbarGrabActive: return "ScrollbarGrabActive";
+    case ImGuiCol_CheckMark: return "CheckMark";
+    case ImGuiCol_SliderGrab: return "SliderGrab";
+    case ImGuiCol_SliderGrabActive: return "SliderGrabActive";
+    case ImGuiCol_Button: return "Button";
+    case ImGuiCol_ButtonHovered: return "ButtonHovered";
+    case ImGuiCol_ButtonActive: return "ButtonActive";
+    case ImGuiCol_Header: return "Header";
+    case ImGuiCol_HeaderHovered: return "HeaderHovered";
+    case ImGuiCol_HeaderActive: return "HeaderActive";
+    case ImGuiCol_Separator: return "Separator";
+    case ImGuiCol_SeparatorHovered: return "SeparatorHovered";
+    case ImGuiCol_SeparatorActive: return "SeparatorActive";
+    case ImGuiCol_ResizeGrip: return "ResizeGrip";
+    case ImGuiCol_ResizeGripHovered: return "ResizeGripHovered";
+    case ImGuiCol_ResizeGripActive: return "ResizeGripActive";
+    case ImGuiCol_PlotLines: return "PlotLines";
+    case ImGuiCol_PlotLinesHovered: return "PlotLinesHovered";
+    case ImGuiCol_PlotHistogram: return "PlotHistogram";
+    case ImGuiCol_PlotHistogramHovered: return "PlotHistogramHovered";
+    case ImGuiCol_TextSelectedBg: return "TextSelectedBg";
+    case ImGuiCol_DragDropTarget: return "DragDropTarget";
+    case ImGuiCol_NavHighlight: return "NavHighlight";
+    case ImGuiCol_NavWindowingHighlight: return "NavWindowingHighlight";
+    case ImGuiCol_NavWindowingDimBg: return "NavWindowingDimBg";
+    case ImGuiCol_ModalWindowDimBg: return "ModalWindowDimBg";
+    }
+    IM_ASSERT(0);
+    return "Unknown";
+}
+
+bool ImGui::IsWindowChildOf(ImGuiWindow* window, ImGuiWindow* potential_parent)
+{
+    if (window->RootWindow == potential_parent)
+        return true;
+    while (window != NULL)
+    {
+        if (window == potential_parent)
+            return true;
+        window = window->ParentWindow;
+    }
+    return false;
+}
+
+bool ImGui::IsWindowHovered(ImGuiHoveredFlags flags)
+{
+    IM_ASSERT((flags & ImGuiHoveredFlags_AllowWhenOverlapped) == 0);   // Flags not supported by this function
+    ImGuiContext& g = *GImGui;
+
+    if (flags & ImGuiHoveredFlags_AnyWindow)
+    {
+        if (g.HoveredWindow == NULL)
+            return false;
+    }
+    else
+    {
+        switch (flags & (ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows))
+        {
+        case ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows:
+            if (g.HoveredRootWindow != g.CurrentWindow->RootWindow)
+                return false;
+            break;
+        case ImGuiHoveredFlags_RootWindow:
+            if (g.HoveredWindow != g.CurrentWindow->RootWindow)
+                return false;
+            break;
+        case ImGuiHoveredFlags_ChildWindows:
+            if (g.HoveredWindow == NULL || !IsWindowChildOf(g.HoveredWindow, g.CurrentWindow))
+                return false;
+            break;
+        default:
+            if (g.HoveredWindow != g.CurrentWindow)
+                return false;
+            break;
+        }
+    }
+
+    if (!IsWindowContentHoverable(g.HoveredRootWindow, flags))
+        return false;
+    if (!(flags & ImGuiHoveredFlags_AllowWhenBlockedByActiveItem))
+        if (g.ActiveId != 0 && !g.ActiveIdAllowOverlap && g.ActiveId != g.HoveredWindow->MoveId)
+            return false;
+    return true;
+}
+
+bool ImGui::IsWindowFocused(ImGuiFocusedFlags flags)
+{
+    ImGuiContext& g = *GImGui;
+    IM_ASSERT(g.CurrentWindow);     // Not inside a Begin()/End()
+
+    if (flags & ImGuiFocusedFlags_AnyWindow)
+        return g.NavWindow != NULL;
+
+    switch (flags & (ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_ChildWindows))
+    {
+    case ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_ChildWindows:
+        return g.NavWindow && g.NavWindow->RootWindow == g.CurrentWindow->RootWindow;
+    case ImGuiFocusedFlags_RootWindow:
+        return g.NavWindow == g.CurrentWindow->RootWindow;
+    case ImGuiFocusedFlags_ChildWindows:
+        return g.NavWindow && IsWindowChildOf(g.NavWindow, g.CurrentWindow);
+    default:
+        return g.NavWindow == g.CurrentWindow;
+    }
+}
+
+// Can we focus this window with CTRL+TAB (or PadMenu + PadFocusPrev/PadFocusNext)
+bool ImGui::IsWindowNavFocusable(ImGuiWindow* window)
+{
+    return window->Active && window == window->RootWindow && !(window->Flags & ImGuiWindowFlags_NoNavFocus);
+}
+
+float ImGui::GetWindowWidth()
+{
+    ImGuiWindow* window = GImGui->CurrentWindow;
+    return window->Size.x;
+}
+
+float ImGui::GetWindowHeight()
+{
+    ImGuiWindow* window = GImGui->CurrentWindow;
+    return window->Size.y;
+}
+
+ImVec2 ImGui::GetWindowPos()
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiWindow* window = g.CurrentWindow;
+    return window->Pos;
+}
+
+void ImGui::SetWindowScrollX(ImGuiWindow* window, float new_scroll_x)
+{
+    window->DC.CursorMaxPos.x += window->Scroll.x; // SizeContents is generally computed based on CursorMaxPos which is affected by scroll position, so we need to apply our change to it.
+    window->Scroll.x = new_scroll_x;
+    window->DC.CursorMaxPos.x -= window->Scroll.x;
+}
+
+void ImGui::SetWindowScrollY(ImGuiWindow* window, float new_scroll_y)
+{
+    window->DC.CursorMaxPos.y += window->Scroll.y; // SizeContents is generally computed based on CursorMaxPos which is affected by scroll position, so we need to apply our change to it.
+    window->Scroll.y = new_scroll_y;
+    window->DC.CursorMaxPos.y -= window->Scroll.y;
+}
+
+static void SetWindowPos(ImGuiWindow* window, const ImVec2& pos, ImGuiCond cond)
+{
+    // Test condition (NB: bit 0 is always true) and clear flags for next time
+    if (cond && (window->SetWindowPosAllowFlags & cond) == 0)
+        return;
+
+    IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags.
+    window->SetWindowPosAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing);
+    window->SetWindowPosVal = ImVec2(FLT_MAX, FLT_MAX);
+
+    // Set
+    const ImVec2 old_pos = window->Pos;
+    window->Pos = ImFloor(pos);
+    window->DC.CursorPos += (window->Pos - old_pos);    // As we happen to move the window while it is being appended to (which is a bad idea - will smear) let's at least offset the cursor
+    window->DC.CursorMaxPos += (window->Pos - old_pos); // And more importantly we need to adjust this so size calculation doesn't get affected.
+}
+
+void ImGui::SetWindowPos(const ImVec2& pos, ImGuiCond cond)
+{
+    ImGuiWindow* window = GetCurrentWindowRead();
+    SetWindowPos(window, pos, cond);
+}
+
+void ImGui::SetWindowPos(const char* name, const ImVec2& pos, ImGuiCond cond)
+{
+    if (ImGuiWindow* window = FindWindowByName(name))
+        SetWindowPos(window, pos, cond);
+}
+
+ImVec2 ImGui::GetWindowSize()
+{
+    ImGuiWindow* window = GetCurrentWindowRead();
+    return window->Size;
+}
+
+static void SetWindowSize(ImGuiWindow* window, const ImVec2& size, ImGuiCond cond)
+{
+    // Test condition (NB: bit 0 is always true) and clear flags for next time
+    if (cond && (window->SetWindowSizeAllowFlags & cond) == 0)
+        return;
+
+    IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags.
+    window->SetWindowSizeAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing);
+
+    // Set
+    if (size.x > 0.0f)
+    {
+        window->AutoFitFramesX = 0;
+        window->SizeFull.x = size.x;
+    }
+    else
+    {
+        window->AutoFitFramesX = 2;
+        window->AutoFitOnlyGrows = false;
+    }
+    if (size.y > 0.0f)
+    {
+        window->AutoFitFramesY = 0;
+        window->SizeFull.y = size.y;
+    }
+    else
+    {
+        window->AutoFitFramesY = 2;
+        window->AutoFitOnlyGrows = false;
+    }
+}
+
+void ImGui::SetWindowSize(const ImVec2& size, ImGuiCond cond)
+{
+    SetWindowSize(GImGui->CurrentWindow, size, cond);
+}
+
+void ImGui::SetWindowSize(const char* name, const ImVec2& size, ImGuiCond cond)
+{
+    if (ImGuiWindow* window = FindWindowByName(name))
+        SetWindowSize(window, size, cond);
+}
+
+static void SetWindowCollapsed(ImGuiWindow* window, bool collapsed, ImGuiCond cond)
+{
+    // Test condition (NB: bit 0 is always true) and clear flags for next time
+    if (cond && (window->SetWindowCollapsedAllowFlags & cond) == 0)
+        return;
+    window->SetWindowCollapsedAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing);
+
+    // Set
+    window->Collapsed = collapsed;
+}
+
+void ImGui::SetWindowCollapsed(bool collapsed, ImGuiCond cond)
+{
+    SetWindowCollapsed(GImGui->CurrentWindow, collapsed, cond);
+}
+
+bool ImGui::IsWindowCollapsed()
+{
+    ImGuiWindow* window = GetCurrentWindowRead();
+    return window->Collapsed;
+}
+
+bool ImGui::IsWindowAppearing()
+{
+    ImGuiWindow* window = GetCurrentWindowRead();
+    return window->Appearing;
+}
+
+void ImGui::SetWindowCollapsed(const char* name, bool collapsed, ImGuiCond cond)
+{
+    if (ImGuiWindow* window = FindWindowByName(name))
+        SetWindowCollapsed(window, collapsed, cond);
+}
+
+void ImGui::SetWindowFocus()
+{
+    FocusWindow(GImGui->CurrentWindow);
+}
+
+void ImGui::SetWindowFocus(const char* name)
+{
+    if (name)
+    {
+        if (ImGuiWindow* window = FindWindowByName(name))
+            FocusWindow(window);
+    }
+    else
+    {
+        FocusWindow(NULL);
+    }
+}
+
+void ImGui::SetNextWindowPos(const ImVec2& pos, ImGuiCond cond, const ImVec2& pivot)
+{
+    ImGuiContext& g = *GImGui;
+    IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags.
+    g.NextWindowData.PosVal = pos;
+    g.NextWindowData.PosPivotVal = pivot;
+    g.NextWindowData.PosCond = cond ? cond : ImGuiCond_Always;
+}
+
+void ImGui::SetNextWindowSize(const ImVec2& size, ImGuiCond cond)
+{
+    ImGuiContext& g = *GImGui;
+    IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags.
+    g.NextWindowData.SizeVal = size;
+    g.NextWindowData.SizeCond = cond ? cond : ImGuiCond_Always;
+}
+
+void ImGui::SetNextWindowSizeConstraints(const ImVec2& size_min, const ImVec2& size_max, ImGuiSizeCallback custom_callback, void* custom_callback_user_data)
+{
+    ImGuiContext& g = *GImGui;
+    g.NextWindowData.SizeConstraintCond = ImGuiCond_Always;
+    g.NextWindowData.SizeConstraintRect = ImRect(size_min, size_max);
+    g.NextWindowData.SizeCallback = custom_callback;
+    g.NextWindowData.SizeCallbackUserData = custom_callback_user_data;
+}
+
+void ImGui::SetNextWindowContentSize(const ImVec2& size)
+{
+    ImGuiContext& g = *GImGui;
+    g.NextWindowData.ContentSizeVal = size;  // In Begin() we will add the size of window decorations (title bar, menu etc.) to that to form a SizeContents value.
+    g.NextWindowData.ContentSizeCond = ImGuiCond_Always;
+}
+
+void ImGui::SetNextWindowCollapsed(bool collapsed, ImGuiCond cond)
+{
+    ImGuiContext& g = *GImGui;
+    IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags.
+    g.NextWindowData.CollapsedVal = collapsed;
+    g.NextWindowData.CollapsedCond = cond ? cond : ImGuiCond_Always;
+}
+
+void ImGui::SetNextWindowFocus()
+{
+    ImGuiContext& g = *GImGui;
+    g.NextWindowData.FocusCond = ImGuiCond_Always;   // Using a Cond member for consistency (may transition all of them to single flag set for fast Clear() op)
+}
+
+void ImGui::SetNextWindowBgAlpha(float alpha)
+{
+    ImGuiContext& g = *GImGui;
+    g.NextWindowData.BgAlphaVal = alpha;
+    g.NextWindowData.BgAlphaCond = ImGuiCond_Always; // Using a Cond member for consistency (may transition all of them to single flag set for fast Clear() op)
+}
+
+// In window space (not screen space!)
+ImVec2 ImGui::GetContentRegionMax()
+{
+    ImGuiWindow* window = GetCurrentWindowRead();
+    ImVec2 mx = window->ContentsRegionRect.Max - window->Pos;
+    if (window->DC.ColumnsSet)
+        mx.x = GetColumnOffset(window->DC.ColumnsSet->Current + 1) - window->WindowPadding.x;
+    return mx;
+}
+
+ImVec2 ImGui::GetContentRegionAvail()
+{
+    ImGuiWindow* window = GetCurrentWindowRead();
+    return GetContentRegionMax() - (window->DC.CursorPos - window->Pos);
+}
+
+float ImGui::GetContentRegionAvailWidth()
+{
+    return GetContentRegionAvail().x;
+}
+
+// In window space (not screen space!)
+ImVec2 ImGui::GetWindowContentRegionMin()
+{
+    ImGuiWindow* window = GetCurrentWindowRead();
+    return window->ContentsRegionRect.Min - window->Pos;
+}
+
+ImVec2 ImGui::GetWindowContentRegionMax()
+{
+    ImGuiWindow* window = GetCurrentWindowRead();
+    return window->ContentsRegionRect.Max - window->Pos;
+}
+
+float ImGui::GetWindowContentRegionWidth()
+{
+    ImGuiWindow* window = GetCurrentWindowRead();
+    return window->ContentsRegionRect.GetWidth();
+}
+
+float ImGui::GetTextLineHeight()
+{
+    ImGuiContext& g = *GImGui;
+    return g.FontSize;
+}
+
+float ImGui::GetTextLineHeightWithSpacing()
+{
+    ImGuiContext& g = *GImGui;
+    return g.FontSize + g.Style.ItemSpacing.y;
+}
+
+float ImGui::GetFrameHeight()
+{
+    ImGuiContext& g = *GImGui;
+    return g.FontSize + g.Style.FramePadding.y * 2.0f;
+}
+
+float ImGui::GetFrameHeightWithSpacing()
+{
+    ImGuiContext& g = *GImGui;
+    return g.FontSize + g.Style.FramePadding.y * 2.0f + g.Style.ItemSpacing.y;
+}
+
+ImDrawList* ImGui::GetWindowDrawList()
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    return window->DrawList;
+}
+
+ImFont* ImGui::GetFont()
+{
+    return GImGui->Font;
+}
+
+float ImGui::GetFontSize()
+{
+    return GImGui->FontSize;
+}
+
+ImVec2 ImGui::GetFontTexUvWhitePixel()
+{
+    return GImGui->DrawListSharedData.TexUvWhitePixel;
+}
+
+void ImGui::SetWindowFontScale(float scale)
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiWindow* window = GetCurrentWindow();
+    window->FontWindowScale = scale;
+    g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize();
+}
+
+// User generally sees positions in window coordinates. Internally we store CursorPos in absolute screen coordinates because it is more convenient.
+// Conversion happens as we pass the value to user, but it makes our naming convention confusing because GetCursorPos() == (DC.CursorPos - window.Pos). May want to rename 'DC.CursorPos'.
+ImVec2 ImGui::GetCursorPos()
+{
+    ImGuiWindow* window = GetCurrentWindowRead();
+    return window->DC.CursorPos - window->Pos + window->Scroll;
+}
+
+float ImGui::GetCursorPosX()
+{
+    ImGuiWindow* window = GetCurrentWindowRead();
+    return window->DC.CursorPos.x - window->Pos.x + window->Scroll.x;
+}
+
+float ImGui::GetCursorPosY()
+{
+    ImGuiWindow* window = GetCurrentWindowRead();
+    return window->DC.CursorPos.y - window->Pos.y + window->Scroll.y;
+}
+
+void ImGui::SetCursorPos(const ImVec2& local_pos)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    window->DC.CursorPos = window->Pos - window->Scroll + local_pos;
+    window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos);
+}
+
+void ImGui::SetCursorPosX(float x)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    window->DC.CursorPos.x = window->Pos.x - window->Scroll.x + x;
+    window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, window->DC.CursorPos.x);
+}
+
+void ImGui::SetCursorPosY(float y)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    window->DC.CursorPos.y = window->Pos.y - window->Scroll.y + y;
+    window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, window->DC.CursorPos.y);
+}
+
+ImVec2 ImGui::GetCursorStartPos()
+{
+    ImGuiWindow* window = GetCurrentWindowRead();
+    return window->DC.CursorStartPos - window->Pos;
+}
+
+ImVec2 ImGui::GetCursorScreenPos()
+{
+    ImGuiWindow* window = GetCurrentWindowRead();
+    return window->DC.CursorPos;
+}
+
+void ImGui::SetCursorScreenPos(const ImVec2& screen_pos)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    window->DC.CursorPos = screen_pos;
+    window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos);
+}
+
+float ImGui::GetScrollX()
+{
+    return GImGui->CurrentWindow->Scroll.x;
+}
+
+float ImGui::GetScrollY()
+{
+    return GImGui->CurrentWindow->Scroll.y;
+}
+
+float ImGui::GetScrollMaxX()
+{
+    return GetScrollMaxX(GImGui->CurrentWindow);
+}
+
+float ImGui::GetScrollMaxY()
+{
+    return GetScrollMaxY(GImGui->CurrentWindow);
+}
+
+void ImGui::SetScrollX(float scroll_x)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    window->ScrollTarget.x = scroll_x;
+    window->ScrollTargetCenterRatio.x = 0.0f;
+}
+
+void ImGui::SetScrollY(float scroll_y)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    window->ScrollTarget.y = scroll_y + window->TitleBarHeight() + window->MenuBarHeight(); // title bar height canceled out when using ScrollTargetRelY
+    window->ScrollTargetCenterRatio.y = 0.0f;
+}
+
+void ImGui::SetScrollFromPosY(float pos_y, float center_y_ratio)
+{
+    // We store a target position so centering can occur on the next frame when we are guaranteed to have a known window size
+    ImGuiWindow* window = GetCurrentWindow();
+    IM_ASSERT(center_y_ratio >= 0.0f && center_y_ratio <= 1.0f);
+    window->ScrollTarget.y = (float)(int)(pos_y + window->Scroll.y);
+    window->ScrollTargetCenterRatio.y = center_y_ratio;
+}
+
+// center_y_ratio: 0.0f top of last item, 0.5f vertical center of last item, 1.0f bottom of last item.
+void ImGui::SetScrollHere(float center_y_ratio)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    float target_y = window->DC.CursorPosPrevLine.y - window->Pos.y; // Top of last item, in window space
+    target_y += (window->DC.PrevLineSize.y * center_y_ratio) + (GImGui->Style.ItemSpacing.y * (center_y_ratio - 0.5f) * 2.0f); // Precisely aim above, in the middle or below the last line.
+    SetScrollFromPosY(target_y, center_y_ratio);
+}
+
+void ImGui::ActivateItem(ImGuiID id)
+{
+    ImGuiContext& g = *GImGui;
+    g.NavNextActivateId = id;
+}
+
+void ImGui::SetKeyboardFocusHere(int offset)
+{
+    IM_ASSERT(offset >= -1);    // -1 is allowed but not below
+    ImGuiWindow* window = GetCurrentWindow();
+    window->FocusIdxAllRequestNext = window->FocusIdxAllCounter + 1 + offset;
+    window->FocusIdxTabRequestNext = INT_MAX;
+}
+
+void ImGui::SetItemDefaultFocus()
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiWindow* window = g.CurrentWindow;
+    if (!window->Appearing)
+        return;
+    if (g.NavWindow == window->RootWindowForNav && (g.NavInitRequest || g.NavInitResultId != 0) && g.NavLayer == g.NavWindow->DC.NavLayerCurrent)
+    {
+        g.NavInitRequest = false;
+        g.NavInitResultId = g.NavWindow->DC.LastItemId;
+        g.NavInitResultRectRel = ImRect(g.NavWindow->DC.LastItemRect.Min - g.NavWindow->Pos, g.NavWindow->DC.LastItemRect.Max - g.NavWindow->Pos);
+        NavUpdateAnyRequestFlag();
+        if (!IsItemVisible())
+            SetScrollHere();
+    }
+}
+
+void ImGui::SetStateStorage(ImGuiStorage* tree)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    window->DC.StateStorage = tree ? tree : &window->StateStorage;
+}
+
+ImGuiStorage* ImGui::GetStateStorage()
+{
+    ImGuiWindow* window = GetCurrentWindowRead();
+    return window->DC.StateStorage;
+}
+
+void ImGui::PushID(const char* str_id)
+{
+    ImGuiWindow* window = GetCurrentWindowRead();
+    window->IDStack.push_back(window->GetIDNoKeepAlive(str_id));
+}
+
+void ImGui::PushID(const char* str_id_begin, const char* str_id_end)
+{
+    ImGuiWindow* window = GetCurrentWindowRead();
+    window->IDStack.push_back(window->GetIDNoKeepAlive(str_id_begin, str_id_end));
+}
+
+void ImGui::PushID(const void* ptr_id)
+{
+    ImGuiWindow* window = GetCurrentWindowRead();
+    window->IDStack.push_back(window->GetIDNoKeepAlive(ptr_id));
+}
+
+void ImGui::PushID(int int_id)
+{
+    const void* ptr_id = (void*)(intptr_t)int_id;
+    ImGuiWindow* window = GetCurrentWindowRead();
+    window->IDStack.push_back(window->GetIDNoKeepAlive(ptr_id));
+}
+
+void ImGui::PopID()
+{
+    ImGuiWindow* window = GetCurrentWindowRead();
+    window->IDStack.pop_back();
+}
+
+ImGuiID ImGui::GetID(const char* str_id)
+{
+    return GImGui->CurrentWindow->GetID(str_id);
+}
+
+ImGuiID ImGui::GetID(const char* str_id_begin, const char* str_id_end)
+{
+    return GImGui->CurrentWindow->GetID(str_id_begin, str_id_end);
+}
+
+ImGuiID ImGui::GetID(const void* ptr_id)
+{
+    return GImGui->CurrentWindow->GetID(ptr_id);
+}
+
+bool ImGui::IsRectVisible(const ImVec2& size)
+{
+    ImGuiWindow* window = GetCurrentWindowRead();
+    return window->ClipRect.Overlaps(ImRect(window->DC.CursorPos, window->DC.CursorPos + size));
+}
+
+bool ImGui::IsRectVisible(const ImVec2& rect_min, const ImVec2& rect_max)
+{
+    ImGuiWindow* window = GetCurrentWindowRead();
+    return window->ClipRect.Overlaps(ImRect(rect_min, rect_max));
+}
+
+// Lock horizontal starting position + capture group bounding box into one "item" (so you can use IsItemHovered() or layout primitives such as SameLine() on whole group, etc.)
+void ImGui::BeginGroup()
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiWindow* window = GetCurrentWindow();
+
+    window->DC.GroupStack.resize(window->DC.GroupStack.Size + 1);
+    ImGuiGroupData& group_data = window->DC.GroupStack.back();
+    group_data.BackupCursorPos = window->DC.CursorPos;
+    group_data.BackupCursorMaxPos = window->DC.CursorMaxPos;
+    group_data.BackupIndent = window->DC.Indent;
+    group_data.BackupGroupOffset = window->DC.GroupOffset;
+    group_data.BackupCurrentLineSize = window->DC.CurrentLineSize;
+    group_data.BackupCurrentLineTextBaseOffset = window->DC.CurrentLineTextBaseOffset;
+    group_data.BackupLogLinePosY = window->DC.LogLinePosY;
+    group_data.BackupActiveIdIsAlive = g.ActiveIdIsAlive;
+    group_data.BackupActiveIdPreviousFrameIsAlive = g.ActiveIdPreviousFrameIsAlive;
+    group_data.AdvanceCursor = true;
+
+    window->DC.GroupOffset.x = window->DC.CursorPos.x - window->Pos.x - window->DC.ColumnsOffset.x;
+    window->DC.Indent = window->DC.GroupOffset;
+    window->DC.CursorMaxPos = window->DC.CursorPos;
+    window->DC.CurrentLineSize = ImVec2(0.0f, 0.0f);
+    window->DC.LogLinePosY = window->DC.CursorPos.y - 9999.0f; // To enforce Log carriage return
+}
+
+void ImGui::EndGroup()
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiWindow* window = GetCurrentWindow();
+    IM_ASSERT(!window->DC.GroupStack.empty());    // Mismatched BeginGroup()/EndGroup() calls
+
+    ImGuiGroupData& group_data = window->DC.GroupStack.back();
+
+    ImRect group_bb(group_data.BackupCursorPos, window->DC.CursorMaxPos);
+    group_bb.Max = ImMax(group_bb.Min, group_bb.Max);
+
+    window->DC.CursorPos = group_data.BackupCursorPos;
+    window->DC.CursorMaxPos = ImMax(group_data.BackupCursorMaxPos, window->DC.CursorMaxPos);
+    window->DC.Indent = group_data.BackupIndent;
+    window->DC.GroupOffset = group_data.BackupGroupOffset;
+    window->DC.CurrentLineSize = group_data.BackupCurrentLineSize;
+    window->DC.CurrentLineTextBaseOffset = group_data.BackupCurrentLineTextBaseOffset;
+    window->DC.LogLinePosY = window->DC.CursorPos.y - 9999.0f; // To enforce Log carriage return
+
+    if (group_data.AdvanceCursor)
+    {
+        window->DC.CurrentLineTextBaseOffset = ImMax(window->DC.PrevLineTextBaseOffset, group_data.BackupCurrentLineTextBaseOffset);      // FIXME: Incorrect, we should grab the base offset from the *first line* of the group but it is hard to obtain now.
+        ItemSize(group_bb.GetSize(), group_data.BackupCurrentLineTextBaseOffset);
+        ItemAdd(group_bb, 0);
+    }
+
+    // If the current ActiveId was declared within the boundary of our group, we copy it to LastItemId so IsItemActive(), IsItemDeactivated() etc. will be functional on the entire group.
+    // It would be be neater if we replaced window.DC.LastItemId by e.g. 'bool LastItemIsActive', but would put a little more burden on individual widgets.
+    // (and if you grep for LastItemId you'll notice it is only used in that context.
+    if ((group_data.BackupActiveIdIsAlive != g.ActiveId) && (g.ActiveIdIsAlive == g.ActiveId) && g.ActiveId) // && g.ActiveIdWindow->RootWindow == window->RootWindow)
+        window->DC.LastItemId = g.ActiveId;
+    else if (!group_data.BackupActiveIdPreviousFrameIsAlive && g.ActiveIdPreviousFrameIsAlive) // && g.ActiveIdPreviousFrameWindow->RootWindow == window->RootWindow)
+        window->DC.LastItemId = g.ActiveIdPreviousFrame;
+    window->DC.LastItemRect = group_bb;
+
+    window->DC.GroupStack.pop_back();
+
+    //window->DrawList->AddRect(group_bb.Min, group_bb.Max, IM_COL32(255,0,255,255));   // [Debug]
+}
+
+// Gets back to previous line and continue with horizontal layout
+//      pos_x == 0      : follow right after previous item
+//      pos_x != 0      : align to specified x position (relative to window/group left)
+//      spacing_w < 0   : use default spacing if pos_x == 0, no spacing if pos_x != 0
+//      spacing_w >= 0  : enforce spacing amount
+void ImGui::SameLine(float pos_x, float spacing_w)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    if (window->SkipItems)
+        return;
+
+    ImGuiContext& g = *GImGui;
+    if (pos_x != 0.0f)
+    {
+        if (spacing_w < 0.0f) spacing_w = 0.0f;
+        window->DC.CursorPos.x = window->Pos.x - window->Scroll.x + pos_x + spacing_w + window->DC.GroupOffset.x + window->DC.ColumnsOffset.x;
+        window->DC.CursorPos.y = window->DC.CursorPosPrevLine.y;
+    }
+    else
+    {
+        if (spacing_w < 0.0f) spacing_w = g.Style.ItemSpacing.x;
+        window->DC.CursorPos.x = window->DC.CursorPosPrevLine.x + spacing_w;
+        window->DC.CursorPos.y = window->DC.CursorPosPrevLine.y;
+    }
+    window->DC.CurrentLineSize = window->DC.PrevLineSize;
+    window->DC.CurrentLineTextBaseOffset = window->DC.PrevLineTextBaseOffset;
+}
+
+void ImGui::Indent(float indent_w)
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiWindow* window = GetCurrentWindow();
+    window->DC.Indent.x += (indent_w != 0.0f) ? indent_w : g.Style.IndentSpacing;
+    window->DC.CursorPos.x = window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x;
+}
+
+void ImGui::Unindent(float indent_w)
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiWindow* window = GetCurrentWindow();
+    window->DC.Indent.x -= (indent_w != 0.0f) ? indent_w : g.Style.IndentSpacing;
+    window->DC.CursorPos.x = window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x;
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] TOOLTIPS
+//-----------------------------------------------------------------------------
+
+void ImGui::BeginTooltip()
+{
+    ImGuiContext& g = *GImGui;
+    if (g.DragDropWithinSourceOrTarget)
+    {
+        // The default tooltip position is a little offset to give space to see the context menu (it's also clamped within the current viewport/monitor)
+        // In the context of a dragging tooltip we try to reduce that offset and we enforce following the cursor.
+        // Whatever we do we want to call SetNextWindowPos() to enforce a tooltip position and disable clipping the tooltip without our display area, like regular tooltip do.
+        //ImVec2 tooltip_pos = g.IO.MousePos - g.ActiveIdClickOffset - g.Style.WindowPadding;
+        ImVec2 tooltip_pos = g.IO.MousePos + ImVec2(16 * g.Style.MouseCursorScale, 8 * g.Style.MouseCursorScale);
+        SetNextWindowPos(tooltip_pos);
+        SetNextWindowBgAlpha(g.Style.Colors[ImGuiCol_PopupBg].w * 0.60f);
+        //PushStyleVar(ImGuiStyleVar_Alpha, g.Style.Alpha * 0.60f); // This would be nice but e.g ColorButton with checkboard has issue with transparent colors :(
+        BeginTooltipEx(0, true);
+    }
+    else
+    {
+        BeginTooltipEx(0, false);
+    }
+}
+
+// Not exposed publicly as BeginTooltip() because bool parameters are evil. Let's see if other needs arise first.
+void ImGui::BeginTooltipEx(ImGuiWindowFlags extra_flags, bool override_previous_tooltip)
+{
+    ImGuiContext& g = *GImGui;
+    char window_name[16];
+    ImFormatString(window_name, IM_ARRAYSIZE(window_name), "##Tooltip_%02d", g.TooltipOverrideCount);
+    if (override_previous_tooltip)
+        if (ImGuiWindow* window = FindWindowByName(window_name))
+            if (window->Active)
+            {
+                // Hide previous tooltip from being displayed. We can't easily "reset" the content of a window so we create a new one.
+                window->Hidden = true;
+                window->HiddenFramesRegular = 1;
+                ImFormatString(window_name, IM_ARRAYSIZE(window_name), "##Tooltip_%02d", ++g.TooltipOverrideCount);
+            }
+    ImGuiWindowFlags flags = ImGuiWindowFlags_Tooltip|ImGuiWindowFlags_NoInputs|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoNav;
+    Begin(window_name, NULL, flags | extra_flags);
+}
+
+void ImGui::EndTooltip()
+{
+    IM_ASSERT(GetCurrentWindowRead()->Flags & ImGuiWindowFlags_Tooltip);   // Mismatched BeginTooltip()/EndTooltip() calls
+    End();
+}
+
+void ImGui::SetTooltipV(const char* fmt, va_list args)
+{
+    ImGuiContext& g = *GImGui;
+    if (g.DragDropWithinSourceOrTarget)
+        BeginTooltip();
+    else
+        BeginTooltipEx(0, true);
+    TextV(fmt, args);
+    EndTooltip();
+}
+
+void ImGui::SetTooltip(const char* fmt, ...)
+{
+    va_list args;
+    va_start(args, fmt);
+    SetTooltipV(fmt, args);
+    va_end(args);
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] POPUPS
+//-----------------------------------------------------------------------------
+
+bool ImGui::IsPopupOpen(ImGuiID id)
+{
+    ImGuiContext& g = *GImGui;
+    return g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].PopupId == id;
+}
+
+bool ImGui::IsPopupOpen(const char* str_id)
+{
+    ImGuiContext& g = *GImGui;
+    return g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].PopupId == g.CurrentWindow->GetID(str_id);
+}
+
+ImGuiWindow* ImGui::GetFrontMostPopupModal()
+{
+    ImGuiContext& g = *GImGui;
+    for (int n = g.OpenPopupStack.Size-1; n >= 0; n--)
+        if (ImGuiWindow* popup = g.OpenPopupStack.Data[n].Window)
+            if (popup->Flags & ImGuiWindowFlags_Modal)
+                return popup;
+    return NULL;
+}
+
+void ImGui::OpenPopup(const char* str_id)
+{
+    ImGuiContext& g = *GImGui;
+    OpenPopupEx(g.CurrentWindow->GetID(str_id));
+}
+
+// Mark popup as open (toggle toward open state).
+// Popups are closed when user click outside, or activate a pressable item, or CloseCurrentPopup() is called within a BeginPopup()/EndPopup() block.
+// Popup identifiers are relative to the current ID-stack (so OpenPopup and BeginPopup needs to be at the same level).
+// One open popup per level of the popup hierarchy (NB: when assigning we reset the Window member of ImGuiPopupRef to NULL)
+void ImGui::OpenPopupEx(ImGuiID id)
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiWindow* parent_window = g.CurrentWindow;
+    int current_stack_size = g.CurrentPopupStack.Size;
+    ImGuiPopupRef popup_ref; // Tagged as new ref as Window will be set back to NULL if we write this into OpenPopupStack.
+    popup_ref.PopupId = id;
+    popup_ref.Window = NULL;
+    popup_ref.ParentWindow = parent_window;
+    popup_ref.OpenFrameCount = g.FrameCount;
+    popup_ref.OpenParentId = parent_window->IDStack.back();
+    popup_ref.OpenMousePos = g.IO.MousePos;
+    popup_ref.OpenPopupPos = NavCalcPreferredRefPos();
+
+    //printf("[%05d] OpenPopupEx(0x%08X)\n", g.FrameCount, id);
+    if (g.OpenPopupStack.Size < current_stack_size + 1)
+    {
+        g.OpenPopupStack.push_back(popup_ref);
+    }
+    else
+    {
+        // Gently handle the user mistakenly calling OpenPopup() every frame. It is a programming mistake! However, if we were to run the regular code path, the ui
+        // would become completely unusable because the popup will always be in hidden-while-calculating-size state _while_ claiming focus. Which would be a very confusing
+        // situation for the programmer. Instead, we silently allow the popup to proceed, it will keep reappearing and the programming error will be more obvious to understand.
+        if (g.OpenPopupStack[current_stack_size].PopupId == id && g.OpenPopupStack[current_stack_size].OpenFrameCount == g.FrameCount - 1)
+        {
+            g.OpenPopupStack[current_stack_size].OpenFrameCount = popup_ref.OpenFrameCount;
+        }
+        else
+        {
+            // Close child popups if any, then flag popup for open/reopen
+            g.OpenPopupStack.resize(current_stack_size + 1);
+            g.OpenPopupStack[current_stack_size] = popup_ref;
+        }
+
+        // When reopening a popup we first refocus its parent, otherwise if its parent is itself a popup it would get closed by ClosePopupsOverWindow().
+        // This is equivalent to what ClosePopupToLevel() does.
+        //if (g.OpenPopupStack[current_stack_size].PopupId == id)
+        //    FocusWindow(parent_window);
+    }
+}
+
+bool ImGui::OpenPopupOnItemClick(const char* str_id, int mouse_button)
+{
+    ImGuiWindow* window = GImGui->CurrentWindow;
+    if (IsMouseReleased(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup))
+    {
+        ImGuiID id = str_id ? window->GetID(str_id) : window->DC.LastItemId; // If user hasn't passed an ID, we can use the LastItemID. Using LastItemID as a Popup ID won't conflict!
+        IM_ASSERT(id != 0);                                                  // You cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item)
+        OpenPopupEx(id);
+        return true;
+    }
+    return false;
+}
+
+void ImGui::ClosePopupsOverWindow(ImGuiWindow* ref_window)
+{
+    ImGuiContext& g = *GImGui;
+    if (g.OpenPopupStack.empty())
+        return;
+
+    // When popups are stacked, clicking on a lower level popups puts focus back to it and close popups above it.
+    // Don't close our own child popup windows.
+    int n = 0;
+    if (ref_window)
+    {
+        for (n = 0; n < g.OpenPopupStack.Size; n++)
+        {
+            ImGuiPopupRef& popup = g.OpenPopupStack[n];
+            if (!popup.Window)
+                continue;
+            IM_ASSERT((popup.Window->Flags & ImGuiWindowFlags_Popup) != 0);
+            if (popup.Window->Flags & ImGuiWindowFlags_ChildWindow)
+                continue;
+
+            // Trim the stack if popups are not direct descendant of the reference window (which is often the NavWindow)
+            bool has_focus = false;
+            for (int m = n; m < g.OpenPopupStack.Size && !has_focus; m++)
+                has_focus = (g.OpenPopupStack[m].Window && g.OpenPopupStack[m].Window->RootWindow == ref_window->RootWindow);
+            if (!has_focus)
+                break;
+        }
+    }
+    if (n < g.OpenPopupStack.Size) // This test is not required but it allows to set a convenient breakpoint on the block below
+        ClosePopupToLevel(n);
+}
+
+void ImGui::ClosePopupToLevel(int remaining)
+{
+    IM_ASSERT(remaining >= 0);
+    ImGuiContext& g = *GImGui;
+    ImGuiWindow* focus_window = (remaining > 0) ? g.OpenPopupStack[remaining-1].Window : g.OpenPopupStack[0].ParentWindow;
+    if (g.NavLayer == 0)
+        focus_window = NavRestoreLastChildNavWindow(focus_window);
+    FocusWindow(focus_window);
+    focus_window->DC.NavHideHighlightOneFrame = true;
+    g.OpenPopupStack.resize(remaining);
+}
+
+void ImGui::ClosePopup(ImGuiID id)
+{
+    if (!IsPopupOpen(id))
+        return;
+    ImGuiContext& g = *GImGui;
+    ClosePopupToLevel(g.OpenPopupStack.Size - 1);
+}
+
+// Close the popup we have begin-ed into.
+void ImGui::CloseCurrentPopup()
+{
+    ImGuiContext& g = *GImGui;
+    int popup_idx = g.CurrentPopupStack.Size - 1;
+    if (popup_idx < 0 || popup_idx >= g.OpenPopupStack.Size || g.CurrentPopupStack[popup_idx].PopupId != g.OpenPopupStack[popup_idx].PopupId)
+        return;
+    while (popup_idx > 0 && g.OpenPopupStack[popup_idx].Window && (g.OpenPopupStack[popup_idx].Window->Flags & ImGuiWindowFlags_ChildMenu))
+        popup_idx--;
+    ClosePopupToLevel(popup_idx);
+}
+
+bool ImGui::BeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_flags)
+{
+    ImGuiContext& g = *GImGui;
+    if (!IsPopupOpen(id))
+    {
+        g.NextWindowData.Clear(); // We behave like Begin() and need to consume those values
+        return false;
+    }
+
+    char name[20];
+    if (extra_flags & ImGuiWindowFlags_ChildMenu)
+        ImFormatString(name, IM_ARRAYSIZE(name), "##Menu_%02d", g.CurrentPopupStack.Size);    // Recycle windows based on depth
+    else
+        ImFormatString(name, IM_ARRAYSIZE(name), "##Popup_%08x", id); // Not recycling, so we can close/open during the same frame
+
+    bool is_open = Begin(name, NULL, extra_flags | ImGuiWindowFlags_Popup);
+    if (!is_open) // NB: Begin can return false when the popup is completely clipped (e.g. zero size display)
+        EndPopup();
+
+    return is_open;
+}
+
+bool ImGui::BeginPopup(const char* str_id, ImGuiWindowFlags flags)
+{
+    ImGuiContext& g = *GImGui;
+    if (g.OpenPopupStack.Size <= g.CurrentPopupStack.Size) // Early out for performance
+    {
+        g.NextWindowData.Clear(); // We behave like Begin() and need to consume those values
+        return false;
+    }
+    return BeginPopupEx(g.CurrentWindow->GetID(str_id), flags|ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings);
+}
+
+bool ImGui::BeginPopupModal(const char* name, bool* p_open, ImGuiWindowFlags flags)
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiWindow* window = g.CurrentWindow;
+    const ImGuiID id = window->GetID(name);
+    if (!IsPopupOpen(id))
+    {
+        g.NextWindowData.Clear(); // We behave like Begin() and need to consume those values
+        return false;
+    }
+
+    // Center modal windows by default
+    // FIXME: Should test for (PosCond & window->SetWindowPosAllowFlags) with the upcoming window.
+    if (g.NextWindowData.PosCond == 0)
+        SetNextWindowPos(g.IO.DisplaySize * 0.5f, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
+
+    bool is_open = Begin(name, p_open, flags | ImGuiWindowFlags_Popup | ImGuiWindowFlags_Modal | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoSavedSettings);
+    if (!is_open || (p_open && !*p_open)) // NB: is_open can be 'false' when the popup is completely clipped (e.g. zero size display)
+    {
+        EndPopup();
+        if (is_open)
+            ClosePopup(id);
+        return false;
+    }
+    return is_open;
+}
+
+void ImGui::EndPopup()
+{
+    ImGuiContext& g = *GImGui; (void)g;
+    IM_ASSERT(g.CurrentWindow->Flags & ImGuiWindowFlags_Popup);  // Mismatched BeginPopup()/EndPopup() calls
+    IM_ASSERT(g.CurrentPopupStack.Size > 0);
+
+    // Make all menus and popups wrap around for now, may need to expose that policy.
+    NavMoveRequestTryWrapping(g.CurrentWindow, ImGuiNavMoveFlags_LoopY);
+
+    End();
+}
+
+// This is a helper to handle the simplest case of associating one named popup to one given widget.
+// You may want to handle this on user side if you have specific needs (e.g. tweaking IsItemHovered() parameters).
+// You can pass a NULL str_id to use the identifier of the last item.
+bool ImGui::BeginPopupContextItem(const char* str_id, int mouse_button)
+{
+    ImGuiWindow* window = GImGui->CurrentWindow;
+    ImGuiID id = str_id ? window->GetID(str_id) : window->DC.LastItemId; // If user hasn't passed an ID, we can use the LastItemID. Using LastItemID as a Popup ID won't conflict!
+    IM_ASSERT(id != 0);                                                  // You cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item)
+    if (IsMouseReleased(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup))
+        OpenPopupEx(id);
+    return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings);
+}
+
+bool ImGui::BeginPopupContextWindow(const char* str_id, int mouse_button, bool also_over_items)
+{
+    if (!str_id)
+        str_id = "window_context";
+    ImGuiID id = GImGui->CurrentWindow->GetID(str_id);
+    if (IsMouseReleased(mouse_button) && IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup))
+        if (also_over_items || !IsAnyItemHovered())
+            OpenPopupEx(id);
+    return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings);
+}
+
+bool ImGui::BeginPopupContextVoid(const char* str_id, int mouse_button)
+{
+    if (!str_id)
+        str_id = "void_context";
+    ImGuiID id = GImGui->CurrentWindow->GetID(str_id);
+    if (IsMouseReleased(mouse_button) && !IsWindowHovered(ImGuiHoveredFlags_AnyWindow))
+        OpenPopupEx(id);
+    return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings);
+}
+
+ImRect ImGui::GetWindowAllowedExtentRect(ImGuiWindow*)
+{
+    ImVec2 padding = GImGui->Style.DisplaySafeAreaPadding;
+    ImRect r_screen = GetViewportRect();
+    r_screen.Expand(ImVec2((r_screen.GetWidth() > padding.x * 2) ? -padding.x : 0.0f, (r_screen.GetHeight() > padding.y * 2) ? -padding.y : 0.0f));
+    return r_screen;
+}
+
+// r_avoid = the rectangle to avoid (e.g. for tooltip it is a rectangle around the mouse cursor which we want to avoid. for popups it's a small point around the cursor.)
+// r_outer = the visible area rectangle, minus safe area padding. If our popup size won't fit because of safe area padding we ignore it.
+ImVec2 ImGui::FindBestWindowPosForPopupEx(const ImVec2& ref_pos, const ImVec2& size, ImGuiDir* last_dir, const ImRect& r_outer, const ImRect& r_avoid, ImGuiPopupPositionPolicy policy)
+{
+    ImVec2 base_pos_clamped = ImClamp(ref_pos, r_outer.Min, r_outer.Max - size);
+    //GImGui->OverlayDrawList.AddRect(r_avoid.Min, r_avoid.Max, IM_COL32(255,0,0,255));
+    //GImGui->OverlayDrawList.AddRect(r_outer.Min, r_outer.Max, IM_COL32(0,255,0,255));
+
+    // Combo Box policy (we want a connecting edge)
+    if (policy == ImGuiPopupPositionPolicy_ComboBox)
+    {
+        const ImGuiDir dir_prefered_order[ImGuiDir_COUNT] = { ImGuiDir_Down, ImGuiDir_Right, ImGuiDir_Left, ImGuiDir_Up };
+        for (int n = (*last_dir != ImGuiDir_None) ? -1 : 0; n < ImGuiDir_COUNT; n++)
+        {
+            const ImGuiDir dir = (n == -1) ? *last_dir : dir_prefered_order[n];
+            if (n != -1 && dir == *last_dir) // Already tried this direction?
+                continue;
+            ImVec2 pos;
+            if (dir == ImGuiDir_Down)  pos = ImVec2(r_avoid.Min.x, r_avoid.Max.y);          // Below, Toward Right (default)
+            if (dir == ImGuiDir_Right) pos = ImVec2(r_avoid.Min.x, r_avoid.Min.y - size.y); // Above, Toward Right
+            if (dir == ImGuiDir_Left)  pos = ImVec2(r_avoid.Max.x - size.x, r_avoid.Max.y); // Below, Toward Left
+            if (dir == ImGuiDir_Up)    pos = ImVec2(r_avoid.Max.x - size.x, r_avoid.Min.y - size.y); // Above, Toward Left
+            if (!r_outer.Contains(ImRect(pos, pos + size)))
+                continue;
+            *last_dir = dir;
+            return pos;
+        }
+    }
+
+    // Default popup policy
+    const ImGuiDir dir_prefered_order[ImGuiDir_COUNT] = { ImGuiDir_Right, ImGuiDir_Down, ImGuiDir_Up, ImGuiDir_Left };
+    for (int n = (*last_dir != ImGuiDir_None) ? -1 : 0; n < ImGuiDir_COUNT; n++)
+    {
+        const ImGuiDir dir = (n == -1) ? *last_dir : dir_prefered_order[n];
+        if (n != -1 && dir == *last_dir) // Already tried this direction?
+            continue;
+        float avail_w = (dir == ImGuiDir_Left ? r_avoid.Min.x : r_outer.Max.x) - (dir == ImGuiDir_Right ? r_avoid.Max.x : r_outer.Min.x);
+        float avail_h = (dir == ImGuiDir_Up ? r_avoid.Min.y : r_outer.Max.y) - (dir == ImGuiDir_Down ? r_avoid.Max.y : r_outer.Min.y);
+        if (avail_w < size.x || avail_h < size.y)
+            continue;
+        ImVec2 pos;
+        pos.x = (dir == ImGuiDir_Left) ? r_avoid.Min.x - size.x : (dir == ImGuiDir_Right) ? r_avoid.Max.x : base_pos_clamped.x;
+        pos.y = (dir == ImGuiDir_Up)   ? r_avoid.Min.y - size.y : (dir == ImGuiDir_Down)  ? r_avoid.Max.y : base_pos_clamped.y;
+        *last_dir = dir;
+        return pos;
+    }
+
+    // Fallback, try to keep within display
+    *last_dir = ImGuiDir_None;
+    ImVec2 pos = ref_pos;
+    pos.x = ImMax(ImMin(pos.x + size.x, r_outer.Max.x) - size.x, r_outer.Min.x);
+    pos.y = ImMax(ImMin(pos.y + size.y, r_outer.Max.y) - size.y, r_outer.Min.y);
+    return pos;
+}
+
+ImVec2 ImGui::FindBestWindowPosForPopup(ImGuiWindow* window)
+{
+    ImGuiContext& g = *GImGui;
+
+    ImRect r_outer = GetWindowAllowedExtentRect(window);
+    if (window->Flags & ImGuiWindowFlags_ChildMenu)
+    {
+        // Child menus typically request _any_ position within the parent menu item, and then our FindBestWindowPosForPopup() function will move the new menu outside the parent bounds.
+        // This is how we end up with child menus appearing (most-commonly) on the right of the parent menu.
+        IM_ASSERT(g.CurrentWindow == window);
+        ImGuiWindow* parent_window = g.CurrentWindowStack[g.CurrentWindowStack.Size - 2];
+        float horizontal_overlap = g.Style.ItemSpacing.x;       // We want some overlap to convey the relative depth of each menu (currently the amount of overlap is hard-coded to style.ItemSpacing.x).
+        ImRect r_avoid;
+        if (parent_window->DC.MenuBarAppending)
+            r_avoid = ImRect(-FLT_MAX, parent_window->Pos.y + parent_window->TitleBarHeight(), FLT_MAX, parent_window->Pos.y + parent_window->TitleBarHeight() + parent_window->MenuBarHeight());
+        else
+            r_avoid = ImRect(parent_window->Pos.x + horizontal_overlap, -FLT_MAX, parent_window->Pos.x + parent_window->Size.x - horizontal_overlap - parent_window->ScrollbarSizes.x, FLT_MAX);
+        return FindBestWindowPosForPopupEx(window->Pos, window->Size, &window->AutoPosLastDirection, r_outer, r_avoid);
+    }
+    if (window->Flags & ImGuiWindowFlags_Popup)
+    {
+        ImRect r_avoid = ImRect(window->Pos.x - 1, window->Pos.y - 1, window->Pos.x + 1, window->Pos.y + 1);
+        return FindBestWindowPosForPopupEx(window->Pos, window->Size, &window->AutoPosLastDirection, r_outer, r_avoid);
+    }
+    if (window->Flags & ImGuiWindowFlags_Tooltip)
+    {
+        // Position tooltip (always follows mouse)
+        float sc = g.Style.MouseCursorScale;
+        ImVec2 ref_pos = NavCalcPreferredRefPos();
+        ImRect r_avoid;
+        if (!g.NavDisableHighlight && g.NavDisableMouseHover && !(g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableSetMousePos))
+            r_avoid = ImRect(ref_pos.x - 16, ref_pos.y - 8, ref_pos.x + 16, ref_pos.y + 8);
+        else
+            r_avoid = ImRect(ref_pos.x - 16, ref_pos.y - 8, ref_pos.x + 24 * sc, ref_pos.y + 24 * sc); // FIXME: Hard-coded based on mouse cursor shape expectation. Exact dimension not very important.
+        ImVec2 pos = FindBestWindowPosForPopupEx(ref_pos, window->Size, &window->AutoPosLastDirection, r_outer, r_avoid);
+        if (window->AutoPosLastDirection == ImGuiDir_None)
+            pos = ref_pos + ImVec2(2, 2); // If there's not enough room, for tooltip we prefer avoiding the cursor at all cost even if it means that part of the tooltip won't be visible.
+        return pos;
+    }
+    IM_ASSERT(0);
+    return window->Pos;
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] KEYBOARD/GAMEPAD NAVIGATION
+//-----------------------------------------------------------------------------
+
+ImGuiDir ImGetDirQuadrantFromDelta(float dx, float dy)
+{
+    if (ImFabs(dx) > ImFabs(dy))
+        return (dx > 0.0f) ? ImGuiDir_Right : ImGuiDir_Left;
+    return (dy > 0.0f) ? ImGuiDir_Down : ImGuiDir_Up;
+}
+
+static float inline NavScoreItemDistInterval(float a0, float a1, float b0, float b1)
+{
+    if (a1 < b0)
+        return a1 - b0;
+    if (b1 < a0)
+        return a0 - b1;
+    return 0.0f;
+}
+
+static void inline NavClampRectToVisibleAreaForMoveDir(ImGuiDir move_dir, ImRect& r, const ImRect& clip_rect)
+{
+    if (move_dir == ImGuiDir_Left || move_dir == ImGuiDir_Right)
+    {
+        r.Min.y = ImClamp(r.Min.y, clip_rect.Min.y, clip_rect.Max.y);
+        r.Max.y = ImClamp(r.Max.y, clip_rect.Min.y, clip_rect.Max.y);
+    }
+    else
+    {
+        r.Min.x = ImClamp(r.Min.x, clip_rect.Min.x, clip_rect.Max.x);
+        r.Max.x = ImClamp(r.Max.x, clip_rect.Min.x, clip_rect.Max.x);
+    }
+}
+
+// Scoring function for directional navigation. Based on https://gist.github.com/rygorous/6981057
+static bool NavScoreItem(ImGuiNavMoveResult* result, ImRect cand)
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiWindow* window = g.CurrentWindow;
+    if (g.NavLayer != window->DC.NavLayerCurrent)
+        return false;
+
+    const ImRect& curr = g.NavScoringRectScreen; // Current modified source rect (NB: we've applied Max.x = Min.x in NavUpdate() to inhibit the effect of having varied item width)
+    g.NavScoringCount++;
+
+    // When entering through a NavFlattened border, we consider child window items as fully clipped for scoring
+    if (window->ParentWindow == g.NavWindow)
+    {
+        IM_ASSERT((window->Flags | g.NavWindow->Flags) & ImGuiWindowFlags_NavFlattened);
+        if (!window->ClipRect.Contains(cand))
+            return false;
+        cand.ClipWithFull(window->ClipRect); // This allows the scored item to not overlap other candidates in the parent window
+    }
+
+    // We perform scoring on items bounding box clipped by the current clipping rectangle on the other axis (clipping on our movement axis would give us equal scores for all clipped items)
+    // For example, this ensure that items in one column are not reached when moving vertically from items in another column.
+    NavClampRectToVisibleAreaForMoveDir(g.NavMoveClipDir, cand, window->ClipRect);
+
+    // Compute distance between boxes
+    // FIXME-NAV: Introducing biases for vertical navigation, needs to be removed.
+    float dbx = NavScoreItemDistInterval(cand.Min.x, cand.Max.x, curr.Min.x, curr.Max.x);
+    float dby = NavScoreItemDistInterval(ImLerp(cand.Min.y, cand.Max.y, 0.2f), ImLerp(cand.Min.y, cand.Max.y, 0.8f), ImLerp(curr.Min.y, curr.Max.y, 0.2f), ImLerp(curr.Min.y, curr.Max.y, 0.8f)); // Scale down on Y to keep using box-distance for vertically touching items
+    if (dby != 0.0f && dbx != 0.0f)
+       dbx = (dbx/1000.0f) + ((dbx > 0.0f) ? +1.0f : -1.0f);
+    float dist_box = ImFabs(dbx) + ImFabs(dby);
+
+    // Compute distance between centers (this is off by a factor of 2, but we only compare center distances with each other so it doesn't matter)
+    float dcx = (cand.Min.x + cand.Max.x) - (curr.Min.x + curr.Max.x);
+    float dcy = (cand.Min.y + cand.Max.y) - (curr.Min.y + curr.Max.y);
+    float dist_center = ImFabs(dcx) + ImFabs(dcy); // L1 metric (need this for our connectedness guarantee)
+
+    // Determine which quadrant of 'curr' our candidate item 'cand' lies in based on distance
+    ImGuiDir quadrant;
+    float dax = 0.0f, day = 0.0f, dist_axial = 0.0f;
+    if (dbx != 0.0f || dby != 0.0f)
+    {
+        // For non-overlapping boxes, use distance between boxes
+        dax = dbx;
+        day = dby;
+        dist_axial = dist_box;
+        quadrant = ImGetDirQuadrantFromDelta(dbx, dby);
+    }
+    else if (dcx != 0.0f || dcy != 0.0f)
+    {
+        // For overlapping boxes with different centers, use distance between centers
+        dax = dcx;
+        day = dcy;
+        dist_axial = dist_center;
+        quadrant = ImGetDirQuadrantFromDelta(dcx, dcy);
+    }
+    else
+    {
+        // Degenerate case: two overlapping buttons with same center, break ties arbitrarily (note that LastItemId here is really the _previous_ item order, but it doesn't matter)
+        quadrant = (window->DC.LastItemId < g.NavId) ? ImGuiDir_Left : ImGuiDir_Right;
+    }
+
+#if IMGUI_DEBUG_NAV_SCORING
+    char buf[128];
+    if (ImGui::IsMouseHoveringRect(cand.Min, cand.Max))
+    {
+        ImFormatString(buf, IM_ARRAYSIZE(buf), "dbox (%.2f,%.2f->%.4f)\ndcen (%.2f,%.2f->%.4f)\nd (%.2f,%.2f->%.4f)\nnav %c, quadrant %c", dbx, dby, dist_box, dcx, dcy, dist_center, dax, day, dist_axial, "WENS"[g.NavMoveDir], "WENS"[quadrant]);
+        ImDrawList* draw_list = ImGui::GetOverlayDrawList();
+        draw_list->AddRect(curr.Min, curr.Max, IM_COL32(255,200,0,100));
+        draw_list->AddRect(cand.Min, cand.Max, IM_COL32(255,255,0,200));
+        draw_list->AddRectFilled(cand.Max-ImVec2(4,4), cand.Max+ImGui::CalcTextSize(buf)+ImVec2(4,4), IM_COL32(40,0,0,150));
+        draw_list->AddText(g.IO.FontDefault, 13.0f, cand.Max, ~0U, buf);
+    }
+    else if (g.IO.KeyCtrl) // Hold to preview score in matching quadrant. Press C to rotate.
+    {
+        if (IsKeyPressedMap(ImGuiKey_C)) { g.NavMoveDirLast = (ImGuiDir)((g.NavMoveDirLast + 1) & 3); g.IO.KeysDownDuration[g.IO.KeyMap[ImGuiKey_C]] = 0.01f; }
+        if (quadrant == g.NavMoveDir)
+        {
+            ImFormatString(buf, IM_ARRAYSIZE(buf), "%.0f/%.0f", dist_box, dist_center);
+            ImDrawList* draw_list = ImGui::GetOverlayDrawList();
+            draw_list->AddRectFilled(cand.Min, cand.Max, IM_COL32(255, 0, 0, 200));
+            draw_list->AddText(g.IO.FontDefault, 13.0f, cand.Min, IM_COL32(255, 255, 255, 255), buf);
+        }
+    }
+ #endif
+
+    // Is it in the quadrant we're interesting in moving to?
+    bool new_best = false;
+    if (quadrant == g.NavMoveDir)
+    {
+        // Does it beat the current best candidate?
+        if (dist_box < result->DistBox)
+        {
+            result->DistBox = dist_box;
+            result->DistCenter = dist_center;
+            return true;
+        }
+        if (dist_box == result->DistBox)
+        {
+            // Try using distance between center points to break ties
+            if (dist_center < result->DistCenter)
+            {
+                result->DistCenter = dist_center;
+                new_best = true;
+            }
+            else if (dist_center == result->DistCenter)
+            {
+                // Still tied! we need to be extra-careful to make sure everything gets linked properly. We consistently break ties by symbolically moving "later" items
+                // (with higher index) to the right/downwards by an infinitesimal amount since we the current "best" button already (so it must have a lower index),
+                // this is fairly easy. This rule ensures that all buttons with dx==dy==0 will end up being linked in order of appearance along the x axis.
+                if (((g.NavMoveDir == ImGuiDir_Up || g.NavMoveDir == ImGuiDir_Down) ? dby : dbx) < 0.0f) // moving bj to the right/down decreases distance
+                    new_best = true;
+            }
+        }
+    }
+
+    // Axial check: if 'curr' has no link at all in some direction and 'cand' lies roughly in that direction, add a tentative link. This will only be kept if no "real" matches
+    // are found, so it only augments the graph produced by the above method using extra links. (important, since it doesn't guarantee strong connectedness)
+    // This is just to avoid buttons having no links in a particular direction when there's a suitable neighbor. you get good graphs without this too.
+    // 2017/09/29: FIXME: This now currently only enabled inside menu bars, ideally we'd disable it everywhere. Menus in particular need to catch failure. For general navigation it feels awkward.
+    // Disabling it may lead to disconnected graphs when nodes are very spaced out on different axis. Perhaps consider offering this as an option?
+    if (result->DistBox == FLT_MAX && dist_axial < result->DistAxial)  // Check axial match
+        if (g.NavLayer == 1 && !(g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu))
+            if ((g.NavMoveDir == ImGuiDir_Left && dax < 0.0f) || (g.NavMoveDir == ImGuiDir_Right && dax > 0.0f) || (g.NavMoveDir == ImGuiDir_Up && day < 0.0f) || (g.NavMoveDir == ImGuiDir_Down && day > 0.0f))
+            {
+                result->DistAxial = dist_axial;
+                new_best = true;
+            }
+
+    return new_best;
+}
+
+// We get there when either NavId == id, or when g.NavAnyRequest is set (which is updated by NavUpdateAnyRequestFlag above)
+static void ImGui::NavProcessItem(ImGuiWindow* window, const ImRect& nav_bb, const ImGuiID id)
+{
+    ImGuiContext& g = *GImGui;
+    //if (!g.IO.NavActive)  // [2017/10/06] Removed this possibly redundant test but I am not sure of all the side-effects yet. Some of the feature here will need to work regardless of using a _NoNavInputs flag.
+    //    return;
+
+    const ImGuiItemFlags item_flags = window->DC.ItemFlags;
+    const ImRect nav_bb_rel(nav_bb.Min - window->Pos, nav_bb.Max - window->Pos);
+
+    // Process Init Request
+    if (g.NavInitRequest && g.NavLayer == window->DC.NavLayerCurrent)
+    {
+        // Even if 'ImGuiItemFlags_NoNavDefaultFocus' is on (typically collapse/close button) we record the first ResultId so they can be used as a fallback
+        if (!(item_flags & ImGuiItemFlags_NoNavDefaultFocus) || g.NavInitResultId == 0)
+        {
+            g.NavInitResultId = id;
+            g.NavInitResultRectRel = nav_bb_rel;
+        }
+        if (!(item_flags & ImGuiItemFlags_NoNavDefaultFocus))
+        {
+            g.NavInitRequest = false; // Found a match, clear request
+            NavUpdateAnyRequestFlag();
+        }
+    }
+
+    // Process Move Request (scoring for navigation)
+    // FIXME-NAV: Consider policy for double scoring (scoring from NavScoringRectScreen + scoring from a rect wrapped according to current wrapping policy)
+    if ((g.NavId != id || (g.NavMoveRequestFlags & ImGuiNavMoveFlags_AllowCurrentNavId)) && !(item_flags & ImGuiItemFlags_NoNav))
+    {
+        ImGuiNavMoveResult* result = (window == g.NavWindow) ? &g.NavMoveResultLocal : &g.NavMoveResultOther;
+#if IMGUI_DEBUG_NAV_SCORING
+        // [DEBUG] Score all items in NavWindow at all times
+        if (!g.NavMoveRequest)
+            g.NavMoveDir = g.NavMoveDirLast;
+        bool new_best = NavScoreItem(result, nav_bb) && g.NavMoveRequest;
+#else
+        bool new_best = g.NavMoveRequest && NavScoreItem(result, nav_bb);
+#endif
+        if (new_best)
+        {
+            result->ID = id;
+            result->Window = window;
+            result->RectRel = nav_bb_rel;
+        }
+
+        const float VISIBLE_RATIO = 0.70f;
+        if ((g.NavMoveRequestFlags & ImGuiNavMoveFlags_AlsoScoreVisibleSet) && window->ClipRect.Overlaps(nav_bb))
+            if (ImClamp(nav_bb.Max.y, window->ClipRect.Min.y, window->ClipRect.Max.y) - ImClamp(nav_bb.Min.y, window->ClipRect.Min.y, window->ClipRect.Max.y) >= (nav_bb.Max.y - nav_bb.Min.y) * VISIBLE_RATIO)
+                if (NavScoreItem(&g.NavMoveResultLocalVisibleSet, nav_bb))
+                {
+                    result = &g.NavMoveResultLocalVisibleSet;
+                    result->ID = id;
+                    result->Window = window;
+                    result->RectRel = nav_bb_rel;
+                }
+    }
+
+    // Update window-relative bounding box of navigated item
+    if (g.NavId == id)
+    {
+        g.NavWindow = window;                                           // Always refresh g.NavWindow, because some operations such as FocusItem() don't have a window.
+        g.NavLayer = window->DC.NavLayerCurrent;
+        g.NavIdIsAlive = true;
+        g.NavIdTabCounter = window->FocusIdxTabCounter;
+        window->NavRectRel[window->DC.NavLayerCurrent] = nav_bb_rel;    // Store item bounding box (relative to window position)
+    }
+}
+
+bool ImGui::NavMoveRequestButNoResultYet()
+{
+    ImGuiContext& g = *GImGui;
+    return g.NavMoveRequest && g.NavMoveResultLocal.ID == 0 && g.NavMoveResultOther.ID == 0;
+}
+
+void ImGui::NavMoveRequestCancel()
+{
+    ImGuiContext& g = *GImGui;
+    g.NavMoveRequest = false;
+    NavUpdateAnyRequestFlag();
+}
+
+void ImGui::NavMoveRequestForward(ImGuiDir move_dir, ImGuiDir clip_dir, const ImRect& bb_rel, ImGuiNavMoveFlags move_flags)
+{
+    ImGuiContext& g = *GImGui;
+    IM_ASSERT(g.NavMoveRequestForward == ImGuiNavForward_None);
+    ImGui::NavMoveRequestCancel();
+    g.NavMoveDir = move_dir;
+    g.NavMoveClipDir = clip_dir;
+    g.NavMoveRequestForward = ImGuiNavForward_ForwardQueued;
+    g.NavMoveRequestFlags = move_flags;
+    g.NavWindow->NavRectRel[g.NavLayer] = bb_rel;
+}
+
+void ImGui::NavMoveRequestTryWrapping(ImGuiWindow* window, ImGuiNavMoveFlags move_flags)
+{
+    ImGuiContext& g = *GImGui;
+    if (g.NavWindow != window || !NavMoveRequestButNoResultYet() || g.NavMoveRequestForward != ImGuiNavForward_None || g.NavLayer != 0)
+        return;
+    IM_ASSERT(move_flags != 0); // No points calling this with no wrapping
+    ImRect bb_rel = window->NavRectRel[0];
+
+    ImGuiDir clip_dir = g.NavMoveDir;
+    if (g.NavMoveDir == ImGuiDir_Left && (move_flags & (ImGuiNavMoveFlags_WrapX | ImGuiNavMoveFlags_LoopX)))
+    {
+        bb_rel.Min.x = bb_rel.Max.x = ImMax(window->SizeFull.x, window->SizeContents.x) - window->Scroll.x;
+        if (move_flags & ImGuiNavMoveFlags_WrapX) { bb_rel.TranslateY(-bb_rel.GetHeight()); clip_dir = ImGuiDir_Up; }
+        NavMoveRequestForward(g.NavMoveDir, clip_dir, bb_rel, move_flags);
+    }
+    if (g.NavMoveDir == ImGuiDir_Right && (move_flags & (ImGuiNavMoveFlags_WrapX | ImGuiNavMoveFlags_LoopX)))
+    {
+        bb_rel.Min.x = bb_rel.Max.x = -window->Scroll.x;
+        if (move_flags & ImGuiNavMoveFlags_WrapX) { bb_rel.TranslateY(+bb_rel.GetHeight()); clip_dir = ImGuiDir_Down; }
+        NavMoveRequestForward(g.NavMoveDir, clip_dir, bb_rel, move_flags);
+    }
+    if (g.NavMoveDir == ImGuiDir_Up && (move_flags & (ImGuiNavMoveFlags_WrapY | ImGuiNavMoveFlags_LoopY)))
+    {
+        bb_rel.Min.y = bb_rel.Max.y = ImMax(window->SizeFull.y, window->SizeContents.y) - window->Scroll.y;
+        if (move_flags & ImGuiNavMoveFlags_WrapY) { bb_rel.TranslateX(-bb_rel.GetWidth()); clip_dir = ImGuiDir_Left; }
+        NavMoveRequestForward(g.NavMoveDir, clip_dir, bb_rel, move_flags);
+    }
+    if (g.NavMoveDir == ImGuiDir_Down && (move_flags & (ImGuiNavMoveFlags_WrapY | ImGuiNavMoveFlags_LoopY)))
+    {
+        bb_rel.Min.y = bb_rel.Max.y = -window->Scroll.y;
+        if (move_flags & ImGuiNavMoveFlags_WrapY) { bb_rel.TranslateX(+bb_rel.GetWidth()); clip_dir = ImGuiDir_Right; }
+        NavMoveRequestForward(g.NavMoveDir, clip_dir, bb_rel, move_flags);
+    }
+}
+
+static void ImGui::NavSaveLastChildNavWindow(ImGuiWindow* nav_window)
+{
+    ImGuiWindow* parent_window = nav_window;
+    while (parent_window && (parent_window->Flags & ImGuiWindowFlags_ChildWindow) != 0 && (parent_window->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu)) == 0)
+        parent_window = parent_window->ParentWindow;
+    if (parent_window && parent_window != nav_window)
+        parent_window->NavLastChildNavWindow = nav_window;
+}
+
+// Call when we are expected to land on Layer 0 after FocusWindow()
+static ImGuiWindow* ImGui::NavRestoreLastChildNavWindow(ImGuiWindow* window)
+{
+    return window->NavLastChildNavWindow ? window->NavLastChildNavWindow : window;
+}
+
+static void NavRestoreLayer(int layer)
+{
+    ImGuiContext& g = *GImGui;
+    g.NavLayer = layer;
+    if (layer == 0)
+        g.NavWindow = ImGui::NavRestoreLastChildNavWindow(g.NavWindow);
+    if (layer == 0 && g.NavWindow->NavLastIds[0] != 0)
+        ImGui::SetNavIDWithRectRel(g.NavWindow->NavLastIds[0], layer, g.NavWindow->NavRectRel[0]);
+    else
+        ImGui::NavInitWindow(g.NavWindow, true);
+}
+
+static inline void ImGui::NavUpdateAnyRequestFlag()
+{
+    ImGuiContext& g = *GImGui;
+    g.NavAnyRequest = g.NavMoveRequest || g.NavInitRequest || (IMGUI_DEBUG_NAV_SCORING && g.NavWindow != NULL);
+    if (g.NavAnyRequest)
+        IM_ASSERT(g.NavWindow != NULL);
+}
+
+// This needs to be called before we submit any widget (aka in or before Begin)
+void ImGui::NavInitWindow(ImGuiWindow* window, bool force_reinit)
+{
+    ImGuiContext& g = *GImGui;
+    IM_ASSERT(window == g.NavWindow);
+    bool init_for_nav = false;
+    if (!(window->Flags & ImGuiWindowFlags_NoNavInputs))
+        if (!(window->Flags & ImGuiWindowFlags_ChildWindow) || (window->Flags & ImGuiWindowFlags_Popup) || (window->NavLastIds[0] == 0) || force_reinit)
+            init_for_nav = true;
+    if (init_for_nav)
+    {
+        SetNavID(0, g.NavLayer);
+        g.NavInitRequest = true;
+        g.NavInitRequestFromMove = false;
+        g.NavInitResultId = 0;
+        g.NavInitResultRectRel = ImRect();
+        NavUpdateAnyRequestFlag();
+    }
+    else
+    {
+        g.NavId = window->NavLastIds[0];
+    }
+}
+
+static ImVec2 ImGui::NavCalcPreferredRefPos()
+{
+    ImGuiContext& g = *GImGui;
+    if (g.NavDisableHighlight || !g.NavDisableMouseHover || !g.NavWindow)
+        return ImFloor(g.IO.MousePos);
+
+    // When navigation is active and mouse is disabled, decide on an arbitrary position around the bottom left of the currently navigated item
+    const ImRect& rect_rel = g.NavWindow->NavRectRel[g.NavLayer];
+    ImVec2 pos = g.NavWindow->Pos + ImVec2(rect_rel.Min.x + ImMin(g.Style.FramePadding.x*4, rect_rel.GetWidth()), rect_rel.Max.y - ImMin(g.Style.FramePadding.y, rect_rel.GetHeight()));
+    ImRect visible_rect = GetViewportRect();
+    return ImFloor(ImClamp(pos, visible_rect.Min, visible_rect.Max));   // ImFloor() is important because non-integer mouse position application in back-end might be lossy and result in undesirable non-zero delta.
+}
+
+float ImGui::GetNavInputAmount(ImGuiNavInput n, ImGuiInputReadMode mode)
+{
+    ImGuiContext& g = *GImGui;
+    if (mode == ImGuiInputReadMode_Down)
+        return g.IO.NavInputs[n];                         // Instant, read analog input (0.0f..1.0f, as provided by user)
+
+    const float t = g.IO.NavInputsDownDuration[n];
+    if (t < 0.0f && mode == ImGuiInputReadMode_Released)  // Return 1.0f when just released, no repeat, ignore analog input.
+        return (g.IO.NavInputsDownDurationPrev[n] >= 0.0f ? 1.0f : 0.0f);
+    if (t < 0.0f)
+        return 0.0f;
+    if (mode == ImGuiInputReadMode_Pressed)               // Return 1.0f when just pressed, no repeat, ignore analog input.
+        return (t == 0.0f) ? 1.0f : 0.0f;
+    if (mode == ImGuiInputReadMode_Repeat)
+        return (float)CalcTypematicPressedRepeatAmount(t, t - g.IO.DeltaTime, g.IO.KeyRepeatDelay * 0.80f, g.IO.KeyRepeatRate * 0.80f);
+    if (mode == ImGuiInputReadMode_RepeatSlow)
+        return (float)CalcTypematicPressedRepeatAmount(t, t - g.IO.DeltaTime, g.IO.KeyRepeatDelay * 1.00f, g.IO.KeyRepeatRate * 2.00f);
+    if (mode == ImGuiInputReadMode_RepeatFast)
+        return (float)CalcTypematicPressedRepeatAmount(t, t - g.IO.DeltaTime, g.IO.KeyRepeatDelay * 0.80f, g.IO.KeyRepeatRate * 0.30f);
+    return 0.0f;
+}
+
+ImVec2 ImGui::GetNavInputAmount2d(ImGuiNavDirSourceFlags dir_sources, ImGuiInputReadMode mode, float slow_factor, float fast_factor)
+{
+    ImVec2 delta(0.0f, 0.0f);
+    if (dir_sources & ImGuiNavDirSourceFlags_Keyboard)
+        delta += ImVec2(GetNavInputAmount(ImGuiNavInput_KeyRight_, mode)   - GetNavInputAmount(ImGuiNavInput_KeyLeft_,   mode), GetNavInputAmount(ImGuiNavInput_KeyDown_,   mode) - GetNavInputAmount(ImGuiNavInput_KeyUp_,   mode));
+    if (dir_sources & ImGuiNavDirSourceFlags_PadDPad)
+        delta += ImVec2(GetNavInputAmount(ImGuiNavInput_DpadRight, mode)   - GetNavInputAmount(ImGuiNavInput_DpadLeft,   mode), GetNavInputAmount(ImGuiNavInput_DpadDown,   mode) - GetNavInputAmount(ImGuiNavInput_DpadUp,   mode));
+    if (dir_sources & ImGuiNavDirSourceFlags_PadLStick)
+        delta += ImVec2(GetNavInputAmount(ImGuiNavInput_LStickRight, mode) - GetNavInputAmount(ImGuiNavInput_LStickLeft, mode), GetNavInputAmount(ImGuiNavInput_LStickDown, mode) - GetNavInputAmount(ImGuiNavInput_LStickUp, mode));
+    if (slow_factor != 0.0f && IsNavInputDown(ImGuiNavInput_TweakSlow))
+        delta *= slow_factor;
+    if (fast_factor != 0.0f && IsNavInputDown(ImGuiNavInput_TweakFast))
+        delta *= fast_factor;
+    return delta;
+}
+
+// Scroll to keep newly navigated item fully into view
+// NB: We modify rect_rel by the amount we scrolled for, so it is immediately updated.
+static void NavScrollToBringItemIntoView(ImGuiWindow* window, const ImRect& item_rect)
+{
+    ImRect window_rect(window->InnerMainRect.Min - ImVec2(1, 1), window->InnerMainRect.Max + ImVec2(1, 1));
+    //g.OverlayDrawList.AddRect(window_rect.Min, window_rect.Max, IM_COL32_WHITE); // [DEBUG]
+    if (window_rect.Contains(item_rect))
+        return;
+
+    ImGuiContext& g = *GImGui;
+    if (window->ScrollbarX && item_rect.Min.x < window_rect.Min.x)
+    {
+        window->ScrollTarget.x = item_rect.Min.x - window->Pos.x + window->Scroll.x - g.Style.ItemSpacing.x;
+        window->ScrollTargetCenterRatio.x = 0.0f;
+    }
+    else if (window->ScrollbarX && item_rect.Max.x >= window_rect.Max.x)
+    {
+        window->ScrollTarget.x = item_rect.Max.x - window->Pos.x + window->Scroll.x + g.Style.ItemSpacing.x;
+        window->ScrollTargetCenterRatio.x = 1.0f;
+    }
+    if (item_rect.Min.y < window_rect.Min.y)
+    {
+        window->ScrollTarget.y = item_rect.Min.y - window->Pos.y + window->Scroll.y - g.Style.ItemSpacing.y;
+        window->ScrollTargetCenterRatio.y = 0.0f;
+    }
+    else if (item_rect.Max.y >= window_rect.Max.y)
+    {
+        window->ScrollTarget.y = item_rect.Max.y - window->Pos.y + window->Scroll.y + g.Style.ItemSpacing.y;
+        window->ScrollTargetCenterRatio.y = 1.0f;
+    }
+}
+
+static void ImGui::NavUpdate()
+{
+    ImGuiContext& g = *GImGui;
+    g.IO.WantSetMousePos = false;
+#if 0
+    if (g.NavScoringCount > 0) printf("[%05d] NavScoringCount %d for '%s' layer %d (Init:%d, Move:%d)\n", g.FrameCount, g.NavScoringCount, g.NavWindow ? g.NavWindow->Name : "NULL", g.NavLayer, g.NavInitRequest || g.NavInitResultId != 0, g.NavMoveRequest);
+#endif
+
+    // Set input source as Gamepad when buttons are pressed before we map Keyboard (some features differs when used with Gamepad vs Keyboard)
+    bool nav_keyboard_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0;
+    bool nav_gamepad_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (g.IO.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0;
+    if (nav_gamepad_active)
+        if (g.IO.NavInputs[ImGuiNavInput_Activate] > 0.0f || g.IO.NavInputs[ImGuiNavInput_Input] > 0.0f || g.IO.NavInputs[ImGuiNavInput_Cancel] > 0.0f || g.IO.NavInputs[ImGuiNavInput_Menu] > 0.0f)
+            g.NavInputSource = ImGuiInputSource_NavGamepad;
+
+    // Update Keyboard->Nav inputs mapping
+    if (nav_keyboard_active)
+    {
+        #define NAV_MAP_KEY(_KEY, _NAV_INPUT) if (IsKeyDown(g.IO.KeyMap[_KEY])) { g.IO.NavInputs[_NAV_INPUT] = 1.0f; g.NavInputSource = ImGuiInputSource_NavKeyboard; }
+        NAV_MAP_KEY(ImGuiKey_Space,     ImGuiNavInput_Activate );
+        NAV_MAP_KEY(ImGuiKey_Enter,     ImGuiNavInput_Input    );
+        NAV_MAP_KEY(ImGuiKey_Escape,    ImGuiNavInput_Cancel   );
+        NAV_MAP_KEY(ImGuiKey_LeftArrow, ImGuiNavInput_KeyLeft_ );
+        NAV_MAP_KEY(ImGuiKey_RightArrow,ImGuiNavInput_KeyRight_);
+        NAV_MAP_KEY(ImGuiKey_UpArrow,   ImGuiNavInput_KeyUp_   );
+        NAV_MAP_KEY(ImGuiKey_DownArrow, ImGuiNavInput_KeyDown_ );
+        if (g.IO.KeyCtrl)   g.IO.NavInputs[ImGuiNavInput_TweakSlow] = 1.0f;
+        if (g.IO.KeyShift)  g.IO.NavInputs[ImGuiNavInput_TweakFast] = 1.0f;
+        if (g.IO.KeyAlt)    g.IO.NavInputs[ImGuiNavInput_KeyMenu_]  = 1.0f;
+        #undef NAV_MAP_KEY
+    }
+    memcpy(g.IO.NavInputsDownDurationPrev, g.IO.NavInputsDownDuration, sizeof(g.IO.NavInputsDownDuration));
+    for (int i = 0; i < IM_ARRAYSIZE(g.IO.NavInputs); i++)
+        g.IO.NavInputsDownDuration[i] = (g.IO.NavInputs[i] > 0.0f) ? (g.IO.NavInputsDownDuration[i] < 0.0f ? 0.0f : g.IO.NavInputsDownDuration[i] + g.IO.DeltaTime) : -1.0f;
+
+    // Process navigation init request (select first/default focus)
+    if (g.NavInitResultId != 0 && (!g.NavDisableHighlight || g.NavInitRequestFromMove))
+    {
+        // Apply result from previous navigation init request (will typically select the first item, unless SetItemDefaultFocus() has been called)
+        IM_ASSERT(g.NavWindow);
+        if (g.NavInitRequestFromMove)
+            SetNavIDWithRectRel(g.NavInitResultId, g.NavLayer, g.NavInitResultRectRel);
+        else
+            SetNavID(g.NavInitResultId, g.NavLayer);
+        g.NavWindow->NavRectRel[g.NavLayer] = g.NavInitResultRectRel;
+    }
+    g.NavInitRequest = false;
+    g.NavInitRequestFromMove = false;
+    g.NavInitResultId = 0;
+    g.NavJustMovedToId = 0;
+
+    // Process navigation move request
+    if (g.NavMoveRequest && (g.NavMoveResultLocal.ID != 0 || g.NavMoveResultOther.ID != 0))
+        NavUpdateMoveResult();
+
+    // When a forwarded move request failed, we restore the highlight that we disabled during the forward frame
+    if (g.NavMoveRequestForward == ImGuiNavForward_ForwardActive)
+    {
+        IM_ASSERT(g.NavMoveRequest);
+        if (g.NavMoveResultLocal.ID == 0 && g.NavMoveResultOther.ID == 0)
+            g.NavDisableHighlight = false;
+        g.NavMoveRequestForward = ImGuiNavForward_None;
+    }
+
+    // Apply application mouse position movement, after we had a chance to process move request result.
+    if (g.NavMousePosDirty && g.NavIdIsAlive)
+    {
+        // Set mouse position given our knowledge of the navigated item position from last frame
+        if ((g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableSetMousePos) && (g.IO.BackendFlags & ImGuiBackendFlags_HasSetMousePos))
+        {
+            if (!g.NavDisableHighlight && g.NavDisableMouseHover && g.NavWindow)
+            {
+                g.IO.MousePos = g.IO.MousePosPrev = NavCalcPreferredRefPos();
+                g.IO.WantSetMousePos = true;
+            }
+        }
+        g.NavMousePosDirty = false;
+    }
+    g.NavIdIsAlive = false;
+    g.NavJustTabbedId = 0;
+    IM_ASSERT(g.NavLayer == 0 || g.NavLayer == 1);
+
+    // Store our return window (for returning from Layer 1 to Layer 0) and clear it as soon as we step back in our own Layer 0
+    if (g.NavWindow)
+        NavSaveLastChildNavWindow(g.NavWindow);
+    if (g.NavWindow && g.NavWindow->NavLastChildNavWindow != NULL && g.NavLayer == 0)
+        g.NavWindow->NavLastChildNavWindow = NULL;
+
+    // Update CTRL+TAB and Windowing features (hold Square to move/resize/etc.)
+    NavUpdateWindowing();
+
+    // Set output flags for user application
+    g.IO.NavActive = (nav_keyboard_active || nav_gamepad_active) && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs);
+    g.IO.NavVisible = (g.IO.NavActive && g.NavId != 0 && !g.NavDisableHighlight) || (g.NavWindowingTarget != NULL) || g.NavInitRequest;
+
+    // Process NavCancel input (to close a popup, get back to parent, clear focus)
+    if (IsNavInputPressed(ImGuiNavInput_Cancel, ImGuiInputReadMode_Pressed))
+    {
+        if (g.ActiveId != 0)
+        {
+            ClearActiveID();
+        }
+        else if (g.NavWindow && (g.NavWindow->Flags & ImGuiWindowFlags_ChildWindow) && !(g.NavWindow->Flags & ImGuiWindowFlags_Popup) && g.NavWindow->ParentWindow)
+        {
+            // Exit child window
+            ImGuiWindow* child_window = g.NavWindow;
+            ImGuiWindow* parent_window = g.NavWindow->ParentWindow;
+            IM_ASSERT(child_window->ChildId != 0);
+            FocusWindow(parent_window);
+            SetNavID(child_window->ChildId, 0);
+            g.NavIdIsAlive = false;
+            if (g.NavDisableMouseHover)
+                g.NavMousePosDirty = true;
+        }
+        else if (g.OpenPopupStack.Size > 0)
+        {
+            // Close open popup/menu
+            if (!(g.OpenPopupStack.back().Window->Flags & ImGuiWindowFlags_Modal))
+                ClosePopupToLevel(g.OpenPopupStack.Size - 1);
+        }
+        else if (g.NavLayer != 0)
+        {
+            // Leave the "menu" layer
+            NavRestoreLayer(0);
+        }
+        else
+        {
+            // Clear NavLastId for popups but keep it for regular child window so we can leave one and come back where we were
+            if (g.NavWindow && ((g.NavWindow->Flags & ImGuiWindowFlags_Popup) || !(g.NavWindow->Flags & ImGuiWindowFlags_ChildWindow)))
+                g.NavWindow->NavLastIds[0] = 0;
+            g.NavId = 0;
+        }
+    }
+
+    // Process manual activation request
+    g.NavActivateId = g.NavActivateDownId = g.NavActivatePressedId = g.NavInputId = 0;
+    if (g.NavId != 0 && !g.NavDisableHighlight && !g.NavWindowingTarget && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs))
+    {
+        bool activate_down = IsNavInputDown(ImGuiNavInput_Activate);
+        bool activate_pressed = activate_down && IsNavInputPressed(ImGuiNavInput_Activate, ImGuiInputReadMode_Pressed);
+        if (g.ActiveId == 0 && activate_pressed)
+            g.NavActivateId = g.NavId;
+        if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && activate_down)
+            g.NavActivateDownId = g.NavId;
+        if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && activate_pressed)
+            g.NavActivatePressedId = g.NavId;
+        if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && IsNavInputPressed(ImGuiNavInput_Input, ImGuiInputReadMode_Pressed))
+            g.NavInputId = g.NavId;
+    }
+    if (g.NavWindow && (g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs))
+        g.NavDisableHighlight = true;
+    if (g.NavActivateId != 0)
+        IM_ASSERT(g.NavActivateDownId == g.NavActivateId);
+    g.NavMoveRequest = false;
+
+    // Process programmatic activation request
+    if (g.NavNextActivateId != 0)
+        g.NavActivateId = g.NavActivateDownId = g.NavActivatePressedId = g.NavInputId = g.NavNextActivateId;
+    g.NavNextActivateId = 0;
+
+    // Initiate directional inputs request
+    const int allowed_dir_flags = (g.ActiveId == 0) ? ~0 : g.ActiveIdAllowNavDirFlags;
+    if (g.NavMoveRequestForward == ImGuiNavForward_None)
+    {
+        g.NavMoveDir = ImGuiDir_None;
+        g.NavMoveRequestFlags = 0;
+        if (g.NavWindow && !g.NavWindowingTarget && allowed_dir_flags && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs))
+        {
+            if ((allowed_dir_flags & (1<<ImGuiDir_Left))  && IsNavInputPressedAnyOfTwo(ImGuiNavInput_DpadLeft, ImGuiNavInput_KeyLeft_, ImGuiInputReadMode_Repeat)) g.NavMoveDir = ImGuiDir_Left;
+            if ((allowed_dir_flags & (1<<ImGuiDir_Right)) && IsNavInputPressedAnyOfTwo(ImGuiNavInput_DpadRight,ImGuiNavInput_KeyRight_,ImGuiInputReadMode_Repeat)) g.NavMoveDir = ImGuiDir_Right;
+            if ((allowed_dir_flags & (1<<ImGuiDir_Up))    && IsNavInputPressedAnyOfTwo(ImGuiNavInput_DpadUp,   ImGuiNavInput_KeyUp_,   ImGuiInputReadMode_Repeat)) g.NavMoveDir = ImGuiDir_Up;
+            if ((allowed_dir_flags & (1<<ImGuiDir_Down))  && IsNavInputPressedAnyOfTwo(ImGuiNavInput_DpadDown, ImGuiNavInput_KeyDown_, ImGuiInputReadMode_Repeat)) g.NavMoveDir = ImGuiDir_Down;
+        }
+        g.NavMoveClipDir = g.NavMoveDir;
+    }
+    else
+    {
+        // Forwarding previous request (which has been modified, e.g. wrap around menus rewrite the requests with a starting rectangle at the other side of the window)
+        // (Preserve g.NavMoveRequestFlags, g.NavMoveClipDir which were set by the NavMoveRequestForward() function)
+        IM_ASSERT(g.NavMoveDir != ImGuiDir_None && g.NavMoveClipDir != ImGuiDir_None);
+        IM_ASSERT(g.NavMoveRequestForward == ImGuiNavForward_ForwardQueued);
+        g.NavMoveRequestForward = ImGuiNavForward_ForwardActive;
+    }
+
+    // Update PageUp/PageDown scroll
+    float nav_scoring_rect_offset_y = 0.0f;
+    if (nav_keyboard_active)
+        nav_scoring_rect_offset_y = NavUpdatePageUpPageDown(allowed_dir_flags);
+
+    // If we initiate a movement request and have no current NavId, we initiate a InitDefautRequest that will be used as a fallback if the direction fails to find a match
+    if (g.NavMoveDir != ImGuiDir_None)
+    {
+        g.NavMoveRequest = true;
+        g.NavMoveDirLast = g.NavMoveDir;
+    }
+    if (g.NavMoveRequest && g.NavId == 0)
+    {
+        g.NavInitRequest = g.NavInitRequestFromMove = true;
+        g.NavInitResultId = 0;
+        g.NavDisableHighlight = false;
+    }
+    NavUpdateAnyRequestFlag();
+
+    // Scrolling
+    if (g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs) && !g.NavWindowingTarget)
+    {
+        // *Fallback* manual-scroll with Nav directional keys when window has no navigable item
+        ImGuiWindow* window = g.NavWindow;
+        const float scroll_speed = ImFloor(window->CalcFontSize() * 100 * g.IO.DeltaTime + 0.5f); // We need round the scrolling speed because sub-pixel scroll isn't reliably supported.
+        if (window->DC.NavLayerActiveMask == 0x00 && window->DC.NavHasScroll && g.NavMoveRequest)
+        {
+            if (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right)
+                SetWindowScrollX(window, ImFloor(window->Scroll.x + ((g.NavMoveDir == ImGuiDir_Left) ? -1.0f : +1.0f) * scroll_speed));
+            if (g.NavMoveDir == ImGuiDir_Up || g.NavMoveDir == ImGuiDir_Down)
+                SetWindowScrollY(window, ImFloor(window->Scroll.y + ((g.NavMoveDir == ImGuiDir_Up) ? -1.0f : +1.0f) * scroll_speed));
+        }
+
+        // *Normal* Manual scroll with NavScrollXXX keys
+        // Next movement request will clamp the NavId reference rectangle to the visible area, so navigation will resume within those bounds.
+        ImVec2 scroll_dir = GetNavInputAmount2d(ImGuiNavDirSourceFlags_PadLStick, ImGuiInputReadMode_Down, 1.0f/10.0f, 10.0f);
+        if (scroll_dir.x != 0.0f && window->ScrollbarX)
+        {
+            SetWindowScrollX(window, ImFloor(window->Scroll.x + scroll_dir.x * scroll_speed));
+            g.NavMoveFromClampedRefRect = true;
+        }
+        if (scroll_dir.y != 0.0f)
+        {
+            SetWindowScrollY(window, ImFloor(window->Scroll.y + scroll_dir.y * scroll_speed));
+            g.NavMoveFromClampedRefRect = true;
+        }
+    }
+
+    // Reset search results
+    g.NavMoveResultLocal.Clear();
+    g.NavMoveResultLocalVisibleSet.Clear();
+    g.NavMoveResultOther.Clear();
+
+    // When we have manually scrolled (without using navigation) and NavId becomes out of bounds, we project its bounding box to the visible area to restart navigation within visible items
+    if (g.NavMoveRequest && g.NavMoveFromClampedRefRect && g.NavLayer == 0)
+    {
+        ImGuiWindow* window = g.NavWindow;
+        ImRect window_rect_rel(window->InnerMainRect.Min - window->Pos - ImVec2(1,1), window->InnerMainRect.Max - window->Pos + ImVec2(1,1));
+        if (!window_rect_rel.Contains(window->NavRectRel[g.NavLayer]))
+        {
+            float pad = window->CalcFontSize() * 0.5f;
+            window_rect_rel.Expand(ImVec2(-ImMin(window_rect_rel.GetWidth(), pad), -ImMin(window_rect_rel.GetHeight(), pad))); // Terrible approximation for the intent of starting navigation from first fully visible item
+            window->NavRectRel[g.NavLayer].ClipWith(window_rect_rel);
+            g.NavId = 0;
+        }
+        g.NavMoveFromClampedRefRect = false;
+    }
+
+    // For scoring we use a single segment on the left side our current item bounding box (not touching the edge to avoid box overlap with zero-spaced items)
+    ImRect nav_rect_rel = (g.NavWindow && !g.NavWindow->NavRectRel[g.NavLayer].IsInverted()) ? g.NavWindow->NavRectRel[g.NavLayer] : ImRect(0,0,0,0);
+    g.NavScoringRectScreen = g.NavWindow ? ImRect(g.NavWindow->Pos + nav_rect_rel.Min, g.NavWindow->Pos + nav_rect_rel.Max) : GetViewportRect();
+    g.NavScoringRectScreen.TranslateY(nav_scoring_rect_offset_y);
+    g.NavScoringRectScreen.Min.x = ImMin(g.NavScoringRectScreen.Min.x + 1.0f, g.NavScoringRectScreen.Max.x);
+    g.NavScoringRectScreen.Max.x = g.NavScoringRectScreen.Min.x;
+    IM_ASSERT(!g.NavScoringRectScreen.IsInverted()); // Ensure if we have a finite, non-inverted bounding box here will allows us to remove extraneous ImFabs() calls in NavScoreItem().
+    //g.OverlayDrawList.AddRect(g.NavScoringRectScreen.Min, g.NavScoringRectScreen.Max, IM_COL32(255,200,0,255)); // [DEBUG]
+    g.NavScoringCount = 0;
+#if IMGUI_DEBUG_NAV_RECTS
+    if (g.NavWindow) { for (int layer = 0; layer < 2; layer++) GetOverlayDrawList()->AddRect(g.NavWindow->Pos + g.NavWindow->NavRectRel[layer].Min, g.NavWindow->Pos + g.NavWindow->NavRectRel[layer].Max, IM_COL32(255,200,0,255)); } // [DEBUG]
+    if (g.NavWindow) { ImU32 col = (g.NavWindow->HiddenFrames == 0) ? IM_COL32(255,0,255,255) : IM_COL32(255,0,0,255); ImVec2 p = NavCalcPreferredRefPos(); char buf[32]; ImFormatString(buf, 32, "%d", g.NavLayer); g.OverlayDrawList.AddCircleFilled(p, 3.0f, col); g.OverlayDrawList.AddText(NULL, 13.0f, p + ImVec2(8,-4), col, buf); }
+#endif
+}
+
+static void ImGui::NavUpdateMoveResult()
+{
+    // Select which result to use
+    ImGuiContext& g = *GImGui;
+    ImGuiNavMoveResult* result = (g.NavMoveResultLocal.ID != 0) ? &g.NavMoveResultLocal : &g.NavMoveResultOther;
+
+    // PageUp/PageDown behavior first jumps to the bottom/top mostly visible item, _otherwise_ use the result from the previous/next page.
+    if (g.NavMoveRequestFlags & ImGuiNavMoveFlags_AlsoScoreVisibleSet)
+        if (g.NavMoveResultLocalVisibleSet.ID != 0 && g.NavMoveResultLocalVisibleSet.ID != g.NavId)
+            result = &g.NavMoveResultLocalVisibleSet;
+
+    // Maybe entering a flattened child from the outside? In this case solve the tie using the regular scoring rules.
+    if (result != &g.NavMoveResultOther && g.NavMoveResultOther.ID != 0 && g.NavMoveResultOther.Window->ParentWindow == g.NavWindow)
+        if ((g.NavMoveResultOther.DistBox < result->DistBox) || (g.NavMoveResultOther.DistBox == result->DistBox && g.NavMoveResultOther.DistCenter < result->DistCenter))
+            result = &g.NavMoveResultOther;
+    IM_ASSERT(g.NavWindow && result->Window);
+
+    // Scroll to keep newly navigated item fully into view.
+    if (g.NavLayer == 0)
+    {
+        ImRect rect_abs = ImRect(result->RectRel.Min + result->Window->Pos, result->RectRel.Max + result->Window->Pos);
+        NavScrollToBringItemIntoView(result->Window, rect_abs);
+
+        // Estimate upcoming scroll so we can offset our result position so mouse position can be applied immediately after in NavUpdate()
+        ImVec2 next_scroll = CalcNextScrollFromScrollTargetAndClamp(result->Window, false);
+        ImVec2 delta_scroll = result->Window->Scroll - next_scroll;
+        result->RectRel.Translate(delta_scroll);
+
+        // Also scroll parent window to keep us into view if necessary (we could/should technically recurse back the whole the parent hierarchy).
+        if (result->Window->Flags & ImGuiWindowFlags_ChildWindow)
+            NavScrollToBringItemIntoView(result->Window->ParentWindow, ImRect(rect_abs.Min + delta_scroll, rect_abs.Max + delta_scroll));
+    }
+
+    // Apply result from previous frame navigation directional move request
+    ClearActiveID();
+    g.NavWindow = result->Window;
+    SetNavIDWithRectRel(result->ID, g.NavLayer, result->RectRel);
+    g.NavJustMovedToId = result->ID;
+    g.NavMoveFromClampedRefRect = false;
+}
+
+static float ImGui::NavUpdatePageUpPageDown(int allowed_dir_flags)
+{
+    ImGuiContext& g = *GImGui;
+    if (g.NavMoveDir == ImGuiDir_None && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs) && !g.NavWindowingTarget && g.NavLayer == 0)
+    {
+        ImGuiWindow* window = g.NavWindow;
+        bool page_up_held = IsKeyDown(g.IO.KeyMap[ImGuiKey_PageUp]) && (allowed_dir_flags & (1 << ImGuiDir_Up));
+        bool page_down_held = IsKeyDown(g.IO.KeyMap[ImGuiKey_PageDown]) && (allowed_dir_flags & (1 << ImGuiDir_Down));
+        if ((page_up_held && !page_down_held) || (page_down_held && !page_up_held))
+        {
+            if (window->DC.NavLayerActiveMask == 0x00 && window->DC.NavHasScroll)
+            {
+                // Fallback manual-scroll when window has no navigable item
+                if (IsKeyPressed(g.IO.KeyMap[ImGuiKey_PageUp], true))
+                    SetWindowScrollY(window, window->Scroll.y - window->InnerClipRect.GetHeight());
+                else if (IsKeyPressed(g.IO.KeyMap[ImGuiKey_PageDown], true))
+                    SetWindowScrollY(window, window->Scroll.y + window->InnerClipRect.GetHeight());
+            }
+            else
+            {
+                const ImRect& nav_rect_rel = window->NavRectRel[g.NavLayer];
+                const float page_offset_y = ImMax(0.0f, window->InnerClipRect.GetHeight() - window->CalcFontSize() * 1.0f + nav_rect_rel.GetHeight());
+                float nav_scoring_rect_offset_y = 0.0f;
+                if (IsKeyPressed(g.IO.KeyMap[ImGuiKey_PageUp], true))
+                {
+                    nav_scoring_rect_offset_y = -page_offset_y;
+                    g.NavMoveDir = ImGuiDir_Down; // Because our scoring rect is offset, we intentionally request the opposite direction (so we can always land on the last item)
+                    g.NavMoveClipDir = ImGuiDir_Up;
+                    g.NavMoveRequestFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_AlsoScoreVisibleSet;
+                }
+                else if (IsKeyPressed(g.IO.KeyMap[ImGuiKey_PageDown], true))
+                {
+                    nav_scoring_rect_offset_y = +page_offset_y;
+                    g.NavMoveDir = ImGuiDir_Up; // Because our scoring rect is offset, we intentionally request the opposite direction (so we can always land on the last item)
+                    g.NavMoveClipDir = ImGuiDir_Down;
+                    g.NavMoveRequestFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_AlsoScoreVisibleSet;
+                }
+                return nav_scoring_rect_offset_y;
+            }
+        }
+    }
+    return 0.0f;
+}
+
+static int FindWindowIndex(ImGuiWindow* window) // FIXME-OPT O(N)
+{
+    ImGuiContext& g = *GImGui;
+    for (int i = g.Windows.Size-1; i >= 0; i--)
+        if (g.Windows[i] == window)
+            return i;
+    return -1;
+}
+
+static ImGuiWindow* FindWindowNavFocusable(int i_start, int i_stop, int dir) // FIXME-OPT O(N)
+{
+    ImGuiContext& g = *GImGui;
+    for (int i = i_start; i >= 0 && i < g.Windows.Size && i != i_stop; i += dir)
+        if (ImGui::IsWindowNavFocusable(g.Windows[i]))
+            return g.Windows[i];
+    return NULL;
+}
+
+static void NavUpdateWindowingHighlightWindow(int focus_change_dir)
+{
+    ImGuiContext& g = *GImGui;
+    IM_ASSERT(g.NavWindowingTarget);
+    if (g.NavWindowingTarget->Flags & ImGuiWindowFlags_Modal)
+        return;
+
+    const int i_current = FindWindowIndex(g.NavWindowingTarget);
+    ImGuiWindow* window_target = FindWindowNavFocusable(i_current + focus_change_dir, -INT_MAX, focus_change_dir);
+    if (!window_target)
+        window_target = FindWindowNavFocusable((focus_change_dir < 0) ? (g.Windows.Size - 1) : 0, i_current, focus_change_dir);
+    if (window_target) // Don't reset windowing target if there's a single window in the list
+        g.NavWindowingTarget = g.NavWindowingTargetAnim = window_target;
+    g.NavWindowingToggleLayer = false;
+}
+
+// Window management mode (hold to: change focus/move/resize, tap to: toggle menu layer)
+static void ImGui::NavUpdateWindowing()
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiWindow* apply_focus_window = NULL;
+    bool apply_toggle_layer = false;
+
+    ImGuiWindow* modal_window = GetFrontMostPopupModal();
+    if (modal_window != NULL)
+    {
+        g.NavWindowingTarget = NULL;
+        return;
+    }
+
+    // Fade out
+    if (g.NavWindowingTargetAnim && g.NavWindowingTarget == NULL)
+    {
+        g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha - g.IO.DeltaTime * 10.0f, 0.0f);
+        if (g.DimBgRatio <= 0.0f && g.NavWindowingHighlightAlpha <= 0.0f)
+            g.NavWindowingTargetAnim = NULL;
+    }
+
+    // Start CTRL-TAB or Square+L/R window selection
+    bool start_windowing_with_gamepad = !g.NavWindowingTarget && IsNavInputPressed(ImGuiNavInput_Menu, ImGuiInputReadMode_Pressed);
+    bool start_windowing_with_keyboard = !g.NavWindowingTarget && g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_Tab) && (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard);
+    if (start_windowing_with_gamepad || start_windowing_with_keyboard)
+        if (ImGuiWindow* window = g.NavWindow ? g.NavWindow : FindWindowNavFocusable(g.Windows.Size - 1, -INT_MAX, -1))
+        {
+            g.NavWindowingTarget = g.NavWindowingTargetAnim = window;
+            g.NavWindowingTimer = g.NavWindowingHighlightAlpha = 0.0f;
+            g.NavWindowingToggleLayer = start_windowing_with_keyboard ? false : true;
+            g.NavInputSource = start_windowing_with_keyboard ? ImGuiInputSource_NavKeyboard : ImGuiInputSource_NavGamepad;
+        }
+
+    // Gamepad update
+    g.NavWindowingTimer += g.IO.DeltaTime;
+    if (g.NavWindowingTarget && g.NavInputSource == ImGuiInputSource_NavGamepad)
+    {
+        // Highlight only appears after a brief time holding the button, so that a fast tap on PadMenu (to toggle NavLayer) doesn't add visual noise
+        g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha, ImSaturate((g.NavWindowingTimer - NAV_WINDOWING_HIGHLIGHT_DELAY) / 0.05f));
+
+        // Select window to focus
+        const int focus_change_dir = (int)IsNavInputPressed(ImGuiNavInput_FocusPrev, ImGuiInputReadMode_RepeatSlow) - (int)IsNavInputPressed(ImGuiNavInput_FocusNext, ImGuiInputReadMode_RepeatSlow);
+        if (focus_change_dir != 0)
+        {
+            NavUpdateWindowingHighlightWindow(focus_change_dir);
+            g.NavWindowingHighlightAlpha = 1.0f;
+        }
+
+        // Single press toggles NavLayer, long press with L/R apply actual focus on release (until then the window was merely rendered front-most)
+        if (!IsNavInputDown(ImGuiNavInput_Menu))
+        {
+            g.NavWindowingToggleLayer &= (g.NavWindowingHighlightAlpha < 1.0f); // Once button was held long enough we don't consider it a tap-to-toggle-layer press anymore.
+            if (g.NavWindowingToggleLayer && g.NavWindow)
+                apply_toggle_layer = true;
+            else if (!g.NavWindowingToggleLayer)
+                apply_focus_window = g.NavWindowingTarget;
+            g.NavWindowingTarget = NULL;
+        }
+    }
+
+    // Keyboard: Focus
+    if (g.NavWindowingTarget && g.NavInputSource == ImGuiInputSource_NavKeyboard)
+    {
+        // Visuals only appears after a brief time after pressing TAB the first time, so that a fast CTRL+TAB doesn't add visual noise
+        g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha, ImSaturate((g.NavWindowingTimer - NAV_WINDOWING_HIGHLIGHT_DELAY) / 0.05f)); // 1.0f
+        if (IsKeyPressedMap(ImGuiKey_Tab, true))
+            NavUpdateWindowingHighlightWindow(g.IO.KeyShift ? +1 : -1);
+        if (!g.IO.KeyCtrl)
+            apply_focus_window = g.NavWindowingTarget;
+    }
+
+    // Keyboard: Press and Release ALT to toggle menu layer
+    // FIXME: We lack an explicit IO variable for "is the imgui window focused", so compare mouse validity to detect the common case of back-end clearing releases all keys on ALT-TAB
+    if ((g.ActiveId == 0 || g.ActiveIdAllowOverlap) && IsNavInputPressed(ImGuiNavInput_KeyMenu_, ImGuiInputReadMode_Released))
+        if (IsMousePosValid(&g.IO.MousePos) == IsMousePosValid(&g.IO.MousePosPrev))
+            apply_toggle_layer = true;
+
+    // Move window
+    if (g.NavWindowingTarget && !(g.NavWindowingTarget->Flags & ImGuiWindowFlags_NoMove))
+    {
+        ImVec2 move_delta;
+        if (g.NavInputSource == ImGuiInputSource_NavKeyboard && !g.IO.KeyShift)
+            move_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard, ImGuiInputReadMode_Down);
+        if (g.NavInputSource == ImGuiInputSource_NavGamepad)
+            move_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_PadLStick, ImGuiInputReadMode_Down);
+        if (move_delta.x != 0.0f || move_delta.y != 0.0f)
+        {
+            const float NAV_MOVE_SPEED = 800.0f;
+            const float move_speed = ImFloor(NAV_MOVE_SPEED * g.IO.DeltaTime * ImMin(g.IO.DisplayFramebufferScale.x, g.IO.DisplayFramebufferScale.y)); // FIXME: Doesn't code variable framerate very well
+            g.NavWindowingTarget->RootWindow->Pos += move_delta * move_speed;
+            g.NavDisableMouseHover = true;
+            MarkIniSettingsDirty(g.NavWindowingTarget);
+        }
+    }
+
+    // Apply final focus
+    if (apply_focus_window && (g.NavWindow == NULL || apply_focus_window != g.NavWindow->RootWindow))
+    {
+        g.NavDisableHighlight = false;
+        g.NavDisableMouseHover = true;
+        apply_focus_window = NavRestoreLastChildNavWindow(apply_focus_window);
+        ClosePopupsOverWindow(apply_focus_window);
+        FocusWindow(apply_focus_window);
+        if (apply_focus_window->NavLastIds[0] == 0)
+            NavInitWindow(apply_focus_window, false);
+
+        // If the window only has a menu layer, select it directly
+        if (apply_focus_window->DC.NavLayerActiveMask == (1 << 1))
+            g.NavLayer = 1;
+    }
+    if (apply_focus_window)
+        g.NavWindowingTarget = NULL;
+
+    // Apply menu/layer toggle
+    if (apply_toggle_layer && g.NavWindow)
+    {
+        // Move to parent menu if necessary
+        ImGuiWindow* new_nav_window = g.NavWindow;
+        while ((new_nav_window->DC.NavLayerActiveMask & (1 << 1)) == 0 && (new_nav_window->Flags & ImGuiWindowFlags_ChildWindow) != 0 && (new_nav_window->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu)) == 0)
+            new_nav_window = new_nav_window->ParentWindow;
+        if (new_nav_window != g.NavWindow)
+        {
+            ImGuiWindow* old_nav_window = g.NavWindow;
+            FocusWindow(new_nav_window);
+            new_nav_window->NavLastChildNavWindow = old_nav_window;
+        }
+        g.NavDisableHighlight = false;
+        g.NavDisableMouseHover = true;
+        NavRestoreLayer((g.NavWindow->DC.NavLayerActiveMask & (1 << 1)) ? (g.NavLayer ^ 1) : 0);
+    }
+}
+
+// Window has already passed the IsWindowNavFocusable()
+static const char* GetFallbackWindowNameForWindowingList(ImGuiWindow* window)
+{
+    if (window->Flags & ImGuiWindowFlags_Popup)
+        return "(Popup)";
+    if ((window->Flags & ImGuiWindowFlags_MenuBar) && strcmp(window->Name, "##MainMenuBar") == 0)
+        return "(Main menu bar)";
+    return "(Untitled)";
+}
+
+// Overlay displayed when using CTRL+TAB. Called by EndFrame().
+void ImGui::NavUpdateWindowingList()
+{
+    ImGuiContext& g = *GImGui;
+    IM_ASSERT(g.NavWindowingTarget != NULL);
+
+    if (g.NavWindowingTimer < NAV_WINDOWING_LIST_APPEAR_DELAY)
+        return;
+
+    if (g.NavWindowingList == NULL)
+        g.NavWindowingList = FindWindowByName("###NavWindowingList");
+    SetNextWindowSizeConstraints(ImVec2(g.IO.DisplaySize.x * 0.20f, g.IO.DisplaySize.y * 0.20f), ImVec2(FLT_MAX, FLT_MAX));
+    SetNextWindowPos(g.IO.DisplaySize * 0.5f, ImGuiCond_Always, ImVec2(0.5f, 0.5f));
+    PushStyleVar(ImGuiStyleVar_WindowPadding, g.Style.WindowPadding * 2.0f);
+    Begin("###NavWindowingList", NULL, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings);
+    for (int n = g.Windows.Size - 1; n >= 0; n--)
+    {
+        ImGuiWindow* window = g.Windows[n];
+        if (!IsWindowNavFocusable(window))
+            continue;
+        const char* label = window->Name;
+        if (label == FindRenderedTextEnd(label))
+            label = GetFallbackWindowNameForWindowingList(window);
+        Selectable(label, g.NavWindowingTarget == window);
+    }
+    End();
+    PopStyleVar();
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] COLUMNS
+// In the current version, Columns are very weak. Needs to be replaced with a more full-featured system.
+//-----------------------------------------------------------------------------
+
+void ImGui::NextColumn()
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    if (window->SkipItems || window->DC.ColumnsSet == NULL)
+        return;
+
+    ImGuiContext& g = *GImGui;
+    PopItemWidth();
+    PopClipRect();
+
+    ImGuiColumnsSet* columns = window->DC.ColumnsSet;
+    columns->LineMaxY = ImMax(columns->LineMaxY, window->DC.CursorPos.y);
+    if (++columns->Current < columns->Count)
+    {
+        // Columns 1+ cancel out IndentX
+        window->DC.ColumnsOffset.x = GetColumnOffset(columns->Current) - window->DC.Indent.x + g.Style.ItemSpacing.x;
+        window->DrawList->ChannelsSetCurrent(columns->Current);
+    }
+    else
+    {
+        window->DC.ColumnsOffset.x = 0.0f;
+        window->DrawList->ChannelsSetCurrent(0);
+        columns->Current = 0;
+        columns->LineMinY = columns->LineMaxY;
+    }
+    window->DC.CursorPos.x = (float)(int)(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x);
+    window->DC.CursorPos.y = columns->LineMinY;
+    window->DC.CurrentLineSize = ImVec2(0.0f, 0.0f);
+    window->DC.CurrentLineTextBaseOffset = 0.0f;
+
+    PushColumnClipRect();
+    PushItemWidth(GetColumnWidth() * 0.65f);  // FIXME: Move on columns setup
+}
+
+int ImGui::GetColumnIndex()
+{
+    ImGuiWindow* window = GetCurrentWindowRead();
+    return window->DC.ColumnsSet ? window->DC.ColumnsSet->Current : 0;
+}
+
+int ImGui::GetColumnsCount()
+{
+    ImGuiWindow* window = GetCurrentWindowRead();
+    return window->DC.ColumnsSet ? window->DC.ColumnsSet->Count : 1;
+}
+
+static float OffsetNormToPixels(const ImGuiColumnsSet* columns, float offset_norm)
+{
+    return offset_norm * (columns->MaxX - columns->MinX);
+}
+
+static float PixelsToOffsetNorm(const ImGuiColumnsSet* columns, float offset)
+{
+    return offset / (columns->MaxX - columns->MinX);
+}
+
+static inline float GetColumnsRectHalfWidth() { return 4.0f; }
+
+static float GetDraggedColumnOffset(ImGuiColumnsSet* columns, int column_index)
+{
+    // Active (dragged) column always follow mouse. The reason we need this is that dragging a column to the right edge of an auto-resizing
+    // window creates a feedback loop because we store normalized positions. So while dragging we enforce absolute positioning.
+    ImGuiContext& g = *GImGui;
+    ImGuiWindow* window = g.CurrentWindow;
+    IM_ASSERT(column_index > 0); // We are not supposed to drag column 0.
+    IM_ASSERT(g.ActiveId == columns->ID + ImGuiID(column_index));
+
+    float x = g.IO.MousePos.x - g.ActiveIdClickOffset.x + GetColumnsRectHalfWidth() - window->Pos.x;
+    x = ImMax(x, ImGui::GetColumnOffset(column_index - 1) + g.Style.ColumnsMinSpacing);
+    if ((columns->Flags & ImGuiColumnsFlags_NoPreserveWidths))
+        x = ImMin(x, ImGui::GetColumnOffset(column_index + 1) - g.Style.ColumnsMinSpacing);
+
+    return x;
+}
+
+float ImGui::GetColumnOffset(int column_index)
+{
+    ImGuiWindow* window = GetCurrentWindowRead();
+    ImGuiColumnsSet* columns = window->DC.ColumnsSet;
+    IM_ASSERT(columns != NULL);
+
+    if (column_index < 0)
+        column_index = columns->Current;
+    IM_ASSERT(column_index < columns->Columns.Size);
+
+    const float t = columns->Columns[column_index].OffsetNorm;
+    const float x_offset = ImLerp(columns->MinX, columns->MaxX, t);
+    return x_offset;
+}
+
+static float GetColumnWidthEx(ImGuiColumnsSet* columns, int column_index, bool before_resize = false)
+{
+    if (column_index < 0)
+        column_index = columns->Current;
+
+    float offset_norm;
+    if (before_resize)
+        offset_norm = columns->Columns[column_index + 1].OffsetNormBeforeResize - columns->Columns[column_index].OffsetNormBeforeResize;
+    else
+        offset_norm = columns->Columns[column_index + 1].OffsetNorm - columns->Columns[column_index].OffsetNorm;
+    return OffsetNormToPixels(columns, offset_norm);
+}
+
+float ImGui::GetColumnWidth(int column_index)
+{
+    ImGuiWindow* window = GetCurrentWindowRead();
+    ImGuiColumnsSet* columns = window->DC.ColumnsSet;
+    IM_ASSERT(columns != NULL);
+
+    if (column_index < 0)
+        column_index = columns->Current;
+    return OffsetNormToPixels(columns, columns->Columns[column_index + 1].OffsetNorm - columns->Columns[column_index].OffsetNorm);
+}
+
+void ImGui::SetColumnOffset(int column_index, float offset)
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiWindow* window = g.CurrentWindow;
+    ImGuiColumnsSet* columns = window->DC.ColumnsSet;
+    IM_ASSERT(columns != NULL);
+
+    if (column_index < 0)
+        column_index = columns->Current;
+    IM_ASSERT(column_index < columns->Columns.Size);
+
+    const bool preserve_width = !(columns->Flags & ImGuiColumnsFlags_NoPreserveWidths) && (column_index < columns->Count-1);
+    const float width = preserve_width ? GetColumnWidthEx(columns, column_index, columns->IsBeingResized) : 0.0f;
+
+    if (!(columns->Flags & ImGuiColumnsFlags_NoForceWithinWindow))
+        offset = ImMin(offset, columns->MaxX - g.Style.ColumnsMinSpacing * (columns->Count - column_index));
+    columns->Columns[column_index].OffsetNorm = PixelsToOffsetNorm(columns, offset - columns->MinX);
+
+    if (preserve_width)
+        SetColumnOffset(column_index + 1, offset + ImMax(g.Style.ColumnsMinSpacing, width));
+}
+
+void ImGui::SetColumnWidth(int column_index, float width)
+{
+    ImGuiWindow* window = GetCurrentWindowRead();
+    ImGuiColumnsSet* columns = window->DC.ColumnsSet;
+    IM_ASSERT(columns != NULL);
+
+    if (column_index < 0)
+        column_index = columns->Current;
+    SetColumnOffset(column_index + 1, GetColumnOffset(column_index) + width);
+}
+
+void ImGui::PushColumnClipRect(int column_index)
+{
+    ImGuiWindow* window = GetCurrentWindowRead();
+    ImGuiColumnsSet* columns = window->DC.ColumnsSet;
+    if (column_index < 0)
+        column_index = columns->Current;
+
+    PushClipRect(columns->Columns[column_index].ClipRect.Min, columns->Columns[column_index].ClipRect.Max, false);
+}
+
+static ImGuiColumnsSet* FindOrAddColumnsSet(ImGuiWindow* window, ImGuiID id)
+{
+    for (int n = 0; n < window->ColumnsStorage.Size; n++)
+        if (window->ColumnsStorage[n].ID == id)
+            return &window->ColumnsStorage[n];
+
+    window->ColumnsStorage.push_back(ImGuiColumnsSet());
+    ImGuiColumnsSet* columns = &window->ColumnsStorage.back();
+    columns->ID = id;
+    return columns;
+}
+
+void ImGui::BeginColumns(const char* str_id, int columns_count, ImGuiColumnsFlags flags)
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiWindow* window = GetCurrentWindow();
+
+    IM_ASSERT(columns_count > 1);
+    IM_ASSERT(window->DC.ColumnsSet == NULL); // Nested columns are currently not supported
+
+    // Differentiate column ID with an arbitrary prefix for cases where users name their columns set the same as another widget.
+    // In addition, when an identifier isn't explicitly provided we include the number of columns in the hash to make it uniquer.
+    PushID(0x11223347 + (str_id ? 0 : columns_count));
+    ImGuiID id = window->GetID(str_id ? str_id : "columns");
+    PopID();
+
+    // Acquire storage for the columns set
+    ImGuiColumnsSet* columns = FindOrAddColumnsSet(window, id);
+    IM_ASSERT(columns->ID == id);
+    columns->Current = 0;
+    columns->Count = columns_count;
+    columns->Flags = flags;
+    window->DC.ColumnsSet = columns;
+
+    // Set state for first column
+    const float content_region_width = (window->SizeContentsExplicit.x != 0.0f) ? (window->SizeContentsExplicit.x) : (window->InnerClipRect.Max.x - window->Pos.x);
+    columns->MinX = window->DC.Indent.x - g.Style.ItemSpacing.x; // Lock our horizontal range
+    columns->MaxX = ImMax(content_region_width - window->Scroll.x, columns->MinX + 1.0f);
+    columns->StartPosY = window->DC.CursorPos.y;
+    columns->StartMaxPosX = window->DC.CursorMaxPos.x;
+    columns->LineMinY = columns->LineMaxY = window->DC.CursorPos.y;
+    window->DC.ColumnsOffset.x = 0.0f;
+    window->DC.CursorPos.x = (float)(int)(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x);
+
+    // Clear data if columns count changed
+    if (columns->Columns.Size != 0 && columns->Columns.Size != columns_count + 1)
+        columns->Columns.resize(0);
+
+    // Initialize defaults
+    columns->IsFirstFrame = (columns->Columns.Size == 0);
+    if (columns->Columns.Size == 0)
+    {
+        columns->Columns.reserve(columns_count + 1);
+        for (int n = 0; n < columns_count + 1; n++)
+        {
+            ImGuiColumnData column;
+            column.OffsetNorm = n / (float)columns_count;
+            columns->Columns.push_back(column);
+        }
+    }
+
+    for (int n = 0; n < columns_count; n++)
+    {
+        // Compute clipping rectangle
+        ImGuiColumnData* column = &columns->Columns[n];
+        float clip_x1 = ImFloor(0.5f + window->Pos.x + GetColumnOffset(n) - 1.0f);
+        float clip_x2 = ImFloor(0.5f + window->Pos.x + GetColumnOffset(n + 1) - 1.0f);
+        column->ClipRect = ImRect(clip_x1, -FLT_MAX, clip_x2, +FLT_MAX);
+        column->ClipRect.ClipWith(window->ClipRect);
+    }
+
+    window->DrawList->ChannelsSplit(columns->Count);
+    PushColumnClipRect();
+    PushItemWidth(GetColumnWidth() * 0.65f);
+}
+
+void ImGui::EndColumns()
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiWindow* window = GetCurrentWindow();
+    ImGuiColumnsSet* columns = window->DC.ColumnsSet;
+    IM_ASSERT(columns != NULL);
+
+    PopItemWidth();
+    PopClipRect();
+    window->DrawList->ChannelsMerge();
+
+    columns->LineMaxY = ImMax(columns->LineMaxY, window->DC.CursorPos.y);
+    window->DC.CursorPos.y = columns->LineMaxY;
+    if (!(columns->Flags & ImGuiColumnsFlags_GrowParentContentsSize))
+        window->DC.CursorMaxPos.x = columns->StartMaxPosX;  // Restore cursor max pos, as columns don't grow parent
+
+    // Draw columns borders and handle resize
+    bool is_being_resized = false;
+    if (!(columns->Flags & ImGuiColumnsFlags_NoBorder) && !window->SkipItems)
+    {
+        const float y1 = columns->StartPosY;
+        const float y2 = window->DC.CursorPos.y;
+        int dragging_column = -1;
+        for (int n = 1; n < columns->Count; n++)
+        {
+            float x = window->Pos.x + GetColumnOffset(n);
+            const ImGuiID column_id = columns->ID + ImGuiID(n);
+            const float column_hw = GetColumnsRectHalfWidth(); // Half-width for interaction
+            const ImRect column_rect(ImVec2(x - column_hw, y1), ImVec2(x + column_hw, y2));
+            KeepAliveID(column_id);
+            if (IsClippedEx(column_rect, column_id, false))
+                continue;
+
+            bool hovered = false, held = false;
+            if (!(columns->Flags & ImGuiColumnsFlags_NoResize))
+            {
+                ButtonBehavior(column_rect, column_id, &hovered, &held);
+                if (hovered || held)
+                    g.MouseCursor = ImGuiMouseCursor_ResizeEW;
+                if (held && !(columns->Columns[n].Flags & ImGuiColumnsFlags_NoResize))
+                    dragging_column = n;
+            }
+
+            // Draw column (we clip the Y boundaries CPU side because very long triangles are mishandled by some GPU drivers.)
+            const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : hovered ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator);
+            const float xi = (float)(int)x;
+            window->DrawList->AddLine(ImVec2(xi, ImMax(y1 + 1.0f, window->ClipRect.Min.y)), ImVec2(xi, ImMin(y2, window->ClipRect.Max.y)), col);
+        }
+
+        // Apply dragging after drawing the column lines, so our rendered lines are in sync with how items were displayed during the frame.
+        if (dragging_column != -1)
+        {
+            if (!columns->IsBeingResized)
+                for (int n = 0; n < columns->Count + 1; n++)
+                    columns->Columns[n].OffsetNormBeforeResize = columns->Columns[n].OffsetNorm;
+            columns->IsBeingResized = is_being_resized = true;
+            float x = GetDraggedColumnOffset(columns, dragging_column);
+            SetColumnOffset(dragging_column, x);
+        }
+    }
+    columns->IsBeingResized = is_being_resized;
+
+    window->DC.ColumnsSet = NULL;
+    window->DC.ColumnsOffset.x = 0.0f;
+    window->DC.CursorPos.x = (float)(int)(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x);
+}
+
+// [2018-03: This is currently the only public API, while we are working on making BeginColumns/EndColumns user-facing]
+void ImGui::Columns(int columns_count, const char* id, bool border)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    IM_ASSERT(columns_count >= 1);
+
+    ImGuiColumnsFlags flags = (border ? 0 : ImGuiColumnsFlags_NoBorder);
+    //flags |= ImGuiColumnsFlags_NoPreserveWidths; // NB: Legacy behavior
+    if (window->DC.ColumnsSet != NULL && window->DC.ColumnsSet->Count == columns_count && window->DC.ColumnsSet->Flags == flags)
+        return;
+
+    if (window->DC.ColumnsSet != NULL)
+        EndColumns();
+
+    if (columns_count != 1)
+        BeginColumns(id, columns_count, flags);
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] DRAG AND DROP
+//-----------------------------------------------------------------------------
+
+void ImGui::ClearDragDrop()
+{
+    ImGuiContext& g = *GImGui;
+    g.DragDropActive = false;
+    g.DragDropPayload.Clear();
+    g.DragDropAcceptFlags = 0;
+    g.DragDropAcceptIdCurr = g.DragDropAcceptIdPrev = 0;
+    g.DragDropAcceptIdCurrRectSurface = FLT_MAX;
+    g.DragDropAcceptFrameCount = -1;
+
+    g.DragDropPayloadBufHeap.clear();
+    memset(&g.DragDropPayloadBufLocal, 0, sizeof(g.DragDropPayloadBufLocal));
+}
+
+// Call when current ID is active.
+// When this returns true you need to: a) call SetDragDropPayload() exactly once, b) you may render the payload visual/description, c) call EndDragDropSource()
+bool ImGui::BeginDragDropSource(ImGuiDragDropFlags flags)
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiWindow* window = g.CurrentWindow;
+
+    bool source_drag_active = false;
+    ImGuiID source_id = 0;
+    ImGuiID source_parent_id = 0;
+    int mouse_button = 0;
+    if (!(flags & ImGuiDragDropFlags_SourceExtern))
+    {
+        source_id = window->DC.LastItemId;
+        if (source_id != 0 && g.ActiveId != source_id) // Early out for most common case
+            return false;
+        if (g.IO.MouseDown[mouse_button] == false)
+            return false;
+
+        if (source_id == 0)
+        {
+            // If you want to use BeginDragDropSource() on an item with no unique identifier for interaction, such as Text() or Image(), you need to:
+            // A) Read the explanation below, B) Use the ImGuiDragDropFlags_SourceAllowNullID flag, C) Swallow your programmer pride.
+            if (!(flags & ImGuiDragDropFlags_SourceAllowNullID))
+            {
+                IM_ASSERT(0);
+                return false;
+            }
+
+            // Magic fallback (=somehow reprehensible) to handle items with no assigned ID, e.g. Text(), Image()
+            // We build a throwaway ID based on current ID stack + relative AABB of items in window.
+            // THE IDENTIFIER WON'T SURVIVE ANY REPOSITIONING OF THE WIDGET, so if your widget moves your dragging operation will be canceled.
+            // We don't need to maintain/call ClearActiveID() as releasing the button will early out this function and trigger !ActiveIdIsAlive.
+            bool is_hovered = (window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect) != 0;
+            if (!is_hovered && (g.ActiveId == 0 || g.ActiveIdWindow != window))
+                return false;
+            source_id = window->DC.LastItemId = window->GetIDFromRectangle(window->DC.LastItemRect);
+            if (is_hovered)
+                SetHoveredID(source_id);
+            if (is_hovered && g.IO.MouseClicked[mouse_button])
+            {
+                SetActiveID(source_id, window);
+                FocusWindow(window);
+            }
+            if (g.ActiveId == source_id) // Allow the underlying widget to display/return hovered during the mouse release frame, else we would get a flicker.
+                g.ActiveIdAllowOverlap = is_hovered;
+        }
+        else
+        {
+            g.ActiveIdAllowOverlap = false;
+        }
+        if (g.ActiveId != source_id)
+            return false;
+        source_parent_id = window->IDStack.back();
+        source_drag_active = IsMouseDragging(mouse_button);
+    }
+    else
+    {
+        window = NULL;
+        source_id = ImHash("#SourceExtern", 0);
+        source_drag_active = true;
+    }
+
+    if (source_drag_active)
+    {
+        if (!g.DragDropActive)
+        {
+            IM_ASSERT(source_id != 0);
+            ClearDragDrop();
+            ImGuiPayload& payload = g.DragDropPayload;
+            payload.SourceId = source_id;
+            payload.SourceParentId = source_parent_id;
+            g.DragDropActive = true;
+            g.DragDropSourceFlags = flags;
+            g.DragDropMouseButton = mouse_button;
+        }
+        g.DragDropSourceFrameCount = g.FrameCount;
+        g.DragDropWithinSourceOrTarget = true;
+
+        if (!(flags & ImGuiDragDropFlags_SourceNoPreviewTooltip))
+        {
+            // Target can request the Source to not display its tooltip (we use a dedicated flag to make this request explicit)
+            // We unfortunately can't just modify the source flags and skip the call to BeginTooltip, as caller may be emitting contents. 
+            BeginTooltip();
+            if (g.DragDropActive && g.DragDropAcceptIdPrev && (g.DragDropAcceptFlags & ImGuiDragDropFlags_AcceptNoPreviewTooltip))
+            {
+                ImGuiWindow* tooltip_window = g.CurrentWindow;
+                tooltip_window->SkipItems = true;
+                tooltip_window->HiddenFramesRegular = 1;
+            }
+        }
+
+        if (!(flags & ImGuiDragDropFlags_SourceNoDisableHover) && !(flags & ImGuiDragDropFlags_SourceExtern))
+            window->DC.LastItemStatusFlags &= ~ImGuiItemStatusFlags_HoveredRect;
+
+        return true;
+    }
+    return false;
+}
+
+void ImGui::EndDragDropSource()
+{
+    ImGuiContext& g = *GImGui;
+    IM_ASSERT(g.DragDropActive);
+    IM_ASSERT(g.DragDropWithinSourceOrTarget && "Not after a BeginDragDropSource()?");
+
+    if (!(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoPreviewTooltip))
+        EndTooltip();
+
+    // Discard the drag if have not called SetDragDropPayload()
+    if (g.DragDropPayload.DataFrameCount == -1)
+        ClearDragDrop();
+    g.DragDropWithinSourceOrTarget = false;
+}
+
+// Use 'cond' to choose to submit payload on drag start or every frame
+bool ImGui::SetDragDropPayload(const char* type, const void* data, size_t data_size, ImGuiCond cond)
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiPayload& payload = g.DragDropPayload;
+    if (cond == 0)
+        cond = ImGuiCond_Always;
+
+    IM_ASSERT(type != NULL);
+    IM_ASSERT(strlen(type) < IM_ARRAYSIZE(payload.DataType) && "Payload type can be at most 32 characters long");
+    IM_ASSERT((data != NULL && data_size > 0) || (data == NULL && data_size == 0));
+    IM_ASSERT(cond == ImGuiCond_Always || cond == ImGuiCond_Once);
+    IM_ASSERT(payload.SourceId != 0);                               // Not called between BeginDragDropSource() and EndDragDropSource()
+
+    if (cond == ImGuiCond_Always || payload.DataFrameCount == -1)
+    {
+        // Copy payload
+        ImStrncpy(payload.DataType, type, IM_ARRAYSIZE(payload.DataType));
+        g.DragDropPayloadBufHeap.resize(0);
+        if (data_size > sizeof(g.DragDropPayloadBufLocal))
+        {
+            // Store in heap
+            g.DragDropPayloadBufHeap.resize((int)data_size);
+            payload.Data = g.DragDropPayloadBufHeap.Data;
+            memcpy(payload.Data, data, data_size);
+        }
+        else if (data_size > 0)
+        {
+            // Store locally
+            memset(&g.DragDropPayloadBufLocal, 0, sizeof(g.DragDropPayloadBufLocal));
+            payload.Data = g.DragDropPayloadBufLocal;
+            memcpy(payload.Data, data, data_size);
+        }
+        else
+        {
+            payload.Data = NULL;
+        }
+        payload.DataSize = (int)data_size;
+    }
+    payload.DataFrameCount = g.FrameCount;
+
+    return (g.DragDropAcceptFrameCount == g.FrameCount) || (g.DragDropAcceptFrameCount == g.FrameCount - 1);
+}
+
+bool ImGui::BeginDragDropTargetCustom(const ImRect& bb, ImGuiID id)
+{
+    ImGuiContext& g = *GImGui;
+    if (!g.DragDropActive)
+        return false;
+
+    ImGuiWindow* window = g.CurrentWindow;
+    if (g.HoveredWindow == NULL || window->RootWindow != g.HoveredWindow->RootWindow)
+        return false;
+    IM_ASSERT(id != 0);
+    if (!IsMouseHoveringRect(bb.Min, bb.Max) || (id == g.DragDropPayload.SourceId))
+        return false;
+    if (window->SkipItems)
+        return false;
+
+    IM_ASSERT(g.DragDropWithinSourceOrTarget == false);
+    g.DragDropTargetRect = bb;
+    g.DragDropTargetId = id;
+    g.DragDropWithinSourceOrTarget = true;
+    return true;
+}
+
+// We don't use BeginDragDropTargetCustom() and duplicate its code because:
+// 1) we use LastItemRectHoveredRect which handles items that pushes a temporarily clip rectangle in their code. Calling BeginDragDropTargetCustom(LastItemRect) would not handle them.
+// 2) and it's faster. as this code may be very frequently called, we want to early out as fast as we can.
+// Also note how the HoveredWindow test is positioned differently in both functions (in both functions we optimize for the cheapest early out case)
+bool ImGui::BeginDragDropTarget()
+{
+    ImGuiContext& g = *GImGui;
+    if (!g.DragDropActive)
+        return false;
+
+    ImGuiWindow* window = g.CurrentWindow;
+    if (!(window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect))
+        return false;
+    if (g.HoveredWindow == NULL || window->RootWindow != g.HoveredWindow->RootWindow)
+        return false;
+
+    const ImRect& display_rect = (window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HasDisplayRect) ? window->DC.LastItemDisplayRect : window->DC.LastItemRect;
+    ImGuiID id = window->DC.LastItemId;
+    if (id == 0)
+        id = window->GetIDFromRectangle(display_rect);
+    if (g.DragDropPayload.SourceId == id)
+        return false;
+
+    IM_ASSERT(g.DragDropWithinSourceOrTarget == false);
+    g.DragDropTargetRect = display_rect;
+    g.DragDropTargetId = id;
+    g.DragDropWithinSourceOrTarget = true;
+    return true;
+}
+
+bool ImGui::IsDragDropPayloadBeingAccepted()
+{
+    ImGuiContext& g = *GImGui;
+    return g.DragDropActive && g.DragDropAcceptIdPrev != 0;
+}
+
+const ImGuiPayload* ImGui::AcceptDragDropPayload(const char* type, ImGuiDragDropFlags flags)
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiWindow* window = g.CurrentWindow;
+    ImGuiPayload& payload = g.DragDropPayload;
+    IM_ASSERT(g.DragDropActive);                        // Not called between BeginDragDropTarget() and EndDragDropTarget() ?
+    IM_ASSERT(payload.DataFrameCount != -1);            // Forgot to call EndDragDropTarget() ?
+    if (type != NULL && !payload.IsDataType(type))
+        return NULL;
+
+    // Accept smallest drag target bounding box, this allows us to nest drag targets conveniently without ordering constraints.
+    // NB: We currently accept NULL id as target. However, overlapping targets requires a unique ID to function!
+    const bool was_accepted_previously = (g.DragDropAcceptIdPrev == g.DragDropTargetId);
+    ImRect r = g.DragDropTargetRect;
+    float r_surface = r.GetWidth() * r.GetHeight();
+    if (r_surface < g.DragDropAcceptIdCurrRectSurface)
+    {
+        g.DragDropAcceptFlags = flags;
+        g.DragDropAcceptIdCurr = g.DragDropTargetId;
+        g.DragDropAcceptIdCurrRectSurface = r_surface;
+    }
+
+    // Render default drop visuals
+    payload.Preview = was_accepted_previously;
+    flags |= (g.DragDropSourceFlags & ImGuiDragDropFlags_AcceptNoDrawDefaultRect); // Source can also inhibit the preview (useful for external sources that lives for 1 frame)
+    if (!(flags & ImGuiDragDropFlags_AcceptNoDrawDefaultRect) && payload.Preview)
+    {
+        // FIXME-DRAG: Settle on a proper default visuals for drop target.
+        r.Expand(3.5f);
+        bool push_clip_rect = !window->ClipRect.Contains(r);
+        if (push_clip_rect) window->DrawList->PushClipRect(r.Min-ImVec2(1,1), r.Max+ImVec2(1,1));
+        window->DrawList->AddRect(r.Min, r.Max, GetColorU32(ImGuiCol_DragDropTarget), 0.0f, ~0, 2.0f);
+        if (push_clip_rect) window->DrawList->PopClipRect();
+    }
+
+    g.DragDropAcceptFrameCount = g.FrameCount;
+    payload.Delivery = was_accepted_previously && !IsMouseDown(g.DragDropMouseButton); // For extern drag sources affecting os window focus, it's easier to just test !IsMouseDown() instead of IsMouseReleased()
+    if (!payload.Delivery && !(flags & ImGuiDragDropFlags_AcceptBeforeDelivery))
+        return NULL;
+
+    return &payload;
+}
+
+// We don't really use/need this now, but added it for the sake of consistency and because we might need it later.
+void ImGui::EndDragDropTarget()
+{
+    ImGuiContext& g = *GImGui;
+    IM_ASSERT(g.DragDropActive);
+    IM_ASSERT(g.DragDropWithinSourceOrTarget);
+    g.DragDropWithinSourceOrTarget = false;
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] LOGGING/CAPTURING
+//-----------------------------------------------------------------------------
+
+// Pass text data straight to log (without being displayed)
+void ImGui::LogText(const char* fmt, ...)
+{
+    ImGuiContext& g = *GImGui;
+    if (!g.LogEnabled)
+        return;
+
+    va_list args;
+    va_start(args, fmt);
+    if (g.LogFile)
+        vfprintf(g.LogFile, fmt, args);
+    else
+        g.LogClipboard.appendfv(fmt, args);
+    va_end(args);
+}
+
+// Internal version that takes a position to decide on newline placement and pad items according to their depth.
+// We split text into individual lines to add current tree level padding
+void ImGui::LogRenderedText(const ImVec2* ref_pos, const char* text, const char* text_end)
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiWindow* window = g.CurrentWindow;
+
+    if (!text_end)
+        text_end = FindRenderedTextEnd(text, text_end);
+
+    const bool log_new_line = ref_pos && (ref_pos->y > window->DC.LogLinePosY + 1);
+    if (ref_pos)
+        window->DC.LogLinePosY = ref_pos->y;
+
+    const char* text_remaining = text;
+    if (g.LogStartDepth > window->DC.TreeDepth)  // Re-adjust padding if we have popped out of our starting depth
+        g.LogStartDepth = window->DC.TreeDepth;
+    const int tree_depth = (window->DC.TreeDepth - g.LogStartDepth);
+    for (;;)
+    {
+        // Split the string. Each new line (after a '\n') is followed by spacing corresponding to the current depth of our log entry.
+        const char* line_end = text_remaining;
+        while (line_end < text_end)
+            if (*line_end == '\n')
+                break;
+            else
+                line_end++;
+        if (line_end >= text_end)
+            line_end = NULL;
+
+        const bool is_first_line = (text == text_remaining);
+        bool is_last_line = false;
+        if (line_end == NULL)
+        {
+            is_last_line = true;
+            line_end = text_end;
+        }
+        if (line_end != NULL && !(is_last_line && (line_end - text_remaining)==0))
+        {
+            const int char_count = (int)(line_end - text_remaining);
+            if (log_new_line || !is_first_line)
+                LogText(IM_NEWLINE "%*s%.*s", tree_depth*4, "", char_count, text_remaining);
+            else
+                LogText(" %.*s", char_count, text_remaining);
+        }
+
+        if (is_last_line)
+            break;
+        text_remaining = line_end + 1;
+    }
+}
+
+// Start logging ImGui output to TTY
+void ImGui::LogToTTY(int max_depth)
+{
+    ImGuiContext& g = *GImGui;
+    if (g.LogEnabled)
+        return;
+    ImGuiWindow* window = g.CurrentWindow;
+
+    IM_ASSERT(g.LogFile == NULL);
+    g.LogFile = stdout;
+    g.LogEnabled = true;
+    g.LogStartDepth = window->DC.TreeDepth;
+    if (max_depth >= 0)
+        g.LogAutoExpandMaxDepth = max_depth;
+}
+
+// Start logging ImGui output to given file
+void ImGui::LogToFile(int max_depth, const char* filename)
+{
+    ImGuiContext& g = *GImGui;
+    if (g.LogEnabled)
+        return;
+    ImGuiWindow* window = g.CurrentWindow;
+
+    if (!filename)
+    {
+        filename = g.IO.LogFilename;
+        if (!filename)
+            return;
+    }
+
+    IM_ASSERT(g.LogFile == NULL);
+    g.LogFile = ImFileOpen(filename, "ab");
+    if (!g.LogFile)
+    {
+        IM_ASSERT(g.LogFile != NULL); // Consider this an error
+        return;
+    }
+    g.LogEnabled = true;
+    g.LogStartDepth = window->DC.TreeDepth;
+    if (max_depth >= 0)
+        g.LogAutoExpandMaxDepth = max_depth;
+}
+
+// Start logging ImGui output to clipboard
+void ImGui::LogToClipboard(int max_depth)
+{
+    ImGuiContext& g = *GImGui;
+    if (g.LogEnabled)
+        return;
+    ImGuiWindow* window = g.CurrentWindow;
+
+    IM_ASSERT(g.LogFile == NULL);
+    g.LogFile = NULL;
+    g.LogEnabled = true;
+    g.LogStartDepth = window->DC.TreeDepth;
+    if (max_depth >= 0)
+        g.LogAutoExpandMaxDepth = max_depth;
+}
+
+void ImGui::LogFinish()
+{
+    ImGuiContext& g = *GImGui;
+    if (!g.LogEnabled)
+        return;
+
+    LogText(IM_NEWLINE);
+    if (g.LogFile != NULL)
+    {
+        if (g.LogFile == stdout)
+            fflush(g.LogFile);
+        else
+            fclose(g.LogFile);
+        g.LogFile = NULL;
+    }
+    if (g.LogClipboard.size() > 1)
+    {
+        SetClipboardText(g.LogClipboard.begin());
+        g.LogClipboard.clear();
+    }
+    g.LogEnabled = false;
+}
+
+// Helper to display logging buttons
+void ImGui::LogButtons()
+{
+    ImGuiContext& g = *GImGui;
+
+    PushID("LogButtons");
+    const bool log_to_tty = Button("Log To TTY"); SameLine();
+    const bool log_to_file = Button("Log To File"); SameLine();
+    const bool log_to_clipboard = Button("Log To Clipboard"); SameLine();
+    PushItemWidth(80.0f);
+    PushAllowKeyboardFocus(false);
+    SliderInt("Depth", &g.LogAutoExpandMaxDepth, 0, 9, NULL);
+    PopAllowKeyboardFocus();
+    PopItemWidth();
+    PopID();
+
+    // Start logging at the end of the function so that the buttons don't appear in the log
+    if (log_to_tty)
+        LogToTTY(g.LogAutoExpandMaxDepth);
+    if (log_to_file)
+        LogToFile(g.LogAutoExpandMaxDepth, g.IO.LogFilename);
+    if (log_to_clipboard)
+        LogToClipboard(g.LogAutoExpandMaxDepth);
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] SETTINGS
+//-----------------------------------------------------------------------------
+
+void ImGui::MarkIniSettingsDirty()
+{
+    ImGuiContext& g = *GImGui;
+    if (g.SettingsDirtyTimer <= 0.0f)
+        g.SettingsDirtyTimer = g.IO.IniSavingRate;
+}
+
+void ImGui::MarkIniSettingsDirty(ImGuiWindow* window)
+{
+    ImGuiContext& g = *GImGui;
+    if (!(window->Flags & ImGuiWindowFlags_NoSavedSettings))
+        if (g.SettingsDirtyTimer <= 0.0f)
+            g.SettingsDirtyTimer = g.IO.IniSavingRate;
+}
+
+static ImGuiWindowSettings* CreateNewWindowSettings(const char* name)
+{
+    ImGuiContext& g = *GImGui;
+    g.SettingsWindows.push_back(ImGuiWindowSettings());
+    ImGuiWindowSettings* settings = &g.SettingsWindows.back();
+    settings->Name = ImStrdup(name);
+    settings->ID = ImHash(name, 0);
+    return settings;
+}
+
+ImGuiWindowSettings* ImGui::FindWindowSettings(ImGuiID id)
+{
+    ImGuiContext& g = *GImGui;
+    for (int i = 0; i != g.SettingsWindows.Size; i++)
+        if (g.SettingsWindows[i].ID == id)
+            return &g.SettingsWindows[i];
+    return NULL;
+}
+
+void ImGui::LoadIniSettingsFromDisk(const char* ini_filename)
+{
+    size_t file_data_size = 0;
+    char* file_data = (char*)ImFileLoadToMemory(ini_filename, "rb", &file_data_size);
+    if (!file_data)
+        return;
+    LoadIniSettingsFromMemory(file_data, (size_t)file_data_size);
+    ImGui::MemFree(file_data);
+}
+
+ImGuiSettingsHandler* ImGui::FindSettingsHandler(const char* type_name)
+{
+    ImGuiContext& g = *GImGui;
+    const ImGuiID type_hash = ImHash(type_name, 0, 0);
+    for (int handler_n = 0; handler_n < g.SettingsHandlers.Size; handler_n++)
+        if (g.SettingsHandlers[handler_n].TypeHash == type_hash)
+            return &g.SettingsHandlers[handler_n];
+    return NULL;
+}
+
+// Zero-tolerance, no error reporting, cheap .ini parsing
+void ImGui::LoadIniSettingsFromMemory(const char* ini_data, size_t ini_size)
+{
+    ImGuiContext& g = *GImGui;
+    IM_ASSERT(g.Initialized);
+    IM_ASSERT(g.SettingsLoaded == false && g.FrameCount == 0);
+
+    // For user convenience, we allow passing a non zero-terminated string (hence the ini_size parameter).
+    // For our convenience and to make the code simpler, we'll also write zero-terminators within the buffer. So let's create a writable copy..
+    if (ini_size == 0)
+        ini_size = strlen(ini_data);
+    char* buf = (char*)ImGui::MemAlloc(ini_size + 1);
+    char* buf_end = buf + ini_size;
+    memcpy(buf, ini_data, ini_size);
+    buf[ini_size] = 0;
+
+    void* entry_data = NULL;
+    ImGuiSettingsHandler* entry_handler = NULL;
+
+    char* line_end = NULL;
+    for (char* line = buf; line < buf_end; line = line_end + 1)
+    {
+        // Skip new lines markers, then find end of the line
+        while (*line == '\n' || *line == '\r')
+            line++;
+        line_end = line;
+        while (line_end < buf_end && *line_end != '\n' && *line_end != '\r')
+            line_end++;
+        line_end[0] = 0;
+        if (line[0] == ';')
+            continue;
+        if (line[0] == '[' && line_end > line && line_end[-1] == ']')
+        {
+            // Parse "[Type][Name]". Note that 'Name' can itself contains [] characters, which is acceptable with the current format and parsing code.
+            line_end[-1] = 0;
+            const char* name_end = line_end - 1;
+            const char* type_start = line + 1;
+            char* type_end = (char*)(intptr_t)ImStrchrRange(type_start, name_end, ']');
+            const char* name_start = type_end ? ImStrchrRange(type_end + 1, name_end, '[') : NULL;
+            if (!type_end || !name_start)
+            {
+                name_start = type_start; // Import legacy entries that have no type
+                type_start = "Window";
+            }
+            else
+            {
+                *type_end = 0; // Overwrite first ']'
+                name_start++;  // Skip second '['
+            }
+            entry_handler = FindSettingsHandler(type_start);
+            entry_data = entry_handler ? entry_handler->ReadOpenFn(&g, entry_handler, name_start) : NULL;
+        }
+        else if (entry_handler != NULL && entry_data != NULL)
+        {
+            // Let type handler parse the line
+            entry_handler->ReadLineFn(&g, entry_handler, entry_data, line);
+        }
+    }
+    ImGui::MemFree(buf);
+    g.SettingsLoaded = true;
+}
+
+void ImGui::SaveIniSettingsToDisk(const char* ini_filename)
+{
+    ImGuiContext& g = *GImGui;
+    g.SettingsDirtyTimer = 0.0f;
+    if (!ini_filename)
+        return;
+
+    size_t ini_data_size = 0;
+    const char* ini_data = SaveIniSettingsToMemory(&ini_data_size);
+    FILE* f = ImFileOpen(ini_filename, "wt");
+    if (!f)
+        return;
+    fwrite(ini_data, sizeof(char), ini_data_size, f);
+    fclose(f);
+}
+
+// Call registered handlers (e.g. SettingsHandlerWindow_WriteAll() + custom handlers) to write their stuff into a text buffer
+const char* ImGui::SaveIniSettingsToMemory(size_t* out_size)
+{
+    ImGuiContext& g = *GImGui;
+    g.SettingsDirtyTimer = 0.0f;
+    g.SettingsIniData.Buf.resize(0);
+    g.SettingsIniData.Buf.push_back(0);
+    for (int handler_n = 0; handler_n < g.SettingsHandlers.Size; handler_n++)
+    {
+        ImGuiSettingsHandler* handler = &g.SettingsHandlers[handler_n];
+        handler->WriteAllFn(&g, handler, &g.SettingsIniData);
+    }
+    if (out_size)
+        *out_size = (size_t)g.SettingsIniData.size();
+    return g.SettingsIniData.c_str();
+}
+
+static void* SettingsHandlerWindow_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name)
+{
+    ImGuiWindowSettings* settings = ImGui::FindWindowSettings(ImHash(name, 0));
+    if (!settings)
+        settings = CreateNewWindowSettings(name);
+    return (void*)settings;
+}
+
+static void SettingsHandlerWindow_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, void* entry, const char* line)
+{
+    ImGuiWindowSettings* settings = (ImGuiWindowSettings*)entry;
+    float x, y;
+    int i;
+    if (sscanf(line, "Pos=%f,%f", &x, &y) == 2)         settings->Pos = ImVec2(x, y);
+    else if (sscanf(line, "Size=%f,%f", &x, &y) == 2)   settings->Size = ImMax(ImVec2(x, y), GImGui->Style.WindowMinSize);
+    else if (sscanf(line, "Collapsed=%d", &i) == 1)     settings->Collapsed = (i != 0);
+}
+
+static void SettingsHandlerWindow_WriteAll(ImGuiContext* imgui_ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf)
+{
+    // Gather data from windows that were active during this session
+    // (if a window wasn't opened in this session we preserve its settings)
+    ImGuiContext& g = *imgui_ctx;
+    for (int i = 0; i != g.Windows.Size; i++)
+    {
+        ImGuiWindow* window = g.Windows[i];
+        if (window->Flags & ImGuiWindowFlags_NoSavedSettings)
+            continue;
+
+        ImGuiWindowSettings* settings = (window->SettingsIdx != -1) ? &g.SettingsWindows[window->SettingsIdx] : ImGui::FindWindowSettings(window->ID);
+        if (!settings)
+        {
+            settings = CreateNewWindowSettings(window->Name);
+            window->SettingsIdx = g.SettingsWindows.index_from_pointer(settings);
+        }
+        IM_ASSERT(settings->ID == window->ID);
+        settings->Pos = window->Pos;
+        settings->Size = window->SizeFull;
+        settings->Collapsed = window->Collapsed;
+    }
+
+    // Write to text buffer
+    buf->reserve(buf->size() + g.SettingsWindows.Size * 96); // ballpark reserve
+    for (int i = 0; i != g.SettingsWindows.Size; i++)
+    {
+        const ImGuiWindowSettings* settings = &g.SettingsWindows[i];
+        if (settings->Pos.x == FLT_MAX)
+            continue;
+        const char* name = settings->Name;
+        if (const char* p = strstr(name, "###"))  // Skip to the "###" marker if any. We don't skip past to match the behavior of GetID()
+            name = p;
+        buf->appendf("[%s][%s]\n", handler->TypeName, name);
+        buf->appendf("Pos=%d,%d\n", (int)settings->Pos.x, (int)settings->Pos.y);
+        buf->appendf("Size=%d,%d\n", (int)settings->Size.x, (int)settings->Size.y);
+        buf->appendf("Collapsed=%d\n", settings->Collapsed);
+        buf->appendf("\n");
+    }
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] PLATFORM DEPENDENT HELPERS
+//-----------------------------------------------------------------------------
+
+#if defined(_WIN32) && !defined(_WINDOWS_) && (!defined(IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS) || !defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS))
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+#ifndef __MINGW32__
+#include <Windows.h>
+#else
+#include <windows.h>
+#endif
+#endif
+
+// Win32 API clipboard implementation
+#if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS)
+
+#ifdef _MSC_VER
+#pragma comment(lib, "user32")
+#endif
+
+static const char* GetClipboardTextFn_DefaultImpl(void*)
+{
+    static ImVector<char> buf_local;
+    buf_local.clear();
+    if (!::OpenClipboard(NULL))
+        return NULL;
+    HANDLE wbuf_handle = ::GetClipboardData(CF_UNICODETEXT);
+    if (wbuf_handle == NULL)
+    {
+        ::CloseClipboard();
+        return NULL;
+    }
+    if (ImWchar* wbuf_global = (ImWchar*)::GlobalLock(wbuf_handle))
+    {
+        int buf_len = ImTextCountUtf8BytesFromStr(wbuf_global, NULL) + 1;
+        buf_local.resize(buf_len);
+        ImTextStrToUtf8(buf_local.Data, buf_len, wbuf_global, NULL);
+    }
+    ::GlobalUnlock(wbuf_handle);
+    ::CloseClipboard();
+    return buf_local.Data;
+}
+
+static void SetClipboardTextFn_DefaultImpl(void*, const char* text)
+{
+    if (!::OpenClipboard(NULL))
+        return;
+    const int wbuf_length = ImTextCountCharsFromUtf8(text, NULL) + 1;
+    HGLOBAL wbuf_handle = ::GlobalAlloc(GMEM_MOVEABLE, (SIZE_T)wbuf_length * sizeof(ImWchar));
+    if (wbuf_handle == NULL)
+    {
+        ::CloseClipboard();
+        return;
+    }
+    ImWchar* wbuf_global = (ImWchar*)::GlobalLock(wbuf_handle);
+    ImTextStrFromUtf8(wbuf_global, wbuf_length, text, NULL);
+    ::GlobalUnlock(wbuf_handle);
+    ::EmptyClipboard();
+    if (::SetClipboardData(CF_UNICODETEXT, wbuf_handle) == NULL)
+        ::GlobalFree(wbuf_handle);
+    ::CloseClipboard();
+}
+
+#else
+
+// Local ImGui-only clipboard implementation, if user hasn't defined better clipboard handlers
+static const char* GetClipboardTextFn_DefaultImpl(void*)
+{
+    ImGuiContext& g = *GImGui;
+    return g.PrivateClipboard.empty() ? NULL : g.PrivateClipboard.begin();
+}
+
+// Local ImGui-only clipboard implementation, if user hasn't defined better clipboard handlers
+static void SetClipboardTextFn_DefaultImpl(void*, const char* text)
+{
+    ImGuiContext& g = *GImGui;
+    g.PrivateClipboard.clear();
+    const char* text_end = text + strlen(text);
+    g.PrivateClipboard.resize((int)(text_end - text) + 1);
+    memcpy(&g.PrivateClipboard[0], text, (size_t)(text_end - text));
+    g.PrivateClipboard[(int)(text_end - text)] = 0;
+}
+
+#endif
+
+// Win32 API IME support (for Asian languages, etc.)
+#if defined(_WIN32) && !defined(__GNUC__) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS)
+
+#include <imm.h>
+#ifdef _MSC_VER
+#pragma comment(lib, "imm32")
+#endif
+
+static void ImeSetInputScreenPosFn_DefaultImpl(int x, int y)
+{
+    // Notify OS Input Method Editor of text input position
+    if (HWND hwnd = (HWND)GImGui->IO.ImeWindowHandle)
+        if (HIMC himc = ::ImmGetContext(hwnd))
+        {
+            COMPOSITIONFORM cf;
+            cf.ptCurrentPos.x = x;
+            cf.ptCurrentPos.y = y;
+            cf.dwStyle = CFS_FORCE_POSITION;
+            ::ImmSetCompositionWindow(himc, &cf);
+            ::ImmReleaseContext(hwnd, himc);
+        }
+}
+
+#else
+
+static void ImeSetInputScreenPosFn_DefaultImpl(int, int) {}
+
+#endif
+
+//-----------------------------------------------------------------------------
+// [SECTION] METRICS/DEBUG WINDOW
+//-----------------------------------------------------------------------------
+
+void ImGui::ShowMetricsWindow(bool* p_open)
+{
+    if (!ImGui::Begin("ImGui Metrics", p_open))
+    {
+        ImGui::End();
+        return;
+    }
+    static bool show_draw_cmd_clip_rects = true;
+    static bool show_window_begin_order = false;
+    ImGuiIO& io = ImGui::GetIO();
+    ImGui::Text("Dear ImGui %s", ImGui::GetVersion());
+    ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate);
+    ImGui::Text("%d vertices, %d indices (%d triangles)", io.MetricsRenderVertices, io.MetricsRenderIndices, io.MetricsRenderIndices / 3);
+    ImGui::Text("%d active windows (%d visible)", io.MetricsActiveWindows, io.MetricsRenderWindows);
+    ImGui::Text("%d allocations", io.MetricsActiveAllocations);
+    ImGui::Checkbox("Show clipping rectangles when hovering draw commands", &show_draw_cmd_clip_rects);
+    ImGui::Checkbox("Ctrl shows window begin order", &show_window_begin_order);
+
+    ImGui::Separator();
+
+    struct Funcs
+    {
+        static void NodeDrawList(ImGuiWindow* window, ImDrawList* draw_list, const char* label)
+        {
+            bool node_open = ImGui::TreeNode(draw_list, "%s: '%s' %d vtx, %d indices, %d cmds", label, draw_list->_OwnerName ? draw_list->_OwnerName : "", draw_list->VtxBuffer.Size, draw_list->IdxBuffer.Size, draw_list->CmdBuffer.Size);
+            if (draw_list == ImGui::GetWindowDrawList())
+            {
+                ImGui::SameLine();
+                ImGui::TextColored(ImColor(255,100,100), "CURRENTLY APPENDING"); // Can't display stats for active draw list! (we don't have the data double-buffered)
+                if (node_open) ImGui::TreePop();
+                return;
+            }
+
+            ImDrawList* overlay_draw_list = GetOverlayDrawList(); // Render additional visuals into the top-most draw list
+            if (window && IsItemHovered())
+                overlay_draw_list->AddRect(window->Pos, window->Pos + window->Size, IM_COL32(255, 255, 0, 255));
+            if (!node_open)
+                return;
+
+            int elem_offset = 0;
+            for (const ImDrawCmd* pcmd = draw_list->CmdBuffer.begin(); pcmd < draw_list->CmdBuffer.end(); elem_offset += pcmd->ElemCount, pcmd++)
+            {
+                if (pcmd->UserCallback == NULL && pcmd->ElemCount == 0)
+                    continue;
+                if (pcmd->UserCallback)
+                {
+                    ImGui::BulletText("Callback %p, user_data %p", pcmd->UserCallback, pcmd->UserCallbackData);
+                    continue;
+                }
+                ImDrawIdx* idx_buffer = (draw_list->IdxBuffer.Size > 0) ? draw_list->IdxBuffer.Data : NULL;
+                bool pcmd_node_open = ImGui::TreeNode((void*)(pcmd - draw_list->CmdBuffer.begin()), "Draw %4d %s vtx, tex 0x%p, clip_rect (%4.0f,%4.0f)-(%4.0f,%4.0f)", pcmd->ElemCount, draw_list->IdxBuffer.Size > 0 ? "indexed" : "non-indexed", pcmd->TextureId, pcmd->ClipRect.x, pcmd->ClipRect.y, pcmd->ClipRect.z, pcmd->ClipRect.w);
+                if (show_draw_cmd_clip_rects && ImGui::IsItemHovered())
+                {
+                    ImRect clip_rect = pcmd->ClipRect;
+                    ImRect vtxs_rect;
+                    for (int i = elem_offset; i < elem_offset + (int)pcmd->ElemCount; i++)
+                        vtxs_rect.Add(draw_list->VtxBuffer[idx_buffer ? idx_buffer[i] : i].pos);
+                    clip_rect.Floor(); overlay_draw_list->AddRect(clip_rect.Min, clip_rect.Max, IM_COL32(255,255,0,255));
+                    vtxs_rect.Floor(); overlay_draw_list->AddRect(vtxs_rect.Min, vtxs_rect.Max, IM_COL32(255,0,255,255));
+                }
+                if (!pcmd_node_open)
+                    continue;
+
+                // Display individual triangles/vertices. Hover on to get the corresponding triangle highlighted.
+                ImGuiListClipper clipper(pcmd->ElemCount/3); // Manually coarse clip our print out of individual vertices to save CPU, only items that may be visible.
+                while (clipper.Step())
+                    for (int prim = clipper.DisplayStart, vtx_i = elem_offset + clipper.DisplayStart*3; prim < clipper.DisplayEnd; prim++)
+                    {
+                        char buf[300];
+                        char *buf_p = buf, *buf_end = buf + IM_ARRAYSIZE(buf);
+                        ImVec2 triangles_pos[3];
+                        for (int n = 0; n < 3; n++, vtx_i++)
+                        {
+                            ImDrawVert& v = draw_list->VtxBuffer[idx_buffer ? idx_buffer[vtx_i] : vtx_i];
+                            triangles_pos[n] = v.pos;
+                            buf_p += ImFormatString(buf_p, (int)(buf_end - buf_p), "%s %04d: pos (%8.2f,%8.2f), uv (%.6f,%.6f), col %08X\n", (n == 0) ? "vtx" : "   ", vtx_i, v.pos.x, v.pos.y, v.uv.x, v.uv.y, v.col);
+                        }
+                        ImGui::Selectable(buf, false);
+                        if (ImGui::IsItemHovered())
+                        {
+                            ImDrawListFlags backup_flags = overlay_draw_list->Flags;
+                            overlay_draw_list->Flags &= ~ImDrawListFlags_AntiAliasedLines; // Disable AA on triangle outlines at is more readable for very large and thin triangles.
+                            overlay_draw_list->AddPolyline(triangles_pos, 3, IM_COL32(255,255,0,255), true, 1.0f);
+                            overlay_draw_list->Flags = backup_flags;
+                        }
+                    }
+                ImGui::TreePop();
+            }
+            ImGui::TreePop();
+        }
+
+        static void NodeWindows(ImVector<ImGuiWindow*>& windows, const char* label)
+        {
+            if (!ImGui::TreeNode(label, "%s (%d)", label, windows.Size))
+                return;
+            for (int i = 0; i < windows.Size; i++)
+                Funcs::NodeWindow(windows[i], "Window");
+            ImGui::TreePop();
+        }
+
+        static void NodeWindow(ImGuiWindow* window, const char* label)
+        {
+            if (!ImGui::TreeNode(window, "%s '%s', %d @ 0x%p", label, window->Name, window->Active || window->WasActive, window))
+                return;
+            ImGuiWindowFlags flags = window->Flags;
+            NodeDrawList(window, window->DrawList, "DrawList");
+            ImGui::BulletText("Pos: (%.1f,%.1f), Size: (%.1f,%.1f), SizeContents (%.1f,%.1f)", window->Pos.x, window->Pos.y, window->Size.x, window->Size.y, window->SizeContents.x, window->SizeContents.y);
+            ImGui::BulletText("Flags: 0x%08X (%s%s%s%s%s%s%s%s..)", flags,
+                (flags & ImGuiWindowFlags_ChildWindow) ? "Child " : "", (flags & ImGuiWindowFlags_Tooltip)   ? "Tooltip "   : "", (flags & ImGuiWindowFlags_Popup) ? "Popup " : "",
+                (flags & ImGuiWindowFlags_Modal)       ? "Modal " : "", (flags & ImGuiWindowFlags_ChildMenu) ? "ChildMenu " : "", (flags & ImGuiWindowFlags_NoSavedSettings) ? "NoSavedSettings " : "",
+                (flags & ImGuiWindowFlags_NoInputs)    ? "NoInputs":"", (flags & ImGuiWindowFlags_AlwaysAutoResize) ? "AlwaysAutoResize" : "");
+            ImGui::BulletText("Scroll: (%.2f/%.2f,%.2f/%.2f)", window->Scroll.x, GetScrollMaxX(window), window->Scroll.y, GetScrollMaxY(window));
+            ImGui::BulletText("Active: %d/%d, WriteAccessed: %d, BeginOrderWithinContext: %d", window->Active, window->WasActive, window->WriteAccessed, (window->Active || window->WasActive) ? window->BeginOrderWithinContext : -1);
+            ImGui::BulletText("Appearing: %d, Hidden: %d (Reg %d Resize %d), SkipItems: %d", window->Appearing, window->Hidden, window->HiddenFramesRegular, window->HiddenFramesForResize, window->SkipItems);
+            ImGui::BulletText("NavLastIds: 0x%08X,0x%08X, NavLayerActiveMask: %X", window->NavLastIds[0], window->NavLastIds[1], window->DC.NavLayerActiveMask);
+            ImGui::BulletText("NavLastChildNavWindow: %s", window->NavLastChildNavWindow ? window->NavLastChildNavWindow->Name : "NULL");
+            if (!window->NavRectRel[0].IsInverted())
+                ImGui::BulletText("NavRectRel[0]: (%.1f,%.1f)(%.1f,%.1f)", window->NavRectRel[0].Min.x, window->NavRectRel[0].Min.y, window->NavRectRel[0].Max.x, window->NavRectRel[0].Max.y);
+            else
+                ImGui::BulletText("NavRectRel[0]: <None>");
+            if (window->RootWindow != window) NodeWindow(window->RootWindow, "RootWindow");
+            if (window->ParentWindow != NULL) NodeWindow(window->ParentWindow, "ParentWindow");
+            if (window->DC.ChildWindows.Size > 0) NodeWindows(window->DC.ChildWindows, "ChildWindows");
+            if (window->ColumnsStorage.Size > 0 && ImGui::TreeNode("Columns", "Columns sets (%d)", window->ColumnsStorage.Size))
+            {
+                for (int n = 0; n < window->ColumnsStorage.Size; n++)
+                {
+                    const ImGuiColumnsSet* columns = &window->ColumnsStorage[n];
+                    if (ImGui::TreeNode((void*)(uintptr_t)columns->ID, "Columns Id: 0x%08X, Count: %d, Flags: 0x%04X", columns->ID, columns->Count, columns->Flags))
+                    {
+                        ImGui::BulletText("Width: %.1f (MinX: %.1f, MaxX: %.1f)", columns->MaxX - columns->MinX, columns->MinX, columns->MaxX);
+                        for (int column_n = 0; column_n < columns->Columns.Size; column_n++)
+                            ImGui::BulletText("Column %02d: OffsetNorm %.3f (= %.1f px)", column_n, columns->Columns[column_n].OffsetNorm, OffsetNormToPixels(columns, columns->Columns[column_n].OffsetNorm));
+                        ImGui::TreePop();
+                    }
+                }
+                ImGui::TreePop();
+            }
+            ImGui::BulletText("Storage: %d bytes", window->StateStorage.Data.Size * (int)sizeof(ImGuiStorage::Pair));
+            ImGui::TreePop();
+        }
+    };
+
+    // Access private state, we are going to display the draw lists from last frame
+    ImGuiContext& g = *GImGui;
+    Funcs::NodeWindows(g.Windows, "Windows");
+    if (ImGui::TreeNode("DrawList", "Active DrawLists (%d)", g.DrawDataBuilder.Layers[0].Size))
+    {
+        for (int i = 0; i < g.DrawDataBuilder.Layers[0].Size; i++)
+            Funcs::NodeDrawList(NULL, g.DrawDataBuilder.Layers[0][i], "DrawList");
+        ImGui::TreePop();
+    }
+    if (ImGui::TreeNode("Popups", "Popups (%d)", g.OpenPopupStack.Size))
+    {
+        for (int i = 0; i < g.OpenPopupStack.Size; i++)
+        {
+            ImGuiWindow* window = g.OpenPopupStack[i].Window;
+            ImGui::BulletText("PopupID: %08x, Window: '%s'%s%s", g.OpenPopupStack[i].PopupId, window ? window->Name : "NULL", window && (window->Flags & ImGuiWindowFlags_ChildWindow) ? " ChildWindow" : "", window && (window->Flags & ImGuiWindowFlags_ChildMenu) ? " ChildMenu" : "");
+        }
+        ImGui::TreePop();
+    }
+    if (ImGui::TreeNode("Internal state"))
+    {
+        const char* input_source_names[] = { "None", "Mouse", "Nav", "NavKeyboard", "NavGamepad" }; IM_ASSERT(IM_ARRAYSIZE(input_source_names) == ImGuiInputSource_COUNT);
+        ImGui::Text("HoveredWindow: '%s'", g.HoveredWindow ? g.HoveredWindow->Name : "NULL");
+        ImGui::Text("HoveredRootWindow: '%s'", g.HoveredRootWindow ? g.HoveredRootWindow->Name : "NULL");
+        ImGui::Text("HoveredId: 0x%08X/0x%08X (%.2f sec), AllowOverlap: %d", g.HoveredId, g.HoveredIdPreviousFrame, g.HoveredIdTimer, g.HoveredIdAllowOverlap); // Data is "in-flight" so depending on when the Metrics window is called we may see current frame information or not
+        ImGui::Text("ActiveId: 0x%08X/0x%08X (%.2f sec), AllowOverlap: %d, Source: %s", g.ActiveId, g.ActiveIdPreviousFrame, g.ActiveIdTimer, g.ActiveIdAllowOverlap, input_source_names[g.ActiveIdSource]);
+        ImGui::Text("ActiveIdWindow: '%s'", g.ActiveIdWindow ? g.ActiveIdWindow->Name : "NULL");
+        ImGui::Text("MovingWindow: '%s'", g.MovingWindow ? g.MovingWindow->Name : "NULL");
+        ImGui::Text("NavWindow: '%s'", g.NavWindow ? g.NavWindow->Name : "NULL");
+        ImGui::Text("NavId: 0x%08X, NavLayer: %d", g.NavId, g.NavLayer);
+        ImGui::Text("NavInputSource: %s", input_source_names[g.NavInputSource]);
+        ImGui::Text("NavActive: %d, NavVisible: %d", g.IO.NavActive, g.IO.NavVisible);
+        ImGui::Text("NavActivateId: 0x%08X, NavInputId: 0x%08X", g.NavActivateId, g.NavInputId);
+        ImGui::Text("NavDisableHighlight: %d, NavDisableMouseHover: %d", g.NavDisableHighlight, g.NavDisableMouseHover);
+        ImGui::Text("NavWindowingTarget: '%s'", g.NavWindowingTarget ? g.NavWindowingTarget->Name : "NULL");
+        ImGui::Text("DragDrop: %d, SourceId = 0x%08X, Payload \"%s\" (%d bytes)", g.DragDropActive, g.DragDropPayload.SourceId, g.DragDropPayload.DataType, g.DragDropPayload.DataSize);
+        ImGui::TreePop();
+    }
+
+
+    if (g.IO.KeyCtrl && show_window_begin_order)
+    {
+        for (int n = 0; n < g.Windows.Size; n++)
+        {
+            ImGuiWindow* window = g.Windows[n];
+            if ((window->Flags & ImGuiWindowFlags_ChildWindow) || !window->WasActive)
+                continue;
+            char buf[32];
+            ImFormatString(buf, IM_ARRAYSIZE(buf), "%d", window->BeginOrderWithinContext);
+            float font_size = ImGui::GetFontSize() * 2;
+            ImDrawList* overlay_draw_list = GetOverlayDrawList();
+            overlay_draw_list->AddRectFilled(window->Pos, window->Pos + ImVec2(font_size, font_size), IM_COL32(200, 100, 100, 255));
+            overlay_draw_list->AddText(NULL, font_size, window->Pos, IM_COL32(255, 255, 255, 255), buf);
+        }
+    }
+    ImGui::End();
+}
+
+//-----------------------------------------------------------------------------
+
+// Include imgui_user.inl at the end of imgui.cpp to access private data/functions that aren't exposed.
+// Prefer just including imgui_internal.h from your code rather than using this define. If a declaration is missing from imgui_internal.h add it or request it on the github.
+#ifdef IMGUI_INCLUDE_IMGUI_USER_INL
+#include "imgui_user.inl"
+#endif
+
+//-----------------------------------------------------------------------------
diff --git a/external/Vulkan/external/imgui/imgui.h b/external/Vulkan/external/imgui/imgui.h
new file mode 100644
index 0000000000000000000000000000000000000000..26c8887c1b0cb68198a5bd0805e976a620dde448
--- /dev/null
+++ b/external/Vulkan/external/imgui/imgui.h
@@ -0,0 +1,1974 @@
+// dear imgui, v1.65
+// (headers)
+
+// See imgui.cpp file for documentation.
+// Call and read ImGui::ShowDemoWindow() in imgui_demo.cpp for demo code.
+// Read 'Programmer guide' in imgui.cpp for notes on how to setup ImGui in your codebase.
+// Get latest version at https://github.com/ocornut/imgui
+
+#pragma once
+
+// Configuration file (edit imconfig.h or define IMGUI_USER_CONFIG to set your own filename)
+#ifdef IMGUI_USER_CONFIG
+#include IMGUI_USER_CONFIG
+#endif
+#if !defined(IMGUI_DISABLE_INCLUDE_IMCONFIG_H) || defined(IMGUI_INCLUDE_IMCONFIG_H)
+#include "imconfig.h"
+#endif
+
+#include <float.h>                  // FLT_MAX
+#include <stdarg.h>                 // va_list
+#include <stddef.h>                 // ptrdiff_t, NULL
+#include <string.h>                 // memset, memmove, memcpy, strlen, strchr, strcpy, strcmp
+
+// Version
+// (Integer encoded as XYYZZ for use in #if preprocessor conditionals. Work in progress versions typically starts at XYY00 then bounced up to XYY01 when release tagging happens)
+#define IMGUI_VERSION               "1.65"
+#define IMGUI_VERSION_NUM           16501
+#define IMGUI_CHECKVERSION()        ImGui::DebugCheckVersionAndDataLayout(IMGUI_VERSION, sizeof(ImGuiIO), sizeof(ImGuiStyle), sizeof(ImVec2), sizeof(ImVec4), sizeof(ImDrawVert))
+
+// Define attributes of all API symbols declarations (e.g. for DLL under Windows)
+// IMGUI_API is used for core imgui functions, IMGUI_IMPL_API is used for the default bindings files (imgui_impl_xxx.h)
+#ifndef IMGUI_API
+#define IMGUI_API
+#endif
+#ifndef IMGUI_IMPL_API
+#define IMGUI_IMPL_API              IMGUI_API
+#endif
+
+// Helpers
+#ifndef IM_ASSERT
+#include <assert.h>
+#define IM_ASSERT(_EXPR)            assert(_EXPR)                               // You can override the default assert handler by editing imconfig.h
+#endif
+#if defined(__clang__) || defined(__GNUC__)
+#define IM_FMTARGS(FMT)             __attribute__((format(printf, FMT, FMT+1))) // Apply printf-style warnings to user functions.
+#define IM_FMTLIST(FMT)             __attribute__((format(printf, FMT, 0)))
+#else
+#define IM_FMTARGS(FMT)
+#define IM_FMTLIST(FMT)
+#endif
+#define IM_ARRAYSIZE(_ARR)          ((int)(sizeof(_ARR)/sizeof(*_ARR)))         // Size of a static C-style array. Don't use on pointers!
+#define IM_OFFSETOF(_TYPE,_MEMBER)  ((size_t)&(((_TYPE*)0)->_MEMBER))           // Offset of _MEMBER within _TYPE. Standardized as offsetof() in modern C++.
+
+#if defined(__clang__)
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wold-style-cast"
+#elif defined(__GNUC__) && __GNUC__ >= 8
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wclass-memaccess"
+#endif
+
+// Forward declarations
+struct ImDrawChannel;               // Temporary storage for outputting drawing commands out of order, used by ImDrawList::ChannelsSplit()
+struct ImDrawCmd;                   // A single draw command within a parent ImDrawList (generally maps to 1 GPU draw call)
+struct ImDrawData;                  // All draw command lists required to render the frame
+struct ImDrawList;                  // A single draw command list (generally one per window, conceptually you may see this as a dynamic "mesh" builder)
+struct ImDrawListSharedData;        // Data shared among multiple draw lists (typically owned by parent ImGui context, but you may create one yourself)
+struct ImDrawVert;                  // A single vertex (20 bytes by default, override layout with IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT)
+struct ImFont;                      // Runtime data for a single font within a parent ImFontAtlas
+struct ImFontAtlas;                 // Runtime data for multiple fonts, bake multiple fonts into a single texture, TTF/OTF font loader
+struct ImFontConfig;                // Configuration data when adding a font or merging fonts
+struct ImColor;                     // Helper functions to create a color that can be converted to either u32 or float4 (*obsolete* please avoid using)
+#ifndef ImTextureID
+typedef void* ImTextureID;          // User data to identify a texture (this is whatever to you want it to be! read the FAQ about ImTextureID in imgui.cpp)
+#endif
+struct ImGuiContext;                // ImGui context (opaque)
+struct ImGuiIO;                     // Main configuration and I/O between your application and ImGui
+struct ImGuiInputTextCallbackData;  // Shared state of InputText() when using custom ImGuiInputTextCallback (rare/advanced use)
+struct ImGuiListClipper;            // Helper to manually clip large list of items
+struct ImGuiOnceUponAFrame;         // Helper for running a block of code not more than once a frame, used by IMGUI_ONCE_UPON_A_FRAME macro
+struct ImGuiPayload;                // User data payload for drag and drop operations
+struct ImGuiSizeCallbackData;       // Callback data when using SetNextWindowSizeConstraints() (rare/advanced use)
+struct ImGuiStorage;                // Helper for key->value storage
+struct ImGuiStyle;                  // Runtime data for styling/colors
+struct ImGuiTextFilter;             // Helper to parse and apply text filters (e.g. "aaaaa[,bbbb][,ccccc]")
+struct ImGuiTextBuffer;             // Helper to hold and append into a text buffer (~string builder)
+
+// Typedefs and Enums/Flags (declared as int for compatibility with old C++, to allow using as flags and to not pollute the top of this file)
+// Use your programming IDE "Go to definition" facility on the names of the center columns to find the actual flags/enum lists.
+typedef unsigned int ImGuiID;       // Unique ID used by widgets (typically hashed from a stack of string)
+typedef unsigned short ImWchar;     // Character for keyboard input/display
+typedef int ImGuiCol;               // -> enum ImGuiCol_             // Enum: A color identifier for styling
+typedef int ImGuiCond;              // -> enum ImGuiCond_            // Enum: A condition for Set*()
+typedef int ImGuiDataType;          // -> enum ImGuiDataType_        // Enum: A primary data type
+typedef int ImGuiDir;               // -> enum ImGuiDir_             // Enum: A cardinal direction
+typedef int ImGuiKey;               // -> enum ImGuiKey_             // Enum: A key identifier (ImGui-side enum)
+typedef int ImGuiNavInput;          // -> enum ImGuiNavInput_        // Enum: An input identifier for navigation
+typedef int ImGuiMouseCursor;       // -> enum ImGuiMouseCursor_     // Enum: A mouse cursor identifier
+typedef int ImGuiStyleVar;          // -> enum ImGuiStyleVar_        // Enum: A variable identifier for styling
+typedef int ImDrawCornerFlags;      // -> enum ImDrawCornerFlags_    // Flags: for ImDrawList::AddRect*() etc.
+typedef int ImDrawListFlags;        // -> enum ImDrawListFlags_      // Flags: for ImDrawList
+typedef int ImFontAtlasFlags;       // -> enum ImFontAtlasFlags_     // Flags: for ImFontAtlas
+typedef int ImGuiBackendFlags;      // -> enum ImGuiBackendFlags_    // Flags: for io.BackendFlags
+typedef int ImGuiColorEditFlags;    // -> enum ImGuiColorEditFlags_  // Flags: for ColorEdit*(), ColorPicker*()
+typedef int ImGuiColumnsFlags;      // -> enum ImGuiColumnsFlags_    // Flags: for Columns(), BeginColumns()
+typedef int ImGuiConfigFlags;       // -> enum ImGuiConfigFlags_     // Flags: for io.ConfigFlags
+typedef int ImGuiComboFlags;        // -> enum ImGuiComboFlags_      // Flags: for BeginCombo()
+typedef int ImGuiDragDropFlags;     // -> enum ImGuiDragDropFlags_   // Flags: for *DragDrop*()
+typedef int ImGuiFocusedFlags;      // -> enum ImGuiFocusedFlags_    // Flags: for IsWindowFocused()
+typedef int ImGuiHoveredFlags;      // -> enum ImGuiHoveredFlags_    // Flags: for IsItemHovered(), IsWindowHovered() etc.
+typedef int ImGuiInputTextFlags;    // -> enum ImGuiInputTextFlags_  // Flags: for InputText*()
+typedef int ImGuiSelectableFlags;   // -> enum ImGuiSelectableFlags_ // Flags: for Selectable()
+typedef int ImGuiTreeNodeFlags;     // -> enum ImGuiTreeNodeFlags_   // Flags: for TreeNode*(),CollapsingHeader()
+typedef int ImGuiWindowFlags;       // -> enum ImGuiWindowFlags_     // Flags: for Begin*()
+typedef int (*ImGuiInputTextCallback)(ImGuiInputTextCallbackData *data);
+typedef void (*ImGuiSizeCallback)(ImGuiSizeCallbackData* data);
+
+// Scalar data types
+typedef signed int          ImS32;  // 32-bit signed integer == int
+typedef unsigned int        ImU32;  // 32-bit unsigned integer (often used to store packed colors)
+#if defined(_MSC_VER) && !defined(__clang__)
+typedef signed   __int64    ImS64;  // 64-bit signed integer (pre and post C++11 with Visual Studio)
+typedef unsigned __int64    ImU64;  // 64-bit unsigned integer (pre and post C++11 with Visual Studio)
+#elif (defined(__clang__) || defined(__GNUC__)) && (__cplusplus < 201100)
+#include <stdint.h>
+typedef int64_t             ImS64;  // 64-bit signed integer (pre C++11)
+typedef uint64_t            ImU64;  // 64-bit unsigned integer (pre C++11)
+#else
+typedef signed   long long  ImS64;  // 64-bit signed integer (post C++11)
+typedef unsigned long long  ImU64;  // 64-bit unsigned integer (post C++11)
+#endif
+
+// 2D vector (often used to store positions, sizes, etc.)
+struct ImVec2
+{
+    float     x, y;
+    ImVec2()  { x = y = 0.0f; }
+    ImVec2(float _x, float _y) { x = _x; y = _y; }
+    float operator[] (size_t i) const { IM_ASSERT(i <= 1); return (&x)[i]; }    // We very rarely use this [] operator, the assert overhead is fine.
+#ifdef IM_VEC2_CLASS_EXTRA
+    IM_VEC2_CLASS_EXTRA     // Define additional constructors and implicit cast operators in imconfig.h to convert back and forth between your math types and ImVec2.
+#endif
+};
+
+// 4D vector (often used to store floating-point colors)
+struct ImVec4
+{
+    float     x, y, z, w;
+    ImVec4()  { x = y = z = w = 0.0f; }
+    ImVec4(float _x, float _y, float _z, float _w) { x = _x; y = _y; z = _z; w = _w; }
+#ifdef IM_VEC4_CLASS_EXTRA
+    IM_VEC4_CLASS_EXTRA     // Define additional constructors and implicit cast operators in imconfig.h to convert back and forth between your math types and ImVec4.
+#endif
+};
+
+// Dear ImGui end-user API
+// (In a namespace so you can add extra functions in your own separate file. Please don't modify imgui.cpp/.h!)
+namespace ImGui
+{
+    // Context creation and access
+    // Each context create its own ImFontAtlas by default. You may instance one yourself and pass it to CreateContext() to share a font atlas between imgui contexts.
+    // All those functions are not reliant on the current context.
+    IMGUI_API ImGuiContext* CreateContext(ImFontAtlas* shared_font_atlas = NULL);
+    IMGUI_API void          DestroyContext(ImGuiContext* ctx = NULL);   // NULL = destroy current context
+    IMGUI_API ImGuiContext* GetCurrentContext();
+    IMGUI_API void          SetCurrentContext(ImGuiContext* ctx);
+    IMGUI_API bool          DebugCheckVersionAndDataLayout(const char* version_str, size_t sz_io, size_t sz_style, size_t sz_vec2, size_t sz_vec4, size_t sz_drawvert);
+
+    // Main
+    IMGUI_API ImGuiIO&      GetIO();                                    // access the IO structure (mouse/keyboard/gamepad inputs, time, various configuration options/flags)
+    IMGUI_API ImGuiStyle&   GetStyle();                                 // access the Style structure (colors, sizes). Always use PushStyleCol(), PushStyleVar() to modify style mid-frame.
+    IMGUI_API void          NewFrame();                                 // start a new ImGui frame, you can submit any command from this point until Render()/EndFrame().
+    IMGUI_API void          EndFrame();                                 // ends the ImGui frame. automatically called by Render(), you likely don't need to call that yourself directly. If you don't need to render data (skipping rendering) you may call EndFrame() but you'll have wasted CPU already! If you don't need to render, better to not create any imgui windows and not call NewFrame() at all!
+    IMGUI_API void          Render();                                   // ends the ImGui frame, finalize the draw data. (Obsolete: optionally call io.RenderDrawListsFn if set. Nowadays, prefer calling your render function yourself.)
+    IMGUI_API ImDrawData*   GetDrawData();                              // valid after Render() and until the next call to NewFrame(). this is what you have to render. (Obsolete: this used to be passed to your io.RenderDrawListsFn() function.)
+
+    // Demo, Debug, Information
+    IMGUI_API void          ShowDemoWindow(bool* p_open = NULL);        // create demo/test window (previously called ShowTestWindow). demonstrate most ImGui features. call this to learn about the library! try to make it always available in your application!
+    IMGUI_API void          ShowMetricsWindow(bool* p_open = NULL);     // create metrics window. display ImGui internals: draw commands (with individual draw calls and vertices), window list, basic internal state, etc.
+    IMGUI_API void          ShowStyleEditor(ImGuiStyle* ref = NULL);    // add style editor block (not a window). you can pass in a reference ImGuiStyle structure to compare to, revert to and save to (else it uses the default style)
+    IMGUI_API bool          ShowStyleSelector(const char* label);       // add style selector block (not a window), essentially a combo listing the default styles.
+    IMGUI_API void          ShowFontSelector(const char* label);        // add font selector block (not a window), essentially a combo listing the loaded fonts.
+    IMGUI_API void          ShowUserGuide();                            // add basic help/info block (not a window): how to manipulate ImGui as a end-user (mouse/keyboard controls).
+    IMGUI_API const char*   GetVersion();                               // get the compiled version string e.g. "1.23"
+
+    // Styles
+    IMGUI_API void          StyleColorsDark(ImGuiStyle* dst = NULL);    // new, recommended style (default)
+    IMGUI_API void          StyleColorsClassic(ImGuiStyle* dst = NULL); // classic imgui style
+    IMGUI_API void          StyleColorsLight(ImGuiStyle* dst = NULL);   // best used with borders and a custom, thicker font
+
+    // Windows
+    // (Begin = push window to the stack and start appending to it. End = pop window from the stack. You may append multiple times to the same window during the same frame)
+    // Begin()/BeginChild() return false to indicate the window being collapsed or fully clipped, so you may early out and omit submitting anything to the window.
+    // You need to always call a matching End()/EndChild() for a Begin()/BeginChild() call, regardless of its return value (this is due to legacy reason and is inconsistent with BeginMenu/EndMenu, BeginPopup/EndPopup and other functions where the End call should only be called if the corresponding Begin function returned true.)
+    // Passing 'bool* p_open != NULL' shows a close widget in the upper-right corner of the window, which when clicking will set the boolean to false.
+    // Use child windows to introduce independent scrolling/clipping regions within a host window. Child windows can embed their own child.
+    IMGUI_API bool          Begin(const char* name, bool* p_open = NULL, ImGuiWindowFlags flags = 0);
+    IMGUI_API void          End();
+    IMGUI_API bool          BeginChild(const char* str_id, const ImVec2& size = ImVec2(0,0), bool border = false, ImGuiWindowFlags flags = 0); // Begin a scrolling region. size==0.0f: use remaining window size, size<0.0f: use remaining window size minus abs(size). size>0.0f: fixed size. each axis can use a different mode, e.g. ImVec2(0,400).
+    IMGUI_API bool          BeginChild(ImGuiID id, const ImVec2& size = ImVec2(0,0), bool border = false, ImGuiWindowFlags flags = 0);
+    IMGUI_API void          EndChild();
+
+    // Windows Utilities
+    IMGUI_API bool          IsWindowAppearing();
+    IMGUI_API bool          IsWindowCollapsed();
+    IMGUI_API bool          IsWindowFocused(ImGuiFocusedFlags flags=0); // is current window focused? or its root/child, depending on flags. see flags for options.
+    IMGUI_API bool          IsWindowHovered(ImGuiHoveredFlags flags=0); // is current window hovered (and typically: not blocked by a popup/modal)? see flags for options. NB: If you are trying to check whether your mouse should be dispatched to imgui or to your app, you should use the 'io.WantCaptureMouse' boolean for that! Please read the FAQ!
+    IMGUI_API ImDrawList*   GetWindowDrawList();                        // get draw list associated to the window, to append your own drawing primitives
+    IMGUI_API ImVec2        GetWindowPos();                             // get current window position in screen space (useful if you want to do your own drawing via the DrawList API)
+    IMGUI_API ImVec2        GetWindowSize();                            // get current window size
+    IMGUI_API float         GetWindowWidth();                           // get current window width (shortcut for GetWindowSize().x)
+    IMGUI_API float         GetWindowHeight();                          // get current window height (shortcut for GetWindowSize().y)
+    IMGUI_API ImVec2        GetContentRegionMax();                      // current content boundaries (typically window boundaries including scrolling, or current column boundaries), in windows coordinates
+    IMGUI_API ImVec2        GetContentRegionAvail();                    // == GetContentRegionMax() - GetCursorPos()
+    IMGUI_API float         GetContentRegionAvailWidth();               //
+    IMGUI_API ImVec2        GetWindowContentRegionMin();                // content boundaries min (roughly (0,0)-Scroll), in window coordinates
+    IMGUI_API ImVec2        GetWindowContentRegionMax();                // content boundaries max (roughly (0,0)+Size-Scroll) where Size can be override with SetNextWindowContentSize(), in window coordinates
+    IMGUI_API float         GetWindowContentRegionWidth();              //
+
+    IMGUI_API void          SetNextWindowPos(const ImVec2& pos, ImGuiCond cond = 0, const ImVec2& pivot = ImVec2(0,0)); // set next window position. call before Begin(). use pivot=(0.5f,0.5f) to center on given point, etc.
+    IMGUI_API void          SetNextWindowSize(const ImVec2& size, ImGuiCond cond = 0);                  // set next window size. set axis to 0.0f to force an auto-fit on this axis. call before Begin()
+    IMGUI_API void          SetNextWindowSizeConstraints(const ImVec2& size_min, const ImVec2& size_max, ImGuiSizeCallback custom_callback = NULL, void* custom_callback_data = NULL); // set next window size limits. use -1,-1 on either X/Y axis to preserve the current size. Use callback to apply non-trivial programmatic constraints.
+    IMGUI_API void          SetNextWindowContentSize(const ImVec2& size);                               // set next window content size (~ enforce the range of scrollbars). not including window decorations (title bar, menu bar, etc.). set an axis to 0.0f to leave it automatic. call before Begin()
+    IMGUI_API void          SetNextWindowCollapsed(bool collapsed, ImGuiCond cond = 0);                 // set next window collapsed state. call before Begin()
+    IMGUI_API void          SetNextWindowFocus();                                                       // set next window to be focused / front-most. call before Begin()
+    IMGUI_API void          SetNextWindowBgAlpha(float alpha);                                          // set next window background color alpha. helper to easily modify ImGuiCol_WindowBg/ChildBg/PopupBg.
+    IMGUI_API void          SetWindowPos(const ImVec2& pos, ImGuiCond cond = 0);                        // (not recommended) set current window position - call within Begin()/End(). prefer using SetNextWindowPos(), as this may incur tearing and side-effects.
+    IMGUI_API void          SetWindowSize(const ImVec2& size, ImGuiCond cond = 0);                      // (not recommended) set current window size - call within Begin()/End(). set to ImVec2(0,0) to force an auto-fit. prefer using SetNextWindowSize(), as this may incur tearing and minor side-effects.
+    IMGUI_API void          SetWindowCollapsed(bool collapsed, ImGuiCond cond = 0);                     // (not recommended) set current window collapsed state. prefer using SetNextWindowCollapsed().
+    IMGUI_API void          SetWindowFocus();                                                           // (not recommended) set current window to be focused / front-most. prefer using SetNextWindowFocus().
+    IMGUI_API void          SetWindowFontScale(float scale);                                            // set font scale. Adjust IO.FontGlobalScale if you want to scale all windows
+    IMGUI_API void          SetWindowPos(const char* name, const ImVec2& pos, ImGuiCond cond = 0);      // set named window position.
+    IMGUI_API void          SetWindowSize(const char* name, const ImVec2& size, ImGuiCond cond = 0);    // set named window size. set axis to 0.0f to force an auto-fit on this axis.
+    IMGUI_API void          SetWindowCollapsed(const char* name, bool collapsed, ImGuiCond cond = 0);   // set named window collapsed state
+    IMGUI_API void          SetWindowFocus(const char* name);                                           // set named window to be focused / front-most. use NULL to remove focus.
+
+    // Windows Scrolling
+    IMGUI_API float         GetScrollX();                                                   // get scrolling amount [0..GetScrollMaxX()]
+    IMGUI_API float         GetScrollY();                                                   // get scrolling amount [0..GetScrollMaxY()]
+    IMGUI_API float         GetScrollMaxX();                                                // get maximum scrolling amount ~~ ContentSize.X - WindowSize.X
+    IMGUI_API float         GetScrollMaxY();                                                // get maximum scrolling amount ~~ ContentSize.Y - WindowSize.Y
+    IMGUI_API void          SetScrollX(float scroll_x);                                     // set scrolling amount [0..GetScrollMaxX()]
+    IMGUI_API void          SetScrollY(float scroll_y);                                     // set scrolling amount [0..GetScrollMaxY()]
+    IMGUI_API void          SetScrollHere(float center_y_ratio = 0.5f);                     // adjust scrolling amount to make current cursor position visible. center_y_ratio=0.0: top, 0.5: center, 1.0: bottom. When using to make a "default/current item" visible, consider using SetItemDefaultFocus() instead.
+    IMGUI_API void          SetScrollFromPosY(float pos_y, float center_y_ratio = 0.5f);    // adjust scrolling amount to make given position valid. use GetCursorPos() or GetCursorStartPos()+offset to get valid positions.
+
+    // Parameters stacks (shared)
+    IMGUI_API void          PushFont(ImFont* font);                                         // use NULL as a shortcut to push default font
+    IMGUI_API void          PopFont();
+    IMGUI_API void          PushStyleColor(ImGuiCol idx, ImU32 col);
+    IMGUI_API void          PushStyleColor(ImGuiCol idx, const ImVec4& col);
+    IMGUI_API void          PopStyleColor(int count = 1);
+    IMGUI_API void          PushStyleVar(ImGuiStyleVar idx, float val);
+    IMGUI_API void          PushStyleVar(ImGuiStyleVar idx, const ImVec2& val);
+    IMGUI_API void          PopStyleVar(int count = 1);
+    IMGUI_API const ImVec4& GetStyleColorVec4(ImGuiCol idx);                                // retrieve style color as stored in ImGuiStyle structure. use to feed back into PushStyleColor(), otherwise use GetColorU32() to get style color with style alpha baked in.
+    IMGUI_API ImFont*       GetFont();                                                      // get current font
+    IMGUI_API float         GetFontSize();                                                  // get current font size (= height in pixels) of current font with current scale applied
+    IMGUI_API ImVec2        GetFontTexUvWhitePixel();                                       // get UV coordinate for a while pixel, useful to draw custom shapes via the ImDrawList API
+    IMGUI_API ImU32         GetColorU32(ImGuiCol idx, float alpha_mul = 1.0f);              // retrieve given style color with style alpha applied and optional extra alpha multiplier
+    IMGUI_API ImU32         GetColorU32(const ImVec4& col);                                 // retrieve given color with style alpha applied
+    IMGUI_API ImU32         GetColorU32(ImU32 col);                                         // retrieve given color with style alpha applied
+
+    // Parameters stacks (current window)
+    IMGUI_API void          PushItemWidth(float item_width);                                // width of items for the common item+label case, pixels. 0.0f = default to ~2/3 of windows width, >0.0f: width in pixels, <0.0f align xx pixels to the right of window (so -1.0f always align width to the right side)
+    IMGUI_API void          PopItemWidth();
+    IMGUI_API float         CalcItemWidth();                                                // width of item given pushed settings and current cursor position
+    IMGUI_API void          PushTextWrapPos(float wrap_pos_x = 0.0f);                       // word-wrapping for Text*() commands. < 0.0f: no wrapping; 0.0f: wrap to end of window (or column); > 0.0f: wrap at 'wrap_pos_x' position in window local space
+    IMGUI_API void          PopTextWrapPos();
+    IMGUI_API void          PushAllowKeyboardFocus(bool allow_keyboard_focus);              // allow focusing using TAB/Shift-TAB, enabled by default but you can disable it for certain widgets
+    IMGUI_API void          PopAllowKeyboardFocus();
+    IMGUI_API void          PushButtonRepeat(bool repeat);                                  // in 'repeat' mode, Button*() functions return repeated true in a typematic manner (using io.KeyRepeatDelay/io.KeyRepeatRate setting). Note that you can call IsItemActive() after any Button() to tell if the button is held in the current frame.
+    IMGUI_API void          PopButtonRepeat();
+
+    // Cursor / Layout
+    IMGUI_API void          Separator();                                                    // separator, generally horizontal. inside a menu bar or in horizontal layout mode, this becomes a vertical separator.
+    IMGUI_API void          SameLine(float pos_x = 0.0f, float spacing_w = -1.0f);          // call between widgets or groups to layout them horizontally
+    IMGUI_API void          NewLine();                                                      // undo a SameLine()
+    IMGUI_API void          Spacing();                                                      // add vertical spacing
+    IMGUI_API void          Dummy(const ImVec2& size);                                      // add a dummy item of given size
+    IMGUI_API void          Indent(float indent_w = 0.0f);                                  // move content position toward the right, by style.IndentSpacing or indent_w if != 0
+    IMGUI_API void          Unindent(float indent_w = 0.0f);                                // move content position back to the left, by style.IndentSpacing or indent_w if != 0
+    IMGUI_API void          BeginGroup();                                                   // lock horizontal starting position + capture group bounding box into one "item" (so you can use IsItemHovered() or layout primitives such as SameLine() on whole group, etc.)
+    IMGUI_API void          EndGroup();
+    IMGUI_API ImVec2        GetCursorPos();                                                 // cursor position is relative to window position
+    IMGUI_API float         GetCursorPosX();                                                // "
+    IMGUI_API float         GetCursorPosY();                                                // "
+    IMGUI_API void          SetCursorPos(const ImVec2& local_pos);                          // "
+    IMGUI_API void          SetCursorPosX(float x);                                         // "
+    IMGUI_API void          SetCursorPosY(float y);                                         // "
+    IMGUI_API ImVec2        GetCursorStartPos();                                            // initial cursor position
+    IMGUI_API ImVec2        GetCursorScreenPos();                                           // cursor position in absolute screen coordinates [0..io.DisplaySize] (useful to work with ImDrawList API)
+    IMGUI_API void          SetCursorScreenPos(const ImVec2& screen_pos);                   // cursor position in absolute screen coordinates [0..io.DisplaySize]
+    IMGUI_API void          AlignTextToFramePadding();                                      // vertically align upcoming text baseline to FramePadding.y so that it will align properly to regularly framed items (call if you have text on a line before a framed item)
+    IMGUI_API float         GetTextLineHeight();                                            // ~ FontSize
+    IMGUI_API float         GetTextLineHeightWithSpacing();                                 // ~ FontSize + style.ItemSpacing.y (distance in pixels between 2 consecutive lines of text)
+    IMGUI_API float         GetFrameHeight();                                               // ~ FontSize + style.FramePadding.y * 2
+    IMGUI_API float         GetFrameHeightWithSpacing();                                    // ~ FontSize + style.FramePadding.y * 2 + style.ItemSpacing.y (distance in pixels between 2 consecutive lines of framed widgets)
+
+    // ID stack/scopes
+    // Read the FAQ for more details about how ID are handled in dear imgui. If you are creating widgets in a loop you most
+    // likely want to push a unique identifier (e.g. object pointer, loop index) to uniquely differentiate them.
+    // You can also use the "##foobar" syntax within widget label to distinguish them from each others.
+    // In this header file we use the "label"/"name" terminology to denote a string that will be displayed and used as an ID,
+    // whereas "str_id" denote a string that is only used as an ID and not aimed to be displayed.
+    IMGUI_API void          PushID(const char* str_id);                                     // push identifier into the ID stack. IDs are hash of the entire stack!
+    IMGUI_API void          PushID(const char* str_id_begin, const char* str_id_end);
+    IMGUI_API void          PushID(const void* ptr_id);
+    IMGUI_API void          PushID(int int_id);
+    IMGUI_API void          PopID();
+    IMGUI_API ImGuiID       GetID(const char* str_id);                                      // calculate unique ID (hash of whole ID stack + given parameter). e.g. if you want to query into ImGuiStorage yourself
+    IMGUI_API ImGuiID       GetID(const char* str_id_begin, const char* str_id_end);
+    IMGUI_API ImGuiID       GetID(const void* ptr_id);
+
+    // Widgets: Text
+    IMGUI_API void          TextUnformatted(const char* text, const char* text_end = NULL);                // raw text without formatting. Roughly equivalent to Text("%s", text) but: A) doesn't require null terminated string if 'text_end' is specified, B) it's faster, no memory copy is done, no buffer size limits, recommended for long chunks of text.
+    IMGUI_API void          Text(const char* fmt, ...)                                      IM_FMTARGS(1); // simple formatted text
+    IMGUI_API void          TextV(const char* fmt, va_list args)                            IM_FMTLIST(1);
+    IMGUI_API void          TextColored(const ImVec4& col, const char* fmt, ...)            IM_FMTARGS(2); // shortcut for PushStyleColor(ImGuiCol_Text, col); Text(fmt, ...); PopStyleColor();
+    IMGUI_API void          TextColoredV(const ImVec4& col, const char* fmt, va_list args)  IM_FMTLIST(2);
+    IMGUI_API void          TextDisabled(const char* fmt, ...)                              IM_FMTARGS(1); // shortcut for PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled]); Text(fmt, ...); PopStyleColor();
+    IMGUI_API void          TextDisabledV(const char* fmt, va_list args)                    IM_FMTLIST(1);
+    IMGUI_API void          TextWrapped(const char* fmt, ...)                               IM_FMTARGS(1); // shortcut for PushTextWrapPos(0.0f); Text(fmt, ...); PopTextWrapPos();. Note that this won't work on an auto-resizing window if there's no other widgets to extend the window width, yoy may need to set a size using SetNextWindowSize().
+    IMGUI_API void          TextWrappedV(const char* fmt, va_list args)                     IM_FMTLIST(1);
+    IMGUI_API void          LabelText(const char* label, const char* fmt, ...)              IM_FMTARGS(2); // display text+label aligned the same way as value+label widgets
+    IMGUI_API void          LabelTextV(const char* label, const char* fmt, va_list args)    IM_FMTLIST(2);
+    IMGUI_API void          BulletText(const char* fmt, ...)                                IM_FMTARGS(1); // shortcut for Bullet()+Text()
+    IMGUI_API void          BulletTextV(const char* fmt, va_list args)                      IM_FMTLIST(1);
+
+    // Widgets: Main
+    // Most widgets return true when the value has been changed or when pressed/selected
+    IMGUI_API bool          Button(const char* label, const ImVec2& size = ImVec2(0,0));    // button
+    IMGUI_API bool          SmallButton(const char* label);                                 // button with FramePadding=(0,0) to easily embed within text
+    IMGUI_API bool          InvisibleButton(const char* str_id, const ImVec2& size);        // button behavior without the visuals, useful to build custom behaviors using the public api (along with IsItemActive, IsItemHovered, etc.)
+    IMGUI_API bool          ArrowButton(const char* str_id, ImGuiDir dir);                  // square button with an arrow shape
+    IMGUI_API void          Image(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0 = ImVec2(0,0), const ImVec2& uv1 = ImVec2(1,1), const ImVec4& tint_col = ImVec4(1,1,1,1), const ImVec4& border_col = ImVec4(0,0,0,0));
+    IMGUI_API bool          ImageButton(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0 = ImVec2(0,0),  const ImVec2& uv1 = ImVec2(1,1), int frame_padding = -1, const ImVec4& bg_col = ImVec4(0,0,0,0), const ImVec4& tint_col = ImVec4(1,1,1,1));    // <0 frame_padding uses default frame padding settings. 0 for no padding
+    IMGUI_API bool          Checkbox(const char* label, bool* v);
+    IMGUI_API bool          CheckboxFlags(const char* label, unsigned int* flags, unsigned int flags_value);
+    IMGUI_API bool          RadioButton(const char* label, bool active);                    // use with e.g. if (RadioButton("one", my_value==1)) { my_value = 1; }
+    IMGUI_API bool          RadioButton(const char* label, int* v, int v_button);           // shortcut to handle the above pattern when value is an integer
+    IMGUI_API void          ProgressBar(float fraction, const ImVec2& size_arg = ImVec2(-1,0), const char* overlay = NULL);
+    IMGUI_API void          Bullet();                                                       // draw a small circle and keep the cursor on the same line. advance cursor x position by GetTreeNodeToLabelSpacing(), same distance that TreeNode() uses
+
+    // Widgets: Combo Box
+    // The new BeginCombo()/EndCombo() api allows you to manage your contents and selection state however you want it, by creating e.g. Selectable() items.
+    // The old Combo() api are helpers over BeginCombo()/EndCombo() which are kept available for convenience purpose.
+    IMGUI_API bool          BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags = 0);
+    IMGUI_API void          EndCombo(); // only call EndCombo() if BeginCombo() returns true!
+    IMGUI_API bool          Combo(const char* label, int* current_item, const char* const items[], int items_count, int popup_max_height_in_items = -1);
+    IMGUI_API bool          Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int popup_max_height_in_items = -1);      // Separate items with \0 within a string, end item-list with \0\0. e.g. "One\0Two\0Three\0"
+    IMGUI_API bool          Combo(const char* label, int* current_item, bool(*items_getter)(void* data, int idx, const char** out_text), void* data, int items_count, int popup_max_height_in_items = -1);
+
+    // Widgets: Drags (tip: ctrl+click on a drag box to input with keyboard. manually input values aren't clamped, can go off-bounds)
+    // For all the Float2/Float3/Float4/Int2/Int3/Int4 versions of every functions, note that a 'float v[X]' function argument is the same as 'float* v', the array syntax is just a way to document the number of elements that are expected to be accessible. You can pass address of your first element out of a contiguous set, e.g. &myvector.x
+    // Adjust format string to decorate the value with a prefix, a suffix, or adapt the editing and display precision e.g. "%.3f" -> 1.234; "%5.2f secs" -> 01.23 secs; "Biscuit: %.0f" -> Biscuit: 1; etc.
+    // Speed are per-pixel of mouse movement (v_speed=0.2f: mouse needs to move by 5 pixels to increase value by 1). For gamepad/keyboard navigation, minimum speed is Max(v_speed, minimum_step_at_given_precision).
+    IMGUI_API bool          DragFloat(const char* label, float* v, float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, const char* format = "%.3f", float power = 1.0f);     // If v_min >= v_max we have no bound
+    IMGUI_API bool          DragFloat2(const char* label, float v[2], float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, const char* format = "%.3f", float power = 1.0f);
+    IMGUI_API bool          DragFloat3(const char* label, float v[3], float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, const char* format = "%.3f", float power = 1.0f);
+    IMGUI_API bool          DragFloat4(const char* label, float v[4], float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, const char* format = "%.3f", float power = 1.0f);
+    IMGUI_API bool          DragFloatRange2(const char* label, float* v_current_min, float* v_current_max, float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, const char* format = "%.3f", const char* format_max = NULL, float power = 1.0f);
+    IMGUI_API bool          DragInt(const char* label, int* v, float v_speed = 1.0f, int v_min = 0, int v_max = 0, const char* format = "%d");                                       // If v_min >= v_max we have no bound
+    IMGUI_API bool          DragInt2(const char* label, int v[2], float v_speed = 1.0f, int v_min = 0, int v_max = 0, const char* format = "%d");
+    IMGUI_API bool          DragInt3(const char* label, int v[3], float v_speed = 1.0f, int v_min = 0, int v_max = 0, const char* format = "%d");
+    IMGUI_API bool          DragInt4(const char* label, int v[4], float v_speed = 1.0f, int v_min = 0, int v_max = 0, const char* format = "%d");
+    IMGUI_API bool          DragIntRange2(const char* label, int* v_current_min, int* v_current_max, float v_speed = 1.0f, int v_min = 0, int v_max = 0, const char* format = "%d", const char* format_max = NULL);
+    IMGUI_API bool          DragScalar(const char* label, ImGuiDataType data_type, void* v, float v_speed, const void* v_min = NULL, const void* v_max = NULL, const char* format = NULL, float power = 1.0f);
+    IMGUI_API bool          DragScalarN(const char* label, ImGuiDataType data_type, void* v, int components, float v_speed, const void* v_min = NULL, const void* v_max = NULL, const char* format = NULL, float power = 1.0f);
+
+    // Widgets: Sliders (tip: ctrl+click on a slider to input with keyboard. manually input values aren't clamped, can go off-bounds)
+    // Adjust format string to decorate the value with a prefix, a suffix, or adapt the editing and display precision e.g. "%.3f" -> 1.234; "%5.2f secs" -> 01.23 secs; "Biscuit: %.0f" -> Biscuit: 1; etc.
+    IMGUI_API bool          SliderFloat(const char* label, float* v, float v_min, float v_max, const char* format = "%.3f", float power = 1.0f);     // adjust format to decorate the value with a prefix or a suffix for in-slider labels or unit display. Use power!=1.0 for power curve sliders
+    IMGUI_API bool          SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* format = "%.3f", float power = 1.0f);
+    IMGUI_API bool          SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* format = "%.3f", float power = 1.0f);
+    IMGUI_API bool          SliderFloat4(const char* label, float v[4], float v_min, float v_max, const char* format = "%.3f", float power = 1.0f);
+    IMGUI_API bool          SliderAngle(const char* label, float* v_rad, float v_degrees_min = -360.0f, float v_degrees_max = +360.0f);
+    IMGUI_API bool          SliderInt(const char* label, int* v, int v_min, int v_max, const char* format = "%d");
+    IMGUI_API bool          SliderInt2(const char* label, int v[2], int v_min, int v_max, const char* format = "%d");
+    IMGUI_API bool          SliderInt3(const char* label, int v[3], int v_min, int v_max, const char* format = "%d");
+    IMGUI_API bool          SliderInt4(const char* label, int v[4], int v_min, int v_max, const char* format = "%d");
+    IMGUI_API bool          SliderScalar(const char* label, ImGuiDataType data_type, void* v, const void* v_min, const void* v_max, const char* format = NULL, float power = 1.0f);
+    IMGUI_API bool          SliderScalarN(const char* label, ImGuiDataType data_type, void* v, int components, const void* v_min, const void* v_max, const char* format = NULL, float power = 1.0f);
+    IMGUI_API bool          VSliderFloat(const char* label, const ImVec2& size, float* v, float v_min, float v_max, const char* format = "%.3f", float power = 1.0f);
+    IMGUI_API bool          VSliderInt(const char* label, const ImVec2& size, int* v, int v_min, int v_max, const char* format = "%d");
+    IMGUI_API bool          VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType data_type, void* v, const void* v_min, const void* v_max, const char* format = NULL, float power = 1.0f);
+
+    // Widgets: Input with Keyboard
+    // If you want to use InputText() with a dynamic string type such as std::string or your own, see misc/stl/imgui_stl.h
+    IMGUI_API bool          InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = NULL, void* user_data = NULL);
+    IMGUI_API bool          InputTextMultiline(const char* label, char* buf, size_t buf_size, const ImVec2& size = ImVec2(0,0), ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = NULL, void* user_data = NULL);
+    IMGUI_API bool          InputFloat(const char* label, float* v, float step = 0.0f, float step_fast = 0.0f, const char* format = "%.3f", ImGuiInputTextFlags extra_flags = 0);
+    IMGUI_API bool          InputFloat2(const char* label, float v[2], const char* format = "%.3f", ImGuiInputTextFlags extra_flags = 0);
+    IMGUI_API bool          InputFloat3(const char* label, float v[3], const char* format = "%.3f", ImGuiInputTextFlags extra_flags = 0);
+    IMGUI_API bool          InputFloat4(const char* label, float v[4], const char* format = "%.3f", ImGuiInputTextFlags extra_flags = 0);
+    IMGUI_API bool          InputInt(const char* label, int* v, int step = 1, int step_fast = 100, ImGuiInputTextFlags extra_flags = 0);
+    IMGUI_API bool          InputInt2(const char* label, int v[2], ImGuiInputTextFlags extra_flags = 0);
+    IMGUI_API bool          InputInt3(const char* label, int v[3], ImGuiInputTextFlags extra_flags = 0);
+    IMGUI_API bool          InputInt4(const char* label, int v[4], ImGuiInputTextFlags extra_flags = 0);
+    IMGUI_API bool          InputDouble(const char* label, double* v, double step = 0.0f, double step_fast = 0.0f, const char* format = "%.6f", ImGuiInputTextFlags extra_flags = 0);
+    IMGUI_API bool          InputScalar(const char* label, ImGuiDataType data_type, void* v, const void* step = NULL, const void* step_fast = NULL, const char* format = NULL, ImGuiInputTextFlags extra_flags = 0);
+    IMGUI_API bool          InputScalarN(const char* label, ImGuiDataType data_type, void* v, int components, const void* step = NULL, const void* step_fast = NULL, const char* format = NULL, ImGuiInputTextFlags extra_flags = 0);
+
+    // Widgets: Color Editor/Picker (tip: the ColorEdit* functions have a little colored preview square that can be left-clicked to open a picker, and right-clicked to open an option menu.)
+    // Note that a 'float v[X]' function argument is the same as 'float* v', the array syntax is just a way to document the number of elements that are expected to be accessible. You can the pass the address of a first float element out of a contiguous structure, e.g. &myvector.x
+    IMGUI_API bool          ColorEdit3(const char* label, float col[3], ImGuiColorEditFlags flags = 0);
+    IMGUI_API bool          ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags = 0);
+    IMGUI_API bool          ColorPicker3(const char* label, float col[3], ImGuiColorEditFlags flags = 0);
+    IMGUI_API bool          ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags flags = 0, const float* ref_col = NULL);
+    IMGUI_API bool          ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFlags flags = 0, ImVec2 size = ImVec2(0,0));  // display a colored square/button, hover for details, return true when pressed.
+    IMGUI_API void          SetColorEditOptions(ImGuiColorEditFlags flags);                     // initialize current options (generally on application startup) if you want to select a default format, picker type, etc. User will be able to change many settings, unless you pass the _NoOptions flag to your calls.
+
+    // Widgets: Trees
+    // TreeNode functions return true when the node is open, in which case you need to also call TreePop() when you are finished displaying the tree node contents.
+    IMGUI_API bool          TreeNode(const char* label);
+    IMGUI_API bool          TreeNode(const char* str_id, const char* fmt, ...) IM_FMTARGS(2);   // helper variation to completely decorelate the id from the displayed string. Read the FAQ about why and how to use ID. to align arbitrary text at the same level as a TreeNode() you can use Bullet().
+    IMGUI_API bool          TreeNode(const void* ptr_id, const char* fmt, ...) IM_FMTARGS(2);   // "
+    IMGUI_API bool          TreeNodeV(const char* str_id, const char* fmt, va_list args) IM_FMTLIST(2);
+    IMGUI_API bool          TreeNodeV(const void* ptr_id, const char* fmt, va_list args) IM_FMTLIST(2);
+    IMGUI_API bool          TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags = 0);
+    IMGUI_API bool          TreeNodeEx(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, ...) IM_FMTARGS(3);
+    IMGUI_API bool          TreeNodeEx(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, ...) IM_FMTARGS(3);
+    IMGUI_API bool          TreeNodeExV(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args) IM_FMTLIST(3);
+    IMGUI_API bool          TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args) IM_FMTLIST(3);
+    IMGUI_API void          TreePush(const char* str_id);                                       // ~ Indent()+PushId(). Already called by TreeNode() when returning true, but you can call TreePush/TreePop yourself if desired.
+    IMGUI_API void          TreePush(const void* ptr_id = NULL);                                // "
+    IMGUI_API void          TreePop();                                                          // ~ Unindent()+PopId()
+    IMGUI_API void          TreeAdvanceToLabelPos();                                            // advance cursor x position by GetTreeNodeToLabelSpacing()
+    IMGUI_API float         GetTreeNodeToLabelSpacing();                                        // horizontal distance preceding label when using TreeNode*() or Bullet() == (g.FontSize + style.FramePadding.x*2) for a regular unframed TreeNode
+    IMGUI_API void          SetNextTreeNodeOpen(bool is_open, ImGuiCond cond = 0);              // set next TreeNode/CollapsingHeader open state.
+    IMGUI_API bool          CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags = 0);  // if returning 'true' the header is open. doesn't indent nor push on ID stack. user doesn't have to call TreePop().
+    IMGUI_API bool          CollapsingHeader(const char* label, bool* p_open, ImGuiTreeNodeFlags flags = 0); // when 'p_open' isn't NULL, display an additional small close button on upper right of the header
+
+    // Widgets: Selectables
+    IMGUI_API bool          Selectable(const char* label, bool selected = false, ImGuiSelectableFlags flags = 0, const ImVec2& size = ImVec2(0,0));  // "bool selected" carry the selection state (read-only). Selectable() is clicked is returns true so you can modify your selection state. size.x==0.0: use remaining width, size.x>0.0: specify width. size.y==0.0: use label height, size.y>0.0: specify height
+    IMGUI_API bool          Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags flags = 0, const ImVec2& size = ImVec2(0,0));       // "bool* p_selected" point to the selection state (read-write), as a convenient helper.
+
+    // Widgets: List Boxes
+    IMGUI_API bool          ListBox(const char* label, int* current_item, const char* const items[], int items_count, int height_in_items = -1);
+    IMGUI_API bool          ListBox(const char* label, int* current_item, bool (*items_getter)(void* data, int idx, const char** out_text), void* data, int items_count, int height_in_items = -1);
+    IMGUI_API bool          ListBoxHeader(const char* label, const ImVec2& size = ImVec2(0,0)); // use if you want to reimplement ListBox() will custom data or interactions. if the function return true, you can output elements then call ListBoxFooter() afterwards.
+    IMGUI_API bool          ListBoxHeader(const char* label, int items_count, int height_in_items = -1); // "
+    IMGUI_API void          ListBoxFooter();                                                    // terminate the scrolling region. only call ListBoxFooter() if ListBoxHeader() returned true!
+
+    // Widgets: Data Plotting
+    IMGUI_API void          PlotLines(const char* label, const float* values, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0, 0), int stride = sizeof(float));
+    IMGUI_API void          PlotLines(const char* label, float(*values_getter)(void* data, int idx), void* data, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0, 0));
+    IMGUI_API void          PlotHistogram(const char* label, const float* values, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0, 0), int stride = sizeof(float));
+    IMGUI_API void          PlotHistogram(const char* label, float(*values_getter)(void* data, int idx), void* data, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0, 0));
+
+    // Widgets: Value() Helpers. Output single value in "name: value" format (tip: freely declare more in your code to handle your types. you can add functions to the ImGui namespace)
+    IMGUI_API void          Value(const char* prefix, bool b);
+    IMGUI_API void          Value(const char* prefix, int v);
+    IMGUI_API void          Value(const char* prefix, unsigned int v);
+    IMGUI_API void          Value(const char* prefix, float v, const char* float_format = NULL);
+
+    // Widgets: Menus
+    IMGUI_API bool          BeginMainMenuBar();                                                 // create and append to a full screen menu-bar.
+    IMGUI_API void          EndMainMenuBar();                                                   // only call EndMainMenuBar() if BeginMainMenuBar() returns true!
+    IMGUI_API bool          BeginMenuBar();                                                     // append to menu-bar of current window (requires ImGuiWindowFlags_MenuBar flag set on parent window).
+    IMGUI_API void          EndMenuBar();                                                       // only call EndMenuBar() if BeginMenuBar() returns true!
+    IMGUI_API bool          BeginMenu(const char* label, bool enabled = true);                  // create a sub-menu entry. only call EndMenu() if this returns true!
+    IMGUI_API void          EndMenu();                                                          // only call EndMenu() if BeginMenu() returns true!
+    IMGUI_API bool          MenuItem(const char* label, const char* shortcut = NULL, bool selected = false, bool enabled = true);  // return true when activated. shortcuts are displayed for convenience but not processed by ImGui at the moment
+    IMGUI_API bool          MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled = true);              // return true when activated + toggle (*p_selected) if p_selected != NULL
+
+    // Tooltips
+    IMGUI_API void          BeginTooltip();                                                     // begin/append a tooltip window. to create full-featured tooltip (with any kind of items).
+    IMGUI_API void          EndTooltip();
+    IMGUI_API void          SetTooltip(const char* fmt, ...) IM_FMTARGS(1);                     // set a text-only tooltip, typically use with ImGui::IsItemHovered(). overidde any previous call to SetTooltip().
+    IMGUI_API void          SetTooltipV(const char* fmt, va_list args) IM_FMTLIST(1);
+
+    // Popups
+    IMGUI_API void          OpenPopup(const char* str_id);                                      // call to mark popup as open (don't call every frame!). popups are closed when user click outside, or if CloseCurrentPopup() is called within a BeginPopup()/EndPopup() block. By default, Selectable()/MenuItem() are calling CloseCurrentPopup(). Popup identifiers are relative to the current ID-stack (so OpenPopup and BeginPopup needs to be at the same level).
+    IMGUI_API bool          BeginPopup(const char* str_id, ImGuiWindowFlags flags = 0);                                             // return true if the popup is open, and you can start outputting to it. only call EndPopup() if BeginPopup() returns true!
+    IMGUI_API bool          BeginPopupContextItem(const char* str_id = NULL, int mouse_button = 1);                                 // helper to open and begin popup when clicked on last item. if you can pass a NULL str_id only if the previous item had an id. If you want to use that on a non-interactive item such as Text() you need to pass in an explicit ID here. read comments in .cpp!
+    IMGUI_API bool          BeginPopupContextWindow(const char* str_id = NULL, int mouse_button = 1, bool also_over_items = true);  // helper to open and begin popup when clicked on current window.
+    IMGUI_API bool          BeginPopupContextVoid(const char* str_id = NULL, int mouse_button = 1);                                 // helper to open and begin popup when clicked in void (where there are no imgui windows).
+    IMGUI_API bool          BeginPopupModal(const char* name, bool* p_open = NULL, ImGuiWindowFlags flags = 0);                     // modal dialog (regular window with title bar, block interactions behind the modal window, can't close the modal window by clicking outside)
+    IMGUI_API void          EndPopup();                                                                                             // only call EndPopup() if BeginPopupXXX() returns true!
+    IMGUI_API bool          OpenPopupOnItemClick(const char* str_id = NULL, int mouse_button = 1);                                  // helper to open popup when clicked on last item. return true when just opened.
+    IMGUI_API bool          IsPopupOpen(const char* str_id);                                    // return true if the popup is open
+    IMGUI_API void          CloseCurrentPopup();                                                // close the popup we have begin-ed into. clicking on a MenuItem or Selectable automatically close the current popup.
+
+    // Columns
+    // You can also use SameLine(pos_x) for simplified columns. The columns API is still work-in-progress and rather lacking.
+    IMGUI_API void          Columns(int count = 1, const char* id = NULL, bool border = true);
+    IMGUI_API void          NextColumn();                                                       // next column, defaults to current row or next row if the current row is finished
+    IMGUI_API int           GetColumnIndex();                                                   // get current column index
+    IMGUI_API float         GetColumnWidth(int column_index = -1);                              // get column width (in pixels). pass -1 to use current column
+    IMGUI_API void          SetColumnWidth(int column_index, float width);                      // set column width (in pixels). pass -1 to use current column
+    IMGUI_API float         GetColumnOffset(int column_index = -1);                             // get position of column line (in pixels, from the left side of the contents region). pass -1 to use current column, otherwise 0..GetColumnsCount() inclusive. column 0 is typically 0.0f
+    IMGUI_API void          SetColumnOffset(int column_index, float offset_x);                  // set position of column line (in pixels, from the left side of the contents region). pass -1 to use current column
+    IMGUI_API int           GetColumnsCount();
+
+    // Logging/Capture: all text output from interface is captured to tty/file/clipboard. By default, tree nodes are automatically opened during logging.
+    IMGUI_API void          LogToTTY(int max_depth = -1);                                       // start logging to tty
+    IMGUI_API void          LogToFile(int max_depth = -1, const char* filename = NULL);         // start logging to file
+    IMGUI_API void          LogToClipboard(int max_depth = -1);                                 // start logging to OS clipboard
+    IMGUI_API void          LogFinish();                                                        // stop logging (close file, etc.)
+    IMGUI_API void          LogButtons();                                                       // helper to display buttons for logging to tty/file/clipboard
+    IMGUI_API void          LogText(const char* fmt, ...) IM_FMTARGS(1);                        // pass text data straight to log (without being displayed)
+
+    // Drag and Drop
+    // [BETA API] Missing Demo code. API may evolve.
+    IMGUI_API bool          BeginDragDropSource(ImGuiDragDropFlags flags = 0);                                      // call when the current item is active. If this return true, you can call SetDragDropPayload() + EndDragDropSource()
+    IMGUI_API bool          SetDragDropPayload(const char* type, const void* data, size_t size, ImGuiCond cond = 0);// type is a user defined string of maximum 32 characters. Strings starting with '_' are reserved for dear imgui internal types. Data is copied and held by imgui.
+    IMGUI_API void          EndDragDropSource();                                                                    // only call EndDragDropSource() if BeginDragDropSource() returns true!
+    IMGUI_API bool          BeginDragDropTarget();                                                                  // call after submitting an item that may receive an item. If this returns true, you can call AcceptDragDropPayload() + EndDragDropTarget()
+    IMGUI_API const ImGuiPayload* AcceptDragDropPayload(const char* type, ImGuiDragDropFlags flags = 0);            // accept contents of a given type. If ImGuiDragDropFlags_AcceptBeforeDelivery is set you can peek into the payload before the mouse button is released.
+    IMGUI_API void          EndDragDropTarget();                                                                    // only call EndDragDropTarget() if BeginDragDropTarget() returns true!
+
+    // Clipping
+    IMGUI_API void          PushClipRect(const ImVec2& clip_rect_min, const ImVec2& clip_rect_max, bool intersect_with_current_clip_rect);
+    IMGUI_API void          PopClipRect();
+
+    // Focus, Activation
+    // (Prefer using "SetItemDefaultFocus()" over "if (IsWindowAppearing()) SetScrollHere()" when applicable, to make your code more forward compatible when navigation branch is merged)
+    IMGUI_API void          SetItemDefaultFocus();                                              // make last item the default focused item of a window. Please use instead of "if (IsWindowAppearing()) SetScrollHere()" to signify "default item".
+    IMGUI_API void          SetKeyboardFocusHere(int offset = 0);                               // focus keyboard on the next widget. Use positive 'offset' to access sub components of a multiple component widget. Use -1 to access previous widget.
+
+    // Utilities
+    // See Demo Window under "Widgets->Querying Status" for an interactive visualization of many of those functions.
+    IMGUI_API bool          IsItemHovered(ImGuiHoveredFlags flags = 0);                         // is the last item hovered? (and usable, aka not blocked by a popup, etc.). See ImGuiHoveredFlags for more options.
+    IMGUI_API bool          IsItemActive();                                                     // is the last item active? (e.g. button being held, text field being edited. This will continuously return true while holding mouse button on an item. Items that don't interact will always return false)
+    IMGUI_API bool          IsItemFocused();                                                    // is the last item focused for keyboard/gamepad navigation?
+    IMGUI_API bool          IsItemClicked(int mouse_button = 0);                                // is the last item clicked? (e.g. button/node just clicked on) == IsMouseClicked(mouse_button) && IsItemHovered()
+    IMGUI_API bool          IsItemVisible();                                                    // is the last item visible? (items may be out of sight because of clipping/scrolling)
+    IMGUI_API bool          IsItemEdited();                                                     // did the last item modify its underlying value this frame? or was pressed? This is generally the same as the "bool" return value of many widgets.
+    IMGUI_API bool          IsItemDeactivated();                                                // was the last item just made inactive (item was previously active). Useful for Undo/Redo patterns with widgets that requires continuous editing.
+    IMGUI_API bool          IsItemDeactivatedAfterEdit();                                       // was the last item just made inactive and made a value change when it was active? (e.g. Slider/Drag moved). Useful for Undo/Redo patterns with widgets that requires continuous editing. Note that you may get false positives (some widgets such as Combo()/ListBox()/Selectable() will return true even when clicking an already selected item).
+    IMGUI_API bool          IsAnyItemHovered();
+    IMGUI_API bool          IsAnyItemActive();
+    IMGUI_API bool          IsAnyItemFocused();
+    IMGUI_API ImVec2        GetItemRectMin();                                                   // get bounding rectangle of last item, in screen space
+    IMGUI_API ImVec2        GetItemRectMax();                                                   // "
+    IMGUI_API ImVec2        GetItemRectSize();                                                  // get size of last item, in screen space
+    IMGUI_API void          SetItemAllowOverlap();                                              // allow last item to be overlapped by a subsequent item. sometimes useful with invisible buttons, selectables, etc. to catch unused area.
+    IMGUI_API bool          IsRectVisible(const ImVec2& size);                                  // test if rectangle (of given size, starting from cursor position) is visible / not clipped.
+    IMGUI_API bool          IsRectVisible(const ImVec2& rect_min, const ImVec2& rect_max);      // test if rectangle (in screen space) is visible / not clipped. to perform coarse clipping on user's side.
+    IMGUI_API double        GetTime();
+    IMGUI_API int           GetFrameCount();
+    IMGUI_API ImDrawList*   GetOverlayDrawList();                                               // this draw list will be the last rendered one, useful to quickly draw overlays shapes/text
+    IMGUI_API ImDrawListSharedData* GetDrawListSharedData();                                    // you may use this when creating your own ImDrawList instances
+    IMGUI_API const char*   GetStyleColorName(ImGuiCol idx);
+    IMGUI_API void          SetStateStorage(ImGuiStorage* storage);                             // replace current window storage with our own (if you want to manipulate it yourself, typically clear subsection of it)
+    IMGUI_API ImGuiStorage* GetStateStorage();
+    IMGUI_API ImVec2        CalcTextSize(const char* text, const char* text_end = NULL, bool hide_text_after_double_hash = false, float wrap_width = -1.0f);
+    IMGUI_API void          CalcListClipping(int items_count, float items_height, int* out_items_display_start, int* out_items_display_end);    // calculate coarse clipping for large list of evenly sized items. Prefer using the ImGuiListClipper higher-level helper if you can.
+
+    IMGUI_API bool          BeginChildFrame(ImGuiID id, const ImVec2& size, ImGuiWindowFlags flags = 0); // helper to create a child window / scrolling region that looks like a normal widget frame
+    IMGUI_API void          EndChildFrame();                                                    // always call EndChildFrame() regardless of BeginChildFrame() return values (which indicates a collapsed/clipped window)
+
+    IMGUI_API ImVec4        ColorConvertU32ToFloat4(ImU32 in);
+    IMGUI_API ImU32         ColorConvertFloat4ToU32(const ImVec4& in);
+    IMGUI_API void          ColorConvertRGBtoHSV(float r, float g, float b, float& out_h, float& out_s, float& out_v);
+    IMGUI_API void          ColorConvertHSVtoRGB(float h, float s, float v, float& out_r, float& out_g, float& out_b);
+
+    // Inputs
+    IMGUI_API int           GetKeyIndex(ImGuiKey imgui_key);                                    // map ImGuiKey_* values into user's key index. == io.KeyMap[key]
+    IMGUI_API bool          IsKeyDown(int user_key_index);                                      // is key being held. == io.KeysDown[user_key_index]. note that imgui doesn't know the semantic of each entry of io.KeysDown[]. Use your own indices/enums according to how your backend/engine stored them into io.KeysDown[]!
+    IMGUI_API bool          IsKeyPressed(int user_key_index, bool repeat = true);               // was key pressed (went from !Down to Down). if repeat=true, uses io.KeyRepeatDelay / KeyRepeatRate
+    IMGUI_API bool          IsKeyReleased(int user_key_index);                                  // was key released (went from Down to !Down)..
+    IMGUI_API int           GetKeyPressedAmount(int key_index, float repeat_delay, float rate); // uses provided repeat rate/delay. return a count, most often 0 or 1 but might be >1 if RepeatRate is small enough that DeltaTime > RepeatRate
+    IMGUI_API bool          IsMouseDown(int button);                                            // is mouse button held (0=left, 1=right, 2=middle)
+    IMGUI_API bool          IsAnyMouseDown();                                                   // is any mouse button held
+    IMGUI_API bool          IsMouseClicked(int button, bool repeat = false);                    // did mouse button clicked (went from !Down to Down) (0=left, 1=right, 2=middle)
+    IMGUI_API bool          IsMouseDoubleClicked(int button);                                   // did mouse button double-clicked. a double-click returns false in IsMouseClicked(). uses io.MouseDoubleClickTime.
+    IMGUI_API bool          IsMouseReleased(int button);                                        // did mouse button released (went from Down to !Down)
+    IMGUI_API bool          IsMouseDragging(int button = 0, float lock_threshold = -1.0f);      // is mouse dragging. if lock_threshold < -1.0f uses io.MouseDraggingThreshold
+    IMGUI_API bool          IsMouseHoveringRect(const ImVec2& r_min, const ImVec2& r_max, bool clip = true);  // is mouse hovering given bounding rect (in screen space). clipped by current clipping settings, but  disregarding of other consideration of focus/window ordering/popup-block.
+    IMGUI_API bool          IsMousePosValid(const ImVec2* mouse_pos = NULL);                    //
+    IMGUI_API ImVec2        GetMousePos();                                                      // shortcut to ImGui::GetIO().MousePos provided by user, to be consistent with other calls
+    IMGUI_API ImVec2        GetMousePosOnOpeningCurrentPopup();                                 // retrieve backup of mouse position at the time of opening popup we have BeginPopup() into
+    IMGUI_API ImVec2        GetMouseDragDelta(int button = 0, float lock_threshold = -1.0f);    // dragging amount since clicking. if lock_threshold < -1.0f uses io.MouseDraggingThreshold
+    IMGUI_API void          ResetMouseDragDelta(int button = 0);                                //
+    IMGUI_API ImGuiMouseCursor GetMouseCursor();                                                // get desired cursor type, reset in ImGui::NewFrame(), this is updated during the frame. valid before Render(). If you use software rendering by setting io.MouseDrawCursor ImGui will render those for you
+    IMGUI_API void          SetMouseCursor(ImGuiMouseCursor type);                              // set desired cursor type
+    IMGUI_API void          CaptureKeyboardFromApp(bool capture = true);                        // manually override io.WantCaptureKeyboard flag next frame (said flag is entirely left for your application to handle). e.g. force capture keyboard when your widget is being hovered.
+    IMGUI_API void          CaptureMouseFromApp(bool capture = true);                           // manually override io.WantCaptureMouse flag next frame (said flag is entirely left for your application to handle).
+
+    // Clipboard Utilities (also see the LogToClipboard() function to capture or output text data to the clipboard)
+    IMGUI_API const char*   GetClipboardText();
+    IMGUI_API void          SetClipboardText(const char* text);
+
+    // Settings/.Ini Utilities
+    // The disk functions are automatically called if io.IniFilename != NULL (default is "imgui.ini").
+    // Set io.IniFilename to NULL to load/save manually. Read io.WantSaveIniSettings description about handling .ini saving manually.
+    IMGUI_API void          LoadIniSettingsFromDisk(const char* ini_filename);                  // call after CreateContext() and before the first call to NewFrame(). NewFrame() automatically calls LoadIniSettingsFromDisk(io.IniFilename).
+    IMGUI_API void          LoadIniSettingsFromMemory(const char* ini_data, size_t ini_size=0); // call after CreateContext() and before the first call to NewFrame() to provide .ini data from your own data source.
+    IMGUI_API void          SaveIniSettingsToDisk(const char* ini_filename);
+    IMGUI_API const char*   SaveIniSettingsToMemory(size_t* out_ini_size = NULL);               // return a zero-terminated string with the .ini data which you can save by your own mean. call when io.WantSaveIniSettings is set, then save data by your own mean and clear io.WantSaveIniSettings.
+
+    // Memory Utilities
+    // All those functions are not reliant on the current context.
+    // If you reload the contents of imgui.cpp at runtime, you may need to call SetCurrentContext() + SetAllocatorFunctions() again.
+    IMGUI_API void          SetAllocatorFunctions(void* (*alloc_func)(size_t sz, void* user_data), void(*free_func)(void* ptr, void* user_data), void* user_data = NULL);
+    IMGUI_API void*         MemAlloc(size_t size);
+    IMGUI_API void          MemFree(void* ptr);
+
+} // namespace ImGui
+
+// Flags for ImGui::Begin()
+enum ImGuiWindowFlags_
+{
+    ImGuiWindowFlags_None                   = 0,
+    ImGuiWindowFlags_NoTitleBar             = 1 << 0,   // Disable title-bar
+    ImGuiWindowFlags_NoResize               = 1 << 1,   // Disable user resizing with the lower-right grip
+    ImGuiWindowFlags_NoMove                 = 1 << 2,   // Disable user moving the window
+    ImGuiWindowFlags_NoScrollbar            = 1 << 3,   // Disable scrollbars (window can still scroll with mouse or programatically)
+    ImGuiWindowFlags_NoScrollWithMouse      = 1 << 4,   // Disable user vertically scrolling with mouse wheel. On child window, mouse wheel will be forwarded to the parent unless NoScrollbar is also set.
+    ImGuiWindowFlags_NoCollapse             = 1 << 5,   // Disable user collapsing window by double-clicking on it
+    ImGuiWindowFlags_AlwaysAutoResize       = 1 << 6,   // Resize every window to its content every frame
+    ImGuiWindowFlags_NoSavedSettings        = 1 << 8,   // Never load/save settings in .ini file
+    ImGuiWindowFlags_NoInputs               = 1 << 9,   // Disable catching mouse or keyboard inputs, hovering test with pass through.
+    ImGuiWindowFlags_MenuBar                = 1 << 10,  // Has a menu-bar
+    ImGuiWindowFlags_HorizontalScrollbar    = 1 << 11,  // Allow horizontal scrollbar to appear (off by default). You may use SetNextWindowContentSize(ImVec2(width,0.0f)); prior to calling Begin() to specify width. Read code in imgui_demo in the "Horizontal Scrolling" section.
+    ImGuiWindowFlags_NoFocusOnAppearing     = 1 << 12,  // Disable taking focus when transitioning from hidden to visible state
+    ImGuiWindowFlags_NoBringToFrontOnFocus  = 1 << 13,  // Disable bringing window to front when taking focus (e.g. clicking on it or programatically giving it focus)
+    ImGuiWindowFlags_AlwaysVerticalScrollbar= 1 << 14,  // Always show vertical scrollbar (even if ContentSize.y < Size.y)
+    ImGuiWindowFlags_AlwaysHorizontalScrollbar=1<< 15,  // Always show horizontal scrollbar (even if ContentSize.x < Size.x)
+    ImGuiWindowFlags_AlwaysUseWindowPadding = 1 << 16,  // Ensure child windows without border uses style.WindowPadding (ignored by default for non-bordered child windows, because more convenient)
+    ImGuiWindowFlags_NoNavInputs            = 1 << 18,  // No gamepad/keyboard navigation within the window
+    ImGuiWindowFlags_NoNavFocus             = 1 << 19,  // No focusing toward this window with gamepad/keyboard navigation (e.g. skipped by CTRL+TAB)
+    ImGuiWindowFlags_NoNav                  = ImGuiWindowFlags_NoNavInputs | ImGuiWindowFlags_NoNavFocus,
+
+    // [Internal]
+    ImGuiWindowFlags_NavFlattened           = 1 << 23,  // [BETA] Allow gamepad/keyboard navigation to cross over parent border to this child (only use on child that have no scrolling!)
+    ImGuiWindowFlags_ChildWindow            = 1 << 24,  // Don't use! For internal use by BeginChild()
+    ImGuiWindowFlags_Tooltip                = 1 << 25,  // Don't use! For internal use by BeginTooltip()
+    ImGuiWindowFlags_Popup                  = 1 << 26,  // Don't use! For internal use by BeginPopup()
+    ImGuiWindowFlags_Modal                  = 1 << 27,  // Don't use! For internal use by BeginPopupModal()
+    ImGuiWindowFlags_ChildMenu              = 1 << 28   // Don't use! For internal use by BeginMenu()
+
+    // [Obsolete]
+    //ImGuiWindowFlags_ShowBorders          = 1 << 7,   // --> Set style.FrameBorderSize=1.0f / style.WindowBorderSize=1.0f to enable borders around windows and items
+    //ImGuiWindowFlags_ResizeFromAnySide    = 1 << 17,  // --> Set io.ConfigResizeWindowsFromEdges and make sure mouse cursors are supported by back-end (io.BackendFlags & ImGuiBackendFlags_HasMouseCursors)
+};
+
+// Flags for ImGui::InputText()
+enum ImGuiInputTextFlags_
+{
+    ImGuiInputTextFlags_None                = 0,
+    ImGuiInputTextFlags_CharsDecimal        = 1 << 0,   // Allow 0123456789.+-*/
+    ImGuiInputTextFlags_CharsHexadecimal    = 1 << 1,   // Allow 0123456789ABCDEFabcdef
+    ImGuiInputTextFlags_CharsUppercase      = 1 << 2,   // Turn a..z into A..Z
+    ImGuiInputTextFlags_CharsNoBlank        = 1 << 3,   // Filter out spaces, tabs
+    ImGuiInputTextFlags_AutoSelectAll       = 1 << 4,   // Select entire text when first taking mouse focus
+    ImGuiInputTextFlags_EnterReturnsTrue    = 1 << 5,   // Return 'true' when Enter is pressed (as opposed to when the value was modified)
+    ImGuiInputTextFlags_CallbackCompletion  = 1 << 6,   // Call user function on pressing TAB (for completion handling)
+    ImGuiInputTextFlags_CallbackHistory     = 1 << 7,   // Call user function on pressing Up/Down arrows (for history handling)
+    ImGuiInputTextFlags_CallbackAlways      = 1 << 8,   // Call user function every time. User code may query cursor position, modify text buffer.
+    ImGuiInputTextFlags_CallbackCharFilter  = 1 << 9,   // Call user function to filter character. Modify data->EventChar to replace/filter input, or return 1 in callback to discard character.
+    ImGuiInputTextFlags_AllowTabInput       = 1 << 10,  // Pressing TAB input a '\t' character into the text field
+    ImGuiInputTextFlags_CtrlEnterForNewLine = 1 << 11,  // In multi-line mode, unfocus with Enter, add new line with Ctrl+Enter (default is opposite: unfocus with Ctrl+Enter, add line with Enter).
+    ImGuiInputTextFlags_NoHorizontalScroll  = 1 << 12,  // Disable following the cursor horizontally
+    ImGuiInputTextFlags_AlwaysInsertMode    = 1 << 13,  // Insert mode
+    ImGuiInputTextFlags_ReadOnly            = 1 << 14,  // Read-only mode
+    ImGuiInputTextFlags_Password            = 1 << 15,  // Password mode, display all characters as '*'
+    ImGuiInputTextFlags_NoUndoRedo          = 1 << 16,  // Disable undo/redo. Note that input text owns the text data while active, if you want to provide your own undo/redo stack you need e.g. to call ClearActiveID().
+    ImGuiInputTextFlags_CharsScientific     = 1 << 17,  // Allow 0123456789.+-*/eE (Scientific notation input)
+    ImGuiInputTextFlags_CallbackResize      = 1 << 18,  // Allow buffer capacity resize + notify when the string wants to be resized (for string types which hold a cache of their Size) (see misc/stl/imgui_stl.h for an example of using this)
+    // [Internal]
+    ImGuiInputTextFlags_Multiline           = 1 << 20   // For internal use by InputTextMultiline()
+};
+
+// Flags for ImGui::TreeNodeEx(), ImGui::CollapsingHeader*()
+enum ImGuiTreeNodeFlags_
+{
+    ImGuiTreeNodeFlags_None                 = 0,
+    ImGuiTreeNodeFlags_Selected             = 1 << 0,   // Draw as selected
+    ImGuiTreeNodeFlags_Framed               = 1 << 1,   // Full colored frame (e.g. for CollapsingHeader)
+    ImGuiTreeNodeFlags_AllowItemOverlap     = 1 << 2,   // Hit testing to allow subsequent widgets to overlap this one
+    ImGuiTreeNodeFlags_NoTreePushOnOpen     = 1 << 3,   // Don't do a TreePush() when open (e.g. for CollapsingHeader) = no extra indent nor pushing on ID stack
+    ImGuiTreeNodeFlags_NoAutoOpenOnLog      = 1 << 4,   // Don't automatically and temporarily open node when Logging is active (by default logging will automatically open tree nodes)
+    ImGuiTreeNodeFlags_DefaultOpen          = 1 << 5,   // Default node to be open
+    ImGuiTreeNodeFlags_OpenOnDoubleClick    = 1 << 6,   // Need double-click to open node
+    ImGuiTreeNodeFlags_OpenOnArrow          = 1 << 7,   // Only open when clicking on the arrow part. If ImGuiTreeNodeFlags_OpenOnDoubleClick is also set, single-click arrow or double-click all box to open.
+    ImGuiTreeNodeFlags_Leaf                 = 1 << 8,   // No collapsing, no arrow (use as a convenience for leaf nodes).
+    ImGuiTreeNodeFlags_Bullet               = 1 << 9,   // Display a bullet instead of arrow
+    ImGuiTreeNodeFlags_FramePadding         = 1 << 10,  // Use FramePadding (even for an unframed text node) to vertically align text baseline to regular widget height. Equivalent to calling AlignTextToFramePadding().
+    //ImGuITreeNodeFlags_SpanAllAvailWidth  = 1 << 11,  // FIXME: TODO: Extend hit box horizontally even if not framed
+    //ImGuiTreeNodeFlags_NoScrollOnOpen     = 1 << 12,  // FIXME: TODO: Disable automatic scroll on TreePop() if node got just open and contents is not visible
+    ImGuiTreeNodeFlags_NavLeftJumpsBackHere = 1 << 13,  // (WIP) Nav: left direction may move to this TreeNode() from any of its child (items submitted between TreeNode and TreePop)
+    ImGuiTreeNodeFlags_CollapsingHeader     = ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_NoAutoOpenOnLog
+
+    // Obsolete names (will be removed)
+#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
+    , ImGuiTreeNodeFlags_AllowOverlapMode = ImGuiTreeNodeFlags_AllowItemOverlap
+#endif
+};
+
+// Flags for ImGui::Selectable()
+enum ImGuiSelectableFlags_
+{
+    ImGuiSelectableFlags_None               = 0,
+    ImGuiSelectableFlags_DontClosePopups    = 1 << 0,   // Clicking this don't close parent popup window
+    ImGuiSelectableFlags_SpanAllColumns     = 1 << 1,   // Selectable frame can span all columns (text will still fit in current column)
+    ImGuiSelectableFlags_AllowDoubleClick   = 1 << 2,   // Generate press events on double clicks too
+    ImGuiSelectableFlags_Disabled           = 1 << 3    // Cannot be selected, display greyed out text
+};
+
+// Flags for ImGui::BeginCombo()
+enum ImGuiComboFlags_
+{
+    ImGuiComboFlags_None                    = 0,
+    ImGuiComboFlags_PopupAlignLeft          = 1 << 0,   // Align the popup toward the left by default
+    ImGuiComboFlags_HeightSmall             = 1 << 1,   // Max ~4 items visible. Tip: If you want your combo popup to be a specific size you can use SetNextWindowSizeConstraints() prior to calling BeginCombo()
+    ImGuiComboFlags_HeightRegular           = 1 << 2,   // Max ~8 items visible (default)
+    ImGuiComboFlags_HeightLarge             = 1 << 3,   // Max ~20 items visible
+    ImGuiComboFlags_HeightLargest           = 1 << 4,   // As many fitting items as possible
+    ImGuiComboFlags_NoArrowButton           = 1 << 5,   // Display on the preview box without the square arrow button
+    ImGuiComboFlags_NoPreview               = 1 << 6,   // Display only a square arrow button
+    ImGuiComboFlags_HeightMask_             = ImGuiComboFlags_HeightSmall | ImGuiComboFlags_HeightRegular | ImGuiComboFlags_HeightLarge | ImGuiComboFlags_HeightLargest
+};
+
+// Flags for ImGui::IsWindowFocused()
+enum ImGuiFocusedFlags_
+{
+    ImGuiFocusedFlags_None                          = 0,
+    ImGuiFocusedFlags_ChildWindows                  = 1 << 0,   // IsWindowFocused(): Return true if any children of the window is focused
+    ImGuiFocusedFlags_RootWindow                    = 1 << 1,   // IsWindowFocused(): Test from root window (top most parent of the current hierarchy)
+    ImGuiFocusedFlags_AnyWindow                     = 1 << 2,   // IsWindowFocused(): Return true if any window is focused
+    ImGuiFocusedFlags_RootAndChildWindows           = ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_ChildWindows
+};
+
+// Flags for ImGui::IsItemHovered(), ImGui::IsWindowHovered()
+// Note: if you are trying to check whether your mouse should be dispatched to imgui or to your app, you should use the 'io.WantCaptureMouse' boolean for that. Please read the FAQ!
+// Note: windows with the ImGuiWindowFlags_NoInputs flag are ignored by IsWindowHovered() calls.
+enum ImGuiHoveredFlags_
+{
+    ImGuiHoveredFlags_None                          = 0,        // Return true if directly over the item/window, not obstructed by another window, not obstructed by an active popup or modal blocking inputs under them.
+    ImGuiHoveredFlags_ChildWindows                  = 1 << 0,   // IsWindowHovered() only: Return true if any children of the window is hovered
+    ImGuiHoveredFlags_RootWindow                    = 1 << 1,   // IsWindowHovered() only: Test from root window (top most parent of the current hierarchy)
+    ImGuiHoveredFlags_AnyWindow                     = 1 << 2,   // IsWindowHovered() only: Return true if any window is hovered
+    ImGuiHoveredFlags_AllowWhenBlockedByPopup       = 1 << 3,   // Return true even if a popup window is normally blocking access to this item/window
+    //ImGuiHoveredFlags_AllowWhenBlockedByModal     = 1 << 4,   // Return true even if a modal popup window is normally blocking access to this item/window. FIXME-TODO: Unavailable yet.
+    ImGuiHoveredFlags_AllowWhenBlockedByActiveItem  = 1 << 5,   // Return true even if an active item is blocking access to this item/window. Useful for Drag and Drop patterns.
+    ImGuiHoveredFlags_AllowWhenOverlapped           = 1 << 6,   // Return true even if the position is overlapped by another window
+    ImGuiHoveredFlags_AllowWhenDisabled             = 1 << 7,   // Return true even if the item is disabled
+    ImGuiHoveredFlags_RectOnly                      = ImGuiHoveredFlags_AllowWhenBlockedByPopup | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem | ImGuiHoveredFlags_AllowWhenOverlapped,
+    ImGuiHoveredFlags_RootAndChildWindows           = ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows
+};
+
+// Flags for ImGui::BeginDragDropSource(), ImGui::AcceptDragDropPayload()
+enum ImGuiDragDropFlags_
+{
+    ImGuiDragDropFlags_None                         = 0,
+    // BeginDragDropSource() flags
+    ImGuiDragDropFlags_SourceNoPreviewTooltip       = 1 << 0,   // By default, a successful call to BeginDragDropSource opens a tooltip so you can display a preview or description of the source contents. This flag disable this behavior.
+    ImGuiDragDropFlags_SourceNoDisableHover         = 1 << 1,   // By default, when dragging we clear data so that IsItemHovered() will return false, to avoid subsequent user code submitting tooltips. This flag disable this behavior so you can still call IsItemHovered() on the source item.
+    ImGuiDragDropFlags_SourceNoHoldToOpenOthers     = 1 << 2,   // Disable the behavior that allows to open tree nodes and collapsing header by holding over them while dragging a source item.
+    ImGuiDragDropFlags_SourceAllowNullID            = 1 << 3,   // Allow items such as Text(), Image() that have no unique identifier to be used as drag source, by manufacturing a temporary identifier based on their window-relative position. This is extremely unusual within the dear imgui ecosystem and so we made it explicit.
+    ImGuiDragDropFlags_SourceExtern                 = 1 << 4,   // External source (from outside of imgui), won't attempt to read current item/window info. Will always return true. Only one Extern source can be active simultaneously.
+    ImGuiDragDropFlags_SourceAutoExpirePayload      = 1 << 5,   // Automatically expire the payload if the source cease to be submitted (otherwise payloads are persisting while being dragged)
+    // AcceptDragDropPayload() flags
+    ImGuiDragDropFlags_AcceptBeforeDelivery         = 1 << 10,  // AcceptDragDropPayload() will returns true even before the mouse button is released. You can then call IsDelivery() to test if the payload needs to be delivered.
+    ImGuiDragDropFlags_AcceptNoDrawDefaultRect      = 1 << 11,  // Do not draw the default highlight rectangle when hovering over target.
+    ImGuiDragDropFlags_AcceptNoPreviewTooltip       = 1 << 12,  // Request hiding the BeginDragDropSource tooltip from the BeginDragDropTarget site.
+    ImGuiDragDropFlags_AcceptPeekOnly               = ImGuiDragDropFlags_AcceptBeforeDelivery | ImGuiDragDropFlags_AcceptNoDrawDefaultRect  // For peeking ahead and inspecting the payload before delivery.
+};
+
+// Standard Drag and Drop payload types. You can define you own payload types using short strings. Types starting with '_' are defined by Dear ImGui.
+#define IMGUI_PAYLOAD_TYPE_COLOR_3F     "_COL3F"    // float[3]: Standard type for colors, without alpha. User code may use this type.
+#define IMGUI_PAYLOAD_TYPE_COLOR_4F     "_COL4F"    // float[4]: Standard type for colors. User code may use this type.
+
+// A primary data type
+enum ImGuiDataType_
+{
+    ImGuiDataType_S32,      // int
+    ImGuiDataType_U32,      // unsigned int
+    ImGuiDataType_S64,      // long long, __int64
+    ImGuiDataType_U64,      // unsigned long long, unsigned __int64
+    ImGuiDataType_Float,    // float
+    ImGuiDataType_Double,   // double
+    ImGuiDataType_COUNT
+};
+
+// A cardinal direction
+enum ImGuiDir_
+{
+    ImGuiDir_None    = -1,
+    ImGuiDir_Left    = 0,
+    ImGuiDir_Right   = 1,
+    ImGuiDir_Up      = 2,
+    ImGuiDir_Down    = 3,
+    ImGuiDir_COUNT
+};
+
+// User fill ImGuiIO.KeyMap[] array with indices into the ImGuiIO.KeysDown[512] array
+enum ImGuiKey_
+{
+    ImGuiKey_Tab,
+    ImGuiKey_LeftArrow,
+    ImGuiKey_RightArrow,
+    ImGuiKey_UpArrow,
+    ImGuiKey_DownArrow,
+    ImGuiKey_PageUp,
+    ImGuiKey_PageDown,
+    ImGuiKey_Home,
+    ImGuiKey_End,
+    ImGuiKey_Insert,
+    ImGuiKey_Delete,
+    ImGuiKey_Backspace,
+    ImGuiKey_Space,
+    ImGuiKey_Enter,
+    ImGuiKey_Escape,
+    ImGuiKey_A,         // for text edit CTRL+A: select all
+    ImGuiKey_C,         // for text edit CTRL+C: copy
+    ImGuiKey_V,         // for text edit CTRL+V: paste
+    ImGuiKey_X,         // for text edit CTRL+X: cut
+    ImGuiKey_Y,         // for text edit CTRL+Y: redo
+    ImGuiKey_Z,         // for text edit CTRL+Z: undo
+    ImGuiKey_COUNT
+};
+
+// Gamepad/Keyboard directional navigation
+// Keyboard: Set io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard to enable. NewFrame() will automatically fill io.NavInputs[] based on your io.KeysDown[] + io.KeyMap[] arrays.
+// Gamepad:  Set io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad to enable. Back-end: set ImGuiBackendFlags_HasGamepad and fill the io.NavInputs[] fields before calling NewFrame(). Note that io.NavInputs[] is cleared by EndFrame().
+// Read instructions in imgui.cpp for more details. Download PNG/PSD at http://goo.gl/9LgVZW.
+enum ImGuiNavInput_
+{
+    // Gamepad Mapping
+    ImGuiNavInput_Activate,      // activate / open / toggle / tweak value       // e.g. Cross  (PS4), A (Xbox), A (Switch), Space (Keyboard)
+    ImGuiNavInput_Cancel,        // cancel / close / exit                        // e.g. Circle (PS4), B (Xbox), B (Switch), Escape (Keyboard)
+    ImGuiNavInput_Input,         // text input / on-screen keyboard              // e.g. Triang.(PS4), Y (Xbox), X (Switch), Return (Keyboard)
+    ImGuiNavInput_Menu,          // tap: toggle menu / hold: focus, move, resize // e.g. Square (PS4), X (Xbox), Y (Switch), Alt (Keyboard)
+    ImGuiNavInput_DpadLeft,      // move / tweak / resize window (w/ PadMenu)    // e.g. D-pad Left/Right/Up/Down (Gamepads), Arrow keys (Keyboard)
+    ImGuiNavInput_DpadRight,     //
+    ImGuiNavInput_DpadUp,        //
+    ImGuiNavInput_DpadDown,      //
+    ImGuiNavInput_LStickLeft,    // scroll / move window (w/ PadMenu)            // e.g. Left Analog Stick Left/Right/Up/Down
+    ImGuiNavInput_LStickRight,   //
+    ImGuiNavInput_LStickUp,      //
+    ImGuiNavInput_LStickDown,    //
+    ImGuiNavInput_FocusPrev,     // next window (w/ PadMenu)                     // e.g. L1 or L2 (PS4), LB or LT (Xbox), L or ZL (Switch)
+    ImGuiNavInput_FocusNext,     // prev window (w/ PadMenu)                     // e.g. R1 or R2 (PS4), RB or RT (Xbox), R or ZL (Switch)
+    ImGuiNavInput_TweakSlow,     // slower tweaks                                // e.g. L1 or L2 (PS4), LB or LT (Xbox), L or ZL (Switch)
+    ImGuiNavInput_TweakFast,     // faster tweaks                                // e.g. R1 or R2 (PS4), RB or RT (Xbox), R or ZL (Switch)
+
+    // [Internal] Don't use directly! This is used internally to differentiate keyboard from gamepad inputs for behaviors that require to differentiate them.
+    // Keyboard behavior that have no corresponding gamepad mapping (e.g. CTRL+TAB) will be directly reading from io.KeysDown[] instead of io.NavInputs[].
+    ImGuiNavInput_KeyMenu_,      // toggle menu                                  // = io.KeyAlt
+    ImGuiNavInput_KeyLeft_,      // move left                                    // = Arrow keys
+    ImGuiNavInput_KeyRight_,     // move right
+    ImGuiNavInput_KeyUp_,        // move up
+    ImGuiNavInput_KeyDown_,      // move down
+    ImGuiNavInput_COUNT,
+    ImGuiNavInput_InternalStart_ = ImGuiNavInput_KeyMenu_
+};
+
+// Configuration flags stored in io.ConfigFlags. Set by user/application.
+enum ImGuiConfigFlags_
+{
+    ImGuiConfigFlags_NavEnableKeyboard      = 1 << 0,   // Master keyboard navigation enable flag. NewFrame() will automatically fill io.NavInputs[] based on io.KeysDown[].
+    ImGuiConfigFlags_NavEnableGamepad       = 1 << 1,   // Master gamepad navigation enable flag. This is mostly to instruct your imgui back-end to fill io.NavInputs[]. Back-end also needs to set ImGuiBackendFlags_HasGamepad.
+    ImGuiConfigFlags_NavEnableSetMousePos   = 1 << 2,   // Instruct navigation to move the mouse cursor. May be useful on TV/console systems where moving a virtual mouse is awkward. Will update io.MousePos and set io.WantSetMousePos=true. If enabled you MUST honor io.WantSetMousePos requests in your binding, otherwise ImGui will react as if the mouse is jumping around back and forth.
+    ImGuiConfigFlags_NavNoCaptureKeyboard   = 1 << 3,   // Instruct navigation to not set the io.WantCaptureKeyboard flag when io.NavActive is set.
+    ImGuiConfigFlags_NoMouse                = 1 << 4,   // Instruct imgui to clear mouse position/buttons in NewFrame(). This allows ignoring the mouse information set by the back-end.
+    ImGuiConfigFlags_NoMouseCursorChange    = 1 << 5,   // Instruct back-end to not alter mouse cursor shape and visibility. Use if the back-end cursor changes are interfering with yours and you don't want to use SetMouseCursor() to change mouse cursor. You may want to honor requests from imgui by reading GetMouseCursor() yourself instead.
+
+    // User storage (to allow your back-end/engine to communicate to code that may be shared between multiple projects. Those flags are not used by core ImGui)
+    ImGuiConfigFlags_IsSRGB                 = 1 << 20,  // Application is SRGB-aware.
+    ImGuiConfigFlags_IsTouchScreen          = 1 << 21   // Application is using a touch screen instead of a mouse.
+};
+
+// Back-end capabilities flags stored in io.BackendFlags. Set by imgui_impl_xxx or custom back-end.
+enum ImGuiBackendFlags_
+{
+    ImGuiBackendFlags_HasGamepad            = 1 << 0,   // Back-end supports gamepad and currently has one connected.
+    ImGuiBackendFlags_HasMouseCursors       = 1 << 1,   // Back-end supports honoring GetMouseCursor() value to change the OS cursor shape.
+    ImGuiBackendFlags_HasSetMousePos        = 1 << 2    // Back-end supports io.WantSetMousePos requests to reposition the OS mouse position (only used if ImGuiConfigFlags_NavEnableSetMousePos is set).
+};
+
+// Enumeration for PushStyleColor() / PopStyleColor()
+enum ImGuiCol_
+{
+    ImGuiCol_Text,
+    ImGuiCol_TextDisabled,
+    ImGuiCol_WindowBg,              // Background of normal windows
+    ImGuiCol_ChildBg,               // Background of child windows
+    ImGuiCol_PopupBg,               // Background of popups, menus, tooltips windows
+    ImGuiCol_Border,
+    ImGuiCol_BorderShadow,
+    ImGuiCol_FrameBg,               // Background of checkbox, radio button, plot, slider, text input
+    ImGuiCol_FrameBgHovered,
+    ImGuiCol_FrameBgActive,
+    ImGuiCol_TitleBg,
+    ImGuiCol_TitleBgActive,
+    ImGuiCol_TitleBgCollapsed,
+    ImGuiCol_MenuBarBg,
+    ImGuiCol_ScrollbarBg,
+    ImGuiCol_ScrollbarGrab,
+    ImGuiCol_ScrollbarGrabHovered,
+    ImGuiCol_ScrollbarGrabActive,
+    ImGuiCol_CheckMark,
+    ImGuiCol_SliderGrab,
+    ImGuiCol_SliderGrabActive,
+    ImGuiCol_Button,
+    ImGuiCol_ButtonHovered,
+    ImGuiCol_ButtonActive,
+    ImGuiCol_Header,
+    ImGuiCol_HeaderHovered,
+    ImGuiCol_HeaderActive,
+    ImGuiCol_Separator,
+    ImGuiCol_SeparatorHovered,
+    ImGuiCol_SeparatorActive,
+    ImGuiCol_ResizeGrip,
+    ImGuiCol_ResizeGripHovered,
+    ImGuiCol_ResizeGripActive,
+    ImGuiCol_PlotLines,
+    ImGuiCol_PlotLinesHovered,
+    ImGuiCol_PlotHistogram,
+    ImGuiCol_PlotHistogramHovered,
+    ImGuiCol_TextSelectedBg,
+    ImGuiCol_DragDropTarget,
+    ImGuiCol_NavHighlight,          // Gamepad/keyboard: current highlighted item
+    ImGuiCol_NavWindowingHighlight, // Highlight window when using CTRL+TAB
+    ImGuiCol_NavWindowingDimBg,     // Darken/colorize entire screen behind the CTRL+TAB window list, when active
+    ImGuiCol_ModalWindowDimBg,      // Darken/colorize entire screen behind a modal window, when one is active
+    ImGuiCol_COUNT
+
+    // Obsolete names (will be removed)
+#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
+    , ImGuiCol_ChildWindowBg = ImGuiCol_ChildBg, ImGuiCol_Column = ImGuiCol_Separator, ImGuiCol_ColumnHovered = ImGuiCol_SeparatorHovered, ImGuiCol_ColumnActive = ImGuiCol_SeparatorActive
+    , ImGuiCol_ModalWindowDarkening = ImGuiCol_ModalWindowDimBg
+    //ImGuiCol_CloseButton, ImGuiCol_CloseButtonActive, ImGuiCol_CloseButtonHovered, // [unused since 1.60+] the close button now uses regular button colors.
+    //ImGuiCol_ComboBg,                                                              // [unused since 1.53+] ComboBg has been merged with PopupBg, so a redirect isn't accurate.
+#endif
+};
+
+// Enumeration for PushStyleVar() / PopStyleVar() to temporarily modify the ImGuiStyle structure.
+// NB: the enum only refers to fields of ImGuiStyle which makes sense to be pushed/popped inside UI code. During initialization, feel free to just poke into ImGuiStyle directly.
+// NB: if changing this enum, you need to update the associated internal table GStyleVarInfo[] accordingly. This is where we link enum values to members offset/type.
+enum ImGuiStyleVar_
+{
+    // Enum name ......................// Member in ImGuiStyle structure (see ImGuiStyle for descriptions)
+    ImGuiStyleVar_Alpha,               // float     Alpha
+    ImGuiStyleVar_WindowPadding,       // ImVec2    WindowPadding
+    ImGuiStyleVar_WindowRounding,      // float     WindowRounding
+    ImGuiStyleVar_WindowBorderSize,    // float     WindowBorderSize
+    ImGuiStyleVar_WindowMinSize,       // ImVec2    WindowMinSize
+    ImGuiStyleVar_WindowTitleAlign,    // ImVec2    WindowTitleAlign
+    ImGuiStyleVar_ChildRounding,       // float     ChildRounding
+    ImGuiStyleVar_ChildBorderSize,     // float     ChildBorderSize
+    ImGuiStyleVar_PopupRounding,       // float     PopupRounding
+    ImGuiStyleVar_PopupBorderSize,     // float     PopupBorderSize
+    ImGuiStyleVar_FramePadding,        // ImVec2    FramePadding
+    ImGuiStyleVar_FrameRounding,       // float     FrameRounding
+    ImGuiStyleVar_FrameBorderSize,     // float     FrameBorderSize
+    ImGuiStyleVar_ItemSpacing,         // ImVec2    ItemSpacing
+    ImGuiStyleVar_ItemInnerSpacing,    // ImVec2    ItemInnerSpacing
+    ImGuiStyleVar_IndentSpacing,       // float     IndentSpacing
+    ImGuiStyleVar_ScrollbarSize,       // float     ScrollbarSize
+    ImGuiStyleVar_ScrollbarRounding,   // float     ScrollbarRounding
+    ImGuiStyleVar_GrabMinSize,         // float     GrabMinSize
+    ImGuiStyleVar_GrabRounding,        // float     GrabRounding
+    ImGuiStyleVar_ButtonTextAlign,     // ImVec2    ButtonTextAlign
+    ImGuiStyleVar_COUNT
+
+    // Obsolete names (will be removed)
+#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
+    , ImGuiStyleVar_Count_ = ImGuiStyleVar_COUNT, ImGuiStyleVar_ChildWindowRounding = ImGuiStyleVar_ChildRounding
+#endif
+};
+
+// Enumeration for ColorEdit3() / ColorEdit4() / ColorPicker3() / ColorPicker4() / ColorButton()
+enum ImGuiColorEditFlags_
+{
+    ImGuiColorEditFlags_None            = 0,
+    ImGuiColorEditFlags_NoAlpha         = 1 << 1,   //              // ColorEdit, ColorPicker, ColorButton: ignore Alpha component (read 3 components from the input pointer).
+    ImGuiColorEditFlags_NoPicker        = 1 << 2,   //              // ColorEdit: disable picker when clicking on colored square.
+    ImGuiColorEditFlags_NoOptions       = 1 << 3,   //              // ColorEdit: disable toggling options menu when right-clicking on inputs/small preview.
+    ImGuiColorEditFlags_NoSmallPreview  = 1 << 4,   //              // ColorEdit, ColorPicker: disable colored square preview next to the inputs. (e.g. to show only the inputs)
+    ImGuiColorEditFlags_NoInputs        = 1 << 5,   //              // ColorEdit, ColorPicker: disable inputs sliders/text widgets (e.g. to show only the small preview colored square).
+    ImGuiColorEditFlags_NoTooltip       = 1 << 6,   //              // ColorEdit, ColorPicker, ColorButton: disable tooltip when hovering the preview.
+    ImGuiColorEditFlags_NoLabel         = 1 << 7,   //              // ColorEdit, ColorPicker: disable display of inline text label (the label is still forwarded to the tooltip and picker).
+    ImGuiColorEditFlags_NoSidePreview   = 1 << 8,   //              // ColorPicker: disable bigger color preview on right side of the picker, use small colored square preview instead.
+    ImGuiColorEditFlags_NoDragDrop      = 1 << 9,   //              // ColorEdit: disable drag and drop target. ColorButton: disable drag and drop source.
+
+    // User Options (right-click on widget to change some of them). You can set application defaults using SetColorEditOptions(). The idea is that you probably don't want to override them in most of your calls, let the user choose and/or call SetColorEditOptions() during startup.
+    ImGuiColorEditFlags_AlphaBar        = 1 << 16,  //              // ColorEdit, ColorPicker: show vertical alpha bar/gradient in picker.
+    ImGuiColorEditFlags_AlphaPreview    = 1 << 17,  //              // ColorEdit, ColorPicker, ColorButton: display preview as a transparent color over a checkerboard, instead of opaque.
+    ImGuiColorEditFlags_AlphaPreviewHalf= 1 << 18,  //              // ColorEdit, ColorPicker, ColorButton: display half opaque / half checkerboard, instead of opaque.
+    ImGuiColorEditFlags_HDR             = 1 << 19,  //              // (WIP) ColorEdit: Currently only disable 0.0f..1.0f limits in RGBA edition (note: you probably want to use ImGuiColorEditFlags_Float flag as well).
+    ImGuiColorEditFlags_RGB             = 1 << 20,  // [Inputs]     // ColorEdit: choose one among RGB/HSV/HEX. ColorPicker: choose any combination using RGB/HSV/HEX.
+    ImGuiColorEditFlags_HSV             = 1 << 21,  // [Inputs]     // "
+    ImGuiColorEditFlags_HEX             = 1 << 22,  // [Inputs]     // "
+    ImGuiColorEditFlags_Uint8           = 1 << 23,  // [DataType]   // ColorEdit, ColorPicker, ColorButton: _display_ values formatted as 0..255.
+    ImGuiColorEditFlags_Float           = 1 << 24,  // [DataType]   // ColorEdit, ColorPicker, ColorButton: _display_ values formatted as 0.0f..1.0f floats instead of 0..255 integers. No round-trip of value via integers.
+    ImGuiColorEditFlags_PickerHueBar    = 1 << 25,  // [PickerMode] // ColorPicker: bar for Hue, rectangle for Sat/Value.
+    ImGuiColorEditFlags_PickerHueWheel  = 1 << 26,  // [PickerMode] // ColorPicker: wheel for Hue, triangle for Sat/Value.
+
+    // [Internal] Masks
+    ImGuiColorEditFlags__InputsMask     = ImGuiColorEditFlags_RGB|ImGuiColorEditFlags_HSV|ImGuiColorEditFlags_HEX,
+    ImGuiColorEditFlags__DataTypeMask   = ImGuiColorEditFlags_Uint8|ImGuiColorEditFlags_Float,
+    ImGuiColorEditFlags__PickerMask     = ImGuiColorEditFlags_PickerHueWheel|ImGuiColorEditFlags_PickerHueBar,
+    ImGuiColorEditFlags__OptionsDefault = ImGuiColorEditFlags_Uint8|ImGuiColorEditFlags_RGB|ImGuiColorEditFlags_PickerHueBar    // Change application default using SetColorEditOptions()
+};
+
+// Enumeration for GetMouseCursor()
+// User code may request binding to display given cursor by calling SetMouseCursor(), which is why we have some cursors that are marked unused here
+enum ImGuiMouseCursor_
+{
+    ImGuiMouseCursor_None = -1,
+    ImGuiMouseCursor_Arrow = 0,
+    ImGuiMouseCursor_TextInput,         // When hovering over InputText, etc.
+    ImGuiMouseCursor_ResizeAll,         // (Unused by imgui functions)
+    ImGuiMouseCursor_ResizeNS,          // When hovering over an horizontal border
+    ImGuiMouseCursor_ResizeEW,          // When hovering over a vertical border or a column
+    ImGuiMouseCursor_ResizeNESW,        // When hovering over the bottom-left corner of a window
+    ImGuiMouseCursor_ResizeNWSE,        // When hovering over the bottom-right corner of a window
+    ImGuiMouseCursor_Hand,              // (Unused by imgui functions. Use for e.g. hyperlinks)
+    ImGuiMouseCursor_COUNT
+
+    // Obsolete names (will be removed)
+#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
+    , ImGuiMouseCursor_Count_ = ImGuiMouseCursor_COUNT
+#endif
+};
+
+// Condition for ImGui::SetWindow***(), SetNextWindow***(), SetNextTreeNode***() functions
+// Important: Treat as a regular enum! Do NOT combine multiple values using binary operators! All the functions above treat 0 as a shortcut to ImGuiCond_Always.
+enum ImGuiCond_
+{
+    ImGuiCond_Always        = 1 << 0,   // Set the variable
+    ImGuiCond_Once          = 1 << 1,   // Set the variable once per runtime session (only the first call with succeed)
+    ImGuiCond_FirstUseEver  = 1 << 2,   // Set the variable if the object/window has no persistently saved data (no entry in .ini file)
+    ImGuiCond_Appearing     = 1 << 3    // Set the variable if the object/window is appearing after being hidden/inactive (or the first time)
+
+    // Obsolete names (will be removed)
+#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
+    , ImGuiSetCond_Always = ImGuiCond_Always, ImGuiSetCond_Once = ImGuiCond_Once, ImGuiSetCond_FirstUseEver = ImGuiCond_FirstUseEver, ImGuiSetCond_Appearing = ImGuiCond_Appearing
+#endif
+};
+
+// You may modify the ImGui::GetStyle() main instance during initialization and before NewFrame().
+// During the frame, use ImGui::PushStyleVar(ImGuiStyleVar_XXXX)/PopStyleVar() to alter the main style values, and ImGui::PushStyleColor(ImGuiCol_XXX)/PopStyleColor() for colors.
+struct ImGuiStyle
+{
+    float       Alpha;                      // Global alpha applies to everything in ImGui.
+    ImVec2      WindowPadding;              // Padding within a window.
+    float       WindowRounding;             // Radius of window corners rounding. Set to 0.0f to have rectangular windows.
+    float       WindowBorderSize;           // Thickness of border around windows. Generally set to 0.0f or 1.0f. (Other values are not well tested and more CPU/GPU costly).
+    ImVec2      WindowMinSize;              // Minimum window size. This is a global setting. If you want to constraint individual windows, use SetNextWindowSizeConstraints().
+    ImVec2      WindowTitleAlign;           // Alignment for title bar text. Defaults to (0.0f,0.5f) for left-aligned,vertically centered.
+    float       ChildRounding;              // Radius of child window corners rounding. Set to 0.0f to have rectangular windows.
+    float       ChildBorderSize;            // Thickness of border around child windows. Generally set to 0.0f or 1.0f. (Other values are not well tested and more CPU/GPU costly).
+    float       PopupRounding;              // Radius of popup window corners rounding. (Note that tooltip windows use WindowRounding)
+    float       PopupBorderSize;            // Thickness of border around popup/tooltip windows. Generally set to 0.0f or 1.0f. (Other values are not well tested and more CPU/GPU costly).
+    ImVec2      FramePadding;               // Padding within a framed rectangle (used by most widgets).
+    float       FrameRounding;              // Radius of frame corners rounding. Set to 0.0f to have rectangular frame (used by most widgets).
+    float       FrameBorderSize;            // Thickness of border around frames. Generally set to 0.0f or 1.0f. (Other values are not well tested and more CPU/GPU costly).
+    ImVec2      ItemSpacing;                // Horizontal and vertical spacing between widgets/lines.
+    ImVec2      ItemInnerSpacing;           // Horizontal and vertical spacing between within elements of a composed widget (e.g. a slider and its label).
+    ImVec2      TouchExtraPadding;          // Expand reactive bounding box for touch-based system where touch position is not accurate enough. Unfortunately we don't sort widgets so priority on overlap will always be given to the first widget. So don't grow this too much!
+    float       IndentSpacing;              // Horizontal indentation when e.g. entering a tree node. Generally == (FontSize + FramePadding.x*2).
+    float       ColumnsMinSpacing;          // Minimum horizontal spacing between two columns.
+    float       ScrollbarSize;              // Width of the vertical scrollbar, Height of the horizontal scrollbar.
+    float       ScrollbarRounding;          // Radius of grab corners for scrollbar.
+    float       GrabMinSize;                // Minimum width/height of a grab box for slider/scrollbar.
+    float       GrabRounding;               // Radius of grabs corners rounding. Set to 0.0f to have rectangular slider grabs.
+    ImVec2      ButtonTextAlign;            // Alignment of button text when button is larger than text. Defaults to (0.5f,0.5f) for horizontally+vertically centered.
+    ImVec2      DisplayWindowPadding;       // Window position are clamped to be visible within the display area by at least this amount. Only applies to regular windows.
+    ImVec2      DisplaySafeAreaPadding;     // If you cannot see the edges of your screen (e.g. on a TV) increase the safe area padding. Apply to popups/tooltips as well regular windows. NB: Prefer configuring your TV sets correctly!
+    float       MouseCursorScale;           // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). May be removed later.
+    bool        AntiAliasedLines;           // Enable anti-aliasing on lines/borders. Disable if you are really tight on CPU/GPU.
+    bool        AntiAliasedFill;            // Enable anti-aliasing on filled shapes (rounded rectangles, circles, etc.)
+    float       CurveTessellationTol;       // Tessellation tolerance when using PathBezierCurveTo() without a specific number of segments. Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality.
+    ImVec4      Colors[ImGuiCol_COUNT];
+
+    IMGUI_API ImGuiStyle();
+    IMGUI_API void ScaleAllSizes(float scale_factor);
+};
+
+// This is where your app communicate with Dear ImGui. Access via ImGui::GetIO().
+// Read 'Programmer guide' section in .cpp file for general usage.
+struct ImGuiIO
+{
+    //------------------------------------------------------------------
+    // Configuration (fill once)            // Default value:
+    //------------------------------------------------------------------
+
+    ImGuiConfigFlags   ConfigFlags;         // = 0                  // See ImGuiConfigFlags_ enum. Set by user/application. Gamepad/keyboard navigation options, etc.
+    ImGuiBackendFlags  BackendFlags;        // = 0                  // Set ImGuiBackendFlags_ enum. Set by imgui_impl_xxx files or custom back-end to communicate features supported by the back-end.
+    ImVec2        DisplaySize;              // <unset>              // Main display size, in pixels. For clamping windows positions.
+    float         DeltaTime;                // = 1.0f/60.0f         // Time elapsed since last frame, in seconds.
+    float         IniSavingRate;            // = 5.0f               // Minimum time between saving positions/sizes to .ini file, in seconds.
+    const char*   IniFilename;              // = "imgui.ini"        // Path to .ini file. Set NULL to disable automatic .ini loading/saving, if e.g. you want to manually load/save from memory.
+    const char*   LogFilename;              // = "imgui_log.txt"    // Path to .log file (default parameter to ImGui::LogToFile when no file is specified).
+    float         MouseDoubleClickTime;     // = 0.30f              // Time for a double-click, in seconds.
+    float         MouseDoubleClickMaxDist;  // = 6.0f               // Distance threshold to stay in to validate a double-click, in pixels.
+    float         MouseDragThreshold;       // = 6.0f               // Distance threshold before considering we are dragging.
+    int           KeyMap[ImGuiKey_COUNT];   // <unset>              // Map of indices into the KeysDown[512] entries array which represent your "native" keyboard state.
+    float         KeyRepeatDelay;           // = 0.250f             // When holding a key/button, time before it starts repeating, in seconds (for buttons in Repeat mode, etc.).
+    float         KeyRepeatRate;            // = 0.050f             // When holding a key/button, rate at which it repeats, in seconds.
+    void*         UserData;                 // = NULL               // Store your own data for retrieval by callbacks.
+
+    ImFontAtlas*  Fonts;                    // <auto>               // Load and assemble one or more fonts into a single tightly packed texture. Output to Fonts array.
+    float         FontGlobalScale;          // = 1.0f               // Global scale all fonts
+    bool          FontAllowUserScaling;     // = false              // Allow user scaling text of individual window with CTRL+Wheel.
+    ImFont*       FontDefault;              // = NULL               // Font to use on NewFrame(). Use NULL to uses Fonts->Fonts[0].
+    ImVec2        DisplayFramebufferScale;  // = (1.0f,1.0f)        // For retina display or other situations where window coordinates are different from framebuffer coordinates. User storage only, presently not used by ImGui.
+    ImVec2        DisplayVisibleMin;        // <unset> (0.0f,0.0f)  // [obsolete] If you use DisplaySize as a virtual space larger than your screen, set DisplayVisibleMin/Max to the visible area.
+    ImVec2        DisplayVisibleMax;        // <unset> (0.0f,0.0f)  // [obsolete: just use io.DisplaySize] If the values are the same, we defaults to Min=(0.0f) and Max=DisplaySize
+
+    // Miscellaneous configuration options
+    bool          MouseDrawCursor;              // = false          // Request ImGui to draw a mouse cursor for you (if you are on a platform without a mouse cursor). Cannot be easily renamed to 'io.ConfigXXX' because this is frequently used by back-end implementations.
+    bool          ConfigMacOSXBehaviors;        // = defined(__APPLE__) // OS X style: Text editing cursor movement using Alt instead of Ctrl, Shortcuts using Cmd/Super instead of Ctrl, Line/Text Start and End using Cmd+Arrows instead of Home/End, Double click selects by word instead of selecting whole text, Multi-selection in lists uses Cmd/Super instead of Ctrl (was called io.OptMacOSXBehaviors prior to 1.63)
+    bool          ConfigInputTextCursorBlink;   // = true           // Set to false to disable blinking cursor, for users who consider it distracting. (was called: io.OptCursorBlink prior to 1.63)
+    bool          ConfigResizeWindowsFromEdges; // = false          // [BETA] Enable resizing of windows from their edges and from the lower-left corner. This requires (io.BackendFlags & ImGuiBackendFlags_HasMouseCursors) because it needs mouse cursor feedback. (This used to be the ImGuiWindowFlags_ResizeFromAnySide flag)
+
+    //------------------------------------------------------------------
+    // Settings (User Functions)
+    //------------------------------------------------------------------
+
+    // Optional: access OS clipboard
+    // (default to use native Win32 clipboard on Windows, otherwise uses a private clipboard. Override to access OS clipboard on other architectures)
+    const char* (*GetClipboardTextFn)(void* user_data);
+    void        (*SetClipboardTextFn)(void* user_data, const char* text);
+    void*       ClipboardUserData;
+
+    // Optional: notify OS Input Method Editor of the screen position of your cursor for text input position (e.g. when using Japanese/Chinese IME in Windows)
+    // (default to use native imm32 api on Windows)
+    void        (*ImeSetInputScreenPosFn)(int x, int y);
+    void*       ImeWindowHandle;            // (Windows) Set this to your HWND to get automatic IME cursor positioning.
+
+#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
+    // [OBSOLETE since 1.60+] Rendering function, will be automatically called in Render(). Please call your rendering function yourself now!
+    // You can obtain the ImDrawData* by calling ImGui::GetDrawData() after Render(). See example applications if you are unsure of how to implement this.
+    void        (*RenderDrawListsFn)(ImDrawData* data);
+#else
+    // This is only here to keep ImGuiIO the same size, so that IMGUI_DISABLE_OBSOLETE_FUNCTIONS can exceptionally be used outside of imconfig.h.
+    void*       RenderDrawListsFnUnused;
+#endif
+
+    //------------------------------------------------------------------
+    // Input - Fill before calling NewFrame()
+    //------------------------------------------------------------------
+
+    ImVec2      MousePos;                       // Mouse position, in pixels. Set to ImVec2(-FLT_MAX,-FLT_MAX) if mouse is unavailable (on another screen, etc.)
+    bool        MouseDown[5];                   // Mouse buttons: 0=left, 1=right, 2=middle + extras. ImGui itself mostly only uses left button (BeginPopupContext** are using right button). Others buttons allows us to track if the mouse is being used by your application + available to user as a convenience via IsMouse** API.
+    float       MouseWheel;                     // Mouse wheel Vertical: 1 unit scrolls about 5 lines text.
+    float       MouseWheelH;                    // Mouse wheel Horizontal. Most users don't have a mouse with an horizontal wheel, may not be filled by all back-ends.
+    bool        KeyCtrl;                        // Keyboard modifier pressed: Control
+    bool        KeyShift;                       // Keyboard modifier pressed: Shift
+    bool        KeyAlt;                         // Keyboard modifier pressed: Alt
+    bool        KeySuper;                       // Keyboard modifier pressed: Cmd/Super/Windows
+    bool        KeysDown[512];                  // Keyboard keys that are pressed (ideally left in the "native" order your engine has access to keyboard keys, so you can use your own defines/enums for keys).
+    ImWchar     InputCharacters[16+1];          // List of characters input (translated by user from keypress+keyboard state). Fill using AddInputCharacter() helper.
+    float       NavInputs[ImGuiNavInput_COUNT]; // Gamepad inputs (keyboard keys will be auto-mapped and be written here by ImGui::NewFrame, all values will be cleared back to zero in ImGui::EndFrame)
+
+    // Functions
+    IMGUI_API void AddInputCharacter(ImWchar c);                        // Add new character into InputCharacters[]
+    IMGUI_API void AddInputCharactersUTF8(const char* utf8_chars);      // Add new characters into InputCharacters[] from an UTF-8 string
+    inline void    ClearInputCharacters() { InputCharacters[0] = 0; }   // Clear the text input buffer manually
+
+    //------------------------------------------------------------------
+    // Output - Retrieve after calling NewFrame()
+    //------------------------------------------------------------------
+
+    bool        WantCaptureMouse;           // When io.WantCaptureMouse is true, imgui will use the mouse inputs, do not dispatch them to your main game/application (in both cases, always pass on mouse inputs to imgui). (e.g. unclicked mouse is hovering over an imgui window, widget is active, mouse was clicked over an imgui window, etc.).
+    bool        WantCaptureKeyboard;        // When io.WantCaptureKeyboard is true, imgui will use the keyboard inputs, do not dispatch them to your main game/application (in both cases, always pass keyboard inputs to imgui). (e.g. InputText active, or an imgui window is focused and navigation is enabled, etc.).
+    bool        WantTextInput;              // Mobile/console: when io.WantTextInput is true, you may display an on-screen keyboard. This is set by ImGui when it wants textual keyboard input to happen (e.g. when a InputText widget is active).
+    bool        WantSetMousePos;            // MousePos has been altered, back-end should reposition mouse on next frame. Set only when ImGuiConfigFlags_NavEnableSetMousePos flag is enabled.
+    bool        WantSaveIniSettings;        // When manual .ini load/save is active (io.IniFilename == NULL), this will be set to notify your application that you can call SaveIniSettingsToMemory() and save yourself. IMPORTANT: You need to clear io.WantSaveIniSettings yourself.
+    bool        NavActive;                  // Directional navigation is currently allowed (will handle ImGuiKey_NavXXX events) = a window is focused and it doesn't use the ImGuiWindowFlags_NoNavInputs flag.
+    bool        NavVisible;                 // Directional navigation is visible and allowed (will handle ImGuiKey_NavXXX events).
+    float       Framerate;                  // Application framerate estimation, in frame per second. Solely for convenience. Rolling average estimation based on IO.DeltaTime over 120 frames
+    int         MetricsRenderVertices;      // Vertices output during last call to Render()
+    int         MetricsRenderIndices;       // Indices output during last call to Render() = number of triangles * 3
+    int         MetricsRenderWindows;       // Number of visible windows
+    int         MetricsActiveWindows;       // Number of active windows
+    int         MetricsActiveAllocations;   // Number of active allocations, updated by MemAlloc/MemFree based on current context. May be off if you have multiple imgui contexts.
+    ImVec2      MouseDelta;                 // Mouse delta. Note that this is zero if either current or previous position are invalid (-FLT_MAX,-FLT_MAX), so a disappearing/reappearing mouse won't have a huge delta.
+
+    //------------------------------------------------------------------
+    // [Internal] ImGui will maintain those fields. Forward compatibility not guaranteed!
+    //------------------------------------------------------------------
+
+    ImVec2      MousePosPrev;               // Previous mouse position temporary storage (nb: not for public use, set to MousePos in NewFrame())
+    ImVec2      MouseClickedPos[5];         // Position at time of clicking
+    double      MouseClickedTime[5];        // Time of last click (used to figure out double-click)
+    bool        MouseClicked[5];            // Mouse button went from !Down to Down
+    bool        MouseDoubleClicked[5];      // Has mouse button been double-clicked?
+    bool        MouseReleased[5];           // Mouse button went from Down to !Down
+    bool        MouseDownOwned[5];          // Track if button was clicked inside a window. We don't request mouse capture from the application if click started outside ImGui bounds.
+    float       MouseDownDuration[5];       // Duration the mouse button has been down (0.0f == just clicked)
+    float       MouseDownDurationPrev[5];   // Previous time the mouse button has been down
+    ImVec2      MouseDragMaxDistanceAbs[5]; // Maximum distance, absolute, on each axis, of how much mouse has traveled from the clicking point
+    float       MouseDragMaxDistanceSqr[5]; // Squared maximum distance of how much mouse has traveled from the clicking point
+    float       KeysDownDuration[512];      // Duration the keyboard key has been down (0.0f == just pressed)
+    float       KeysDownDurationPrev[512];  // Previous duration the key has been down
+    float       NavInputsDownDuration[ImGuiNavInput_COUNT];
+    float       NavInputsDownDurationPrev[ImGuiNavInput_COUNT];
+
+    IMGUI_API   ImGuiIO();
+};
+
+//-----------------------------------------------------------------------------
+// Obsolete functions (Will be removed! Read 'API BREAKING CHANGES' section in imgui.cpp for details)
+//-----------------------------------------------------------------------------
+
+#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
+namespace ImGui
+{
+    // OBSOLETED in 1.63 (from Aug 2018)
+    static inline bool  IsItemDeactivatedAfterChange()        { return IsItemDeactivatedAfterEdit(); }
+    // OBSOLETED in 1.61 (from Apr 2018)
+    IMGUI_API bool      InputFloat(const char* label, float* v, float step, float step_fast, int decimal_precision, ImGuiInputTextFlags extra_flags = 0); // Use the 'const char* format' version instead of 'decimal_precision'!
+    IMGUI_API bool      InputFloat2(const char* label, float v[2], int decimal_precision, ImGuiInputTextFlags extra_flags = 0);
+    IMGUI_API bool      InputFloat3(const char* label, float v[3], int decimal_precision, ImGuiInputTextFlags extra_flags = 0);
+    IMGUI_API bool      InputFloat4(const char* label, float v[4], int decimal_precision, ImGuiInputTextFlags extra_flags = 0);
+    // OBSOLETED in 1.60 (from Dec 2017)
+    static inline bool  IsAnyWindowFocused()                  { return IsWindowFocused(ImGuiFocusedFlags_AnyWindow); }
+    static inline bool  IsAnyWindowHovered()                  { return IsWindowHovered(ImGuiHoveredFlags_AnyWindow); }
+    static inline ImVec2 CalcItemRectClosestPoint(const ImVec2& pos, bool on_edge = false, float outward = 0.f) { (void)on_edge; (void)outward; IM_ASSERT(0); return pos; }
+    // OBSOLETED in 1.53 (between Oct 2017 and Dec 2017)
+    static inline void  ShowTestWindow()                      { return ShowDemoWindow(); }
+    static inline bool  IsRootWindowFocused()                 { return IsWindowFocused(ImGuiFocusedFlags_RootWindow); }
+    static inline bool  IsRootWindowOrAnyChildFocused()       { return IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows); }
+    static inline void  SetNextWindowContentWidth(float w)    { SetNextWindowContentSize(ImVec2(w, 0.0f)); }
+    static inline float GetItemsLineHeightWithSpacing()       { return GetFrameHeightWithSpacing(); }
+    // OBSOLETED in 1.52 (between Aug 2017 and Oct 2017)
+    IMGUI_API bool      Begin(const char* name, bool* p_open, const ImVec2& size_on_first_use, float bg_alpha_override = -1.0f, ImGuiWindowFlags flags = 0); // Use SetNextWindowSize(size, ImGuiCond_FirstUseEver) + SetNextWindowBgAlpha() instead.
+    static inline bool  IsRootWindowOrAnyChildHovered()       { return IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows); }
+    static inline void  AlignFirstTextHeightToWidgets()       { AlignTextToFramePadding(); }
+    static inline void  SetNextWindowPosCenter(ImGuiCond c=0) { ImGuiIO& io = GetIO(); SetNextWindowPos(ImVec2(io.DisplaySize.x * 0.5f, io.DisplaySize.y * 0.5f), c, ImVec2(0.5f, 0.5f)); }
+    // OBSOLETED in 1.51 (between Jun 2017 and Aug 2017)
+    static inline bool  IsItemHoveredRect()                   { return IsItemHovered(ImGuiHoveredFlags_RectOnly); }
+    static inline bool  IsPosHoveringAnyWindow(const ImVec2&) { IM_ASSERT(0); return false; } // This was misleading and partly broken. You probably want to use the ImGui::GetIO().WantCaptureMouse flag instead.
+    static inline bool  IsMouseHoveringAnyWindow()            { return IsWindowHovered(ImGuiHoveredFlags_AnyWindow); }
+    static inline bool  IsMouseHoveringWindow()               { return IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem); }
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// Helpers
+//-----------------------------------------------------------------------------
+
+// Helper: Lightweight std::vector<> like class to avoid dragging dependencies (also: Windows implementation of STL with debug enabled is absurdly slow, so let's bypass it so our code runs fast in debug).
+// *Important* Our implementation does NOT call C++ constructors/destructors. This is intentional, we do not require it but you have to be mindful of that. Do _not_ use this class as a std::vector replacement in your code!
+template<typename T>
+class ImVector
+{
+public:
+    int                         Size;
+    int                         Capacity;
+    T*                          Data;
+
+    typedef T                   value_type;
+    typedef value_type*         iterator;
+    typedef const value_type*   const_iterator;
+
+    inline ImVector()           { Size = Capacity = 0; Data = NULL; }
+    inline ~ImVector()          { if (Data) ImGui::MemFree(Data); }
+    inline ImVector(const ImVector<T>& src)                     { Size = Capacity = 0; Data = NULL; operator=(src); }
+    inline ImVector& operator=(const ImVector<T>& src)          { clear(); resize(src.Size); memcpy(Data, src.Data, (size_t)Size * sizeof(value_type)); return *this; }
+
+    inline bool                 empty() const                   { return Size == 0; }
+    inline int                  size() const                    { return Size; }
+    inline int                  capacity() const                { return Capacity; }
+    inline value_type&          operator[](int i)               { IM_ASSERT(i < Size); return Data[i]; }
+    inline const value_type&    operator[](int i) const         { IM_ASSERT(i < Size); return Data[i]; }
+
+    inline void                 clear()                         { if (Data) { Size = Capacity = 0; ImGui::MemFree(Data); Data = NULL; } }
+    inline iterator             begin()                         { return Data; }
+    inline const_iterator       begin() const                   { return Data; }
+    inline iterator             end()                           { return Data + Size; }
+    inline const_iterator       end() const                     { return Data + Size; }
+    inline value_type&          front()                         { IM_ASSERT(Size > 0); return Data[0]; }
+    inline const value_type&    front() const                   { IM_ASSERT(Size > 0); return Data[0]; }
+    inline value_type&          back()                          { IM_ASSERT(Size > 0); return Data[Size - 1]; }
+    inline const value_type&    back() const                    { IM_ASSERT(Size > 0); return Data[Size - 1]; }
+    inline void                 swap(ImVector<value_type>& rhs) { int rhs_size = rhs.Size; rhs.Size = Size; Size = rhs_size; int rhs_cap = rhs.Capacity; rhs.Capacity = Capacity; Capacity = rhs_cap; value_type* rhs_data = rhs.Data; rhs.Data = Data; Data = rhs_data; }
+
+    inline int          _grow_capacity(int sz) const            { int new_capacity = Capacity ? (Capacity + Capacity/2) : 8; return new_capacity > sz ? new_capacity : sz; }
+    inline void         resize(int new_size)                    { if (new_size > Capacity) reserve(_grow_capacity(new_size)); Size = new_size; }
+    inline void         resize(int new_size,const value_type& v){ if (new_size > Capacity) reserve(_grow_capacity(new_size)); if (new_size > Size) for (int n = Size; n < new_size; n++) memcpy(&Data[n], &v, sizeof(v)); Size = new_size; }
+    inline void         reserve(int new_capacity)
+    {
+        if (new_capacity <= Capacity)
+            return;
+        value_type* new_data = (value_type*)ImGui::MemAlloc((size_t)new_capacity * sizeof(value_type));
+        if (Data)
+        {
+            memcpy(new_data, Data, (size_t)Size * sizeof(value_type));
+            ImGui::MemFree(Data);
+        }
+        Data = new_data;
+        Capacity = new_capacity;
+    }
+
+    // NB: It is forbidden to call push_back/push_front/insert with a reference pointing inside the ImVector data itself! e.g. v.push_back(v[10]) is forbidden.
+    inline void         push_back(const value_type& v)                  { if (Size == Capacity) reserve(_grow_capacity(Size + 1)); memcpy(&Data[Size], &v, sizeof(v)); Size++; }
+    inline void         pop_back()                                      { IM_ASSERT(Size > 0); Size--; }
+    inline void         push_front(const value_type& v)                 { if (Size == 0) push_back(v); else insert(Data, v); }
+    inline iterator     erase(const_iterator it)                        { IM_ASSERT(it >= Data && it < Data+Size); const ptrdiff_t off = it - Data; memmove(Data + off, Data + off + 1, ((size_t)Size - (size_t)off - 1) * sizeof(value_type)); Size--; return Data + off; }
+    inline iterator     erase(const_iterator it, const_iterator it_last){ IM_ASSERT(it >= Data && it < Data+Size && it_last > it && it_last <= Data+Size); const ptrdiff_t count = it_last - it; const ptrdiff_t off = it - Data; memmove(Data + off, Data + off + count, ((size_t)Size - (size_t)off - count) * sizeof(value_type)); Size -= (int)count; return Data + off; }
+    inline iterator     erase_unsorted(const_iterator it)               { IM_ASSERT(it >= Data && it < Data+Size);  const ptrdiff_t off = it - Data; if (it < Data+Size-1) memcpy(Data + off, Data + Size - 1, sizeof(value_type)); Size--; return Data + off; }
+    inline iterator     insert(const_iterator it, const value_type& v)  { IM_ASSERT(it >= Data && it <= Data+Size); const ptrdiff_t off = it - Data; if (Size == Capacity) reserve(_grow_capacity(Size + 1)); if (off < (int)Size) memmove(Data + off + 1, Data + off, ((size_t)Size - (size_t)off) * sizeof(value_type)); memcpy(&Data[off], &v, sizeof(v)); Size++; return Data + off; }
+    inline bool         contains(const value_type& v) const             { const T* data = Data;  const T* data_end = Data + Size; while (data < data_end) if (*data++ == v) return true; return false; }
+    inline int          index_from_pointer(const_iterator it) const     { IM_ASSERT(it >= Data && it <= Data+Size); const ptrdiff_t off = it - Data; return (int)off; }
+};
+
+// Helper: IM_NEW(), IM_PLACEMENT_NEW(), IM_DELETE() macros to call MemAlloc + Placement New, Placement Delete + MemFree
+// We call C++ constructor on own allocated memory via the placement "new(ptr) Type()" syntax.
+// Defining a custom placement new() with a dummy parameter allows us to bypass including <new> which on some platforms complains when user has disabled exceptions.
+struct ImNewDummy {};
+inline void* operator new(size_t, ImNewDummy, void* ptr) { return ptr; }
+inline void  operator delete(void*, ImNewDummy, void*)   {} // This is only required so we can use the symetrical new()
+#define IM_PLACEMENT_NEW(_PTR)              new(ImNewDummy(), _PTR)
+#define IM_NEW(_TYPE)                       new(ImNewDummy(), ImGui::MemAlloc(sizeof(_TYPE))) _TYPE
+template<typename T> void IM_DELETE(T* p)   { if (p) { p->~T(); ImGui::MemFree(p); } }
+
+// Helper: Execute a block of code at maximum once a frame. Convenient if you want to quickly create an UI within deep-nested code that runs multiple times every frame.
+// Usage: static ImGuiOnceUponAFrame oaf; if (oaf) ImGui::Text("This will be called only once per frame");
+struct ImGuiOnceUponAFrame
+{
+    ImGuiOnceUponAFrame() { RefFrame = -1; }
+    mutable int RefFrame;
+    operator bool() const { int current_frame = ImGui::GetFrameCount(); if (RefFrame == current_frame) return false; RefFrame = current_frame; return true; }
+};
+
+// Helper: Macro for ImGuiOnceUponAFrame. Attention: The macro expands into 2 statement so make sure you don't use it within e.g. an if() statement without curly braces.
+#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS    // Will obsolete
+#define IMGUI_ONCE_UPON_A_FRAME     static ImGuiOnceUponAFrame imgui_oaf; if (imgui_oaf)
+#endif
+
+// Helper: Parse and apply text filters. In format "aaaaa[,bbbb][,ccccc]"
+struct ImGuiTextFilter
+{
+    IMGUI_API           ImGuiTextFilter(const char* default_filter = "");
+    IMGUI_API bool      Draw(const char* label = "Filter (inc,-exc)", float width = 0.0f);    // Helper calling InputText+Build
+    IMGUI_API bool      PassFilter(const char* text, const char* text_end = NULL) const;
+    IMGUI_API void      Build();
+    void                Clear()          { InputBuf[0] = 0; Build(); }
+    bool                IsActive() const { return !Filters.empty(); }
+
+    // [Internal]
+    struct TextRange
+    {
+        const char* b;
+        const char* e;
+
+        TextRange() { b = e = NULL; }
+        TextRange(const char* _b, const char* _e) { b = _b; e = _e; }
+        const char*     begin() const   { return b; }
+        const char*     end () const    { return e; }
+        bool            empty() const   { return b == e; }
+        IMGUI_API void  split(char separator, ImVector<TextRange>* out) const;
+    };
+    char                InputBuf[256];
+    ImVector<TextRange> Filters;
+    int                 CountGrep;
+};
+
+// Helper: Text buffer for logging/accumulating text
+struct ImGuiTextBuffer
+{
+    ImVector<char>      Buf;
+
+    ImGuiTextBuffer()   { Buf.push_back(0); }
+    inline char         operator[](int i) { return Buf.Data[i]; }
+    const char*         begin() const { return &Buf.front(); }
+    const char*         end() const { return &Buf.back(); }      // Buf is zero-terminated, so end() will point on the zero-terminator
+    int                 size() const { return Buf.Size - 1; }
+    bool                empty() { return Buf.Size <= 1; }
+    void                clear() { Buf.clear(); Buf.push_back(0); }
+    void                reserve(int capacity) { Buf.reserve(capacity); }
+    const char*         c_str() const { return Buf.Data; }
+    IMGUI_API void      appendf(const char* fmt, ...) IM_FMTARGS(2);
+    IMGUI_API void      appendfv(const char* fmt, va_list args) IM_FMTLIST(2);
+};
+
+// Helper: key->value storage
+// Typically you don't have to worry about this since a storage is held within each Window.
+// We use it to e.g. store collapse state for a tree (Int 0/1)
+// This is optimized for efficient lookup (dichotomy into a contiguous buffer) and rare insertion (typically tied to user interactions aka max once a frame)
+// You can use it as custom user storage for temporary values. Declare your own storage if, for example:
+// - You want to manipulate the open/close state of a particular sub-tree in your interface (tree node uses Int 0/1 to store their state).
+// - You want to store custom debug data easily without adding or editing structures in your code (probably not efficient, but convenient)
+// Types are NOT stored, so it is up to you to make sure your Key don't collide with different types.
+struct ImGuiStorage
+{
+    struct Pair
+    {
+        ImGuiID key;
+        union { int val_i; float val_f; void* val_p; };
+        Pair(ImGuiID _key, int _val_i)   { key = _key; val_i = _val_i; }
+        Pair(ImGuiID _key, float _val_f) { key = _key; val_f = _val_f; }
+        Pair(ImGuiID _key, void* _val_p) { key = _key; val_p = _val_p; }
+    };
+    ImVector<Pair>      Data;
+
+    // - Get***() functions find pair, never add/allocate. Pairs are sorted so a query is O(log N)
+    // - Set***() functions find pair, insertion on demand if missing.
+    // - Sorted insertion is costly, paid once. A typical frame shouldn't need to insert any new pair.
+    void                Clear() { Data.clear(); }
+    IMGUI_API int       GetInt(ImGuiID key, int default_val = 0) const;
+    IMGUI_API void      SetInt(ImGuiID key, int val);
+    IMGUI_API bool      GetBool(ImGuiID key, bool default_val = false) const;
+    IMGUI_API void      SetBool(ImGuiID key, bool val);
+    IMGUI_API float     GetFloat(ImGuiID key, float default_val = 0.0f) const;
+    IMGUI_API void      SetFloat(ImGuiID key, float val);
+    IMGUI_API void*     GetVoidPtr(ImGuiID key) const; // default_val is NULL
+    IMGUI_API void      SetVoidPtr(ImGuiID key, void* val);
+
+    // - Get***Ref() functions finds pair, insert on demand if missing, return pointer. Useful if you intend to do Get+Set.
+    // - References are only valid until a new value is added to the storage. Calling a Set***() function or a Get***Ref() function invalidates the pointer.
+    // - A typical use case where this is convenient for quick hacking (e.g. add storage during a live Edit&Continue session if you can't modify existing struct)
+    //      float* pvar = ImGui::GetFloatRef(key); ImGui::SliderFloat("var", pvar, 0, 100.0f); some_var += *pvar;
+    IMGUI_API int*      GetIntRef(ImGuiID key, int default_val = 0);
+    IMGUI_API bool*     GetBoolRef(ImGuiID key, bool default_val = false);
+    IMGUI_API float*    GetFloatRef(ImGuiID key, float default_val = 0.0f);
+    IMGUI_API void**    GetVoidPtrRef(ImGuiID key, void* default_val = NULL);
+
+    // Use on your own storage if you know only integer are being stored (open/close all tree nodes)
+    IMGUI_API void      SetAllInt(int val);
+
+    // For quicker full rebuild of a storage (instead of an incremental one), you may add all your contents and then sort once.
+    IMGUI_API void      BuildSortByKey();
+};
+
+// Shared state of InputText(), passed as an argument to your callback when a ImGuiInputTextFlags_Callback* flag is used.
+// The callback function should return 0 by default.
+// Special processing:
+// - ImGuiInputTextFlags_CallbackCharFilter:  return 1 if the character is not allowed. You may also set 'EventChar=0' as any character replacement are allowed.
+// - ImGuiInputTextFlags_CallbackResize:      notified by InputText() when the string is resized. BufTextLen is set to the new desired string length so you can update the string size on your side of the fence. You can also replace Buf pointer if your underlying data is reallocated. No need to initialize new characters or zero-terminator as InputText will do it right after the resize callback.
+struct ImGuiInputTextCallbackData
+{
+    ImGuiInputTextFlags EventFlag;      // One ImGuiInputTextFlags_Callback*    // Read-only
+    ImGuiInputTextFlags Flags;          // What user passed to InputText()      // Read-only
+    void*               UserData;       // What user passed to InputText()      // Read-only
+
+    // Arguments for the different callback events
+    // - To modify the text buffer in a callback, prefer using the InsertChars() / DeleteChars() function. InsertChars() will take care of calling the resize callback if necessary.
+    // - If you know your edits are not going to resize the underlying buffer allocation, you may modify the contents of 'Buf[]' directly. You need to update 'BufTextLen' accordingly (0 <= BufTextLen < BufSize) and set 'BufDirty'' to true so InputText can update its internal state.
+    ImWchar             EventChar;      // Character input                      // Read-write   // [CharFilter] Replace character or set to zero. return 1 is equivalent to setting EventChar=0;
+    ImGuiKey            EventKey;       // Key pressed (Up/Down/TAB)            // Read-only    // [Completion,History]
+    char*               Buf;            // Text buffer                          // Read-write   // [Resize] Can replace pointer / [Completion,History,Always] Only write to pointed data, don't replace the actual pointer!
+    int                 BufTextLen;     // Text length in bytes                 // Read-write   // [Resize,Completion,History,Always] Exclude zero-terminator storage. In C land: == strlen(some_text), in C++ land: string.length()
+    int                 BufSize;        // Buffer capacity in bytes             // Read-only    // [Resize,Completion,History,Always] Include zero-terminator storage. In C land == ARRAYSIZE(my_char_array), in C++ land: string.capacity()+1
+    bool                BufDirty;       // Set if you modify Buf/BufTextLen!!   // Write        // [Completion,History,Always]
+    int                 CursorPos;      //                                      // Read-write   // [Completion,History,Always]
+    int                 SelectionStart; //                                      // Read-write   // [Completion,History,Always] == to SelectionEnd when no selection)
+    int                 SelectionEnd;   //                                      // Read-write   // [Completion,History,Always]
+
+    // Helper functions for text manipulation.
+    // Use those function to benefit from the CallbackResize behaviors. Calling those function reset the selection.
+    ImGuiInputTextCallbackData();
+    IMGUI_API void      DeleteChars(int pos, int bytes_count);
+    IMGUI_API void      InsertChars(int pos, const char* text, const char* text_end = NULL);
+    bool                HasSelection() const { return SelectionStart != SelectionEnd; }
+};
+
+#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
+typedef ImGuiInputTextCallback      ImGuiTextEditCallback;      // [OBSOLETE 1.63+] Made the names consistent
+typedef ImGuiInputTextCallbackData  ImGuiTextEditCallbackData;
+#endif
+
+// Resizing callback data to apply custom constraint. As enabled by SetNextWindowSizeConstraints(). Callback is called during the next Begin().
+// NB: For basic min/max size constraint on each axis you don't need to use the callback! The SetNextWindowSizeConstraints() parameters are enough.
+struct ImGuiSizeCallbackData
+{
+    void*   UserData;       // Read-only.   What user passed to SetNextWindowSizeConstraints()
+    ImVec2  Pos;            // Read-only.   Window position, for reference.
+    ImVec2  CurrentSize;    // Read-only.   Current window size.
+    ImVec2  DesiredSize;    // Read-write.  Desired size, based on user's mouse position. Write to this field to restrain resizing.
+};
+
+// Data payload for Drag and Drop operations
+struct ImGuiPayload
+{
+    // Members
+    void*           Data;               // Data (copied and owned by dear imgui)
+    int             DataSize;           // Data size
+
+    // [Internal]
+    ImGuiID         SourceId;           // Source item id
+    ImGuiID         SourceParentId;     // Source parent id (if available)
+    int             DataFrameCount;     // Data timestamp
+    char            DataType[32+1];     // Data type tag (short user-supplied string, 32 characters max)
+    bool            Preview;            // Set when AcceptDragDropPayload() was called and mouse has been hovering the target item (nb: handle overlapping drag targets)
+    bool            Delivery;           // Set when AcceptDragDropPayload() was called and mouse button is released over the target item.
+
+    ImGuiPayload()  { Clear(); }
+    void Clear()    { SourceId = SourceParentId = 0; Data = NULL; DataSize = 0; memset(DataType, 0, sizeof(DataType)); DataFrameCount = -1; Preview = Delivery = false; }
+    bool IsDataType(const char* type) const { return DataFrameCount != -1 && strcmp(type, DataType) == 0; }
+    bool IsPreview() const                  { return Preview; }
+    bool IsDelivery() const                 { return Delivery; }
+};
+
+// Helpers macros to generate 32-bits encoded colors
+#ifdef IMGUI_USE_BGRA_PACKED_COLOR
+#define IM_COL32_R_SHIFT    16
+#define IM_COL32_G_SHIFT    8
+#define IM_COL32_B_SHIFT    0
+#define IM_COL32_A_SHIFT    24
+#define IM_COL32_A_MASK     0xFF000000
+#else
+#define IM_COL32_R_SHIFT    0
+#define IM_COL32_G_SHIFT    8
+#define IM_COL32_B_SHIFT    16
+#define IM_COL32_A_SHIFT    24
+#define IM_COL32_A_MASK     0xFF000000
+#endif
+#define IM_COL32(R,G,B,A)    (((ImU32)(A)<<IM_COL32_A_SHIFT) | ((ImU32)(B)<<IM_COL32_B_SHIFT) | ((ImU32)(G)<<IM_COL32_G_SHIFT) | ((ImU32)(R)<<IM_COL32_R_SHIFT))
+#define IM_COL32_WHITE       IM_COL32(255,255,255,255)  // Opaque white = 0xFFFFFFFF
+#define IM_COL32_BLACK       IM_COL32(0,0,0,255)        // Opaque black
+#define IM_COL32_BLACK_TRANS IM_COL32(0,0,0,0)          // Transparent black = 0x00000000
+
+// Helper: ImColor() implicity converts colors to either ImU32 (packed 4x1 byte) or ImVec4 (4x1 float)
+// Prefer using IM_COL32() macros if you want a guaranteed compile-time ImU32 for usage with ImDrawList API.
+// **Avoid storing ImColor! Store either u32 of ImVec4. This is not a full-featured color class. MAY OBSOLETE.
+// **None of the ImGui API are using ImColor directly but you can use it as a convenience to pass colors in either ImU32 or ImVec4 formats. Explicitly cast to ImU32 or ImVec4 if needed.
+struct ImColor
+{
+    ImVec4              Value;
+
+    ImColor()                                                       { Value.x = Value.y = Value.z = Value.w = 0.0f; }
+    ImColor(int r, int g, int b, int a = 255)                       { float sc = 1.0f/255.0f; Value.x = (float)r * sc; Value.y = (float)g * sc; Value.z = (float)b * sc; Value.w = (float)a * sc; }
+    ImColor(ImU32 rgba)                                             { float sc = 1.0f/255.0f; Value.x = (float)((rgba>>IM_COL32_R_SHIFT)&0xFF) * sc; Value.y = (float)((rgba>>IM_COL32_G_SHIFT)&0xFF) * sc; Value.z = (float)((rgba>>IM_COL32_B_SHIFT)&0xFF) * sc; Value.w = (float)((rgba>>IM_COL32_A_SHIFT)&0xFF) * sc; }
+    ImColor(float r, float g, float b, float a = 1.0f)              { Value.x = r; Value.y = g; Value.z = b; Value.w = a; }
+    ImColor(const ImVec4& col)                                      { Value = col; }
+    inline operator ImU32() const                                   { return ImGui::ColorConvertFloat4ToU32(Value); }
+    inline operator ImVec4() const                                  { return Value; }
+
+    // FIXME-OBSOLETE: May need to obsolete/cleanup those helpers.
+    inline void    SetHSV(float h, float s, float v, float a = 1.0f){ ImGui::ColorConvertHSVtoRGB(h, s, v, Value.x, Value.y, Value.z); Value.w = a; }
+    static ImColor HSV(float h, float s, float v, float a = 1.0f)   { float r,g,b; ImGui::ColorConvertHSVtoRGB(h, s, v, r, g, b); return ImColor(r,g,b,a); }
+};
+
+// Helper: Manually clip large list of items.
+// If you are submitting lots of evenly spaced items and you have a random access to the list, you can perform coarse clipping based on visibility to save yourself from processing those items at all.
+// The clipper calculates the range of visible items and advance the cursor to compensate for the non-visible items we have skipped.
+// ImGui already clip items based on their bounds but it needs to measure text size to do so. Coarse clipping before submission makes this cost and your own data fetching/submission cost null.
+// Usage:
+//     ImGuiListClipper clipper(1000);  // we have 1000 elements, evenly spaced.
+//     while (clipper.Step())
+//         for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
+//             ImGui::Text("line number %d", i);
+// - Step 0: the clipper let you process the first element, regardless of it being visible or not, so we can measure the element height (step skipped if we passed a known height as second arg to constructor).
+// - Step 1: the clipper infer height from first element, calculate the actual range of elements to display, and position the cursor before the first element.
+// - (Step 2: dummy step only required if an explicit items_height was passed to constructor or Begin() and user call Step(). Does nothing and switch to Step 3.)
+// - Step 3: the clipper validate that we have reached the expected Y position (corresponding to element DisplayEnd), advance the cursor to the end of the list and then returns 'false' to end the loop.
+struct ImGuiListClipper
+{
+    float   StartPosY;
+    float   ItemsHeight;
+    int     ItemsCount, StepNo, DisplayStart, DisplayEnd;
+
+    // items_count:  Use -1 to ignore (you can call Begin later). Use INT_MAX if you don't know how many items you have (in which case the cursor won't be advanced in the final step).
+    // items_height: Use -1.0f to be calculated automatically on first step. Otherwise pass in the distance between your items, typically GetTextLineHeightWithSpacing() or GetFrameHeightWithSpacing().
+    // If you don't specify an items_height, you NEED to call Step(). If you specify items_height you may call the old Begin()/End() api directly, but prefer calling Step().
+    ImGuiListClipper(int items_count = -1, float items_height = -1.0f)  { Begin(items_count, items_height); } // NB: Begin() initialize every fields (as we allow user to call Begin/End multiple times on a same instance if they want).
+    ~ImGuiListClipper()                                                 { IM_ASSERT(ItemsCount == -1); }      // Assert if user forgot to call End() or Step() until false.
+
+    IMGUI_API bool Step();                                              // Call until it returns false. The DisplayStart/DisplayEnd fields will be set and you can process/draw those items.
+    IMGUI_API void Begin(int items_count, float items_height = -1.0f);  // Automatically called by constructor if you passed 'items_count' or by Step() in Step 1.
+    IMGUI_API void End();                                               // Automatically called on the last call of Step() that returns false.
+};
+
+//-----------------------------------------------------------------------------
+// Draw List
+// Hold a series of drawing commands. The user provides a renderer for ImDrawData which essentially contains an array of ImDrawList.
+//-----------------------------------------------------------------------------
+
+// Draw callbacks for advanced uses.
+// NB- You most likely do NOT need to use draw callbacks just to create your own widget or customized UI rendering (you can poke into the draw list for that)
+// Draw callback may be useful for example, A) Change your GPU render state, B) render a complex 3D scene inside a UI element (without an intermediate texture/render target), etc.
+// The expected behavior from your rendering function is 'if (cmd.UserCallback != NULL) cmd.UserCallback(parent_list, cmd); else RenderTriangles()'
+typedef void (*ImDrawCallback)(const ImDrawList* parent_list, const ImDrawCmd* cmd);
+
+// Typically, 1 command = 1 GPU draw call (unless command is a callback)
+struct ImDrawCmd
+{
+    unsigned int    ElemCount;              // Number of indices (multiple of 3) to be rendered as triangles. Vertices are stored in the callee ImDrawList's vtx_buffer[] array, indices in idx_buffer[].
+    ImVec4          ClipRect;               // Clipping rectangle (x1, y1, x2, y2). Subtract ImDrawData->DisplayPos to get clipping rectangle in "viewport" coordinates
+    ImTextureID     TextureId;              // User-provided texture ID. Set by user in ImfontAtlas::SetTexID() for fonts or passed to Image*() functions. Ignore if never using images or multiple fonts atlas.
+    ImDrawCallback  UserCallback;           // If != NULL, call the function instead of rendering the vertices. clip_rect and texture_id will be set normally.
+    void*           UserCallbackData;       // The draw callback code can access this.
+
+    ImDrawCmd() { ElemCount = 0; ClipRect.x = ClipRect.y = ClipRect.z = ClipRect.w = 0.0f; TextureId = NULL; UserCallback = NULL; UserCallbackData = NULL; }
+};
+
+// Vertex index (override with '#define ImDrawIdx unsigned int' inside in imconfig.h)
+#ifndef ImDrawIdx
+typedef unsigned short ImDrawIdx;
+#endif
+
+// Vertex layout
+#ifndef IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT
+struct ImDrawVert
+{
+    ImVec2  pos;
+    ImVec2  uv;
+    ImU32   col;
+};
+#else
+// You can override the vertex format layout by defining IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT in imconfig.h
+// The code expect ImVec2 pos (8 bytes), ImVec2 uv (8 bytes), ImU32 col (4 bytes), but you can re-order them or add other fields as needed to simplify integration in your engine.
+// The type has to be described within the macro (you can either declare the struct or use a typedef)
+// NOTE: IMGUI DOESN'T CLEAR THE STRUCTURE AND DOESN'T CALL A CONSTRUCTOR SO ANY CUSTOM FIELD WILL BE UNINITIALIZED. IF YOU ADD EXTRA FIELDS (SUCH AS A 'Z' COORDINATES) YOU WILL NEED TO CLEAR THEM DURING RENDER OR TO IGNORE THEM.
+IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT;
+#endif
+
+// Draw channels are used by the Columns API to "split" the render list into different channels while building, so items of each column can be batched together.
+// You can also use them to simulate drawing layers and submit primitives in a different order than how they will be rendered.
+struct ImDrawChannel
+{
+    ImVector<ImDrawCmd>     CmdBuffer;
+    ImVector<ImDrawIdx>     IdxBuffer;
+};
+
+enum ImDrawCornerFlags_
+{
+    ImDrawCornerFlags_TopLeft   = 1 << 0, // 0x1
+    ImDrawCornerFlags_TopRight  = 1 << 1, // 0x2
+    ImDrawCornerFlags_BotLeft   = 1 << 2, // 0x4
+    ImDrawCornerFlags_BotRight  = 1 << 3, // 0x8
+    ImDrawCornerFlags_Top       = ImDrawCornerFlags_TopLeft | ImDrawCornerFlags_TopRight,   // 0x3
+    ImDrawCornerFlags_Bot       = ImDrawCornerFlags_BotLeft | ImDrawCornerFlags_BotRight,   // 0xC
+    ImDrawCornerFlags_Left      = ImDrawCornerFlags_TopLeft | ImDrawCornerFlags_BotLeft,    // 0x5
+    ImDrawCornerFlags_Right     = ImDrawCornerFlags_TopRight | ImDrawCornerFlags_BotRight,  // 0xA
+    ImDrawCornerFlags_All       = 0xF     // In your function calls you may use ~0 (= all bits sets) instead of ImDrawCornerFlags_All, as a convenience
+};
+
+enum ImDrawListFlags_
+{
+    ImDrawListFlags_AntiAliasedLines = 1 << 0,
+    ImDrawListFlags_AntiAliasedFill  = 1 << 1
+};
+
+// Draw command list
+// This is the low-level list of polygons that ImGui functions are filling. At the end of the frame, all command lists are passed to your ImGuiIO::RenderDrawListFn function for rendering.
+// Each ImGui window contains its own ImDrawList. You can use ImGui::GetWindowDrawList() to access the current window draw list and draw custom primitives.
+// You can interleave normal ImGui:: calls and adding primitives to the current draw list.
+// All positions are generally in pixel coordinates (top-left at (0,0), bottom-right at io.DisplaySize), but you are totally free to apply whatever transformation matrix to want to the data (if you apply such transformation you'll want to apply it to ClipRect as well)
+// Important: Primitives are always added to the list and not culled (culling is done at higher-level by ImGui:: functions), if you use this API a lot consider coarse culling your drawn objects.
+struct ImDrawList
+{
+    // This is what you have to render
+    ImVector<ImDrawCmd>     CmdBuffer;          // Draw commands. Typically 1 command = 1 GPU draw call, unless the command is a callback.
+    ImVector<ImDrawIdx>     IdxBuffer;          // Index buffer. Each command consume ImDrawCmd::ElemCount of those
+    ImVector<ImDrawVert>    VtxBuffer;          // Vertex buffer.
+    ImDrawListFlags         Flags;              // Flags, you may poke into these to adjust anti-aliasing settings per-primitive.
+
+    // [Internal, used while building lists]
+    const ImDrawListSharedData* _Data;          // Pointer to shared draw data (you can use ImGui::GetDrawListSharedData() to get the one from current ImGui context)
+    const char*             _OwnerName;         // Pointer to owner window's name for debugging
+    unsigned int            _VtxCurrentIdx;     // [Internal] == VtxBuffer.Size
+    ImDrawVert*             _VtxWritePtr;       // [Internal] point within VtxBuffer.Data after each add command (to avoid using the ImVector<> operators too much)
+    ImDrawIdx*              _IdxWritePtr;       // [Internal] point within IdxBuffer.Data after each add command (to avoid using the ImVector<> operators too much)
+    ImVector<ImVec4>        _ClipRectStack;     // [Internal]
+    ImVector<ImTextureID>   _TextureIdStack;    // [Internal]
+    ImVector<ImVec2>        _Path;              // [Internal] current path building
+    int                     _ChannelsCurrent;   // [Internal] current channel number (0)
+    int                     _ChannelsCount;     // [Internal] number of active channels (1+)
+    ImVector<ImDrawChannel> _Channels;          // [Internal] draw channels for columns API (not resized down so _ChannelsCount may be smaller than _Channels.Size)
+
+    // If you want to create ImDrawList instances, pass them ImGui::GetDrawListSharedData() or create and use your own ImDrawListSharedData (so you can use ImDrawList without ImGui)
+    ImDrawList(const ImDrawListSharedData* shared_data) { _Data = shared_data; _OwnerName = NULL; Clear(); }
+    ~ImDrawList() { ClearFreeMemory(); }
+    IMGUI_API void  PushClipRect(ImVec2 clip_rect_min, ImVec2 clip_rect_max, bool intersect_with_current_clip_rect = false);  // Render-level scissoring. This is passed down to your render function but not used for CPU-side coarse clipping. Prefer using higher-level ImGui::PushClipRect() to affect logic (hit-testing and widget culling)
+    IMGUI_API void  PushClipRectFullScreen();
+    IMGUI_API void  PopClipRect();
+    IMGUI_API void  PushTextureID(ImTextureID texture_id);
+    IMGUI_API void  PopTextureID();
+    inline ImVec2   GetClipRectMin() const { const ImVec4& cr = _ClipRectStack.back(); return ImVec2(cr.x, cr.y); }
+    inline ImVec2   GetClipRectMax() const { const ImVec4& cr = _ClipRectStack.back(); return ImVec2(cr.z, cr.w); }
+
+    // Primitives
+    IMGUI_API void  AddLine(const ImVec2& a, const ImVec2& b, ImU32 col, float thickness = 1.0f);
+    IMGUI_API void  AddRect(const ImVec2& a, const ImVec2& b, ImU32 col, float rounding = 0.0f, int rounding_corners_flags = ImDrawCornerFlags_All, float thickness = 1.0f);   // a: upper-left, b: lower-right, rounding_corners_flags: 4-bits corresponding to which corner to round
+    IMGUI_API void  AddRectFilled(const ImVec2& a, const ImVec2& b, ImU32 col, float rounding = 0.0f, int rounding_corners_flags = ImDrawCornerFlags_All);                     // a: upper-left, b: lower-right
+    IMGUI_API void  AddRectFilledMultiColor(const ImVec2& a, const ImVec2& b, ImU32 col_upr_left, ImU32 col_upr_right, ImU32 col_bot_right, ImU32 col_bot_left);
+    IMGUI_API void  AddQuad(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, ImU32 col, float thickness = 1.0f);
+    IMGUI_API void  AddQuadFilled(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, ImU32 col);
+    IMGUI_API void  AddTriangle(const ImVec2& a, const ImVec2& b, const ImVec2& c, ImU32 col, float thickness = 1.0f);
+    IMGUI_API void  AddTriangleFilled(const ImVec2& a, const ImVec2& b, const ImVec2& c, ImU32 col);
+    IMGUI_API void  AddCircle(const ImVec2& centre, float radius, ImU32 col, int num_segments = 12, float thickness = 1.0f);
+    IMGUI_API void  AddCircleFilled(const ImVec2& centre, float radius, ImU32 col, int num_segments = 12);
+    IMGUI_API void  AddText(const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end = NULL);
+    IMGUI_API void  AddText(const ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end = NULL, float wrap_width = 0.0f, const ImVec4* cpu_fine_clip_rect = NULL);
+    IMGUI_API void  AddImage(ImTextureID user_texture_id, const ImVec2& a, const ImVec2& b, const ImVec2& uv_a = ImVec2(0,0), const ImVec2& uv_b = ImVec2(1,1), ImU32 col = 0xFFFFFFFF);
+    IMGUI_API void  AddImageQuad(ImTextureID user_texture_id, const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, const ImVec2& uv_a = ImVec2(0,0), const ImVec2& uv_b = ImVec2(1,0), const ImVec2& uv_c = ImVec2(1,1), const ImVec2& uv_d = ImVec2(0,1), ImU32 col = 0xFFFFFFFF);
+    IMGUI_API void  AddImageRounded(ImTextureID user_texture_id, const ImVec2& a, const ImVec2& b, const ImVec2& uv_a, const ImVec2& uv_b, ImU32 col, float rounding, int rounding_corners = ImDrawCornerFlags_All);
+    IMGUI_API void  AddPolyline(const ImVec2* points, const int num_points, ImU32 col, bool closed, float thickness);
+    IMGUI_API void  AddConvexPolyFilled(const ImVec2* points, const int num_points, ImU32 col); // Note: Anti-aliased filling requires points to be in clockwise order.
+    IMGUI_API void  AddBezierCurve(const ImVec2& pos0, const ImVec2& cp0, const ImVec2& cp1, const ImVec2& pos1, ImU32 col, float thickness, int num_segments = 0);
+
+    // Stateful path API, add points then finish with PathFillConvex() or PathStroke()
+    inline    void  PathClear()                                                 { _Path.resize(0); }
+    inline    void  PathLineTo(const ImVec2& pos)                               { _Path.push_back(pos); }
+    inline    void  PathLineToMergeDuplicate(const ImVec2& pos)                 { if (_Path.Size == 0 || memcmp(&_Path[_Path.Size-1], &pos, 8) != 0) _Path.push_back(pos); }
+    inline    void  PathFillConvex(ImU32 col)                                   { AddConvexPolyFilled(_Path.Data, _Path.Size, col); PathClear(); }  // Note: Anti-aliased filling requires points to be in clockwise order.
+    inline    void  PathStroke(ImU32 col, bool closed, float thickness = 1.0f)  { AddPolyline(_Path.Data, _Path.Size, col, closed, thickness); PathClear(); }
+    IMGUI_API void  PathArcTo(const ImVec2& centre, float radius, float a_min, float a_max, int num_segments = 10);
+    IMGUI_API void  PathArcToFast(const ImVec2& centre, float radius, int a_min_of_12, int a_max_of_12);                                            // Use precomputed angles for a 12 steps circle
+    IMGUI_API void  PathBezierCurveTo(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, int num_segments = 0);
+    IMGUI_API void  PathRect(const ImVec2& rect_min, const ImVec2& rect_max, float rounding = 0.0f, int rounding_corners_flags = ImDrawCornerFlags_All);
+
+    // Channels
+    // - Use to simulate layers. By switching channels to can render out-of-order (e.g. submit foreground primitives before background primitives)
+    // - Use to minimize draw calls (e.g. if going back-and-forth between multiple non-overlapping clipping rectangles, prefer to append into separate channels then merge at the end)
+    IMGUI_API void  ChannelsSplit(int channels_count);
+    IMGUI_API void  ChannelsMerge();
+    IMGUI_API void  ChannelsSetCurrent(int channel_index);
+
+    // Advanced
+    IMGUI_API void  AddCallback(ImDrawCallback callback, void* callback_data);  // Your rendering function must check for 'UserCallback' in ImDrawCmd and call the function instead of rendering triangles.
+    IMGUI_API void  AddDrawCmd();                                               // This is useful if you need to forcefully create a new draw call (to allow for dependent rendering / blending). Otherwise primitives are merged into the same draw-call as much as possible
+    IMGUI_API ImDrawList* CloneOutput() const;                                  // Create a clone of the CmdBuffer/IdxBuffer/VtxBuffer.
+
+    // Internal helpers
+    // NB: all primitives needs to be reserved via PrimReserve() beforehand!
+    IMGUI_API void  Clear();
+    IMGUI_API void  ClearFreeMemory();
+    IMGUI_API void  PrimReserve(int idx_count, int vtx_count);
+    IMGUI_API void  PrimRect(const ImVec2& a, const ImVec2& b, ImU32 col);      // Axis aligned rectangle (composed of two triangles)
+    IMGUI_API void  PrimRectUV(const ImVec2& a, const ImVec2& b, const ImVec2& uv_a, const ImVec2& uv_b, ImU32 col);
+    IMGUI_API void  PrimQuadUV(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, const ImVec2& uv_a, const ImVec2& uv_b, const ImVec2& uv_c, const ImVec2& uv_d, ImU32 col);
+    inline    void  PrimWriteVtx(const ImVec2& pos, const ImVec2& uv, ImU32 col){ _VtxWritePtr->pos = pos; _VtxWritePtr->uv = uv; _VtxWritePtr->col = col; _VtxWritePtr++; _VtxCurrentIdx++; }
+    inline    void  PrimWriteIdx(ImDrawIdx idx)                                 { *_IdxWritePtr = idx; _IdxWritePtr++; }
+    inline    void  PrimVtx(const ImVec2& pos, const ImVec2& uv, ImU32 col)     { PrimWriteIdx((ImDrawIdx)_VtxCurrentIdx); PrimWriteVtx(pos, uv, col); }
+    IMGUI_API void  UpdateClipRect();
+    IMGUI_API void  UpdateTextureID();
+};
+
+// All draw data to render an ImGui frame
+// (NB: the style and the naming convention here is a little inconsistent but we preserve them for backward compatibility purpose)
+struct ImDrawData
+{
+    bool            Valid;                  // Only valid after Render() is called and before the next NewFrame() is called.
+    ImDrawList**    CmdLists;               // Array of ImDrawList* to render. The ImDrawList are owned by ImGuiContext and only pointed to from here.
+    int             CmdListsCount;          // Number of ImDrawList* to render
+    int             TotalIdxCount;          // For convenience, sum of all ImDrawList's IdxBuffer.Size
+    int             TotalVtxCount;          // For convenience, sum of all ImDrawList's VtxBuffer.Size
+    ImVec2          DisplayPos;             // Upper-left position of the viewport to render (== upper-left of the orthogonal projection matrix to use)
+    ImVec2          DisplaySize;            // Size of the viewport to render (== io.DisplaySize for the main viewport) (DisplayPos + DisplaySize == lower-right of the orthogonal projection matrix to use)
+
+    // Functions
+    ImDrawData()    { Valid = false; Clear(); }
+    ~ImDrawData()   { Clear(); }
+    void Clear()    { Valid = false; CmdLists = NULL; CmdListsCount = TotalVtxCount = TotalIdxCount = 0; DisplayPos = DisplaySize = ImVec2(0.f, 0.f); } // The ImDrawList are owned by ImGuiContext!
+    IMGUI_API void  DeIndexAllBuffers();                // Helper to convert all buffers from indexed to non-indexed, in case you cannot render indexed. Note: this is slow and most likely a waste of resources. Always prefer indexed rendering!
+    IMGUI_API void  ScaleClipRects(const ImVec2& sc);   // Helper to scale the ClipRect field of each ImDrawCmd. Use if your final output buffer is at a different scale than ImGui expects, or if there is a difference between your window resolution and framebuffer resolution.
+};
+
+struct ImFontConfig
+{
+    void*           FontData;               //          // TTF/OTF data
+    int             FontDataSize;           //          // TTF/OTF data size
+    bool            FontDataOwnedByAtlas;   // true     // TTF/OTF data ownership taken by the container ImFontAtlas (will delete memory itself).
+    int             FontNo;                 // 0        // Index of font within TTF/OTF file
+    float           SizePixels;             //          // Size in pixels for rasterizer (more or less maps to the resulting font height).
+    int             OversampleH;            // 3        // Rasterize at higher quality for sub-pixel positioning. We don't use sub-pixel positions on the Y axis.
+    int             OversampleV;            // 1        // Rasterize at higher quality for sub-pixel positioning. We don't use sub-pixel positions on the Y axis.
+    bool            PixelSnapH;             // false    // Align every glyph to pixel boundary. Useful e.g. if you are merging a non-pixel aligned font with the default font. If enabled, you can set OversampleH/V to 1.
+    ImVec2          GlyphExtraSpacing;      // 0, 0     // Extra spacing (in pixels) between glyphs. Only X axis is supported for now.
+    ImVec2          GlyphOffset;            // 0, 0     // Offset all glyphs from this font input.
+    const ImWchar*  GlyphRanges;            // NULL     // Pointer to a user-provided list of Unicode range (2 value per range, values are inclusive, zero-terminated list). THE ARRAY DATA NEEDS TO PERSIST AS LONG AS THE FONT IS ALIVE.
+    float           GlyphMinAdvanceX;       // 0        // Minimum AdvanceX for glyphs, set Min to align font icons, set both Min/Max to enforce mono-space font
+    float           GlyphMaxAdvanceX;       // FLT_MAX  // Maximum AdvanceX for glyphs
+    bool            MergeMode;              // false    // Merge into previous ImFont, so you can combine multiple inputs font into one ImFont (e.g. ASCII font + icons + Japanese glyphs). You may want to use GlyphOffset.y when merge font of different heights.
+    unsigned int    RasterizerFlags;        // 0x00     // Settings for custom font rasterizer (e.g. ImGuiFreeType). Leave as zero if you aren't using one.
+    float           RasterizerMultiply;     // 1.0f     // Brighten (>1.0f) or darken (<1.0f) font output. Brightening small fonts may be a good workaround to make them more readable.
+
+    // [Internal]
+    char            Name[40];               // Name (strictly to ease debugging)
+    ImFont*         DstFont;
+
+    IMGUI_API ImFontConfig();
+};
+
+struct ImFontGlyph
+{
+    ImWchar         Codepoint;          // 0x0000..0xFFFF
+    float           AdvanceX;           // Distance to next character (= data from font + ImFontConfig::GlyphExtraSpacing.x baked in)
+    float           X0, Y0, X1, Y1;     // Glyph corners
+    float           U0, V0, U1, V1;     // Texture coordinates
+};
+
+enum ImFontAtlasFlags_
+{
+    ImFontAtlasFlags_None               = 0,
+    ImFontAtlasFlags_NoPowerOfTwoHeight = 1 << 0,   // Don't round the height to next power of two
+    ImFontAtlasFlags_NoMouseCursors     = 1 << 1    // Don't build software mouse cursors into the atlas
+};
+
+// Load and rasterize multiple TTF/OTF fonts into a same texture. The font atlas will build a single texture holding:
+//  - One or more fonts.
+//  - Custom graphics data needed to render the shapes needed by Dear ImGui.
+//  - Mouse cursor shapes for software cursor rendering (unless setting 'Flags |= ImFontAtlasFlags_NoMouseCursors' in the font atlas).
+// It is the user-code responsibility to setup/build the atlas, then upload the pixel data into a texture accessible by your graphics api.
+//  - Optionally, call any of the AddFont*** functions. If you don't call any, the default font embedded in the code will be loaded for you.
+//  - Call GetTexDataAsAlpha8() or GetTexDataAsRGBA32() to build and retrieve pixels data.
+//  - Upload the pixels data into a texture within your graphics system (see imgui_impl_xxxx.cpp examples)
+//  - Call SetTexID(my_tex_id); and pass the pointer/identifier to your texture in a format natural to your graphics API. 
+//    This value will be passed back to you during rendering to identify the texture. Read FAQ entry about ImTextureID for more details.
+// Common pitfalls:
+// - If you pass a 'glyph_ranges' array to AddFont*** functions, you need to make sure that your array persist up until the 
+//   atlas is build (when calling GetTexData*** or Build()). We only copy the pointer, not the data.
+// - Important: By default, AddFontFromMemoryTTF() takes ownership of the data. Even though we are not writing to it, we will free the pointer on destruction.
+//   You can set font_cfg->FontDataOwnedByAtlas=false to keep ownership of your data and it won't be freed, 
+// - Even though many functions are suffixed with "TTF", OTF data is supported just as well.
+// - This is an old API and it is currently awkward for those and and various other reasons! We will address them in the future!
+struct ImFontAtlas
+{
+    IMGUI_API ImFontAtlas();
+    IMGUI_API ~ImFontAtlas();
+    IMGUI_API ImFont*           AddFont(const ImFontConfig* font_cfg);
+    IMGUI_API ImFont*           AddFontDefault(const ImFontConfig* font_cfg = NULL);
+    IMGUI_API ImFont*           AddFontFromFileTTF(const char* filename, float size_pixels, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL);
+    IMGUI_API ImFont*           AddFontFromMemoryTTF(void* font_data, int font_size, float size_pixels, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // Note: Transfer ownership of 'ttf_data' to ImFontAtlas! Will be deleted after destruction of the atlas. Set font_cfg->FontDataOwnedByAtlas=false to keep ownership of your data and it won't be freed.
+    IMGUI_API ImFont*           AddFontFromMemoryCompressedTTF(const void* compressed_font_data, int compressed_font_size, float size_pixels, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // 'compressed_font_data' still owned by caller. Compress with binary_to_compressed_c.cpp.
+    IMGUI_API ImFont*           AddFontFromMemoryCompressedBase85TTF(const char* compressed_font_data_base85, float size_pixels, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL);              // 'compressed_font_data_base85' still owned by caller. Compress with binary_to_compressed_c.cpp with -base85 parameter.
+    IMGUI_API void              ClearInputData();           // Clear input data (all ImFontConfig structures including sizes, TTF data, glyph ranges, etc.) = all the data used to build the texture and fonts.
+    IMGUI_API void              ClearTexData();             // Clear output texture data (CPU side). Saves RAM once the texture has been copied to graphics memory.
+    IMGUI_API void              ClearFonts();               // Clear output font data (glyphs storage, UV coordinates).
+    IMGUI_API void              Clear();                    // Clear all input and output.
+
+    // Build atlas, retrieve pixel data.
+    // User is in charge of copying the pixels into graphics memory (e.g. create a texture with your engine). Then store your texture handle with SetTexID().
+    // The pitch is always = Width * BytesPerPixels (1 or 4)
+    // Building in RGBA32 format is provided for convenience and compatibility, but note that unless you manually manipulate or copy color data into 
+    // the texture (e.g. when using the AddCustomRect*** api), then the RGB pixels emitted will always be white (~75% of memory/bandwidth waste.
+    IMGUI_API bool              Build();                    // Build pixels data. This is called automatically for you by the GetTexData*** functions.
+    IMGUI_API bool              IsBuilt()                   { return Fonts.Size > 0 && (TexPixelsAlpha8 != NULL || TexPixelsRGBA32 != NULL); }
+    IMGUI_API void              GetTexDataAsAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL);  // 1 byte per-pixel
+    IMGUI_API void              GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL);  // 4 bytes-per-pixel
+    void                        SetTexID(ImTextureID id)    { TexID = id; }
+
+    //-------------------------------------------
+    // Glyph Ranges
+    //-------------------------------------------
+
+    // Helpers to retrieve list of common Unicode ranges (2 value per range, values are inclusive, zero-terminated list)
+    // NB: Make sure that your string are UTF-8 and NOT in your local code page. In C++11, you can create UTF-8 string literal using the u8"Hello world" syntax. See FAQ for details.
+    // NB: Consider using GlyphRangesBuilder to build glyph ranges from textual data.
+    IMGUI_API const ImWchar*    GetGlyphRangesDefault();                // Basic Latin, Extended Latin
+    IMGUI_API const ImWchar*    GetGlyphRangesKorean();                 // Default + Korean characters
+    IMGUI_API const ImWchar*    GetGlyphRangesJapanese();               // Default + Hiragana, Katakana, Half-Width, Selection of 1946 Ideographs
+    IMGUI_API const ImWchar*    GetGlyphRangesChineseFull();            // Default + Half-Width + Japanese Hiragana/Katakana + full set of about 21000 CJK Unified Ideographs
+    IMGUI_API const ImWchar*    GetGlyphRangesChineseSimplifiedCommon();// Default + Half-Width + Japanese Hiragana/Katakana + set of 2500 CJK Unified Ideographs for common simplified Chinese
+    IMGUI_API const ImWchar*    GetGlyphRangesCyrillic();               // Default + about 400 Cyrillic characters
+    IMGUI_API const ImWchar*    GetGlyphRangesThai();                   // Default + Thai characters
+
+    // Helpers to build glyph ranges from text data. Feed your application strings/characters to it then call BuildRanges().
+    struct GlyphRangesBuilder
+    {
+        ImVector<unsigned char> UsedChars;  // Store 1-bit per Unicode code point (0=unused, 1=used)
+        GlyphRangesBuilder()                { UsedChars.resize(0x10000 / 8); memset(UsedChars.Data, 0, 0x10000 / 8); }
+        bool           GetBit(int n) const  { return (UsedChars[n >> 3] & (1 << (n & 7))) != 0; }
+        void           SetBit(int n)        { UsedChars[n >> 3] |= 1 << (n & 7); }  // Set bit 'c' in the array
+        void           AddChar(ImWchar c)   { SetBit(c); }                          // Add character
+        IMGUI_API void AddText(const char* text, const char* text_end = NULL);      // Add string (each character of the UTF-8 string are added)
+        IMGUI_API void AddRanges(const ImWchar* ranges);                            // Add ranges, e.g. builder.AddRanges(ImFontAtlas::GetGlyphRangesDefault()) to force add all of ASCII/Latin+Ext
+        IMGUI_API void BuildRanges(ImVector<ImWchar>* out_ranges);                  // Output new ranges
+    };
+
+    //-------------------------------------------
+    // Custom Rectangles/Glyphs API
+    //-------------------------------------------
+
+    // You can request arbitrary rectangles to be packed into the atlas, for your own purposes. After calling Build(), you can query the rectangle position and render your pixels.
+    // You can also request your rectangles to be mapped as font glyph (given a font + Unicode point), so you can render e.g. custom colorful icons and use them as regular glyphs.
+    struct CustomRect
+    {
+        unsigned int    ID;             // Input    // User ID. Use <0x10000 to map into a font glyph, >=0x10000 for other/internal/custom texture data.
+        unsigned short  Width, Height;  // Input    // Desired rectangle dimension
+        unsigned short  X, Y;           // Output   // Packed position in Atlas
+        float           GlyphAdvanceX;  // Input    // For custom font glyphs only (ID<0x10000): glyph xadvance
+        ImVec2          GlyphOffset;    // Input    // For custom font glyphs only (ID<0x10000): glyph display offset
+        ImFont*         Font;           // Input    // For custom font glyphs only (ID<0x10000): target font
+        CustomRect()            { ID = 0xFFFFFFFF; Width = Height = 0; X = Y = 0xFFFF; GlyphAdvanceX = 0.0f; GlyphOffset = ImVec2(0,0); Font = NULL; }
+        bool IsPacked() const   { return X != 0xFFFF; }
+    };
+
+    IMGUI_API int       AddCustomRectRegular(unsigned int id, int width, int height);                                                                   // Id needs to be >= 0x10000. Id >= 0x80000000 are reserved for ImGui and ImDrawList
+    IMGUI_API int       AddCustomRectFontGlyph(ImFont* font, ImWchar id, int width, int height, float advance_x, const ImVec2& offset = ImVec2(0,0));   // Id needs to be < 0x10000 to register a rectangle to map into a specific font.
+    const CustomRect*   GetCustomRectByIndex(int index) const { if (index < 0) return NULL; return &CustomRects[index]; }
+
+    // [Internal]
+    IMGUI_API void      CalcCustomRectUV(const CustomRect* rect, ImVec2* out_uv_min, ImVec2* out_uv_max);
+    IMGUI_API bool      GetMouseCursorTexData(ImGuiMouseCursor cursor, ImVec2* out_offset, ImVec2* out_size, ImVec2 out_uv_border[2], ImVec2 out_uv_fill[2]);
+
+    //-------------------------------------------
+    // Members
+    //-------------------------------------------
+
+    bool                        Locked;             // Marked as Locked by ImGui::NewFrame() so attempt to modify the atlas will assert.
+    ImFontAtlasFlags            Flags;              // Build flags (see ImFontAtlasFlags_)
+    ImTextureID                 TexID;              // User data to refer to the texture once it has been uploaded to user's graphic systems. It is passed back to you during rendering via the ImDrawCmd structure.
+    int                         TexDesiredWidth;    // Texture width desired by user before Build(). Must be a power-of-two. If have many glyphs your graphics API have texture size restrictions you may want to increase texture width to decrease height.
+    int                         TexGlyphPadding;    // Padding between glyphs within texture in pixels. Defaults to 1.
+
+    // [Internal]
+    // NB: Access texture data via GetTexData*() calls! Which will setup a default font for you.
+    unsigned char*              TexPixelsAlpha8;    // 1 component per pixel, each component is unsigned 8-bit. Total size = TexWidth * TexHeight
+    unsigned int*               TexPixelsRGBA32;    // 4 component per pixel, each component is unsigned 8-bit. Total size = TexWidth * TexHeight * 4
+    int                         TexWidth;           // Texture width calculated during Build().
+    int                         TexHeight;          // Texture height calculated during Build().
+    ImVec2                      TexUvScale;         // = (1.0f/TexWidth, 1.0f/TexHeight)
+    ImVec2                      TexUvWhitePixel;    // Texture coordinates to a white pixel
+    ImVector<ImFont*>           Fonts;              // Hold all the fonts returned by AddFont*. Fonts[0] is the default font upon calling ImGui::NewFrame(), use ImGui::PushFont()/PopFont() to change the current font.
+    ImVector<CustomRect>        CustomRects;        // Rectangles for packing custom texture data into the atlas.
+    ImVector<ImFontConfig>      ConfigData;         // Internal data
+    int                         CustomRectIds[1];   // Identifiers of custom texture rectangle used by ImFontAtlas/ImDrawList
+};
+
+// Font runtime data and rendering
+// ImFontAtlas automatically loads a default embedded font for you when you call GetTexDataAsAlpha8() or GetTexDataAsRGBA32().
+struct ImFont
+{
+    // Members: Hot ~62/78 bytes
+    float                       FontSize;           // <user set>   // Height of characters, set during loading (don't change after loading)
+    float                       Scale;              // = 1.f        // Base font scale, multiplied by the per-window font scale which you can adjust with SetFontScale()
+    ImVec2                      DisplayOffset;      // = (0.f,0.f)  // Offset font rendering by xx pixels
+    ImVector<ImFontGlyph>       Glyphs;             //              // All glyphs.
+    ImVector<float>             IndexAdvanceX;      //              // Sparse. Glyphs->AdvanceX in a directly indexable way (more cache-friendly, for CalcTextSize functions which are often bottleneck in large UI).
+    ImVector<unsigned short>    IndexLookup;        //              // Sparse. Index glyphs by Unicode code-point.
+    const ImFontGlyph*          FallbackGlyph;      // == FindGlyph(FontFallbackChar)
+    float                       FallbackAdvanceX;   // == FallbackGlyph->AdvanceX
+    ImWchar                     FallbackChar;       // = '?'        // Replacement glyph if one isn't found. Only set via SetFallbackChar()
+
+    // Members: Cold ~18/26 bytes
+    short                       ConfigDataCount;    // ~ 1          // Number of ImFontConfig involved in creating this font. Bigger than 1 when merging multiple font sources into one ImFont.
+    ImFontConfig*               ConfigData;         //              // Pointer within ContainerAtlas->ConfigData
+    ImFontAtlas*                ContainerAtlas;     //              // What we has been loaded into
+    float                       Ascent, Descent;    //              // Ascent: distance from top to bottom of e.g. 'A' [0..FontSize]
+    bool                        DirtyLookupTables;
+    int                         MetricsTotalSurface;//              // Total surface in pixels to get an idea of the font rasterization/texture cost (not exact, we approximate the cost of padding between glyphs)
+
+    // Methods
+    IMGUI_API ImFont();
+    IMGUI_API ~ImFont();
+    IMGUI_API void              ClearOutputData();
+    IMGUI_API void              BuildLookupTable();
+    IMGUI_API const ImFontGlyph*FindGlyph(ImWchar c) const;
+    IMGUI_API const ImFontGlyph*FindGlyphNoFallback(ImWchar c) const;
+    IMGUI_API void              SetFallbackChar(ImWchar c);
+    float                       GetCharAdvance(ImWchar c) const     { return ((int)c < IndexAdvanceX.Size) ? IndexAdvanceX[(int)c] : FallbackAdvanceX; }
+    bool                        IsLoaded() const                    { return ContainerAtlas != NULL; }
+    const char*                 GetDebugName() const                { return ConfigData ? ConfigData->Name : "<unknown>"; }
+
+    // 'max_width' stops rendering after a certain width (could be turned into a 2d size). FLT_MAX to disable.
+    // 'wrap_width' enable automatic word-wrapping across multiple lines to fit into given width. 0.0f to disable.
+    IMGUI_API ImVec2            CalcTextSizeA(float size, float max_width, float wrap_width, const char* text_begin, const char* text_end = NULL, const char** remaining = NULL) const; // utf8
+    IMGUI_API const char*       CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) const;
+    IMGUI_API void              RenderChar(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col, unsigned short c) const;
+    IMGUI_API void              RenderText(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, float wrap_width = 0.0f, bool cpu_fine_clip = false) const;
+
+    // [Internal]
+    IMGUI_API void              GrowIndex(int new_size);
+    IMGUI_API void              AddGlyph(ImWchar c, float x0, float y0, float x1, float y1, float u0, float v0, float u1, float v1, float advance_x);
+    IMGUI_API void              AddRemapChar(ImWchar dst, ImWchar src, bool overwrite_dst = true); // Makes 'dst' character/glyph points to 'src' character/glyph. Currently needs to be called AFTER fonts have been built.
+
+#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
+    typedef ImFontGlyph Glyph; // OBSOLETE 1.52+
+#endif
+};
+
+#if defined(__clang__)
+#pragma clang diagnostic pop
+#elif defined(__GNUC__) && __GNUC__ >= 8
+#pragma GCC diagnostic pop
+#endif
+
+// Include imgui_user.h at the end of imgui.h (convenient for user to only explicitly include vanilla imgui.h)
+#ifdef IMGUI_INCLUDE_IMGUI_USER_H
+#include "imgui_user.h"
+#endif
diff --git a/external/Vulkan/external/imgui/imgui_demo.cpp b/external/Vulkan/external/imgui/imgui_demo.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..de891bec1901ea4cf0c4e3f7ccc94b89a7dda188
--- /dev/null
+++ b/external/Vulkan/external/imgui/imgui_demo.cpp
@@ -0,0 +1,3589 @@
+// dear imgui, v1.65
+// (demo code)
+
+// Message to the person tempted to delete this file when integrating ImGui into their code base:
+// Don't do it! Do NOT remove this file from your project! It is useful reference code that you and other users will want to refer to.
+// Everything in this file will be stripped out by the linker if you don't call ImGui::ShowDemoWindow().
+// During development, you can call ImGui::ShowDemoWindow() in your code to learn about various features of ImGui. Have it wired in a debug menu!
+// Removing this file from your project is hindering access to documentation for everyone in your team, likely leading you to poorer usage of the library.
+// Note that you can #define IMGUI_DISABLE_DEMO_WINDOWS in imconfig.h for the same effect.
+// If you want to link core ImGui in your final builds but not those demo windows, #define IMGUI_DISABLE_DEMO_WINDOWS in imconfig.h and those functions will be empty.
+// In other situation, when you have ImGui available you probably want this to be available for reference and execution.
+// Thank you,
+// -Your beloved friend, imgui_demo.cpp (that you won't delete)
+
+// Message to beginner C/C++ programmers about the meaning of the 'static' keyword: in this demo code, we frequently we use 'static' variables inside functions.
+// A static variable persist across calls, so it is essentially like a global variable but declared inside the scope of the function.
+// We do this as a way to gather code and data in the same place, just to make the demo code faster to read, faster to write, and use less code.
+// It also happens to be a convenient way of storing simple UI related information as long as your function doesn't need to be reentrant or used in threads.
+// This might be a pattern you occasionally want to use in your code, but most of the real data you would be editing is likely to be stored outside your functions.
+
+/*
+
+Index of this file:
+
+// [SECTION] Forward Declarations, Helpers
+// [SECTION] Demo Window / ShowDemoWindow()
+// [SECTION] Style Editor / ShowStyleEditor()
+// [SECTION] Example App: Main Menu Bar / ShowExampleAppMainMenuBar()
+// [SECTION] Example App: Debug Console / ShowExampleAppConsole()
+// [SECTION] Example App: Debug Log / ShowExampleAppLog()
+// [SECTION] Example App: Simple Layout / ShowExampleAppLayout()
+// [SECTION] Example App: Property Editor / ShowExampleAppPropertyEditor()
+// [SECTION] Example App: Long Text / ShowExampleAppLongText()
+// [SECTION] Example App: Auto Resize / ShowExampleAppAutoResize()
+// [SECTION] Example App: Constrained Resize / ShowExampleAppConstrainedResize()
+// [SECTION] Example App: Simple Overlay / ShowExampleAppSimpleOverlay()
+// [SECTION] Example App: Manipulating Window Titles / ShowExampleAppWindowTitles()
+// [SECTION] Example App: Custom Rendering using ImDrawList API / ShowExampleAppCustomRendering()
+
+*/
+
+#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
+#define _CRT_SECURE_NO_WARNINGS
+#endif
+
+#include "imgui.h"
+#include <ctype.h>          // toupper, isprint
+#include <limits.h>         // INT_MIN, INT_MAX
+#include <math.h>           // sqrtf, powf, cosf, sinf, floorf, ceilf
+#include <stdio.h>          // vsnprintf, sscanf, printf
+#include <stdlib.h>         // NULL, malloc, free, atoi
+#if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier
+#include <stddef.h>         // intptr_t
+#else
+#include <stdint.h>         // intptr_t
+#endif
+
+#ifdef _MSC_VER
+#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen
+#define vsnprintf _vsnprintf
+#endif
+#ifdef __clang__
+#pragma clang diagnostic ignored "-Wold-style-cast"             // warning : use of old-style cast                              // yes, they are more terse.
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"    // warning : 'xx' is deprecated: The POSIX name for this item.. // for strdup used in demo code (so user can copy & paste the code)
+#pragma clang diagnostic ignored "-Wint-to-void-pointer-cast"   // warning : cast to 'void *' from smaller integer type 'int'
+#pragma clang diagnostic ignored "-Wformat-security"            // warning : warning: format string is not a string literal
+#pragma clang diagnostic ignored "-Wexit-time-destructors"      // warning : declaration requires an exit-time destructor       // exit-time destruction order is undefined. if MemFree() leads to users code that has been disabled before exit it might cause problems. ImGui coding style welcomes static/globals.
+#if __has_warning("-Wreserved-id-macro")
+#pragma clang diagnostic ignored "-Wreserved-id-macro"          // warning : macro name is a reserved identifier                //
+#endif
+#elif defined(__GNUC__)
+#pragma GCC diagnostic ignored "-Wint-to-pointer-cast"          // warning: cast to pointer from integer of different size
+#pragma GCC diagnostic ignored "-Wformat-security"              // warning : format string is not a string literal (potentially insecure)
+#pragma GCC diagnostic ignored "-Wdouble-promotion"             // warning: implicit conversion from 'float' to 'double' when passing argument to function
+#pragma GCC diagnostic ignored "-Wconversion"                   // warning: conversion to 'xxxx' from 'xxxx' may alter its value
+#if (__GNUC__ >= 6)
+#pragma GCC diagnostic ignored "-Wmisleading-indentation"       // warning: this 'if' clause does not guard this statement      // GCC 6.0+ only. See #883 on GitHub.
+#endif
+#endif
+
+// Play it nice with Windows users. Notepad in 2017 still doesn't display text data with Unix-style \n.
+#ifdef _WIN32
+#define IM_NEWLINE "\r\n"
+#else
+#define IM_NEWLINE "\n"
+#endif
+
+#define IM_MAX(_A,_B)       (((_A) >= (_B)) ? (_A) : (_B))
+
+//-----------------------------------------------------------------------------
+// [SECTION] Forward Declarations, Helpers
+//-----------------------------------------------------------------------------
+
+#if !defined(IMGUI_DISABLE_OBSOLETE_FUNCTIONS) && defined(IMGUI_DISABLE_TEST_WINDOWS) && !defined(IMGUI_DISABLE_DEMO_WINDOWS)   // Obsolete name since 1.53, TEST->DEMO
+#define IMGUI_DISABLE_DEMO_WINDOWS
+#endif
+
+#if !defined(IMGUI_DISABLE_DEMO_WINDOWS)
+
+// Forward Declarations
+static void ShowExampleAppMainMenuBar();
+static void ShowExampleAppConsole(bool* p_open);
+static void ShowExampleAppLog(bool* p_open);
+static void ShowExampleAppLayout(bool* p_open);
+static void ShowExampleAppPropertyEditor(bool* p_open);
+static void ShowExampleAppLongText(bool* p_open);
+static void ShowExampleAppAutoResize(bool* p_open);
+static void ShowExampleAppConstrainedResize(bool* p_open);
+static void ShowExampleAppSimpleOverlay(bool* p_open);
+static void ShowExampleAppWindowTitles(bool* p_open);
+static void ShowExampleAppCustomRendering(bool* p_open);
+static void ShowExampleMenuFile();
+
+// Helper to display a little (?) mark which shows a tooltip when hovered.
+static void ShowHelpMarker(const char* desc)
+{
+    ImGui::TextDisabled("(?)");
+    if (ImGui::IsItemHovered())
+    {
+        ImGui::BeginTooltip();
+        ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
+        ImGui::TextUnformatted(desc);
+        ImGui::PopTextWrapPos();
+        ImGui::EndTooltip();
+    }
+}
+
+// Helper to display basic user controls.
+void ImGui::ShowUserGuide()
+{
+    ImGui::BulletText("Double-click on title bar to collapse window.");
+    ImGui::BulletText("Click and drag on lower right corner to resize window\n(double-click to auto fit window to its contents).");
+    ImGui::BulletText("Click and drag on any empty space to move window.");
+    ImGui::BulletText("TAB/SHIFT+TAB to cycle through keyboard editable fields.");
+    ImGui::BulletText("CTRL+Click on a slider or drag box to input value as text.");
+    if (ImGui::GetIO().FontAllowUserScaling)
+        ImGui::BulletText("CTRL+Mouse Wheel to zoom window contents.");
+    ImGui::BulletText("Mouse Wheel to scroll.");
+    ImGui::BulletText("While editing text:\n");
+    ImGui::Indent();
+    ImGui::BulletText("Hold SHIFT or use mouse to select text.");
+    ImGui::BulletText("CTRL+Left/Right to word jump.");
+    ImGui::BulletText("CTRL+A or double-click to select all.");
+    ImGui::BulletText("CTRL+X,CTRL+C,CTRL+V to use clipboard.");
+    ImGui::BulletText("CTRL+Z,CTRL+Y to undo/redo.");
+    ImGui::BulletText("ESCAPE to revert.");
+    ImGui::BulletText("You can apply arithmetic operators +,*,/ on numerical values.\nUse +- to subtract.");
+    ImGui::Unindent();
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] Demo Window / ShowDemoWindow()
+//-----------------------------------------------------------------------------
+
+// Demonstrate most Dear ImGui features (this is big function!)
+// You may execute this function to experiment with the UI and understand what it does. You may then search for keywords in the code when you are interested by a specific feature.
+void ImGui::ShowDemoWindow(bool* p_open)
+{
+    // Examples Apps (accessible from the "Examples" menu)
+    static bool show_app_main_menu_bar = false;
+    static bool show_app_console = false;
+    static bool show_app_log = false;
+    static bool show_app_layout = false;
+    static bool show_app_property_editor = false;
+    static bool show_app_long_text = false;
+    static bool show_app_auto_resize = false;
+    static bool show_app_constrained_resize = false;
+    static bool show_app_simple_overlay = false;
+    static bool show_app_window_titles = false;
+    static bool show_app_custom_rendering = false;
+
+    if (show_app_main_menu_bar)       ShowExampleAppMainMenuBar();
+    if (show_app_console)             ShowExampleAppConsole(&show_app_console);
+    if (show_app_log)                 ShowExampleAppLog(&show_app_log);
+    if (show_app_layout)              ShowExampleAppLayout(&show_app_layout);
+    if (show_app_property_editor)     ShowExampleAppPropertyEditor(&show_app_property_editor);
+    if (show_app_long_text)           ShowExampleAppLongText(&show_app_long_text);
+    if (show_app_auto_resize)         ShowExampleAppAutoResize(&show_app_auto_resize);
+    if (show_app_constrained_resize)  ShowExampleAppConstrainedResize(&show_app_constrained_resize);
+    if (show_app_simple_overlay)      ShowExampleAppSimpleOverlay(&show_app_simple_overlay);
+    if (show_app_window_titles)       ShowExampleAppWindowTitles(&show_app_window_titles);
+    if (show_app_custom_rendering)    ShowExampleAppCustomRendering(&show_app_custom_rendering);
+
+    // Dear ImGui Apps (accessible from the "Help" menu)
+    static bool show_app_metrics = false;
+    static bool show_app_style_editor = false;
+    static bool show_app_about = false;
+
+    if (show_app_metrics)             { ImGui::ShowMetricsWindow(&show_app_metrics); }
+    if (show_app_style_editor)        { ImGui::Begin("Style Editor", &show_app_style_editor); ImGui::ShowStyleEditor(); ImGui::End(); }
+    if (show_app_about)
+    {
+        ImGui::Begin("About Dear ImGui", &show_app_about, ImGuiWindowFlags_AlwaysAutoResize);
+        ImGui::Text("Dear ImGui, %s", ImGui::GetVersion());
+        ImGui::Separator();
+        ImGui::Text("By Omar Cornut and all dear imgui contributors.");
+        ImGui::Text("Dear ImGui is licensed under the MIT License, see LICENSE for more information.");
+        ImGui::End();
+    }
+
+    // Demonstrate the various window flags. Typically you would just use the default!
+    static bool no_titlebar = false;
+    static bool no_scrollbar = false;
+    static bool no_menu = false;
+    static bool no_move = false;
+    static bool no_resize = false;
+    static bool no_collapse = false;
+    static bool no_close = false;
+    static bool no_nav = false;
+
+    ImGuiWindowFlags window_flags = 0;
+    if (no_titlebar)  window_flags |= ImGuiWindowFlags_NoTitleBar;
+    if (no_scrollbar) window_flags |= ImGuiWindowFlags_NoScrollbar;
+    if (!no_menu)     window_flags |= ImGuiWindowFlags_MenuBar;
+    if (no_move)      window_flags |= ImGuiWindowFlags_NoMove;
+    if (no_resize)    window_flags |= ImGuiWindowFlags_NoResize;
+    if (no_collapse)  window_flags |= ImGuiWindowFlags_NoCollapse;
+    if (no_nav)       window_flags |= ImGuiWindowFlags_NoNav;
+    if (no_close)     p_open = NULL; // Don't pass our bool* to Begin
+
+    // We specify a default position/size in case there's no data in the .ini file. Typically this isn't required! We only do it to make the Demo applications a little more welcoming.
+    ImGui::SetNextWindowPos(ImVec2(650, 20), ImGuiCond_FirstUseEver);
+    ImGui::SetNextWindowSize(ImVec2(550, 680), ImGuiCond_FirstUseEver);
+
+    // Main body of the Demo window starts here.
+    if (!ImGui::Begin("ImGui Demo", p_open, window_flags))
+    {
+        // Early out if the window is collapsed, as an optimization.
+        ImGui::End();
+        return;
+    }
+    ImGui::Text("dear imgui says hello. (%s)", IMGUI_VERSION);
+
+    // Most "big" widgets share a common width settings by default.
+    //ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.65f);    // Use 2/3 of the space for widgets and 1/3 for labels (default)
+    ImGui::PushItemWidth(ImGui::GetFontSize() * -12);           // Use fixed width for labels (by passing a negative value), the rest goes to widgets. We choose a width proportional to our font size.
+
+    // Menu
+    if (ImGui::BeginMenuBar())
+    {
+        if (ImGui::BeginMenu("Menu"))
+        {
+            ShowExampleMenuFile();
+            ImGui::EndMenu();
+        }
+        if (ImGui::BeginMenu("Examples"))
+        {
+            ImGui::MenuItem("Main menu bar", NULL, &show_app_main_menu_bar);
+            ImGui::MenuItem("Console", NULL, &show_app_console);
+            ImGui::MenuItem("Log", NULL, &show_app_log);
+            ImGui::MenuItem("Simple layout", NULL, &show_app_layout);
+            ImGui::MenuItem("Property editor", NULL, &show_app_property_editor);
+            ImGui::MenuItem("Long text display", NULL, &show_app_long_text);
+            ImGui::MenuItem("Auto-resizing window", NULL, &show_app_auto_resize);
+            ImGui::MenuItem("Constrained-resizing window", NULL, &show_app_constrained_resize);
+            ImGui::MenuItem("Simple overlay", NULL, &show_app_simple_overlay);
+            ImGui::MenuItem("Manipulating window titles", NULL, &show_app_window_titles);
+            ImGui::MenuItem("Custom rendering", NULL, &show_app_custom_rendering);
+            ImGui::EndMenu();
+        }
+        if (ImGui::BeginMenu("Help"))
+        {
+            ImGui::MenuItem("Metrics", NULL, &show_app_metrics);
+            ImGui::MenuItem("Style Editor", NULL, &show_app_style_editor);
+            ImGui::MenuItem("About Dear ImGui", NULL, &show_app_about);
+            ImGui::EndMenu();
+        }
+        ImGui::EndMenuBar();
+    }
+
+    ImGui::Spacing();
+    if (ImGui::CollapsingHeader("Help"))
+    {
+        ImGui::Text("PROGRAMMER GUIDE:");
+        ImGui::BulletText("Please see the ShowDemoWindow() code in imgui_demo.cpp. <- you are here!");
+        ImGui::BulletText("Please see the comments in imgui.cpp.");
+        ImGui::BulletText("Please see the examples/ in application.");
+        ImGui::BulletText("Enable 'io.ConfigFlags |= NavEnableKeyboard' for keyboard controls.");
+        ImGui::BulletText("Enable 'io.ConfigFlags |= NavEnableGamepad' for gamepad controls.");
+        ImGui::Separator();
+
+        ImGui::Text("USER GUIDE:");
+        ImGui::ShowUserGuide();
+    }
+
+    if (ImGui::CollapsingHeader("Configuration"))
+    {
+        ImGuiIO& io = ImGui::GetIO();
+
+        if (ImGui::TreeNode("Configuration##2"))
+        {
+            ImGui::CheckboxFlags("io.ConfigFlags: NavEnableKeyboard", (unsigned int *)&io.ConfigFlags, ImGuiConfigFlags_NavEnableKeyboard);
+            ImGui::CheckboxFlags("io.ConfigFlags: NavEnableGamepad", (unsigned int *)&io.ConfigFlags, ImGuiConfigFlags_NavEnableGamepad);
+            ImGui::SameLine(); ShowHelpMarker("Required back-end to feed in gamepad inputs in io.NavInputs[] and set io.BackendFlags |= ImGuiBackendFlags_HasGamepad.\n\nRead instructions in imgui.cpp for details.");
+            ImGui::CheckboxFlags("io.ConfigFlags: NavEnableSetMousePos", (unsigned int *)&io.ConfigFlags, ImGuiConfigFlags_NavEnableSetMousePos);
+            ImGui::SameLine(); ShowHelpMarker("Instruct navigation to move the mouse cursor. See comment for ImGuiConfigFlags_NavEnableSetMousePos.");
+            ImGui::CheckboxFlags("io.ConfigFlags: NoMouse", (unsigned int *)&io.ConfigFlags, ImGuiConfigFlags_NoMouse);
+            if (io.ConfigFlags & ImGuiConfigFlags_NoMouse) // Create a way to restore this flag otherwise we could be stuck completely!
+            {
+                if (fmodf((float)ImGui::GetTime(), 0.40f) < 0.20f)
+                {
+                    ImGui::SameLine();
+                    ImGui::Text("<<PRESS SPACE TO DISABLE>>");
+                }
+                if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Space)))
+                    io.ConfigFlags &= ~ImGuiConfigFlags_NoMouse;
+            }
+            ImGui::CheckboxFlags("io.ConfigFlags: NoMouseCursorChange", (unsigned int *)&io.ConfigFlags, ImGuiConfigFlags_NoMouseCursorChange);
+            ImGui::SameLine(); ShowHelpMarker("Instruct back-end to not alter mouse cursor shape and visibility.");
+            ImGui::Checkbox("io.ConfigInputTextCursorBlink", &io.ConfigInputTextCursorBlink);
+            ImGui::SameLine(); ShowHelpMarker("Set to false to disable blinking cursor, for users who consider it distracting");
+            ImGui::Checkbox("io.ConfigResizeWindowsFromEdges [beta]", &io.ConfigResizeWindowsFromEdges);
+            ImGui::SameLine(); ShowHelpMarker("Enable resizing of windows from their edges and from the lower-left corner.\nThis requires (io.BackendFlags & ImGuiBackendFlags_HasMouseCursors) because it needs mouse cursor feedback.");
+            ImGui::Checkbox("io.MouseDrawCursor", &io.MouseDrawCursor);
+            ImGui::SameLine(); ShowHelpMarker("Instruct Dear ImGui to render a mouse cursor for you. Note that a mouse cursor rendered via your application GPU rendering path will feel more laggy than hardware cursor, but will be more in sync with your other visuals.\n\nSome desktop applications may use both kinds of cursors (e.g. enable software cursor only when resizing/dragging something).");
+            ImGui::TreePop();
+            ImGui::Separator();
+        }
+
+        if (ImGui::TreeNode("Backend Flags"))
+        {
+            ImGuiBackendFlags backend_flags = io.BackendFlags; // Make a local copy to avoid modifying the back-end flags.
+            ImGui::CheckboxFlags("io.BackendFlags: HasGamepad", (unsigned int *)&backend_flags, ImGuiBackendFlags_HasGamepad);
+            ImGui::CheckboxFlags("io.BackendFlags: HasMouseCursors", (unsigned int *)&backend_flags, ImGuiBackendFlags_HasMouseCursors);
+            ImGui::CheckboxFlags("io.BackendFlags: HasSetMousePos", (unsigned int *)&backend_flags, ImGuiBackendFlags_HasSetMousePos);
+            ImGui::TreePop();
+            ImGui::Separator();
+        }
+
+        if (ImGui::TreeNode("Style"))
+        {
+            ImGui::ShowStyleEditor();
+            ImGui::TreePop();
+            ImGui::Separator();
+        }
+
+        if (ImGui::TreeNode("Capture/Logging"))
+        {
+            ImGui::TextWrapped("The logging API redirects all text output so you can easily capture the content of a window or a block. Tree nodes can be automatically expanded.");
+            ShowHelpMarker("Try opening any of the contents below in this window and then click one of the \"Log To\" button.");
+            ImGui::LogButtons();
+            ImGui::TextWrapped("You can also call ImGui::LogText() to output directly to the log without a visual output.");
+            if (ImGui::Button("Copy \"Hello, world!\" to clipboard"))
+            {
+                ImGui::LogToClipboard();
+                ImGui::LogText("Hello, world!");
+                ImGui::LogFinish();
+            }
+            ImGui::TreePop();
+        }
+    }
+
+    if (ImGui::CollapsingHeader("Window options"))
+    {
+        ImGui::Checkbox("No titlebar", &no_titlebar); ImGui::SameLine(150);
+        ImGui::Checkbox("No scrollbar", &no_scrollbar); ImGui::SameLine(300);
+        ImGui::Checkbox("No menu", &no_menu);
+        ImGui::Checkbox("No move", &no_move); ImGui::SameLine(150);
+        ImGui::Checkbox("No resize", &no_resize); ImGui::SameLine(300);
+        ImGui::Checkbox("No collapse", &no_collapse);
+        ImGui::Checkbox("No close", &no_close); ImGui::SameLine(150);
+        ImGui::Checkbox("No nav", &no_nav);
+    }
+
+    if (ImGui::CollapsingHeader("Widgets"))
+    {
+        if (ImGui::TreeNode("Basic"))
+        {
+            static int clicked = 0;
+            if (ImGui::Button("Button"))
+                clicked++;
+            if (clicked & 1)
+            {
+                ImGui::SameLine();
+                ImGui::Text("Thanks for clicking me!");
+            }
+
+            static bool check = true;
+            ImGui::Checkbox("checkbox", &check);
+
+            static int e = 0;
+            ImGui::RadioButton("radio a", &e, 0); ImGui::SameLine();
+            ImGui::RadioButton("radio b", &e, 1); ImGui::SameLine();
+            ImGui::RadioButton("radio c", &e, 2);
+
+            // Color buttons, demonstrate using PushID() to add unique identifier in the ID stack, and changing style.
+            for (int i = 0; i < 7; i++)
+            {
+                if (i > 0) ImGui::SameLine();
+                ImGui::PushID(i);
+                ImGui::PushStyleColor(ImGuiCol_Button, (ImVec4)ImColor::HSV(i/7.0f, 0.6f, 0.6f));
+                ImGui::PushStyleColor(ImGuiCol_ButtonHovered, (ImVec4)ImColor::HSV(i/7.0f, 0.7f, 0.7f));
+                ImGui::PushStyleColor(ImGuiCol_ButtonActive, (ImVec4)ImColor::HSV(i/7.0f, 0.8f, 0.8f));
+                ImGui::Button("Click");
+                ImGui::PopStyleColor(3);
+                ImGui::PopID();
+            }
+
+            // Arrow buttons
+            static int counter = 0;
+            float spacing = ImGui::GetStyle().ItemInnerSpacing.x;
+            ImGui::PushButtonRepeat(true);
+            if (ImGui::ArrowButton("##left", ImGuiDir_Left)) { counter--; }
+            ImGui::SameLine(0.0f, spacing);
+            if (ImGui::ArrowButton("##right", ImGuiDir_Right)) { counter++; }
+            ImGui::PopButtonRepeat();
+            ImGui::SameLine();
+            ImGui::Text("%d", counter);
+
+            ImGui::Text("Hover over me");
+            if (ImGui::IsItemHovered())
+                ImGui::SetTooltip("I am a tooltip");
+
+            ImGui::SameLine();
+            ImGui::Text("- or me");
+            if (ImGui::IsItemHovered())
+            {
+                ImGui::BeginTooltip();
+                ImGui::Text("I am a fancy tooltip");
+                static float arr[] = { 0.6f, 0.1f, 1.0f, 0.5f, 0.92f, 0.1f, 0.2f };
+                ImGui::PlotLines("Curve", arr, IM_ARRAYSIZE(arr));
+                ImGui::EndTooltip();
+            }
+
+            ImGui::Separator();
+
+            ImGui::LabelText("label", "Value");
+
+            {
+                // Using the _simplified_ one-liner Combo() api here
+                // See "Combo" section for examples of how to use the more complete BeginCombo()/EndCombo() api.
+                const char* items[] = { "AAAA", "BBBB", "CCCC", "DDDD", "EEEE", "FFFF", "GGGG", "HHHH", "IIII", "JJJJ", "KKKK", "LLLLLLL", "MMMM", "OOOOOOO" };
+                static int item_current = 0;
+                ImGui::Combo("combo", &item_current, items, IM_ARRAYSIZE(items));
+                ImGui::SameLine(); ShowHelpMarker("Refer to the \"Combo\" section below for an explanation of the full BeginCombo/EndCombo API, and demonstration of various flags.\n");
+            }
+
+            {
+                static char str0[128] = "Hello, world!";
+                static int i0 = 123;
+                ImGui::InputText("input text", str0, IM_ARRAYSIZE(str0));
+                ImGui::SameLine(); ShowHelpMarker("USER:\nHold SHIFT or use mouse to select text.\n" "CTRL+Left/Right to word jump.\n" "CTRL+A or double-click to select all.\n" "CTRL+X,CTRL+C,CTRL+V clipboard.\n" "CTRL+Z,CTRL+Y undo/redo.\n" "ESCAPE to revert.\n\nPROGRAMMER:\nYou can use the ImGuiInputTextFlags_CallbackResize facility if you need to wire InputText() to a dynamic string type. See misc/stl/imgui_stl.h for an example (this is not demonstrated in imgui_demo.cpp).");
+
+                ImGui::InputInt("input int", &i0);
+                ImGui::SameLine(); ShowHelpMarker("You can apply arithmetic operators +,*,/ on numerical values.\n  e.g. [ 100 ], input \'*2\', result becomes [ 200 ]\nUse +- to subtract.\n");
+
+                static float f0 = 0.001f;
+                ImGui::InputFloat("input float", &f0, 0.01f, 1.0f);
+
+                static double d0 = 999999.00000001;
+                ImGui::InputDouble("input double", &d0, 0.01f, 1.0f, "%.8f");
+
+                static float f1 = 1.e10f;
+                ImGui::InputFloat("input scientific", &f1, 0.0f, 0.0f, "%e");
+                ImGui::SameLine(); ShowHelpMarker("You can input value using the scientific notation,\n  e.g. \"1e+8\" becomes \"100000000\".\n");
+
+                static float vec4a[4] = { 0.10f, 0.20f, 0.30f, 0.44f };
+                ImGui::InputFloat3("input float3", vec4a);
+            }
+
+            {
+                static int i1 = 50, i2 = 42;
+                ImGui::DragInt("drag int", &i1, 1);
+                ImGui::SameLine(); ShowHelpMarker("Click and drag to edit value.\nHold SHIFT/ALT for faster/slower edit.\nDouble-click or CTRL+click to input value.");
+
+                ImGui::DragInt("drag int 0..100", &i2, 1, 0, 100, "%d%%");
+
+                static float f1=1.00f, f2=0.0067f;
+                ImGui::DragFloat("drag float", &f1, 0.005f);
+                ImGui::DragFloat("drag small float", &f2, 0.0001f, 0.0f, 0.0f, "%.06f ns");
+            }
+
+            {
+                static int i1=0;
+                ImGui::SliderInt("slider int", &i1, -1, 3);
+                ImGui::SameLine(); ShowHelpMarker("CTRL+click to input value.");
+
+                static float f1=0.123f, f2=0.0f;
+                ImGui::SliderFloat("slider float", &f1, 0.0f, 1.0f, "ratio = %.3f");
+                ImGui::SliderFloat("slider float (curve)", &f2, -10.0f, 10.0f, "%.4f", 2.0f);
+                static float angle = 0.0f;
+                ImGui::SliderAngle("slider angle", &angle);
+            }
+
+            {
+                static float col1[3] = { 1.0f,0.0f,0.2f };
+                static float col2[4] = { 0.4f,0.7f,0.0f,0.5f };
+                ImGui::ColorEdit3("color 1", col1);
+                ImGui::SameLine(); ShowHelpMarker("Click on the colored square to open a color picker.\nClick and hold to use drag and drop.\nRight-click on the colored square to show options.\nCTRL+click on individual component to input value.\n");
+
+                ImGui::ColorEdit4("color 2", col2);
+            }
+
+            {
+                // List box
+                const char* listbox_items[] = { "Apple", "Banana", "Cherry", "Kiwi", "Mango", "Orange", "Pineapple", "Strawberry", "Watermelon" };
+                static int listbox_item_current = 1;
+                ImGui::ListBox("listbox\n(single select)", &listbox_item_current, listbox_items, IM_ARRAYSIZE(listbox_items), 4);
+
+                //static int listbox_item_current2 = 2;
+                //ImGui::PushItemWidth(-1);
+                //ImGui::ListBox("##listbox2", &listbox_item_current2, listbox_items, IM_ARRAYSIZE(listbox_items), 4);
+                //ImGui::PopItemWidth();
+            }
+
+            ImGui::TreePop();
+        }
+
+        // Testing ImGuiOnceUponAFrame helper.
+        //static ImGuiOnceUponAFrame once;
+        //for (int i = 0; i < 5; i++)
+        //    if (once)
+        //        ImGui::Text("This will be displayed only once.");
+
+        if (ImGui::TreeNode("Trees"))
+        {
+            if (ImGui::TreeNode("Basic trees"))
+            {
+                for (int i = 0; i < 5; i++)
+                    if (ImGui::TreeNode((void*)(intptr_t)i, "Child %d", i))
+                    {
+                        ImGui::Text("blah blah");
+                        ImGui::SameLine();
+                        if (ImGui::SmallButton("button")) { };
+                        ImGui::TreePop();
+                    }
+                ImGui::TreePop();
+            }
+
+            if (ImGui::TreeNode("Advanced, with Selectable nodes"))
+            {
+                ShowHelpMarker("This is a more standard looking tree with selectable nodes.\nClick to select, CTRL+Click to toggle, click on arrows or double-click to open.");
+                static bool align_label_with_current_x_position = false;
+                ImGui::Checkbox("Align label with current X position)", &align_label_with_current_x_position);
+                ImGui::Text("Hello!");
+                if (align_label_with_current_x_position)
+                    ImGui::Unindent(ImGui::GetTreeNodeToLabelSpacing());
+
+                static int selection_mask = (1 << 2); // Dumb representation of what may be user-side selection state. You may carry selection state inside or outside your objects in whatever format you see fit.
+                int node_clicked = -1;                // Temporary storage of what node we have clicked to process selection at the end of the loop. May be a pointer to your own node type, etc.
+                ImGui::PushStyleVar(ImGuiStyleVar_IndentSpacing, ImGui::GetFontSize()*3); // Increase spacing to differentiate leaves from expanded contents.
+                for (int i = 0; i < 6; i++)
+                {
+                    // Disable the default open on single-click behavior and pass in Selected flag according to our selection state.
+                    ImGuiTreeNodeFlags node_flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick | ((selection_mask & (1 << i)) ? ImGuiTreeNodeFlags_Selected : 0);
+                    if (i < 3)
+                    {
+                        // Node
+                        bool node_open = ImGui::TreeNodeEx((void*)(intptr_t)i, node_flags, "Selectable Node %d", i);
+                        if (ImGui::IsItemClicked())
+                            node_clicked = i;
+                        if (node_open)
+                        {
+                            ImGui::Text("Blah blah\nBlah Blah");
+                            ImGui::TreePop();
+                        }
+                    }
+                    else
+                    {
+                        // Leaf: The only reason we have a TreeNode at all is to allow selection of the leaf. Otherwise we can use BulletText() or TreeAdvanceToLabelPos()+Text().
+                        node_flags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen; // ImGuiTreeNodeFlags_Bullet
+                        ImGui::TreeNodeEx((void*)(intptr_t)i, node_flags, "Selectable Leaf %d", i);
+                        if (ImGui::IsItemClicked())
+                            node_clicked = i;
+                    }
+                }
+                if (node_clicked != -1)
+                {
+                    // Update selection state. Process outside of tree loop to avoid visual inconsistencies during the clicking-frame.
+                    if (ImGui::GetIO().KeyCtrl)
+                        selection_mask ^= (1 << node_clicked);          // CTRL+click to toggle
+                    else //if (!(selection_mask & (1 << node_clicked))) // Depending on selection behavior you want, this commented bit preserve selection when clicking on item that is part of the selection
+                        selection_mask = (1 << node_clicked);           // Click to single-select
+                }
+                ImGui::PopStyleVar();
+                if (align_label_with_current_x_position)
+                    ImGui::Indent(ImGui::GetTreeNodeToLabelSpacing());
+                ImGui::TreePop();
+            }
+            ImGui::TreePop();
+        }
+
+        if (ImGui::TreeNode("Collapsing Headers"))
+        {
+            static bool closable_group = true;
+            ImGui::Checkbox("Enable extra group", &closable_group);
+            if (ImGui::CollapsingHeader("Header"))
+            {
+                ImGui::Text("IsItemHovered: %d", IsItemHovered());
+                for (int i = 0; i < 5; i++)
+                    ImGui::Text("Some content %d", i);
+            }
+            if (ImGui::CollapsingHeader("Header with a close button", &closable_group))
+            {
+                ImGui::Text("IsItemHovered: %d", IsItemHovered());
+                for (int i = 0; i < 5; i++)
+                    ImGui::Text("More content %d", i);
+            }
+            ImGui::TreePop();
+        }
+
+        if (ImGui::TreeNode("Bullets"))
+        {
+            ImGui::BulletText("Bullet point 1");
+            ImGui::BulletText("Bullet point 2\nOn multiple lines");
+            ImGui::Bullet(); ImGui::Text("Bullet point 3 (two calls)");
+            ImGui::Bullet(); ImGui::SmallButton("Button");
+            ImGui::TreePop();
+        }
+
+        if (ImGui::TreeNode("Text"))
+        {
+            if (ImGui::TreeNode("Colored Text"))
+            {
+                // Using shortcut. You can use PushStyleColor()/PopStyleColor() for more flexibility.
+                ImGui::TextColored(ImVec4(1.0f,0.0f,1.0f,1.0f), "Pink");
+                ImGui::TextColored(ImVec4(1.0f,1.0f,0.0f,1.0f), "Yellow");
+                ImGui::TextDisabled("Disabled");
+                ImGui::SameLine(); ShowHelpMarker("The TextDisabled color is stored in ImGuiStyle.");
+                ImGui::TreePop();
+            }
+
+            if (ImGui::TreeNode("Word Wrapping"))
+            {
+                // Using shortcut. You can use PushTextWrapPos()/PopTextWrapPos() for more flexibility.
+                ImGui::TextWrapped("This text should automatically wrap on the edge of the window. The current implementation for text wrapping follows simple rules suitable for English and possibly other languages.");
+                ImGui::Spacing();
+
+                static float wrap_width = 200.0f;
+                ImGui::SliderFloat("Wrap width", &wrap_width, -20, 600, "%.0f");
+
+                ImGui::Text("Test paragraph 1:");
+                ImVec2 pos = ImGui::GetCursorScreenPos();
+                ImGui::GetWindowDrawList()->AddRectFilled(ImVec2(pos.x + wrap_width, pos.y), ImVec2(pos.x + wrap_width + 10, pos.y + ImGui::GetTextLineHeight()), IM_COL32(255,0,255,255));
+                ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + wrap_width);
+                ImGui::Text("The lazy dog is a good dog. This paragraph is made to fit within %.0f pixels. Testing a 1 character word. The quick brown fox jumps over the lazy dog.", wrap_width);
+                ImGui::GetWindowDrawList()->AddRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), IM_COL32(255,255,0,255));
+                ImGui::PopTextWrapPos();
+
+                ImGui::Text("Test paragraph 2:");
+                pos = ImGui::GetCursorScreenPos();
+                ImGui::GetWindowDrawList()->AddRectFilled(ImVec2(pos.x + wrap_width, pos.y), ImVec2(pos.x + wrap_width + 10, pos.y + ImGui::GetTextLineHeight()), IM_COL32(255,0,255,255));
+                ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + wrap_width);
+                ImGui::Text("aaaaaaaa bbbbbbbb, c cccccccc,dddddddd. d eeeeeeee   ffffffff. gggggggg!hhhhhhhh");
+                ImGui::GetWindowDrawList()->AddRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), IM_COL32(255,255,0,255));
+                ImGui::PopTextWrapPos();
+
+                ImGui::TreePop();
+            }
+
+            if (ImGui::TreeNode("UTF-8 Text"))
+            {
+                // UTF-8 test with Japanese characters
+                // (Needs a suitable font, try Noto, or Arial Unicode, or M+ fonts. Read misc/fonts/README.txt for details.)
+                // - From C++11 you can use the u8"my text" syntax to encode literal strings as UTF-8
+                // - For earlier compiler, you may be able to encode your sources as UTF-8 (e.g. Visual Studio save your file as 'UTF-8 without signature')
+                // - FOR THIS DEMO FILE ONLY, BECAUSE WE WANT TO SUPPORT OLD COMPILERS, WE ARE *NOT* INCLUDING RAW UTF-8 CHARACTERS IN THIS SOURCE FILE.
+                //   Instead we are encoding a few strings with hexadecimal constants. Don't do this in your application!
+                //   Please use u8"text in any language" in your application!
+                // Note that characters values are preserved even by InputText() if the font cannot be displayed, so you can safely copy & paste garbled characters into another application.
+                ImGui::TextWrapped("CJK text will only appears if the font was loaded with the appropriate CJK character ranges. Call io.Font->LoadFromFileTTF() manually to load extra character ranges. Read misc/fonts/README.txt for details.");
+                ImGui::Text("Hiragana: \xe3\x81\x8b\xe3\x81\x8d\xe3\x81\x8f\xe3\x81\x91\xe3\x81\x93 (kakikukeko)"); // Normally we would use u8"blah blah" with the proper characters directly in the string.
+                ImGui::Text("Kanjis: \xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e (nihongo)");
+                static char buf[32] = "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e";
+                //static char buf[32] = u8"NIHONGO"; // <- this is how you would write it with C++11, using real kanjis
+                ImGui::InputText("UTF-8 input", buf, IM_ARRAYSIZE(buf));
+                ImGui::TreePop();
+            }
+            ImGui::TreePop();
+        }
+
+        if (ImGui::TreeNode("Images"))
+        {
+            ImGuiIO& io = ImGui::GetIO();
+            ImGui::TextWrapped("Below we are displaying the font texture (which is the only texture we have access to in this demo). Use the 'ImTextureID' type as storage to pass pointers or identifier to your own texture data. Hover the texture for a zoomed view!");
+
+            // Here we are grabbing the font texture because that's the only one we have access to inside the demo code.
+            // Remember that ImTextureID is just storage for whatever you want it to be, it is essentially a value that will be passed to the render function inside the ImDrawCmd structure.
+            // If you use one of the default imgui_impl_XXXX.cpp renderer, they all have comments at the top of their file to specify what they expect to be stored in ImTextureID.
+            // (for example, the imgui_impl_dx11.cpp renderer expect a 'ID3D11ShaderResourceView*' pointer. The imgui_impl_glfw_gl3.cpp renderer expect a GLuint OpenGL texture identifier etc.)
+            // If you decided that ImTextureID = MyEngineTexture*, then you can pass your MyEngineTexture* pointers to ImGui::Image(), and gather width/height through your own functions, etc.
+            // Using ShowMetricsWindow() as a "debugger" to inspect the draw data that are being passed to your render will help you debug issues if you are confused about this.
+            // Consider using the lower-level ImDrawList::AddImage() API, via ImGui::GetWindowDrawList()->AddImage().
+            ImTextureID my_tex_id = io.Fonts->TexID;
+            float my_tex_w = (float)io.Fonts->TexWidth;
+            float my_tex_h = (float)io.Fonts->TexHeight;
+
+            ImGui::Text("%.0fx%.0f", my_tex_w, my_tex_h);
+            ImVec2 pos = ImGui::GetCursorScreenPos();
+            ImGui::Image(my_tex_id, ImVec2(my_tex_w, my_tex_h), ImVec2(0,0), ImVec2(1,1), ImColor(255,255,255,255), ImColor(255,255,255,128));
+            if (ImGui::IsItemHovered())
+            {
+                ImGui::BeginTooltip();
+                float region_sz = 32.0f;
+                float region_x = io.MousePos.x - pos.x - region_sz * 0.5f; if (region_x < 0.0f) region_x = 0.0f; else if (region_x > my_tex_w - region_sz) region_x = my_tex_w - region_sz;
+                float region_y = io.MousePos.y - pos.y - region_sz * 0.5f; if (region_y < 0.0f) region_y = 0.0f; else if (region_y > my_tex_h - region_sz) region_y = my_tex_h - region_sz;
+                float zoom = 4.0f;
+                ImGui::Text("Min: (%.2f, %.2f)", region_x, region_y);
+                ImGui::Text("Max: (%.2f, %.2f)", region_x + region_sz, region_y + region_sz);
+                ImVec2 uv0 = ImVec2((region_x) / my_tex_w, (region_y) / my_tex_h);
+                ImVec2 uv1 = ImVec2((region_x + region_sz) / my_tex_w, (region_y + region_sz) / my_tex_h);
+                ImGui::Image(my_tex_id, ImVec2(region_sz * zoom, region_sz * zoom), uv0, uv1, ImColor(255,255,255,255), ImColor(255,255,255,128));
+                ImGui::EndTooltip();
+            }
+            ImGui::TextWrapped("And now some textured buttons..");
+            static int pressed_count = 0;
+            for (int i = 0; i < 8; i++)
+            {
+                ImGui::PushID(i);
+                int frame_padding = -1 + i;     // -1 = uses default padding
+                if (ImGui::ImageButton(my_tex_id, ImVec2(32,32), ImVec2(0,0), ImVec2(32.0f/my_tex_w,32/my_tex_h), frame_padding, ImColor(0,0,0,255)))
+                    pressed_count += 1;
+                ImGui::PopID();
+                ImGui::SameLine();
+            }
+            ImGui::NewLine();
+            ImGui::Text("Pressed %d times.", pressed_count);
+            ImGui::TreePop();
+        }
+
+        if (ImGui::TreeNode("Combo"))
+        {
+            // Expose flags as checkbox for the demo
+            static ImGuiComboFlags flags = 0;
+            ImGui::CheckboxFlags("ImGuiComboFlags_PopupAlignLeft", (unsigned int*)&flags, ImGuiComboFlags_PopupAlignLeft);
+            if (ImGui::CheckboxFlags("ImGuiComboFlags_NoArrowButton", (unsigned int*)&flags, ImGuiComboFlags_NoArrowButton))
+                flags &= ~ImGuiComboFlags_NoPreview;     // Clear the other flag, as we cannot combine both
+            if (ImGui::CheckboxFlags("ImGuiComboFlags_NoPreview", (unsigned int*)&flags, ImGuiComboFlags_NoPreview))
+                flags &= ~ImGuiComboFlags_NoArrowButton; // Clear the other flag, as we cannot combine both
+
+            // General BeginCombo() API, you have full control over your selection data and display type.
+            // (your selection data could be an index, a pointer to the object, an id for the object, a flag stored in the object itself, etc.)
+            const char* items[] = { "AAAA", "BBBB", "CCCC", "DDDD", "EEEE", "FFFF", "GGGG", "HHHH", "IIII", "JJJJ", "KKKK", "LLLLLLL", "MMMM", "OOOOOOO" };
+            static const char* item_current = items[0];            // Here our selection is a single pointer stored outside the object.
+            if (ImGui::BeginCombo("combo 1", item_current, flags)) // The second parameter is the label previewed before opening the combo.
+            {
+                for (int n = 0; n < IM_ARRAYSIZE(items); n++)
+                {
+                    bool is_selected = (item_current == items[n]);
+                    if (ImGui::Selectable(items[n], is_selected))
+                        item_current = items[n];
+                    if (is_selected)
+                        ImGui::SetItemDefaultFocus();   // Set the initial focus when opening the combo (scrolling + for keyboard navigation support in the upcoming navigation branch)
+                }
+                ImGui::EndCombo();
+            }
+
+            // Simplified one-liner Combo() API, using values packed in a single constant string
+            static int item_current_2 = 0;
+            ImGui::Combo("combo 2 (one-liner)", &item_current_2, "aaaa\0bbbb\0cccc\0dddd\0eeee\0\0");
+
+            // Simplified one-liner Combo() using an array of const char*
+            static int item_current_3 = -1; // If the selection isn't within 0..count, Combo won't display a preview
+            ImGui::Combo("combo 3 (array)", &item_current_3, items, IM_ARRAYSIZE(items));
+
+            // Simplified one-liner Combo() using an accessor function
+            struct FuncHolder { static bool ItemGetter(void* data, int idx, const char** out_str) { *out_str = ((const char**)data)[idx]; return true; } };
+            static int item_current_4 = 0;
+            ImGui::Combo("combo 4 (function)", &item_current_4, &FuncHolder::ItemGetter, items, IM_ARRAYSIZE(items));
+
+            ImGui::TreePop();
+        }
+
+        if (ImGui::TreeNode("Selectables"))
+        {
+            // Selectable() has 2 overloads:
+            // - The one taking "bool selected" as a read-only selection information. When Selectable() has been clicked is returns true and you can alter selection state accordingly.
+            // - The one taking "bool* p_selected" as a read-write selection information (convenient in some cases)
+            // The earlier is more flexible, as in real application your selection may be stored in a different manner (in flags within objects, as an external list, etc).
+            if (ImGui::TreeNode("Basic"))
+            {
+                static bool selection[5] = { false, true, false, false, false };
+                ImGui::Selectable("1. I am selectable", &selection[0]);
+                ImGui::Selectable("2. I am selectable", &selection[1]);
+                ImGui::Text("3. I am not selectable");
+                ImGui::Selectable("4. I am selectable", &selection[3]);
+                if (ImGui::Selectable("5. I am double clickable", selection[4], ImGuiSelectableFlags_AllowDoubleClick))
+                    if (ImGui::IsMouseDoubleClicked(0))
+                        selection[4] = !selection[4];
+                ImGui::TreePop();
+            }
+            if (ImGui::TreeNode("Selection State: Single Selection"))
+            {
+                static int selected = -1;
+                for (int n = 0; n < 5; n++)
+                {
+                    char buf[32];
+                    sprintf(buf, "Object %d", n);
+                    if (ImGui::Selectable(buf, selected == n))
+                        selected = n;
+                }
+                ImGui::TreePop();
+            }
+            if (ImGui::TreeNode("Selection State: Multiple Selection"))
+            {
+                ShowHelpMarker("Hold CTRL and click to select multiple items.");
+                static bool selection[5] = { false, false, false, false, false };
+                for (int n = 0; n < 5; n++)
+                {
+                    char buf[32];
+                    sprintf(buf, "Object %d", n);
+                    if (ImGui::Selectable(buf, selection[n]))
+                    {
+                        if (!ImGui::GetIO().KeyCtrl)    // Clear selection when CTRL is not held
+                            memset(selection, 0, sizeof(selection));
+                        selection[n] ^= 1;
+                    }
+                }
+                ImGui::TreePop();
+            }
+            if (ImGui::TreeNode("Rendering more text into the same line"))
+            {
+                // Using the Selectable() override that takes "bool* p_selected" parameter and toggle your booleans automatically.
+                static bool selected[3] = { false, false, false };
+                ImGui::Selectable("main.c",    &selected[0]); ImGui::SameLine(300); ImGui::Text(" 2,345 bytes");
+                ImGui::Selectable("Hello.cpp", &selected[1]); ImGui::SameLine(300); ImGui::Text("12,345 bytes");
+                ImGui::Selectable("Hello.h",   &selected[2]); ImGui::SameLine(300); ImGui::Text(" 2,345 bytes");
+                ImGui::TreePop();
+            }
+            if (ImGui::TreeNode("In columns"))
+            {
+                ImGui::Columns(3, NULL, false);
+                static bool selected[16] = { 0 };
+                for (int i = 0; i < 16; i++)
+                {
+                    char label[32]; sprintf(label, "Item %d", i);
+                    if (ImGui::Selectable(label, &selected[i])) {}
+                    ImGui::NextColumn();
+                }
+                ImGui::Columns(1);
+                ImGui::TreePop();
+            }
+            if (ImGui::TreeNode("Grid"))
+            {
+                static bool selected[16] = { true, false, false, false, false, true, false, false, false, false, true, false, false, false, false, true };
+                for (int i = 0; i < 16; i++)
+                {
+                    ImGui::PushID(i);
+                    if (ImGui::Selectable("Sailor", &selected[i], 0, ImVec2(50,50)))
+                    {
+                        int x = i % 4, y = i / 4;
+                        if (x > 0) selected[i - 1] ^= 1;
+                        if (x < 3) selected[i + 1] ^= 1;
+                        if (y > 0) selected[i - 4] ^= 1;
+                        if (y < 3) selected[i + 4] ^= 1;
+                    }
+                    if ((i % 4) < 3) ImGui::SameLine();
+                    ImGui::PopID();
+                }
+                ImGui::TreePop();
+            }
+            ImGui::TreePop();
+        }
+
+        if (ImGui::TreeNode("Filtered Text Input"))
+        {
+            static char buf1[64] = ""; ImGui::InputText("default", buf1, 64);
+            static char buf2[64] = ""; ImGui::InputText("decimal", buf2, 64, ImGuiInputTextFlags_CharsDecimal);
+            static char buf3[64] = ""; ImGui::InputText("hexadecimal", buf3, 64, ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase);
+            static char buf4[64] = ""; ImGui::InputText("uppercase", buf4, 64, ImGuiInputTextFlags_CharsUppercase);
+            static char buf5[64] = ""; ImGui::InputText("no blank", buf5, 64, ImGuiInputTextFlags_CharsNoBlank);
+            struct TextFilters { static int FilterImGuiLetters(ImGuiInputTextCallbackData* data) { if (data->EventChar < 256 && strchr("imgui", (char)data->EventChar)) return 0; return 1; } };
+            static char buf6[64] = ""; ImGui::InputText("\"imgui\" letters", buf6, 64, ImGuiInputTextFlags_CallbackCharFilter, TextFilters::FilterImGuiLetters);
+
+            ImGui::Text("Password input");
+            static char bufpass[64] = "password123";
+            ImGui::InputText("password", bufpass, 64, ImGuiInputTextFlags_Password | ImGuiInputTextFlags_CharsNoBlank);
+            ImGui::SameLine(); ShowHelpMarker("Display all characters as '*'.\nDisable clipboard cut and copy.\nDisable logging.\n");
+            ImGui::InputText("password (clear)", bufpass, 64, ImGuiInputTextFlags_CharsNoBlank);
+
+            ImGui::TreePop();
+        }
+
+        if (ImGui::TreeNode("Multi-line Text Input"))
+        {
+            static bool read_only = false;
+            static char text[1024*16] =
+                "/*\n"
+                " The Pentium F00F bug, shorthand for F0 0F C7 C8,\n"
+                " the hexadecimal encoding of one offending instruction,\n"
+                " more formally, the invalid operand with locked CMPXCHG8B\n"
+                " instruction bug, is a design flaw in the majority of\n"
+                " Intel Pentium, Pentium MMX, and Pentium OverDrive\n"
+                " processors (all in the P5 microarchitecture).\n"
+                "*/\n\n"
+                "label:\n"
+                "\tlock cmpxchg8b eax\n";
+
+            ShowHelpMarker("You can use the ImGuiInputTextFlags_CallbackResize facility if you need to wire InputTextMultiline() to a dynamic string type. See misc/stl/imgui_stl.h for an example. (This is not demonstrated in imgui_demo.cpp)");
+            ImGui::Checkbox("Read-only", &read_only);
+            ImGuiInputTextFlags flags = ImGuiInputTextFlags_AllowTabInput | (read_only ? ImGuiInputTextFlags_ReadOnly : 0);
+            ImGui::InputTextMultiline("##source", text, IM_ARRAYSIZE(text), ImVec2(-1.0f, ImGui::GetTextLineHeight() * 16), flags);
+            ImGui::TreePop();
+        }
+
+        if (ImGui::TreeNode("Plots Widgets"))
+        {
+            static bool animate = true;
+            ImGui::Checkbox("Animate", &animate);
+
+            static float arr[] = { 0.6f, 0.1f, 1.0f, 0.5f, 0.92f, 0.1f, 0.2f };
+            ImGui::PlotLines("Frame Times", arr, IM_ARRAYSIZE(arr));
+
+            // Create a dummy array of contiguous float values to plot
+            // Tip: If your float aren't contiguous but part of a structure, you can pass a pointer to your first float and the sizeof() of your structure in the Stride parameter.
+            static float values[90] = { 0 };
+            static int values_offset = 0;
+            static double refresh_time = 0.0;
+            if (!animate || refresh_time == 0.0f)
+                refresh_time = ImGui::GetTime();
+            while (refresh_time < ImGui::GetTime()) // Create dummy data at fixed 60 hz rate for the demo
+            {
+                static float phase = 0.0f;
+                values[values_offset] = cosf(phase);
+                values_offset = (values_offset+1) % IM_ARRAYSIZE(values);
+                phase += 0.10f*values_offset;
+                refresh_time += 1.0f/60.0f;
+            }
+            ImGui::PlotLines("Lines", values, IM_ARRAYSIZE(values), values_offset, "avg 0.0", -1.0f, 1.0f, ImVec2(0,80));
+            ImGui::PlotHistogram("Histogram", arr, IM_ARRAYSIZE(arr), 0, NULL, 0.0f, 1.0f, ImVec2(0,80));
+
+            // Use functions to generate output
+            // FIXME: This is rather awkward because current plot API only pass in indices. We probably want an API passing floats and user provide sample rate/count.
+            struct Funcs
+            {
+                static float Sin(void*, int i) { return sinf(i * 0.1f); }
+                static float Saw(void*, int i) { return (i & 1) ? 1.0f : -1.0f; }
+            };
+            static int func_type = 0, display_count = 70;
+            ImGui::Separator();
+            ImGui::PushItemWidth(100); ImGui::Combo("func", &func_type, "Sin\0Saw\0"); ImGui::PopItemWidth();
+            ImGui::SameLine();
+            ImGui::SliderInt("Sample count", &display_count, 1, 400);
+            float (*func)(void*, int) = (func_type == 0) ? Funcs::Sin : Funcs::Saw;
+            ImGui::PlotLines("Lines", func, NULL, display_count, 0, NULL, -1.0f, 1.0f, ImVec2(0,80));
+            ImGui::PlotHistogram("Histogram", func, NULL, display_count, 0, NULL, -1.0f, 1.0f, ImVec2(0,80));
+            ImGui::Separator();
+
+            // Animate a simple progress bar
+            static float progress = 0.0f, progress_dir = 1.0f;
+            if (animate)
+            {
+                progress += progress_dir * 0.4f * ImGui::GetIO().DeltaTime;
+                if (progress >= +1.1f) { progress = +1.1f; progress_dir *= -1.0f; }
+                if (progress <= -0.1f) { progress = -0.1f; progress_dir *= -1.0f; }
+            }
+
+            // Typically we would use ImVec2(-1.0f,0.0f) to use all available width, or ImVec2(width,0.0f) for a specified width. ImVec2(0.0f,0.0f) uses ItemWidth.
+            ImGui::ProgressBar(progress, ImVec2(0.0f,0.0f));
+            ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
+            ImGui::Text("Progress Bar");
+
+            float progress_saturated = (progress < 0.0f) ? 0.0f : (progress > 1.0f) ? 1.0f : progress;
+            char buf[32];
+            sprintf(buf, "%d/%d", (int)(progress_saturated*1753), 1753);
+            ImGui::ProgressBar(progress, ImVec2(0.f,0.f), buf);
+            ImGui::TreePop();
+        }
+
+        if (ImGui::TreeNode("Color/Picker Widgets"))
+        {
+            static ImVec4 color = ImColor(114, 144, 154, 200);
+
+            static bool alpha_preview = true;
+            static bool alpha_half_preview = false;
+            static bool drag_and_drop = true;
+            static bool options_menu = true;
+            static bool hdr = false;
+            ImGui::Checkbox("With Alpha Preview", &alpha_preview);
+            ImGui::Checkbox("With Half Alpha Preview", &alpha_half_preview);
+            ImGui::Checkbox("With Drag and Drop", &drag_and_drop);
+            ImGui::Checkbox("With Options Menu", &options_menu); ImGui::SameLine(); ShowHelpMarker("Right-click on the individual color widget to show options.");
+            ImGui::Checkbox("With HDR", &hdr); ImGui::SameLine(); ShowHelpMarker("Currently all this does is to lift the 0..1 limits on dragging widgets.");
+            int misc_flags = (hdr ? ImGuiColorEditFlags_HDR : 0) | (drag_and_drop ? 0 : ImGuiColorEditFlags_NoDragDrop) | (alpha_half_preview ? ImGuiColorEditFlags_AlphaPreviewHalf : (alpha_preview ? ImGuiColorEditFlags_AlphaPreview : 0)) | (options_menu ? 0 : ImGuiColorEditFlags_NoOptions);
+
+            ImGui::Text("Color widget:");
+            ImGui::SameLine(); ShowHelpMarker("Click on the colored square to open a color picker.\nCTRL+click on individual component to input value.\n");
+            ImGui::ColorEdit3("MyColor##1", (float*)&color, misc_flags);
+
+            ImGui::Text("Color widget HSV with Alpha:");
+            ImGui::ColorEdit4("MyColor##2", (float*)&color, ImGuiColorEditFlags_HSV | misc_flags);
+
+            ImGui::Text("Color widget with Float Display:");
+            ImGui::ColorEdit4("MyColor##2f", (float*)&color, ImGuiColorEditFlags_Float | misc_flags);
+
+            ImGui::Text("Color button with Picker:");
+            ImGui::SameLine(); ShowHelpMarker("With the ImGuiColorEditFlags_NoInputs flag you can hide all the slider/text inputs.\nWith the ImGuiColorEditFlags_NoLabel flag you can pass a non-empty label which will only be used for the tooltip and picker popup.");
+            ImGui::ColorEdit4("MyColor##3", (float*)&color, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel | misc_flags);
+
+            ImGui::Text("Color button with Custom Picker Popup:");
+
+            // Generate a dummy palette
+            static bool saved_palette_inited = false;
+            static ImVec4 saved_palette[32];
+            if (!saved_palette_inited)
+                for (int n = 0; n < IM_ARRAYSIZE(saved_palette); n++)
+                {
+                    ImGui::ColorConvertHSVtoRGB(n / 31.0f, 0.8f, 0.8f, saved_palette[n].x, saved_palette[n].y, saved_palette[n].z);
+                    saved_palette[n].w = 1.0f; // Alpha
+                }
+            saved_palette_inited = true;
+
+            static ImVec4 backup_color;
+            bool open_popup = ImGui::ColorButton("MyColor##3b", color, misc_flags);
+            ImGui::SameLine();
+            open_popup |= ImGui::Button("Palette");
+            if (open_popup)
+            {
+                ImGui::OpenPopup("mypicker");
+                backup_color = color;
+            }
+            if (ImGui::BeginPopup("mypicker"))
+            {
+                // FIXME: Adding a drag and drop example here would be perfect!
+                ImGui::Text("MY CUSTOM COLOR PICKER WITH AN AMAZING PALETTE!");
+                ImGui::Separator();
+                ImGui::ColorPicker4("##picker", (float*)&color, misc_flags | ImGuiColorEditFlags_NoSidePreview | ImGuiColorEditFlags_NoSmallPreview);
+                ImGui::SameLine();
+                ImGui::BeginGroup();
+                ImGui::Text("Current");
+                ImGui::ColorButton("##current", color, ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf, ImVec2(60,40));
+                ImGui::Text("Previous");
+                if (ImGui::ColorButton("##previous", backup_color, ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf, ImVec2(60,40)))
+                    color = backup_color;
+                ImGui::Separator();
+                ImGui::Text("Palette");
+                for (int n = 0; n < IM_ARRAYSIZE(saved_palette); n++)
+                {
+                    ImGui::PushID(n);
+                    if ((n % 8) != 0)
+                        ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.y);
+                    if (ImGui::ColorButton("##palette", saved_palette[n], ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_NoTooltip, ImVec2(20,20)))
+                        color = ImVec4(saved_palette[n].x, saved_palette[n].y, saved_palette[n].z, color.w); // Preserve alpha!
+
+                    if (ImGui::BeginDragDropTarget())
+                    {
+                        if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))
+                            memcpy((float*)&saved_palette[n], payload->Data, sizeof(float) * 3);
+                        if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))
+                            memcpy((float*)&saved_palette[n], payload->Data, sizeof(float) * 4);
+                        EndDragDropTarget();
+                    }
+
+                    ImGui::PopID();
+                }
+                ImGui::EndGroup();
+                ImGui::EndPopup();
+            }
+
+            ImGui::Text("Color button only:");
+            ImGui::ColorButton("MyColor##3c", *(ImVec4*)&color, misc_flags, ImVec2(80,80));
+
+            ImGui::Text("Color picker:");
+            static bool alpha = true;
+            static bool alpha_bar = true;
+            static bool side_preview = true;
+            static bool ref_color = false;
+            static ImVec4 ref_color_v(1.0f,0.0f,1.0f,0.5f);
+            static int inputs_mode = 2;
+            static int picker_mode = 0;
+            ImGui::Checkbox("With Alpha", &alpha);
+            ImGui::Checkbox("With Alpha Bar", &alpha_bar);
+            ImGui::Checkbox("With Side Preview", &side_preview);
+            if (side_preview)
+            {
+                ImGui::SameLine();
+                ImGui::Checkbox("With Ref Color", &ref_color);
+                if (ref_color)
+                {
+                    ImGui::SameLine();
+                    ImGui::ColorEdit4("##RefColor", &ref_color_v.x, ImGuiColorEditFlags_NoInputs | misc_flags);
+                }
+            }
+            ImGui::Combo("Inputs Mode", &inputs_mode, "All Inputs\0No Inputs\0RGB Input\0HSV Input\0HEX Input\0");
+            ImGui::Combo("Picker Mode", &picker_mode, "Auto/Current\0Hue bar + SV rect\0Hue wheel + SV triangle\0");
+            ImGui::SameLine(); ShowHelpMarker("User can right-click the picker to change mode.");
+            ImGuiColorEditFlags flags = misc_flags;
+            if (!alpha) flags |= ImGuiColorEditFlags_NoAlpha; // This is by default if you call ColorPicker3() instead of ColorPicker4()
+            if (alpha_bar) flags |= ImGuiColorEditFlags_AlphaBar;
+            if (!side_preview) flags |= ImGuiColorEditFlags_NoSidePreview;
+            if (picker_mode == 1) flags |= ImGuiColorEditFlags_PickerHueBar;
+            if (picker_mode == 2) flags |= ImGuiColorEditFlags_PickerHueWheel;
+            if (inputs_mode == 1) flags |= ImGuiColorEditFlags_NoInputs;
+            if (inputs_mode == 2) flags |= ImGuiColorEditFlags_RGB;
+            if (inputs_mode == 3) flags |= ImGuiColorEditFlags_HSV;
+            if (inputs_mode == 4) flags |= ImGuiColorEditFlags_HEX;
+            ImGui::ColorPicker4("MyColor##4", (float*)&color, flags, ref_color ? &ref_color_v.x : NULL);
+
+            ImGui::Text("Programmatically set defaults:");
+            ImGui::SameLine(); ShowHelpMarker("SetColorEditOptions() is designed to allow you to set boot-time default.\nWe don't have Push/Pop functions because you can force options on a per-widget basis if needed, and the user can change non-forced ones with the options menu.\nWe don't have a getter to avoid encouraging you to persistently save values that aren't forward-compatible.");
+            if (ImGui::Button("Default: Uint8 + HSV + Hue Bar"))
+                ImGui::SetColorEditOptions(ImGuiColorEditFlags_Uint8 | ImGuiColorEditFlags_HSV | ImGuiColorEditFlags_PickerHueBar);
+            if (ImGui::Button("Default: Float + HDR + Hue Wheel"))
+                ImGui::SetColorEditOptions(ImGuiColorEditFlags_Float | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_PickerHueWheel);
+
+            ImGui::TreePop();
+        }
+
+        if (ImGui::TreeNode("Range Widgets"))
+        {
+            static float begin = 10, end = 90;
+            static int begin_i = 100, end_i = 1000;
+            ImGui::DragFloatRange2("range", &begin, &end, 0.25f, 0.0f, 100.0f, "Min: %.1f %%", "Max: %.1f %%");
+            ImGui::DragIntRange2("range int (no bounds)", &begin_i, &end_i, 5, 0, 0, "Min: %d units", "Max: %d units");
+            ImGui::TreePop();
+        }
+
+        if (ImGui::TreeNode("Data Types"))
+        {
+            // The DragScalar/InputScalar/SliderScalar functions allow various data types: signed/unsigned int/long long and float/double
+            // To avoid polluting the public API with all possible combinations, we use the ImGuiDataType enum to pass the type, 
+            // and passing all arguments by address. 
+            // This is the reason the test code below creates local variables to hold "zero" "one" etc. for each types.
+            // In practice, if you frequently use a given type that is not covered by the normal API entry points, you can wrap it 
+            // yourself inside a 1 line function which can take typed argument as value instead of void*, and then pass their address 
+            // to the generic function. For example:
+            //   bool MySliderU64(const char *label, u64* value, u64 min = 0, u64 max = 0, const char* format = "%lld") 
+            //   { 
+            //      return SliderScalar(label, ImGuiDataType_U64, value, &min, &max, format); 
+            //   }
+
+            // Limits (as helper variables that we can take the address of)
+            // Note that the SliderScalar function has a maximum usable range of half the natural type maximum, hence the /2 below.
+            #ifndef LLONG_MIN
+            ImS64 LLONG_MIN = -9223372036854775807LL - 1;
+            ImS64 LLONG_MAX = 9223372036854775807LL;
+            ImU64 ULLONG_MAX = (2ULL * 9223372036854775807LL + 1);
+            #endif
+            const ImS32   s32_zero = 0,   s32_one = 1,   s32_fifty = 50, s32_min = INT_MIN/2,   s32_max = INT_MAX/2,    s32_hi_a = INT_MAX/2 - 100,    s32_hi_b = INT_MAX/2;
+            const ImU32   u32_zero = 0,   u32_one = 1,   u32_fifty = 50, u32_min = 0,           u32_max = UINT_MAX/2,   u32_hi_a = UINT_MAX/2 - 100,   u32_hi_b = UINT_MAX/2;
+            const ImS64   s64_zero = 0,   s64_one = 1,   s64_fifty = 50, s64_min = LLONG_MIN/2, s64_max = LLONG_MAX/2,  s64_hi_a = LLONG_MAX/2 - 100,  s64_hi_b = LLONG_MAX/2;
+            const ImU64   u64_zero = 0,   u64_one = 1,   u64_fifty = 50, u64_min = 0,           u64_max = ULLONG_MAX/2, u64_hi_a = ULLONG_MAX/2 - 100, u64_hi_b = ULLONG_MAX/2;
+            const float   f32_zero = 0.f, f32_one = 1.f, f32_lo_a = -10000000000.0f, f32_hi_a = +10000000000.0f;
+            const double  f64_zero = 0.,  f64_one = 1.,  f64_lo_a = -1000000000000000.0, f64_hi_a = +1000000000000000.0;
+
+            // State
+            static ImS32  s32_v = -1;
+            static ImU32  u32_v = (ImU32)-1;
+            static ImS64  s64_v = -1;
+            static ImU64  u64_v = (ImU64)-1;
+            static float  f32_v = 0.123f;
+            static double f64_v = 90000.01234567890123456789;
+
+            const float drag_speed = 0.2f;
+            static bool drag_clamp = false;
+            ImGui::Text("Drags:");
+            ImGui::Checkbox("Clamp integers to 0..50", &drag_clamp); ImGui::SameLine(); ShowHelpMarker("As with every widgets in dear imgui, we never modify values unless there is a user interaction.\nYou can override the clamping limits by using CTRL+Click to input a value.");
+            ImGui::DragScalar("drag s32",       ImGuiDataType_S32,    &s32_v, drag_speed, drag_clamp ? &s32_zero : NULL, drag_clamp ? &s32_fifty : NULL);
+            ImGui::DragScalar("drag u32",       ImGuiDataType_U32,    &u32_v, drag_speed, drag_clamp ? &u32_zero : NULL, drag_clamp ? &u32_fifty : NULL, "%u ms");
+            ImGui::DragScalar("drag s64",       ImGuiDataType_S64,    &s64_v, drag_speed, drag_clamp ? &s64_zero : NULL, drag_clamp ? &s64_fifty : NULL);
+            ImGui::DragScalar("drag u64",       ImGuiDataType_U64,    &u64_v, drag_speed, drag_clamp ? &u64_zero : NULL, drag_clamp ? &u64_fifty : NULL);
+            ImGui::DragScalar("drag float",     ImGuiDataType_Float,  &f32_v, 0.005f,  &f32_zero, &f32_one, "%f", 1.0f);
+            ImGui::DragScalar("drag float ^2",  ImGuiDataType_Float,  &f32_v, 0.005f,  &f32_zero, &f32_one, "%f", 2.0f); ImGui::SameLine(); ShowHelpMarker("You can use the 'power' parameter to increase tweaking precision on one side of the range.");
+            ImGui::DragScalar("drag double",    ImGuiDataType_Double, &f64_v, 0.0005f, &f64_zero, NULL,     "%.10f grams", 1.0f);
+            ImGui::DragScalar("drag double ^2", ImGuiDataType_Double, &f64_v, 0.0005f, &f64_zero, &f64_one, "0 < %.10f < 1", 2.0f);
+
+            ImGui::Text("Sliders");
+            ImGui::SliderScalar("slider s32 low",     ImGuiDataType_S32,    &s32_v, &s32_zero, &s32_fifty,"%d");
+            ImGui::SliderScalar("slider s32 high",    ImGuiDataType_S32,    &s32_v, &s32_hi_a, &s32_hi_b, "%d");
+            ImGui::SliderScalar("slider s32 full",    ImGuiDataType_S32,    &s32_v, &s32_min,  &s32_max,  "%d");
+            ImGui::SliderScalar("slider u32 low",     ImGuiDataType_U32,    &u32_v, &u32_zero, &u32_fifty,"%u");
+            ImGui::SliderScalar("slider u32 high",    ImGuiDataType_U32,    &u32_v, &u32_hi_a, &u32_hi_b, "%u");
+            ImGui::SliderScalar("slider u32 full",    ImGuiDataType_U32,    &u32_v, &u32_min,  &u32_max,  "%u");
+            ImGui::SliderScalar("slider s64 low",     ImGuiDataType_S64,    &s64_v, &s64_zero, &s64_fifty,"%I64d");
+            ImGui::SliderScalar("slider s64 high",    ImGuiDataType_S64,    &s64_v, &s64_hi_a, &s64_hi_b, "%I64d");
+            ImGui::SliderScalar("slider s64 full",    ImGuiDataType_S64,    &s64_v, &s64_min,  &s64_max,  "%I64d");
+            ImGui::SliderScalar("slider u64 low",     ImGuiDataType_U64,    &u64_v, &u64_zero, &u64_fifty,"%I64u ms");
+            ImGui::SliderScalar("slider u64 high",    ImGuiDataType_U64,    &u64_v, &u64_hi_a, &u64_hi_b, "%I64u ms");
+            ImGui::SliderScalar("slider u64 full",    ImGuiDataType_U64,    &u64_v, &u64_min,  &u64_max,  "%I64u ms");
+            ImGui::SliderScalar("slider float low",   ImGuiDataType_Float,  &f32_v, &f32_zero, &f32_one);
+            ImGui::SliderScalar("slider float low^2", ImGuiDataType_Float,  &f32_v, &f32_zero, &f32_one,  "%.10f", 2.0f);
+            ImGui::SliderScalar("slider float high",  ImGuiDataType_Float,  &f32_v, &f32_lo_a, &f32_hi_a, "%e");
+            ImGui::SliderScalar("slider double low",  ImGuiDataType_Double, &f64_v, &f64_zero, &f64_one,  "%.10f grams", 1.0f);
+            ImGui::SliderScalar("slider double low^2",ImGuiDataType_Double, &f64_v, &f64_zero, &f64_one,  "%.10f", 2.0f);
+            ImGui::SliderScalar("slider double high", ImGuiDataType_Double, &f64_v, &f64_lo_a, &f64_hi_a, "%e grams", 1.0f);
+
+            static bool inputs_step = true;
+            ImGui::Text("Inputs");
+            ImGui::Checkbox("Show step buttons", &inputs_step);
+            ImGui::InputScalar("input s32",     ImGuiDataType_S32,    &s32_v, inputs_step ? &s32_one : NULL, NULL, "%d");
+            ImGui::InputScalar("input s32 hex", ImGuiDataType_S32,    &s32_v, inputs_step ? &s32_one : NULL, NULL, "%08X", ImGuiInputTextFlags_CharsHexadecimal);
+            ImGui::InputScalar("input u32",     ImGuiDataType_U32,    &u32_v, inputs_step ? &u32_one : NULL, NULL, "%u");
+            ImGui::InputScalar("input u32 hex", ImGuiDataType_U32,    &u32_v, inputs_step ? &u32_one : NULL, NULL, "%08X", ImGuiInputTextFlags_CharsHexadecimal);
+            ImGui::InputScalar("input s64",     ImGuiDataType_S64,    &s64_v, inputs_step ? &s64_one : NULL);
+            ImGui::InputScalar("input u64",     ImGuiDataType_U64,    &u64_v, inputs_step ? &u64_one : NULL);
+            ImGui::InputScalar("input float",   ImGuiDataType_Float,  &f32_v, inputs_step ? &f32_one : NULL);
+            ImGui::InputScalar("input double",  ImGuiDataType_Double, &f64_v, inputs_step ? &f64_one : NULL);
+
+            ImGui::TreePop();
+        }
+
+        if (ImGui::TreeNode("Multi-component Widgets"))
+        {
+            static float vec4f[4] = { 0.10f, 0.20f, 0.30f, 0.44f };
+            static int vec4i[4] = { 1, 5, 100, 255 };
+
+            ImGui::InputFloat2("input float2", vec4f);
+            ImGui::DragFloat2("drag float2", vec4f, 0.01f, 0.0f, 1.0f);
+            ImGui::SliderFloat2("slider float2", vec4f, 0.0f, 1.0f);
+            ImGui::InputInt2("input int2", vec4i);
+            ImGui::DragInt2("drag int2", vec4i, 1, 0, 255);
+            ImGui::SliderInt2("slider int2", vec4i, 0, 255);
+            ImGui::Spacing();
+
+            ImGui::InputFloat3("input float3", vec4f);
+            ImGui::DragFloat3("drag float3", vec4f, 0.01f, 0.0f, 1.0f);
+            ImGui::SliderFloat3("slider float3", vec4f, 0.0f, 1.0f);
+            ImGui::InputInt3("input int3", vec4i);
+            ImGui::DragInt3("drag int3", vec4i, 1, 0, 255);
+            ImGui::SliderInt3("slider int3", vec4i, 0, 255);
+            ImGui::Spacing();
+
+            ImGui::InputFloat4("input float4", vec4f);
+            ImGui::DragFloat4("drag float4", vec4f, 0.01f, 0.0f, 1.0f);
+            ImGui::SliderFloat4("slider float4", vec4f, 0.0f, 1.0f);
+            ImGui::InputInt4("input int4", vec4i);
+            ImGui::DragInt4("drag int4", vec4i, 1, 0, 255);
+            ImGui::SliderInt4("slider int4", vec4i, 0, 255);
+
+            ImGui::TreePop();
+        }
+
+        if (ImGui::TreeNode("Vertical Sliders"))
+        {
+            const float spacing = 4;
+            ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(spacing, spacing));
+
+            static int int_value = 0;
+            ImGui::VSliderInt("##int", ImVec2(18,160), &int_value, 0, 5);
+            ImGui::SameLine();
+
+            static float values[7] = { 0.0f, 0.60f, 0.35f, 0.9f, 0.70f, 0.20f, 0.0f };
+            ImGui::PushID("set1");
+            for (int i = 0; i < 7; i++)
+            {
+                if (i > 0) ImGui::SameLine();
+                ImGui::PushID(i);
+                ImGui::PushStyleColor(ImGuiCol_FrameBg, (ImVec4)ImColor::HSV(i/7.0f, 0.5f, 0.5f));
+                ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, (ImVec4)ImColor::HSV(i/7.0f, 0.6f, 0.5f));
+                ImGui::PushStyleColor(ImGuiCol_FrameBgActive, (ImVec4)ImColor::HSV(i/7.0f, 0.7f, 0.5f));
+                ImGui::PushStyleColor(ImGuiCol_SliderGrab, (ImVec4)ImColor::HSV(i/7.0f, 0.9f, 0.9f));
+                ImGui::VSliderFloat("##v", ImVec2(18,160), &values[i], 0.0f, 1.0f, "");
+                if (ImGui::IsItemActive() || ImGui::IsItemHovered())
+                    ImGui::SetTooltip("%.3f", values[i]);
+                ImGui::PopStyleColor(4);
+                ImGui::PopID();
+            }
+            ImGui::PopID();
+
+            ImGui::SameLine();
+            ImGui::PushID("set2");
+            static float values2[4] = { 0.20f, 0.80f, 0.40f, 0.25f };
+            const int rows = 3;
+            const ImVec2 small_slider_size(18, (160.0f-(rows-1)*spacing)/rows);
+            for (int nx = 0; nx < 4; nx++)
+            {
+                if (nx > 0) ImGui::SameLine();
+                ImGui::BeginGroup();
+                for (int ny = 0; ny < rows; ny++)
+                {
+                    ImGui::PushID(nx*rows+ny);
+                    ImGui::VSliderFloat("##v", small_slider_size, &values2[nx], 0.0f, 1.0f, "");
+                    if (ImGui::IsItemActive() || ImGui::IsItemHovered())
+                        ImGui::SetTooltip("%.3f", values2[nx]);
+                    ImGui::PopID();
+                }
+                ImGui::EndGroup();
+            }
+            ImGui::PopID();
+
+            ImGui::SameLine();
+            ImGui::PushID("set3");
+            for (int i = 0; i < 4; i++)
+            {
+                if (i > 0) ImGui::SameLine();
+                ImGui::PushID(i);
+                ImGui::PushStyleVar(ImGuiStyleVar_GrabMinSize, 40);
+                ImGui::VSliderFloat("##v", ImVec2(40,160), &values[i], 0.0f, 1.0f, "%.2f\nsec");
+                ImGui::PopStyleVar();
+                ImGui::PopID();
+            }
+            ImGui::PopID();
+            ImGui::PopStyleVar();
+            ImGui::TreePop();
+        }
+
+        if (ImGui::TreeNode("Drag and Drop"))
+        {
+            {
+                // ColorEdit widgets automatically act as drag source and drag target.
+                // They are using standardized payload strings IMGUI_PAYLOAD_TYPE_COLOR_3F and IMGUI_PAYLOAD_TYPE_COLOR_4F to allow your own widgets
+                // to use colors in their drag and drop interaction. Also see the demo in Color Picker -> Palette demo.
+                ImGui::BulletText("Drag and drop in standard widgets");
+                ImGui::Indent();
+                static float col1[3] = { 1.0f,0.0f,0.2f };
+                static float col2[4] = { 0.4f,0.7f,0.0f,0.5f };
+                ImGui::ColorEdit3("color 1", col1);
+                ImGui::ColorEdit4("color 2", col2);
+                ImGui::Unindent();
+            }
+
+            {
+                ImGui::BulletText("Drag and drop to copy/swap items");
+                ImGui::Indent();
+                enum Mode
+                {
+                    Mode_Copy,
+                    Mode_Move,
+                    Mode_Swap
+                };
+                static int mode = 0;
+                if (ImGui::RadioButton("Copy", mode == Mode_Copy)) { mode = Mode_Copy; } ImGui::SameLine();
+                if (ImGui::RadioButton("Move", mode == Mode_Move)) { mode = Mode_Move; } ImGui::SameLine();
+                if (ImGui::RadioButton("Swap", mode == Mode_Swap)) { mode = Mode_Swap; } 
+                static const char* names[9] = { "Bobby", "Beatrice", "Betty", "Brianna", "Barry", "Bernard", "Bibi", "Blaine", "Bryn" };
+                for (int n = 0; n < IM_ARRAYSIZE(names); n++)
+                {
+                    ImGui::PushID(n);
+                    if ((n % 3) != 0)
+                        ImGui::SameLine();
+                    ImGui::Button(names[n], ImVec2(60,60));
+
+                    // Our buttons are both drag sources and drag targets here!
+                    if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None))
+                    {
+                        ImGui::SetDragDropPayload("DND_DEMO_CELL", &n, sizeof(int));        // Set payload to carry the index of our item (could be anything)
+                        if (mode == Mode_Copy) { ImGui::Text("Copy %s", names[n]); }        // Display preview (could be anything, e.g. when dragging an image we could decide to display the filename and a small preview of the image, etc.)
+                        if (mode == Mode_Move) { ImGui::Text("Move %s", names[n]); }
+                        if (mode == Mode_Swap) { ImGui::Text("Swap %s", names[n]); }
+                        ImGui::EndDragDropSource();
+                    }
+                    if (ImGui::BeginDragDropTarget())
+                    {
+                        if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("DND_DEMO_CELL"))
+                        {
+                            IM_ASSERT(payload->DataSize == sizeof(int));
+                            int payload_n = *(const int*)payload->Data;
+                            if (mode == Mode_Copy)
+                            {
+                                names[n] = names[payload_n];
+                            }
+                            if (mode == Mode_Move)
+                            {
+                                names[n] = names[payload_n];
+                                names[payload_n] = "";
+                            }
+                            if (mode == Mode_Swap)
+                            {
+                                const char* tmp = names[n];
+                                names[n] = names[payload_n];
+                                names[payload_n] = tmp;
+                            }
+                        }
+                        ImGui::EndDragDropTarget();
+                    }
+                    ImGui::PopID();
+                }
+                ImGui::Unindent();
+            }
+
+            ImGui::TreePop();
+        }
+
+        if (ImGui::TreeNode("Querying Status (Active/Focused/Hovered etc.)"))
+        {
+            // Display the value of IsItemHovered() and other common item state functions. Note that the flags can be combined.
+            // (because BulletText is an item itself and that would affect the output of IsItemHovered() we pass all state in a single call to simplify the code).
+            static int item_type = 1;
+            static bool b = false;
+            static float col4f[4] = { 1.0f, 0.5, 0.0f, 1.0f };
+            ImGui::RadioButton("Text", &item_type, 0);
+            ImGui::RadioButton("Button", &item_type, 1);
+            ImGui::RadioButton("CheckBox", &item_type, 2);
+            ImGui::RadioButton("SliderFloat", &item_type, 3);
+            ImGui::RadioButton("ColorEdit4", &item_type, 4);
+            ImGui::RadioButton("ListBox", &item_type, 5);
+            ImGui::Separator();
+            bool ret = false;
+            if (item_type == 0) { ImGui::Text("ITEM: Text"); }                                              // Testing text items with no identifier/interaction
+            if (item_type == 1) { ret = ImGui::Button("ITEM: Button"); }                                    // Testing button
+            if (item_type == 2) { ret = ImGui::Checkbox("ITEM: CheckBox", &b); }                            // Testing checkbox
+            if (item_type == 3) { ret = ImGui::SliderFloat("ITEM: SliderFloat", &col4f[0], 0.0f, 1.0f); }   // Testing basic item
+            if (item_type == 4) { ret = ImGui::ColorEdit4("ITEM: ColorEdit4", col4f); }                     // Testing multi-component items (IsItemXXX flags are reported merged)
+            if (item_type == 5) { const char* items[] = { "Apple", "Banana", "Cherry", "Kiwi" }; static int current = 1; ret = ImGui::ListBox("ITEM: ListBox", &current, items, IM_ARRAYSIZE(items), IM_ARRAYSIZE(items)); }
+            ImGui::BulletText(
+                "Return value = %d\n"
+                "IsItemFocused() = %d\n"
+                "IsItemHovered() = %d\n"
+                "IsItemHovered(_AllowWhenBlockedByPopup) = %d\n"
+                "IsItemHovered(_AllowWhenBlockedByActiveItem) = %d\n"
+                "IsItemHovered(_AllowWhenOverlapped) = %d\n"
+                "IsItemHovered(_RectOnly) = %d\n"
+                "IsItemActive() = %d\n"
+                "IsItemEdited() = %d\n"
+                "IsItemDeactivated() = %d\n"
+                "IsItemDeactivatedEdit() = %d\n"
+                "IsItemVisible() = %d\n"
+                "GetItemRectMin() = (%.1f, %.1f)\n"
+                "GetItemRectMax() = (%.1f, %.1f)\n"
+                "GetItemRectSize() = (%.1f, %.1f)",
+                ret,
+                ImGui::IsItemFocused(),
+                ImGui::IsItemHovered(),
+                ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup),
+                ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem),
+                ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenOverlapped),
+                ImGui::IsItemHovered(ImGuiHoveredFlags_RectOnly),
+                ImGui::IsItemActive(),
+                ImGui::IsItemEdited(),
+                ImGui::IsItemDeactivated(),
+                ImGui::IsItemDeactivatedAfterEdit(),
+                ImGui::IsItemVisible(),
+                ImGui::GetItemRectMin().x, ImGui::GetItemRectMin().y,
+                ImGui::GetItemRectMax().x, ImGui::GetItemRectMax().y,
+                ImGui::GetItemRectSize().x, ImGui::GetItemRectSize().y
+            );
+
+            static bool embed_all_inside_a_child_window = false;
+            ImGui::Checkbox("Embed everything inside a child window (for additional testing)", &embed_all_inside_a_child_window);
+            if (embed_all_inside_a_child_window)
+                ImGui::BeginChild("outer_child", ImVec2(0, ImGui::GetFontSize() * 20), true);
+
+            // Testing IsWindowFocused() function with its various flags. Note that the flags can be combined.
+            ImGui::BulletText(
+                "IsWindowFocused() = %d\n"
+                "IsWindowFocused(_ChildWindows) = %d\n"
+                "IsWindowFocused(_ChildWindows|_RootWindow) = %d\n"
+                "IsWindowFocused(_RootWindow) = %d\n"
+                "IsWindowFocused(_AnyWindow) = %d\n",
+                ImGui::IsWindowFocused(),
+                ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows),
+                ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows | ImGuiFocusedFlags_RootWindow),
+                ImGui::IsWindowFocused(ImGuiFocusedFlags_RootWindow),
+                ImGui::IsWindowFocused(ImGuiFocusedFlags_AnyWindow));
+
+            // Testing IsWindowHovered() function with its various flags. Note that the flags can be combined.
+            ImGui::BulletText(
+                "IsWindowHovered() = %d\n"
+                "IsWindowHovered(_AllowWhenBlockedByPopup) = %d\n"
+                "IsWindowHovered(_AllowWhenBlockedByActiveItem) = %d\n"
+                "IsWindowHovered(_ChildWindows) = %d\n"
+                "IsWindowHovered(_ChildWindows|_RootWindow) = %d\n"
+                "IsWindowHovered(_RootWindow) = %d\n"
+                "IsWindowHovered(_AnyWindow) = %d\n",
+                ImGui::IsWindowHovered(),
+                ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup),
+                ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem),
+                ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows),
+                ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows | ImGuiHoveredFlags_RootWindow),
+                ImGui::IsWindowHovered(ImGuiHoveredFlags_RootWindow),
+                ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow));
+
+            ImGui::BeginChild("child", ImVec2(0, 50), true);
+            ImGui::Text("This is another child window for testing with the _ChildWindows flag.");
+            ImGui::EndChild();
+            if (embed_all_inside_a_child_window)
+                EndChild();
+
+            // Calling IsItemHovered() after begin returns the hovered status of the title bar. 
+            // This is useful in particular if you want to create a context menu (with BeginPopupContextItem) associated to the title bar of a window.
+            static bool test_window = false;
+            ImGui::Checkbox("Hovered/Active tests after Begin() for title bar testing", &test_window);
+            if (test_window)
+            {
+                ImGui::Begin("Title bar Hovered/Active tests", &test_window);
+                if (ImGui::BeginPopupContextItem()) // <-- This is using IsItemHovered()
+                {
+                    if (ImGui::MenuItem("Close")) { test_window = false; }
+                    ImGui::EndPopup();
+                }
+                ImGui::Text(
+                    "IsItemHovered() after begin = %d (== is title bar hovered)\n"
+                    "IsItemActive() after begin = %d (== is window being clicked/moved)\n",
+                    ImGui::IsItemHovered(), ImGui::IsItemActive());
+                ImGui::End();
+            }
+
+            ImGui::TreePop();
+        }
+    }
+
+    if (ImGui::CollapsingHeader("Layout"))
+    {
+        if (ImGui::TreeNode("Child regions"))
+        {
+            static bool disable_mouse_wheel = false;
+            static bool disable_menu = false;
+            ImGui::Checkbox("Disable Mouse Wheel", &disable_mouse_wheel);
+            ImGui::Checkbox("Disable Menu", &disable_menu);
+
+            static int line = 50;
+            bool goto_line = ImGui::Button("Goto");
+            ImGui::SameLine();
+            ImGui::PushItemWidth(100);
+            goto_line |= ImGui::InputInt("##Line", &line, 0, 0, ImGuiInputTextFlags_EnterReturnsTrue);
+            ImGui::PopItemWidth();
+
+            // Child 1: no border, enable horizontal scrollbar
+            {
+                ImGui::BeginChild("Child1", ImVec2(ImGui::GetWindowContentRegionWidth() * 0.5f, 300), false, ImGuiWindowFlags_HorizontalScrollbar | (disable_mouse_wheel ? ImGuiWindowFlags_NoScrollWithMouse : 0));
+                for (int i = 0; i < 100; i++)
+                {
+                    ImGui::Text("%04d: scrollable region", i);
+                    if (goto_line && line == i)
+                        ImGui::SetScrollHere();
+                }
+                if (goto_line && line >= 100)
+                    ImGui::SetScrollHere();
+                ImGui::EndChild();
+            }
+
+            ImGui::SameLine();
+
+            // Child 2: rounded border
+            {
+                ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 5.0f);
+                ImGui::BeginChild("Child2", ImVec2(0,300), true, (disable_mouse_wheel ? ImGuiWindowFlags_NoScrollWithMouse : 0) | (disable_menu ? 0 : ImGuiWindowFlags_MenuBar));
+                if (!disable_menu && ImGui::BeginMenuBar())
+                {
+                    if (ImGui::BeginMenu("Menu"))
+                    {
+                        ShowExampleMenuFile();
+                        ImGui::EndMenu();
+                    }
+                    ImGui::EndMenuBar();
+                }
+                ImGui::Columns(2);
+                for (int i = 0; i < 100; i++)
+                {
+                    char buf[32];
+                    sprintf(buf, "%03d", i);
+                    ImGui::Button(buf, ImVec2(-1.0f, 0.0f));
+                    ImGui::NextColumn();
+                }
+                ImGui::EndChild();
+                ImGui::PopStyleVar();
+            }
+
+            ImGui::TreePop();
+        }
+
+        if (ImGui::TreeNode("Widgets Width"))
+        {
+            static float f = 0.0f;
+            ImGui::Text("PushItemWidth(100)");
+            ImGui::SameLine(); ShowHelpMarker("Fixed width.");
+            ImGui::PushItemWidth(100);
+            ImGui::DragFloat("float##1", &f);
+            ImGui::PopItemWidth();
+
+            ImGui::Text("PushItemWidth(GetWindowWidth() * 0.5f)");
+            ImGui::SameLine(); ShowHelpMarker("Half of window width.");
+            ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.5f);
+            ImGui::DragFloat("float##2", &f);
+            ImGui::PopItemWidth();
+
+            ImGui::Text("PushItemWidth(GetContentRegionAvailWidth() * 0.5f)");
+            ImGui::SameLine(); ShowHelpMarker("Half of available width.\n(~ right-cursor_pos)\n(works within a column set)");
+            ImGui::PushItemWidth(ImGui::GetContentRegionAvailWidth() * 0.5f);
+            ImGui::DragFloat("float##3", &f);
+            ImGui::PopItemWidth();
+
+            ImGui::Text("PushItemWidth(-100)");
+            ImGui::SameLine(); ShowHelpMarker("Align to right edge minus 100");
+            ImGui::PushItemWidth(-100);
+            ImGui::DragFloat("float##4", &f);
+            ImGui::PopItemWidth();
+
+            ImGui::Text("PushItemWidth(-1)");
+            ImGui::SameLine(); ShowHelpMarker("Align to right edge");
+            ImGui::PushItemWidth(-1);
+            ImGui::DragFloat("float##5", &f);
+            ImGui::PopItemWidth();
+
+            ImGui::TreePop();
+        }
+
+        if (ImGui::TreeNode("Basic Horizontal Layout"))
+        {
+            ImGui::TextWrapped("(Use ImGui::SameLine() to keep adding items to the right of the preceding item)");
+
+            // Text
+            ImGui::Text("Two items: Hello"); ImGui::SameLine();
+            ImGui::TextColored(ImVec4(1,1,0,1), "Sailor");
+
+            // Adjust spacing
+            ImGui::Text("More spacing: Hello"); ImGui::SameLine(0, 20);
+            ImGui::TextColored(ImVec4(1,1,0,1), "Sailor");
+
+            // Button
+            ImGui::AlignTextToFramePadding();
+            ImGui::Text("Normal buttons"); ImGui::SameLine();
+            ImGui::Button("Banana"); ImGui::SameLine();
+            ImGui::Button("Apple"); ImGui::SameLine();
+            ImGui::Button("Corniflower");
+
+            // Button
+            ImGui::Text("Small buttons"); ImGui::SameLine();
+            ImGui::SmallButton("Like this one"); ImGui::SameLine();
+            ImGui::Text("can fit within a text block.");
+
+            // Aligned to arbitrary position. Easy/cheap column.
+            ImGui::Text("Aligned");
+            ImGui::SameLine(150); ImGui::Text("x=150");
+            ImGui::SameLine(300); ImGui::Text("x=300");
+            ImGui::Text("Aligned");
+            ImGui::SameLine(150); ImGui::SmallButton("x=150");
+            ImGui::SameLine(300); ImGui::SmallButton("x=300");
+
+            // Checkbox
+            static bool c1=false,c2=false,c3=false,c4=false;
+            ImGui::Checkbox("My", &c1); ImGui::SameLine();
+            ImGui::Checkbox("Tailor", &c2); ImGui::SameLine();
+            ImGui::Checkbox("Is", &c3); ImGui::SameLine();
+            ImGui::Checkbox("Rich", &c4);
+
+            // Various
+            static float f0=1.0f, f1=2.0f, f2=3.0f;
+            ImGui::PushItemWidth(80);
+            const char* items[] = { "AAAA", "BBBB", "CCCC", "DDDD" };
+            static int item = -1;
+            ImGui::Combo("Combo", &item, items, IM_ARRAYSIZE(items)); ImGui::SameLine();
+            ImGui::SliderFloat("X", &f0, 0.0f,5.0f); ImGui::SameLine();
+            ImGui::SliderFloat("Y", &f1, 0.0f,5.0f); ImGui::SameLine();
+            ImGui::SliderFloat("Z", &f2, 0.0f,5.0f);
+            ImGui::PopItemWidth();
+
+            ImGui::PushItemWidth(80);
+            ImGui::Text("Lists:");
+            static int selection[4] = { 0, 1, 2, 3 };
+            for (int i = 0; i < 4; i++)
+            {
+                if (i > 0) ImGui::SameLine();
+                ImGui::PushID(i);
+                ImGui::ListBox("", &selection[i], items, IM_ARRAYSIZE(items));
+                ImGui::PopID();
+                //if (ImGui::IsItemHovered()) ImGui::SetTooltip("ListBox %d hovered", i);
+            }
+            ImGui::PopItemWidth();
+
+            // Dummy
+            ImVec2 button_sz(40,40);
+            ImGui::Button("A", button_sz); ImGui::SameLine();
+            ImGui::Dummy(button_sz); ImGui::SameLine();
+            ImGui::Button("B", button_sz);
+
+            // Manually wrapping (we should eventually provide this as an automatic layout feature, but for now you can do it manually)
+            ImGui::Text("Manually wrapping:");
+            ImGuiStyle& style = ImGui::GetStyle();
+            int buttons_count = 20;
+            float window_visible_x2 = ImGui::GetWindowPos().x + ImGui::GetWindowContentRegionMax().x;
+            for (int n = 0; n < buttons_count; n++)
+            {
+                ImGui::PushID(n);
+                ImGui::Button("Box", button_sz);
+                float last_button_x2 = ImGui::GetItemRectMax().x;
+                float next_button_x2 = last_button_x2 + style.ItemSpacing.x + button_sz.x; // Expected position if next button was on same line
+                if (n + 1 < buttons_count && next_button_x2 < window_visible_x2)
+                    ImGui::SameLine();
+                ImGui::PopID();
+            }
+
+            ImGui::TreePop();
+        }
+
+        if (ImGui::TreeNode("Groups"))
+        {
+            ImGui::TextWrapped("(Using ImGui::BeginGroup()/EndGroup() to layout items. BeginGroup() basically locks the horizontal position. EndGroup() bundles the whole group so that you can use functions such as IsItemHovered() on it.)");
+            ImGui::BeginGroup();
+            {
+                ImGui::BeginGroup();
+                ImGui::Button("AAA");
+                ImGui::SameLine();
+                ImGui::Button("BBB");
+                ImGui::SameLine();
+                ImGui::BeginGroup();
+                ImGui::Button("CCC");
+                ImGui::Button("DDD");
+                ImGui::EndGroup();
+                ImGui::SameLine();
+                ImGui::Button("EEE");
+                ImGui::EndGroup();
+                if (ImGui::IsItemHovered())
+                    ImGui::SetTooltip("First group hovered");
+            }
+            // Capture the group size and create widgets using the same size
+            ImVec2 size = ImGui::GetItemRectSize();
+            const float values[5] = { 0.5f, 0.20f, 0.80f, 0.60f, 0.25f };
+            ImGui::PlotHistogram("##values", values, IM_ARRAYSIZE(values), 0, NULL, 0.0f, 1.0f, size);
+
+            ImGui::Button("ACTION", ImVec2((size.x - ImGui::GetStyle().ItemSpacing.x)*0.5f,size.y));
+            ImGui::SameLine();
+            ImGui::Button("REACTION", ImVec2((size.x - ImGui::GetStyle().ItemSpacing.x)*0.5f,size.y));
+            ImGui::EndGroup();
+            ImGui::SameLine();
+
+            ImGui::Button("LEVERAGE\nBUZZWORD", size);
+            ImGui::SameLine();
+
+            if (ImGui::ListBoxHeader("List", size))
+            {
+                ImGui::Selectable("Selected", true);
+                ImGui::Selectable("Not Selected", false);
+                ImGui::ListBoxFooter();
+            }
+
+            ImGui::TreePop();
+        }
+
+        if (ImGui::TreeNode("Text Baseline Alignment"))
+        {
+            ImGui::TextWrapped("(This is testing the vertical alignment that occurs on text to keep it at the same baseline as widgets. Lines only composed of text or \"small\" widgets fit in less vertical spaces than lines with normal widgets)");
+
+            ImGui::Text("One\nTwo\nThree"); ImGui::SameLine();
+            ImGui::Text("Hello\nWorld"); ImGui::SameLine();
+            ImGui::Text("Banana");
+
+            ImGui::Text("Banana"); ImGui::SameLine();
+            ImGui::Text("Hello\nWorld"); ImGui::SameLine();
+            ImGui::Text("One\nTwo\nThree");
+
+            ImGui::Button("HOP##1"); ImGui::SameLine();
+            ImGui::Text("Banana"); ImGui::SameLine();
+            ImGui::Text("Hello\nWorld"); ImGui::SameLine();
+            ImGui::Text("Banana");
+
+            ImGui::Button("HOP##2"); ImGui::SameLine();
+            ImGui::Text("Hello\nWorld"); ImGui::SameLine();
+            ImGui::Text("Banana");
+
+            ImGui::Button("TEST##1"); ImGui::SameLine();
+            ImGui::Text("TEST"); ImGui::SameLine();
+            ImGui::SmallButton("TEST##2");
+
+            ImGui::AlignTextToFramePadding(); // If your line starts with text, call this to align it to upcoming widgets.
+            ImGui::Text("Text aligned to Widget"); ImGui::SameLine();
+            ImGui::Button("Widget##1"); ImGui::SameLine();
+            ImGui::Text("Widget"); ImGui::SameLine();
+            ImGui::SmallButton("Widget##2"); ImGui::SameLine();
+            ImGui::Button("Widget##3");
+
+            // Tree
+            const float spacing = ImGui::GetStyle().ItemInnerSpacing.x;
+            ImGui::Button("Button##1");
+            ImGui::SameLine(0.0f, spacing);
+            if (ImGui::TreeNode("Node##1")) { for (int i = 0; i < 6; i++) ImGui::BulletText("Item %d..", i); ImGui::TreePop(); }    // Dummy tree data
+
+            ImGui::AlignTextToFramePadding();         // Vertically align text node a bit lower so it'll be vertically centered with upcoming widget. Otherwise you can use SmallButton (smaller fit).
+            bool node_open = ImGui::TreeNode("Node##2");  // Common mistake to avoid: if we want to SameLine after TreeNode we need to do it before we add child content.
+            ImGui::SameLine(0.0f, spacing); ImGui::Button("Button##2");
+            if (node_open) { for (int i = 0; i < 6; i++) ImGui::BulletText("Item %d..", i); ImGui::TreePop(); }   // Dummy tree data
+
+            // Bullet
+            ImGui::Button("Button##3");
+            ImGui::SameLine(0.0f, spacing);
+            ImGui::BulletText("Bullet text");
+
+            ImGui::AlignTextToFramePadding();
+            ImGui::BulletText("Node");
+            ImGui::SameLine(0.0f, spacing); ImGui::Button("Button##4");
+
+            ImGui::TreePop();
+        }
+
+        if (ImGui::TreeNode("Scrolling"))
+        {
+            ImGui::TextWrapped("(Use SetScrollHere() or SetScrollFromPosY() to scroll to a given position.)");
+            static bool track = true;
+            static int track_line = 50, scroll_to_px = 200;
+            ImGui::Checkbox("Track", &track);
+            ImGui::PushItemWidth(100);
+            ImGui::SameLine(130); track |= ImGui::DragInt("##line", &track_line, 0.25f, 0, 99, "Line = %d");
+            bool scroll_to = ImGui::Button("Scroll To Pos");
+            ImGui::SameLine(130); scroll_to |= ImGui::DragInt("##pos_y", &scroll_to_px, 1.00f, 0, 9999, "Y = %d px");
+            ImGui::PopItemWidth();
+            if (scroll_to) track = false;
+
+            for (int i = 0; i < 5; i++)
+            {
+                if (i > 0) ImGui::SameLine();
+                ImGui::BeginGroup();
+                ImGui::Text("%s", i == 0 ? "Top" : i == 1 ? "25%" : i == 2 ? "Center" : i == 3 ? "75%" : "Bottom");
+                ImGui::BeginChild(ImGui::GetID((void*)(intptr_t)i), ImVec2(ImGui::GetWindowWidth() * 0.17f, 200.0f), true);
+                if (scroll_to)
+                    ImGui::SetScrollFromPosY(ImGui::GetCursorStartPos().y + scroll_to_px, i * 0.25f);
+                for (int line = 0; line < 100; line++)
+                {
+                    if (track && line == track_line)
+                    {
+                        ImGui::TextColored(ImColor(255,255,0), "Line %d", line);
+                        ImGui::SetScrollHere(i * 0.25f); // 0.0f:top, 0.5f:center, 1.0f:bottom
+                    }
+                    else
+                    {
+                        ImGui::Text("Line %d", line);
+                    }
+                }
+                float scroll_y = ImGui::GetScrollY(), scroll_max_y = ImGui::GetScrollMaxY();
+                ImGui::EndChild();
+                ImGui::Text("%.0f/%0.f", scroll_y, scroll_max_y);
+                ImGui::EndGroup();
+            }
+            ImGui::TreePop();
+        }
+
+        if (ImGui::TreeNode("Horizontal Scrolling"))
+        {
+            ImGui::Bullet(); ImGui::TextWrapped("Horizontal scrolling for a window has to be enabled explicitly via the ImGuiWindowFlags_HorizontalScrollbar flag.");
+            ImGui::Bullet(); ImGui::TextWrapped("You may want to explicitly specify content width by calling SetNextWindowContentWidth() before Begin().");
+            static int lines = 7;
+            ImGui::SliderInt("Lines", &lines, 1, 15);
+            ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f);
+            ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2.0f, 1.0f));
+            ImGui::BeginChild("scrolling", ImVec2(0, ImGui::GetFrameHeightWithSpacing()*7 + 30), true, ImGuiWindowFlags_HorizontalScrollbar);
+            for (int line = 0; line < lines; line++)
+            {
+                // Display random stuff (for the sake of this trivial demo we are using basic Button+SameLine. If you want to create your own time line for a real application you may be better off
+                // manipulating the cursor position yourself, aka using SetCursorPos/SetCursorScreenPos to position the widgets yourself. You may also want to use the lower-level ImDrawList API)
+                int num_buttons = 10 + ((line & 1) ? line * 9 : line * 3);
+                for (int n = 0; n < num_buttons; n++)
+                {
+                    if (n > 0) ImGui::SameLine();
+                    ImGui::PushID(n + line * 1000);
+                    char num_buf[16];
+                    sprintf(num_buf, "%d", n);
+                    const char* label = (!(n%15)) ? "FizzBuzz" : (!(n%3)) ? "Fizz" : (!(n%5)) ? "Buzz" : num_buf;
+                    float hue = n*0.05f;
+                    ImGui::PushStyleColor(ImGuiCol_Button, (ImVec4)ImColor::HSV(hue, 0.6f, 0.6f));
+                    ImGui::PushStyleColor(ImGuiCol_ButtonHovered, (ImVec4)ImColor::HSV(hue, 0.7f, 0.7f));
+                    ImGui::PushStyleColor(ImGuiCol_ButtonActive, (ImVec4)ImColor::HSV(hue, 0.8f, 0.8f));
+                    ImGui::Button(label, ImVec2(40.0f + sinf((float)(line + n)) * 20.0f, 0.0f));
+                    ImGui::PopStyleColor(3);
+                    ImGui::PopID();
+                }
+            }
+            float scroll_x = ImGui::GetScrollX(), scroll_max_x = ImGui::GetScrollMaxX();
+            ImGui::EndChild();
+            ImGui::PopStyleVar(2);
+            float scroll_x_delta = 0.0f;
+            ImGui::SmallButton("<<"); if (ImGui::IsItemActive()) scroll_x_delta = -ImGui::GetIO().DeltaTime * 1000.0f; ImGui::SameLine();
+            ImGui::Text("Scroll from code"); ImGui::SameLine();
+            ImGui::SmallButton(">>"); if (ImGui::IsItemActive()) scroll_x_delta = +ImGui::GetIO().DeltaTime * 1000.0f; ImGui::SameLine();
+            ImGui::Text("%.0f/%.0f", scroll_x, scroll_max_x);
+            if (scroll_x_delta != 0.0f)
+            {
+                ImGui::BeginChild("scrolling"); // Demonstrate a trick: you can use Begin to set yourself in the context of another window (here we are already out of your child window)
+                ImGui::SetScrollX(ImGui::GetScrollX() + scroll_x_delta);
+                ImGui::End();
+            }
+            ImGui::TreePop();
+        }
+
+        if (ImGui::TreeNode("Clipping"))
+        {
+            static ImVec2 size(100, 100), offset(50, 20);
+            ImGui::TextWrapped("On a per-widget basis we are occasionally clipping text CPU-side if it won't fit in its frame. Otherwise we are doing coarser clipping + passing a scissor rectangle to the renderer. The system is designed to try minimizing both execution and CPU/GPU rendering cost.");
+            ImGui::DragFloat2("size", (float*)&size, 0.5f, 0.0f, 200.0f, "%.0f");
+            ImGui::TextWrapped("(Click and drag)");
+            ImVec2 pos = ImGui::GetCursorScreenPos();
+            ImVec4 clip_rect(pos.x, pos.y, pos.x+size.x, pos.y+size.y);
+            ImGui::InvisibleButton("##dummy", size);
+            if (ImGui::IsItemActive() && ImGui::IsMouseDragging()) { offset.x += ImGui::GetIO().MouseDelta.x; offset.y += ImGui::GetIO().MouseDelta.y; }
+            ImGui::GetWindowDrawList()->AddRectFilled(pos, ImVec2(pos.x+size.x,pos.y+size.y), IM_COL32(90,90,120,255));
+            ImGui::GetWindowDrawList()->AddText(ImGui::GetFont(), ImGui::GetFontSize()*2.0f, ImVec2(pos.x+offset.x,pos.y+offset.y), IM_COL32(255,255,255,255), "Line 1 hello\nLine 2 clip me!", NULL, 0.0f, &clip_rect);
+            ImGui::TreePop();
+        }
+    }
+
+    if (ImGui::CollapsingHeader("Popups & Modal windows"))
+    {
+        if (ImGui::TreeNode("Popups"))
+        {
+            ImGui::TextWrapped("When a popup is active, it inhibits interacting with windows that are behind the popup. Clicking outside the popup closes it.");
+
+            static int selected_fish = -1;
+            const char* names[] = { "Bream", "Haddock", "Mackerel", "Pollock", "Tilefish" };
+            static bool toggles[] = { true, false, false, false, false };
+
+            // Simple selection popup
+            // (If you want to show the current selection inside the Button itself, you may want to build a string using the "###" operator to preserve a constant ID with a variable label)
+            if (ImGui::Button("Select.."))
+                ImGui::OpenPopup("select");
+            ImGui::SameLine();
+            ImGui::TextUnformatted(selected_fish == -1 ? "<None>" : names[selected_fish]);
+            if (ImGui::BeginPopup("select"))
+            {
+                ImGui::Text("Aquarium");
+                ImGui::Separator();
+                for (int i = 0; i < IM_ARRAYSIZE(names); i++)
+                    if (ImGui::Selectable(names[i]))
+                        selected_fish = i;
+                ImGui::EndPopup();
+            }
+
+            // Showing a menu with toggles
+            if (ImGui::Button("Toggle.."))
+                ImGui::OpenPopup("toggle");
+            if (ImGui::BeginPopup("toggle"))
+            {
+                for (int i = 0; i < IM_ARRAYSIZE(names); i++)
+                    ImGui::MenuItem(names[i], "", &toggles[i]);
+                if (ImGui::BeginMenu("Sub-menu"))
+                {
+                    ImGui::MenuItem("Click me");
+                    ImGui::EndMenu();
+                }
+
+                ImGui::Separator();
+                ImGui::Text("Tooltip here");
+                if (ImGui::IsItemHovered())
+                    ImGui::SetTooltip("I am a tooltip over a popup");
+
+                if (ImGui::Button("Stacked Popup"))
+                    ImGui::OpenPopup("another popup");
+                if (ImGui::BeginPopup("another popup"))
+                {
+                    for (int i = 0; i < IM_ARRAYSIZE(names); i++)
+                        ImGui::MenuItem(names[i], "", &toggles[i]);
+                    if (ImGui::BeginMenu("Sub-menu"))
+                    {
+                        ImGui::MenuItem("Click me");
+                        ImGui::EndMenu();
+                    }
+                    ImGui::EndPopup();
+                }
+                ImGui::EndPopup();
+            }
+
+            if (ImGui::Button("Popup Menu.."))
+                ImGui::OpenPopup("FilePopup");
+            if (ImGui::BeginPopup("FilePopup"))
+            {
+                ShowExampleMenuFile();
+                ImGui::EndPopup();
+            }
+
+            ImGui::TreePop();
+        }
+
+        if (ImGui::TreeNode("Context menus"))
+        {
+            // BeginPopupContextItem() is a helper to provide common/simple popup behavior of essentially doing:
+            //    if (IsItemHovered() && IsMouseClicked(0))
+            //       OpenPopup(id);
+            //    return BeginPopup(id);
+            // For more advanced uses you may want to replicate and cuztomize this code. This the comments inside BeginPopupContextItem() implementation.
+            static float value = 0.5f;
+            ImGui::Text("Value = %.3f (<-- right-click here)", value);
+            if (ImGui::BeginPopupContextItem("item context menu"))
+            {
+                if (ImGui::Selectable("Set to zero")) value = 0.0f;
+                if (ImGui::Selectable("Set to PI")) value = 3.1415f;
+                ImGui::PushItemWidth(-1);
+                ImGui::DragFloat("##Value", &value, 0.1f, 0.0f, 0.0f);
+                ImGui::PopItemWidth();
+                ImGui::EndPopup();
+            }
+
+            static char name[32] = "Label1";
+            char buf[64]; sprintf(buf, "Button: %s###Button", name); // ### operator override ID ignoring the preceding label
+            ImGui::Button(buf);
+            if (ImGui::BeginPopupContextItem()) // When used after an item that has an ID (here the Button), we can skip providing an ID to BeginPopupContextItem().
+            {
+                ImGui::Text("Edit name:");
+                ImGui::InputText("##edit", name, IM_ARRAYSIZE(name));
+                if (ImGui::Button("Close"))
+                    ImGui::CloseCurrentPopup();
+                ImGui::EndPopup();
+            }
+            ImGui::SameLine(); ImGui::Text("(<-- right-click here)");
+
+            ImGui::TreePop();
+        }
+
+        if (ImGui::TreeNode("Modals"))
+        {
+            ImGui::TextWrapped("Modal windows are like popups but the user cannot close them by clicking outside the window.");
+
+            if (ImGui::Button("Delete.."))
+                ImGui::OpenPopup("Delete?");
+            if (ImGui::BeginPopupModal("Delete?", NULL, ImGuiWindowFlags_AlwaysAutoResize))
+            {
+                ImGui::Text("All those beautiful files will be deleted.\nThis operation cannot be undone!\n\n");
+                ImGui::Separator();
+
+                //static int dummy_i = 0;
+                //ImGui::Combo("Combo", &dummy_i, "Delete\0Delete harder\0");
+
+                static bool dont_ask_me_next_time = false;
+                ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0,0));
+                ImGui::Checkbox("Don't ask me next time", &dont_ask_me_next_time);
+                ImGui::PopStyleVar();
+
+                if (ImGui::Button("OK", ImVec2(120,0))) { ImGui::CloseCurrentPopup(); }
+                ImGui::SetItemDefaultFocus();
+                ImGui::SameLine();
+                if (ImGui::Button("Cancel", ImVec2(120,0))) { ImGui::CloseCurrentPopup(); }
+                ImGui::EndPopup();
+            }
+
+            if (ImGui::Button("Stacked modals.."))
+                ImGui::OpenPopup("Stacked 1");
+            if (ImGui::BeginPopupModal("Stacked 1"))
+            {
+                ImGui::Text("Hello from Stacked The First\nUsing style.Colors[ImGuiCol_ModalWindowDimBg] behind it.");
+                static int item = 1;
+                ImGui::Combo("Combo", &item, "aaaa\0bbbb\0cccc\0dddd\0eeee\0\0");
+                static float color[4] = { 0.4f,0.7f,0.0f,0.5f };
+                ImGui::ColorEdit4("color", color);  // This is to test behavior of stacked regular popups over a modal
+
+                if (ImGui::Button("Add another modal.."))
+                    ImGui::OpenPopup("Stacked 2");
+                if (ImGui::BeginPopupModal("Stacked 2"))
+                {
+                    ImGui::Text("Hello from Stacked The Second!");
+                    if (ImGui::Button("Close"))
+                        ImGui::CloseCurrentPopup();
+                    ImGui::EndPopup();
+                }
+
+                if (ImGui::Button("Close"))
+                    ImGui::CloseCurrentPopup();
+                ImGui::EndPopup();
+            }
+
+            ImGui::TreePop();
+        }
+
+        if (ImGui::TreeNode("Menus inside a regular window"))
+        {
+            ImGui::TextWrapped("Below we are testing adding menu items to a regular window. It's rather unusual but should work!");
+            ImGui::Separator();
+            // NB: As a quirk in this very specific example, we want to differentiate the parent of this menu from the parent of the various popup menus above.
+            // To do so we are encloding the items in a PushID()/PopID() block to make them two different menusets. If we don't, opening any popup above and hovering our menu here
+            // would open it. This is because once a menu is active, we allow to switch to a sibling menu by just hovering on it, which is the desired behavior for regular menus.
+            ImGui::PushID("foo");
+            ImGui::MenuItem("Menu item", "CTRL+M");
+            if (ImGui::BeginMenu("Menu inside a regular window"))
+            {
+                ShowExampleMenuFile();
+                ImGui::EndMenu();
+            }
+            ImGui::PopID();
+            ImGui::Separator();
+            ImGui::TreePop();
+        }
+    }
+
+    if (ImGui::CollapsingHeader("Columns"))
+    {
+        ImGui::PushID("Columns");
+
+        // Basic columns
+        if (ImGui::TreeNode("Basic"))
+        {
+            ImGui::Text("Without border:");
+            ImGui::Columns(3, "mycolumns3", false);  // 3-ways, no border
+            ImGui::Separator();
+            for (int n = 0; n < 14; n++)
+            {
+                char label[32];
+                sprintf(label, "Item %d", n);
+                if (ImGui::Selectable(label)) {}
+                //if (ImGui::Button(label, ImVec2(-1,0))) {}
+                ImGui::NextColumn();
+            }
+            ImGui::Columns(1);
+            ImGui::Separator();
+
+            ImGui::Text("With border:");
+            ImGui::Columns(4, "mycolumns"); // 4-ways, with border
+            ImGui::Separator();
+            ImGui::Text("ID"); ImGui::NextColumn();
+            ImGui::Text("Name"); ImGui::NextColumn();
+            ImGui::Text("Path"); ImGui::NextColumn();
+            ImGui::Text("Hovered"); ImGui::NextColumn();
+            ImGui::Separator();
+            const char* names[3] = { "One", "Two", "Three" };
+            const char* paths[3] = { "/path/one", "/path/two", "/path/three" };
+            static int selected = -1;
+            for (int i = 0; i < 3; i++)
+            {
+                char label[32];
+                sprintf(label, "%04d", i);
+                if (ImGui::Selectable(label, selected == i, ImGuiSelectableFlags_SpanAllColumns))
+                    selected = i;
+                bool hovered = ImGui::IsItemHovered();
+                ImGui::NextColumn();
+                ImGui::Text(names[i]); ImGui::NextColumn();
+                ImGui::Text(paths[i]); ImGui::NextColumn();
+                ImGui::Text("%d", hovered); ImGui::NextColumn();
+            }
+            ImGui::Columns(1);
+            ImGui::Separator();
+            ImGui::TreePop();
+        }
+
+        // Create multiple items in a same cell before switching to next column
+        if (ImGui::TreeNode("Mixed items"))
+        {
+            ImGui::Columns(3, "mixed");
+            ImGui::Separator();
+
+            ImGui::Text("Hello");
+            ImGui::Button("Banana");
+            ImGui::NextColumn();
+
+            ImGui::Text("ImGui");
+            ImGui::Button("Apple");
+            static float foo = 1.0f;
+            ImGui::InputFloat("red", &foo, 0.05f, 0, "%.3f");
+            ImGui::Text("An extra line here.");
+            ImGui::NextColumn();
+
+                ImGui::Text("Sailor");
+            ImGui::Button("Corniflower");
+            static float bar = 1.0f;
+            ImGui::InputFloat("blue", &bar, 0.05f, 0, "%.3f");
+            ImGui::NextColumn();
+
+            if (ImGui::CollapsingHeader("Category A")) { ImGui::Text("Blah blah blah"); } ImGui::NextColumn();
+            if (ImGui::CollapsingHeader("Category B")) { ImGui::Text("Blah blah blah"); } ImGui::NextColumn();
+            if (ImGui::CollapsingHeader("Category C")) { ImGui::Text("Blah blah blah"); } ImGui::NextColumn();
+            ImGui::Columns(1);
+            ImGui::Separator();
+            ImGui::TreePop();
+        }
+
+        // Word wrapping
+        if (ImGui::TreeNode("Word-wrapping"))
+        {
+            ImGui::Columns(2, "word-wrapping");
+            ImGui::Separator();
+            ImGui::TextWrapped("The quick brown fox jumps over the lazy dog.");
+            ImGui::TextWrapped("Hello Left");
+            ImGui::NextColumn();
+            ImGui::TextWrapped("The quick brown fox jumps over the lazy dog.");
+            ImGui::TextWrapped("Hello Right");
+            ImGui::Columns(1);
+            ImGui::Separator();
+            ImGui::TreePop();
+        }
+
+        if (ImGui::TreeNode("Borders"))
+        {
+            // NB: Future columns API should allow automatic horizontal borders.
+            static bool h_borders = true;
+            static bool v_borders = true;
+            ImGui::Checkbox("horizontal", &h_borders);
+            ImGui::SameLine();
+            ImGui::Checkbox("vertical", &v_borders);
+            ImGui::Columns(4, NULL, v_borders);
+            for (int i = 0; i < 4*3; i++)
+            {
+                if (h_borders && ImGui::GetColumnIndex() == 0)
+                    ImGui::Separator();
+                ImGui::Text("%c%c%c", 'a'+i, 'a'+i, 'a'+i);
+                ImGui::Text("Width %.2f\nOffset %.2f", ImGui::GetColumnWidth(), ImGui::GetColumnOffset());
+                ImGui::NextColumn();
+            }
+            ImGui::Columns(1);
+            if (h_borders)
+                ImGui::Separator();
+            ImGui::TreePop();
+        }
+
+        // Scrolling columns
+        /*
+        if (ImGui::TreeNode("Vertical Scrolling"))
+        {
+            ImGui::BeginChild("##header", ImVec2(0, ImGui::GetTextLineHeightWithSpacing()+ImGui::GetStyle().ItemSpacing.y));
+            ImGui::Columns(3);
+            ImGui::Text("ID"); ImGui::NextColumn();
+            ImGui::Text("Name"); ImGui::NextColumn();
+            ImGui::Text("Path"); ImGui::NextColumn();
+            ImGui::Columns(1);
+            ImGui::Separator();
+            ImGui::EndChild();
+            ImGui::BeginChild("##scrollingregion", ImVec2(0, 60));
+            ImGui::Columns(3);
+            for (int i = 0; i < 10; i++)
+            {
+                ImGui::Text("%04d", i); ImGui::NextColumn();
+                ImGui::Text("Foobar"); ImGui::NextColumn();
+                ImGui::Text("/path/foobar/%04d/", i); ImGui::NextColumn();
+            }
+            ImGui::Columns(1);
+            ImGui::EndChild();
+            ImGui::TreePop();
+        }
+        */
+
+        if (ImGui::TreeNode("Horizontal Scrolling"))
+        {
+            ImGui::SetNextWindowContentSize(ImVec2(1500.0f, 0.0f));
+            ImGui::BeginChild("##ScrollingRegion", ImVec2(0, ImGui::GetFontSize() * 20), false, ImGuiWindowFlags_HorizontalScrollbar);
+            ImGui::Columns(10);
+            int ITEMS_COUNT = 2000;
+            ImGuiListClipper clipper(ITEMS_COUNT);  // Also demonstrate using the clipper for large list
+            while (clipper.Step())
+            {
+                for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
+                    for (int j = 0; j < 10; j++)
+                    {
+                        ImGui::Text("Line %d Column %d...", i, j);
+                        ImGui::NextColumn();
+                    }
+            }
+            ImGui::Columns(1);
+            ImGui::EndChild();
+            ImGui::TreePop();
+        }
+
+        bool node_open = ImGui::TreeNode("Tree within single cell");
+        ImGui::SameLine(); ShowHelpMarker("NB: Tree node must be poped before ending the cell. There's no storage of state per-cell.");
+        if (node_open)
+        {
+            ImGui::Columns(2, "tree items");
+            ImGui::Separator();
+            if (ImGui::TreeNode("Hello")) { ImGui::BulletText("Sailor"); ImGui::TreePop(); } ImGui::NextColumn();
+            if (ImGui::TreeNode("Bonjour")) { ImGui::BulletText("Marin"); ImGui::TreePop(); } ImGui::NextColumn();
+            ImGui::Columns(1);
+            ImGui::Separator();
+            ImGui::TreePop();
+        }
+        ImGui::PopID();
+    }
+
+    if (ImGui::CollapsingHeader("Filtering"))
+    {
+        static ImGuiTextFilter filter;
+        ImGui::Text("Filter usage:\n"
+                    "  \"\"         display all lines\n"
+                    "  \"xxx\"      display lines containing \"xxx\"\n"
+                    "  \"xxx,yyy\"  display lines containing \"xxx\" or \"yyy\"\n"
+                    "  \"-xxx\"     hide lines containing \"xxx\"");
+        filter.Draw();
+        const char* lines[] = { "aaa1.c", "bbb1.c", "ccc1.c", "aaa2.cpp", "bbb2.cpp", "ccc2.cpp", "abc.h", "hello, world" };
+        for (int i = 0; i < IM_ARRAYSIZE(lines); i++)
+            if (filter.PassFilter(lines[i]))
+                ImGui::BulletText("%s", lines[i]);
+    }
+
+    if (ImGui::CollapsingHeader("Inputs, Navigation & Focus"))
+    {
+        ImGuiIO& io = ImGui::GetIO();
+
+        ImGui::Text("WantCaptureMouse: %d", io.WantCaptureMouse);
+        ImGui::Text("WantCaptureKeyboard: %d", io.WantCaptureKeyboard);
+        ImGui::Text("WantTextInput: %d", io.WantTextInput);
+        ImGui::Text("WantSetMousePos: %d", io.WantSetMousePos);
+        ImGui::Text("NavActive: %d, NavVisible: %d", io.NavActive, io.NavVisible);
+
+        if (ImGui::TreeNode("Keyboard, Mouse & Navigation State"))
+        {
+            if (ImGui::IsMousePosValid())
+                ImGui::Text("Mouse pos: (%g, %g)", io.MousePos.x, io.MousePos.y);
+            else
+                ImGui::Text("Mouse pos: <INVALID>");
+            ImGui::Text("Mouse delta: (%g, %g)", io.MouseDelta.x, io.MouseDelta.y);
+            ImGui::Text("Mouse down:");     for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (io.MouseDownDuration[i] >= 0.0f)   { ImGui::SameLine(); ImGui::Text("b%d (%.02f secs)", i, io.MouseDownDuration[i]); }
+            ImGui::Text("Mouse clicked:");  for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (ImGui::IsMouseClicked(i))          { ImGui::SameLine(); ImGui::Text("b%d", i); }
+            ImGui::Text("Mouse dbl-clicked:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (ImGui::IsMouseDoubleClicked(i)) { ImGui::SameLine(); ImGui::Text("b%d", i); }
+            ImGui::Text("Mouse released:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (ImGui::IsMouseReleased(i))         { ImGui::SameLine(); ImGui::Text("b%d", i); }
+            ImGui::Text("Mouse wheel: %.1f", io.MouseWheel);
+
+            ImGui::Text("Keys down:");      for (int i = 0; i < IM_ARRAYSIZE(io.KeysDown); i++) if (io.KeysDownDuration[i] >= 0.0f)     { ImGui::SameLine(); ImGui::Text("%d (%.02f secs)", i, io.KeysDownDuration[i]); }
+            ImGui::Text("Keys pressed:");   for (int i = 0; i < IM_ARRAYSIZE(io.KeysDown); i++) if (ImGui::IsKeyPressed(i))             { ImGui::SameLine(); ImGui::Text("%d", i); }
+            ImGui::Text("Keys release:");   for (int i = 0; i < IM_ARRAYSIZE(io.KeysDown); i++) if (ImGui::IsKeyReleased(i))            { ImGui::SameLine(); ImGui::Text("%d", i); }
+            ImGui::Text("Keys mods: %s%s%s%s", io.KeyCtrl ? "CTRL " : "", io.KeyShift ? "SHIFT " : "", io.KeyAlt ? "ALT " : "", io.KeySuper ? "SUPER " : "");
+
+            ImGui::Text("NavInputs down:"); for (int i = 0; i < IM_ARRAYSIZE(io.NavInputs); i++) if (io.NavInputs[i] > 0.0f)                    { ImGui::SameLine(); ImGui::Text("[%d] %.2f", i, io.NavInputs[i]); }
+            ImGui::Text("NavInputs pressed:"); for (int i = 0; i < IM_ARRAYSIZE(io.NavInputs); i++) if (io.NavInputsDownDuration[i] == 0.0f)    { ImGui::SameLine(); ImGui::Text("[%d]", i); }
+            ImGui::Text("NavInputs duration:"); for (int i = 0; i < IM_ARRAYSIZE(io.NavInputs); i++) if (io.NavInputsDownDuration[i] >= 0.0f)   { ImGui::SameLine(); ImGui::Text("[%d] %.2f", i, io.NavInputsDownDuration[i]); }
+
+            ImGui::Button("Hovering me sets the\nkeyboard capture flag");
+            if (ImGui::IsItemHovered())
+                ImGui::CaptureKeyboardFromApp(true);
+            ImGui::SameLine();
+            ImGui::Button("Holding me clears the\nthe keyboard capture flag");
+            if (ImGui::IsItemActive())
+                ImGui::CaptureKeyboardFromApp(false);
+
+            ImGui::TreePop();
+        }
+
+        if (ImGui::TreeNode("Tabbing"))
+        {
+            ImGui::Text("Use TAB/SHIFT+TAB to cycle through keyboard editable fields.");
+            static char buf[32] = "dummy";
+            ImGui::InputText("1", buf, IM_ARRAYSIZE(buf));
+            ImGui::InputText("2", buf, IM_ARRAYSIZE(buf));
+            ImGui::InputText("3", buf, IM_ARRAYSIZE(buf));
+            ImGui::PushAllowKeyboardFocus(false);
+            ImGui::InputText("4 (tab skip)", buf, IM_ARRAYSIZE(buf));
+            //ImGui::SameLine(); ShowHelperMarker("Use ImGui::PushAllowKeyboardFocus(bool)\nto disable tabbing through certain widgets.");
+            ImGui::PopAllowKeyboardFocus();
+            ImGui::InputText("5", buf, IM_ARRAYSIZE(buf));
+            ImGui::TreePop();
+        }
+
+        if (ImGui::TreeNode("Focus from code"))
+        {
+            bool focus_1 = ImGui::Button("Focus on 1"); ImGui::SameLine();
+            bool focus_2 = ImGui::Button("Focus on 2"); ImGui::SameLine();
+            bool focus_3 = ImGui::Button("Focus on 3");
+            int has_focus = 0;
+            static char buf[128] = "click on a button to set focus";
+
+            if (focus_1) ImGui::SetKeyboardFocusHere();
+            ImGui::InputText("1", buf, IM_ARRAYSIZE(buf));
+            if (ImGui::IsItemActive()) has_focus = 1;
+
+            if (focus_2) ImGui::SetKeyboardFocusHere();
+            ImGui::InputText("2", buf, IM_ARRAYSIZE(buf));
+            if (ImGui::IsItemActive()) has_focus = 2;
+
+            ImGui::PushAllowKeyboardFocus(false);
+            if (focus_3) ImGui::SetKeyboardFocusHere();
+            ImGui::InputText("3 (tab skip)", buf, IM_ARRAYSIZE(buf));
+            if (ImGui::IsItemActive()) has_focus = 3;
+            ImGui::PopAllowKeyboardFocus();
+
+            if (has_focus)
+                ImGui::Text("Item with focus: %d", has_focus);
+            else
+                ImGui::Text("Item with focus: <none>");
+
+            // Use >= 0 parameter to SetKeyboardFocusHere() to focus an upcoming item
+            static float f3[3] = { 0.0f, 0.0f, 0.0f };
+            int focus_ahead = -1;
+            if (ImGui::Button("Focus on X")) focus_ahead = 0; ImGui::SameLine();
+            if (ImGui::Button("Focus on Y")) focus_ahead = 1; ImGui::SameLine();
+            if (ImGui::Button("Focus on Z")) focus_ahead = 2;
+            if (focus_ahead != -1) ImGui::SetKeyboardFocusHere(focus_ahead);
+            ImGui::SliderFloat3("Float3", &f3[0], 0.0f, 1.0f);
+
+            ImGui::TextWrapped("NB: Cursor & selection are preserved when refocusing last used item in code.");
+            ImGui::TreePop();
+        }
+
+        if (ImGui::TreeNode("Dragging"))
+        {
+            ImGui::TextWrapped("You can use ImGui::GetMouseDragDelta(0) to query for the dragged amount on any widget.");
+            for (int button = 0; button < 3; button++)
+                ImGui::Text("IsMouseDragging(%d):\n  w/ default threshold: %d,\n  w/ zero threshold: %d\n  w/ large threshold: %d",
+                    button, ImGui::IsMouseDragging(button), ImGui::IsMouseDragging(button, 0.0f), ImGui::IsMouseDragging(button, 20.0f));
+            ImGui::Button("Drag Me");
+            if (ImGui::IsItemActive())
+            {
+                // Draw a line between the button and the mouse cursor
+                ImDrawList* draw_list = ImGui::GetWindowDrawList();
+                draw_list->PushClipRectFullScreen();
+                draw_list->AddLine(io.MouseClickedPos[0], io.MousePos, ImGui::GetColorU32(ImGuiCol_Button), 4.0f);
+                draw_list->PopClipRect();
+
+                // Drag operations gets "unlocked" when the mouse has moved past a certain threshold (the default threshold is stored in io.MouseDragThreshold)
+                // You can request a lower or higher threshold using the second parameter of IsMouseDragging() and GetMouseDragDelta()
+                ImVec2 value_raw = ImGui::GetMouseDragDelta(0, 0.0f);
+                ImVec2 value_with_lock_threshold = ImGui::GetMouseDragDelta(0);
+                ImVec2 mouse_delta = io.MouseDelta;
+                ImGui::SameLine(); ImGui::Text("Raw (%.1f, %.1f), WithLockThresold (%.1f, %.1f), MouseDelta (%.1f, %.1f)", value_raw.x, value_raw.y, value_with_lock_threshold.x, value_with_lock_threshold.y, mouse_delta.x, mouse_delta.y);
+            }
+            ImGui::TreePop();
+        }
+
+        if (ImGui::TreeNode("Mouse cursors"))
+        {
+            const char* mouse_cursors_names[] = { "Arrow", "TextInput", "Move", "ResizeNS", "ResizeEW", "ResizeNESW", "ResizeNWSE", "Hand" };
+            IM_ASSERT(IM_ARRAYSIZE(mouse_cursors_names) == ImGuiMouseCursor_COUNT);
+
+            ImGui::Text("Current mouse cursor = %d: %s", ImGui::GetMouseCursor(), mouse_cursors_names[ImGui::GetMouseCursor()]);
+            ImGui::Text("Hover to see mouse cursors:");
+            ImGui::SameLine(); ShowHelpMarker("Your application can render a different mouse cursor based on what ImGui::GetMouseCursor() returns. If software cursor rendering (io.MouseDrawCursor) is set ImGui will draw the right cursor for you, otherwise your backend needs to handle it.");
+            for (int i = 0; i < ImGuiMouseCursor_COUNT; i++)
+            {
+                char label[32];
+                sprintf(label, "Mouse cursor %d: %s", i, mouse_cursors_names[i]);
+                ImGui::Bullet(); ImGui::Selectable(label, false);
+                if (ImGui::IsItemHovered() || ImGui::IsItemFocused())
+                    ImGui::SetMouseCursor(i);
+            }
+            ImGui::TreePop();
+        }
+    }
+
+    // End of ShowDemoWindow()
+    ImGui::End();
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] Style Editor / ShowStyleEditor()
+//-----------------------------------------------------------------------------
+
+// Demo helper function to select among default colors. See ShowStyleEditor() for more advanced options.
+// Here we use the simplified Combo() api that packs items into a single literal string. Useful for quick combo boxes where the choices are known locally.
+bool ImGui::ShowStyleSelector(const char* label)
+{
+    static int style_idx = -1;
+    if (ImGui::Combo(label, &style_idx, "Classic\0Dark\0Light\0"))
+    {
+        switch (style_idx)
+        {
+        case 0: ImGui::StyleColorsClassic(); break;
+        case 1: ImGui::StyleColorsDark(); break;
+        case 2: ImGui::StyleColorsLight(); break;
+        }
+        return true;
+    }
+    return false;
+}
+
+// Demo helper function to select among loaded fonts.
+// Here we use the regular BeginCombo()/EndCombo() api which is more the more flexible one.
+void ImGui::ShowFontSelector(const char* label)
+{
+    ImGuiIO& io = ImGui::GetIO();
+    ImFont* font_current = ImGui::GetFont();
+    if (ImGui::BeginCombo(label, font_current->GetDebugName()))
+    {
+        for (int n = 0; n < io.Fonts->Fonts.Size; n++)
+            if (ImGui::Selectable(io.Fonts->Fonts[n]->GetDebugName(), io.Fonts->Fonts[n] == font_current))
+                io.FontDefault = io.Fonts->Fonts[n];
+        ImGui::EndCombo();
+    }
+    ImGui::SameLine();
+    ShowHelpMarker(
+        "- Load additional fonts with io.Fonts->AddFontFromFileTTF().\n"
+        "- The font atlas is built when calling io.Fonts->GetTexDataAsXXXX() or io.Fonts->Build().\n"
+        "- Read FAQ and documentation in misc/fonts/ for more details.\n"
+        "- If you need to add/remove fonts at runtime (e.g. for DPI change), do it before calling NewFrame().");
+}
+
+void ImGui::ShowStyleEditor(ImGuiStyle* ref)
+{
+    // You can pass in a reference ImGuiStyle structure to compare to, revert to and save to (else it compares to an internally stored reference)
+    ImGuiStyle& style = ImGui::GetStyle();
+    static ImGuiStyle ref_saved_style;
+
+    // Default to using internal storage as reference
+    static bool init = true;
+    if (init && ref == NULL)
+        ref_saved_style = style;
+    init = false;
+    if (ref == NULL)
+        ref = &ref_saved_style;
+
+    ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.50f);
+
+    if (ImGui::ShowStyleSelector("Colors##Selector"))
+        ref_saved_style = style;
+    ImGui::ShowFontSelector("Fonts##Selector");
+
+    // Simplified Settings
+    if (ImGui::SliderFloat("FrameRounding", &style.FrameRounding, 0.0f, 12.0f, "%.0f"))
+        style.GrabRounding = style.FrameRounding; // Make GrabRounding always the same value as FrameRounding
+    { bool window_border = (style.WindowBorderSize > 0.0f); if (ImGui::Checkbox("WindowBorder", &window_border)) style.WindowBorderSize = window_border ? 1.0f : 0.0f; }
+    ImGui::SameLine();
+    { bool frame_border = (style.FrameBorderSize > 0.0f); if (ImGui::Checkbox("FrameBorder", &frame_border)) style.FrameBorderSize = frame_border ? 1.0f : 0.0f; }
+    ImGui::SameLine();
+    { bool popup_border = (style.PopupBorderSize > 0.0f); if (ImGui::Checkbox("PopupBorder", &popup_border)) style.PopupBorderSize = popup_border ? 1.0f : 0.0f; }
+
+    // Save/Revert button
+    if (ImGui::Button("Save Ref"))
+        *ref = ref_saved_style = style;
+    ImGui::SameLine();
+    if (ImGui::Button("Revert Ref"))
+        style = *ref;
+    ImGui::SameLine();
+    ShowHelpMarker("Save/Revert in local non-persistent storage. Default Colors definition are not affected. Use \"Export Colors\" below to save them somewhere.");
+
+    if (ImGui::TreeNode("Rendering"))
+    {
+        ImGui::Checkbox("Anti-aliased lines", &style.AntiAliasedLines); ImGui::SameLine(); ShowHelpMarker("When disabling anti-aliasing lines, you'll probably want to disable borders in your style as well.");
+        ImGui::Checkbox("Anti-aliased fill", &style.AntiAliasedFill);
+        ImGui::PushItemWidth(100);
+        ImGui::DragFloat("Curve Tessellation Tolerance", &style.CurveTessellationTol, 0.02f, 0.10f, FLT_MAX, NULL, 2.0f);
+        if (style.CurveTessellationTol < 0.0f) style.CurveTessellationTol = 0.10f;
+        ImGui::DragFloat("Global Alpha", &style.Alpha, 0.005f, 0.20f, 1.0f, "%.2f"); // Not exposing zero here so user doesn't "lose" the UI (zero alpha clips all widgets). But application code could have a toggle to switch between zero and non-zero.
+        ImGui::PopItemWidth();
+        ImGui::TreePop();
+    }
+
+    if (ImGui::TreeNode("Settings"))
+    {
+        ImGui::SliderFloat2("WindowPadding", (float*)&style.WindowPadding, 0.0f, 20.0f, "%.0f");
+        ImGui::SliderFloat("PopupRounding", &style.PopupRounding, 0.0f, 16.0f, "%.0f");
+        ImGui::SliderFloat2("FramePadding", (float*)&style.FramePadding, 0.0f, 20.0f, "%.0f");
+        ImGui::SliderFloat2("ItemSpacing", (float*)&style.ItemSpacing, 0.0f, 20.0f, "%.0f");
+        ImGui::SliderFloat2("ItemInnerSpacing", (float*)&style.ItemInnerSpacing, 0.0f, 20.0f, "%.0f");
+        ImGui::SliderFloat2("TouchExtraPadding", (float*)&style.TouchExtraPadding, 0.0f, 10.0f, "%.0f");
+        ImGui::SliderFloat("IndentSpacing", &style.IndentSpacing, 0.0f, 30.0f, "%.0f");
+        ImGui::SliderFloat("ScrollbarSize", &style.ScrollbarSize, 1.0f, 20.0f, "%.0f");
+        ImGui::SliderFloat("GrabMinSize", &style.GrabMinSize, 1.0f, 20.0f, "%.0f");
+        ImGui::Text("BorderSize");
+        ImGui::SliderFloat("WindowBorderSize", &style.WindowBorderSize, 0.0f, 1.0f, "%.0f");
+        ImGui::SliderFloat("ChildBorderSize", &style.ChildBorderSize, 0.0f, 1.0f, "%.0f");
+        ImGui::SliderFloat("PopupBorderSize", &style.PopupBorderSize, 0.0f, 1.0f, "%.0f");
+        ImGui::SliderFloat("FrameBorderSize", &style.FrameBorderSize, 0.0f, 1.0f, "%.0f");
+        ImGui::Text("Rounding");
+        ImGui::SliderFloat("WindowRounding", &style.WindowRounding, 0.0f, 14.0f, "%.0f");
+        ImGui::SliderFloat("ChildRounding", &style.ChildRounding, 0.0f, 16.0f, "%.0f");
+        ImGui::SliderFloat("FrameRounding", &style.FrameRounding, 0.0f, 12.0f, "%.0f");
+        ImGui::SliderFloat("ScrollbarRounding", &style.ScrollbarRounding, 0.0f, 12.0f, "%.0f");
+        ImGui::SliderFloat("GrabRounding", &style.GrabRounding, 0.0f, 12.0f, "%.0f");
+        ImGui::Text("Alignment");
+        ImGui::SliderFloat2("WindowTitleAlign", (float*)&style.WindowTitleAlign, 0.0f, 1.0f, "%.2f");
+        ImGui::SliderFloat2("ButtonTextAlign", (float*)&style.ButtonTextAlign, 0.0f, 1.0f, "%.2f"); ImGui::SameLine(); ShowHelpMarker("Alignment applies when a button is larger than its text content.");
+        ImGui::Text("Safe Area Padding"); ImGui::SameLine(); ShowHelpMarker("Adjust if you cannot see the edges of your screen (e.g. on a TV where scaling has not been configured).");
+        ImGui::SliderFloat2("DisplaySafeAreaPadding", (float*)&style.DisplaySafeAreaPadding, 0.0f, 30.0f, "%.0f");
+        ImGui::TreePop();
+    }
+
+    if (ImGui::TreeNode("Colors"))
+    {
+        static int output_dest = 0;
+        static bool output_only_modified = true;
+        if (ImGui::Button("Export Unsaved"))
+        {
+            if (output_dest == 0)
+                ImGui::LogToClipboard();
+            else
+                ImGui::LogToTTY();
+            ImGui::LogText("ImVec4* colors = ImGui::GetStyle().Colors;" IM_NEWLINE);
+            for (int i = 0; i < ImGuiCol_COUNT; i++)
+            {
+                const ImVec4& col = style.Colors[i];
+                const char* name = ImGui::GetStyleColorName(i);
+                if (!output_only_modified || memcmp(&col, &ref->Colors[i], sizeof(ImVec4)) != 0)
+                    ImGui::LogText("colors[ImGuiCol_%s]%*s= ImVec4(%.2ff, %.2ff, %.2ff, %.2ff);" IM_NEWLINE, name, 23-(int)strlen(name), "", col.x, col.y, col.z, col.w);
+            }
+            ImGui::LogFinish();
+        }
+        ImGui::SameLine(); ImGui::PushItemWidth(120); ImGui::Combo("##output_type", &output_dest, "To Clipboard\0To TTY\0"); ImGui::PopItemWidth();
+        ImGui::SameLine(); ImGui::Checkbox("Only Modified Colors", &output_only_modified);
+
+        ImGui::Text("Tip: Left-click on colored square to open color picker,\nRight-click to open edit options menu.");
+
+        static ImGuiTextFilter filter;
+        filter.Draw("Filter colors", 200);
+
+        static ImGuiColorEditFlags alpha_flags = 0;
+        ImGui::RadioButton("Opaque", &alpha_flags, 0); ImGui::SameLine();
+        ImGui::RadioButton("Alpha", &alpha_flags, ImGuiColorEditFlags_AlphaPreview); ImGui::SameLine();
+        ImGui::RadioButton("Both", &alpha_flags, ImGuiColorEditFlags_AlphaPreviewHalf);
+
+        ImGui::BeginChild("#colors", ImVec2(0, 300), true, ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar | ImGuiWindowFlags_NavFlattened);
+        ImGui::PushItemWidth(-160);
+        for (int i = 0; i < ImGuiCol_COUNT; i++)
+        {
+            const char* name = ImGui::GetStyleColorName(i);
+            if (!filter.PassFilter(name))
+                continue;
+            ImGui::PushID(i);
+            ImGui::ColorEdit4("##color", (float*)&style.Colors[i], ImGuiColorEditFlags_AlphaBar | alpha_flags);
+            if (memcmp(&style.Colors[i], &ref->Colors[i], sizeof(ImVec4)) != 0)
+            {
+                // Tips: in a real user application, you may want to merge and use an icon font into the main font, so instead of "Save"/"Revert" you'd use icons.
+                // Read the FAQ and misc/fonts/README.txt about using icon fonts. It's really easy and super convenient!
+                ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); if (ImGui::Button("Save")) ref->Colors[i] = style.Colors[i];
+                ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); if (ImGui::Button("Revert")) style.Colors[i] = ref->Colors[i];
+            }
+            ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);
+            ImGui::TextUnformatted(name);
+            ImGui::PopID();
+        }
+        ImGui::PopItemWidth();
+        ImGui::EndChild();
+
+        ImGui::TreePop();
+    }
+
+    bool fonts_opened = ImGui::TreeNode("Fonts", "Fonts (%d)", ImGui::GetIO().Fonts->Fonts.Size);
+    if (fonts_opened)
+    {
+        ImFontAtlas* atlas = ImGui::GetIO().Fonts;
+        if (ImGui::TreeNode("Atlas texture", "Atlas texture (%dx%d pixels)", atlas->TexWidth, atlas->TexHeight))
+        {
+            ImGui::Image(atlas->TexID, ImVec2((float)atlas->TexWidth, (float)atlas->TexHeight), ImVec2(0,0), ImVec2(1,1), ImColor(255,255,255,255), ImColor(255,255,255,128));
+            ImGui::TreePop();
+        }
+        ImGui::PushItemWidth(100);
+        for (int i = 0; i < atlas->Fonts.Size; i++)
+        {
+            ImFont* font = atlas->Fonts[i];
+            ImGui::PushID(font);
+            bool font_details_opened = ImGui::TreeNode(font, "Font %d: \'%s\', %.2f px, %d glyphs", i, font->ConfigData ? font->ConfigData[0].Name : "", font->FontSize, font->Glyphs.Size);
+            ImGui::SameLine(); if (ImGui::SmallButton("Set as default")) ImGui::GetIO().FontDefault = font;
+            if (font_details_opened)
+            {
+                ImGui::PushFont(font);
+                ImGui::Text("The quick brown fox jumps over the lazy dog");
+                ImGui::PopFont();
+                ImGui::DragFloat("Font scale", &font->Scale, 0.005f, 0.3f, 2.0f, "%.1f");   // Scale only this font
+                ImGui::SameLine(); ShowHelpMarker("Note than the default embedded font is NOT meant to be scaled.\n\nFont are currently rendered into bitmaps at a given size at the time of building the atlas. You may oversample them to get some flexibility with scaling. You can also render at multiple sizes and select which one to use at runtime.\n\n(Glimmer of hope: the atlas system should hopefully be rewritten in the future to make scaling more natural and automatic.)");
+                ImGui::InputFloat("Font offset", &font->DisplayOffset.y, 1, 1, "%.0f");
+                ImGui::Text("Ascent: %f, Descent: %f, Height: %f", font->Ascent, font->Descent, font->Ascent - font->Descent);
+                ImGui::Text("Fallback character: '%c' (%d)", font->FallbackChar, font->FallbackChar);
+                ImGui::Text("Texture surface: %d pixels (approx) ~ %dx%d", font->MetricsTotalSurface, (int)sqrtf((float)font->MetricsTotalSurface), (int)sqrtf((float)font->MetricsTotalSurface));
+                for (int config_i = 0; config_i < font->ConfigDataCount; config_i++)
+                    if (ImFontConfig* cfg = &font->ConfigData[config_i])
+                        ImGui::BulletText("Input %d: \'%s\', Oversample: (%d,%d), PixelSnapH: %d", config_i, cfg->Name, cfg->OversampleH, cfg->OversampleV, cfg->PixelSnapH);
+                if (ImGui::TreeNode("Glyphs", "Glyphs (%d)", font->Glyphs.Size))
+                {
+                    // Display all glyphs of the fonts in separate pages of 256 characters
+                    for (int base = 0; base < 0x10000; base += 256)
+                    {
+                        int count = 0;
+                        for (int n = 0; n < 256; n++)
+                            count += font->FindGlyphNoFallback((ImWchar)(base + n)) ? 1 : 0;
+                        if (count > 0 && ImGui::TreeNode((void*)(intptr_t)base, "U+%04X..U+%04X (%d %s)", base, base+255, count, count > 1 ? "glyphs" : "glyph"))
+                        {
+                            float cell_size = font->FontSize * 1;
+                            float cell_spacing = style.ItemSpacing.y;
+                            ImVec2 base_pos = ImGui::GetCursorScreenPos();
+                            ImDrawList* draw_list = ImGui::GetWindowDrawList();
+                            for (int n = 0; n < 256; n++)
+                            {
+                                ImVec2 cell_p1(base_pos.x + (n % 16) * (cell_size + cell_spacing), base_pos.y + (n / 16) * (cell_size + cell_spacing));
+                                ImVec2 cell_p2(cell_p1.x + cell_size, cell_p1.y + cell_size);
+                                const ImFontGlyph* glyph = font->FindGlyphNoFallback((ImWchar)(base+n));
+                                draw_list->AddRect(cell_p1, cell_p2, glyph ? IM_COL32(255,255,255,100) : IM_COL32(255,255,255,50));
+                                if (glyph)
+                                    font->RenderChar(draw_list, cell_size, cell_p1, ImGui::GetColorU32(ImGuiCol_Text), (ImWchar)(base+n)); // We use ImFont::RenderChar as a shortcut because we don't have UTF-8 conversion functions available to generate a string.
+                                if (glyph && ImGui::IsMouseHoveringRect(cell_p1, cell_p2))
+                                {
+                                    ImGui::BeginTooltip();
+                                    ImGui::Text("Codepoint: U+%04X", base+n);
+                                    ImGui::Separator();
+                                    ImGui::Text("AdvanceX: %.1f", glyph->AdvanceX);
+                                    ImGui::Text("Pos: (%.2f,%.2f)->(%.2f,%.2f)", glyph->X0, glyph->Y0, glyph->X1, glyph->Y1);
+                                    ImGui::Text("UV: (%.3f,%.3f)->(%.3f,%.3f)", glyph->U0, glyph->V0, glyph->U1, glyph->V1);
+                                    ImGui::EndTooltip();
+                                }
+                            }
+                            ImGui::Dummy(ImVec2((cell_size + cell_spacing) * 16, (cell_size + cell_spacing) * 16));
+                            ImGui::TreePop();
+                        }
+                    }
+                    ImGui::TreePop();
+                }
+                ImGui::TreePop();
+            }
+            ImGui::PopID();
+        }
+        static float window_scale = 1.0f;
+        ImGui::DragFloat("this window scale", &window_scale, 0.005f, 0.3f, 2.0f, "%.1f");              // scale only this window
+        ImGui::DragFloat("global scale", &ImGui::GetIO().FontGlobalScale, 0.005f, 0.3f, 2.0f, "%.1f"); // scale everything
+        ImGui::PopItemWidth();
+        ImGui::SetWindowFontScale(window_scale);
+        ImGui::TreePop();
+    }
+
+    ImGui::PopItemWidth();
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] Example App: Main Menu Bar / ShowExampleAppMainMenuBar()
+//-----------------------------------------------------------------------------
+
+// Demonstrate creating a fullscreen menu bar and populating it.
+static void ShowExampleAppMainMenuBar()
+{
+    if (ImGui::BeginMainMenuBar())
+    {
+        if (ImGui::BeginMenu("File"))
+        {
+            ShowExampleMenuFile();
+            ImGui::EndMenu();
+        }
+        if (ImGui::BeginMenu("Edit"))
+        {
+            if (ImGui::MenuItem("Undo", "CTRL+Z")) {}
+            if (ImGui::MenuItem("Redo", "CTRL+Y", false, false)) {}  // Disabled item
+            ImGui::Separator();
+            if (ImGui::MenuItem("Cut", "CTRL+X")) {}
+            if (ImGui::MenuItem("Copy", "CTRL+C")) {}
+            if (ImGui::MenuItem("Paste", "CTRL+V")) {}
+            ImGui::EndMenu();
+        }
+        ImGui::EndMainMenuBar();
+    }
+}
+
+static void ShowExampleMenuFile()
+{
+    ImGui::MenuItem("(dummy menu)", NULL, false, false);
+    if (ImGui::MenuItem("New")) {}
+    if (ImGui::MenuItem("Open", "Ctrl+O")) {}
+    if (ImGui::BeginMenu("Open Recent"))
+    {
+        ImGui::MenuItem("fish_hat.c");
+        ImGui::MenuItem("fish_hat.inl");
+        ImGui::MenuItem("fish_hat.h");
+        if (ImGui::BeginMenu("More.."))
+        {
+            ImGui::MenuItem("Hello");
+            ImGui::MenuItem("Sailor");
+            if (ImGui::BeginMenu("Recurse.."))
+            {
+                ShowExampleMenuFile();
+                ImGui::EndMenu();
+            }
+            ImGui::EndMenu();
+        }
+        ImGui::EndMenu();
+    }
+    if (ImGui::MenuItem("Save", "Ctrl+S")) {}
+    if (ImGui::MenuItem("Save As..")) {}
+    ImGui::Separator();
+    if (ImGui::BeginMenu("Options"))
+    {
+        static bool enabled = true;
+        ImGui::MenuItem("Enabled", "", &enabled);
+        ImGui::BeginChild("child", ImVec2(0, 60), true);
+        for (int i = 0; i < 10; i++)
+            ImGui::Text("Scrolling Text %d", i);
+        ImGui::EndChild();
+        static float f = 0.5f;
+        static int n = 0;
+        static bool b = true;
+        ImGui::SliderFloat("Value", &f, 0.0f, 1.0f);
+        ImGui::InputFloat("Input", &f, 0.1f);
+        ImGui::Combo("Combo", &n, "Yes\0No\0Maybe\0\0");
+        ImGui::Checkbox("Check", &b);
+        ImGui::EndMenu();
+    }
+    if (ImGui::BeginMenu("Colors"))
+    {
+        float sz = ImGui::GetTextLineHeight();
+        for (int i = 0; i < ImGuiCol_COUNT; i++)
+        {
+            const char* name = ImGui::GetStyleColorName((ImGuiCol)i);
+            ImVec2 p = ImGui::GetCursorScreenPos();
+            ImGui::GetWindowDrawList()->AddRectFilled(p, ImVec2(p.x+sz, p.y+sz), ImGui::GetColorU32((ImGuiCol)i));
+            ImGui::Dummy(ImVec2(sz, sz));
+            ImGui::SameLine();
+            ImGui::MenuItem(name);
+        }
+        ImGui::EndMenu();
+    }
+    if (ImGui::BeginMenu("Disabled", false)) // Disabled
+    {
+        IM_ASSERT(0);
+    }
+    if (ImGui::MenuItem("Checked", NULL, true)) {}
+    if (ImGui::MenuItem("Quit", "Alt+F4")) {}
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] Example App: Debug Console / ShowExampleAppConsole()
+//-----------------------------------------------------------------------------
+
+// Demonstrate creating a simple console window, with scrolling, filtering, completion and history.
+// For the console example, here we are using a more C++ like approach of declaring a class to hold the data and the functions.
+struct ExampleAppConsole
+{
+    char                  InputBuf[256];
+    ImVector<char*>       Items;
+    bool                  ScrollToBottom;
+    ImVector<char*>       History;
+    int                   HistoryPos;    // -1: new line, 0..History.Size-1 browsing history.
+    ImVector<const char*> Commands;
+
+    ExampleAppConsole()
+    {
+        ClearLog();
+        memset(InputBuf, 0, sizeof(InputBuf));
+        HistoryPos = -1;
+        Commands.push_back("HELP");
+        Commands.push_back("HISTORY");
+        Commands.push_back("CLEAR");
+        Commands.push_back("CLASSIFY");  // "classify" is only here to provide an example of "C"+[tab] completing to "CL" and displaying matches.
+        AddLog("Welcome to Dear ImGui!");
+    }
+    ~ExampleAppConsole()
+    {
+        ClearLog();
+        for (int i = 0; i < History.Size; i++)
+            free(History[i]);
+    }
+
+    // Portable helpers
+    static int   Stricmp(const char* str1, const char* str2)         { int d; while ((d = toupper(*str2) - toupper(*str1)) == 0 && *str1) { str1++; str2++; } return d; }
+    static int   Strnicmp(const char* str1, const char* str2, int n) { int d = 0; while (n > 0 && (d = toupper(*str2) - toupper(*str1)) == 0 && *str1) { str1++; str2++; n--; } return d; }
+    static char* Strdup(const char *str)                             { size_t len = strlen(str) + 1; void* buff = malloc(len); return (char*)memcpy(buff, (const void*)str, len); }
+    static void  Strtrim(char* str)                                  { char* str_end = str + strlen(str); while (str_end > str && str_end[-1] == ' ') str_end--; *str_end = 0; }
+
+    void    ClearLog()
+    {
+        for (int i = 0; i < Items.Size; i++)
+            free(Items[i]);
+        Items.clear();
+        ScrollToBottom = true;
+    }
+
+    void    AddLog(const char* fmt, ...) IM_FMTARGS(2)
+    {
+        // FIXME-OPT
+        char buf[1024];
+        va_list args;
+        va_start(args, fmt);
+        vsnprintf(buf, IM_ARRAYSIZE(buf), fmt, args);
+        buf[IM_ARRAYSIZE(buf)-1] = 0;
+        va_end(args);
+        Items.push_back(Strdup(buf));
+        ScrollToBottom = true;
+    }
+
+    void    Draw(const char* title, bool* p_open)
+    {
+        ImGui::SetNextWindowSize(ImVec2(520,600), ImGuiCond_FirstUseEver);
+        if (!ImGui::Begin(title, p_open))
+        {
+            ImGui::End();
+            return;
+        }
+
+        // As a specific feature guaranteed by the library, after calling Begin() the last Item represent the title bar. So e.g. IsItemHovered() will return true when hovering the title bar.
+        // Here we create a context menu only available from the title bar.
+        if (ImGui::BeginPopupContextItem())
+        {
+            if (ImGui::MenuItem("Close Console"))
+                *p_open = false;
+            ImGui::EndPopup();
+        }
+
+        ImGui::TextWrapped("This example implements a console with basic coloring, completion and history. A more elaborate implementation may want to store entries along with extra data such as timestamp, emitter, etc.");
+        ImGui::TextWrapped("Enter 'HELP' for help, press TAB to use text completion.");
+
+        // TODO: display items starting from the bottom
+
+        if (ImGui::SmallButton("Add Dummy Text")) { AddLog("%d some text", Items.Size); AddLog("some more text"); AddLog("display very important message here!"); } ImGui::SameLine();
+        if (ImGui::SmallButton("Add Dummy Error")) { AddLog("[error] something went wrong"); } ImGui::SameLine();
+        if (ImGui::SmallButton("Clear")) { ClearLog(); } ImGui::SameLine();
+        bool copy_to_clipboard = ImGui::SmallButton("Copy"); ImGui::SameLine();
+        if (ImGui::SmallButton("Scroll to bottom")) ScrollToBottom = true;
+        //static float t = 0.0f; if (ImGui::GetTime() - t > 0.02f) { t = ImGui::GetTime(); AddLog("Spam %f", t); }
+
+        ImGui::Separator();
+
+        ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0,0));
+        static ImGuiTextFilter filter;
+        filter.Draw("Filter (\"incl,-excl\") (\"error\")", 180);
+        ImGui::PopStyleVar();
+        ImGui::Separator();
+
+        const float footer_height_to_reserve = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing(); // 1 separator, 1 input text
+        ImGui::BeginChild("ScrollingRegion", ImVec2(0, -footer_height_to_reserve), false, ImGuiWindowFlags_HorizontalScrollbar); // Leave room for 1 separator + 1 InputText
+        if (ImGui::BeginPopupContextWindow())
+        {
+            if (ImGui::Selectable("Clear")) ClearLog();
+            ImGui::EndPopup();
+        }
+
+        // Display every line as a separate entry so we can change their color or add custom widgets. If you only want raw text you can use ImGui::TextUnformatted(log.begin(), log.end());
+        // NB- if you have thousands of entries this approach may be too inefficient and may require user-side clipping to only process visible items.
+        // You can seek and display only the lines that are visible using the ImGuiListClipper helper, if your elements are evenly spaced and you have cheap random access to the elements.
+        // To use the clipper we could replace the 'for (int i = 0; i < Items.Size; i++)' loop with:
+        //     ImGuiListClipper clipper(Items.Size);
+        //     while (clipper.Step())
+        //         for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
+        // However, note that you can not use this code as is if a filter is active because it breaks the 'cheap random-access' property. We would need random-access on the post-filtered list.
+        // A typical application wanting coarse clipping and filtering may want to pre-compute an array of indices that passed the filtering test, recomputing this array when user changes the filter,
+        // and appending newly elements as they are inserted. This is left as a task to the user until we can manage to improve this example code!
+        // If your items are of variable size you may want to implement code similar to what ImGuiListClipper does. Or split your data into fixed height items to allow random-seeking into your list.
+        ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4,1)); // Tighten spacing
+        if (copy_to_clipboard)
+            ImGui::LogToClipboard();
+        ImVec4 col_default_text = ImGui::GetStyleColorVec4(ImGuiCol_Text);
+        for (int i = 0; i < Items.Size; i++)
+        {
+            const char* item = Items[i];
+            if (!filter.PassFilter(item))
+                continue;
+            ImVec4 col = col_default_text;
+            if (strstr(item, "[error]")) col = ImColor(1.0f,0.4f,0.4f,1.0f);
+            else if (strncmp(item, "# ", 2) == 0) col = ImColor(1.0f,0.78f,0.58f,1.0f);
+            ImGui::PushStyleColor(ImGuiCol_Text, col);
+            ImGui::TextUnformatted(item);
+            ImGui::PopStyleColor();
+        }
+        if (copy_to_clipboard)
+            ImGui::LogFinish();
+        if (ScrollToBottom)
+            ImGui::SetScrollHere(1.0f);
+        ScrollToBottom = false;
+        ImGui::PopStyleVar();
+        ImGui::EndChild();
+        ImGui::Separator();
+
+        // Command-line
+        bool reclaim_focus = false;
+        if (ImGui::InputText("Input", InputBuf, IM_ARRAYSIZE(InputBuf), ImGuiInputTextFlags_EnterReturnsTrue|ImGuiInputTextFlags_CallbackCompletion|ImGuiInputTextFlags_CallbackHistory, &TextEditCallbackStub, (void*)this))
+        {
+            char* s = InputBuf;
+            Strtrim(s);
+            if (s[0])
+                ExecCommand(s);
+            strcpy(s, "");
+            reclaim_focus = true;
+        }
+
+        // Auto-focus on window apparition
+        ImGui::SetItemDefaultFocus();
+        if (reclaim_focus)
+            ImGui::SetKeyboardFocusHere(-1); // Auto focus previous widget
+
+        ImGui::End();
+    }
+
+    void    ExecCommand(const char* command_line)
+    {
+        AddLog("# %s\n", command_line);
+
+        // Insert into history. First find match and delete it so it can be pushed to the back. This isn't trying to be smart or optimal.
+        HistoryPos = -1;
+        for (int i = History.Size-1; i >= 0; i--)
+            if (Stricmp(History[i], command_line) == 0)
+            {
+                free(History[i]);
+                History.erase(History.begin() + i);
+                break;
+            }
+        History.push_back(Strdup(command_line));
+
+        // Process command
+        if (Stricmp(command_line, "CLEAR") == 0)
+        {
+            ClearLog();
+        }
+        else if (Stricmp(command_line, "HELP") == 0)
+        {
+            AddLog("Commands:");
+            for (int i = 0; i < Commands.Size; i++)
+                AddLog("- %s", Commands[i]);
+        }
+        else if (Stricmp(command_line, "HISTORY") == 0)
+        {
+            int first = History.Size - 10;
+            for (int i = first > 0 ? first : 0; i < History.Size; i++)
+                AddLog("%3d: %s\n", i, History[i]);
+        }
+        else
+        {
+            AddLog("Unknown command: '%s'\n", command_line);
+        }
+    }
+
+    static int TextEditCallbackStub(ImGuiInputTextCallbackData* data) // In C++11 you are better off using lambdas for this sort of forwarding callbacks
+    {
+        ExampleAppConsole* console = (ExampleAppConsole*)data->UserData;
+        return console->TextEditCallback(data);
+    }
+
+    int     TextEditCallback(ImGuiInputTextCallbackData* data)
+    {
+        //AddLog("cursor: %d, selection: %d-%d", data->CursorPos, data->SelectionStart, data->SelectionEnd);
+        switch (data->EventFlag)
+        {
+        case ImGuiInputTextFlags_CallbackCompletion:
+            {
+                // Example of TEXT COMPLETION
+
+                // Locate beginning of current word
+                const char* word_end = data->Buf + data->CursorPos;
+                const char* word_start = word_end;
+                while (word_start > data->Buf)
+                {
+                    const char c = word_start[-1];
+                    if (c == ' ' || c == '\t' || c == ',' || c == ';')
+                        break;
+                    word_start--;
+                }
+
+                // Build a list of candidates
+                ImVector<const char*> candidates;
+                for (int i = 0; i < Commands.Size; i++)
+                    if (Strnicmp(Commands[i], word_start, (int)(word_end-word_start)) == 0)
+                        candidates.push_back(Commands[i]);
+
+                if (candidates.Size == 0)
+                {
+                    // No match
+                    AddLog("No match for \"%.*s\"!\n", (int)(word_end-word_start), word_start);
+                }
+                else if (candidates.Size == 1)
+                {
+                    // Single match. Delete the beginning of the word and replace it entirely so we've got nice casing
+                    data->DeleteChars((int)(word_start-data->Buf), (int)(word_end-word_start));
+                    data->InsertChars(data->CursorPos, candidates[0]);
+                    data->InsertChars(data->CursorPos, " ");
+                }
+                else
+                {
+                    // Multiple matches. Complete as much as we can, so inputing "C" will complete to "CL" and display "CLEAR" and "CLASSIFY"
+                    int match_len = (int)(word_end - word_start);
+                    for (;;)
+                    {
+                        int c = 0;
+                        bool all_candidates_matches = true;
+                        for (int i = 0; i < candidates.Size && all_candidates_matches; i++)
+                            if (i == 0)
+                                c = toupper(candidates[i][match_len]);
+                            else if (c == 0 || c != toupper(candidates[i][match_len]))
+                                all_candidates_matches = false;
+                        if (!all_candidates_matches)
+                            break;
+                        match_len++;
+                    }
+
+                    if (match_len > 0)
+                    {
+                        data->DeleteChars((int)(word_start - data->Buf), (int)(word_end-word_start));
+                        data->InsertChars(data->CursorPos, candidates[0], candidates[0] + match_len);
+                    }
+
+                    // List matches
+                    AddLog("Possible matches:\n");
+                    for (int i = 0; i < candidates.Size; i++)
+                        AddLog("- %s\n", candidates[i]);
+                }
+
+                break;
+            }
+        case ImGuiInputTextFlags_CallbackHistory:
+            {
+                // Example of HISTORY
+                const int prev_history_pos = HistoryPos;
+                if (data->EventKey == ImGuiKey_UpArrow)
+                {
+                    if (HistoryPos == -1)
+                        HistoryPos = History.Size - 1;
+                    else if (HistoryPos > 0)
+                        HistoryPos--;
+                }
+                else if (data->EventKey == ImGuiKey_DownArrow)
+                {
+                    if (HistoryPos != -1)
+                        if (++HistoryPos >= History.Size)
+                            HistoryPos = -1;
+                }
+
+                // A better implementation would preserve the data on the current input line along with cursor position.
+                if (prev_history_pos != HistoryPos)
+                {
+                    const char* history_str = (HistoryPos >= 0) ? History[HistoryPos] : "";
+                    data->DeleteChars(0, data->BufTextLen);
+                    data->InsertChars(0, history_str);
+                }
+            }
+        }
+        return 0;
+    }
+};
+
+static void ShowExampleAppConsole(bool* p_open)
+{
+    static ExampleAppConsole console;
+    console.Draw("Example: Console", p_open);
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] Example App: Debug Log / ShowExampleAppLog()
+//-----------------------------------------------------------------------------
+
+// Usage:
+//  static ExampleAppLog my_log;
+//  my_log.AddLog("Hello %d world\n", 123);
+//  my_log.Draw("title");
+struct ExampleAppLog
+{
+    ImGuiTextBuffer     Buf;
+    ImGuiTextFilter     Filter;
+    ImVector<int>       LineOffsets;        // Index to lines offset
+    bool                ScrollToBottom;
+
+    void    Clear()     { Buf.clear(); LineOffsets.clear(); }
+
+    void    AddLog(const char* fmt, ...) IM_FMTARGS(2)
+    {
+        int old_size = Buf.size();
+        va_list args;
+        va_start(args, fmt);
+        Buf.appendfv(fmt, args);
+        va_end(args);
+        for (int new_size = Buf.size(); old_size < new_size; old_size++)
+            if (Buf[old_size] == '\n')
+                LineOffsets.push_back(old_size);
+        ScrollToBottom = true;
+    }
+
+    void    Draw(const char* title, bool* p_open = NULL)
+    {
+        ImGui::SetNextWindowSize(ImVec2(500,400), ImGuiCond_FirstUseEver);
+        if (!ImGui::Begin(title, p_open))
+        {
+            ImGui::End();
+            return;
+        }
+        if (ImGui::Button("Clear")) Clear();
+        ImGui::SameLine();
+        bool copy = ImGui::Button("Copy");
+        ImGui::SameLine();
+        Filter.Draw("Filter", -100.0f);
+        ImGui::Separator();
+        ImGui::BeginChild("scrolling", ImVec2(0,0), false, ImGuiWindowFlags_HorizontalScrollbar);
+        if (copy) ImGui::LogToClipboard();
+
+        if (Filter.IsActive())
+        {
+            const char* buf_begin = Buf.begin();
+            const char* line = buf_begin;
+            for (int line_no = 0; line != NULL; line_no++)
+            {
+                const char* line_end = (line_no < LineOffsets.Size) ? buf_begin + LineOffsets[line_no] : NULL;
+                if (Filter.PassFilter(line, line_end))
+                    ImGui::TextUnformatted(line, line_end);
+                line = line_end && line_end[1] ? line_end + 1 : NULL;
+            }
+        }
+        else
+        {
+            ImGui::TextUnformatted(Buf.begin());
+        }
+
+        if (ScrollToBottom)
+            ImGui::SetScrollHere(1.0f);
+        ScrollToBottom = false;
+        ImGui::EndChild();
+        ImGui::End();
+    }
+};
+
+// Demonstrate creating a simple log window with basic filtering.
+static void ShowExampleAppLog(bool* p_open)
+{
+    static ExampleAppLog log;
+
+    // Demo: add random items (unless Ctrl is held)
+    static double last_time = -1.0;
+    double time = ImGui::GetTime();
+    if (time - last_time >= 0.20f && !ImGui::GetIO().KeyCtrl)
+    {
+        const char* random_words[] = { "system", "info", "warning", "error", "fatal", "notice", "log" };
+        log.AddLog("[%s] Hello, time is %.1f, frame count is %d\n", random_words[rand() % IM_ARRAYSIZE(random_words)], time, ImGui::GetFrameCount());
+        last_time = time;
+    }
+
+    log.Draw("Example: Log", p_open);
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] Example App: Simple Layout / ShowExampleAppLayout()
+//-----------------------------------------------------------------------------
+
+// Demonstrate create a window with multiple child windows.
+static void ShowExampleAppLayout(bool* p_open)
+{
+    ImGui::SetNextWindowSize(ImVec2(500, 440), ImGuiCond_FirstUseEver);
+    if (ImGui::Begin("Example: Layout", p_open, ImGuiWindowFlags_MenuBar))
+    {
+        if (ImGui::BeginMenuBar())
+        {
+            if (ImGui::BeginMenu("File"))
+            {
+                if (ImGui::MenuItem("Close")) *p_open = false;
+                ImGui::EndMenu();
+            }
+            ImGui::EndMenuBar();
+        }
+
+        // left
+        static int selected = 0;
+        ImGui::BeginChild("left pane", ImVec2(150, 0), true);
+        for (int i = 0; i < 100; i++)
+        {
+            char label[128];
+            sprintf(label, "MyObject %d", i);
+            if (ImGui::Selectable(label, selected == i))
+                selected = i;
+        }
+        ImGui::EndChild();
+        ImGui::SameLine();
+
+        // right
+        ImGui::BeginGroup();
+            ImGui::BeginChild("item view", ImVec2(0, -ImGui::GetFrameHeightWithSpacing())); // Leave room for 1 line below us
+                ImGui::Text("MyObject: %d", selected);
+                ImGui::Separator();
+                ImGui::TextWrapped("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ");
+            ImGui::EndChild();
+            if (ImGui::Button("Revert")) {}
+            ImGui::SameLine();
+            if (ImGui::Button("Save")) {}
+        ImGui::EndGroup();
+    }
+    ImGui::End();
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] Example App: Property Editor / ShowExampleAppPropertyEditor()
+//-----------------------------------------------------------------------------
+
+// Demonstrate create a simple property editor.
+static void ShowExampleAppPropertyEditor(bool* p_open)
+{
+    ImGui::SetNextWindowSize(ImVec2(430,450), ImGuiCond_FirstUseEver);
+    if (!ImGui::Begin("Example: Property editor", p_open))
+    {
+        ImGui::End();
+        return;
+    }
+
+    ShowHelpMarker("This example shows how you may implement a property editor using two columns.\nAll objects/fields data are dummies here.\nRemember that in many simple cases, you can use ImGui::SameLine(xxx) to position\nyour cursor horizontally instead of using the Columns() API.");
+
+    ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2,2));
+    ImGui::Columns(2);
+    ImGui::Separator();
+
+    struct funcs
+    {
+        static void ShowDummyObject(const char* prefix, int uid)
+        {
+            ImGui::PushID(uid);                      // Use object uid as identifier. Most commonly you could also use the object pointer as a base ID.
+            ImGui::AlignTextToFramePadding();  // Text and Tree nodes are less high than regular widgets, here we add vertical spacing to make the tree lines equal high.
+            bool node_open = ImGui::TreeNode("Object", "%s_%u", prefix, uid);
+            ImGui::NextColumn();
+            ImGui::AlignTextToFramePadding();
+            ImGui::Text("my sailor is rich");
+            ImGui::NextColumn();
+            if (node_open)
+            {
+                static float dummy_members[8] = { 0.0f,0.0f,1.0f,3.1416f,100.0f,999.0f };
+                for (int i = 0; i < 8; i++)
+                {
+                    ImGui::PushID(i); // Use field index as identifier.
+                    if (i < 2)
+                    {
+                        ShowDummyObject("Child", 424242);
+                    }
+                    else
+                    {
+                        // Here we use a TreeNode to highlight on hover (we could use e.g. Selectable as well)
+                        ImGui::AlignTextToFramePadding();
+                        ImGui::TreeNodeEx("Field", ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_Bullet, "Field_%d", i);
+                        ImGui::NextColumn();
+                        ImGui::PushItemWidth(-1);
+                        if (i >= 5)
+                            ImGui::InputFloat("##value", &dummy_members[i], 1.0f);
+                        else
+                            ImGui::DragFloat("##value", &dummy_members[i], 0.01f);
+                        ImGui::PopItemWidth();
+                        ImGui::NextColumn();
+                    }
+                    ImGui::PopID();
+                }
+                ImGui::TreePop();
+            }
+            ImGui::PopID();
+        }
+    };
+
+    // Iterate dummy objects with dummy members (all the same data)
+    for (int obj_i = 0; obj_i < 3; obj_i++)
+        funcs::ShowDummyObject("Object", obj_i);
+
+    ImGui::Columns(1);
+    ImGui::Separator();
+    ImGui::PopStyleVar();
+    ImGui::End();
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] Example App: Long Text / ShowExampleAppLongText()
+//-----------------------------------------------------------------------------
+
+// Demonstrate/test rendering huge amount of text, and the incidence of clipping.
+static void ShowExampleAppLongText(bool* p_open)
+{
+    ImGui::SetNextWindowSize(ImVec2(520,600), ImGuiCond_FirstUseEver);
+    if (!ImGui::Begin("Example: Long text display", p_open))
+    {
+        ImGui::End();
+        return;
+    }
+
+    static int test_type = 0;
+    static ImGuiTextBuffer log;
+    static int lines = 0;
+    ImGui::Text("Printing unusually long amount of text.");
+    ImGui::Combo("Test type", &test_type, "Single call to TextUnformatted()\0Multiple calls to Text(), clipped manually\0Multiple calls to Text(), not clipped (slow)\0");
+    ImGui::Text("Buffer contents: %d lines, %d bytes", lines, log.size());
+    if (ImGui::Button("Clear")) { log.clear(); lines = 0; }
+    ImGui::SameLine();
+    if (ImGui::Button("Add 1000 lines"))
+    {
+        for (int i = 0; i < 1000; i++)
+            log.appendf("%i The quick brown fox jumps over the lazy dog\n", lines+i);
+        lines += 1000;
+    }
+    ImGui::BeginChild("Log");
+    switch (test_type)
+    {
+    case 0:
+        // Single call to TextUnformatted() with a big buffer
+        ImGui::TextUnformatted(log.begin(), log.end());
+        break;
+    case 1:
+        {
+            // Multiple calls to Text(), manually coarsely clipped - demonstrate how to use the ImGuiListClipper helper.
+            ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0,0));
+            ImGuiListClipper clipper(lines);
+            while (clipper.Step())
+                for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
+                    ImGui::Text("%i The quick brown fox jumps over the lazy dog", i);
+            ImGui::PopStyleVar();
+            break;
+        }
+    case 2:
+        // Multiple calls to Text(), not clipped (slow)
+        ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0,0));
+        for (int i = 0; i < lines; i++)
+            ImGui::Text("%i The quick brown fox jumps over the lazy dog", i);
+        ImGui::PopStyleVar();
+        break;
+    }
+    ImGui::EndChild();
+    ImGui::End();
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] Example App: Auto Resize / ShowExampleAppAutoResize()
+//-----------------------------------------------------------------------------
+
+// Demonstrate creating a window which gets auto-resized according to its content.
+static void ShowExampleAppAutoResize(bool* p_open)
+{
+    if (!ImGui::Begin("Example: Auto-resizing window", p_open, ImGuiWindowFlags_AlwaysAutoResize))
+    {
+        ImGui::End();
+        return;
+    }
+
+    static int lines = 10;
+    ImGui::Text("Window will resize every-frame to the size of its content.\nNote that you probably don't want to query the window size to\noutput your content because that would create a feedback loop.");
+    ImGui::SliderInt("Number of lines", &lines, 1, 20);
+    for (int i = 0; i < lines; i++)
+        ImGui::Text("%*sThis is line %d", i * 4, "", i); // Pad with space to extend size horizontally
+    ImGui::End();
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] Example App: Constrained Resize / ShowExampleAppConstrainedResize()
+//-----------------------------------------------------------------------------
+
+// Demonstrate creating a window with custom resize constraints.
+static void ShowExampleAppConstrainedResize(bool* p_open)
+{
+    struct CustomConstraints // Helper functions to demonstrate programmatic constraints
+    {
+        static void Square(ImGuiSizeCallbackData* data) { data->DesiredSize = ImVec2(IM_MAX(data->DesiredSize.x, data->DesiredSize.y), IM_MAX(data->DesiredSize.x, data->DesiredSize.y)); }
+        static void Step(ImGuiSizeCallbackData* data)   { float step = (float)(int)(intptr_t)data->UserData; data->DesiredSize = ImVec2((int)(data->DesiredSize.x / step + 0.5f) * step, (int)(data->DesiredSize.y / step + 0.5f) * step); }
+    };
+
+    static bool auto_resize = false;
+    static int type = 0;
+    static int display_lines = 10;
+    if (type == 0) ImGui::SetNextWindowSizeConstraints(ImVec2(-1, 0),    ImVec2(-1, FLT_MAX));      // Vertical only
+    if (type == 1) ImGui::SetNextWindowSizeConstraints(ImVec2(0, -1),    ImVec2(FLT_MAX, -1));      // Horizontal only
+    if (type == 2) ImGui::SetNextWindowSizeConstraints(ImVec2(100, 100), ImVec2(FLT_MAX, FLT_MAX)); // Width > 100, Height > 100
+    if (type == 3) ImGui::SetNextWindowSizeConstraints(ImVec2(400, -1),  ImVec2(500, -1));          // Width 400-500
+    if (type == 4) ImGui::SetNextWindowSizeConstraints(ImVec2(-1, 400),  ImVec2(-1, 500));          // Height 400-500
+    if (type == 5) ImGui::SetNextWindowSizeConstraints(ImVec2(0, 0),     ImVec2(FLT_MAX, FLT_MAX), CustomConstraints::Square);          // Always Square
+    if (type == 6) ImGui::SetNextWindowSizeConstraints(ImVec2(0, 0),     ImVec2(FLT_MAX, FLT_MAX), CustomConstraints::Step, (void*)100);// Fixed Step
+
+    ImGuiWindowFlags flags = auto_resize ? ImGuiWindowFlags_AlwaysAutoResize : 0;
+    if (ImGui::Begin("Example: Constrained Resize", p_open, flags))
+    {
+        const char* desc[] =
+        {
+            "Resize vertical only",
+            "Resize horizontal only",
+            "Width > 100, Height > 100",
+            "Width 400-500",
+            "Height 400-500",
+            "Custom: Always Square",
+            "Custom: Fixed Steps (100)",
+        };
+        if (ImGui::Button("200x200")) { ImGui::SetWindowSize(ImVec2(200, 200)); } ImGui::SameLine();
+        if (ImGui::Button("500x500")) { ImGui::SetWindowSize(ImVec2(500, 500)); } ImGui::SameLine();
+        if (ImGui::Button("800x200")) { ImGui::SetWindowSize(ImVec2(800, 200)); }
+        ImGui::PushItemWidth(200);
+        ImGui::Combo("Constraint", &type, desc, IM_ARRAYSIZE(desc));
+        ImGui::DragInt("Lines", &display_lines, 0.2f, 1, 100);
+        ImGui::PopItemWidth();
+        ImGui::Checkbox("Auto-resize", &auto_resize);
+        for (int i = 0; i < display_lines; i++)
+            ImGui::Text("%*sHello, sailor! Making this line long enough for the example.", i * 4, "");
+    }
+    ImGui::End();
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] Example App: Simple Overlay / ShowExampleAppSimpleOverlay()
+//-----------------------------------------------------------------------------
+
+// Demonstrate creating a simple static window with no decoration + a context-menu to choose which corner of the screen to use.
+static void ShowExampleAppSimpleOverlay(bool* p_open)
+{
+    const float DISTANCE = 10.0f;
+    static int corner = 0;
+    ImVec2 window_pos = ImVec2((corner & 1) ? ImGui::GetIO().DisplaySize.x - DISTANCE : DISTANCE, (corner & 2) ? ImGui::GetIO().DisplaySize.y - DISTANCE : DISTANCE);
+    ImVec2 window_pos_pivot = ImVec2((corner & 1) ? 1.0f : 0.0f, (corner & 2) ? 1.0f : 0.0f);
+    if (corner != -1)
+        ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always, window_pos_pivot);
+    ImGui::SetNextWindowBgAlpha(0.3f); // Transparent background
+    if (ImGui::Begin("Example: Simple Overlay", p_open, (corner != -1 ? ImGuiWindowFlags_NoMove : 0) | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav))
+    {
+        ImGui::Text("Simple overlay\n" "in the corner of the screen.\n" "(right-click to change position)");
+        ImGui::Separator();
+        if (ImGui::IsMousePosValid())
+            ImGui::Text("Mouse Position: (%.1f,%.1f)", ImGui::GetIO().MousePos.x, ImGui::GetIO().MousePos.y);
+        else
+            ImGui::Text("Mouse Position: <invalid>");
+        if (ImGui::BeginPopupContextWindow())
+        {
+            if (ImGui::MenuItem("Custom",       NULL, corner == -1)) corner = -1;
+            if (ImGui::MenuItem("Top-left",     NULL, corner == 0)) corner = 0;
+            if (ImGui::MenuItem("Top-right",    NULL, corner == 1)) corner = 1;
+            if (ImGui::MenuItem("Bottom-left",  NULL, corner == 2)) corner = 2;
+            if (ImGui::MenuItem("Bottom-right", NULL, corner == 3)) corner = 3;
+            if (p_open && ImGui::MenuItem("Close")) *p_open = false;
+            ImGui::EndPopup();
+        }
+    }
+    ImGui::End();
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] Example App: Manipulating Window Titles / ShowExampleAppWindowTitles()
+//-----------------------------------------------------------------------------
+
+// Demonstrate using "##" and "###" in identifiers to manipulate ID generation.
+// This apply to all regular items as well. Read FAQ section "How can I have multiple widgets with the same label? Can I have widget without a label? (Yes). A primer on the purpose of labels/IDs." for details.
+static void ShowExampleAppWindowTitles(bool*)
+{
+    // By default, Windows are uniquely identified by their title.
+    // You can use the "##" and "###" markers to manipulate the display/ID.
+
+    // Using "##" to display same title but have unique identifier.
+    ImGui::SetNextWindowPos(ImVec2(100, 100), ImGuiCond_FirstUseEver);
+    ImGui::Begin("Same title as another window##1");
+    ImGui::Text("This is window 1.\nMy title is the same as window 2, but my identifier is unique.");
+    ImGui::End();
+
+    ImGui::SetNextWindowPos(ImVec2(100, 200), ImGuiCond_FirstUseEver);
+    ImGui::Begin("Same title as another window##2");
+    ImGui::Text("This is window 2.\nMy title is the same as window 1, but my identifier is unique.");
+    ImGui::End();
+
+    // Using "###" to display a changing title but keep a static identifier "AnimatedTitle"
+    char buf[128];
+    sprintf(buf, "Animated title %c %d###AnimatedTitle", "|/-\\"[(int)(ImGui::GetTime() / 0.25f) & 3], ImGui::GetFrameCount());
+    ImGui::SetNextWindowPos(ImVec2(100, 300), ImGuiCond_FirstUseEver);
+    ImGui::Begin(buf);
+    ImGui::Text("This window has a changing title.");
+    ImGui::End();
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] Example App: Custom Rendering using ImDrawList API / ShowExampleAppCustomRendering()
+//-----------------------------------------------------------------------------
+
+// Demonstrate using the low-level ImDrawList to draw custom shapes.
+static void ShowExampleAppCustomRendering(bool* p_open)
+{
+    ImGui::SetNextWindowSize(ImVec2(350, 560), ImGuiCond_FirstUseEver);
+    if (!ImGui::Begin("Example: Custom rendering", p_open))
+    {
+        ImGui::End();
+        return;
+    }
+
+    // Tip: If you do a lot of custom rendering, you probably want to use your own geometrical types and benefit of overloaded operators, etc.
+    // Define IM_VEC2_CLASS_EXTRA in imconfig.h to create implicit conversions between your types and ImVec2/ImVec4.
+    // ImGui defines overloaded operators but they are internal to imgui.cpp and not exposed outside (to avoid messing with your types)
+    // In this example we are not using the maths operators!
+    ImDrawList* draw_list = ImGui::GetWindowDrawList();
+
+    // Primitives
+    ImGui::Text("Primitives");
+    static float sz = 36.0f;
+    static float thickness = 4.0f;
+    static ImVec4 col = ImVec4(1.0f, 1.0f, 0.4f, 1.0f);
+    ImGui::DragFloat("Size", &sz, 0.2f, 2.0f, 72.0f, "%.0f");
+    ImGui::DragFloat("Thickness", &thickness, 0.05f, 1.0f, 8.0f, "%.02f");
+    ImGui::ColorEdit3("Color", &col.x);
+    {
+        const ImVec2 p = ImGui::GetCursorScreenPos();
+        const ImU32 col32 = ImColor(col);
+        float x = p.x + 4.0f, y = p.y + 4.0f, spacing = 8.0f;
+        for (int n = 0; n < 2; n++)
+        {
+            float curr_thickness = (n == 0) ? 1.0f : thickness;
+            draw_list->AddCircle(ImVec2(x+sz*0.5f, y+sz*0.5f), sz*0.5f, col32, 20, curr_thickness); x += sz+spacing;
+            draw_list->AddRect(ImVec2(x, y), ImVec2(x+sz, y+sz), col32, 0.0f, ImDrawCornerFlags_All, curr_thickness); x += sz+spacing;
+            draw_list->AddRect(ImVec2(x, y), ImVec2(x+sz, y+sz), col32, 10.0f, ImDrawCornerFlags_All, curr_thickness); x += sz+spacing;
+            draw_list->AddRect(ImVec2(x, y), ImVec2(x+sz, y+sz), col32, 10.0f, ImDrawCornerFlags_TopLeft|ImDrawCornerFlags_BotRight, curr_thickness); x += sz+spacing;
+            draw_list->AddTriangle(ImVec2(x+sz*0.5f, y), ImVec2(x+sz,y+sz-0.5f), ImVec2(x,y+sz-0.5f), col32, curr_thickness); x += sz+spacing;
+            draw_list->AddLine(ImVec2(x, y), ImVec2(x+sz, y   ), col32, curr_thickness); x += sz+spacing;   // Horizontal line (note: drawing a filled rectangle will be faster!)
+            draw_list->AddLine(ImVec2(x, y), ImVec2(x,    y+sz), col32, curr_thickness); x += spacing;      // Vertical line (note: drawing a filled rectangle will be faster!)
+            draw_list->AddLine(ImVec2(x, y), ImVec2(x+sz, y+sz), col32, curr_thickness); x += sz+spacing;   // Diagonal line
+            draw_list->AddBezierCurve(ImVec2(x, y), ImVec2(x+sz*1.3f,y+sz*0.3f), ImVec2(x+sz-sz*1.3f,y+sz-sz*0.3f), ImVec2(x+sz, y+sz), col32, curr_thickness);
+            x = p.x + 4;
+            y += sz+spacing;
+        }
+        draw_list->AddCircleFilled(ImVec2(x+sz*0.5f, y+sz*0.5f), sz*0.5f, col32, 32); x += sz+spacing;
+        draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x+sz, y+sz), col32); x += sz+spacing;
+        draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x+sz, y+sz), col32, 10.0f); x += sz+spacing;
+        draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x+sz, y+sz), col32, 10.0f, ImDrawCornerFlags_TopLeft|ImDrawCornerFlags_BotRight); x += sz+spacing;
+        draw_list->AddTriangleFilled(ImVec2(x+sz*0.5f, y), ImVec2(x+sz,y+sz-0.5f), ImVec2(x,y+sz-0.5f), col32); x += sz+spacing;
+        draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x+sz, y+thickness), col32); x += sz+spacing;          // Horizontal line (faster than AddLine, but only handle integer thickness)
+        draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x+thickness, y+sz), col32); x += spacing+spacing;     // Vertical line (faster than AddLine, but only handle integer thickness)
+        draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x+1, y+1), col32);          x += sz;                  // Pixel (faster than AddLine)
+        draw_list->AddRectFilledMultiColor(ImVec2(x, y), ImVec2(x+sz, y+sz), IM_COL32(0,0,0,255), IM_COL32(255,0,0,255), IM_COL32(255,255,0,255), IM_COL32(0,255,0,255));
+        ImGui::Dummy(ImVec2((sz+spacing)*8, (sz+spacing)*3));
+    }
+    ImGui::Separator();
+    {
+        static ImVector<ImVec2> points;
+        static bool adding_line = false;
+        ImGui::Text("Canvas example");
+        if (ImGui::Button("Clear")) points.clear();
+        if (points.Size >= 2) { ImGui::SameLine(); if (ImGui::Button("Undo")) { points.pop_back(); points.pop_back(); } }
+        ImGui::Text("Left-click and drag to add lines,\nRight-click to undo");
+
+        // Here we are using InvisibleButton() as a convenience to 1) advance the cursor and 2) allows us to use IsItemHovered()
+        // But you can also draw directly and poll mouse/keyboard by yourself. You can manipulate the cursor using GetCursorPos() and SetCursorPos().
+        // If you only use the ImDrawList API, you can notify the owner window of its extends by using SetCursorPos(max).
+        ImVec2 canvas_pos = ImGui::GetCursorScreenPos();            // ImDrawList API uses screen coordinates!
+        ImVec2 canvas_size = ImGui::GetContentRegionAvail();        // Resize canvas to what's available
+        if (canvas_size.x < 50.0f) canvas_size.x = 50.0f;
+        if (canvas_size.y < 50.0f) canvas_size.y = 50.0f;
+        draw_list->AddRectFilledMultiColor(canvas_pos, ImVec2(canvas_pos.x + canvas_size.x, canvas_pos.y + canvas_size.y), IM_COL32(50, 50, 50, 255), IM_COL32(50, 50, 60, 255), IM_COL32(60, 60, 70, 255), IM_COL32(50, 50, 60, 255));
+        draw_list->AddRect(canvas_pos, ImVec2(canvas_pos.x + canvas_size.x, canvas_pos.y + canvas_size.y), IM_COL32(255, 255, 255, 255));
+
+        bool adding_preview = false;
+        ImGui::InvisibleButton("canvas", canvas_size);
+        ImVec2 mouse_pos_in_canvas = ImVec2(ImGui::GetIO().MousePos.x - canvas_pos.x, ImGui::GetIO().MousePos.y - canvas_pos.y);
+        if (adding_line)
+        {
+            adding_preview = true;
+            points.push_back(mouse_pos_in_canvas);
+            if (!ImGui::IsMouseDown(0))
+                adding_line = adding_preview = false;
+        }
+        if (ImGui::IsItemHovered())
+        {
+            if (!adding_line && ImGui::IsMouseClicked(0))
+            {
+                points.push_back(mouse_pos_in_canvas);
+                adding_line = true;
+            }
+            if (ImGui::IsMouseClicked(1) && !points.empty())
+            {
+                adding_line = adding_preview = false;
+                points.pop_back();
+                points.pop_back();
+            }
+        }
+        draw_list->PushClipRect(canvas_pos, ImVec2(canvas_pos.x + canvas_size.x, canvas_pos.y + canvas_size.y), true);      // clip lines within the canvas (if we resize it, etc.)
+        for (int i = 0; i < points.Size - 1; i += 2)
+            draw_list->AddLine(ImVec2(canvas_pos.x + points[i].x, canvas_pos.y + points[i].y), ImVec2(canvas_pos.x + points[i + 1].x, canvas_pos.y + points[i + 1].y), IM_COL32(255, 255, 0, 255), 2.0f);
+        draw_list->PopClipRect();
+        if (adding_preview)
+            points.pop_back();
+    }
+    ImGui::End();
+}
+
+// End of Demo code
+#else
+
+void ImGui::ShowDemoWindow(bool*) {}
+void ImGui::ShowUserGuide() {}
+void ImGui::ShowStyleEditor(ImGuiStyle*) {}
+
+#endif
diff --git a/external/Vulkan/external/imgui/imgui_draw.cpp b/external/Vulkan/external/imgui/imgui_draw.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3afcac4e9019772bbeb5ce1ff1793f59c916fb9e
--- /dev/null
+++ b/external/Vulkan/external/imgui/imgui_draw.cpp
@@ -0,0 +1,3153 @@
+// dear imgui, v1.65
+// (drawing and font code)
+
+/*
+
+Index of this file:
+
+// [SECTION] STB libraries implementation
+// [SECTION] Style functions
+// [SECTION] ImDrawList
+// [SECTION] ImDrawData
+// [SECTION] Helpers ShadeVertsXXX functions
+// [SECTION] ImFontConfig
+// [SECTION] ImFontAtlas
+// [SECTION] ImFontAtlas glyph ranges helpers + GlyphRangesBuilder
+// [SECTION] ImFont
+// [SECTION] Internal Render Helpers
+// [SECTION] Decompression code
+// [SECTION] Default font data (ProggyClean.ttf)
+
+*/
+
+#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
+#define _CRT_SECURE_NO_WARNINGS
+#endif
+
+#include "imgui.h"
+#ifndef IMGUI_DEFINE_MATH_OPERATORS
+#define IMGUI_DEFINE_MATH_OPERATORS
+#endif
+#include "imgui_internal.h"
+
+#include <stdio.h>      // vsnprintf, sscanf, printf
+#if !defined(alloca)
+#if defined(__GLIBC__) || defined(__sun) || defined(__CYGWIN__)
+#include <alloca.h>     // alloca (glibc uses <alloca.h>. Note that Cygwin may have _WIN32 defined, so the order matters here)
+#elif defined(_WIN32)
+#include <malloc.h>     // alloca
+#if !defined(alloca)
+#define alloca _alloca  // for clang with MS Codegen
+#endif
+#else
+#include <stdlib.h>     // alloca
+#endif
+#endif
+
+// Visual Studio warnings
+#ifdef _MSC_VER
+#pragma warning (disable: 4505) // unreferenced local function has been removed (stb stuff)
+#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen
+#endif
+
+// Clang/GCC warnings with -Weverything
+#ifdef __clang__
+#pragma clang diagnostic ignored "-Wold-style-cast"         // warning : use of old-style cast                              // yes, they are more terse.
+#pragma clang diagnostic ignored "-Wfloat-equal"            // warning : comparing floating point with == or != is unsafe   // storing and comparing against same constants ok.
+#pragma clang diagnostic ignored "-Wglobal-constructors"    // warning : declaration requires a global destructor           // similar to above, not sure what the exact difference it.
+#pragma clang diagnostic ignored "-Wsign-conversion"        // warning : implicit conversion changes signedness             //
+#if __has_warning("-Wcomma")
+#pragma clang diagnostic ignored "-Wcomma"                  // warning : possible misuse of comma operator here             //
+#endif
+#if __has_warning("-Wreserved-id-macro")
+#pragma clang diagnostic ignored "-Wreserved-id-macro"      // warning : macro name is a reserved identifier                //
+#endif
+#if __has_warning("-Wdouble-promotion")
+#pragma clang diagnostic ignored "-Wdouble-promotion"       // warning: implicit conversion from 'float' to 'double' when passing argument to function
+#endif
+#elif defined(__GNUC__)
+#pragma GCC diagnostic ignored "-Wunused-function"          // warning: 'xxxx' defined but not used
+#pragma GCC diagnostic ignored "-Wdouble-promotion"         // warning: implicit conversion from 'float' to 'double' when passing argument to function
+#pragma GCC diagnostic ignored "-Wconversion"               // warning: conversion to 'xxxx' from 'xxxx' may alter its value
+#if __GNUC__ >= 8
+#pragma GCC diagnostic ignored "-Wclass-memaccess"          // warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead
+#endif
+#endif
+
+//-------------------------------------------------------------------------
+// [SECTION] STB libraries implementation
+//-------------------------------------------------------------------------
+
+// Compile time options:
+//#define IMGUI_STB_NAMESPACE           ImGuiStb
+//#define IMGUI_STB_TRUETYPE_FILENAME   "my_folder/stb_truetype.h"
+//#define IMGUI_STB_RECT_PACK_FILENAME  "my_folder/stb_rect_pack.h"
+//#define IMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION
+//#define IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION
+
+#ifdef IMGUI_STB_NAMESPACE
+namespace IMGUI_STB_NAMESPACE
+{
+#endif
+
+#ifdef _MSC_VER
+#pragma warning (push)
+#pragma warning (disable: 4456)                             // declaration of 'xx' hides previous local declaration
+#endif
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunused-function"
+#pragma clang diagnostic ignored "-Wmissing-prototypes"
+#pragma clang diagnostic ignored "-Wimplicit-fallthrough"
+#pragma clang diagnostic ignored "-Wcast-qual"              // warning : cast from 'const xxxx *' to 'xxx *' drops const qualifier //
+#endif
+
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wtype-limits"              // warning: comparison is always true due to limited range of data type [-Wtype-limits]
+#pragma GCC diagnostic ignored "-Wcast-qual"                // warning: cast from type 'const xxxx *' to type 'xxxx *' casts away qualifiers
+#endif
+
+#ifndef STB_RECT_PACK_IMPLEMENTATION                        // in case the user already have an implementation in the _same_ compilation unit (e.g. unity builds)
+#ifndef IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION
+#define STBRP_STATIC
+#define STBRP_ASSERT(x)     IM_ASSERT(x)
+#define STBRP_SORT          ImQsort
+#define STB_RECT_PACK_IMPLEMENTATION
+#endif
+#ifdef IMGUI_STB_RECT_PACK_FILENAME
+#include IMGUI_STB_RECT_PACK_FILENAME
+#else
+#include "imstb_rectpack.h"
+#endif
+#endif
+
+#ifndef STB_TRUETYPE_IMPLEMENTATION                         // in case the user already have an implementation in the _same_ compilation unit (e.g. unity builds)
+#ifndef IMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION
+#define STBTT_malloc(x,u)   ((void)(u), ImGui::MemAlloc(x))
+#define STBTT_free(x,u)     ((void)(u), ImGui::MemFree(x))
+#define STBTT_assert(x)     IM_ASSERT(x)
+#define STBTT_fmod(x,y)     ImFmod(x,y)
+#define STBTT_sqrt(x)       ImSqrt(x)
+#define STBTT_pow(x,y)      ImPow(x,y)
+#define STBTT_fabs(x)       ImFabs(x)
+#define STBTT_ifloor(x)     ((int)ImFloorStd(x))
+#define STBTT_iceil(x)      ((int)ImCeil(x))
+#define STBTT_STATIC
+#define STB_TRUETYPE_IMPLEMENTATION
+#else
+#define STBTT_DEF extern
+#endif
+#ifdef IMGUI_STB_TRUETYPE_FILENAME
+#include IMGUI_STB_TRUETYPE_FILENAME
+#else
+#include "imstb_truetype.h"
+#endif
+#endif
+
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+#ifdef _MSC_VER
+#pragma warning (pop)
+#endif
+
+#ifdef IMGUI_STB_NAMESPACE
+} // namespace ImGuiStb
+using namespace IMGUI_STB_NAMESPACE;
+#endif
+
+//-----------------------------------------------------------------------------
+// [SECTION] Style functions
+//-----------------------------------------------------------------------------
+
+void ImGui::StyleColorsDark(ImGuiStyle* dst)
+{
+    ImGuiStyle* style = dst ? dst : &ImGui::GetStyle();
+    ImVec4* colors = style->Colors;
+
+    colors[ImGuiCol_Text]                   = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
+    colors[ImGuiCol_TextDisabled]           = ImVec4(0.50f, 0.50f, 0.50f, 1.00f);
+    colors[ImGuiCol_WindowBg]               = ImVec4(0.06f, 0.06f, 0.06f, 0.94f);
+    colors[ImGuiCol_ChildBg]                = ImVec4(1.00f, 1.00f, 1.00f, 0.00f);
+    colors[ImGuiCol_PopupBg]                = ImVec4(0.08f, 0.08f, 0.08f, 0.94f);
+    colors[ImGuiCol_Border]                 = ImVec4(0.43f, 0.43f, 0.50f, 0.50f);
+    colors[ImGuiCol_BorderShadow]           = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
+    colors[ImGuiCol_FrameBg]                = ImVec4(0.16f, 0.29f, 0.48f, 0.54f);
+    colors[ImGuiCol_FrameBgHovered]         = ImVec4(0.26f, 0.59f, 0.98f, 0.40f);
+    colors[ImGuiCol_FrameBgActive]          = ImVec4(0.26f, 0.59f, 0.98f, 0.67f);
+    colors[ImGuiCol_TitleBg]                = ImVec4(0.04f, 0.04f, 0.04f, 1.00f);
+    colors[ImGuiCol_TitleBgActive]          = ImVec4(0.16f, 0.29f, 0.48f, 1.00f);
+    colors[ImGuiCol_TitleBgCollapsed]       = ImVec4(0.00f, 0.00f, 0.00f, 0.51f);
+    colors[ImGuiCol_MenuBarBg]              = ImVec4(0.14f, 0.14f, 0.14f, 1.00f);
+    colors[ImGuiCol_ScrollbarBg]            = ImVec4(0.02f, 0.02f, 0.02f, 0.53f);
+    colors[ImGuiCol_ScrollbarGrab]          = ImVec4(0.31f, 0.31f, 0.31f, 1.00f);
+    colors[ImGuiCol_ScrollbarGrabHovered]   = ImVec4(0.41f, 0.41f, 0.41f, 1.00f);
+    colors[ImGuiCol_ScrollbarGrabActive]    = ImVec4(0.51f, 0.51f, 0.51f, 1.00f);
+    colors[ImGuiCol_CheckMark]              = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);
+    colors[ImGuiCol_SliderGrab]             = ImVec4(0.24f, 0.52f, 0.88f, 1.00f);
+    colors[ImGuiCol_SliderGrabActive]       = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);
+    colors[ImGuiCol_Button]                 = ImVec4(0.26f, 0.59f, 0.98f, 0.40f);
+    colors[ImGuiCol_ButtonHovered]          = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);
+    colors[ImGuiCol_ButtonActive]           = ImVec4(0.06f, 0.53f, 0.98f, 1.00f);
+    colors[ImGuiCol_Header]                 = ImVec4(0.26f, 0.59f, 0.98f, 0.31f);
+    colors[ImGuiCol_HeaderHovered]          = ImVec4(0.26f, 0.59f, 0.98f, 0.80f);
+    colors[ImGuiCol_HeaderActive]           = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);
+    colors[ImGuiCol_Separator]              = colors[ImGuiCol_Border];
+    colors[ImGuiCol_SeparatorHovered]       = ImVec4(0.10f, 0.40f, 0.75f, 0.78f);
+    colors[ImGuiCol_SeparatorActive]        = ImVec4(0.10f, 0.40f, 0.75f, 1.00f);
+    colors[ImGuiCol_ResizeGrip]             = ImVec4(0.26f, 0.59f, 0.98f, 0.25f);
+    colors[ImGuiCol_ResizeGripHovered]      = ImVec4(0.26f, 0.59f, 0.98f, 0.67f);
+    colors[ImGuiCol_ResizeGripActive]       = ImVec4(0.26f, 0.59f, 0.98f, 0.95f);
+    colors[ImGuiCol_PlotLines]              = ImVec4(0.61f, 0.61f, 0.61f, 1.00f);
+    colors[ImGuiCol_PlotLinesHovered]       = ImVec4(1.00f, 0.43f, 0.35f, 1.00f);
+    colors[ImGuiCol_PlotHistogram]          = ImVec4(0.90f, 0.70f, 0.00f, 1.00f);
+    colors[ImGuiCol_PlotHistogramHovered]   = ImVec4(1.00f, 0.60f, 0.00f, 1.00f);
+    colors[ImGuiCol_TextSelectedBg]         = ImVec4(0.26f, 0.59f, 0.98f, 0.35f);
+    colors[ImGuiCol_DragDropTarget]         = ImVec4(1.00f, 1.00f, 0.00f, 0.90f);
+    colors[ImGuiCol_NavHighlight]           = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);
+    colors[ImGuiCol_NavWindowingHighlight]  = ImVec4(1.00f, 1.00f, 1.00f, 0.70f);
+    colors[ImGuiCol_NavWindowingDimBg]      = ImVec4(0.80f, 0.80f, 0.80f, 0.20f);
+    colors[ImGuiCol_ModalWindowDimBg]       = ImVec4(0.80f, 0.80f, 0.80f, 0.35f);
+}
+
+void ImGui::StyleColorsClassic(ImGuiStyle* dst)
+{
+    ImGuiStyle* style = dst ? dst : &ImGui::GetStyle();
+    ImVec4* colors = style->Colors;
+
+    colors[ImGuiCol_Text]                   = ImVec4(0.90f, 0.90f, 0.90f, 1.00f);
+    colors[ImGuiCol_TextDisabled]           = ImVec4(0.60f, 0.60f, 0.60f, 1.00f);
+    colors[ImGuiCol_WindowBg]               = ImVec4(0.00f, 0.00f, 0.00f, 0.70f);
+    colors[ImGuiCol_ChildBg]                = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
+    colors[ImGuiCol_PopupBg]                = ImVec4(0.11f, 0.11f, 0.14f, 0.92f);
+    colors[ImGuiCol_Border]                 = ImVec4(0.50f, 0.50f, 0.50f, 0.50f);
+    colors[ImGuiCol_BorderShadow]           = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
+    colors[ImGuiCol_FrameBg]                = ImVec4(0.43f, 0.43f, 0.43f, 0.39f);
+    colors[ImGuiCol_FrameBgHovered]         = ImVec4(0.47f, 0.47f, 0.69f, 0.40f);
+    colors[ImGuiCol_FrameBgActive]          = ImVec4(0.42f, 0.41f, 0.64f, 0.69f);
+    colors[ImGuiCol_TitleBg]                = ImVec4(0.27f, 0.27f, 0.54f, 0.83f);
+    colors[ImGuiCol_TitleBgActive]          = ImVec4(0.32f, 0.32f, 0.63f, 0.87f);
+    colors[ImGuiCol_TitleBgCollapsed]       = ImVec4(0.40f, 0.40f, 0.80f, 0.20f);
+    colors[ImGuiCol_MenuBarBg]              = ImVec4(0.40f, 0.40f, 0.55f, 0.80f);
+    colors[ImGuiCol_ScrollbarBg]            = ImVec4(0.20f, 0.25f, 0.30f, 0.60f);
+    colors[ImGuiCol_ScrollbarGrab]          = ImVec4(0.40f, 0.40f, 0.80f, 0.30f);
+    colors[ImGuiCol_ScrollbarGrabHovered]   = ImVec4(0.40f, 0.40f, 0.80f, 0.40f);
+    colors[ImGuiCol_ScrollbarGrabActive]    = ImVec4(0.41f, 0.39f, 0.80f, 0.60f);
+    colors[ImGuiCol_CheckMark]              = ImVec4(0.90f, 0.90f, 0.90f, 0.50f);
+    colors[ImGuiCol_SliderGrab]             = ImVec4(1.00f, 1.00f, 1.00f, 0.30f);
+    colors[ImGuiCol_SliderGrabActive]       = ImVec4(0.41f, 0.39f, 0.80f, 0.60f);
+    colors[ImGuiCol_Button]                 = ImVec4(0.35f, 0.40f, 0.61f, 0.62f);
+    colors[ImGuiCol_ButtonHovered]          = ImVec4(0.40f, 0.48f, 0.71f, 0.79f);
+    colors[ImGuiCol_ButtonActive]           = ImVec4(0.46f, 0.54f, 0.80f, 1.00f);
+    colors[ImGuiCol_Header]                 = ImVec4(0.40f, 0.40f, 0.90f, 0.45f);
+    colors[ImGuiCol_HeaderHovered]          = ImVec4(0.45f, 0.45f, 0.90f, 0.80f);
+    colors[ImGuiCol_HeaderActive]           = ImVec4(0.53f, 0.53f, 0.87f, 0.80f);
+    colors[ImGuiCol_Separator]              = ImVec4(0.50f, 0.50f, 0.50f, 1.00f);
+    colors[ImGuiCol_SeparatorHovered]       = ImVec4(0.60f, 0.60f, 0.70f, 1.00f);
+    colors[ImGuiCol_SeparatorActive]        = ImVec4(0.70f, 0.70f, 0.90f, 1.00f);
+    colors[ImGuiCol_ResizeGrip]             = ImVec4(1.00f, 1.00f, 1.00f, 0.16f);
+    colors[ImGuiCol_ResizeGripHovered]      = ImVec4(0.78f, 0.82f, 1.00f, 0.60f);
+    colors[ImGuiCol_ResizeGripActive]       = ImVec4(0.78f, 0.82f, 1.00f, 0.90f);
+    colors[ImGuiCol_PlotLines]              = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
+    colors[ImGuiCol_PlotLinesHovered]       = ImVec4(0.90f, 0.70f, 0.00f, 1.00f);
+    colors[ImGuiCol_PlotHistogram]          = ImVec4(0.90f, 0.70f, 0.00f, 1.00f);
+    colors[ImGuiCol_PlotHistogramHovered]   = ImVec4(1.00f, 0.60f, 0.00f, 1.00f);
+    colors[ImGuiCol_TextSelectedBg]         = ImVec4(0.00f, 0.00f, 1.00f, 0.35f);
+    colors[ImGuiCol_DragDropTarget]         = ImVec4(1.00f, 1.00f, 0.00f, 0.90f);
+    colors[ImGuiCol_NavHighlight]           = colors[ImGuiCol_HeaderHovered];
+    colors[ImGuiCol_NavWindowingHighlight]  = ImVec4(1.00f, 1.00f, 1.00f, 0.70f);
+    colors[ImGuiCol_NavWindowingDimBg]      = ImVec4(0.80f, 0.80f, 0.80f, 0.20f);
+    colors[ImGuiCol_ModalWindowDimBg]       = ImVec4(0.20f, 0.20f, 0.20f, 0.35f);
+}
+
+// Those light colors are better suited with a thicker font than the default one + FrameBorder
+void ImGui::StyleColorsLight(ImGuiStyle* dst)
+{
+    ImGuiStyle* style = dst ? dst : &ImGui::GetStyle();
+    ImVec4* colors = style->Colors;
+
+    colors[ImGuiCol_Text]                   = ImVec4(0.00f, 0.00f, 0.00f, 1.00f);
+    colors[ImGuiCol_TextDisabled]           = ImVec4(0.60f, 0.60f, 0.60f, 1.00f);
+    colors[ImGuiCol_WindowBg]               = ImVec4(0.94f, 0.94f, 0.94f, 1.00f);
+    colors[ImGuiCol_ChildBg]                = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
+    colors[ImGuiCol_PopupBg]                = ImVec4(1.00f, 1.00f, 1.00f, 0.98f);
+    colors[ImGuiCol_Border]                 = ImVec4(0.00f, 0.00f, 0.00f, 0.30f);
+    colors[ImGuiCol_BorderShadow]           = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
+    colors[ImGuiCol_FrameBg]                = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
+    colors[ImGuiCol_FrameBgHovered]         = ImVec4(0.26f, 0.59f, 0.98f, 0.40f);
+    colors[ImGuiCol_FrameBgActive]          = ImVec4(0.26f, 0.59f, 0.98f, 0.67f);
+    colors[ImGuiCol_TitleBg]                = ImVec4(0.96f, 0.96f, 0.96f, 1.00f);
+    colors[ImGuiCol_TitleBgActive]          = ImVec4(0.82f, 0.82f, 0.82f, 1.00f);
+    colors[ImGuiCol_TitleBgCollapsed]       = ImVec4(1.00f, 1.00f, 1.00f, 0.51f);
+    colors[ImGuiCol_MenuBarBg]              = ImVec4(0.86f, 0.86f, 0.86f, 1.00f);
+    colors[ImGuiCol_ScrollbarBg]            = ImVec4(0.98f, 0.98f, 0.98f, 0.53f);
+    colors[ImGuiCol_ScrollbarGrab]          = ImVec4(0.69f, 0.69f, 0.69f, 0.80f);
+    colors[ImGuiCol_ScrollbarGrabHovered]   = ImVec4(0.49f, 0.49f, 0.49f, 0.80f);
+    colors[ImGuiCol_ScrollbarGrabActive]    = ImVec4(0.49f, 0.49f, 0.49f, 1.00f);
+    colors[ImGuiCol_CheckMark]              = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);
+    colors[ImGuiCol_SliderGrab]             = ImVec4(0.26f, 0.59f, 0.98f, 0.78f);
+    colors[ImGuiCol_SliderGrabActive]       = ImVec4(0.46f, 0.54f, 0.80f, 0.60f);
+    colors[ImGuiCol_Button]                 = ImVec4(0.26f, 0.59f, 0.98f, 0.40f);
+    colors[ImGuiCol_ButtonHovered]          = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);
+    colors[ImGuiCol_ButtonActive]           = ImVec4(0.06f, 0.53f, 0.98f, 1.00f);
+    colors[ImGuiCol_Header]                 = ImVec4(0.26f, 0.59f, 0.98f, 0.31f);
+    colors[ImGuiCol_HeaderHovered]          = ImVec4(0.26f, 0.59f, 0.98f, 0.80f);
+    colors[ImGuiCol_HeaderActive]           = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);
+    colors[ImGuiCol_Separator]              = ImVec4(0.39f, 0.39f, 0.39f, 1.00f);
+    colors[ImGuiCol_SeparatorHovered]       = ImVec4(0.14f, 0.44f, 0.80f, 0.78f);
+    colors[ImGuiCol_SeparatorActive]        = ImVec4(0.14f, 0.44f, 0.80f, 1.00f);
+    colors[ImGuiCol_ResizeGrip]             = ImVec4(0.80f, 0.80f, 0.80f, 0.56f);
+    colors[ImGuiCol_ResizeGripHovered]      = ImVec4(0.26f, 0.59f, 0.98f, 0.67f);
+    colors[ImGuiCol_ResizeGripActive]       = ImVec4(0.26f, 0.59f, 0.98f, 0.95f);
+    colors[ImGuiCol_PlotLines]              = ImVec4(0.39f, 0.39f, 0.39f, 1.00f);
+    colors[ImGuiCol_PlotLinesHovered]       = ImVec4(1.00f, 0.43f, 0.35f, 1.00f);
+    colors[ImGuiCol_PlotHistogram]          = ImVec4(0.90f, 0.70f, 0.00f, 1.00f);
+    colors[ImGuiCol_PlotHistogramHovered]   = ImVec4(1.00f, 0.45f, 0.00f, 1.00f);
+    colors[ImGuiCol_TextSelectedBg]         = ImVec4(0.26f, 0.59f, 0.98f, 0.35f);
+    colors[ImGuiCol_DragDropTarget]         = ImVec4(0.26f, 0.59f, 0.98f, 0.95f);
+    colors[ImGuiCol_NavHighlight]           = colors[ImGuiCol_HeaderHovered];
+    colors[ImGuiCol_NavWindowingHighlight]  = ImVec4(0.70f, 0.70f, 0.70f, 0.70f);
+    colors[ImGuiCol_NavWindowingDimBg]      = ImVec4(0.20f, 0.20f, 0.20f, 0.20f);
+    colors[ImGuiCol_ModalWindowDimBg]       = ImVec4(0.20f, 0.20f, 0.20f, 0.35f);
+}
+
+//-----------------------------------------------------------------------------
+// ImDrawList
+//-----------------------------------------------------------------------------
+
+ImDrawListSharedData::ImDrawListSharedData()
+{
+    Font = NULL;
+    FontSize = 0.0f;
+    CurveTessellationTol = 0.0f;
+    ClipRectFullscreen = ImVec4(-8192.0f, -8192.0f, +8192.0f, +8192.0f);
+
+    // Const data
+    for (int i = 0; i < IM_ARRAYSIZE(CircleVtx12); i++)
+    {
+        const float a = ((float)i * 2 * IM_PI) / (float)IM_ARRAYSIZE(CircleVtx12);
+        CircleVtx12[i] = ImVec2(ImCos(a), ImSin(a));
+    }
+}
+
+void ImDrawList::Clear()
+{
+    CmdBuffer.resize(0);
+    IdxBuffer.resize(0);
+    VtxBuffer.resize(0);
+    Flags = ImDrawListFlags_AntiAliasedLines | ImDrawListFlags_AntiAliasedFill;
+    _VtxCurrentIdx = 0;
+    _VtxWritePtr = NULL;
+    _IdxWritePtr = NULL;
+    _ClipRectStack.resize(0);
+    _TextureIdStack.resize(0);
+    _Path.resize(0);
+    _ChannelsCurrent = 0;
+    _ChannelsCount = 1;
+    // NB: Do not clear channels so our allocations are re-used after the first frame.
+}
+
+void ImDrawList::ClearFreeMemory()
+{
+    CmdBuffer.clear();
+    IdxBuffer.clear();
+    VtxBuffer.clear();
+    _VtxCurrentIdx = 0;
+    _VtxWritePtr = NULL;
+    _IdxWritePtr = NULL;
+    _ClipRectStack.clear();
+    _TextureIdStack.clear();
+    _Path.clear();
+    _ChannelsCurrent = 0;
+    _ChannelsCount = 1;
+    for (int i = 0; i < _Channels.Size; i++)
+    {
+        if (i == 0) memset(&_Channels[0], 0, sizeof(_Channels[0]));  // channel 0 is a copy of CmdBuffer/IdxBuffer, don't destruct again
+        _Channels[i].CmdBuffer.clear();
+        _Channels[i].IdxBuffer.clear();
+    }
+    _Channels.clear();
+}
+
+ImDrawList* ImDrawList::CloneOutput() const
+{
+    ImDrawList* dst = IM_NEW(ImDrawList(NULL));
+    dst->CmdBuffer = CmdBuffer;
+    dst->IdxBuffer = IdxBuffer;
+    dst->VtxBuffer = VtxBuffer;
+    dst->Flags = Flags;
+    return dst;
+}
+
+// Using macros because C++ is a terrible language, we want guaranteed inline, no code in header, and no overhead in Debug builds
+#define GetCurrentClipRect()    (_ClipRectStack.Size ? _ClipRectStack.Data[_ClipRectStack.Size-1]  : _Data->ClipRectFullscreen)
+#define GetCurrentTextureId()   (_TextureIdStack.Size ? _TextureIdStack.Data[_TextureIdStack.Size-1] : NULL)
+
+void ImDrawList::AddDrawCmd()
+{
+    ImDrawCmd draw_cmd;
+    draw_cmd.ClipRect = GetCurrentClipRect();
+    draw_cmd.TextureId = GetCurrentTextureId();
+
+    IM_ASSERT(draw_cmd.ClipRect.x <= draw_cmd.ClipRect.z && draw_cmd.ClipRect.y <= draw_cmd.ClipRect.w);
+    CmdBuffer.push_back(draw_cmd);
+}
+
+void ImDrawList::AddCallback(ImDrawCallback callback, void* callback_data)
+{
+    ImDrawCmd* current_cmd = CmdBuffer.Size ? &CmdBuffer.back() : NULL;
+    if (!current_cmd || current_cmd->ElemCount != 0 || current_cmd->UserCallback != NULL)
+    {
+        AddDrawCmd();
+        current_cmd = &CmdBuffer.back();
+    }
+    current_cmd->UserCallback = callback;
+    current_cmd->UserCallbackData = callback_data;
+
+    AddDrawCmd(); // Force a new command after us (see comment below)
+}
+
+// Our scheme may appears a bit unusual, basically we want the most-common calls AddLine AddRect etc. to not have to perform any check so we always have a command ready in the stack.
+// The cost of figuring out if a new command has to be added or if we can merge is paid in those Update** functions only.
+void ImDrawList::UpdateClipRect()
+{
+    // If current command is used with different settings we need to add a new command
+    const ImVec4 curr_clip_rect = GetCurrentClipRect();
+    ImDrawCmd* curr_cmd = CmdBuffer.Size > 0 ? &CmdBuffer.Data[CmdBuffer.Size-1] : NULL;
+    if (!curr_cmd || (curr_cmd->ElemCount != 0 && memcmp(&curr_cmd->ClipRect, &curr_clip_rect, sizeof(ImVec4)) != 0) || curr_cmd->UserCallback != NULL)
+    {
+        AddDrawCmd();
+        return;
+    }
+
+    // Try to merge with previous command if it matches, else use current command
+    ImDrawCmd* prev_cmd = CmdBuffer.Size > 1 ? curr_cmd - 1 : NULL;
+    if (curr_cmd->ElemCount == 0 && prev_cmd && memcmp(&prev_cmd->ClipRect, &curr_clip_rect, sizeof(ImVec4)) == 0 && prev_cmd->TextureId == GetCurrentTextureId() && prev_cmd->UserCallback == NULL)
+        CmdBuffer.pop_back();
+    else
+        curr_cmd->ClipRect = curr_clip_rect;
+}
+
+void ImDrawList::UpdateTextureID()
+{
+    // If current command is used with different settings we need to add a new command
+    const ImTextureID curr_texture_id = GetCurrentTextureId();
+    ImDrawCmd* curr_cmd = CmdBuffer.Size ? &CmdBuffer.back() : NULL;
+    if (!curr_cmd || (curr_cmd->ElemCount != 0 && curr_cmd->TextureId != curr_texture_id) || curr_cmd->UserCallback != NULL)
+    {
+        AddDrawCmd();
+        return;
+    }
+
+    // Try to merge with previous command if it matches, else use current command
+    ImDrawCmd* prev_cmd = CmdBuffer.Size > 1 ? curr_cmd - 1 : NULL;
+    if (curr_cmd->ElemCount == 0 && prev_cmd && prev_cmd->TextureId == curr_texture_id && memcmp(&prev_cmd->ClipRect, &GetCurrentClipRect(), sizeof(ImVec4)) == 0 && prev_cmd->UserCallback == NULL)
+        CmdBuffer.pop_back();
+    else
+        curr_cmd->TextureId = curr_texture_id;
+}
+
+#undef GetCurrentClipRect
+#undef GetCurrentTextureId
+
+// Render-level scissoring. This is passed down to your render function but not used for CPU-side coarse clipping. Prefer using higher-level ImGui::PushClipRect() to affect logic (hit-testing and widget culling)
+void ImDrawList::PushClipRect(ImVec2 cr_min, ImVec2 cr_max, bool intersect_with_current_clip_rect)
+{
+    ImVec4 cr(cr_min.x, cr_min.y, cr_max.x, cr_max.y);
+    if (intersect_with_current_clip_rect && _ClipRectStack.Size)
+    {
+        ImVec4 current = _ClipRectStack.Data[_ClipRectStack.Size-1];
+        if (cr.x < current.x) cr.x = current.x;
+        if (cr.y < current.y) cr.y = current.y;
+        if (cr.z > current.z) cr.z = current.z;
+        if (cr.w > current.w) cr.w = current.w;
+    }
+    cr.z = ImMax(cr.x, cr.z);
+    cr.w = ImMax(cr.y, cr.w);
+
+    _ClipRectStack.push_back(cr);
+    UpdateClipRect();
+}
+
+void ImDrawList::PushClipRectFullScreen()
+{
+    PushClipRect(ImVec2(_Data->ClipRectFullscreen.x, _Data->ClipRectFullscreen.y), ImVec2(_Data->ClipRectFullscreen.z, _Data->ClipRectFullscreen.w));
+}
+
+void ImDrawList::PopClipRect()
+{
+    IM_ASSERT(_ClipRectStack.Size > 0);
+    _ClipRectStack.pop_back();
+    UpdateClipRect();
+}
+
+void ImDrawList::PushTextureID(ImTextureID texture_id)
+{
+    _TextureIdStack.push_back(texture_id);
+    UpdateTextureID();
+}
+
+void ImDrawList::PopTextureID()
+{
+    IM_ASSERT(_TextureIdStack.Size > 0);
+    _TextureIdStack.pop_back();
+    UpdateTextureID();
+}
+
+void ImDrawList::ChannelsSplit(int channels_count)
+{
+    IM_ASSERT(_ChannelsCurrent == 0 && _ChannelsCount == 1);
+    int old_channels_count = _Channels.Size;
+    if (old_channels_count < channels_count)
+        _Channels.resize(channels_count);
+    _ChannelsCount = channels_count;
+
+    // _Channels[] (24/32 bytes each) hold storage that we'll swap with this->_CmdBuffer/_IdxBuffer
+    // The content of _Channels[0] at this point doesn't matter. We clear it to make state tidy in a debugger but we don't strictly need to.
+    // When we switch to the next channel, we'll copy _CmdBuffer/_IdxBuffer into _Channels[0] and then _Channels[1] into _CmdBuffer/_IdxBuffer
+    memset(&_Channels[0], 0, sizeof(ImDrawChannel));
+    for (int i = 1; i < channels_count; i++)
+    {
+        if (i >= old_channels_count)
+        {
+            IM_PLACEMENT_NEW(&_Channels[i]) ImDrawChannel();
+        }
+        else
+        {
+            _Channels[i].CmdBuffer.resize(0);
+            _Channels[i].IdxBuffer.resize(0);
+        }
+        if (_Channels[i].CmdBuffer.Size == 0)
+        {
+            ImDrawCmd draw_cmd;
+            draw_cmd.ClipRect = _ClipRectStack.back();
+            draw_cmd.TextureId = _TextureIdStack.back();
+            _Channels[i].CmdBuffer.push_back(draw_cmd);
+        }
+    }
+}
+
+void ImDrawList::ChannelsMerge()
+{
+    // Note that we never use or rely on channels.Size because it is merely a buffer that we never shrink back to 0 to keep all sub-buffers ready for use.
+    if (_ChannelsCount <= 1)
+        return;
+
+    ChannelsSetCurrent(0);
+    if (CmdBuffer.Size && CmdBuffer.back().ElemCount == 0)
+        CmdBuffer.pop_back();
+
+    int new_cmd_buffer_count = 0, new_idx_buffer_count = 0;
+    for (int i = 1; i < _ChannelsCount; i++)
+    {
+        ImDrawChannel& ch = _Channels[i];
+        if (ch.CmdBuffer.Size && ch.CmdBuffer.back().ElemCount == 0)
+            ch.CmdBuffer.pop_back();
+        new_cmd_buffer_count += ch.CmdBuffer.Size;
+        new_idx_buffer_count += ch.IdxBuffer.Size;
+    }
+    CmdBuffer.resize(CmdBuffer.Size + new_cmd_buffer_count);
+    IdxBuffer.resize(IdxBuffer.Size + new_idx_buffer_count);
+
+    ImDrawCmd* cmd_write = CmdBuffer.Data + CmdBuffer.Size - new_cmd_buffer_count;
+    _IdxWritePtr = IdxBuffer.Data + IdxBuffer.Size - new_idx_buffer_count;
+    for (int i = 1; i < _ChannelsCount; i++)
+    {
+        ImDrawChannel& ch = _Channels[i];
+        if (int sz = ch.CmdBuffer.Size) { memcpy(cmd_write, ch.CmdBuffer.Data, sz * sizeof(ImDrawCmd)); cmd_write += sz; }
+        if (int sz = ch.IdxBuffer.Size) { memcpy(_IdxWritePtr, ch.IdxBuffer.Data, sz * sizeof(ImDrawIdx)); _IdxWritePtr += sz; }
+    }
+    UpdateClipRect(); // We call this instead of AddDrawCmd(), so that empty channels won't produce an extra draw call.
+    _ChannelsCount = 1;
+}
+
+void ImDrawList::ChannelsSetCurrent(int idx)
+{
+    IM_ASSERT(idx < _ChannelsCount);
+    if (_ChannelsCurrent == idx) return;
+    memcpy(&_Channels.Data[_ChannelsCurrent].CmdBuffer, &CmdBuffer, sizeof(CmdBuffer)); // copy 12 bytes, four times
+    memcpy(&_Channels.Data[_ChannelsCurrent].IdxBuffer, &IdxBuffer, sizeof(IdxBuffer));
+    _ChannelsCurrent = idx;
+    memcpy(&CmdBuffer, &_Channels.Data[_ChannelsCurrent].CmdBuffer, sizeof(CmdBuffer));
+    memcpy(&IdxBuffer, &_Channels.Data[_ChannelsCurrent].IdxBuffer, sizeof(IdxBuffer));
+    _IdxWritePtr = IdxBuffer.Data + IdxBuffer.Size;
+}
+
+// NB: this can be called with negative count for removing primitives (as long as the result does not underflow)
+void ImDrawList::PrimReserve(int idx_count, int vtx_count)
+{
+    ImDrawCmd& draw_cmd = CmdBuffer.Data[CmdBuffer.Size-1];
+    draw_cmd.ElemCount += idx_count;
+
+    int vtx_buffer_old_size = VtxBuffer.Size;
+    VtxBuffer.resize(vtx_buffer_old_size + vtx_count);
+    _VtxWritePtr = VtxBuffer.Data + vtx_buffer_old_size;
+
+    int idx_buffer_old_size = IdxBuffer.Size;
+    IdxBuffer.resize(idx_buffer_old_size + idx_count);
+    _IdxWritePtr = IdxBuffer.Data + idx_buffer_old_size;
+}
+
+// Fully unrolled with inline call to keep our debug builds decently fast.
+void ImDrawList::PrimRect(const ImVec2& a, const ImVec2& c, ImU32 col)
+{
+    ImVec2 b(c.x, a.y), d(a.x, c.y), uv(_Data->TexUvWhitePixel);
+    ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx;
+    _IdxWritePtr[0] = idx; _IdxWritePtr[1] = (ImDrawIdx)(idx+1); _IdxWritePtr[2] = (ImDrawIdx)(idx+2);
+    _IdxWritePtr[3] = idx; _IdxWritePtr[4] = (ImDrawIdx)(idx+2); _IdxWritePtr[5] = (ImDrawIdx)(idx+3);
+    _VtxWritePtr[0].pos = a; _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col;
+    _VtxWritePtr[1].pos = b; _VtxWritePtr[1].uv = uv; _VtxWritePtr[1].col = col;
+    _VtxWritePtr[2].pos = c; _VtxWritePtr[2].uv = uv; _VtxWritePtr[2].col = col;
+    _VtxWritePtr[3].pos = d; _VtxWritePtr[3].uv = uv; _VtxWritePtr[3].col = col;
+    _VtxWritePtr += 4;
+    _VtxCurrentIdx += 4;
+    _IdxWritePtr += 6;
+}
+
+void ImDrawList::PrimRectUV(const ImVec2& a, const ImVec2& c, const ImVec2& uv_a, const ImVec2& uv_c, ImU32 col)
+{
+    ImVec2 b(c.x, a.y), d(a.x, c.y), uv_b(uv_c.x, uv_a.y), uv_d(uv_a.x, uv_c.y);
+    ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx;
+    _IdxWritePtr[0] = idx; _IdxWritePtr[1] = (ImDrawIdx)(idx+1); _IdxWritePtr[2] = (ImDrawIdx)(idx+2);
+    _IdxWritePtr[3] = idx; _IdxWritePtr[4] = (ImDrawIdx)(idx+2); _IdxWritePtr[5] = (ImDrawIdx)(idx+3);
+    _VtxWritePtr[0].pos = a; _VtxWritePtr[0].uv = uv_a; _VtxWritePtr[0].col = col;
+    _VtxWritePtr[1].pos = b; _VtxWritePtr[1].uv = uv_b; _VtxWritePtr[1].col = col;
+    _VtxWritePtr[2].pos = c; _VtxWritePtr[2].uv = uv_c; _VtxWritePtr[2].col = col;
+    _VtxWritePtr[3].pos = d; _VtxWritePtr[3].uv = uv_d; _VtxWritePtr[3].col = col;
+    _VtxWritePtr += 4;
+    _VtxCurrentIdx += 4;
+    _IdxWritePtr += 6;
+}
+
+void ImDrawList::PrimQuadUV(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, const ImVec2& uv_a, const ImVec2& uv_b, const ImVec2& uv_c, const ImVec2& uv_d, ImU32 col)
+{
+    ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx;
+    _IdxWritePtr[0] = idx; _IdxWritePtr[1] = (ImDrawIdx)(idx+1); _IdxWritePtr[2] = (ImDrawIdx)(idx+2);
+    _IdxWritePtr[3] = idx; _IdxWritePtr[4] = (ImDrawIdx)(idx+2); _IdxWritePtr[5] = (ImDrawIdx)(idx+3);
+    _VtxWritePtr[0].pos = a; _VtxWritePtr[0].uv = uv_a; _VtxWritePtr[0].col = col;
+    _VtxWritePtr[1].pos = b; _VtxWritePtr[1].uv = uv_b; _VtxWritePtr[1].col = col;
+    _VtxWritePtr[2].pos = c; _VtxWritePtr[2].uv = uv_c; _VtxWritePtr[2].col = col;
+    _VtxWritePtr[3].pos = d; _VtxWritePtr[3].uv = uv_d; _VtxWritePtr[3].col = col;
+    _VtxWritePtr += 4;
+    _VtxCurrentIdx += 4;
+    _IdxWritePtr += 6;
+}
+
+// TODO: Thickness anti-aliased lines cap are missing their AA fringe.
+void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 col, bool closed, float thickness)
+{
+    if (points_count < 2)
+        return;
+
+    const ImVec2 uv = _Data->TexUvWhitePixel;
+
+    int count = points_count;
+    if (!closed)
+        count = points_count-1;
+
+    const bool thick_line = thickness > 1.0f;
+    if (Flags & ImDrawListFlags_AntiAliasedLines)
+    {
+        // Anti-aliased stroke
+        const float AA_SIZE = 1.0f;
+        const ImU32 col_trans = col & ~IM_COL32_A_MASK;
+
+        const int idx_count = thick_line ? count*18 : count*12;
+        const int vtx_count = thick_line ? points_count*4 : points_count*3;
+        PrimReserve(idx_count, vtx_count);
+
+        // Temporary buffer
+        ImVec2* temp_normals = (ImVec2*)alloca(points_count * (thick_line ? 5 : 3) * sizeof(ImVec2));
+        ImVec2* temp_points = temp_normals + points_count;
+
+        for (int i1 = 0; i1 < count; i1++)
+        {
+            const int i2 = (i1+1) == points_count ? 0 : i1+1;
+            ImVec2 diff = points[i2] - points[i1];
+            diff *= ImInvLength(diff, 1.0f);
+            temp_normals[i1].x = diff.y;
+            temp_normals[i1].y = -diff.x;
+        }
+        if (!closed)
+            temp_normals[points_count-1] = temp_normals[points_count-2];
+
+        if (!thick_line)
+        {
+            if (!closed)
+            {
+                temp_points[0] = points[0] + temp_normals[0] * AA_SIZE;
+                temp_points[1] = points[0] - temp_normals[0] * AA_SIZE;
+                temp_points[(points_count-1)*2+0] = points[points_count-1] + temp_normals[points_count-1] * AA_SIZE;
+                temp_points[(points_count-1)*2+1] = points[points_count-1] - temp_normals[points_count-1] * AA_SIZE;
+            }
+
+            // FIXME-OPT: Merge the different loops, possibly remove the temporary buffer.
+            unsigned int idx1 = _VtxCurrentIdx;
+            for (int i1 = 0; i1 < count; i1++)
+            {
+                const int i2 = (i1+1) == points_count ? 0 : i1+1;
+                unsigned int idx2 = (i1+1) == points_count ? _VtxCurrentIdx : idx1+3;
+
+                // Average normals
+                ImVec2 dm = (temp_normals[i1] + temp_normals[i2]) * 0.5f;
+                float dmr2 = dm.x*dm.x + dm.y*dm.y;
+                if (dmr2 > 0.000001f)
+                {
+                    float scale = 1.0f / dmr2;
+                    if (scale > 100.0f) scale = 100.0f;
+                    dm *= scale;
+                }
+                dm *= AA_SIZE;
+                temp_points[i2*2+0] = points[i2] + dm;
+                temp_points[i2*2+1] = points[i2] - dm;
+
+                // Add indexes
+                _IdxWritePtr[0] = (ImDrawIdx)(idx2+0); _IdxWritePtr[1] = (ImDrawIdx)(idx1+0); _IdxWritePtr[2] = (ImDrawIdx)(idx1+2);
+                _IdxWritePtr[3] = (ImDrawIdx)(idx1+2); _IdxWritePtr[4] = (ImDrawIdx)(idx2+2); _IdxWritePtr[5] = (ImDrawIdx)(idx2+0);
+                _IdxWritePtr[6] = (ImDrawIdx)(idx2+1); _IdxWritePtr[7] = (ImDrawIdx)(idx1+1); _IdxWritePtr[8] = (ImDrawIdx)(idx1+0);
+                _IdxWritePtr[9] = (ImDrawIdx)(idx1+0); _IdxWritePtr[10]= (ImDrawIdx)(idx2+0); _IdxWritePtr[11]= (ImDrawIdx)(idx2+1);
+                _IdxWritePtr += 12;
+
+                idx1 = idx2;
+            }
+
+            // Add vertexes
+            for (int i = 0; i < points_count; i++)
+            {
+                _VtxWritePtr[0].pos = points[i];          _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col;
+                _VtxWritePtr[1].pos = temp_points[i*2+0]; _VtxWritePtr[1].uv = uv; _VtxWritePtr[1].col = col_trans;
+                _VtxWritePtr[2].pos = temp_points[i*2+1]; _VtxWritePtr[2].uv = uv; _VtxWritePtr[2].col = col_trans;
+                _VtxWritePtr += 3;
+            }
+        }
+        else
+        {
+            const float half_inner_thickness = (thickness - AA_SIZE) * 0.5f;
+            if (!closed)
+            {
+                temp_points[0] = points[0] + temp_normals[0] * (half_inner_thickness + AA_SIZE);
+                temp_points[1] = points[0] + temp_normals[0] * (half_inner_thickness);
+                temp_points[2] = points[0] - temp_normals[0] * (half_inner_thickness);
+                temp_points[3] = points[0] - temp_normals[0] * (half_inner_thickness + AA_SIZE);
+                temp_points[(points_count-1)*4+0] = points[points_count-1] + temp_normals[points_count-1] * (half_inner_thickness + AA_SIZE);
+                temp_points[(points_count-1)*4+1] = points[points_count-1] + temp_normals[points_count-1] * (half_inner_thickness);
+                temp_points[(points_count-1)*4+2] = points[points_count-1] - temp_normals[points_count-1] * (half_inner_thickness);
+                temp_points[(points_count-1)*4+3] = points[points_count-1] - temp_normals[points_count-1] * (half_inner_thickness + AA_SIZE);
+            }
+
+            // FIXME-OPT: Merge the different loops, possibly remove the temporary buffer.
+            unsigned int idx1 = _VtxCurrentIdx;
+            for (int i1 = 0; i1 < count; i1++)
+            {
+                const int i2 = (i1+1) == points_count ? 0 : i1+1;
+                unsigned int idx2 = (i1+1) == points_count ? _VtxCurrentIdx : idx1+4;
+
+                // Average normals
+                ImVec2 dm = (temp_normals[i1] + temp_normals[i2]) * 0.5f;
+                float dmr2 = dm.x*dm.x + dm.y*dm.y;
+                if (dmr2 > 0.000001f)
+                {
+                    float scale = 1.0f / dmr2;
+                    if (scale > 100.0f) scale = 100.0f;
+                    dm *= scale;
+                }
+                ImVec2 dm_out = dm * (half_inner_thickness + AA_SIZE);
+                ImVec2 dm_in = dm * half_inner_thickness;
+                temp_points[i2*4+0] = points[i2] + dm_out;
+                temp_points[i2*4+1] = points[i2] + dm_in;
+                temp_points[i2*4+2] = points[i2] - dm_in;
+                temp_points[i2*4+3] = points[i2] - dm_out;
+
+                // Add indexes
+                _IdxWritePtr[0]  = (ImDrawIdx)(idx2+1); _IdxWritePtr[1]  = (ImDrawIdx)(idx1+1); _IdxWritePtr[2]  = (ImDrawIdx)(idx1+2);
+                _IdxWritePtr[3]  = (ImDrawIdx)(idx1+2); _IdxWritePtr[4]  = (ImDrawIdx)(idx2+2); _IdxWritePtr[5]  = (ImDrawIdx)(idx2+1);
+                _IdxWritePtr[6]  = (ImDrawIdx)(idx2+1); _IdxWritePtr[7]  = (ImDrawIdx)(idx1+1); _IdxWritePtr[8]  = (ImDrawIdx)(idx1+0);
+                _IdxWritePtr[9]  = (ImDrawIdx)(idx1+0); _IdxWritePtr[10] = (ImDrawIdx)(idx2+0); _IdxWritePtr[11] = (ImDrawIdx)(idx2+1);
+                _IdxWritePtr[12] = (ImDrawIdx)(idx2+2); _IdxWritePtr[13] = (ImDrawIdx)(idx1+2); _IdxWritePtr[14] = (ImDrawIdx)(idx1+3);
+                _IdxWritePtr[15] = (ImDrawIdx)(idx1+3); _IdxWritePtr[16] = (ImDrawIdx)(idx2+3); _IdxWritePtr[17] = (ImDrawIdx)(idx2+2);
+                _IdxWritePtr += 18;
+
+                idx1 = idx2;
+            }
+
+            // Add vertexes
+            for (int i = 0; i < points_count; i++)
+            {
+                _VtxWritePtr[0].pos = temp_points[i*4+0]; _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col_trans;
+                _VtxWritePtr[1].pos = temp_points[i*4+1]; _VtxWritePtr[1].uv = uv; _VtxWritePtr[1].col = col;
+                _VtxWritePtr[2].pos = temp_points[i*4+2]; _VtxWritePtr[2].uv = uv; _VtxWritePtr[2].col = col;
+                _VtxWritePtr[3].pos = temp_points[i*4+3]; _VtxWritePtr[3].uv = uv; _VtxWritePtr[3].col = col_trans;
+                _VtxWritePtr += 4;
+            }
+        }
+        _VtxCurrentIdx += (ImDrawIdx)vtx_count;
+    }
+    else
+    {
+        // Non Anti-aliased Stroke
+        const int idx_count = count*6;
+        const int vtx_count = count*4;      // FIXME-OPT: Not sharing edges
+        PrimReserve(idx_count, vtx_count);
+
+        for (int i1 = 0; i1 < count; i1++)
+        {
+            const int i2 = (i1+1) == points_count ? 0 : i1+1;
+            const ImVec2& p1 = points[i1];
+            const ImVec2& p2 = points[i2];
+            ImVec2 diff = p2 - p1;
+            diff *= ImInvLength(diff, 1.0f);
+
+            const float dx = diff.x * (thickness * 0.5f);
+            const float dy = diff.y * (thickness * 0.5f);
+            _VtxWritePtr[0].pos.x = p1.x + dy; _VtxWritePtr[0].pos.y = p1.y - dx; _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col;
+            _VtxWritePtr[1].pos.x = p2.x + dy; _VtxWritePtr[1].pos.y = p2.y - dx; _VtxWritePtr[1].uv = uv; _VtxWritePtr[1].col = col;
+            _VtxWritePtr[2].pos.x = p2.x - dy; _VtxWritePtr[2].pos.y = p2.y + dx; _VtxWritePtr[2].uv = uv; _VtxWritePtr[2].col = col;
+            _VtxWritePtr[3].pos.x = p1.x - dy; _VtxWritePtr[3].pos.y = p1.y + dx; _VtxWritePtr[3].uv = uv; _VtxWritePtr[3].col = col;
+            _VtxWritePtr += 4;
+
+            _IdxWritePtr[0] = (ImDrawIdx)(_VtxCurrentIdx); _IdxWritePtr[1] = (ImDrawIdx)(_VtxCurrentIdx+1); _IdxWritePtr[2] = (ImDrawIdx)(_VtxCurrentIdx+2);
+            _IdxWritePtr[3] = (ImDrawIdx)(_VtxCurrentIdx); _IdxWritePtr[4] = (ImDrawIdx)(_VtxCurrentIdx+2); _IdxWritePtr[5] = (ImDrawIdx)(_VtxCurrentIdx+3);
+            _IdxWritePtr += 6;
+            _VtxCurrentIdx += 4;
+        }
+    }
+}
+
+void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_count, ImU32 col)
+{
+    const ImVec2 uv = _Data->TexUvWhitePixel;
+
+    if (Flags & ImDrawListFlags_AntiAliasedFill)
+    {
+        // Anti-aliased Fill
+        const float AA_SIZE = 1.0f;
+        const ImU32 col_trans = col & ~IM_COL32_A_MASK;
+        const int idx_count = (points_count-2)*3 + points_count*6;
+        const int vtx_count = (points_count*2);
+        PrimReserve(idx_count, vtx_count);
+
+        // Add indexes for fill
+        unsigned int vtx_inner_idx = _VtxCurrentIdx;
+        unsigned int vtx_outer_idx = _VtxCurrentIdx+1;
+        for (int i = 2; i < points_count; i++)
+        {
+            _IdxWritePtr[0] = (ImDrawIdx)(vtx_inner_idx); _IdxWritePtr[1] = (ImDrawIdx)(vtx_inner_idx+((i-1)<<1)); _IdxWritePtr[2] = (ImDrawIdx)(vtx_inner_idx+(i<<1));
+            _IdxWritePtr += 3;
+        }
+
+        // Compute normals
+        ImVec2* temp_normals = (ImVec2*)alloca(points_count * sizeof(ImVec2));
+        for (int i0 = points_count-1, i1 = 0; i1 < points_count; i0 = i1++)
+        {
+            const ImVec2& p0 = points[i0];
+            const ImVec2& p1 = points[i1];
+            ImVec2 diff = p1 - p0;
+            diff *= ImInvLength(diff, 1.0f);
+            temp_normals[i0].x = diff.y;
+            temp_normals[i0].y = -diff.x;
+        }
+
+        for (int i0 = points_count-1, i1 = 0; i1 < points_count; i0 = i1++)
+        {
+            // Average normals
+            const ImVec2& n0 = temp_normals[i0];
+            const ImVec2& n1 = temp_normals[i1];
+            ImVec2 dm = (n0 + n1) * 0.5f;
+            float dmr2 = dm.x*dm.x + dm.y*dm.y;
+            if (dmr2 > 0.000001f)
+            {
+                float scale = 1.0f / dmr2;
+                if (scale > 100.0f) scale = 100.0f;
+                dm *= scale;
+            }
+            dm *= AA_SIZE * 0.5f;
+
+            // Add vertices
+            _VtxWritePtr[0].pos = (points[i1] - dm); _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col;        // Inner
+            _VtxWritePtr[1].pos = (points[i1] + dm); _VtxWritePtr[1].uv = uv; _VtxWritePtr[1].col = col_trans;  // Outer
+            _VtxWritePtr += 2;
+
+            // Add indexes for fringes
+            _IdxWritePtr[0] = (ImDrawIdx)(vtx_inner_idx+(i1<<1)); _IdxWritePtr[1] = (ImDrawIdx)(vtx_inner_idx+(i0<<1)); _IdxWritePtr[2] = (ImDrawIdx)(vtx_outer_idx+(i0<<1));
+            _IdxWritePtr[3] = (ImDrawIdx)(vtx_outer_idx+(i0<<1)); _IdxWritePtr[4] = (ImDrawIdx)(vtx_outer_idx+(i1<<1)); _IdxWritePtr[5] = (ImDrawIdx)(vtx_inner_idx+(i1<<1));
+            _IdxWritePtr += 6;
+        }
+        _VtxCurrentIdx += (ImDrawIdx)vtx_count;
+    }
+    else
+    {
+        // Non Anti-aliased Fill
+        const int idx_count = (points_count-2)*3;
+        const int vtx_count = points_count;
+        PrimReserve(idx_count, vtx_count);
+        for (int i = 0; i < vtx_count; i++)
+        {
+            _VtxWritePtr[0].pos = points[i]; _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col;
+            _VtxWritePtr++;
+        }
+        for (int i = 2; i < points_count; i++)
+        {
+            _IdxWritePtr[0] = (ImDrawIdx)(_VtxCurrentIdx); _IdxWritePtr[1] = (ImDrawIdx)(_VtxCurrentIdx+i-1); _IdxWritePtr[2] = (ImDrawIdx)(_VtxCurrentIdx+i);
+            _IdxWritePtr += 3;
+        }
+        _VtxCurrentIdx += (ImDrawIdx)vtx_count;
+    }
+}
+
+void ImDrawList::PathArcToFast(const ImVec2& centre, float radius, int a_min_of_12, int a_max_of_12)
+{
+    if (radius == 0.0f || a_min_of_12 > a_max_of_12)
+    {
+        _Path.push_back(centre);
+        return;
+    }
+    _Path.reserve(_Path.Size + (a_max_of_12 - a_min_of_12 + 1));
+    for (int a = a_min_of_12; a <= a_max_of_12; a++)
+    {
+        const ImVec2& c = _Data->CircleVtx12[a % IM_ARRAYSIZE(_Data->CircleVtx12)];
+        _Path.push_back(ImVec2(centre.x + c.x * radius, centre.y + c.y * radius));
+    }
+}
+
+void ImDrawList::PathArcTo(const ImVec2& centre, float radius, float a_min, float a_max, int num_segments)
+{
+    if (radius == 0.0f)
+    {
+        _Path.push_back(centre);
+        return;
+    }
+    _Path.reserve(_Path.Size + (num_segments + 1));
+    for (int i = 0; i <= num_segments; i++)
+    {
+        const float a = a_min + ((float)i / (float)num_segments) * (a_max - a_min);
+        _Path.push_back(ImVec2(centre.x + ImCos(a) * radius, centre.y + ImSin(a) * radius));
+    }
+}
+
+static void PathBezierToCasteljau(ImVector<ImVec2>* path, float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, float tess_tol, int level)
+{
+    float dx = x4 - x1;
+    float dy = y4 - y1;
+    float d2 = ((x2 - x4) * dy - (y2 - y4) * dx);
+    float d3 = ((x3 - x4) * dy - (y3 - y4) * dx);
+    d2 = (d2 >= 0) ? d2 : -d2;
+    d3 = (d3 >= 0) ? d3 : -d3;
+    if ((d2+d3) * (d2+d3) < tess_tol * (dx*dx + dy*dy))
+    {
+        path->push_back(ImVec2(x4, y4));
+    }
+    else if (level < 10)
+    {
+        float x12 = (x1+x2)*0.5f,       y12 = (y1+y2)*0.5f;
+        float x23 = (x2+x3)*0.5f,       y23 = (y2+y3)*0.5f;
+        float x34 = (x3+x4)*0.5f,       y34 = (y3+y4)*0.5f;
+        float x123 = (x12+x23)*0.5f,    y123 = (y12+y23)*0.5f;
+        float x234 = (x23+x34)*0.5f,    y234 = (y23+y34)*0.5f;
+        float x1234 = (x123+x234)*0.5f, y1234 = (y123+y234)*0.5f;
+
+        PathBezierToCasteljau(path, x1,y1,        x12,y12,    x123,y123,  x1234,y1234, tess_tol, level+1);
+        PathBezierToCasteljau(path, x1234,y1234,  x234,y234,  x34,y34,    x4,y4,       tess_tol, level+1);
+    }
+}
+
+void ImDrawList::PathBezierCurveTo(const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, int num_segments)
+{
+    ImVec2 p1 = _Path.back();
+    if (num_segments == 0)
+    {
+        // Auto-tessellated
+        PathBezierToCasteljau(&_Path, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y, _Data->CurveTessellationTol, 0);
+    }
+    else
+    {
+        float t_step = 1.0f / (float)num_segments;
+        for (int i_step = 1; i_step <= num_segments; i_step++)
+        {
+            float t = t_step * i_step;
+            float u = 1.0f - t;
+            float w1 = u*u*u;
+            float w2 = 3*u*u*t;
+            float w3 = 3*u*t*t;
+            float w4 = t*t*t;
+            _Path.push_back(ImVec2(w1*p1.x + w2*p2.x + w3*p3.x + w4*p4.x, w1*p1.y + w2*p2.y + w3*p3.y + w4*p4.y));
+        }
+    }
+}
+
+void ImDrawList::PathRect(const ImVec2& a, const ImVec2& b, float rounding, int rounding_corners)
+{
+    rounding = ImMin(rounding, ImFabs(b.x - a.x) * ( ((rounding_corners & ImDrawCornerFlags_Top)  == ImDrawCornerFlags_Top)  || ((rounding_corners & ImDrawCornerFlags_Bot)   == ImDrawCornerFlags_Bot)   ? 0.5f : 1.0f ) - 1.0f);
+    rounding = ImMin(rounding, ImFabs(b.y - a.y) * ( ((rounding_corners & ImDrawCornerFlags_Left) == ImDrawCornerFlags_Left) || ((rounding_corners & ImDrawCornerFlags_Right) == ImDrawCornerFlags_Right) ? 0.5f : 1.0f ) - 1.0f);
+
+    if (rounding <= 0.0f || rounding_corners == 0)
+    {
+        PathLineTo(a);
+        PathLineTo(ImVec2(b.x, a.y));
+        PathLineTo(b);
+        PathLineTo(ImVec2(a.x, b.y));
+    }
+    else
+    {
+        const float rounding_tl = (rounding_corners & ImDrawCornerFlags_TopLeft) ? rounding : 0.0f;
+        const float rounding_tr = (rounding_corners & ImDrawCornerFlags_TopRight) ? rounding : 0.0f;
+        const float rounding_br = (rounding_corners & ImDrawCornerFlags_BotRight) ? rounding : 0.0f;
+        const float rounding_bl = (rounding_corners & ImDrawCornerFlags_BotLeft) ? rounding : 0.0f;
+        PathArcToFast(ImVec2(a.x + rounding_tl, a.y + rounding_tl), rounding_tl, 6, 9);
+        PathArcToFast(ImVec2(b.x - rounding_tr, a.y + rounding_tr), rounding_tr, 9, 12);
+        PathArcToFast(ImVec2(b.x - rounding_br, b.y - rounding_br), rounding_br, 0, 3);
+        PathArcToFast(ImVec2(a.x + rounding_bl, b.y - rounding_bl), rounding_bl, 3, 6);
+    }
+}
+
+void ImDrawList::AddLine(const ImVec2& a, const ImVec2& b, ImU32 col, float thickness)
+{
+    if ((col & IM_COL32_A_MASK) == 0)
+        return;
+    PathLineTo(a + ImVec2(0.5f,0.5f));
+    PathLineTo(b + ImVec2(0.5f,0.5f));
+    PathStroke(col, false, thickness);
+}
+
+// a: upper-left, b: lower-right. we don't render 1 px sized rectangles properly.
+void ImDrawList::AddRect(const ImVec2& a, const ImVec2& b, ImU32 col, float rounding, int rounding_corners_flags, float thickness)
+{
+    if ((col & IM_COL32_A_MASK) == 0)
+        return;
+    if (Flags & ImDrawListFlags_AntiAliasedLines)
+        PathRect(a + ImVec2(0.5f,0.5f), b - ImVec2(0.50f,0.50f), rounding, rounding_corners_flags);
+    else
+        PathRect(a + ImVec2(0.5f,0.5f), b - ImVec2(0.49f,0.49f), rounding, rounding_corners_flags); // Better looking lower-right corner and rounded non-AA shapes.
+    PathStroke(col, true, thickness);
+}
+
+void ImDrawList::AddRectFilled(const ImVec2& a, const ImVec2& b, ImU32 col, float rounding, int rounding_corners_flags)
+{
+    if ((col & IM_COL32_A_MASK) == 0)
+        return;
+    if (rounding > 0.0f)
+    {
+        PathRect(a, b, rounding, rounding_corners_flags);
+        PathFillConvex(col);
+    }
+    else
+    {
+        PrimReserve(6, 4);
+        PrimRect(a, b, col);
+    }
+}
+
+void ImDrawList::AddRectFilledMultiColor(const ImVec2& a, const ImVec2& c, ImU32 col_upr_left, ImU32 col_upr_right, ImU32 col_bot_right, ImU32 col_bot_left)
+{
+    if (((col_upr_left | col_upr_right | col_bot_right | col_bot_left) & IM_COL32_A_MASK) == 0)
+        return;
+
+    const ImVec2 uv = _Data->TexUvWhitePixel;
+    PrimReserve(6, 4);
+    PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx)); PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx+1)); PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx+2));
+    PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx)); PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx+2)); PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx+3));
+    PrimWriteVtx(a, uv, col_upr_left);
+    PrimWriteVtx(ImVec2(c.x, a.y), uv, col_upr_right);
+    PrimWriteVtx(c, uv, col_bot_right);
+    PrimWriteVtx(ImVec2(a.x, c.y), uv, col_bot_left);
+}
+
+void ImDrawList::AddQuad(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, ImU32 col, float thickness)
+{
+    if ((col & IM_COL32_A_MASK) == 0)
+        return;
+
+    PathLineTo(a);
+    PathLineTo(b);
+    PathLineTo(c);
+    PathLineTo(d);
+    PathStroke(col, true, thickness);
+}
+
+void ImDrawList::AddQuadFilled(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, ImU32 col)
+{
+    if ((col & IM_COL32_A_MASK) == 0)
+        return;
+
+    PathLineTo(a);
+    PathLineTo(b);
+    PathLineTo(c);
+    PathLineTo(d);
+    PathFillConvex(col);
+}
+
+void ImDrawList::AddTriangle(const ImVec2& a, const ImVec2& b, const ImVec2& c, ImU32 col, float thickness)
+{
+    if ((col & IM_COL32_A_MASK) == 0)
+        return;
+
+    PathLineTo(a);
+    PathLineTo(b);
+    PathLineTo(c);
+    PathStroke(col, true, thickness);
+}
+
+void ImDrawList::AddTriangleFilled(const ImVec2& a, const ImVec2& b, const ImVec2& c, ImU32 col)
+{
+    if ((col & IM_COL32_A_MASK) == 0)
+        return;
+
+    PathLineTo(a);
+    PathLineTo(b);
+    PathLineTo(c);
+    PathFillConvex(col);
+}
+
+void ImDrawList::AddCircle(const ImVec2& centre, float radius, ImU32 col, int num_segments, float thickness)
+{
+    if ((col & IM_COL32_A_MASK) == 0)
+        return;
+
+    const float a_max = IM_PI*2.0f * ((float)num_segments - 1.0f) / (float)num_segments;
+    PathArcTo(centre, radius-0.5f, 0.0f, a_max, num_segments);
+    PathStroke(col, true, thickness);
+}
+
+void ImDrawList::AddCircleFilled(const ImVec2& centre, float radius, ImU32 col, int num_segments)
+{
+    if ((col & IM_COL32_A_MASK) == 0)
+        return;
+
+    const float a_max = IM_PI*2.0f * ((float)num_segments - 1.0f) / (float)num_segments;
+    PathArcTo(centre, radius, 0.0f, a_max, num_segments);
+    PathFillConvex(col);
+}
+
+void ImDrawList::AddBezierCurve(const ImVec2& pos0, const ImVec2& cp0, const ImVec2& cp1, const ImVec2& pos1, ImU32 col, float thickness, int num_segments)
+{
+    if ((col & IM_COL32_A_MASK) == 0)
+        return;
+
+    PathLineTo(pos0);
+    PathBezierCurveTo(cp0, cp1, pos1, num_segments);
+    PathStroke(col, false, thickness);
+}
+
+void ImDrawList::AddText(const ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end, float wrap_width, const ImVec4* cpu_fine_clip_rect)
+{
+    if ((col & IM_COL32_A_MASK) == 0)
+        return;
+
+    if (text_end == NULL)
+        text_end = text_begin + strlen(text_begin);
+    if (text_begin == text_end)
+        return;
+
+    // Pull default font/size from the shared ImDrawListSharedData instance
+    if (font == NULL)
+        font = _Data->Font;
+    if (font_size == 0.0f)
+        font_size = _Data->FontSize;
+
+    IM_ASSERT(font->ContainerAtlas->TexID == _TextureIdStack.back());  // Use high-level ImGui::PushFont() or low-level ImDrawList::PushTextureId() to change font.
+
+    ImVec4 clip_rect = _ClipRectStack.back();
+    if (cpu_fine_clip_rect)
+    {
+        clip_rect.x = ImMax(clip_rect.x, cpu_fine_clip_rect->x);
+        clip_rect.y = ImMax(clip_rect.y, cpu_fine_clip_rect->y);
+        clip_rect.z = ImMin(clip_rect.z, cpu_fine_clip_rect->z);
+        clip_rect.w = ImMin(clip_rect.w, cpu_fine_clip_rect->w);
+    }
+    font->RenderText(this, font_size, pos, col, clip_rect, text_begin, text_end, wrap_width, cpu_fine_clip_rect != NULL);
+}
+
+void ImDrawList::AddText(const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end)
+{
+    AddText(NULL, 0.0f, pos, col, text_begin, text_end);
+}
+
+void ImDrawList::AddImage(ImTextureID user_texture_id, const ImVec2& a, const ImVec2& b, const ImVec2& uv_a, const ImVec2& uv_b, ImU32 col)
+{
+    if ((col & IM_COL32_A_MASK) == 0)
+        return;
+
+    const bool push_texture_id = _TextureIdStack.empty() || user_texture_id != _TextureIdStack.back();
+    if (push_texture_id)
+        PushTextureID(user_texture_id);
+
+    PrimReserve(6, 4);
+    PrimRectUV(a, b, uv_a, uv_b, col);
+
+    if (push_texture_id)
+        PopTextureID();
+}
+
+void ImDrawList::AddImageQuad(ImTextureID user_texture_id, const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, const ImVec2& uv_a, const ImVec2& uv_b, const ImVec2& uv_c, const ImVec2& uv_d, ImU32 col)
+{
+    if ((col & IM_COL32_A_MASK) == 0)
+        return;
+
+    const bool push_texture_id = _TextureIdStack.empty() || user_texture_id != _TextureIdStack.back();
+    if (push_texture_id)
+        PushTextureID(user_texture_id);
+
+    PrimReserve(6, 4);
+    PrimQuadUV(a, b, c, d, uv_a, uv_b, uv_c, uv_d, col);
+
+    if (push_texture_id)
+        PopTextureID();
+}
+
+void ImDrawList::AddImageRounded(ImTextureID user_texture_id, const ImVec2& a, const ImVec2& b, const ImVec2& uv_a, const ImVec2& uv_b, ImU32 col, float rounding, int rounding_corners)
+{
+    if ((col & IM_COL32_A_MASK) == 0)
+        return;
+
+    if (rounding <= 0.0f || (rounding_corners & ImDrawCornerFlags_All) == 0)
+    {
+        AddImage(user_texture_id, a, b, uv_a, uv_b, col);
+        return;
+    }
+
+    const bool push_texture_id = _TextureIdStack.empty() || user_texture_id != _TextureIdStack.back();
+    if (push_texture_id)
+        PushTextureID(user_texture_id);
+
+    int vert_start_idx = VtxBuffer.Size;
+    PathRect(a, b, rounding, rounding_corners);
+    PathFillConvex(col);
+    int vert_end_idx = VtxBuffer.Size;
+    ImGui::ShadeVertsLinearUV(this, vert_start_idx, vert_end_idx, a, b, uv_a, uv_b, true);
+
+    if (push_texture_id)
+        PopTextureID();
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] ImDrawData
+//-----------------------------------------------------------------------------
+
+// For backward compatibility: convert all buffers from indexed to de-indexed, in case you cannot render indexed. Note: this is slow and most likely a waste of resources. Always prefer indexed rendering!
+void ImDrawData::DeIndexAllBuffers()
+{
+    ImVector<ImDrawVert> new_vtx_buffer;
+    TotalVtxCount = TotalIdxCount = 0;
+    for (int i = 0; i < CmdListsCount; i++)
+    {
+        ImDrawList* cmd_list = CmdLists[i];
+        if (cmd_list->IdxBuffer.empty())
+            continue;
+        new_vtx_buffer.resize(cmd_list->IdxBuffer.Size);
+        for (int j = 0; j < cmd_list->IdxBuffer.Size; j++)
+            new_vtx_buffer[j] = cmd_list->VtxBuffer[cmd_list->IdxBuffer[j]];
+        cmd_list->VtxBuffer.swap(new_vtx_buffer);
+        cmd_list->IdxBuffer.resize(0);
+        TotalVtxCount += cmd_list->VtxBuffer.Size;
+    }
+}
+
+// Helper to scale the ClipRect field of each ImDrawCmd. Use if your final output buffer is at a different scale than ImGui expects, or if there is a difference between your window resolution and framebuffer resolution.
+void ImDrawData::ScaleClipRects(const ImVec2& scale)
+{
+    for (int i = 0; i < CmdListsCount; i++)
+    {
+        ImDrawList* cmd_list = CmdLists[i];
+        for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)
+        {
+            ImDrawCmd* cmd = &cmd_list->CmdBuffer[cmd_i];
+            cmd->ClipRect = ImVec4(cmd->ClipRect.x * scale.x, cmd->ClipRect.y * scale.y, cmd->ClipRect.z * scale.x, cmd->ClipRect.w * scale.y);
+        }
+    }
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] Helpers ShadeVertsXXX functions
+//-----------------------------------------------------------------------------
+
+// Generic linear color gradient, write to RGB fields, leave A untouched.
+void ImGui::ShadeVertsLinearColorGradientKeepAlpha(ImDrawList* draw_list, int vert_start_idx, int vert_end_idx, ImVec2 gradient_p0, ImVec2 gradient_p1, ImU32 col0, ImU32 col1)
+{
+    ImVec2 gradient_extent = gradient_p1 - gradient_p0;
+    float gradient_inv_length2 = 1.0f / ImLengthSqr(gradient_extent);
+    ImDrawVert* vert_start = draw_list->VtxBuffer.Data + vert_start_idx;
+    ImDrawVert* vert_end = draw_list->VtxBuffer.Data + vert_end_idx;
+    for (ImDrawVert* vert = vert_start; vert < vert_end; vert++)
+    {
+        float d = ImDot(vert->pos - gradient_p0, gradient_extent);
+        float t = ImClamp(d * gradient_inv_length2, 0.0f, 1.0f);
+        int r = ImLerp((int)(col0 >> IM_COL32_R_SHIFT) & 0xFF, (int)(col1 >> IM_COL32_R_SHIFT) & 0xFF, t);
+        int g = ImLerp((int)(col0 >> IM_COL32_G_SHIFT) & 0xFF, (int)(col1 >> IM_COL32_G_SHIFT) & 0xFF, t);
+        int b = ImLerp((int)(col0 >> IM_COL32_B_SHIFT) & 0xFF, (int)(col1 >> IM_COL32_B_SHIFT) & 0xFF, t);
+        vert->col = (r << IM_COL32_R_SHIFT) | (g << IM_COL32_G_SHIFT) | (b << IM_COL32_B_SHIFT) | (vert->col & IM_COL32_A_MASK);
+    }
+}
+
+// Distribute UV over (a, b) rectangle
+void ImGui::ShadeVertsLinearUV(ImDrawList* draw_list, int vert_start_idx, int vert_end_idx, const ImVec2& a, const ImVec2& b, const ImVec2& uv_a, const ImVec2& uv_b, bool clamp)
+{
+    const ImVec2 size = b - a;
+    const ImVec2 uv_size = uv_b - uv_a;
+    const ImVec2 scale = ImVec2(
+        size.x != 0.0f ? (uv_size.x / size.x) : 0.0f,
+        size.y != 0.0f ? (uv_size.y / size.y) : 0.0f);
+
+    ImDrawVert* vert_start = draw_list->VtxBuffer.Data + vert_start_idx;
+    ImDrawVert* vert_end = draw_list->VtxBuffer.Data + vert_end_idx;
+    if (clamp)
+    {
+        const ImVec2 min = ImMin(uv_a, uv_b);
+        const ImVec2 max = ImMax(uv_a, uv_b);
+        for (ImDrawVert* vertex = vert_start; vertex < vert_end; ++vertex)
+            vertex->uv = ImClamp(uv_a + ImMul(ImVec2(vertex->pos.x, vertex->pos.y) - a, scale), min, max);
+    }
+    else
+    {
+        for (ImDrawVert* vertex = vert_start; vertex < vert_end; ++vertex)
+            vertex->uv = uv_a + ImMul(ImVec2(vertex->pos.x, vertex->pos.y) - a, scale);
+    }
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] ImFontConfig
+//-----------------------------------------------------------------------------
+
+ImFontConfig::ImFontConfig()
+{
+    FontData = NULL;
+    FontDataSize = 0;
+    FontDataOwnedByAtlas = true;
+    FontNo = 0;
+    SizePixels = 0.0f;
+    OversampleH = 3;
+    OversampleV = 1;
+    PixelSnapH = false;
+    GlyphExtraSpacing = ImVec2(0.0f, 0.0f);
+    GlyphOffset = ImVec2(0.0f, 0.0f);
+    GlyphRanges = NULL;
+    GlyphMinAdvanceX = 0.0f;
+    GlyphMaxAdvanceX = FLT_MAX;
+    MergeMode = false;
+    RasterizerFlags = 0x00;
+    RasterizerMultiply = 1.0f;
+    memset(Name, 0, sizeof(Name));
+    DstFont = NULL;
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] ImFontAtlas
+//-----------------------------------------------------------------------------
+
+// A work of art lies ahead! (. = white layer, X = black layer, others are blank)
+// The white texels on the top left are the ones we'll use everywhere in ImGui to render filled shapes.
+const int FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF = 108;
+const int FONT_ATLAS_DEFAULT_TEX_DATA_H      = 27;
+const unsigned int FONT_ATLAS_DEFAULT_TEX_DATA_ID = 0x80000000;
+static const char FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS[FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF * FONT_ATLAS_DEFAULT_TEX_DATA_H + 1] =
+{
+    "..-         -XXXXXXX-    X    -           X           -XXXXXXX          -          XXXXXXX-     XX          "
+    "..-         -X.....X-   X.X   -          X.X          -X.....X          -          X.....X-    X..X         "
+    "---         -XXX.XXX-  X...X  -         X...X         -X....X           -           X....X-    X..X         "
+    "X           -  X.X  - X.....X -        X.....X        -X...X            -            X...X-    X..X         "
+    "XX          -  X.X  -X.......X-       X.......X       -X..X.X           -           X.X..X-    X..X         "
+    "X.X         -  X.X  -XXXX.XXXX-       XXXX.XXXX       -X.X X.X          -          X.X X.X-    X..XXX       "
+    "X..X        -  X.X  -   X.X   -          X.X          -XX   X.X         -         X.X   XX-    X..X..XXX    "
+    "X...X       -  X.X  -   X.X   -    XX    X.X    XX    -      X.X        -        X.X      -    X..X..X..XX  "
+    "X....X      -  X.X  -   X.X   -   X.X    X.X    X.X   -       X.X       -       X.X       -    X..X..X..X.X "
+    "X.....X     -  X.X  -   X.X   -  X..X    X.X    X..X  -        X.X      -      X.X        -XXX X..X..X..X..X"
+    "X......X    -  X.X  -   X.X   - X...XXXXXX.XXXXXX...X -         X.X   XX-XX   X.X         -X..XX........X..X"
+    "X.......X   -  X.X  -   X.X   -X.....................X-          X.X X.X-X.X X.X          -X...X...........X"
+    "X........X  -  X.X  -   X.X   - X...XXXXXX.XXXXXX...X -           X.X..X-X..X.X           - X..............X"
+    "X.........X -XXX.XXX-   X.X   -  X..X    X.X    X..X  -            X...X-X...X            -  X.............X"
+    "X..........X-X.....X-   X.X   -   X.X    X.X    X.X   -           X....X-X....X           -  X.............X"
+    "X......XXXXX-XXXXXXX-   X.X   -    XX    X.X    XX    -          X.....X-X.....X          -   X............X"
+    "X...X..X    ---------   X.X   -          X.X          -          XXXXXXX-XXXXXXX          -   X...........X "
+    "X..X X..X   -       -XXXX.XXXX-       XXXX.XXXX       -------------------------------------    X..........X "
+    "X.X  X..X   -       -X.......X-       X.......X       -    XX           XX    -           -    X..........X "
+    "XX    X..X  -       - X.....X -        X.....X        -   X.X           X.X   -           -     X........X  "
+    "      X..X          -  X...X  -         X...X         -  X..X           X..X  -           -     X........X  "
+    "       XX           -   X.X   -          X.X          - X...XXXXXXXXXXXXX...X -           -     XXXXXXXXXX  "
+    "------------        -    X    -           X           -X.....................X-           ------------------"
+    "                    ----------------------------------- X...XXXXXXXXXXXXX...X -                             "
+    "                                                      -  X..X           X..X  -                             "
+    "                                                      -   X.X           X.X   -                             "
+    "                                                      -    XX           XX    -                             "
+};
+
+static const ImVec2 FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[ImGuiMouseCursor_COUNT][3] =
+{
+    // Pos ........ Size ......... Offset ......
+    { ImVec2( 0,3), ImVec2(12,19), ImVec2( 0, 0) }, // ImGuiMouseCursor_Arrow
+    { ImVec2(13,0), ImVec2( 7,16), ImVec2( 1, 8) }, // ImGuiMouseCursor_TextInput
+    { ImVec2(31,0), ImVec2(23,23), ImVec2(11,11) }, // ImGuiMouseCursor_ResizeAll
+    { ImVec2(21,0), ImVec2( 9,23), ImVec2( 4,11) }, // ImGuiMouseCursor_ResizeNS
+    { ImVec2(55,18),ImVec2(23, 9), ImVec2(11, 4) }, // ImGuiMouseCursor_ResizeEW
+    { ImVec2(73,0), ImVec2(17,17), ImVec2( 8, 8) }, // ImGuiMouseCursor_ResizeNESW
+    { ImVec2(55,0), ImVec2(17,17), ImVec2( 8, 8) }, // ImGuiMouseCursor_ResizeNWSE
+    { ImVec2(91,0), ImVec2(17,22), ImVec2( 5, 0) }, // ImGuiMouseCursor_Hand
+};
+
+ImFontAtlas::ImFontAtlas()
+{
+    Locked = false;
+    Flags = ImFontAtlasFlags_None;
+    TexID = NULL;
+    TexDesiredWidth = 0;
+    TexGlyphPadding = 1;
+
+    TexPixelsAlpha8 = NULL;
+    TexPixelsRGBA32 = NULL;
+    TexWidth = TexHeight = 0;
+    TexUvScale = ImVec2(0.0f, 0.0f);
+    TexUvWhitePixel = ImVec2(0.0f, 0.0f);
+    for (int n = 0; n < IM_ARRAYSIZE(CustomRectIds); n++)
+        CustomRectIds[n] = -1;
+}
+
+ImFontAtlas::~ImFontAtlas()
+{
+    IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!");
+    Clear();
+}
+
+void    ImFontAtlas::ClearInputData()
+{
+    IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!");
+    for (int i = 0; i < ConfigData.Size; i++)
+        if (ConfigData[i].FontData && ConfigData[i].FontDataOwnedByAtlas)
+        {
+            ImGui::MemFree(ConfigData[i].FontData);
+            ConfigData[i].FontData = NULL;
+        }
+
+    // When clearing this we lose access to the font name and other information used to build the font.
+    for (int i = 0; i < Fonts.Size; i++)
+        if (Fonts[i]->ConfigData >= ConfigData.Data && Fonts[i]->ConfigData < ConfigData.Data + ConfigData.Size)
+        {
+            Fonts[i]->ConfigData = NULL;
+            Fonts[i]->ConfigDataCount = 0;
+        }
+    ConfigData.clear();
+    CustomRects.clear();
+    for (int n = 0; n < IM_ARRAYSIZE(CustomRectIds); n++)
+        CustomRectIds[n] = -1;
+}
+
+void    ImFontAtlas::ClearTexData()
+{
+    IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!");
+    if (TexPixelsAlpha8)
+        ImGui::MemFree(TexPixelsAlpha8);
+    if (TexPixelsRGBA32)
+        ImGui::MemFree(TexPixelsRGBA32);
+    TexPixelsAlpha8 = NULL;
+    TexPixelsRGBA32 = NULL;
+}
+
+void    ImFontAtlas::ClearFonts()
+{
+    IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!");
+    for (int i = 0; i < Fonts.Size; i++)
+        IM_DELETE(Fonts[i]);
+    Fonts.clear();
+}
+
+void    ImFontAtlas::Clear()
+{
+    ClearInputData();
+    ClearTexData();
+    ClearFonts();
+}
+
+void    ImFontAtlas::GetTexDataAsAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel)
+{
+    // Build atlas on demand
+    if (TexPixelsAlpha8 == NULL)
+    {
+        if (ConfigData.empty())
+            AddFontDefault();
+        Build();
+    }
+
+    *out_pixels = TexPixelsAlpha8;
+    if (out_width) *out_width = TexWidth;
+    if (out_height) *out_height = TexHeight;
+    if (out_bytes_per_pixel) *out_bytes_per_pixel = 1;
+}
+
+void    ImFontAtlas::GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel)
+{
+    // Convert to RGBA32 format on demand
+    // Although it is likely to be the most commonly used format, our font rendering is 1 channel / 8 bpp
+    if (!TexPixelsRGBA32)
+    {
+        unsigned char* pixels = NULL;
+        GetTexDataAsAlpha8(&pixels, NULL, NULL);
+        if (pixels)
+        {
+            TexPixelsRGBA32 = (unsigned int*)ImGui::MemAlloc((size_t)(TexWidth * TexHeight * 4));
+            const unsigned char* src = pixels;
+            unsigned int* dst = TexPixelsRGBA32;
+            for (int n = TexWidth * TexHeight; n > 0; n--)
+                *dst++ = IM_COL32(255, 255, 255, (unsigned int)(*src++));
+        }
+    }
+
+    *out_pixels = (unsigned char*)TexPixelsRGBA32;
+    if (out_width) *out_width = TexWidth;
+    if (out_height) *out_height = TexHeight;
+    if (out_bytes_per_pixel) *out_bytes_per_pixel = 4;
+}
+
+ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg)
+{
+    IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!");
+    IM_ASSERT(font_cfg->FontData != NULL && font_cfg->FontDataSize > 0);
+    IM_ASSERT(font_cfg->SizePixels > 0.0f);
+
+    // Create new font
+    if (!font_cfg->MergeMode)
+        Fonts.push_back(IM_NEW(ImFont));
+    else
+        IM_ASSERT(!Fonts.empty()); // When using MergeMode make sure that a font has already been added before. You can use ImGui::GetIO().Fonts->AddFontDefault() to add the default imgui font.
+
+    ConfigData.push_back(*font_cfg);
+    ImFontConfig& new_font_cfg = ConfigData.back();
+    if (!new_font_cfg.DstFont)
+        new_font_cfg.DstFont = Fonts.back();
+    if (!new_font_cfg.FontDataOwnedByAtlas)
+    {
+        new_font_cfg.FontData = ImGui::MemAlloc(new_font_cfg.FontDataSize);
+        new_font_cfg.FontDataOwnedByAtlas = true;
+        memcpy(new_font_cfg.FontData, font_cfg->FontData, (size_t)new_font_cfg.FontDataSize);
+    }
+
+    // Invalidate texture
+    ClearTexData();
+    return new_font_cfg.DstFont;
+}
+
+// Default font TTF is compressed with stb_compress then base85 encoded (see misc/fonts/binary_to_compressed_c.cpp for encoder)
+static unsigned int stb_decompress_length(const unsigned char *input);
+static unsigned int stb_decompress(unsigned char *output, const unsigned char *input, unsigned int length);
+static const char*  GetDefaultCompressedFontDataTTFBase85();
+static unsigned int Decode85Byte(char c)                                    { return c >= '\\' ? c-36 : c-35; }
+static void         Decode85(const unsigned char* src, unsigned char* dst)
+{
+    while (*src)
+    {
+        unsigned int tmp = Decode85Byte(src[0]) + 85*(Decode85Byte(src[1]) + 85*(Decode85Byte(src[2]) + 85*(Decode85Byte(src[3]) + 85*Decode85Byte(src[4]))));
+        dst[0] = ((tmp >> 0) & 0xFF); dst[1] = ((tmp >> 8) & 0xFF); dst[2] = ((tmp >> 16) & 0xFF); dst[3] = ((tmp >> 24) & 0xFF);   // We can't assume little-endianness.
+        src += 5;
+        dst += 4;
+    }
+}
+
+// Load embedded ProggyClean.ttf at size 13, disable oversampling
+ImFont* ImFontAtlas::AddFontDefault(const ImFontConfig* font_cfg_template)
+{
+    ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig();
+    if (!font_cfg_template)
+    {
+        font_cfg.OversampleH = font_cfg.OversampleV = 1;
+        font_cfg.PixelSnapH = true;
+    }
+    if (font_cfg.Name[0] == '\0') strcpy(font_cfg.Name, "ProggyClean.ttf, 13px");
+    if (font_cfg.SizePixels <= 0.0f) font_cfg.SizePixels = 13.0f;
+
+    const char* ttf_compressed_base85 = GetDefaultCompressedFontDataTTFBase85();
+    const ImWchar* glyph_ranges = font_cfg.GlyphRanges != NULL ? font_cfg.GlyphRanges : GetGlyphRangesDefault();
+    ImFont* font = AddFontFromMemoryCompressedBase85TTF(ttf_compressed_base85, font_cfg.SizePixels, &font_cfg, glyph_ranges);
+    font->DisplayOffset.y = 1.0f;
+    return font;
+}
+
+ImFont* ImFontAtlas::AddFontFromFileTTF(const char* filename, float size_pixels, const ImFontConfig* font_cfg_template, const ImWchar* glyph_ranges)
+{
+    IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!");
+    size_t data_size = 0;
+    void* data = ImFileLoadToMemory(filename, "rb", &data_size, 0);
+    if (!data)
+    {
+        IM_ASSERT(0); // Could not load file.
+        return NULL;
+    }
+    ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig();
+    if (font_cfg.Name[0] == '\0')
+    {
+        // Store a short copy of filename into into the font name for convenience
+        const char* p;
+        for (p = filename + strlen(filename); p > filename && p[-1] != '/' && p[-1] != '\\'; p--) {}
+        ImFormatString(font_cfg.Name, IM_ARRAYSIZE(font_cfg.Name), "%s, %.0fpx", p, size_pixels);
+    }
+    return AddFontFromMemoryTTF(data, (int)data_size, size_pixels, &font_cfg, glyph_ranges);
+}
+
+// NB: Transfer ownership of 'ttf_data' to ImFontAtlas, unless font_cfg_template->FontDataOwnedByAtlas == false. Owned TTF buffer will be deleted after Build().
+ImFont* ImFontAtlas::AddFontFromMemoryTTF(void* ttf_data, int ttf_size, float size_pixels, const ImFontConfig* font_cfg_template, const ImWchar* glyph_ranges)
+{
+    IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!");
+    ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig();
+    IM_ASSERT(font_cfg.FontData == NULL);
+    font_cfg.FontData = ttf_data;
+    font_cfg.FontDataSize = ttf_size;
+    font_cfg.SizePixels = size_pixels;
+    if (glyph_ranges)
+        font_cfg.GlyphRanges = glyph_ranges;
+    return AddFont(&font_cfg);
+}
+
+ImFont* ImFontAtlas::AddFontFromMemoryCompressedTTF(const void* compressed_ttf_data, int compressed_ttf_size, float size_pixels, const ImFontConfig* font_cfg_template, const ImWchar* glyph_ranges)
+{
+    const unsigned int buf_decompressed_size = stb_decompress_length((const unsigned char*)compressed_ttf_data);
+    unsigned char* buf_decompressed_data = (unsigned char *)ImGui::MemAlloc(buf_decompressed_size);
+    stb_decompress(buf_decompressed_data, (const unsigned char*)compressed_ttf_data, (unsigned int)compressed_ttf_size);
+
+    ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig();
+    IM_ASSERT(font_cfg.FontData == NULL);
+    font_cfg.FontDataOwnedByAtlas = true;
+    return AddFontFromMemoryTTF(buf_decompressed_data, (int)buf_decompressed_size, size_pixels, &font_cfg, glyph_ranges);
+}
+
+ImFont* ImFontAtlas::AddFontFromMemoryCompressedBase85TTF(const char* compressed_ttf_data_base85, float size_pixels, const ImFontConfig* font_cfg, const ImWchar* glyph_ranges)
+{
+    int compressed_ttf_size = (((int)strlen(compressed_ttf_data_base85) + 4) / 5) * 4;
+    void* compressed_ttf = ImGui::MemAlloc((size_t)compressed_ttf_size);
+    Decode85((const unsigned char*)compressed_ttf_data_base85, (unsigned char*)compressed_ttf);
+    ImFont* font = AddFontFromMemoryCompressedTTF(compressed_ttf, compressed_ttf_size, size_pixels, font_cfg, glyph_ranges);
+    ImGui::MemFree(compressed_ttf);
+    return font;
+}
+
+int ImFontAtlas::AddCustomRectRegular(unsigned int id, int width, int height)
+{
+    IM_ASSERT(id >= 0x10000);
+    IM_ASSERT(width > 0 && width <= 0xFFFF);
+    IM_ASSERT(height > 0 && height <= 0xFFFF);
+    CustomRect r;
+    r.ID = id;
+    r.Width = (unsigned short)width;
+    r.Height = (unsigned short)height;
+    CustomRects.push_back(r);
+    return CustomRects.Size - 1; // Return index
+}
+
+int ImFontAtlas::AddCustomRectFontGlyph(ImFont* font, ImWchar id, int width, int height, float advance_x, const ImVec2& offset)
+{
+    IM_ASSERT(font != NULL);
+    IM_ASSERT(width > 0 && width <= 0xFFFF);
+    IM_ASSERT(height > 0 && height <= 0xFFFF);
+    CustomRect r;
+    r.ID = id;
+    r.Width = (unsigned short)width;
+    r.Height = (unsigned short)height;
+    r.GlyphAdvanceX = advance_x;
+    r.GlyphOffset = offset;
+    r.Font = font;
+    CustomRects.push_back(r);
+    return CustomRects.Size - 1; // Return index
+}
+
+void ImFontAtlas::CalcCustomRectUV(const CustomRect* rect, ImVec2* out_uv_min, ImVec2* out_uv_max)
+{
+    IM_ASSERT(TexWidth > 0 && TexHeight > 0);   // Font atlas needs to be built before we can calculate UV coordinates
+    IM_ASSERT(rect->IsPacked());                // Make sure the rectangle has been packed
+    *out_uv_min = ImVec2((float)rect->X * TexUvScale.x, (float)rect->Y * TexUvScale.y);
+    *out_uv_max = ImVec2((float)(rect->X + rect->Width) * TexUvScale.x, (float)(rect->Y + rect->Height) * TexUvScale.y);
+}
+
+bool ImFontAtlas::GetMouseCursorTexData(ImGuiMouseCursor cursor_type, ImVec2* out_offset, ImVec2* out_size, ImVec2 out_uv_border[2], ImVec2 out_uv_fill[2])
+{
+    if (cursor_type <= ImGuiMouseCursor_None || cursor_type >= ImGuiMouseCursor_COUNT)
+        return false;
+    if (Flags & ImFontAtlasFlags_NoMouseCursors)
+        return false;
+
+    IM_ASSERT(CustomRectIds[0] != -1);
+    ImFontAtlas::CustomRect& r = CustomRects[CustomRectIds[0]];
+    IM_ASSERT(r.ID == FONT_ATLAS_DEFAULT_TEX_DATA_ID);
+    ImVec2 pos = FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[cursor_type][0] + ImVec2((float)r.X, (float)r.Y);
+    ImVec2 size = FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[cursor_type][1];
+    *out_size = size;
+    *out_offset = FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[cursor_type][2];
+    out_uv_border[0] = (pos) * TexUvScale;
+    out_uv_border[1] = (pos + size) * TexUvScale;
+    pos.x += FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF + 1;
+    out_uv_fill[0] = (pos) * TexUvScale;
+    out_uv_fill[1] = (pos + size) * TexUvScale;
+    return true;
+}
+
+bool    ImFontAtlas::Build()
+{
+    IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!");
+    return ImFontAtlasBuildWithStbTruetype(this);
+}
+
+void    ImFontAtlasBuildMultiplyCalcLookupTable(unsigned char out_table[256], float in_brighten_factor)
+{
+    for (unsigned int i = 0; i < 256; i++)
+    {
+        unsigned int value = (unsigned int)(i * in_brighten_factor);
+        out_table[i] = value > 255 ? 255 : (value & 0xFF);
+    }
+}
+
+void    ImFontAtlasBuildMultiplyRectAlpha8(const unsigned char table[256], unsigned char* pixels, int x, int y, int w, int h, int stride)
+{
+    unsigned char* data = pixels + x + y * stride;
+    for (int j = h; j > 0; j--, data += stride)
+        for (int i = 0; i < w; i++)
+            data[i] = table[data[i]];
+}
+
+bool    ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas)
+{
+    IM_ASSERT(atlas->ConfigData.Size > 0);
+
+    ImFontAtlasBuildRegisterDefaultCustomRects(atlas);
+
+    atlas->TexID = NULL;
+    atlas->TexWidth = atlas->TexHeight = 0;
+    atlas->TexUvScale = ImVec2(0.0f, 0.0f);
+    atlas->TexUvWhitePixel = ImVec2(0.0f, 0.0f);
+    atlas->ClearTexData();
+
+    // Count glyphs/ranges
+    int total_glyphs_count = 0;
+    int total_ranges_count = 0;
+    for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++)
+    {
+        ImFontConfig& cfg = atlas->ConfigData[input_i];
+        if (!cfg.GlyphRanges)
+            cfg.GlyphRanges = atlas->GetGlyphRangesDefault();
+        for (const ImWchar* in_range = cfg.GlyphRanges; in_range[0] && in_range[1]; in_range += 2, total_ranges_count++)
+            total_glyphs_count += (in_range[1] - in_range[0]) + 1;
+    }
+
+    // We need a width for the skyline algorithm. Using a dumb heuristic here to decide of width. User can override TexDesiredWidth and TexGlyphPadding if they wish.
+    // Width doesn't really matter much, but some API/GPU have texture size limitations and increasing width can decrease height.
+    atlas->TexWidth = (atlas->TexDesiredWidth > 0) ? atlas->TexDesiredWidth : (total_glyphs_count > 4000) ? 4096 : (total_glyphs_count > 2000) ? 2048 : (total_glyphs_count > 1000) ? 1024 : 512;
+    atlas->TexHeight = 0;
+
+    // Start packing
+    const int max_tex_height = 1024*32;
+    stbtt_pack_context spc = {};
+    if (!stbtt_PackBegin(&spc, NULL, atlas->TexWidth, max_tex_height, 0, atlas->TexGlyphPadding, NULL))
+        return false;
+    stbtt_PackSetOversampling(&spc, 1, 1);
+
+    // Pack our extra data rectangles first, so it will be on the upper-left corner of our texture (UV will have small values).
+    ImFontAtlasBuildPackCustomRects(atlas, spc.pack_info);
+
+    // Initialize font information (so we can error without any cleanup)
+    struct ImFontTempBuildData
+    {
+        stbtt_fontinfo      FontInfo;
+        stbrp_rect*         Rects;
+        int                 RectsCount;
+        stbtt_pack_range*   Ranges;
+        int                 RangesCount;
+    };
+    ImFontTempBuildData* tmp_array = (ImFontTempBuildData*)ImGui::MemAlloc((size_t)atlas->ConfigData.Size * sizeof(ImFontTempBuildData));
+    for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++)
+    {
+        ImFontConfig& cfg = atlas->ConfigData[input_i];
+        ImFontTempBuildData& tmp = tmp_array[input_i];
+        IM_ASSERT(cfg.DstFont && (!cfg.DstFont->IsLoaded() || cfg.DstFont->ContainerAtlas == atlas));
+
+        const int font_offset = stbtt_GetFontOffsetForIndex((unsigned char*)cfg.FontData, cfg.FontNo);
+        IM_ASSERT(font_offset >= 0 && "FontData is incorrect, or FontNo cannot be found.");
+        if (!stbtt_InitFont(&tmp.FontInfo, (unsigned char*)cfg.FontData, font_offset))
+        {
+            atlas->TexWidth = atlas->TexHeight = 0; // Reset output on failure
+            ImGui::MemFree(tmp_array);
+            return false;
+        }
+    }
+
+    // Allocate packing character data and flag packed characters buffer as non-packed (x0=y0=x1=y1=0)
+    int buf_packedchars_n = 0, buf_rects_n = 0, buf_ranges_n = 0;
+    stbtt_packedchar* buf_packedchars = (stbtt_packedchar*)ImGui::MemAlloc(total_glyphs_count * sizeof(stbtt_packedchar));
+    stbrp_rect* buf_rects = (stbrp_rect*)ImGui::MemAlloc(total_glyphs_count * sizeof(stbrp_rect));
+    stbtt_pack_range* buf_ranges = (stbtt_pack_range*)ImGui::MemAlloc(total_ranges_count * sizeof(stbtt_pack_range));
+    memset(buf_packedchars, 0, total_glyphs_count * sizeof(stbtt_packedchar));
+    memset(buf_rects, 0, total_glyphs_count * sizeof(stbrp_rect));              // Unnecessary but let's clear this for the sake of sanity.
+    memset(buf_ranges, 0, total_ranges_count * sizeof(stbtt_pack_range));
+
+    // First font pass: pack all glyphs (no rendering at this point, we are working with rectangles in an infinitely tall texture at this point)
+    for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++)
+    {
+        ImFontConfig& cfg = atlas->ConfigData[input_i];
+        ImFontTempBuildData& tmp = tmp_array[input_i];
+
+        // Setup ranges
+        int font_glyphs_count = 0;
+        int font_ranges_count = 0;
+        for (const ImWchar* in_range = cfg.GlyphRanges; in_range[0] && in_range[1]; in_range += 2, font_ranges_count++)
+            font_glyphs_count += (in_range[1] - in_range[0]) + 1;
+        tmp.Ranges = buf_ranges + buf_ranges_n;
+        tmp.RangesCount = font_ranges_count;
+        buf_ranges_n += font_ranges_count;
+        for (int i = 0; i < font_ranges_count; i++)
+        {
+            const ImWchar* in_range = &cfg.GlyphRanges[i * 2];
+            stbtt_pack_range& range = tmp.Ranges[i];
+            range.font_size = cfg.SizePixels;
+            range.first_unicode_codepoint_in_range = in_range[0];
+            range.num_chars = (in_range[1] - in_range[0]) + 1;
+            range.chardata_for_range = buf_packedchars + buf_packedchars_n;
+            buf_packedchars_n += range.num_chars;
+        }
+
+        // Gather the sizes of all rectangle we need
+        tmp.Rects = buf_rects + buf_rects_n;
+        tmp.RectsCount = font_glyphs_count;
+        buf_rects_n += font_glyphs_count;
+        stbtt_PackSetOversampling(&spc, cfg.OversampleH, cfg.OversampleV);
+        int n = stbtt_PackFontRangesGatherRects(&spc, &tmp.FontInfo, tmp.Ranges, tmp.RangesCount, tmp.Rects);
+        IM_ASSERT(n == font_glyphs_count);
+
+        // Detect missing glyphs and replace them with a zero-sized box instead of relying on the default glyphs
+        // This allows us merging overlapping icon fonts more easily.
+        int rect_i = 0;
+        for (int range_i = 0; range_i < tmp.RangesCount; range_i++)
+            for (int char_i = 0; char_i < tmp.Ranges[range_i].num_chars; char_i++, rect_i++)
+                if (stbtt_FindGlyphIndex(&tmp.FontInfo, tmp.Ranges[range_i].first_unicode_codepoint_in_range + char_i) == 0)
+                    tmp.Rects[rect_i].w = tmp.Rects[rect_i].h = 0;
+
+        // Pack
+        stbrp_pack_rects((stbrp_context*)spc.pack_info, tmp.Rects, n);
+
+        // Extend texture height
+        // Also mark missing glyphs as non-packed so we don't attempt to render into them
+        for (int i = 0; i < n; i++)
+        {
+            if (tmp.Rects[i].w == 0 && tmp.Rects[i].h == 0)
+                tmp.Rects[i].was_packed = 0;
+            if (tmp.Rects[i].was_packed)
+                atlas->TexHeight = ImMax(atlas->TexHeight, tmp.Rects[i].y + tmp.Rects[i].h);
+        }
+    }
+    IM_ASSERT(buf_rects_n == total_glyphs_count);
+    IM_ASSERT(buf_packedchars_n == total_glyphs_count);
+    IM_ASSERT(buf_ranges_n == total_ranges_count);
+
+    // Create texture
+    atlas->TexHeight = (atlas->Flags & ImFontAtlasFlags_NoPowerOfTwoHeight) ? (atlas->TexHeight + 1) : ImUpperPowerOfTwo(atlas->TexHeight);
+    atlas->TexUvScale = ImVec2(1.0f / atlas->TexWidth, 1.0f / atlas->TexHeight);
+    atlas->TexPixelsAlpha8 = (unsigned char*)ImGui::MemAlloc(atlas->TexWidth * atlas->TexHeight);
+    memset(atlas->TexPixelsAlpha8, 0, atlas->TexWidth * atlas->TexHeight);
+    spc.pixels = atlas->TexPixelsAlpha8;
+    spc.height = atlas->TexHeight;
+
+    // Second pass: render font characters
+    for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++)
+    {
+        ImFontConfig& cfg = atlas->ConfigData[input_i];
+        ImFontTempBuildData& tmp = tmp_array[input_i];
+        stbtt_PackSetOversampling(&spc, cfg.OversampleH, cfg.OversampleV);
+        stbtt_PackFontRangesRenderIntoRects(&spc, &tmp.FontInfo, tmp.Ranges, tmp.RangesCount, tmp.Rects);
+        if (cfg.RasterizerMultiply != 1.0f)
+        {
+            unsigned char multiply_table[256];
+            ImFontAtlasBuildMultiplyCalcLookupTable(multiply_table, cfg.RasterizerMultiply);
+            for (const stbrp_rect* r = tmp.Rects; r != tmp.Rects + tmp.RectsCount; r++)
+                if (r->was_packed)
+                    ImFontAtlasBuildMultiplyRectAlpha8(multiply_table, spc.pixels, r->x, r->y, r->w, r->h, spc.stride_in_bytes);
+        }
+        tmp.Rects = NULL;
+    }
+
+    // End packing
+    stbtt_PackEnd(&spc);
+    ImGui::MemFree(buf_rects);
+    buf_rects = NULL;
+
+    // Third pass: setup ImFont and glyphs for runtime
+    for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++)
+    {
+        ImFontConfig& cfg = atlas->ConfigData[input_i];
+        ImFontTempBuildData& tmp = tmp_array[input_i];
+        ImFont* dst_font = cfg.DstFont; // We can have multiple input fonts writing into a same destination font (when using MergeMode=true)
+        if (cfg.MergeMode)
+            dst_font->BuildLookupTable();
+
+        const float font_scale = stbtt_ScaleForPixelHeight(&tmp.FontInfo, cfg.SizePixels);
+        int unscaled_ascent, unscaled_descent, unscaled_line_gap;
+        stbtt_GetFontVMetrics(&tmp.FontInfo, &unscaled_ascent, &unscaled_descent, &unscaled_line_gap);
+
+        const float ascent = ImFloor(unscaled_ascent * font_scale + ((unscaled_ascent > 0.0f) ? +1 : -1));
+        const float descent = ImFloor(unscaled_descent * font_scale + ((unscaled_descent > 0.0f) ? +1 : -1));
+        ImFontAtlasBuildSetupFont(atlas, dst_font, &cfg, ascent, descent);
+        const float font_off_x = cfg.GlyphOffset.x;
+        const float font_off_y = cfg.GlyphOffset.y + (float)(int)(dst_font->Ascent + 0.5f);
+
+        for (int i = 0; i < tmp.RangesCount; i++)
+        {
+            stbtt_pack_range& range = tmp.Ranges[i];
+            for (int char_idx = 0; char_idx < range.num_chars; char_idx += 1)
+            {
+                const stbtt_packedchar& pc = range.chardata_for_range[char_idx];
+                if (!pc.x0 && !pc.x1 && !pc.y0 && !pc.y1)
+                    continue;
+
+                const int codepoint = range.first_unicode_codepoint_in_range + char_idx;
+                if (cfg.MergeMode && dst_font->FindGlyphNoFallback((unsigned short)codepoint))
+                    continue;
+
+                float char_advance_x_org = pc.xadvance;
+                float char_advance_x_mod = ImClamp(char_advance_x_org, cfg.GlyphMinAdvanceX, cfg.GlyphMaxAdvanceX);
+                float char_off_x = font_off_x;
+                if (char_advance_x_org != char_advance_x_mod)
+                    char_off_x += cfg.PixelSnapH ? (float)(int)((char_advance_x_mod - char_advance_x_org) * 0.5f) : (char_advance_x_mod - char_advance_x_org) * 0.5f;
+
+                stbtt_aligned_quad q;
+                float dummy_x = 0.0f, dummy_y = 0.0f;
+                stbtt_GetPackedQuad(range.chardata_for_range, atlas->TexWidth, atlas->TexHeight, char_idx, &dummy_x, &dummy_y, &q, 0);
+                dst_font->AddGlyph((ImWchar)codepoint, q.x0 + char_off_x, q.y0 + font_off_y, q.x1 + char_off_x, q.y1 + font_off_y, q.s0, q.t0, q.s1, q.t1, char_advance_x_mod);
+            }
+        }
+    }
+
+    // Cleanup temporaries
+    ImGui::MemFree(buf_packedchars);
+    ImGui::MemFree(buf_ranges);
+    ImGui::MemFree(tmp_array);
+
+    ImFontAtlasBuildFinish(atlas);
+
+    return true;
+}
+
+void ImFontAtlasBuildRegisterDefaultCustomRects(ImFontAtlas* atlas)
+{
+    if (atlas->CustomRectIds[0] >= 0)
+        return;
+    if (!(atlas->Flags & ImFontAtlasFlags_NoMouseCursors))
+        atlas->CustomRectIds[0] = atlas->AddCustomRectRegular(FONT_ATLAS_DEFAULT_TEX_DATA_ID, FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF*2+1, FONT_ATLAS_DEFAULT_TEX_DATA_H);
+    else
+        atlas->CustomRectIds[0] = atlas->AddCustomRectRegular(FONT_ATLAS_DEFAULT_TEX_DATA_ID, 2, 2);
+}
+
+void ImFontAtlasBuildSetupFont(ImFontAtlas* atlas, ImFont* font, ImFontConfig* font_config, float ascent, float descent)
+{
+    if (!font_config->MergeMode)
+    {
+        font->ClearOutputData();
+        font->FontSize = font_config->SizePixels;
+        font->ConfigData = font_config;
+        font->ContainerAtlas = atlas;
+        font->Ascent = ascent;
+        font->Descent = descent;
+    }
+    font->ConfigDataCount++;
+}
+
+void ImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas, void* pack_context_opaque)
+{
+    stbrp_context* pack_context = (stbrp_context*)pack_context_opaque;
+
+    ImVector<ImFontAtlas::CustomRect>& user_rects = atlas->CustomRects;
+    IM_ASSERT(user_rects.Size >= 1); // We expect at least the default custom rects to be registered, else something went wrong.
+
+    ImVector<stbrp_rect> pack_rects;
+    pack_rects.resize(user_rects.Size);
+    memset(pack_rects.Data, 0, sizeof(stbrp_rect) * user_rects.Size);
+    for (int i = 0; i < user_rects.Size; i++)
+    {
+        pack_rects[i].w = user_rects[i].Width;
+        pack_rects[i].h = user_rects[i].Height;
+    }
+    stbrp_pack_rects(pack_context, &pack_rects[0], pack_rects.Size);
+    for (int i = 0; i < pack_rects.Size; i++)
+        if (pack_rects[i].was_packed)
+        {
+            user_rects[i].X = pack_rects[i].x;
+            user_rects[i].Y = pack_rects[i].y;
+            IM_ASSERT(pack_rects[i].w == user_rects[i].Width && pack_rects[i].h == user_rects[i].Height);
+            atlas->TexHeight = ImMax(atlas->TexHeight, pack_rects[i].y + pack_rects[i].h);
+        }
+}
+
+static void ImFontAtlasBuildRenderDefaultTexData(ImFontAtlas* atlas)
+{
+    IM_ASSERT(atlas->CustomRectIds[0] >= 0);
+    IM_ASSERT(atlas->TexPixelsAlpha8 != NULL);
+    ImFontAtlas::CustomRect& r = atlas->CustomRects[atlas->CustomRectIds[0]];
+    IM_ASSERT(r.ID == FONT_ATLAS_DEFAULT_TEX_DATA_ID);
+    IM_ASSERT(r.IsPacked());
+
+    const int w = atlas->TexWidth;
+    if (!(atlas->Flags & ImFontAtlasFlags_NoMouseCursors))
+    {
+        // Render/copy pixels
+        IM_ASSERT(r.Width == FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF * 2 + 1 && r.Height == FONT_ATLAS_DEFAULT_TEX_DATA_H);
+        for (int y = 0, n = 0; y < FONT_ATLAS_DEFAULT_TEX_DATA_H; y++)
+            for (int x = 0; x < FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF; x++, n++)
+            {
+                const int offset0 = (int)(r.X + x) + (int)(r.Y + y) * w;
+                const int offset1 = offset0 + FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF + 1;
+                atlas->TexPixelsAlpha8[offset0] = FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS[n] == '.' ? 0xFF : 0x00;
+                atlas->TexPixelsAlpha8[offset1] = FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS[n] == 'X' ? 0xFF : 0x00;
+            }
+    }
+    else
+    {
+        IM_ASSERT(r.Width == 2 && r.Height == 2);
+        const int offset = (int)(r.X) + (int)(r.Y) * w;
+        atlas->TexPixelsAlpha8[offset] = atlas->TexPixelsAlpha8[offset + 1] = atlas->TexPixelsAlpha8[offset + w] = atlas->TexPixelsAlpha8[offset + w + 1] = 0xFF;
+    }
+    atlas->TexUvWhitePixel = ImVec2((r.X + 0.5f) * atlas->TexUvScale.x, (r.Y + 0.5f) * atlas->TexUvScale.y);
+}
+
+void ImFontAtlasBuildFinish(ImFontAtlas* atlas)
+{
+    // Render into our custom data block
+    ImFontAtlasBuildRenderDefaultTexData(atlas);
+
+    // Register custom rectangle glyphs
+    for (int i = 0; i < atlas->CustomRects.Size; i++)
+    {
+        const ImFontAtlas::CustomRect& r = atlas->CustomRects[i];
+        if (r.Font == NULL || r.ID > 0x10000)
+            continue;
+
+        IM_ASSERT(r.Font->ContainerAtlas == atlas);
+        ImVec2 uv0, uv1;
+        atlas->CalcCustomRectUV(&r, &uv0, &uv1);
+        r.Font->AddGlyph((ImWchar)r.ID, r.GlyphOffset.x, r.GlyphOffset.y, r.GlyphOffset.x + r.Width, r.GlyphOffset.y + r.Height, uv0.x, uv0.y, uv1.x, uv1.y, r.GlyphAdvanceX);
+    }
+
+    // Build all fonts lookup tables
+    for (int i = 0; i < atlas->Fonts.Size; i++)
+        if (atlas->Fonts[i]->DirtyLookupTables)
+            atlas->Fonts[i]->BuildLookupTable();
+}
+
+// Retrieve list of range (2 int per range, values are inclusive)
+const ImWchar*   ImFontAtlas::GetGlyphRangesDefault()
+{
+    static const ImWchar ranges[] =
+    {
+        0x0020, 0x00FF, // Basic Latin + Latin Supplement
+        0,
+    };
+    return &ranges[0];
+}
+
+const ImWchar*  ImFontAtlas::GetGlyphRangesKorean()
+{
+    static const ImWchar ranges[] =
+    {
+        0x0020, 0x00FF, // Basic Latin + Latin Supplement
+        0x3131, 0x3163, // Korean alphabets
+        0xAC00, 0xD79D, // Korean characters
+        0,
+    };
+    return &ranges[0];
+}
+
+const ImWchar*  ImFontAtlas::GetGlyphRangesChineseFull()
+{
+    static const ImWchar ranges[] =
+    {
+        0x0020, 0x00FF, // Basic Latin + Latin Supplement
+        0x3000, 0x30FF, // Punctuations, Hiragana, Katakana
+        0x31F0, 0x31FF, // Katakana Phonetic Extensions
+        0xFF00, 0xFFEF, // Half-width characters
+        0x4e00, 0x9FAF, // CJK Ideograms
+        0,
+    };
+    return &ranges[0];
+}
+
+static void UnpackAccumulativeOffsetsIntoRanges(int base_codepoint, const short* accumulative_offsets, int accumulative_offsets_count, ImWchar* out_ranges)
+{
+    for (int n = 0; n < accumulative_offsets_count; n++, out_ranges += 2)
+    {
+        out_ranges[0] = out_ranges[1] = (ImWchar)(base_codepoint + accumulative_offsets[n]);
+        base_codepoint += accumulative_offsets[n];
+    }
+    out_ranges[0] = 0;
+}
+
+//-------------------------------------------------------------------------
+// [SECTION] ImFontAtlas glyph ranges helpers + GlyphRangesBuilder
+//-------------------------------------------------------------------------
+
+const ImWchar*  ImFontAtlas::GetGlyphRangesChineseSimplifiedCommon()
+{
+    // Store 2500 regularly used characters for Simplified Chinese.
+    // Sourced from https://zh.wiktionary.org/wiki/%E9%99%84%E5%BD%95:%E7%8E%B0%E4%BB%A3%E6%B1%89%E8%AF%AD%E5%B8%B8%E7%94%A8%E5%AD%97%E8%A1%A8
+    // This table covers 97.97% of all characters used during the month in July, 1987.
+    // You can use ImFontAtlas::GlyphRangesBuilder to create your own ranges derived from this, by merging existing ranges or adding new characters.
+    // (Stored as accumulative offsets from the initial unicode codepoint 0x4E00. This encoding is designed to helps us compact the source code size.)
+    static const short accumulative_offsets_from_0x4E00[] =
+    {
+        0,1,2,4,1,1,1,1,2,1,3,2,1,2,2,1,1,1,1,1,5,2,1,2,3,3,3,2,2,4,1,1,1,2,1,5,2,3,1,2,1,2,1,1,2,1,1,2,2,1,4,1,1,1,1,5,10,1,2,19,2,1,2,1,2,1,2,1,2,
+        1,5,1,6,3,2,1,2,2,1,1,1,4,8,5,1,1,4,1,1,3,1,2,1,5,1,2,1,1,1,10,1,1,5,2,4,6,1,4,2,2,2,12,2,1,1,6,1,1,1,4,1,1,4,6,5,1,4,2,2,4,10,7,1,1,4,2,4,
+        2,1,4,3,6,10,12,5,7,2,14,2,9,1,1,6,7,10,4,7,13,1,5,4,8,4,1,1,2,28,5,6,1,1,5,2,5,20,2,2,9,8,11,2,9,17,1,8,6,8,27,4,6,9,20,11,27,6,68,2,2,1,1,
+        1,2,1,2,2,7,6,11,3,3,1,1,3,1,2,1,1,1,1,1,3,1,1,8,3,4,1,5,7,2,1,4,4,8,4,2,1,2,1,1,4,5,6,3,6,2,12,3,1,3,9,2,4,3,4,1,5,3,3,1,3,7,1,5,1,1,1,1,2,
+        3,4,5,2,3,2,6,1,1,2,1,7,1,7,3,4,5,15,2,2,1,5,3,22,19,2,1,1,1,1,2,5,1,1,1,6,1,1,12,8,2,9,18,22,4,1,1,5,1,16,1,2,7,10,15,1,1,6,2,4,1,2,4,1,6,
+        1,1,3,2,4,1,6,4,5,1,2,1,1,2,1,10,3,1,3,2,1,9,3,2,5,7,2,19,4,3,6,1,1,1,1,1,4,3,2,1,1,1,2,5,3,1,1,1,2,2,1,1,2,1,1,2,1,3,1,1,1,3,7,1,4,1,1,2,1,
+        1,2,1,2,4,4,3,8,1,1,1,2,1,3,5,1,3,1,3,4,6,2,2,14,4,6,6,11,9,1,15,3,1,28,5,2,5,5,3,1,3,4,5,4,6,14,3,2,3,5,21,2,7,20,10,1,2,19,2,4,28,28,2,3,
+        2,1,14,4,1,26,28,42,12,40,3,52,79,5,14,17,3,2,2,11,3,4,6,3,1,8,2,23,4,5,8,10,4,2,7,3,5,1,1,6,3,1,2,2,2,5,28,1,1,7,7,20,5,3,29,3,17,26,1,8,4,
+        27,3,6,11,23,5,3,4,6,13,24,16,6,5,10,25,35,7,3,2,3,3,14,3,6,2,6,1,4,2,3,8,2,1,1,3,3,3,4,1,1,13,2,2,4,5,2,1,14,14,1,2,2,1,4,5,2,3,1,14,3,12,
+        3,17,2,16,5,1,2,1,8,9,3,19,4,2,2,4,17,25,21,20,28,75,1,10,29,103,4,1,2,1,1,4,2,4,1,2,3,24,2,2,2,1,1,2,1,3,8,1,1,1,2,1,1,3,1,1,1,6,1,5,3,1,1,
+        1,3,4,1,1,5,2,1,5,6,13,9,16,1,1,1,1,3,2,3,2,4,5,2,5,2,2,3,7,13,7,2,2,1,1,1,1,2,3,3,2,1,6,4,9,2,1,14,2,14,2,1,18,3,4,14,4,11,41,15,23,15,23,
+        176,1,3,4,1,1,1,1,5,3,1,2,3,7,3,1,1,2,1,2,4,4,6,2,4,1,9,7,1,10,5,8,16,29,1,1,2,2,3,1,3,5,2,4,5,4,1,1,2,2,3,3,7,1,6,10,1,17,1,44,4,6,2,1,1,6,
+        5,4,2,10,1,6,9,2,8,1,24,1,2,13,7,8,8,2,1,4,1,3,1,3,3,5,2,5,10,9,4,9,12,2,1,6,1,10,1,1,7,7,4,10,8,3,1,13,4,3,1,6,1,3,5,2,1,2,17,16,5,2,16,6,
+        1,4,2,1,3,3,6,8,5,11,11,1,3,3,2,4,6,10,9,5,7,4,7,4,7,1,1,4,2,1,3,6,8,7,1,6,11,5,5,3,24,9,4,2,7,13,5,1,8,82,16,61,1,1,1,4,2,2,16,10,3,8,1,1,
+        6,4,2,1,3,1,1,1,4,3,8,4,2,2,1,1,1,1,1,6,3,5,1,1,4,6,9,2,1,1,1,2,1,7,2,1,6,1,5,4,4,3,1,8,1,3,3,1,3,2,2,2,2,3,1,6,1,2,1,2,1,3,7,1,8,2,1,2,1,5,
+        2,5,3,5,10,1,2,1,1,3,2,5,11,3,9,3,5,1,1,5,9,1,2,1,5,7,9,9,8,1,3,3,3,6,8,2,3,2,1,1,32,6,1,2,15,9,3,7,13,1,3,10,13,2,14,1,13,10,2,1,3,10,4,15,
+        2,15,15,10,1,3,9,6,9,32,25,26,47,7,3,2,3,1,6,3,4,3,2,8,5,4,1,9,4,2,2,19,10,6,2,3,8,1,2,2,4,2,1,9,4,4,4,6,4,8,9,2,3,1,1,1,1,3,5,5,1,3,8,4,6,
+        2,1,4,12,1,5,3,7,13,2,5,8,1,6,1,2,5,14,6,1,5,2,4,8,15,5,1,23,6,62,2,10,1,1,8,1,2,2,10,4,2,2,9,2,1,1,3,2,3,1,5,3,3,2,1,3,8,1,1,1,11,3,1,1,4,
+        3,7,1,14,1,2,3,12,5,2,5,1,6,7,5,7,14,11,1,3,1,8,9,12,2,1,11,8,4,4,2,6,10,9,13,1,1,3,1,5,1,3,2,4,4,1,18,2,3,14,11,4,29,4,2,7,1,3,13,9,2,2,5,
+        3,5,20,7,16,8,5,72,34,6,4,22,12,12,28,45,36,9,7,39,9,191,1,1,1,4,11,8,4,9,2,3,22,1,1,1,1,4,17,1,7,7,1,11,31,10,2,4,8,2,3,2,1,4,2,16,4,32,2,
+        3,19,13,4,9,1,5,2,14,8,1,1,3,6,19,6,5,1,16,6,2,10,8,5,1,2,3,1,5,5,1,11,6,6,1,3,3,2,6,3,8,1,1,4,10,7,5,7,7,5,8,9,2,1,3,4,1,1,3,1,3,3,2,6,16,
+        1,4,6,3,1,10,6,1,3,15,2,9,2,10,25,13,9,16,6,2,2,10,11,4,3,9,1,2,6,6,5,4,30,40,1,10,7,12,14,33,6,3,6,7,3,1,3,1,11,14,4,9,5,12,11,49,18,51,31,
+        140,31,2,2,1,5,1,8,1,10,1,4,4,3,24,1,10,1,3,6,6,16,3,4,5,2,1,4,2,57,10,6,22,2,22,3,7,22,6,10,11,36,18,16,33,36,2,5,5,1,1,1,4,10,1,4,13,2,7,
+        5,2,9,3,4,1,7,43,3,7,3,9,14,7,9,1,11,1,1,3,7,4,18,13,1,14,1,3,6,10,73,2,2,30,6,1,11,18,19,13,22,3,46,42,37,89,7,3,16,34,2,2,3,9,1,7,1,1,1,2,
+        2,4,10,7,3,10,3,9,5,28,9,2,6,13,7,3,1,3,10,2,7,2,11,3,6,21,54,85,2,1,4,2,2,1,39,3,21,2,2,5,1,1,1,4,1,1,3,4,15,1,3,2,4,4,2,3,8,2,20,1,8,7,13,
+        4,1,26,6,2,9,34,4,21,52,10,4,4,1,5,12,2,11,1,7,2,30,12,44,2,30,1,1,3,6,16,9,17,39,82,2,2,24,7,1,7,3,16,9,14,44,2,1,2,1,2,3,5,2,4,1,6,7,5,3,
+        2,6,1,11,5,11,2,1,18,19,8,1,3,24,29,2,1,3,5,2,2,1,13,6,5,1,46,11,3,5,1,1,5,8,2,10,6,12,6,3,7,11,2,4,16,13,2,5,1,1,2,2,5,2,28,5,2,23,10,8,4,
+        4,22,39,95,38,8,14,9,5,1,13,5,4,3,13,12,11,1,9,1,27,37,2,5,4,4,63,211,95,2,2,2,1,3,5,2,1,1,2,2,1,1,1,3,2,4,1,2,1,1,5,2,2,1,1,2,3,1,3,1,1,1,
+        3,1,4,2,1,3,6,1,1,3,7,15,5,3,2,5,3,9,11,4,2,22,1,6,3,8,7,1,4,28,4,16,3,3,25,4,4,27,27,1,4,1,2,2,7,1,3,5,2,28,8,2,14,1,8,6,16,25,3,3,3,14,3,
+        3,1,1,2,1,4,6,3,8,4,1,1,1,2,3,6,10,6,2,3,18,3,2,5,5,4,3,1,5,2,5,4,23,7,6,12,6,4,17,11,9,5,1,1,10,5,12,1,1,11,26,33,7,3,6,1,17,7,1,5,12,1,11,
+        2,4,1,8,14,17,23,1,2,1,7,8,16,11,9,6,5,2,6,4,16,2,8,14,1,11,8,9,1,1,1,9,25,4,11,19,7,2,15,2,12,8,52,7,5,19,2,16,4,36,8,1,16,8,24,26,4,6,2,9,
+        5,4,36,3,28,12,25,15,37,27,17,12,59,38,5,32,127,1,2,9,17,14,4,1,2,1,1,8,11,50,4,14,2,19,16,4,17,5,4,5,26,12,45,2,23,45,104,30,12,8,3,10,2,2,
+        3,3,1,4,20,7,2,9,6,15,2,20,1,3,16,4,11,15,6,134,2,5,59,1,2,2,2,1,9,17,3,26,137,10,211,59,1,2,4,1,4,1,1,1,2,6,2,3,1,1,2,3,2,3,1,3,4,4,2,3,3,
+        1,4,3,1,7,2,2,3,1,2,1,3,3,3,2,2,3,2,1,3,14,6,1,3,2,9,6,15,27,9,34,145,1,1,2,1,1,1,1,2,1,1,1,1,2,2,2,3,1,2,1,1,1,2,3,5,8,3,5,2,4,1,3,2,2,2,12,
+        4,1,1,1,10,4,5,1,20,4,16,1,15,9,5,12,2,9,2,5,4,2,26,19,7,1,26,4,30,12,15,42,1,6,8,172,1,1,4,2,1,1,11,2,2,4,2,1,2,1,10,8,1,2,1,4,5,1,2,5,1,8,
+        4,1,3,4,2,1,6,2,1,3,4,1,2,1,1,1,1,12,5,7,2,4,3,1,1,1,3,3,6,1,2,2,3,3,3,2,1,2,12,14,11,6,6,4,12,2,8,1,7,10,1,35,7,4,13,15,4,3,23,21,28,52,5,
+        26,5,6,1,7,10,2,7,53,3,2,1,1,1,2,163,532,1,10,11,1,3,3,4,8,2,8,6,2,2,23,22,4,2,2,4,2,1,3,1,3,3,5,9,8,2,1,2,8,1,10,2,12,21,20,15,105,2,3,1,1,
+        3,2,3,1,1,2,5,1,4,15,11,19,1,1,1,1,5,4,5,1,1,2,5,3,5,12,1,2,5,1,11,1,1,15,9,1,4,5,3,26,8,2,1,3,1,1,15,19,2,12,1,2,5,2,7,2,19,2,20,6,26,7,5,
+        2,2,7,34,21,13,70,2,128,1,1,2,1,1,2,1,1,3,2,2,2,15,1,4,1,3,4,42,10,6,1,49,85,8,1,2,1,1,4,4,2,3,6,1,5,7,4,3,211,4,1,2,1,2,5,1,2,4,2,2,6,5,6,
+        10,3,4,48,100,6,2,16,296,5,27,387,2,2,3,7,16,8,5,38,15,39,21,9,10,3,7,59,13,27,21,47,5,21,6
+    };
+    static ImWchar base_ranges[] = // not zero-terminated
+    {
+        0x0020, 0x00FF, // Basic Latin + Latin Supplement
+        0x3000, 0x30FF, // Punctuations, Hiragana, Katakana
+        0x31F0, 0x31FF, // Katakana Phonetic Extensions
+        0xFF00, 0xFFEF, // Half-width characters
+    };
+    static ImWchar full_ranges[IM_ARRAYSIZE(base_ranges) + IM_ARRAYSIZE(accumulative_offsets_from_0x4E00) * 2 + 1] = { 0 };
+    if (!full_ranges[0])
+    {
+        memcpy(full_ranges, base_ranges, sizeof(base_ranges));
+        UnpackAccumulativeOffsetsIntoRanges(0x4E00, accumulative_offsets_from_0x4E00, IM_ARRAYSIZE(accumulative_offsets_from_0x4E00), full_ranges + IM_ARRAYSIZE(base_ranges));
+    }
+    return &full_ranges[0];
+}
+
+const ImWchar*  ImFontAtlas::GetGlyphRangesJapanese()
+{
+    // 1946 common ideograms code points for Japanese
+    // Sourced from http://theinstructionlimit.com/common-kanji-character-ranges-for-xna-spritefont-rendering
+    // FIXME: Source a list of the revised 2136 Joyo Kanji list from 2010 and rebuild this.
+    // You can use ImFontAtlas::GlyphRangesBuilder to create your own ranges derived from this, by merging existing ranges or adding new characters.
+    // (Stored as accumulative offsets from the initial unicode codepoint 0x4E00. This encoding is designed to helps us compact the source code size.)
+    static const short accumulative_offsets_from_0x4E00[] =
+    {
+        0,1,2,4,1,1,1,1,2,1,6,2,2,1,8,5,7,11,1,2,10,10,8,2,4,20,2,11,8,2,1,2,1,6,2,1,7,5,3,7,1,1,13,7,9,1,4,6,1,2,1,10,1,1,9,2,2,4,5,6,14,1,1,9,3,18,
+        5,4,2,2,10,7,1,1,1,3,2,4,3,23,2,10,12,2,14,2,4,13,1,6,10,3,1,7,13,6,4,13,5,2,3,17,2,2,5,7,6,4,1,7,14,16,6,13,9,15,1,1,7,16,4,7,1,19,9,2,7,15,
+        2,6,5,13,25,4,14,13,11,25,1,1,1,2,1,2,2,3,10,11,3,3,1,1,4,4,2,1,4,9,1,4,3,5,5,2,7,12,11,15,7,16,4,5,16,2,1,1,6,3,3,1,1,2,7,6,6,7,1,4,7,6,1,1,
+        2,1,12,3,3,9,5,8,1,11,1,2,3,18,20,4,1,3,6,1,7,3,5,5,7,2,2,12,3,1,4,2,3,2,3,11,8,7,4,17,1,9,25,1,1,4,2,2,4,1,2,7,1,1,1,3,1,2,6,16,1,2,1,1,3,12,
+        20,2,5,20,8,7,6,2,1,1,1,1,6,2,1,2,10,1,1,6,1,3,1,2,1,4,1,12,4,1,3,1,1,1,1,1,10,4,7,5,13,1,15,1,1,30,11,9,1,15,38,14,1,32,17,20,1,9,31,2,21,9,
+        4,49,22,2,1,13,1,11,45,35,43,55,12,19,83,1,3,2,3,13,2,1,7,3,18,3,13,8,1,8,18,5,3,7,25,24,9,24,40,3,17,24,2,1,6,2,3,16,15,6,7,3,12,1,9,7,3,3,
+        3,15,21,5,16,4,5,12,11,11,3,6,3,2,31,3,2,1,1,23,6,6,1,4,2,6,5,2,1,1,3,3,22,2,6,2,3,17,3,2,4,5,1,9,5,1,1,6,15,12,3,17,2,14,2,8,1,23,16,4,2,23,
+        8,15,23,20,12,25,19,47,11,21,65,46,4,3,1,5,6,1,2,5,26,2,1,1,3,11,1,1,1,2,1,2,3,1,1,10,2,3,1,1,1,3,6,3,2,2,6,6,9,2,2,2,6,2,5,10,2,4,1,2,1,2,2,
+        3,1,1,3,1,2,9,23,9,2,1,1,1,1,5,3,2,1,10,9,6,1,10,2,31,25,3,7,5,40,1,15,6,17,7,27,180,1,3,2,2,1,1,1,6,3,10,7,1,3,6,17,8,6,2,2,1,3,5,5,8,16,14,
+        15,1,1,4,1,2,1,1,1,3,2,7,5,6,2,5,10,1,4,2,9,1,1,11,6,1,44,1,3,7,9,5,1,3,1,1,10,7,1,10,4,2,7,21,15,7,2,5,1,8,3,4,1,3,1,6,1,4,2,1,4,10,8,1,4,5,
+        1,5,10,2,7,1,10,1,1,3,4,11,10,29,4,7,3,5,2,3,33,5,2,19,3,1,4,2,6,31,11,1,3,3,3,1,8,10,9,12,11,12,8,3,14,8,6,11,1,4,41,3,1,2,7,13,1,5,6,2,6,12,
+        12,22,5,9,4,8,9,9,34,6,24,1,1,20,9,9,3,4,1,7,2,2,2,6,2,28,5,3,6,1,4,6,7,4,2,1,4,2,13,6,4,4,3,1,8,8,3,2,1,5,1,2,2,3,1,11,11,7,3,6,10,8,6,16,16,
+        22,7,12,6,21,5,4,6,6,3,6,1,3,2,1,2,8,29,1,10,1,6,13,6,6,19,31,1,13,4,4,22,17,26,33,10,4,15,12,25,6,67,10,2,3,1,6,10,2,6,2,9,1,9,4,4,1,2,16,2,
+        5,9,2,3,8,1,8,3,9,4,8,6,4,8,11,3,2,1,1,3,26,1,7,5,1,11,1,5,3,5,2,13,6,39,5,1,5,2,11,6,10,5,1,15,5,3,6,19,21,22,2,4,1,6,1,8,1,4,8,2,4,2,2,9,2,
+        1,1,1,4,3,6,3,12,7,1,14,2,4,10,2,13,1,17,7,3,2,1,3,2,13,7,14,12,3,1,29,2,8,9,15,14,9,14,1,3,1,6,5,9,11,3,38,43,20,7,7,8,5,15,12,19,15,81,8,7,
+        1,5,73,13,37,28,8,8,1,15,18,20,165,28,1,6,11,8,4,14,7,15,1,3,3,6,4,1,7,14,1,1,11,30,1,5,1,4,14,1,4,2,7,52,2,6,29,3,1,9,1,21,3,5,1,26,3,11,14,
+        11,1,17,5,1,2,1,3,2,8,1,2,9,12,1,1,2,3,8,3,24,12,7,7,5,17,3,3,3,1,23,10,4,4,6,3,1,16,17,22,3,10,21,16,16,6,4,10,2,1,1,2,8,8,6,5,3,3,3,39,25,
+        15,1,1,16,6,7,25,15,6,6,12,1,22,13,1,4,9,5,12,2,9,1,12,28,8,3,5,10,22,60,1,2,40,4,61,63,4,1,13,12,1,4,31,12,1,14,89,5,16,6,29,14,2,5,49,18,18,
+        5,29,33,47,1,17,1,19,12,2,9,7,39,12,3,7,12,39,3,1,46,4,12,3,8,9,5,31,15,18,3,2,2,66,19,13,17,5,3,46,124,13,57,34,2,5,4,5,8,1,1,1,4,3,1,17,5,
+        3,5,3,1,8,5,6,3,27,3,26,7,12,7,2,17,3,7,18,78,16,4,36,1,2,1,6,2,1,39,17,7,4,13,4,4,4,1,10,4,2,4,6,3,10,1,19,1,26,2,4,33,2,73,47,7,3,8,2,4,15,
+        18,1,29,2,41,14,1,21,16,41,7,39,25,13,44,2,2,10,1,13,7,1,7,3,5,20,4,8,2,49,1,10,6,1,6,7,10,7,11,16,3,12,20,4,10,3,1,2,11,2,28,9,2,4,7,2,15,1,
+        27,1,28,17,4,5,10,7,3,24,10,11,6,26,3,2,7,2,2,49,16,10,16,15,4,5,27,61,30,14,38,22,2,7,5,1,3,12,23,24,17,17,3,3,2,4,1,6,2,7,5,1,1,5,1,1,9,4,
+        1,3,6,1,8,2,8,4,14,3,5,11,4,1,3,32,1,19,4,1,13,11,5,2,1,8,6,8,1,6,5,13,3,23,11,5,3,16,3,9,10,1,24,3,198,52,4,2,2,5,14,5,4,22,5,20,4,11,6,41,
+        1,5,2,2,11,5,2,28,35,8,22,3,18,3,10,7,5,3,4,1,5,3,8,9,3,6,2,16,22,4,5,5,3,3,18,23,2,6,23,5,27,8,1,33,2,12,43,16,5,2,3,6,1,20,4,2,9,7,1,11,2,
+        10,3,14,31,9,3,25,18,20,2,5,5,26,14,1,11,17,12,40,19,9,6,31,83,2,7,9,19,78,12,14,21,76,12,113,79,34,4,1,1,61,18,85,10,2,2,13,31,11,50,6,33,159,
+        179,6,6,7,4,4,2,4,2,5,8,7,20,32,22,1,3,10,6,7,28,5,10,9,2,77,19,13,2,5,1,4,4,7,4,13,3,9,31,17,3,26,2,6,6,5,4,1,7,11,3,4,2,1,6,2,20,4,1,9,2,6,
+        3,7,1,1,1,20,2,3,1,6,2,3,6,2,4,8,1,5,13,8,4,11,23,1,10,6,2,1,3,21,2,2,4,24,31,4,10,10,2,5,192,15,4,16,7,9,51,1,2,1,1,5,1,1,2,1,3,5,3,1,3,4,1,
+        3,1,3,3,9,8,1,2,2,2,4,4,18,12,92,2,10,4,3,14,5,25,16,42,4,14,4,2,21,5,126,30,31,2,1,5,13,3,22,5,6,6,20,12,1,14,12,87,3,19,1,8,2,9,9,3,3,23,2,
+        3,7,6,3,1,2,3,9,1,3,1,6,3,2,1,3,11,3,1,6,10,3,2,3,1,2,1,5,1,1,11,3,6,4,1,7,2,1,2,5,5,34,4,14,18,4,19,7,5,8,2,6,79,1,5,2,14,8,2,9,2,1,36,28,16,
+        4,1,1,1,2,12,6,42,39,16,23,7,15,15,3,2,12,7,21,64,6,9,28,8,12,3,3,41,59,24,51,55,57,294,9,9,2,6,2,15,1,2,13,38,90,9,9,9,3,11,7,1,1,1,5,6,3,2,
+        1,2,2,3,8,1,4,4,1,5,7,1,4,3,20,4,9,1,1,1,5,5,17,1,5,2,6,2,4,1,4,5,7,3,18,11,11,32,7,5,4,7,11,127,8,4,3,3,1,10,1,1,6,21,14,1,16,1,7,1,3,6,9,65,
+        51,4,3,13,3,10,1,1,12,9,21,110,3,19,24,1,1,10,62,4,1,29,42,78,28,20,18,82,6,3,15,6,84,58,253,15,155,264,15,21,9,14,7,58,40,39, 
+    };
+    static ImWchar base_ranges[] = // not zero-terminated
+    {
+        0x0020, 0x00FF, // Basic Latin + Latin Supplement
+        0x3000, 0x30FF, // Punctuations, Hiragana, Katakana
+        0x31F0, 0x31FF, // Katakana Phonetic Extensions
+        0xFF00, 0xFFEF, // Half-width characters
+    };
+    static ImWchar full_ranges[IM_ARRAYSIZE(base_ranges) + IM_ARRAYSIZE(accumulative_offsets_from_0x4E00)*2 + 1] = { 0 };
+    if (!full_ranges[0])
+    {
+        memcpy(full_ranges, base_ranges, sizeof(base_ranges));
+        UnpackAccumulativeOffsetsIntoRanges(0x4E00, accumulative_offsets_from_0x4E00, IM_ARRAYSIZE(accumulative_offsets_from_0x4E00), full_ranges + IM_ARRAYSIZE(base_ranges));
+    }
+    return &full_ranges[0];
+}
+
+const ImWchar*  ImFontAtlas::GetGlyphRangesCyrillic()
+{
+    static const ImWchar ranges[] =
+    {
+        0x0020, 0x00FF, // Basic Latin + Latin Supplement
+        0x0400, 0x052F, // Cyrillic + Cyrillic Supplement
+        0x2DE0, 0x2DFF, // Cyrillic Extended-A
+        0xA640, 0xA69F, // Cyrillic Extended-B
+        0,
+    };
+    return &ranges[0];
+}
+
+const ImWchar*  ImFontAtlas::GetGlyphRangesThai()
+{
+    static const ImWchar ranges[] =
+    {
+        0x0020, 0x00FF, // Basic Latin
+        0x2010, 0x205E, // Punctuations
+        0x0E00, 0x0E7F, // Thai
+        0,
+    };
+    return &ranges[0];
+}
+
+void ImFontAtlas::GlyphRangesBuilder::AddText(const char* text, const char* text_end)
+{
+    while (text_end ? (text < text_end) : *text)
+    {
+        unsigned int c = 0;
+        int c_len = ImTextCharFromUtf8(&c, text, text_end);
+        text += c_len;
+        if (c_len == 0)
+            break;
+        if (c < 0x10000)
+            AddChar((ImWchar)c);
+    }
+}
+
+void ImFontAtlas::GlyphRangesBuilder::AddRanges(const ImWchar* ranges)
+{
+    for (; ranges[0]; ranges += 2)
+        for (ImWchar c = ranges[0]; c <= ranges[1]; c++)
+            AddChar(c);
+}
+
+void ImFontAtlas::GlyphRangesBuilder::BuildRanges(ImVector<ImWchar>* out_ranges)
+{
+    for (int n = 0; n < 0x10000; n++)
+        if (GetBit(n))
+        {
+            out_ranges->push_back((ImWchar)n);
+            while (n < 0x10000 && GetBit(n + 1))
+                n++;
+            out_ranges->push_back((ImWchar)n);
+        }
+    out_ranges->push_back(0);
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] ImFont
+//-----------------------------------------------------------------------------
+
+ImFont::ImFont()
+{
+    Scale = 1.0f;
+    FallbackChar = (ImWchar)'?';
+    DisplayOffset = ImVec2(0.0f, 0.0f);
+    ClearOutputData();
+}
+
+ImFont::~ImFont()
+{
+    // Invalidate active font so that the user gets a clear crash instead of a dangling pointer.
+    // If you want to delete fonts you need to do it between Render() and NewFrame().
+    // FIXME-CLEANUP
+    /*
+    ImGuiContext& g = *GImGui;
+    if (g.Font == this)
+        g.Font = NULL;
+    */
+    ClearOutputData();
+}
+
+void    ImFont::ClearOutputData()
+{
+    FontSize = 0.0f;
+    Glyphs.clear();
+    IndexAdvanceX.clear();
+    IndexLookup.clear();
+    FallbackGlyph = NULL;
+    FallbackAdvanceX = 0.0f;
+    ConfigDataCount = 0;
+    ConfigData = NULL;
+    ContainerAtlas = NULL;
+    Ascent = Descent = 0.0f;
+    DirtyLookupTables = true;
+    MetricsTotalSurface = 0;
+}
+
+void ImFont::BuildLookupTable()
+{
+    int max_codepoint = 0;
+    for (int i = 0; i != Glyphs.Size; i++)
+        max_codepoint = ImMax(max_codepoint, (int)Glyphs[i].Codepoint);
+
+    IM_ASSERT(Glyphs.Size < 0xFFFF); // -1 is reserved
+    IndexAdvanceX.clear();
+    IndexLookup.clear();
+    DirtyLookupTables = false;
+    GrowIndex(max_codepoint + 1);
+    for (int i = 0; i < Glyphs.Size; i++)
+    {
+        int codepoint = (int)Glyphs[i].Codepoint;
+        IndexAdvanceX[codepoint] = Glyphs[i].AdvanceX;
+        IndexLookup[codepoint] = (unsigned short)i;
+    }
+
+    // Create a glyph to handle TAB
+    // FIXME: Needs proper TAB handling but it needs to be contextualized (or we could arbitrary say that each string starts at "column 0" ?)
+    if (FindGlyph((unsigned short)' '))
+    {
+        if (Glyphs.back().Codepoint != '\t')   // So we can call this function multiple times
+            Glyphs.resize(Glyphs.Size + 1);
+        ImFontGlyph& tab_glyph = Glyphs.back();
+        tab_glyph = *FindGlyph((unsigned short)' ');
+        tab_glyph.Codepoint = '\t';
+        tab_glyph.AdvanceX *= 4;
+        IndexAdvanceX[(int)tab_glyph.Codepoint] = (float)tab_glyph.AdvanceX;
+        IndexLookup[(int)tab_glyph.Codepoint] = (unsigned short)(Glyphs.Size-1);
+    }
+
+    FallbackGlyph = FindGlyphNoFallback(FallbackChar);
+    FallbackAdvanceX = FallbackGlyph ? FallbackGlyph->AdvanceX : 0.0f;
+    for (int i = 0; i < max_codepoint + 1; i++)
+        if (IndexAdvanceX[i] < 0.0f)
+            IndexAdvanceX[i] = FallbackAdvanceX;
+}
+
+void ImFont::SetFallbackChar(ImWchar c)
+{
+    FallbackChar = c;
+    BuildLookupTable();
+}
+
+void ImFont::GrowIndex(int new_size)
+{
+    IM_ASSERT(IndexAdvanceX.Size == IndexLookup.Size);
+    if (new_size <= IndexLookup.Size)
+        return;
+    IndexAdvanceX.resize(new_size, -1.0f);
+    IndexLookup.resize(new_size, (unsigned short)-1);
+}
+
+// x0/y0/x1/y1 are offset from the character upper-left layout position, in pixels. Therefore x0/y0 are often fairly close to zero.
+// Not to be mistaken with texture coordinates, which are held by u0/v0/u1/v1 in normalized format (0.0..1.0 on each texture axis).
+void ImFont::AddGlyph(ImWchar codepoint, float x0, float y0, float x1, float y1, float u0, float v0, float u1, float v1, float advance_x)
+{
+    Glyphs.resize(Glyphs.Size + 1);
+    ImFontGlyph& glyph = Glyphs.back();
+    glyph.Codepoint = (ImWchar)codepoint;
+    glyph.X0 = x0;
+    glyph.Y0 = y0;
+    glyph.X1 = x1;
+    glyph.Y1 = y1;
+    glyph.U0 = u0;
+    glyph.V0 = v0;
+    glyph.U1 = u1;
+    glyph.V1 = v1;
+    glyph.AdvanceX = advance_x + ConfigData->GlyphExtraSpacing.x;  // Bake spacing into AdvanceX
+
+    if (ConfigData->PixelSnapH)
+        glyph.AdvanceX = (float)(int)(glyph.AdvanceX + 0.5f);
+
+    // Compute rough surface usage metrics (+1 to account for average padding, +0.99 to round)
+    DirtyLookupTables = true;
+    MetricsTotalSurface += (int)((glyph.U1 - glyph.U0) * ContainerAtlas->TexWidth + 1.99f) * (int)((glyph.V1 - glyph.V0) * ContainerAtlas->TexHeight + 1.99f);
+}
+
+void ImFont::AddRemapChar(ImWchar dst, ImWchar src, bool overwrite_dst)
+{
+    IM_ASSERT(IndexLookup.Size > 0);    // Currently this can only be called AFTER the font has been built, aka after calling ImFontAtlas::GetTexDataAs*() function.
+    int index_size = IndexLookup.Size;
+
+    if (dst < index_size && IndexLookup.Data[dst] == (unsigned short)-1 && !overwrite_dst) // 'dst' already exists
+        return;
+    if (src >= index_size && dst >= index_size) // both 'dst' and 'src' don't exist -> no-op
+        return;
+
+    GrowIndex(dst + 1);
+    IndexLookup[dst] = (src < index_size) ? IndexLookup.Data[src] : (unsigned short)-1;
+    IndexAdvanceX[dst] = (src < index_size) ? IndexAdvanceX.Data[src] : 1.0f;
+}
+
+const ImFontGlyph* ImFont::FindGlyph(ImWchar c) const
+{
+    if (c >= IndexLookup.Size)
+        return FallbackGlyph;
+    const unsigned short i = IndexLookup[c];
+    if (i == (unsigned short)-1)
+        return FallbackGlyph;
+    return &Glyphs.Data[i];
+}
+
+const ImFontGlyph* ImFont::FindGlyphNoFallback(ImWchar c) const
+{
+    if (c >= IndexLookup.Size)
+        return NULL;
+    const unsigned short i = IndexLookup[c];
+    if (i == (unsigned short)-1)
+        return NULL;
+    return &Glyphs.Data[i];
+}
+
+const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) const
+{
+    // Simple word-wrapping for English, not full-featured. Please submit failing cases!
+    // FIXME: Much possible improvements (don't cut things like "word !", "word!!!" but cut within "word,,,,", more sensible support for punctuations, support for Unicode punctuations, etc.)
+
+    // For references, possible wrap point marked with ^
+    //  "aaa bbb, ccc,ddd. eee   fff. ggg!"
+    //      ^    ^    ^   ^   ^__    ^    ^
+
+    // List of hardcoded separators: .,;!?'"
+
+    // Skip extra blanks after a line returns (that includes not counting them in width computation)
+    // e.g. "Hello    world" --> "Hello" "World"
+
+    // Cut words that cannot possibly fit within one line.
+    // e.g.: "The tropical fish" with ~5 characters worth of width --> "The tr" "opical" "fish"
+
+    float line_width = 0.0f;
+    float word_width = 0.0f;
+    float blank_width = 0.0f;
+    wrap_width /= scale; // We work with unscaled widths to avoid scaling every characters
+
+    const char* word_end = text;
+    const char* prev_word_end = NULL;
+    bool inside_word = true;
+
+    const char* s = text;
+    while (s < text_end)
+    {
+        unsigned int c = (unsigned int)*s;
+        const char* next_s;
+        if (c < 0x80)
+            next_s = s + 1;
+        else
+            next_s = s + ImTextCharFromUtf8(&c, s, text_end);
+        if (c == 0)
+            break;
+
+        if (c < 32)
+        {
+            if (c == '\n')
+            {
+                line_width = word_width = blank_width = 0.0f;
+                inside_word = true;
+                s = next_s;
+                continue;
+            }
+            if (c == '\r')
+            {
+                s = next_s;
+                continue;
+            }
+        }
+
+        const float char_width = ((int)c < IndexAdvanceX.Size ? IndexAdvanceX[(int)c] : FallbackAdvanceX);
+        if (ImCharIsBlankW(c))
+        {
+            if (inside_word)
+            {
+                line_width += blank_width;
+                blank_width = 0.0f;
+                word_end = s;
+            }
+            blank_width += char_width;
+            inside_word = false;
+        }
+        else
+        {
+            word_width += char_width;
+            if (inside_word)
+            {
+                word_end = next_s;
+            }
+            else
+            {
+                prev_word_end = word_end;
+                line_width += word_width + blank_width;
+                word_width = blank_width = 0.0f;
+            }
+
+            // Allow wrapping after punctuation.
+            inside_word = !(c == '.' || c == ',' || c == ';' || c == '!' || c == '?' || c == '\"');
+        }
+
+        // We ignore blank width at the end of the line (they can be skipped)
+        if (line_width + word_width >= wrap_width)
+        {
+            // Words that cannot possibly fit within an entire line will be cut anywhere.
+            if (word_width < wrap_width)
+                s = prev_word_end ? prev_word_end : word_end;
+            break;
+        }
+
+        s = next_s;
+    }
+
+    return s;
+}
+
+ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, const char* text_begin, const char* text_end, const char** remaining) const
+{
+    if (!text_end)
+        text_end = text_begin + strlen(text_begin); // FIXME-OPT: Need to avoid this.
+
+    const float line_height = size;
+    const float scale = size / FontSize;
+
+    ImVec2 text_size = ImVec2(0,0);
+    float line_width = 0.0f;
+
+    const bool word_wrap_enabled = (wrap_width > 0.0f);
+    const char* word_wrap_eol = NULL;
+
+    const char* s = text_begin;
+    while (s < text_end)
+    {
+        if (word_wrap_enabled)
+        {
+            // Calculate how far we can render. Requires two passes on the string data but keeps the code simple and not intrusive for what's essentially an uncommon feature.
+            if (!word_wrap_eol)
+            {
+                word_wrap_eol = CalcWordWrapPositionA(scale, s, text_end, wrap_width - line_width);
+                if (word_wrap_eol == s) // Wrap_width is too small to fit anything. Force displaying 1 character to minimize the height discontinuity.
+                    word_wrap_eol++;    // +1 may not be a character start point in UTF-8 but it's ok because we use s >= word_wrap_eol below
+            }
+
+            if (s >= word_wrap_eol)
+            {
+                if (text_size.x < line_width)
+                    text_size.x = line_width;
+                text_size.y += line_height;
+                line_width = 0.0f;
+                word_wrap_eol = NULL;
+
+                // Wrapping skips upcoming blanks
+                while (s < text_end)
+                {
+                    const char c = *s;
+                    if (ImCharIsBlankA(c)) { s++; } else if (c == '\n') { s++; break; } else { break; }
+                }
+                continue;
+            }
+        }
+
+        // Decode and advance source
+        const char* prev_s = s;
+        unsigned int c = (unsigned int)*s;
+        if (c < 0x80)
+        {
+            s += 1;
+        }
+        else
+        {
+            s += ImTextCharFromUtf8(&c, s, text_end);
+            if (c == 0) // Malformed UTF-8?
+                break;
+        }
+
+        if (c < 32)
+        {
+            if (c == '\n')
+            {
+                text_size.x = ImMax(text_size.x, line_width);
+                text_size.y += line_height;
+                line_width = 0.0f;
+                continue;
+            }
+            if (c == '\r')
+                continue;
+        }
+
+        const float char_width = ((int)c < IndexAdvanceX.Size ? IndexAdvanceX[(int)c] : FallbackAdvanceX) * scale;
+        if (line_width + char_width >= max_width)
+        {
+            s = prev_s;
+            break;
+        }
+
+        line_width += char_width;
+    }
+
+    if (text_size.x < line_width)
+        text_size.x = line_width;
+
+    if (line_width > 0 || text_size.y == 0.0f)
+        text_size.y += line_height;
+
+    if (remaining)
+        *remaining = s;
+
+    return text_size;
+}
+
+void ImFont::RenderChar(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col, unsigned short c) const
+{
+    if (c == ' ' || c == '\t' || c == '\n' || c == '\r') // Match behavior of RenderText(), those 4 codepoints are hard-coded.
+        return;
+    if (const ImFontGlyph* glyph = FindGlyph(c))
+    {
+        float scale = (size >= 0.0f) ? (size / FontSize) : 1.0f;
+        pos.x = (float)(int)pos.x + DisplayOffset.x;
+        pos.y = (float)(int)pos.y + DisplayOffset.y;
+        draw_list->PrimReserve(6, 4);
+        draw_list->PrimRectUV(ImVec2(pos.x + glyph->X0 * scale, pos.y + glyph->Y0 * scale), ImVec2(pos.x + glyph->X1 * scale, pos.y + glyph->Y1 * scale), ImVec2(glyph->U0, glyph->V0), ImVec2(glyph->U1, glyph->V1), col);
+    }
+}
+
+void ImFont::RenderText(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, float wrap_width, bool cpu_fine_clip) const
+{
+    if (!text_end)
+        text_end = text_begin + strlen(text_begin); // ImGui functions generally already provides a valid text_end, so this is merely to handle direct calls.
+
+    // Align to be pixel perfect
+    pos.x = (float)(int)pos.x + DisplayOffset.x;
+    pos.y = (float)(int)pos.y + DisplayOffset.y;
+    float x = pos.x;
+    float y = pos.y;
+    if (y > clip_rect.w)
+        return;
+
+    const float scale = size / FontSize;
+    const float line_height = FontSize * scale;
+    const bool word_wrap_enabled = (wrap_width > 0.0f);
+    const char* word_wrap_eol = NULL;
+
+    // Fast-forward to first visible line
+    const char* s = text_begin;
+    if (y + line_height < clip_rect.y && !word_wrap_enabled)
+        while (y + line_height < clip_rect.y)
+        {
+            while (s < text_end)
+                if (*s++ == '\n')
+                    break;
+            y += line_height;
+        }
+
+    // For large text, scan for the last visible line in order to avoid over-reserving in the call to PrimReserve()
+    // Note that very large horizontal line will still be affected by the issue (e.g. a one megabyte string buffer without a newline will likely crash atm)
+    if (text_end - s > 10000 && !word_wrap_enabled)
+    {
+        const char* s_end = s;
+        float y_end = y;
+        while (y_end < clip_rect.w)
+        {
+            while (s_end < text_end)
+                if (*s_end++ == '\n')
+                    break;
+            y_end += line_height;
+        }
+        text_end = s_end;
+    }
+
+    // Reserve vertices for remaining worse case (over-reserving is useful and easily amortized)
+    const int vtx_count_max = (int)(text_end - s) * 4;
+    const int idx_count_max = (int)(text_end - s) * 6;
+    const int idx_expected_size = draw_list->IdxBuffer.Size + idx_count_max;
+    draw_list->PrimReserve(idx_count_max, vtx_count_max);
+
+    ImDrawVert* vtx_write = draw_list->_VtxWritePtr;
+    ImDrawIdx* idx_write = draw_list->_IdxWritePtr;
+    unsigned int vtx_current_idx = draw_list->_VtxCurrentIdx;
+
+    while (s < text_end)
+    {
+        if (word_wrap_enabled)
+        {
+            // Calculate how far we can render. Requires two passes on the string data but keeps the code simple and not intrusive for what's essentially an uncommon feature.
+            if (!word_wrap_eol)
+            {
+                word_wrap_eol = CalcWordWrapPositionA(scale, s, text_end, wrap_width - (x - pos.x));
+                if (word_wrap_eol == s) // Wrap_width is too small to fit anything. Force displaying 1 character to minimize the height discontinuity.
+                    word_wrap_eol++;    // +1 may not be a character start point in UTF-8 but it's ok because we use s >= word_wrap_eol below
+            }
+
+            if (s >= word_wrap_eol)
+            {
+                x = pos.x;
+                y += line_height;
+                word_wrap_eol = NULL;
+
+                // Wrapping skips upcoming blanks
+                while (s < text_end)
+                {
+                    const char c = *s;
+                    if (ImCharIsBlankA(c)) { s++; } else if (c == '\n') { s++; break; } else { break; }
+                }
+                continue;
+            }
+        }
+
+        // Decode and advance source
+        unsigned int c = (unsigned int)*s;
+        if (c < 0x80)
+        {
+            s += 1;
+        }
+        else
+        {
+            s += ImTextCharFromUtf8(&c, s, text_end);
+            if (c == 0) // Malformed UTF-8?
+                break;
+        }
+
+        if (c < 32)
+        {
+            if (c == '\n')
+            {
+                x = pos.x;
+                y += line_height;
+                if (y > clip_rect.w)
+                    break; // break out of main loop
+                continue;
+            }
+            if (c == '\r')
+                continue;
+        }
+
+        float char_width = 0.0f;
+        if (const ImFontGlyph* glyph = FindGlyph((unsigned short)c))
+        {
+            char_width = glyph->AdvanceX * scale;
+
+            // Arbitrarily assume that both space and tabs are empty glyphs as an optimization
+            if (c != ' ' && c != '\t')
+            {
+                // We don't do a second finer clipping test on the Y axis as we've already skipped anything before clip_rect.y and exit once we pass clip_rect.w
+                float x1 = x + glyph->X0 * scale;
+                float x2 = x + glyph->X1 * scale;
+                float y1 = y + glyph->Y0 * scale;
+                float y2 = y + glyph->Y1 * scale;
+                if (x1 <= clip_rect.z && x2 >= clip_rect.x)
+                {
+                    // Render a character
+                    float u1 = glyph->U0;
+                    float v1 = glyph->V0;
+                    float u2 = glyph->U1;
+                    float v2 = glyph->V1;
+
+                    // CPU side clipping used to fit text in their frame when the frame is too small. Only does clipping for axis aligned quads.
+                    if (cpu_fine_clip)
+                    {
+                        if (x1 < clip_rect.x)
+                        {
+                            u1 = u1 + (1.0f - (x2 - clip_rect.x) / (x2 - x1)) * (u2 - u1);
+                            x1 = clip_rect.x;
+                        }
+                        if (y1 < clip_rect.y)
+                        {
+                            v1 = v1 + (1.0f - (y2 - clip_rect.y) / (y2 - y1)) * (v2 - v1);
+                            y1 = clip_rect.y;
+                        }
+                        if (x2 > clip_rect.z)
+                        {
+                            u2 = u1 + ((clip_rect.z - x1) / (x2 - x1)) * (u2 - u1);
+                            x2 = clip_rect.z;
+                        }
+                        if (y2 > clip_rect.w)
+                        {
+                            v2 = v1 + ((clip_rect.w - y1) / (y2 - y1)) * (v2 - v1);
+                            y2 = clip_rect.w;
+                        }
+                        if (y1 >= y2)
+                        {
+                            x += char_width;
+                            continue;
+                        }
+                    }
+
+                    // We are NOT calling PrimRectUV() here because non-inlined causes too much overhead in a debug builds. Inlined here:
+                    {
+                        idx_write[0] = (ImDrawIdx)(vtx_current_idx); idx_write[1] = (ImDrawIdx)(vtx_current_idx+1); idx_write[2] = (ImDrawIdx)(vtx_current_idx+2);
+                        idx_write[3] = (ImDrawIdx)(vtx_current_idx); idx_write[4] = (ImDrawIdx)(vtx_current_idx+2); idx_write[5] = (ImDrawIdx)(vtx_current_idx+3);
+                        vtx_write[0].pos.x = x1; vtx_write[0].pos.y = y1; vtx_write[0].col = col; vtx_write[0].uv.x = u1; vtx_write[0].uv.y = v1;
+                        vtx_write[1].pos.x = x2; vtx_write[1].pos.y = y1; vtx_write[1].col = col; vtx_write[1].uv.x = u2; vtx_write[1].uv.y = v1;
+                        vtx_write[2].pos.x = x2; vtx_write[2].pos.y = y2; vtx_write[2].col = col; vtx_write[2].uv.x = u2; vtx_write[2].uv.y = v2;
+                        vtx_write[3].pos.x = x1; vtx_write[3].pos.y = y2; vtx_write[3].col = col; vtx_write[3].uv.x = u1; vtx_write[3].uv.y = v2;
+                        vtx_write += 4;
+                        vtx_current_idx += 4;
+                        idx_write += 6;
+                    }
+                }
+            }
+        }
+
+        x += char_width;
+    }
+
+    // Give back unused vertices
+    draw_list->VtxBuffer.resize((int)(vtx_write - draw_list->VtxBuffer.Data));
+    draw_list->IdxBuffer.resize((int)(idx_write - draw_list->IdxBuffer.Data));
+    draw_list->CmdBuffer[draw_list->CmdBuffer.Size-1].ElemCount -= (idx_expected_size - draw_list->IdxBuffer.Size);
+    draw_list->_VtxWritePtr = vtx_write;
+    draw_list->_IdxWritePtr = idx_write;
+    draw_list->_VtxCurrentIdx = (unsigned int)draw_list->VtxBuffer.Size;
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] Internal Render Helpers
+// (progressively moved from imgui.cpp to here when they are redesigned to stop accessing ImGui global state)
+//-----------------------------------------------------------------------------
+// - RenderMouseCursor()
+// - RenderArrowPointingAt()
+// - RenderRectFilledRangeH()
+//-----------------------------------------------------------------------------
+
+void ImGui::RenderMouseCursor(ImDrawList* draw_list, ImVec2 pos, float scale, ImGuiMouseCursor mouse_cursor)
+{
+    if (mouse_cursor == ImGuiMouseCursor_None)
+        return;
+    IM_ASSERT(mouse_cursor > ImGuiMouseCursor_None && mouse_cursor < ImGuiMouseCursor_COUNT);
+
+    const ImU32 col_shadow = IM_COL32(0, 0, 0, 48);
+    const ImU32 col_border = IM_COL32(0, 0, 0, 255);          // Black
+    const ImU32 col_fill   = IM_COL32(255, 255, 255, 255);    // White
+
+    ImFontAtlas* font_atlas = draw_list->_Data->Font->ContainerAtlas;
+    ImVec2 offset, size, uv[4];
+    if (font_atlas->GetMouseCursorTexData(mouse_cursor, &offset, &size, &uv[0], &uv[2]))
+    {
+        pos -= offset;
+        const ImTextureID tex_id = font_atlas->TexID;
+        draw_list->PushTextureID(tex_id);
+        draw_list->AddImage(tex_id, pos + ImVec2(1,0)*scale, pos + ImVec2(1,0)*scale + size*scale, uv[2], uv[3], col_shadow);
+        draw_list->AddImage(tex_id, pos + ImVec2(2,0)*scale, pos + ImVec2(2,0)*scale + size*scale, uv[2], uv[3], col_shadow);
+        draw_list->AddImage(tex_id, pos,                     pos + size*scale,                     uv[2], uv[3], col_border);
+        draw_list->AddImage(tex_id, pos,                     pos + size*scale,                     uv[0], uv[1], col_fill);
+        draw_list->PopTextureID();
+    }
+}
+
+// Render an arrow. 'pos' is position of the arrow tip. half_sz.x is length from base to tip. half_sz.y is length on each side.
+void ImGui::RenderArrowPointingAt(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, ImGuiDir direction, ImU32 col)
+{
+    switch (direction)
+    {
+    case ImGuiDir_Left:  draw_list->AddTriangleFilled(ImVec2(pos.x + half_sz.x, pos.y - half_sz.y), ImVec2(pos.x + half_sz.x, pos.y + half_sz.y), pos, col); return;
+    case ImGuiDir_Right: draw_list->AddTriangleFilled(ImVec2(pos.x - half_sz.x, pos.y + half_sz.y), ImVec2(pos.x - half_sz.x, pos.y - half_sz.y), pos, col); return;
+    case ImGuiDir_Up:    draw_list->AddTriangleFilled(ImVec2(pos.x + half_sz.x, pos.y + half_sz.y), ImVec2(pos.x - half_sz.x, pos.y + half_sz.y), pos, col); return;
+    case ImGuiDir_Down:  draw_list->AddTriangleFilled(ImVec2(pos.x - half_sz.x, pos.y - half_sz.y), ImVec2(pos.x + half_sz.x, pos.y - half_sz.y), pos, col); return;
+    case ImGuiDir_None: case ImGuiDir_COUNT: break; // Fix warnings
+    }
+}
+
+static inline float ImAcos01(float x)
+{
+    if (x <= 0.0f) return IM_PI * 0.5f;
+    if (x >= 1.0f) return 0.0f;
+    return ImAcos(x);
+    //return (-0.69813170079773212f * x * x - 0.87266462599716477f) * x + 1.5707963267948966f; // Cheap approximation, may be enough for what we do.
+}
+
+// FIXME: Cleanup and move code to ImDrawList.
+void ImGui::RenderRectFilledRangeH(ImDrawList* draw_list, const ImRect& rect, ImU32 col, float x_start_norm, float x_end_norm, float rounding)
+{
+    if (x_end_norm == x_start_norm)
+        return;
+    if (x_start_norm > x_end_norm)
+        ImSwap(x_start_norm, x_end_norm);
+
+    ImVec2 p0 = ImVec2(ImLerp(rect.Min.x, rect.Max.x, x_start_norm), rect.Min.y);
+    ImVec2 p1 = ImVec2(ImLerp(rect.Min.x, rect.Max.x, x_end_norm), rect.Max.y);
+    if (rounding == 0.0f)
+    {
+        draw_list->AddRectFilled(p0, p1, col, 0.0f);
+        return;
+    }
+
+    rounding = ImClamp(ImMin((rect.Max.x - rect.Min.x) * 0.5f, (rect.Max.y - rect.Min.y) * 0.5f) - 1.0f, 0.0f, rounding);
+    const float inv_rounding = 1.0f / rounding;
+    const float arc0_b = ImAcos01(1.0f - (p0.x - rect.Min.x) * inv_rounding);
+    const float arc0_e = ImAcos01(1.0f - (p1.x - rect.Min.x) * inv_rounding);
+    const float x0 = ImMax(p0.x, rect.Min.x + rounding);
+    if (arc0_b == arc0_e)
+    {
+        draw_list->PathLineTo(ImVec2(x0, p1.y));
+        draw_list->PathLineTo(ImVec2(x0, p0.y));
+    }
+    else if (arc0_b == 0.0f && arc0_e == IM_PI*0.5f)
+    {
+        draw_list->PathArcToFast(ImVec2(x0, p1.y - rounding), rounding, 3, 6); // BL
+        draw_list->PathArcToFast(ImVec2(x0, p0.y + rounding), rounding, 6, 9); // TR
+    }
+    else
+    {
+        draw_list->PathArcTo(ImVec2(x0, p1.y - rounding), rounding, IM_PI - arc0_e, IM_PI - arc0_b, 3); // BL
+        draw_list->PathArcTo(ImVec2(x0, p0.y + rounding), rounding, IM_PI + arc0_b, IM_PI + arc0_e, 3); // TR
+    }
+    if (p1.x > rect.Min.x + rounding)
+    {
+        const float arc1_b = ImAcos01(1.0f - (rect.Max.x - p1.x) * inv_rounding);
+        const float arc1_e = ImAcos01(1.0f - (rect.Max.x - p0.x) * inv_rounding);
+        const float x1 = ImMin(p1.x, rect.Max.x - rounding);
+        if (arc1_b == arc1_e)
+        {
+            draw_list->PathLineTo(ImVec2(x1, p0.y));
+            draw_list->PathLineTo(ImVec2(x1, p1.y));
+        }
+        else if (arc1_b == 0.0f && arc1_e == IM_PI*0.5f)
+        {
+            draw_list->PathArcToFast(ImVec2(x1, p0.y + rounding), rounding, 9, 12); // TR
+            draw_list->PathArcToFast(ImVec2(x1, p1.y - rounding), rounding, 0, 3);  // BR
+        }
+        else
+        {
+            draw_list->PathArcTo(ImVec2(x1, p0.y + rounding), rounding, -arc1_e, -arc1_b, 3); // TR
+            draw_list->PathArcTo(ImVec2(x1, p1.y - rounding), rounding, +arc1_b, +arc1_e, 3); // BR
+        }
+    }
+    draw_list->PathFillConvex(col);
+}
+
+
+//-----------------------------------------------------------------------------
+// [SECTION] Decompression code
+//-----------------------------------------------------------------------------
+// Compressed with stb_compress() then converted to a C array and encoded as base85.
+// Use the program in misc/fonts/binary_to_compressed_c.cpp to create the array from a TTF file.
+// The purpose of encoding as base85 instead of "0x00,0x01,..." style is only save on _source code_ size.
+// Decompression from stb.h (public domain) by Sean Barrett https://github.com/nothings/stb/blob/master/stb.h
+//-----------------------------------------------------------------------------
+
+static unsigned int stb_decompress_length(const unsigned char *input)
+{
+    return (input[8] << 24) + (input[9] << 16) + (input[10] << 8) + input[11];
+}
+
+static unsigned char *stb__barrier_out_e, *stb__barrier_out_b;
+static const unsigned char *stb__barrier_in_b;
+static unsigned char *stb__dout;
+static void stb__match(const unsigned char *data, unsigned int length)
+{
+    // INVERSE of memmove... write each byte before copying the next...
+    IM_ASSERT(stb__dout + length <= stb__barrier_out_e);
+    if (stb__dout + length > stb__barrier_out_e) { stb__dout += length; return; }
+    if (data < stb__barrier_out_b) { stb__dout = stb__barrier_out_e+1; return; }
+    while (length--) *stb__dout++ = *data++;
+}
+
+static void stb__lit(const unsigned char *data, unsigned int length)
+{
+    IM_ASSERT(stb__dout + length <= stb__barrier_out_e);
+    if (stb__dout + length > stb__barrier_out_e) { stb__dout += length; return; }
+    if (data < stb__barrier_in_b) { stb__dout = stb__barrier_out_e+1; return; }
+    memcpy(stb__dout, data, length);
+    stb__dout += length;
+}
+
+#define stb__in2(x)   ((i[x] << 8) + i[(x)+1])
+#define stb__in3(x)   ((i[x] << 16) + stb__in2((x)+1))
+#define stb__in4(x)   ((i[x] << 24) + stb__in3((x)+1))
+
+static const unsigned char *stb_decompress_token(const unsigned char *i)
+{
+    if (*i >= 0x20) { // use fewer if's for cases that expand small
+        if (*i >= 0x80)       stb__match(stb__dout-i[1]-1, i[0] - 0x80 + 1), i += 2;
+        else if (*i >= 0x40)  stb__match(stb__dout-(stb__in2(0) - 0x4000 + 1), i[2]+1), i += 3;
+        else /* *i >= 0x20 */ stb__lit(i+1, i[0] - 0x20 + 1), i += 1 + (i[0] - 0x20 + 1);
+    } else { // more ifs for cases that expand large, since overhead is amortized
+        if (*i >= 0x18)       stb__match(stb__dout-(stb__in3(0) - 0x180000 + 1), i[3]+1), i += 4;
+        else if (*i >= 0x10)  stb__match(stb__dout-(stb__in3(0) - 0x100000 + 1), stb__in2(3)+1), i += 5;
+        else if (*i >= 0x08)  stb__lit(i+2, stb__in2(0) - 0x0800 + 1), i += 2 + (stb__in2(0) - 0x0800 + 1);
+        else if (*i == 0x07)  stb__lit(i+3, stb__in2(1) + 1), i += 3 + (stb__in2(1) + 1);
+        else if (*i == 0x06)  stb__match(stb__dout-(stb__in3(1)+1), i[4]+1), i += 5;
+        else if (*i == 0x04)  stb__match(stb__dout-(stb__in3(1)+1), stb__in2(4)+1), i += 6;
+    }
+    return i;
+}
+
+static unsigned int stb_adler32(unsigned int adler32, unsigned char *buffer, unsigned int buflen)
+{
+    const unsigned long ADLER_MOD = 65521;
+    unsigned long s1 = adler32 & 0xffff, s2 = adler32 >> 16;
+    unsigned long blocklen, i;
+
+    blocklen = buflen % 5552;
+    while (buflen) {
+        for (i=0; i + 7 < blocklen; i += 8) {
+            s1 += buffer[0], s2 += s1;
+            s1 += buffer[1], s2 += s1;
+            s1 += buffer[2], s2 += s1;
+            s1 += buffer[3], s2 += s1;
+            s1 += buffer[4], s2 += s1;
+            s1 += buffer[5], s2 += s1;
+            s1 += buffer[6], s2 += s1;
+            s1 += buffer[7], s2 += s1;
+
+            buffer += 8;
+        }
+
+        for (; i < blocklen; ++i)
+            s1 += *buffer++, s2 += s1;
+
+        s1 %= ADLER_MOD, s2 %= ADLER_MOD;
+        buflen -= blocklen;
+        blocklen = 5552;
+    }
+    return (unsigned int)(s2 << 16) + (unsigned int)s1;
+}
+
+static unsigned int stb_decompress(unsigned char *output, const unsigned char *i, unsigned int /*length*/)
+{
+    unsigned int olen;
+    if (stb__in4(0) != 0x57bC0000) return 0;
+    if (stb__in4(4) != 0)          return 0; // error! stream is > 4GB
+    olen = stb_decompress_length(i);
+    stb__barrier_in_b = i;
+    stb__barrier_out_e = output + olen;
+    stb__barrier_out_b = output;
+    i += 16;
+
+    stb__dout = output;
+    for (;;) {
+        const unsigned char *old_i = i;
+        i = stb_decompress_token(i);
+        if (i == old_i) {
+            if (*i == 0x05 && i[1] == 0xfa) {
+                IM_ASSERT(stb__dout == output + olen);
+                if (stb__dout != output + olen) return 0;
+                if (stb_adler32(1, output, olen) != (unsigned int) stb__in4(2))
+                    return 0;
+                return olen;
+            } else {
+                IM_ASSERT(0); /* NOTREACHED */
+                return 0;
+            }
+        }
+        IM_ASSERT(stb__dout <= output + olen);
+        if (stb__dout > output + olen)
+            return 0;
+    }
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] Default font data (ProggyClean.ttf)
+//-----------------------------------------------------------------------------
+// ProggyClean.ttf
+// Copyright (c) 2004, 2005 Tristan Grimmer
+// MIT license (see License.txt in http://www.upperbounds.net/download/ProggyClean.ttf.zip)
+// Download and more information at http://upperbounds.net
+//-----------------------------------------------------------------------------
+// File: 'ProggyClean.ttf' (41208 bytes)
+// Exported using misc/fonts/binary_to_compressed_c.cpp (with compression + base85 string encoding).
+// The purpose of encoding as base85 instead of "0x00,0x01,..." style is only save on _source code_ size.
+//-----------------------------------------------------------------------------
+static const char proggy_clean_ttf_compressed_data_base85[11980+1] =
+    "7])#######hV0qs'/###[),##/l:$#Q6>##5[n42>c-TH`->>#/e>11NNV=Bv(*:.F?uu#(gRU.o0XGH`$vhLG1hxt9?W`#,5LsCp#-i>.r$<$6pD>Lb';9Crc6tgXmKVeU2cD4Eo3R/"
+    "2*>]b(MC;$jPfY.;h^`IWM9<Lh2TlS+f-s$o6Q<BWH`YiU.xfLq$N;$0iR/GX:U(jcW2p/W*q?-qmnUCI;jHSAiFWM.R*kU@C=GH?a9wp8f$e.-4^Qg1)Q-GL(lf(r/7GrRgwV%MS=C#"
+    "`8ND>Qo#t'X#(v#Y9w0#1D$CIf;W'#pWUPXOuxXuU(H9M(1<q-UE31#^-V'8IRUo7Qf./L>=Ke$$'5F%)]0^#0X@U.a<r:QLtFsLcL6##lOj)#.Y5<-R&KgLwqJfLgN&;Q?gI^#DY2uL"
+    "i@^rMl9t=cWq6##weg>$FBjVQTSDgEKnIS7EM9>ZY9w0#L;>>#Mx&4Mvt//L[MkA#W@lK.N'[0#7RL_&#w+F%HtG9M#XL`N&.,GM4Pg;-<nLENhvx>-VsM.M0rJfLH2eTM`*oJMHRC`N"
+    "kfimM2J,W-jXS:)r0wK#@Fge$U>`w'N7G#$#fB#$E^$#:9:hk+eOe--6x)F7*E%?76%^GMHePW-Z5l'&GiF#$956:rS?dA#fiK:)Yr+`&#0j@'DbG&#^$PG.Ll+DNa<XCMKEV*N)LN/N"
+    "*b=%Q6pia-Xg8I$<MR&,VdJe$<(7G;Ckl'&hF;;$<_=X(b.RS%%)###MPBuuE1V:v&cX&#2m#(&cV]`k9OhLMbn%s$G2,B$BfD3X*sp5#l,$R#]x_X1xKX%b5U*[r5iMfUo9U`N99hG)"
+    "tm+/Us9pG)XPu`<0s-)WTt(gCRxIg(%6sfh=ktMKn3j)<6<b5Sk_/0(^]AaN#(p/L>&VZ>1i%h1S9u5o@YaaW$e+b<TWFn/Z:Oh(Cx2$lNEoN^e)#CFY@@I;BOQ*sRwZtZxRcU7uW6CX"
+    "ow0i(?$Q[cjOd[P4d)]>ROPOpxTO7Stwi1::iB1q)C_=dV26J;2,]7op$]uQr@_V7$q^%lQwtuHY]=DX,n3L#0PHDO4f9>dC@O>HBuKPpP*E,N+b3L#lpR/MrTEH.IAQk.a>D[.e;mc."
+    "x]Ip.PH^'/aqUO/$1WxLoW0[iLA<QT;5HKD+@qQ'NQ(3_PLhE48R.qAPSwQ0/WK?Z,[x?-J;jQTWA0X@KJ(_Y8N-:/M74:/-ZpKrUss?d#dZq]DAbkU*JqkL+nwX@@47`5>w=4h(9.`G"
+    "CRUxHPeR`5Mjol(dUWxZa(>STrPkrJiWx`5U7F#.g*jrohGg`cg:lSTvEY/EV_7H4Q9[Z%cnv;JQYZ5q.l7Zeas:HOIZOB?G<Nald$qs]@]L<J7bR*>gv:[7MI2k).'2($5FNP&EQ(,)"
+    "U]W]+fh18.vsai00);D3@4ku5P?DP8aJt+;qUM]=+b'8@;mViBKx0DE[-auGl8:PJ&Dj+M6OC]O^((##]`0i)drT;-7X`=-H3[igUnPG-NZlo.#k@h#=Ork$m>a>$-?Tm$UV(?#P6YY#"
+    "'/###xe7q.73rI3*pP/$1>s9)W,JrM7SN]'/4C#v$U`0#V.[0>xQsH$fEmPMgY2u7Kh(G%siIfLSoS+MK2eTM$=5,M8p`A.;_R%#u[K#$x4AG8.kK/HSB==-'Ie/QTtG?-.*^N-4B/ZM"
+    "_3YlQC7(p7q)&](`6_c)$/*JL(L-^(]$wIM`dPtOdGA,U3:w2M-0<q-]L_?^)1vw'.,MRsqVr.L;aN&#/EgJ)PBc[-f>+WomX2u7lqM2iEumMTcsF?-aT=Z-97UEnXglEn1K-bnEO`gu"
+    "Ft(c%=;Am_Qs@jLooI&NX;]0#j4#F14;gl8-GQpgwhrq8'=l_f-b49'UOqkLu7-##oDY2L(te+Mch&gLYtJ,MEtJfLh'x'M=$CS-ZZ%P]8bZ>#S?YY#%Q&q'3^Fw&?D)UDNrocM3A76/"
+    "/oL?#h7gl85[qW/NDOk%16ij;+:1a'iNIdb-ou8.P*w,v5#EI$TWS>Pot-R*H'-SEpA:g)f+O$%%`kA#G=8RMmG1&O`>to8bC]T&$,n.LoO>29sp3dt-52U%VM#q7'DHpg+#Z9%H[K<L"
+    "%a2E-grWVM3@2=-k22tL]4$##6We'8UJCKE[d_=%wI;'6X-GsLX4j^SgJ$##R*w,vP3wK#iiW&#*h^D&R?jp7+/u&#(AP##XU8c$fSYW-J95_-Dp[g9wcO&#M-h1OcJlc-*vpw0xUX&#"
+    "OQFKNX@QI'IoPp7nb,QU//MQ&ZDkKP)X<WSVL(68uVl&#c'[0#(s1X&xm$Y%B7*K:eDA323j998GXbA#pwMs-jgD$9QISB-A_(aN4xoFM^@C58D0+Q+q3n0#3U1InDjF682-SjMXJK)("
+    "h$hxua_K]ul92%'BOU&#BRRh-slg8KDlr:%L71Ka:.A;%YULjDPmL<LYs8i#XwJOYaKPKc1h:'9Ke,g)b),78=I39B;xiY$bgGw-&.Zi9InXDuYa%G*f2Bq7mn9^#p1vv%#(Wi-;/Z5h"
+    "o;#2:;%d&#x9v68C5g?ntX0X)pT`;%pB3q7mgGN)3%(P8nTd5L7GeA-GL@+%J3u2:(Yf>et`e;)f#Km8&+DC$I46>#Kr]]u-[=99tts1.qb#q72g1WJO81q+eN'03'eM>&1XxY-caEnO"
+    "j%2n8)),?ILR5^.Ibn<-X-Mq7[a82Lq:F&#ce+S9wsCK*x`569E8ew'He]h:sI[2LM$[guka3ZRd6:t%IG:;$%YiJ:Nq=?eAw;/:nnDq0(CYcMpG)qLN4$##&J<j$UpK<Q4a1]MupW^-"
+    "sj_$%[HK%'F####QRZJ::Y3EGl4'@%FkiAOg#p[##O`gukTfBHagL<LHw%q&OV0##F=6/:chIm0@eCP8X]:kFI%hl8hgO@RcBhS-@Qb$%+m=hPDLg*%K8ln(wcf3/'DW-$.lR?n[nCH-"
+    "eXOONTJlh:.RYF%3'p6sq:UIMA945&^HFS87@$EP2iG<-lCO$%c`uKGD3rC$x0BL8aFn--`ke%#HMP'vh1/R&O_J9'um,.<tx[@%wsJk&bUT2`0uMv7gg#qp/ij.L56'hl;.s5CUrxjO"
+    "M7-##.l+Au'A&O:-T72L]P`&=;ctp'XScX*rU.>-XTt,%OVU4)S1+R-#dg0/Nn?Ku1^0f$B*P:Rowwm-`0PKjYDDM'3]d39VZHEl4,.j']Pk-M.h^&:0FACm$maq-&sgw0t7/6(^xtk%"
+    "LuH88Fj-ekm>GA#_>568x6(OFRl-IZp`&b,_P'$M<Jnq79VsJW/mWS*PUiq76;]/NM_>hLbxfc$mj`,O;&%W2m`Zh:/)Uetw:aJ%]K9h:TcF]u_-Sj9,VK3M.*'&0D[Ca]J9gp8,kAW]"
+    "%(?A%R$f<->Zts'^kn=-^@c4%-pY6qI%J%1IGxfLU9CP8cbPlXv);C=b),<2mOvP8up,UVf3839acAWAW-W?#ao/^#%KYo8fRULNd2.>%m]UK:n%r$'sw]J;5pAoO_#2mO3n,'=H5(et"
+    "Hg*`+RLgv>=4U8guD$I%D:W>-r5V*%j*W:Kvej.Lp$<M-SGZ':+Q_k+uvOSLiEo(<aD/K<CCc`'Lx>'?;++O'>()jLR-^u68PHm8ZFWe+ej8h:9r6L*0//c&iH&R8pRbA#Kjm%upV1g:"
+    "a_#Ur7FuA#(tRh#.Y5K+@?3<-8m0$PEn;J:rh6?I6uG<-`wMU'ircp0LaE_OtlMb&1#6T.#FDKu#1Lw%u%+GM+X'e?YLfjM[VO0MbuFp7;>Q&#WIo)0@F%q7c#4XAXN-U&VB<HFF*qL("
+    "$/V,;(kXZejWO`<[5?\?ewY(*9=%wDc;,u<'9t3W-(H1th3+G]ucQ]kLs7df($/*JL]@*t7Bu_G3_7mp7<iaQjO@.kLg;x3B0lqp7Hf,^Ze7-##@/c58Mo(3;knp0%)A7?-W+eI'o8)b<"
+    "nKnw'Ho8C=Y>pqB>0ie&jhZ[?iLR@@_AvA-iQC(=ksRZRVp7`.=+NpBC%rh&3]R:8XDmE5^V8O(x<<aG/1N$#FX$0V5Y6x'aErI3I$7x%E`v<-BY,)%-?Psf*l?%C3.mM(=/M0:JxG'?"
+    "7WhH%o'a<-80g0NBxoO(GH<dM]n.+%q@jH?f.UsJ2Ggs&4<-e47&Kl+f//9@`b+?.TeN_&B8Ss?v;^Trk;f#YvJkl&w$]>-+k?'(<S:68tq*WoDfZu';mM?8X[ma8W%*`-=;D.(nc7/;"
+    ")g:T1=^J$&BRV(-lTmNB6xqB[@0*o.erM*<SWF]u2=st-*(6v>^](H.aREZSi,#1:[IXaZFOm<-ui#qUq2$##Ri;u75OK#(RtaW-K-F`S+cF]uN`-KMQ%rP/Xri.LRcB##=YL3BgM/3M"
+    "D?@f&1'BW-)Ju<L25gl8uhVm1hL$##*8###'A3/LkKW+(^rWX?5W_8g)a(m&K8P>#bmmWCMkk&#TR`C,5d>g)F;t,4:@_l8G/5h4vUd%&%950:VXD'QdWoY-F$BtUwmfe$YqL'8(PWX("
+    "P?^@Po3$##`MSs?DWBZ/S>+4%>fX,VWv/w'KD`LP5IbH;rTV>n3cEK8U#bX]l-/V+^lj3;vlMb&[5YQ8#pekX9JP3XUC72L,,?+Ni&co7ApnO*5NK,((W-i:$,kp'UDAO(G0Sq7MVjJs"
+    "bIu)'Z,*[>br5fX^:FPAWr-m2KgL<LUN098kTF&#lvo58=/vjDo;.;)Ka*hLR#/k=rKbxuV`>Q_nN6'8uTG&#1T5g)uLv:873UpTLgH+#FgpH'_o1780Ph8KmxQJ8#H72L4@768@Tm&Q"
+    "h4CB/5OvmA&,Q&QbUoi$a_%3M01H)4x7I^&KQVgtFnV+;[Pc>[m4k//,]1?#`VY[Jr*3&&slRfLiVZJ:]?=K3Sw=[$=uRB?3xk48@aeg<Z'<$#4H)6,>e0jT6'N#(q%.O=?2S]u*(m<-"
+    "V8J'(1)G][68hW$5'q[GC&5j`TE?m'esFGNRM)j,ffZ?-qx8;->g4t*:CIP/[Qap7/9'#(1sao7w-.qNUdkJ)tCF&#B^;xGvn2r9FEPFFFcL@.iFNkTve$m%#QvQS8U@)2Z+3K:AKM5i"
+    "sZ88+dKQ)W6>J%CL<KE>`.d*(B`-n8D9oK<Up]c$X$(,)M8Zt7/[rdkqTgl-0cuGMv'?>-XV1q['-5k'cAZ69e;D_?$ZPP&s^+7])$*$#@QYi9,5P&#9r+$%CE=68>K8r0=dSC%%(@p7"
+    ".m7jilQ02'0-VWAg<a/''3u.=4L$Y)6k/K:_[3=&jvL<L0C/2'v:^;-DIBW,B4E68:kZ;%?8(Q8BH=kO65BW?xSG&#@uU,DS*,?.+(o(#1vCS8#CHF>TlGW'b)Tq7VT9q^*^$$.:&N@@"
+    "$&)WHtPm*5_rO0&e%K&#-30j(E4#'Zb.o/(Tpm$>K'f@[PvFl,hfINTNU6u'0pao7%XUp9]5.>%h`8_=VYbxuel.NTSsJfLacFu3B'lQSu/m6-Oqem8T+oE--$0a/k]uj9EwsG>%veR*"
+    "hv^BFpQj:K'#SJ,sB-'#](j.Lg92rTw-*n%@/;39rrJF,l#qV%OrtBeC6/,;qB3ebNW[?,Hqj2L.1NP&GjUR=1D8QaS3Up&@*9wP?+lo7b?@%'k4`p0Z$22%K3+iCZj?XJN4Nm&+YF]u"
+    "@-W$U%VEQ/,,>>#)D<h#`)h0:<Q6909ua+&VU%n2:cG3FJ-%@Bj-DgLr`Hw&HAKjKjseK</xKT*)B,N9X3]krc12t'pgTV(Lv-tL[xg_%=M_q7a^x?7Ubd>#%8cY#YZ?=,`Wdxu/ae&#"
+    "w6)R89tI#6@s'(6Bf7a&?S=^ZI_kS&ai`&=tE72L_D,;^R)7[$s<Eh#c&)q.MXI%#v9ROa5FZO%sF7q7Nwb&#ptUJ:aqJe$Sl68%.D###EC><?-aF&#RNQv>o8lKN%5/$(vdfq7+ebA#"
+    "u1p]ovUKW&Y%q]'>$1@-[xfn$7ZTp7mM,G,Ko7a&Gu%G[RMxJs[0MM%wci.LFDK)(<c`Q8N)jEIF*+?P2a8g%)$q]o2aH8C&<SibC/q,(e:v;-b#6[$NtDZ84Je2KNvB#$P5?tQ3nt(0"
+    "d=j.LQf./Ll33+(;q3L-w=8dX$#WF&uIJ@-bfI>%:_i2B5CsR8&9Z&#=mPEnm0f`<&c)QL5uJ#%u%lJj+D-r;BoF&#4DoS97h5g)E#o:&S4weDF,9^Hoe`h*L+_a*NrLW-1pG_&2UdB8"
+    "6e%B/:=>)N4xeW.*wft-;$'58-ESqr<b?UI(_%@[P46>#U`'6AQ]m&6/`Z>#S?YY#Vc;r7U2&326d=w&H####?TZ`*4?&.MK?LP8Vxg>$[QXc%QJv92.(Db*B)gb*BM9dM*hJMAo*c&#"
+    "b0v=Pjer]$gG&JXDf->'StvU7505l9$AFvgYRI^&<^b68?j#q9QX4SM'RO#&sL1IM.rJfLUAj221]d##DW=m83u5;'bYx,*Sl0hL(W;;$doB&O/TQ:(Z^xBdLjL<Lni;''X.`$#8+1GD"
+    ":k$YUWsbn8ogh6rxZ2Z9]%nd+>V#*8U_72Lh+2Q8Cj0i:6hp&$C/:p(HK>T8Y[gHQ4`4)'$Ab(Nof%V'8hL&#<NEdtg(n'=S1A(Q1/I&4([%dM`,Iu'1:_hL>SfD07&6D<fp8dHM7/g+"
+    "tlPN9J*rKaPct&?'uBCem^jn%9_K)<,C5K3s=5g&GmJb*[SYq7K;TRLGCsM-$$;S%:Y@r7AK0pprpL<Lrh,q7e/%KWK:50I^+m'vi`3?%Zp+<-d+$L-Sv:@.o19n$s0&39;kn;S%BSq*"
+    "$3WoJSCLweV[aZ'MQIjO<7;X-X;&+dMLvu#^UsGEC9WEc[X(wI7#2.(F0jV*eZf<-Qv3J-c+J5AlrB#$p(H68LvEA'q3n0#m,[`*8Ft)FcYgEud]CWfm68,(aLA$@EFTgLXoBq/UPlp7"
+    ":d[/;r_ix=:TF`S5H-b<LI&HY(K=h#)]Lk$K14lVfm:x$H<3^Ql<M`$OhapBnkup'D#L$Pb_`N*g]2e;X/Dtg,bsj&K#2[-:iYr'_wgH)NUIR8a1n#S?Yej'h8^58UbZd+^FKD*T@;6A"
+    "7aQC[K8d-(v6GI$x:T<&'Gp5Uf>@M.*J:;$-rv29'M]8qMv-tLp,'886iaC=Hb*YJoKJ,(j%K=H`K.v9HggqBIiZu'QvBT.#=)0ukruV&.)3=(^1`o*Pj4<-<aN((^7('#Z0wK#5GX@7"
+    "u][`*S^43933A4rl][`*O4CgLEl]v$1Q3AeF37dbXk,.)vj#x'd`;qgbQR%FW,2(?LO=s%Sc68%NP'##Aotl8x=BE#j1UD([3$M(]UI2LX3RpKN@;/#f'f/&_mt&F)XdF<9t4)Qa.*kT"
+    "LwQ'(TTB9.xH'>#MJ+gLq9-##@HuZPN0]u:h7.T..G:;$/Usj(T7`Q8tT72LnYl<-qx8;-HV7Q-&Xdx%1a,hC=0u+HlsV>nuIQL-5<N?)NBS)QN*_I,?&)2'IM%L3I)X((e/dl2&8'<M"
+    ":^#M*Q+[T.Xri.LYS3v%fF`68h;b-X[/En'CR.q7E)p'/kle2HM,u;^%OKC-N+Ll%F9CF<Nf'^#t2L,;27W:0O@6##U6W7:$rJfLWHj$#)woqBefIZ.PK<b*t7ed;p*_m;4ExK#h@&]>"
+    "_>@kXQtMacfD.m-VAb8;IReM3$wf0''hra*so568'Ip&vRs849'MRYSp%:t:h5qSgwpEr$B>Q,;s(C#$)`svQuF$##-D,##,g68@2[T;.XSdN9Qe)rpt._K-#5wF)sP'##p#C0c%-Gb%"
+    "hd+<-j'Ai*x&&HMkT]C'OSl##5RG[JXaHN;d'uA#x._U;.`PU@(Z3dt4r152@:v,'R.Sj'w#0<-;kPI)FfJ&#AYJ&#//)>-k=m=*XnK$>=)72L]0I%>.G690a:$##<,);?;72#?x9+d;"
+    "^V'9;jY@;)br#q^YQpx:X#Te$Z^'=-=bGhLf:D6&bNwZ9-ZD#n^9HhLMr5G;']d&6'wYmTFmL<LD)F^%[tC'8;+9E#C$g%#5Y>q9wI>P(9mI[>kC-ekLC/R&CH+s'B;K-M6$EB%is00:"
+    "+A4[7xks.LrNk0&E)wILYF@2L'0Nb$+pv<(2.768/FrY&h$^3i&@+G%JT'<-,v`3;_)I9M^AE]CN?Cl2AZg+%4iTpT3<n-&%H%b<FDj2M<hH=&Eh<2Len$b*aTX=-8QxN)k11IM1c^j%"
+    "9s<L<NFSo)B?+<-(GxsF,^-Eh@$4dXhN$+#rxK8'je'D7k`e;)2pYwPA'_p9&@^18ml1^[@g4t*[JOa*[=Qp7(qJ_oOL^('7fB&Hq-:sf,sNj8xq^>$U4O]GKx'm9)b@p7YsvK3w^YR-"
+    "CdQ*:Ir<($u&)#(&?L9Rg3H)4fiEp^iI9O8KnTj,]H?D*r7'M;PwZ9K0E^k&-cpI;.p/6_vwoFMV<->#%Xi.LxVnrU(4&8/P+:hLSKj$#U%]49t'I:rgMi'FL@a:0Y-uA[39',(vbma*"
+    "hU%<-SRF`Tt:542R_VV$p@[p8DV[A,?1839FWdF<TddF<9Ah-6&9tWoDlh]&1SpGMq>Ti1O*H&#(AL8[_P%.M>v^-))qOT*F5Cq0`Ye%+$B6i:7@0IX<N+T+0MlMBPQ*Vj>SsD<U4JHY"
+    "8kD2)2fU/M#$e.)T4,_=8hLim[&);?UkK'-x?'(:siIfL<$pFM`i<?%W(mGDHM%>iWP,##P`%/L<eXi:@Z9C.7o=@(pXdAO/NLQ8lPl+HPOQa8wD8=^GlPa8TKI1CjhsCTSLJM'/Wl>-"
+    "S(qw%sf/@%#B6;/U7K]uZbi^Oc^2n<bhPmUkMw>%t<)'mEVE''n`WnJra$^TKvX5B>;_aSEK',(hwa0:i4G?.Bci.(X[?b*($,=-n<.Q%`(X=?+@Am*Js0&=3bh8K]mL<LoNs'6,'85`"
+    "0?t/'_U59@]ddF<#LdF<eWdF<OuN/45rY<-L@&#+fm>69=Lb,OcZV/);TTm8VI;?%OtJ<(b4mq7M6:u?KRdF<gR@2L=FNU-<b[(9c/ML3m;Z[$oF3g)GAWqpARc=<ROu7cL5l;-[A]%/"
+    "+fsd;l#SafT/f*W]0=O'$(Tb<[)*@e775R-:Yob%g*>l*:xP?Yb.5)%w_I?7uk5JC+FS(m#i'k.'a0i)9<7b'fs'59hq$*5Uhv##pi^8+hIEBF`nvo`;'l0.^S1<-wUK2/Coh58KKhLj"
+    "M=SO*rfO`+qC`W-On.=AJ56>>i2@2LH6A:&5q`?9I3@@'04&p2/LVa*T-4<-i3;M9UvZd+N7>b*eIwg:CC)c<>nO&#<IGe;__.thjZl<%w(Wk2xmp4Q@I#I9,DF]u7-P=.-_:YJ]aS@V"
+    "?6*C()dOp7:WL,b&3Rg/.cmM9&r^>$(>.Z-I&J(Q0Hd5Q%7Co-b`-c<N(6r@ip+AurK<m86QIth*#v;-OBqi+L7wDE-Ir8K['m+DDSLwK&/.?-V%U_%3:qKNu$_b*B-kp7NaD'QdWQPK"
+    "Yq[@>P)hI;*_F]u`Rb[.j8_Q/<&>uu+VsH$sM9TA%?)(vmJ80),P7E>)tjD%2L=-t#fK[%`v=Q8<FfNkgg^oIbah*#8/Qt$F&:K*-(N/'+1vMB,u()-a.VUU*#[e%gAAO(S>WlA2);Sa"
+    ">gXm8YB`1d@K#n]76-a$U,mF<fX]idqd)<3,]J7JmW4`6]uks=4-72L(jEk+:bJ0M^q-8Dm_Z?0olP1C9Sa&H[d&c$ooQUj]Exd*3ZM@-WGW2%s',B-_M%>%Ul:#/'xoFM9QX-$.QN'>"
+    "[%$Z$uF6pA6Ki2O5:8w*vP1<-1`[G,)-m#>0`P&#eb#.3i)rtB61(o'$?X3B</R90;eZ]%Ncq;-Tl]#F>2Qft^ae_5tKL9MUe9b*sLEQ95C&`=G?@Mj=wh*'3E>=-<)Gt*Iw)'QG:`@I"
+    "wOf7&]1i'S01B+Ev/Nac#9S;=;YQpg_6U`*kVY39xK,[/6Aj7:'1Bm-_1EYfa1+o&o4hp7KN_Q(OlIo@S%;jVdn0'1<Vc52=u`3^o-n1'g4v58Hj&6_t7$##?M)c<$bgQ_'SY((-xkA#"
+    "Y(,p'H9rIVY-b,'%bCPF7.J<Up^,(dU1VY*5#WkTU>h19w,WQhLI)3S#f$2(eb,jr*b;3Vw]*7NH%$c4Vs,eD9>XW8?N]o+(*pgC%/72LV-u<Hp,3@e^9UB1J+ak9-TN/mhKPg+AJYd$"
+    "MlvAF_jCK*.O-^(63adMT->W%iewS8W6m2rtCpo'RS1R84=@paTKt)>=%&1[)*vp'u+x,VrwN;&]kuO9JDbg=pO$J*.jVe;u'm0dr9l,<*wMK*Oe=g8lV_KEBFkO'oU]^=[-792#ok,)"
+    "i]lR8qQ2oA8wcRCZ^7w/Njh;?.stX?Q1>S1q4Bn$)K1<-rGdO'$Wr.Lc.CG)$/*JL4tNR/,SVO3,aUw'DJN:)Ss;wGn9A32ijw%FL+Z0Fn.U9;reSq)bmI32U==5ALuG&#Vf1398/pVo"
+    "1*c-(aY168o<`JsSbk-,1N;$>0:OUas(3:8Z972LSfF8eb=c-;>SPw7.6hn3m`9^Xkn(r.qS[0;T%&Qc=+STRxX'q1BNk3&*eu2;&8q$&x>Q#Q7^Tf+6<(d%ZVmj2bDi%.3L2n+4W'$P"
+    "iDDG)g,r%+?,$@?uou5tSe2aN_AQU*<h`e-GI7)?OK2A.d7_c)?wQ5AS@DL3r#7fSkgl6-++D:'A,uq7SvlB$pcpH'q3n0#_%dY#xCpr-l<F0NR@-##FEV6NTF6##$l84N1w?AO>'IAO"
+    "URQ##V^Fv-XFbGM7Fl(N<3DhLGF%q.1rC$#:T__&Pi68%0xi_&[qFJ(77j_&JWoF.V735&T,[R*:xFR*K5>>#`bW-?4Ne_&6Ne_&6Ne_&n`kr-#GJcM6X;uM6X;uM(.a..^2TkL%oR(#"
+    ";u.T%fAr%4tJ8&><1=GHZ_+m9/#H1F^R#SC#*N=BA9(D?v[UiFY>>^8p,KKF.W]L29uLkLlu/+4T<XoIB&hx=T1PcDaB&;HH+-AFr?(m9HZV)FKS8JCw;SD=6[^/DZUL`EUDf]GGlG&>"
+    "w$)F./^n3+rlo+DB;5sIYGNk+i1t-69Jg--0pao7Sm#K)pdHW&;LuDNH@H>#/X-TI(;P>#,Gc>#0Su>#4`1?#8lC?#<xU?#@.i?#D:%@#HF7@#LRI@#P_[@#Tkn@#Xw*A#]-=A#a9OA#"
+    "d<F&#*;G##.GY##2Sl##6`($#:l:$#>xL$#B.`$#F:r$#JF.%#NR@%#R_R%#Vke%#Zww%#_-4&#3^Rh%Sflr-k'MS.o?.5/sWel/wpEM0%3'/1)K^f1-d>G21&v(35>V`39V7A4=onx4"
+    "A1OY5EI0;6Ibgr6M$HS7Q<)58C5w,;WoA*#[%T*#`1g*#d=#+#hI5+#lUG+#pbY+#tnl+#x$),#&1;,#*=M,#.I`,#2Ur,#6b.-#;w[H#iQtA#m^0B#qjBB#uvTB##-hB#'9$C#+E6C#"
+    "/QHC#3^ZC#7jmC#;v)D#?,<D#C8ND#GDaD#KPsD#O]/E#g1A5#KA*1#gC17#MGd;#8(02#L-d3#rWM4#Hga1#,<w0#T.j<#O#'2#CYN1#qa^:#_4m3#o@/=#eG8=#t8J5#`+78#4uI-#"
+    "m3B2#SB[8#Q0@8#i[*9#iOn8#1Nm;#^sN9#qh<9#:=x-#P;K2#$%X9#bC+.#Rg;<#mN=.#MTF.#RZO.#2?)4#Y#(/#[)1/#b;L/#dAU/#0Sv;#lY$0#n`-0#sf60#(F24#wrH0#%/e0#"
+    "TmD<#%JSMFove:CTBEXI:<eh2g)B,3h2^G3i;#d3jD>)4kMYD4lVu`4m`:&5niUA5@(A5BA1]PBB:xlBCC=2CDLXMCEUtiCf&0g2'tN?PGT4CPGT4CPGT4CPGT4CPGT4CPGT4CPGT4CP"
+    "GT4CPGT4CPGT4CPGT4CPGT4CPGT4CP-qekC`.9kEg^+F$kwViFJTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5o,^<-28ZI'O?;xp"
+    "O?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xp;7q-#lLYI:xvD=#";
+
+static const char* GetDefaultCompressedFontDataTTFBase85()
+{
+    return proggy_clean_ttf_compressed_data_base85;
+}
diff --git a/external/Vulkan/external/imgui/imgui_internal.h b/external/Vulkan/external/imgui/imgui_internal.h
new file mode 100644
index 0000000000000000000000000000000000000000..050f2acf241790e5dbd1060a1ab4383b62716dd9
--- /dev/null
+++ b/external/Vulkan/external/imgui/imgui_internal.h
@@ -0,0 +1,1291 @@
+// dear imgui, v1.65
+// (internal structures/api)
+
+// You may use this file to debug, understand or extend ImGui features but we don't provide any guarantee of forward compatibility!
+// Set:
+//   #define IMGUI_DEFINE_MATH_OPERATORS
+// To implement maths operators for ImVec2 (disabled by default to not collide with using IM_VEC2_CLASS_EXTRA along with your own math types+operators)
+
+#pragma once
+
+#ifndef IMGUI_VERSION
+#error Must include imgui.h before imgui_internal.h
+#endif
+
+#include <stdio.h>      // FILE*
+#include <stdlib.h>     // NULL, malloc, free, qsort, atoi, atof
+#include <math.h>       // sqrtf, fabsf, fmodf, powf, floorf, ceilf, cosf, sinf
+#include <limits.h>     // INT_MIN, INT_MAX
+
+#ifdef _MSC_VER
+#pragma warning (push)
+#pragma warning (disable: 4251) // class 'xxx' needs to have dll-interface to be used by clients of struct 'xxx' // when IMGUI_API is set to__declspec(dllexport)
+#endif
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunused-function"        // for stb_textedit.h
+#pragma clang diagnostic ignored "-Wmissing-prototypes"     // for stb_textedit.h
+#pragma clang diagnostic ignored "-Wold-style-cast"
+#endif
+
+//-----------------------------------------------------------------------------
+// Forward Declarations
+//-----------------------------------------------------------------------------
+
+struct ImRect;                      // An axis-aligned rectangle (2 points)
+struct ImDrawDataBuilder;           // Helper to build a ImDrawData instance
+struct ImDrawListSharedData;        // Data shared between all ImDrawList instances
+struct ImGuiColorMod;               // Stacked color modifier, backup of modified data so we can restore it
+struct ImGuiColumnData;             // Storage data for a single column
+struct ImGuiColumnsSet;             // Storage data for a columns set
+struct ImGuiContext;                // Main imgui context
+struct ImGuiGroupData;              // Stacked storage data for BeginGroup()/EndGroup()
+struct ImGuiInputTextState;         // Internal state of the currently focused/edited text input box
+struct ImGuiItemHoveredDataBackup;  // Backup and restore IsItemHovered() internal data
+struct ImGuiMenuColumns;            // Simple column measurement, currently used for MenuItem() only
+struct ImGuiNavMoveResult;          // Result of a directional navigation move query result
+struct ImGuiNextWindowData;         // Storage for SetNexWindow** functions
+struct ImGuiPopupRef;               // Storage for current popup stack
+struct ImGuiSettingsHandler;        // Storage for one type registered in the .ini file
+struct ImGuiStyleMod;               // Stacked style modifier, backup of modified data so we can restore it
+struct ImGuiWindow;                 // Storage for one window
+struct ImGuiWindowTempData;         // Temporary storage for one window (that's the data which in theory we could ditch at the end of the frame)
+struct ImGuiWindowSettings;         // Storage for window settings stored in .ini file (we keep one of those even if the actual window wasn't instanced during this session)
+
+// Use your programming IDE "Go to definition" facility on the names of the center columns to find the actual flags/enum lists.
+typedef int ImGuiLayoutType;        // -> enum ImGuiLayoutType_        // Enum: Horizontal or vertical
+typedef int ImGuiButtonFlags;       // -> enum ImGuiButtonFlags_       // Flags: for ButtonEx(), ButtonBehavior()
+typedef int ImGuiItemFlags;         // -> enum ImGuiItemFlags_         // Flags: for PushItemFlag()
+typedef int ImGuiItemStatusFlags;   // -> enum ImGuiItemStatusFlags_   // Flags: for DC.LastItemStatusFlags
+typedef int ImGuiNavHighlightFlags; // -> enum ImGuiNavHighlightFlags_ // Flags: for RenderNavHighlight()
+typedef int ImGuiNavDirSourceFlags; // -> enum ImGuiNavDirSourceFlags_ // Flags: for GetNavInputAmount2d()
+typedef int ImGuiNavMoveFlags;      // -> enum ImGuiNavMoveFlags_      // Flags: for navigation requests
+typedef int ImGuiSeparatorFlags;    // -> enum ImGuiSeparatorFlags_    // Flags: for Separator() - internal
+typedef int ImGuiSliderFlags;       // -> enum ImGuiSliderFlags_       // Flags: for SliderBehavior()
+
+//-------------------------------------------------------------------------
+// STB libraries
+//-------------------------------------------------------------------------
+
+namespace ImGuiStb
+{
+
+#undef STB_TEXTEDIT_STRING
+#undef STB_TEXTEDIT_CHARTYPE
+#define STB_TEXTEDIT_STRING             ImGuiInputTextState
+#define STB_TEXTEDIT_CHARTYPE           ImWchar
+#define STB_TEXTEDIT_GETWIDTH_NEWLINE   -1.0f
+#include "imstb_textedit.h"
+
+} // namespace ImGuiStb
+
+//-----------------------------------------------------------------------------
+// Context
+//-----------------------------------------------------------------------------
+
+#ifndef GImGui
+extern IMGUI_API ImGuiContext* GImGui;  // Current implicit ImGui context pointer
+#endif
+
+//-----------------------------------------------------------------------------
+// Helpers
+//-----------------------------------------------------------------------------
+
+#define IM_PI           3.14159265358979323846f
+#ifdef _WIN32
+#define IM_NEWLINE      "\r\n"   // Play it nice with Windows users (2018/05 news: Microsoft announced that Notepad will finally display Unix-style carriage returns!)
+#else
+#define IM_NEWLINE      "\n"
+#endif
+#define IM_STATIC_ASSERT(_COND)         typedef char static_assertion_##__line__[(_COND)?1:-1]
+#define IM_F32_TO_INT8_UNBOUND(_VAL)    ((int)((_VAL) * 255.0f + ((_VAL)>=0 ? 0.5f : -0.5f)))   // Unsaturated, for display purpose
+#define IM_F32_TO_INT8_SAT(_VAL)        ((int)(ImSaturate(_VAL) * 255.0f + 0.5f))               // Saturated, always output 0..255
+
+// Enforce cdecl calling convention for functions called by the standard library, in case compilation settings changed the default to e.g. __vectorcall
+#ifdef _MSC_VER
+#define IMGUI_CDECL __cdecl
+#else
+#define IMGUI_CDECL
+#endif
+
+// Helpers: UTF-8 <> wchar
+IMGUI_API int           ImTextStrToUtf8(char* buf, int buf_size, const ImWchar* in_text, const ImWchar* in_text_end);      // return output UTF-8 bytes count
+IMGUI_API int           ImTextCharFromUtf8(unsigned int* out_char, const char* in_text, const char* in_text_end);          // read one character. return input UTF-8 bytes count
+IMGUI_API int           ImTextStrFromUtf8(ImWchar* buf, int buf_size, const char* in_text, const char* in_text_end, const char** in_remaining = NULL);   // return input UTF-8 bytes count
+IMGUI_API int           ImTextCountCharsFromUtf8(const char* in_text, const char* in_text_end);                            // return number of UTF-8 code-points (NOT bytes count)
+IMGUI_API int           ImTextCountUtf8BytesFromChar(const char* in_text, const char* in_text_end);                        // return number of bytes to express one char in UTF-8
+IMGUI_API int           ImTextCountUtf8BytesFromStr(const ImWchar* in_text, const ImWchar* in_text_end);                   // return number of bytes to express string in UTF-8
+
+// Helpers: Misc
+IMGUI_API ImU32         ImHash(const void* data, int data_size, ImU32 seed = 0);    // Pass data_size==0 for zero-terminated strings
+IMGUI_API void*         ImFileLoadToMemory(const char* filename, const char* file_open_mode, size_t* out_file_size = NULL, int padding_bytes = 0);
+IMGUI_API FILE*         ImFileOpen(const char* filename, const char* file_open_mode);
+static inline bool      ImCharIsBlankA(char c)          { return c == ' ' || c == '\t'; }
+static inline bool      ImCharIsBlankW(unsigned int c)  { return c == ' ' || c == '\t' || c == 0x3000; }
+static inline bool      ImIsPowerOfTwo(int v)           { return v != 0 && (v & (v - 1)) == 0; }
+static inline int       ImUpperPowerOfTwo(int v)        { v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; v++; return v; }
+#define ImQsort         qsort
+
+// Helpers: Geometry
+IMGUI_API ImVec2        ImLineClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& p);
+IMGUI_API bool          ImTriangleContainsPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p);
+IMGUI_API ImVec2        ImTriangleClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p);
+IMGUI_API void          ImTriangleBarycentricCoords(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p, float& out_u, float& out_v, float& out_w);
+IMGUI_API ImGuiDir      ImGetDirQuadrantFromDelta(float dx, float dy);
+
+// Helpers: String
+IMGUI_API int           ImStricmp(const char* str1, const char* str2);
+IMGUI_API int           ImStrnicmp(const char* str1, const char* str2, size_t count);
+IMGUI_API void          ImStrncpy(char* dst, const char* src, size_t count);
+IMGUI_API char*         ImStrdup(const char* str);
+IMGUI_API const char*   ImStrchrRange(const char* str_begin, const char* str_end, char c);
+IMGUI_API int           ImStrlenW(const ImWchar* str);
+IMGUI_API const ImWchar*ImStrbolW(const ImWchar* buf_mid_line, const ImWchar* buf_begin); // Find beginning-of-line
+IMGUI_API const char*   ImStristr(const char* haystack, const char* haystack_end, const char* needle, const char* needle_end);
+IMGUI_API void          ImStrTrimBlanks(char* str);
+IMGUI_API int           ImFormatString(char* buf, size_t buf_size, const char* fmt, ...) IM_FMTARGS(3);
+IMGUI_API int           ImFormatStringV(char* buf, size_t buf_size, const char* fmt, va_list args) IM_FMTLIST(3);
+IMGUI_API const char*   ImParseFormatFindStart(const char* format);
+IMGUI_API const char*   ImParseFormatFindEnd(const char* format);
+IMGUI_API const char*   ImParseFormatTrimDecorations(const char* format, char* buf, int buf_size);
+IMGUI_API int           ImParseFormatPrecision(const char* format, int default_value);
+
+// Helpers: ImVec2/ImVec4 operators
+// We are keeping those disabled by default so they don't leak in user space, to allow user enabling implicit cast operators between ImVec2 and their own types (using IM_VEC2_CLASS_EXTRA etc.)
+// We unfortunately don't have a unary- operator for ImVec2 because this would needs to be defined inside the class itself.
+#ifdef IMGUI_DEFINE_MATH_OPERATORS
+static inline ImVec2 operator*(const ImVec2& lhs, const float rhs)              { return ImVec2(lhs.x*rhs, lhs.y*rhs); }
+static inline ImVec2 operator/(const ImVec2& lhs, const float rhs)              { return ImVec2(lhs.x/rhs, lhs.y/rhs); }
+static inline ImVec2 operator+(const ImVec2& lhs, const ImVec2& rhs)            { return ImVec2(lhs.x+rhs.x, lhs.y+rhs.y); }
+static inline ImVec2 operator-(const ImVec2& lhs, const ImVec2& rhs)            { return ImVec2(lhs.x-rhs.x, lhs.y-rhs.y); }
+static inline ImVec2 operator*(const ImVec2& lhs, const ImVec2& rhs)            { return ImVec2(lhs.x*rhs.x, lhs.y*rhs.y); }
+static inline ImVec2 operator/(const ImVec2& lhs, const ImVec2& rhs)            { return ImVec2(lhs.x/rhs.x, lhs.y/rhs.y); }
+static inline ImVec2& operator+=(ImVec2& lhs, const ImVec2& rhs)                { lhs.x += rhs.x; lhs.y += rhs.y; return lhs; }
+static inline ImVec2& operator-=(ImVec2& lhs, const ImVec2& rhs)                { lhs.x -= rhs.x; lhs.y -= rhs.y; return lhs; }
+static inline ImVec2& operator*=(ImVec2& lhs, const float rhs)                  { lhs.x *= rhs; lhs.y *= rhs; return lhs; }
+static inline ImVec2& operator/=(ImVec2& lhs, const float rhs)                  { lhs.x /= rhs; lhs.y /= rhs; return lhs; }
+static inline ImVec4 operator+(const ImVec4& lhs, const ImVec4& rhs)            { return ImVec4(lhs.x+rhs.x, lhs.y+rhs.y, lhs.z+rhs.z, lhs.w+rhs.w); }
+static inline ImVec4 operator-(const ImVec4& lhs, const ImVec4& rhs)            { return ImVec4(lhs.x-rhs.x, lhs.y-rhs.y, lhs.z-rhs.z, lhs.w-rhs.w); }
+static inline ImVec4 operator*(const ImVec4& lhs, const ImVec4& rhs)            { return ImVec4(lhs.x*rhs.x, lhs.y*rhs.y, lhs.z*rhs.z, lhs.w*rhs.w); }
+#endif
+
+// Helpers: Maths
+// - Wrapper for standard libs functions. (Note that imgui_demo.cpp does _not_ use them to keep the code easy to copy)
+#ifndef IMGUI_DISABLE_MATH_FUNCTIONS
+static inline float  ImFabs(float x)                                            { return fabsf(x); }
+static inline float  ImSqrt(float x)                                            { return sqrtf(x); }
+static inline float  ImPow(float x, float y)                                    { return powf(x, y); }
+static inline double ImPow(double x, double y)                                  { return pow(x, y); }
+static inline float  ImFmod(float x, float y)                                   { return fmodf(x, y); }
+static inline double ImFmod(double x, double y)                                 { return fmod(x, y); }
+static inline float  ImCos(float x)                                             { return cosf(x); }
+static inline float  ImSin(float x)                                             { return sinf(x); }
+static inline float  ImAcos(float x)                                            { return acosf(x); }
+static inline float  ImAtan2(float y, float x)                                  { return atan2f(y, x); }
+static inline double ImAtof(const char* s)                                      { return atof(s); }
+static inline float  ImFloorStd(float x)                                        { return floorf(x); }   // we already uses our own ImFloor() { return (float)(int)v } internally so the standard one wrapper is named differently (it's used by stb_truetype)
+static inline float  ImCeil(float x)                                            { return ceilf(x); }
+#endif
+// - ImMin/ImMax/ImClamp/ImLerp/ImSwap are used by widgets which support for variety of types: signed/unsigned int/long long float/double, using templates here but we could also redefine them 6 times
+template<typename T> static inline T ImMin(T lhs, T rhs)                        { return lhs < rhs ? lhs : rhs; }
+template<typename T> static inline T ImMax(T lhs, T rhs)                        { return lhs >= rhs ? lhs : rhs; }
+template<typename T> static inline T ImClamp(T v, T mn, T mx)                   { return (v < mn) ? mn : (v > mx) ? mx : v; }
+template<typename T> static inline T ImLerp(T a, T b, float t)                  { return (T)(a + (b - a) * t); }
+template<typename T> static inline void ImSwap(T& a, T& b)                      { T tmp = a; a = b; b = tmp; }
+// - Misc maths helpers
+static inline ImVec2 ImMin(const ImVec2& lhs, const ImVec2& rhs)                { return ImVec2(lhs.x < rhs.x ? lhs.x : rhs.x, lhs.y < rhs.y ? lhs.y : rhs.y); }
+static inline ImVec2 ImMax(const ImVec2& lhs, const ImVec2& rhs)                { return ImVec2(lhs.x >= rhs.x ? lhs.x : rhs.x, lhs.y >= rhs.y ? lhs.y : rhs.y); }
+static inline ImVec2 ImClamp(const ImVec2& v, const ImVec2& mn, ImVec2 mx)      { return ImVec2((v.x < mn.x) ? mn.x : (v.x > mx.x) ? mx.x : v.x, (v.y < mn.y) ? mn.y : (v.y > mx.y) ? mx.y : v.y); }
+static inline ImVec2 ImLerp(const ImVec2& a, const ImVec2& b, float t)          { return ImVec2(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t); }
+static inline ImVec2 ImLerp(const ImVec2& a, const ImVec2& b, const ImVec2& t)  { return ImVec2(a.x + (b.x - a.x) * t.x, a.y + (b.y - a.y) * t.y); }
+static inline ImVec4 ImLerp(const ImVec4& a, const ImVec4& b, float t)          { return ImVec4(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t, a.w + (b.w - a.w) * t); }
+static inline float  ImSaturate(float f)                                        { return (f < 0.0f) ? 0.0f : (f > 1.0f) ? 1.0f : f; }
+static inline float  ImLengthSqr(const ImVec2& lhs)                             { return lhs.x*lhs.x + lhs.y*lhs.y; }
+static inline float  ImLengthSqr(const ImVec4& lhs)                             { return lhs.x*lhs.x + lhs.y*lhs.y + lhs.z*lhs.z + lhs.w*lhs.w; }
+static inline float  ImInvLength(const ImVec2& lhs, float fail_value)           { float d = lhs.x*lhs.x + lhs.y*lhs.y; if (d > 0.0f) return 1.0f / ImSqrt(d); return fail_value; }
+static inline float  ImFloor(float f)                                           { return (float)(int)f; }
+static inline ImVec2 ImFloor(const ImVec2& v)                                   { return ImVec2((float)(int)v.x, (float)(int)v.y); }
+static inline float  ImDot(const ImVec2& a, const ImVec2& b)                    { return a.x * b.x + a.y * b.y; }
+static inline ImVec2 ImRotate(const ImVec2& v, float cos_a, float sin_a)        { return ImVec2(v.x * cos_a - v.y * sin_a, v.x * sin_a + v.y * cos_a); }
+static inline float  ImLinearSweep(float current, float target, float speed)    { if (current < target) return ImMin(current + speed, target); if (current > target) return ImMax(current - speed, target); return current; }
+static inline ImVec2 ImMul(const ImVec2& lhs, const ImVec2& rhs)                { return ImVec2(lhs.x * rhs.x, lhs.y * rhs.y); }
+
+//-----------------------------------------------------------------------------
+// Types
+//-----------------------------------------------------------------------------
+
+// 1D vector (this odd construct is used to facilitate the transition between 1D and 2D and maintenance of some patches)
+struct ImVec1
+{
+    float     x;
+    ImVec1() { x = 0.0f; }
+    ImVec1(float _x) { x = _x; }
+};
+
+enum ImGuiButtonFlags_
+{
+    ImGuiButtonFlags_None                   = 0,
+    ImGuiButtonFlags_Repeat                 = 1 << 0,   // hold to repeat
+    ImGuiButtonFlags_PressedOnClickRelease  = 1 << 1,   // return true on click + release on same item [DEFAULT if no PressedOn* flag is set]
+    ImGuiButtonFlags_PressedOnClick         = 1 << 2,   // return true on click (default requires click+release)
+    ImGuiButtonFlags_PressedOnRelease       = 1 << 3,   // return true on release (default requires click+release)
+    ImGuiButtonFlags_PressedOnDoubleClick   = 1 << 4,   // return true on double-click (default requires click+release)
+    ImGuiButtonFlags_FlattenChildren        = 1 << 5,   // allow interactions even if a child window is overlapping
+    ImGuiButtonFlags_AllowItemOverlap       = 1 << 6,   // require previous frame HoveredId to either match id or be null before being usable, use along with SetItemAllowOverlap()
+    ImGuiButtonFlags_DontClosePopups        = 1 << 7,   // disable automatically closing parent popup on press // [UNUSED]
+    ImGuiButtonFlags_Disabled               = 1 << 8,   // disable interactions
+    ImGuiButtonFlags_AlignTextBaseLine      = 1 << 9,   // vertically align button to match text baseline - ButtonEx() only // FIXME: Should be removed and handled by SmallButton(), not possible currently because of DC.CursorPosPrevLine
+    ImGuiButtonFlags_NoKeyModifiers         = 1 << 10,  // disable interaction if a key modifier is held
+    ImGuiButtonFlags_NoHoldingActiveID      = 1 << 11,  // don't set ActiveId while holding the mouse (ImGuiButtonFlags_PressedOnClick only)
+    ImGuiButtonFlags_PressedOnDragDropHold  = 1 << 12,  // press when held into while we are drag and dropping another item (used by e.g. tree nodes, collapsing headers)
+    ImGuiButtonFlags_NoNavFocus             = 1 << 13   // don't override navigation focus when activated
+};
+
+enum ImGuiSliderFlags_
+{
+    ImGuiSliderFlags_None                   = 0,
+    ImGuiSliderFlags_Vertical               = 1 << 0
+};
+
+enum ImGuiColumnsFlags_
+{
+    // Default: 0
+    ImGuiColumnsFlags_None                  = 0,
+    ImGuiColumnsFlags_NoBorder              = 1 << 0,   // Disable column dividers
+    ImGuiColumnsFlags_NoResize              = 1 << 1,   // Disable resizing columns when clicking on the dividers
+    ImGuiColumnsFlags_NoPreserveWidths      = 1 << 2,   // Disable column width preservation when adjusting columns
+    ImGuiColumnsFlags_NoForceWithinWindow   = 1 << 3,   // Disable forcing columns to fit within window
+    ImGuiColumnsFlags_GrowParentContentsSize= 1 << 4    // (WIP) Restore pre-1.51 behavior of extending the parent window contents size but _without affecting the columns width at all_. Will eventually remove.
+};
+
+enum ImGuiSelectableFlagsPrivate_
+{
+    // NB: need to be in sync with last value of ImGuiSelectableFlags_
+    ImGuiSelectableFlags_NoHoldingActiveID  = 1 << 10,
+    ImGuiSelectableFlags_PressedOnClick     = 1 << 11,
+    ImGuiSelectableFlags_PressedOnRelease   = 1 << 12,
+    ImGuiSelectableFlags_DrawFillAvailWidth = 1 << 13
+};
+
+enum ImGuiSeparatorFlags_
+{
+    ImGuiSeparatorFlags_None                = 0,
+    ImGuiSeparatorFlags_Horizontal          = 1 << 0,   // Axis default to current layout type, so generally Horizontal unless e.g. in a menu bar
+    ImGuiSeparatorFlags_Vertical            = 1 << 1
+};
+
+// Storage for LastItem data
+enum ImGuiItemStatusFlags_
+{
+    ImGuiItemStatusFlags_None               = 0,
+    ImGuiItemStatusFlags_HoveredRect        = 1 << 0,
+    ImGuiItemStatusFlags_HasDisplayRect     = 1 << 1,
+    ImGuiItemStatusFlags_Edited             = 1 << 2    // Value exposed by item was edited in the current frame (should match the bool return value of most widgets)
+};
+
+// FIXME: this is in development, not exposed/functional as a generic feature yet.
+enum ImGuiLayoutType_
+{
+    ImGuiLayoutType_Vertical,
+    ImGuiLayoutType_Horizontal
+};
+
+enum ImGuiAxis
+{
+    ImGuiAxis_None = -1,
+    ImGuiAxis_X = 0,
+    ImGuiAxis_Y = 1
+};
+
+enum ImGuiPlotType
+{
+    ImGuiPlotType_Lines,
+    ImGuiPlotType_Histogram
+};
+
+enum ImGuiInputSource
+{
+    ImGuiInputSource_None = 0,
+    ImGuiInputSource_Mouse,
+    ImGuiInputSource_Nav,
+    ImGuiInputSource_NavKeyboard,   // Only used occasionally for storage, not tested/handled by most code
+    ImGuiInputSource_NavGamepad,    // "
+    ImGuiInputSource_COUNT
+};
+
+// FIXME-NAV: Clarify/expose various repeat delay/rate
+enum ImGuiInputReadMode
+{
+    ImGuiInputReadMode_Down,
+    ImGuiInputReadMode_Pressed,
+    ImGuiInputReadMode_Released,
+    ImGuiInputReadMode_Repeat,
+    ImGuiInputReadMode_RepeatSlow,
+    ImGuiInputReadMode_RepeatFast
+};
+
+enum ImGuiNavHighlightFlags_
+{
+    ImGuiNavHighlightFlags_None         = 0,
+    ImGuiNavHighlightFlags_TypeDefault  = 1 << 0,
+    ImGuiNavHighlightFlags_TypeThin     = 1 << 1,
+    ImGuiNavHighlightFlags_AlwaysDraw   = 1 << 2,
+    ImGuiNavHighlightFlags_NoRounding   = 1 << 3
+};
+
+enum ImGuiNavDirSourceFlags_
+{
+    ImGuiNavDirSourceFlags_None         = 0,
+    ImGuiNavDirSourceFlags_Keyboard     = 1 << 0,
+    ImGuiNavDirSourceFlags_PadDPad      = 1 << 1,
+    ImGuiNavDirSourceFlags_PadLStick    = 1 << 2
+};
+
+enum ImGuiNavMoveFlags_
+{
+    ImGuiNavMoveFlags_None                  = 0,
+    ImGuiNavMoveFlags_LoopX                 = 1 << 0,   // On failed request, restart from opposite side
+    ImGuiNavMoveFlags_LoopY                 = 1 << 1,
+    ImGuiNavMoveFlags_WrapX                 = 1 << 2,   // On failed request, request from opposite side one line down (when NavDir==right) or one line up (when NavDir==left)
+    ImGuiNavMoveFlags_WrapY                 = 1 << 3,   // This is not super useful for provided for completeness
+    ImGuiNavMoveFlags_AllowCurrentNavId     = 1 << 4,   // Allow scoring and considering the current NavId as a move target candidate. This is used when the move source is offset (e.g. pressing PageDown actually needs to send a Up move request, if we are pressing PageDown from the bottom-most item we need to stay in place)
+    ImGuiNavMoveFlags_AlsoScoreVisibleSet   = 1 << 5    // Store alternate result in NavMoveResultLocalVisibleSet that only comprise elements that are already fully visible.
+};
+
+enum ImGuiNavForward
+{
+    ImGuiNavForward_None,
+    ImGuiNavForward_ForwardQueued,
+    ImGuiNavForward_ForwardActive
+};
+
+enum ImGuiPopupPositionPolicy
+{
+    ImGuiPopupPositionPolicy_Default,
+    ImGuiPopupPositionPolicy_ComboBox
+};
+
+// 2D axis aligned bounding-box
+// NB: we can't rely on ImVec2 math operators being available here
+struct IMGUI_API ImRect
+{
+    ImVec2      Min;    // Upper-left
+    ImVec2      Max;    // Lower-right
+
+    ImRect()                                        : Min(FLT_MAX,FLT_MAX), Max(-FLT_MAX,-FLT_MAX)  {}
+    ImRect(const ImVec2& min, const ImVec2& max)    : Min(min), Max(max)                            {}
+    ImRect(const ImVec4& v)                         : Min(v.x, v.y), Max(v.z, v.w)                  {}
+    ImRect(float x1, float y1, float x2, float y2)  : Min(x1, y1), Max(x2, y2)                      {}
+
+    ImVec2      GetCenter() const                   { return ImVec2((Min.x + Max.x) * 0.5f, (Min.y + Max.y) * 0.5f); }
+    ImVec2      GetSize() const                     { return ImVec2(Max.x - Min.x, Max.y - Min.y); }
+    float       GetWidth() const                    { return Max.x - Min.x; }
+    float       GetHeight() const                   { return Max.y - Min.y; }
+    ImVec2      GetTL() const                       { return Min; }                   // Top-left
+    ImVec2      GetTR() const                       { return ImVec2(Max.x, Min.y); }  // Top-right
+    ImVec2      GetBL() const                       { return ImVec2(Min.x, Max.y); }  // Bottom-left
+    ImVec2      GetBR() const                       { return Max; }                   // Bottom-right
+    bool        Contains(const ImVec2& p) const     { return p.x     >= Min.x && p.y     >= Min.y && p.x     <  Max.x && p.y     <  Max.y; }
+    bool        Contains(const ImRect& r) const     { return r.Min.x >= Min.x && r.Min.y >= Min.y && r.Max.x <= Max.x && r.Max.y <= Max.y; }
+    bool        Overlaps(const ImRect& r) const     { return r.Min.y <  Max.y && r.Max.y >  Min.y && r.Min.x <  Max.x && r.Max.x >  Min.x; }
+    void        Add(const ImVec2& p)                { if (Min.x > p.x)     Min.x = p.x;     if (Min.y > p.y)     Min.y = p.y;     if (Max.x < p.x)     Max.x = p.x;     if (Max.y < p.y)     Max.y = p.y; }
+    void        Add(const ImRect& r)                { if (Min.x > r.Min.x) Min.x = r.Min.x; if (Min.y > r.Min.y) Min.y = r.Min.y; if (Max.x < r.Max.x) Max.x = r.Max.x; if (Max.y < r.Max.y) Max.y = r.Max.y; }
+    void        Expand(const float amount)          { Min.x -= amount;   Min.y -= amount;   Max.x += amount;   Max.y += amount; }
+    void        Expand(const ImVec2& amount)        { Min.x -= amount.x; Min.y -= amount.y; Max.x += amount.x; Max.y += amount.y; }
+    void        Translate(const ImVec2& d)          { Min.x += d.x; Min.y += d.y; Max.x += d.x; Max.y += d.y; }
+    void        TranslateX(float dx)                { Min.x += dx; Max.x += dx; }
+    void        TranslateY(float dy)                { Min.y += dy; Max.y += dy; }
+    void        ClipWith(const ImRect& r)           { Min = ImMax(Min, r.Min); Max = ImMin(Max, r.Max); }                   // Simple version, may lead to an inverted rectangle, which is fine for Contains/Overlaps test but not for display.
+    void        ClipWithFull(const ImRect& r)       { Min = ImClamp(Min, r.Min, r.Max); Max = ImClamp(Max, r.Min, r.Max); } // Full version, ensure both points are fully clipped.
+    void        Floor()                             { Min.x = (float)(int)Min.x; Min.y = (float)(int)Min.y; Max.x = (float)(int)Max.x; Max.y = (float)(int)Max.y; }
+    bool        IsInverted() const                  { return Min.x > Max.x || Min.y > Max.y; }
+};
+
+// Stacked color modifier, backup of modified data so we can restore it
+struct ImGuiColorMod
+{
+    ImGuiCol    Col;
+    ImVec4      BackupValue;
+};
+
+// Stacked style modifier, backup of modified data so we can restore it. Data type inferred from the variable.
+struct ImGuiStyleMod
+{
+    ImGuiStyleVar   VarIdx;
+    union           { int BackupInt[2]; float BackupFloat[2]; };
+    ImGuiStyleMod(ImGuiStyleVar idx, int v)     { VarIdx = idx; BackupInt[0] = v; }
+    ImGuiStyleMod(ImGuiStyleVar idx, float v)   { VarIdx = idx; BackupFloat[0] = v; }
+    ImGuiStyleMod(ImGuiStyleVar idx, ImVec2 v)  { VarIdx = idx; BackupFloat[0] = v.x; BackupFloat[1] = v.y; }
+};
+
+// Stacked storage data for BeginGroup()/EndGroup()
+struct ImGuiGroupData
+{
+    ImVec2      BackupCursorPos;
+    ImVec2      BackupCursorMaxPos;
+    ImVec1      BackupIndent;
+    ImVec1      BackupGroupOffset;
+    ImVec2      BackupCurrentLineSize;
+    float       BackupCurrentLineTextBaseOffset;
+    float       BackupLogLinePosY;
+    ImGuiID     BackupActiveIdIsAlive;
+    bool        BackupActiveIdPreviousFrameIsAlive;
+    bool        AdvanceCursor;
+};
+
+// Simple column measurement, currently used for MenuItem() only.. This is very short-sighted/throw-away code and NOT a generic helper.
+struct IMGUI_API ImGuiMenuColumns
+{
+    int         Count;
+    float       Spacing;
+    float       Width, NextWidth;
+    float       Pos[4], NextWidths[4];
+
+    ImGuiMenuColumns();
+    void        Update(int count, float spacing, bool clear);
+    float       DeclColumns(float w0, float w1, float w2);
+    float       CalcExtraSpace(float avail_w);
+};
+
+// Internal state of the currently focused/edited text input box
+struct IMGUI_API ImGuiInputTextState
+{
+    ImGuiID                 ID;                     // widget id owning the text state
+    ImVector<ImWchar>       TextW;                  // edit buffer, we need to persist but can't guarantee the persistence of the user-provided buffer. so we copy into own buffer.
+    ImVector<char>          InitialText;            // backup of end-user buffer at the time of focus (in UTF-8, unaltered)
+    ImVector<char>          TempBuffer;             // temporary buffer for callback and other other operations. size=capacity.
+    int                     CurLenA, CurLenW;       // we need to maintain our buffer length in both UTF-8 and wchar format.
+    int                     BufCapacityA;           // end-user buffer capacity
+    float                   ScrollX;
+    ImGuiStb::STB_TexteditState StbState;
+    float                   CursorAnim;
+    bool                    CursorFollow;
+    bool                    SelectedAllMouseLock;
+
+    // Temporarily set when active
+    ImGuiInputTextFlags     UserFlags;
+    ImGuiInputTextCallback  UserCallback;
+    void*                   UserCallbackData;
+
+    ImGuiInputTextState()                           { memset(this, 0, sizeof(*this)); }
+    void                CursorAnimReset()           { CursorAnim = -0.30f; }                                   // After a user-input the cursor stays on for a while without blinking
+    void                CursorClamp()               { StbState.cursor = ImMin(StbState.cursor, CurLenW); StbState.select_start = ImMin(StbState.select_start, CurLenW); StbState.select_end = ImMin(StbState.select_end, CurLenW); }
+    bool                HasSelection() const        { return StbState.select_start != StbState.select_end; }
+    void                ClearSelection()            { StbState.select_start = StbState.select_end = StbState.cursor; }
+    void                SelectAll()                 { StbState.select_start = 0; StbState.cursor = StbState.select_end = CurLenW; StbState.has_preferred_x = false; }
+    void                OnKeyPressed(int key);      // Cannot be inline because we call in code in stb_textedit.h implementation
+};
+
+// Windows data saved in imgui.ini file
+struct ImGuiWindowSettings
+{
+    char*       Name;
+    ImGuiID     ID;
+    ImVec2      Pos;
+    ImVec2      Size;
+    bool        Collapsed;
+
+    ImGuiWindowSettings() { Name = NULL; ID = 0; Pos = Size = ImVec2(0,0); Collapsed = false; }
+};
+
+struct ImGuiSettingsHandler
+{
+    const char* TypeName;   // Short description stored in .ini file. Disallowed characters: '[' ']'
+    ImGuiID     TypeHash;   // == ImHash(TypeName, 0, 0)
+    void*       (*ReadOpenFn)(ImGuiContext* ctx, ImGuiSettingsHandler* handler, const char* name);              // Read: Called when entering into a new ini entry e.g. "[Window][Name]"
+    void        (*ReadLineFn)(ImGuiContext* ctx, ImGuiSettingsHandler* handler, void* entry, const char* line); // Read: Called for every line of text within an ini entry
+    void        (*WriteAllFn)(ImGuiContext* ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* out_buf);      // Write: Output every entries into 'out_buf'
+    void*       UserData;
+
+    ImGuiSettingsHandler() { memset(this, 0, sizeof(*this)); }
+};
+
+// Storage for current popup stack
+struct ImGuiPopupRef
+{
+    ImGuiID             PopupId;        // Set on OpenPopup()
+    ImGuiWindow*        Window;         // Resolved on BeginPopup() - may stay unresolved if user never calls OpenPopup()
+    ImGuiWindow*        ParentWindow;   // Set on OpenPopup()
+    int                 OpenFrameCount; // Set on OpenPopup()
+    ImGuiID             OpenParentId;   // Set on OpenPopup(), we need this to differenciate multiple menu sets from each others (e.g. inside menu bar vs loose menu items)
+    ImVec2              OpenPopupPos;   // Set on OpenPopup(), preferred popup position (typically == OpenMousePos when using mouse)
+    ImVec2              OpenMousePos;   // Set on OpenPopup(), copy of mouse position at the time of opening popup
+};
+
+struct ImGuiColumnData
+{
+    float               OffsetNorm;         // Column start offset, normalized 0.0 (far left) -> 1.0 (far right)
+    float               OffsetNormBeforeResize;
+    ImGuiColumnsFlags   Flags;              // Not exposed
+    ImRect              ClipRect;
+
+    ImGuiColumnData()   { OffsetNorm = OffsetNormBeforeResize = 0.0f; Flags = 0; }
+};
+
+struct ImGuiColumnsSet
+{
+    ImGuiID             ID;
+    ImGuiColumnsFlags   Flags;
+    bool                IsFirstFrame;
+    bool                IsBeingResized;
+    int                 Current;
+    int                 Count;
+    float               MinX, MaxX;
+    float               LineMinY, LineMaxY;
+    float               StartPosY;          // Copy of CursorPos
+    float               StartMaxPosX;       // Copy of CursorMaxPos
+    ImVector<ImGuiColumnData> Columns;
+
+    ImGuiColumnsSet()   { Clear(); }
+    void Clear()
+    {
+        ID = 0;
+        Flags = 0;
+        IsFirstFrame = false;
+        IsBeingResized = false;
+        Current = 0;
+        Count = 1;
+        MinX = MaxX = 0.0f;
+        LineMinY = LineMaxY = 0.0f;
+        StartPosY = 0.0f;
+        StartMaxPosX = 0.0f;
+        Columns.clear();
+    }
+};
+
+// Data shared between all ImDrawList instances
+struct IMGUI_API ImDrawListSharedData
+{
+    ImVec2          TexUvWhitePixel;            // UV of white pixel in the atlas
+    ImFont*         Font;                       // Current/default font (optional, for simplified AddText overload)
+    float           FontSize;                   // Current/default font size (optional, for simplified AddText overload)
+    float           CurveTessellationTol;
+    ImVec4          ClipRectFullscreen;         // Value for PushClipRectFullscreen()
+
+    // Const data
+    // FIXME: Bake rounded corners fill/borders in atlas
+    ImVec2          CircleVtx12[12];
+
+    ImDrawListSharedData();
+};
+
+struct ImDrawDataBuilder
+{
+    ImVector<ImDrawList*>   Layers[2];           // Global layers for: regular, tooltip
+
+    void Clear()            { for (int n = 0; n < IM_ARRAYSIZE(Layers); n++) Layers[n].resize(0); }
+    void ClearFreeMemory()  { for (int n = 0; n < IM_ARRAYSIZE(Layers); n++) Layers[n].clear(); }
+    IMGUI_API void FlattenIntoSingleLayer();
+};
+
+struct ImGuiNavMoveResult
+{
+    ImGuiID       ID;           // Best candidate
+    ImGuiWindow*  Window;       // Best candidate window
+    float         DistBox;      // Best candidate box distance to current NavId
+    float         DistCenter;   // Best candidate center distance to current NavId
+    float         DistAxial;
+    ImRect        RectRel;      // Best candidate bounding box in window relative space
+
+    ImGuiNavMoveResult() { Clear(); }
+    void Clear()         { ID = 0; Window = NULL; DistBox = DistCenter = DistAxial = FLT_MAX; RectRel = ImRect(); }
+};
+
+// Storage for SetNexWindow** functions
+struct ImGuiNextWindowData
+{
+    ImGuiCond               PosCond;
+    ImGuiCond               SizeCond;
+    ImGuiCond               ContentSizeCond;
+    ImGuiCond               CollapsedCond;
+    ImGuiCond               SizeConstraintCond;
+    ImGuiCond               FocusCond;
+    ImGuiCond               BgAlphaCond;
+    ImVec2                  PosVal;
+    ImVec2                  PosPivotVal;
+    ImVec2                  SizeVal;
+    ImVec2                  ContentSizeVal;
+    bool                    CollapsedVal;
+    ImRect                  SizeConstraintRect;
+    ImGuiSizeCallback       SizeCallback;
+    void*                   SizeCallbackUserData;
+    float                   BgAlphaVal;
+    ImVec2                  MenuBarOffsetMinVal;                // This is not exposed publicly, so we don't clear it.
+
+    ImGuiNextWindowData()
+    {
+        PosCond = SizeCond = ContentSizeCond = CollapsedCond = SizeConstraintCond = FocusCond = BgAlphaCond = 0;
+        PosVal = PosPivotVal = SizeVal = ImVec2(0.0f, 0.0f);
+        ContentSizeVal = ImVec2(0.0f, 0.0f);
+        CollapsedVal = false;
+        SizeConstraintRect = ImRect();
+        SizeCallback = NULL;
+        SizeCallbackUserData = NULL;
+        BgAlphaVal = FLT_MAX;
+        MenuBarOffsetMinVal = ImVec2(0.0f, 0.0f);
+    }
+
+    void    Clear()
+    {
+        PosCond = SizeCond = ContentSizeCond = CollapsedCond = SizeConstraintCond = FocusCond = BgAlphaCond = 0;
+    }
+};
+
+// Main imgui context
+struct ImGuiContext
+{
+    bool                    Initialized;
+    bool                    FrameScopeActive;                   // Set by NewFrame(), cleared by EndFrame()/Render()
+    bool                    FontAtlasOwnedByContext;            // Io.Fonts-> is owned by the ImGuiContext and will be destructed along with it.
+    ImGuiIO                 IO;
+    ImGuiStyle              Style;
+    ImFont*                 Font;                               // (Shortcut) == FontStack.empty() ? IO.Font : FontStack.back()
+    float                   FontSize;                           // (Shortcut) == FontBaseSize * g.CurrentWindow->FontWindowScale == window->FontSize(). Text height for current window.
+    float                   FontBaseSize;                       // (Shortcut) == IO.FontGlobalScale * Font->Scale * Font->FontSize. Base text height.
+    ImDrawListSharedData    DrawListSharedData;
+
+    double                  Time;
+    int                     FrameCount;
+    int                     FrameCountEnded;
+    int                     FrameCountRendered;
+    ImVector<ImGuiWindow*>  Windows;
+    ImVector<ImGuiWindow*>  WindowsSortBuffer;
+    ImVector<ImGuiWindow*>  CurrentWindowStack;
+    ImGuiStorage            WindowsById;
+    int                     WindowsActiveCount;
+    ImGuiWindow*            CurrentWindow;                      // Being drawn into
+    ImGuiWindow*            HoveredWindow;                      // Will catch mouse inputs
+    ImGuiWindow*            HoveredRootWindow;                  // Will catch mouse inputs (for focus/move only)
+    ImGuiID                 HoveredId;                          // Hovered widget
+    bool                    HoveredIdAllowOverlap;
+    ImGuiID                 HoveredIdPreviousFrame;
+    float                   HoveredIdTimer;
+    ImGuiID                 ActiveId;                           // Active widget
+    ImGuiID                 ActiveIdPreviousFrame;
+    ImGuiID                 ActiveIdIsAlive;                    // Active widget has been seen this frame (we can't use a bool as the ActiveId may change within the frame)
+    float                   ActiveIdTimer;
+    bool                    ActiveIdIsJustActivated;            // Set at the time of activation for one frame
+    bool                    ActiveIdAllowOverlap;               // Active widget allows another widget to steal active id (generally for overlapping widgets, but not always)
+    bool                    ActiveIdHasBeenEdited;              // Was the value associated to the widget Edited over the course of the Active state.
+    bool                    ActiveIdPreviousFrameIsAlive;
+    bool                    ActiveIdPreviousFrameHasBeenEdited;
+    int                     ActiveIdAllowNavDirFlags;           // Active widget allows using directional navigation (e.g. can activate a button and move away from it)
+    ImVec2                  ActiveIdClickOffset;                // Clicked offset from upper-left corner, if applicable (currently only set by ButtonBehavior)
+    ImGuiWindow*            ActiveIdWindow;
+    ImGuiWindow*            ActiveIdPreviousFrameWindow;
+    ImGuiInputSource        ActiveIdSource;                     // Activating with mouse or nav (gamepad/keyboard)
+    ImGuiID                 LastActiveId;                       // Store the last non-zero ActiveId, useful for animation.
+    float                   LastActiveIdTimer;                  // Store the last non-zero ActiveId timer since the beginning of activation, useful for animation.
+    ImGuiWindow*            MovingWindow;                       // Track the window we clicked on (in order to preserve focus). The actually window that is moved is generally MovingWindow->RootWindow.
+    ImVector<ImGuiColorMod>   ColorModifiers;                     // Stack for PushStyleColor()/PopStyleColor()
+    ImVector<ImGuiStyleMod> StyleModifiers;                     // Stack for PushStyleVar()/PopStyleVar()
+    ImVector<ImFont*>       FontStack;                          // Stack for PushFont()/PopFont()
+    ImVector<ImGuiPopupRef> OpenPopupStack;                     // Which popups are open (persistent)
+    ImVector<ImGuiPopupRef> CurrentPopupStack;                  // Which level of BeginPopup() we are in (reset every frame)
+    ImGuiNextWindowData     NextWindowData;                     // Storage for SetNextWindow** functions
+    bool                    NextTreeNodeOpenVal;                // Storage for SetNextTreeNode** functions
+    ImGuiCond               NextTreeNodeOpenCond;
+
+    // Navigation data (for gamepad/keyboard)
+    ImGuiWindow*            NavWindow;                          // Focused window for navigation. Could be called 'FocusWindow'
+    ImGuiID                 NavId;                              // Focused item for navigation
+    ImGuiID                 NavActivateId;                      // ~~ (g.ActiveId == 0) && IsNavInputPressed(ImGuiNavInput_Activate) ? NavId : 0, also set when calling ActivateItem()
+    ImGuiID                 NavActivateDownId;                  // ~~ IsNavInputDown(ImGuiNavInput_Activate) ? NavId : 0
+    ImGuiID                 NavActivatePressedId;               // ~~ IsNavInputPressed(ImGuiNavInput_Activate) ? NavId : 0
+    ImGuiID                 NavInputId;                         // ~~ IsNavInputPressed(ImGuiNavInput_Input) ? NavId : 0
+    ImGuiID                 NavJustTabbedId;                    // Just tabbed to this id.
+    ImGuiID                 NavJustMovedToId;                   // Just navigated to this id (result of a successfully MoveRequest)
+    ImGuiID                 NavNextActivateId;                  // Set by ActivateItem(), queued until next frame
+    ImGuiInputSource        NavInputSource;                     // Keyboard or Gamepad mode?
+    ImRect                  NavScoringRectScreen;               // Rectangle used for scoring, in screen space. Based of window->DC.NavRefRectRel[], modified for directional navigation scoring.
+    int                     NavScoringCount;                    // Metrics for debugging
+    ImGuiWindow*            NavWindowingTarget;                 // When selecting a window (holding Menu+FocusPrev/Next, or equivalent of CTRL-TAB) this window is temporarily displayed front-most.
+    ImGuiWindow*            NavWindowingTargetAnim;             // Record of last valid NavWindowingTarget until DimBgRatio and NavWindowingHighlightAlpha becomes 0.0f
+    ImGuiWindow*            NavWindowingList;
+    float                   NavWindowingTimer;
+    float                   NavWindowingHighlightAlpha;
+    bool                    NavWindowingToggleLayer;
+    int                     NavLayer;                           // Layer we are navigating on. For now the system is hard-coded for 0=main contents and 1=menu/title bar, may expose layers later.
+    int                     NavIdTabCounter;                    // == NavWindow->DC.FocusIdxTabCounter at time of NavId processing
+    bool                    NavIdIsAlive;                       // Nav widget has been seen this frame ~~ NavRefRectRel is valid
+    bool                    NavMousePosDirty;                   // When set we will update mouse position if (io.ConfigFlags & ImGuiConfigFlags_NavEnableSetMousePos) if set (NB: this not enabled by default)
+    bool                    NavDisableHighlight;                // When user starts using mouse, we hide gamepad/keyboard highlight (NB: but they are still available, which is why NavDisableHighlight isn't always != NavDisableMouseHover)
+    bool                    NavDisableMouseHover;               // When user starts using gamepad/keyboard, we hide mouse hovering highlight until mouse is touched again.
+    bool                    NavAnyRequest;                      // ~~ NavMoveRequest || NavInitRequest
+    bool                    NavInitRequest;                     // Init request for appearing window to select first item
+    bool                    NavInitRequestFromMove;
+    ImGuiID                 NavInitResultId;
+    ImRect                  NavInitResultRectRel;
+    bool                    NavMoveFromClampedRefRect;          // Set by manual scrolling, if we scroll to a point where NavId isn't visible we reset navigation from visible items
+    bool                    NavMoveRequest;                     // Move request for this frame
+    ImGuiNavMoveFlags       NavMoveRequestFlags;
+    ImGuiNavForward         NavMoveRequestForward;              // None / ForwardQueued / ForwardActive (this is used to navigate sibling parent menus from a child menu)
+    ImGuiDir                NavMoveDir, NavMoveDirLast;         // Direction of the move request (left/right/up/down), direction of the previous move request
+    ImGuiDir                NavMoveClipDir;
+    ImGuiNavMoveResult      NavMoveResultLocal;                 // Best move request candidate within NavWindow
+    ImGuiNavMoveResult      NavMoveResultLocalVisibleSet;       // Best move request candidate within NavWindow that are mostly visible (when using ImGuiNavMoveFlags_AlsoScoreVisibleSet flag)
+    ImGuiNavMoveResult      NavMoveResultOther;                 // Best move request candidate within NavWindow's flattened hierarchy (when using ImGuiWindowFlags_NavFlattened flag)
+
+    // Render
+    ImDrawData              DrawData;                           // Main ImDrawData instance to pass render information to the user
+    ImDrawDataBuilder       DrawDataBuilder;
+    float                   DimBgRatio;                         // 0.0..1.0 animation when fading in a dimming background (for modal window and CTRL+TAB list)
+    ImDrawList              OverlayDrawList;                    // Optional software render of mouse cursors, if io.MouseDrawCursor is set + a few debug overlays
+    ImGuiMouseCursor        MouseCursor;
+
+    // Drag and Drop
+    bool                    DragDropActive;
+    bool                    DragDropWithinSourceOrTarget;
+    ImGuiDragDropFlags      DragDropSourceFlags;
+    int                     DragDropSourceFrameCount;
+    int                     DragDropMouseButton;
+    ImGuiPayload            DragDropPayload;
+    ImRect                  DragDropTargetRect;
+    ImGuiID                 DragDropTargetId;
+    ImGuiDragDropFlags      DragDropAcceptFlags;
+    float                   DragDropAcceptIdCurrRectSurface;    // Target item surface (we resolve overlapping targets by prioritizing the smaller surface)
+    ImGuiID                 DragDropAcceptIdCurr;               // Target item id (set at the time of accepting the payload)
+    ImGuiID                 DragDropAcceptIdPrev;               // Target item id from previous frame (we need to store this to allow for overlapping drag and drop targets)
+    int                     DragDropAcceptFrameCount;           // Last time a target expressed a desire to accept the source
+    ImVector<unsigned char> DragDropPayloadBufHeap;             // We don't expose the ImVector<> directly
+    unsigned char           DragDropPayloadBufLocal[8];         // Local buffer for small payloads
+
+    // Widget state
+    ImGuiInputTextState     InputTextState;
+    ImFont                  InputTextPasswordFont;
+    ImGuiID                 ScalarAsInputTextId;                // Temporary text input when CTRL+clicking on a slider, etc.
+    ImGuiColorEditFlags     ColorEditOptions;                   // Store user options for color edit widgets
+    ImVec4                  ColorPickerRef;
+    bool                    DragCurrentAccumDirty;
+    float                   DragCurrentAccum;                   // Accumulator for dragging modification. Always high-precision, not rounded by end-user precision settings
+    float                   DragSpeedDefaultRatio;              // If speed == 0.0f, uses (max-min) * DragSpeedDefaultRatio
+    ImVec2                  ScrollbarClickDeltaToGrabCenter;    // Distance between mouse and center of grab box, normalized in parent space. Use storage?
+    int                     TooltipOverrideCount;
+    ImVector<char>          PrivateClipboard;                   // If no custom clipboard handler is defined
+    ImVec2                  PlatformImePos, PlatformImeLastPos; // Cursor position request & last passed to the OS Input Method Editor
+
+    // Settings
+    bool                           SettingsLoaded;
+    float                          SettingsDirtyTimer;          // Save .ini Settings to memory when time reaches zero
+    ImGuiTextBuffer                SettingsIniData;             // In memory .ini settings
+    ImVector<ImGuiSettingsHandler> SettingsHandlers;            // List of .ini settings handlers
+    ImVector<ImGuiWindowSettings>  SettingsWindows;             // ImGuiWindow .ini settings entries (parsed from the last loaded .ini file and maintained on saving)
+
+    // Logging
+    bool                    LogEnabled;
+    FILE*                   LogFile;                            // If != NULL log to stdout/ file
+    ImGuiTextBuffer         LogClipboard;                       // Accumulation buffer when log to clipboard. This is pointer so our GImGui static constructor doesn't call heap allocators.
+    int                     LogStartDepth;
+    int                     LogAutoExpandMaxDepth;
+
+    // Misc
+    float                   FramerateSecPerFrame[120];          // Calculate estimate of framerate for user over the last 2 seconds.
+    int                     FramerateSecPerFrameIdx;
+    float                   FramerateSecPerFrameAccum;
+    int                     WantCaptureMouseNextFrame;          // Explicit capture via CaptureKeyboardFromApp()/CaptureMouseFromApp() sets those flags
+    int                     WantCaptureKeyboardNextFrame;
+    int                     WantTextInputNextFrame;
+    char                    TempBuffer[1024*3+1];               // Temporary text buffer
+
+    ImGuiContext(ImFontAtlas* shared_font_atlas) : OverlayDrawList(NULL)
+    {
+        Initialized = false;
+        FrameScopeActive = false;
+        Font = NULL;
+        FontSize = FontBaseSize = 0.0f;
+        FontAtlasOwnedByContext = shared_font_atlas ? false : true;
+        IO.Fonts = shared_font_atlas ? shared_font_atlas : IM_NEW(ImFontAtlas)();
+
+        Time = 0.0f;
+        FrameCount = 0;
+        FrameCountEnded = FrameCountRendered = -1;
+        WindowsActiveCount = 0;
+        CurrentWindow = NULL;
+        HoveredWindow = NULL;
+        HoveredRootWindow = NULL;
+        HoveredId = 0;
+        HoveredIdAllowOverlap = false;
+        HoveredIdPreviousFrame = 0;
+        HoveredIdTimer = 0.0f;
+        ActiveId = 0;
+        ActiveIdPreviousFrame = 0;
+        ActiveIdIsAlive = 0;
+        ActiveIdTimer = 0.0f;
+        ActiveIdIsJustActivated = false;
+        ActiveIdAllowOverlap = false;
+        ActiveIdHasBeenEdited = false;
+        ActiveIdPreviousFrameIsAlive = false;
+        ActiveIdPreviousFrameHasBeenEdited = false;
+        ActiveIdAllowNavDirFlags = 0;
+        ActiveIdClickOffset = ImVec2(-1,-1);
+        ActiveIdWindow = ActiveIdPreviousFrameWindow = NULL;
+        ActiveIdSource = ImGuiInputSource_None;
+        LastActiveId = 0;
+        LastActiveIdTimer = 0.0f;
+        MovingWindow = NULL;
+        NextTreeNodeOpenVal = false;
+        NextTreeNodeOpenCond = 0;
+
+        NavWindow = NULL;
+        NavId = NavActivateId = NavActivateDownId = NavActivatePressedId = NavInputId = 0;
+        NavJustTabbedId = NavJustMovedToId = NavNextActivateId = 0;
+        NavInputSource = ImGuiInputSource_None;
+        NavScoringRectScreen = ImRect();
+        NavScoringCount = 0;
+        NavWindowingTarget = NavWindowingTargetAnim = NavWindowingList = NULL;
+        NavWindowingTimer = NavWindowingHighlightAlpha = 0.0f;
+        NavWindowingToggleLayer = false;
+        NavLayer = 0;
+        NavIdTabCounter = INT_MAX;
+        NavIdIsAlive = false;
+        NavMousePosDirty = false;
+        NavDisableHighlight = true;
+        NavDisableMouseHover = false;
+        NavAnyRequest = false;
+        NavInitRequest = false;
+        NavInitRequestFromMove = false;
+        NavInitResultId = 0;
+        NavMoveFromClampedRefRect = false;
+        NavMoveRequest = false;
+        NavMoveRequestFlags = 0;
+        NavMoveRequestForward = ImGuiNavForward_None;
+        NavMoveDir = NavMoveDirLast = NavMoveClipDir = ImGuiDir_None;
+
+        DimBgRatio = 0.0f;
+        OverlayDrawList._Data = &DrawListSharedData;
+        OverlayDrawList._OwnerName = "##Overlay"; // Give it a name for debugging
+        MouseCursor = ImGuiMouseCursor_Arrow;
+
+        DragDropActive = DragDropWithinSourceOrTarget = false;
+        DragDropSourceFlags = 0;
+        DragDropSourceFrameCount = -1;
+        DragDropMouseButton = -1;
+        DragDropTargetId = 0;
+        DragDropAcceptFlags = 0;
+        DragDropAcceptIdCurrRectSurface = 0.0f;
+        DragDropAcceptIdPrev = DragDropAcceptIdCurr = 0;
+        DragDropAcceptFrameCount = -1;
+        memset(DragDropPayloadBufLocal, 0, sizeof(DragDropPayloadBufLocal));
+
+        ScalarAsInputTextId = 0;
+        ColorEditOptions = ImGuiColorEditFlags__OptionsDefault;
+        DragCurrentAccumDirty = false;
+        DragCurrentAccum = 0.0f;
+        DragSpeedDefaultRatio = 1.0f / 100.0f;
+        ScrollbarClickDeltaToGrabCenter = ImVec2(0.0f, 0.0f);
+        TooltipOverrideCount = 0;
+        PlatformImePos = PlatformImeLastPos = ImVec2(FLT_MAX, FLT_MAX);
+
+        SettingsLoaded = false;
+        SettingsDirtyTimer = 0.0f;
+
+        LogEnabled = false;
+        LogFile = NULL;
+        LogStartDepth = 0;
+        LogAutoExpandMaxDepth = 2;
+
+        memset(FramerateSecPerFrame, 0, sizeof(FramerateSecPerFrame));
+        FramerateSecPerFrameIdx = 0;
+        FramerateSecPerFrameAccum = 0.0f;
+        WantCaptureMouseNextFrame = WantCaptureKeyboardNextFrame = WantTextInputNextFrame = -1;
+        memset(TempBuffer, 0, sizeof(TempBuffer));
+    }
+};
+
+// Transient per-window flags, reset at the beginning of the frame. For child window, inherited from parent on first Begin().
+// This is going to be exposed in imgui.h when stabilized enough.
+enum ImGuiItemFlags_
+{
+    ImGuiItemFlags_AllowKeyboardFocus           = 1 << 0,  // true
+    ImGuiItemFlags_ButtonRepeat                 = 1 << 1,  // false    // Button() will return true multiple times based on io.KeyRepeatDelay and io.KeyRepeatRate settings.
+    ImGuiItemFlags_Disabled                     = 1 << 2,  // false    // [BETA] Disable interactions but doesn't affect visuals yet. See github.com/ocornut/imgui/issues/211
+    ImGuiItemFlags_NoNav                        = 1 << 3,  // false
+    ImGuiItemFlags_NoNavDefaultFocus            = 1 << 4,  // false
+    ImGuiItemFlags_SelectableDontClosePopup     = 1 << 5,  // false    // MenuItem/Selectable() automatically closes current Popup window
+    ImGuiItemFlags_Default_                     = ImGuiItemFlags_AllowKeyboardFocus
+};
+
+// Transient per-window data, reset at the beginning of the frame. This used to be called ImGuiDrawContext, hence the DC variable name in ImGuiWindow.
+// FIXME: That's theory, in practice the delimitation between ImGuiWindow and ImGuiWindowTempData is quite tenuous and could be reconsidered.
+struct IMGUI_API ImGuiWindowTempData
+{
+    ImVec2                  CursorPos;
+    ImVec2                  CursorPosPrevLine;
+    ImVec2                  CursorStartPos;         // Initial position in client area with padding
+    ImVec2                  CursorMaxPos;           // Used to implicitly calculate the size of our contents, always growing during the frame. Turned into window->SizeContents at the beginning of next frame
+    ImVec2                  CurrentLineSize;
+    float                   CurrentLineTextBaseOffset;
+    ImVec2                  PrevLineSize;
+    float                   PrevLineTextBaseOffset;
+    float                   LogLinePosY;
+    int                     TreeDepth;
+    ImU32                   TreeDepthMayJumpToParentOnPop; // Store a copy of !g.NavIdIsAlive for TreeDepth 0..31
+    ImGuiID                 LastItemId;
+    ImGuiItemStatusFlags    LastItemStatusFlags;
+    ImRect                  LastItemRect;           // Interaction rect
+    ImRect                  LastItemDisplayRect;    // End-user display rect (only valid if LastItemStatusFlags & ImGuiItemStatusFlags_HasDisplayRect)
+    bool                    NavHideHighlightOneFrame;
+    bool                    NavHasScroll;           // Set when scrolling can be used (ScrollMax > 0.0f)
+    int                     NavLayerCurrent;        // Current layer, 0..31 (we currently only use 0..1)
+    int                     NavLayerCurrentMask;    // = (1 << NavLayerCurrent) used by ItemAdd prior to clipping.
+    int                     NavLayerActiveMask;     // Which layer have been written to (result from previous frame)
+    int                     NavLayerActiveMaskNext; // Which layer have been written to (buffer for current frame)
+    bool                    MenuBarAppending;       // FIXME: Remove this
+    ImVec2                  MenuBarOffset;          // MenuBarOffset.x is sort of equivalent of a per-layer CursorPos.x, saved/restored as we switch to the menu bar. The only situation when MenuBarOffset.y is > 0 if when (SafeAreaPadding.y > FramePadding.y), often used on TVs.
+    ImVector<ImGuiWindow*>  ChildWindows;
+    ImGuiStorage*           StateStorage;
+    ImGuiLayoutType         LayoutType;
+    ImGuiLayoutType         ParentLayoutType;       // Layout type of parent window at the time of Begin()
+
+    // We store the current settings outside of the vectors to increase memory locality (reduce cache misses). The vectors are rarely modified. Also it allows us to not heap allocate for short-lived windows which are not using those settings.
+    ImGuiItemFlags          ItemFlags;              // == ItemFlagsStack.back() [empty == ImGuiItemFlags_Default]
+    float                   ItemWidth;              // == ItemWidthStack.back(). 0.0: default, >0.0: width in pixels, <0.0: align xx pixels to the right of window
+    float                   TextWrapPos;            // == TextWrapPosStack.back() [empty == -1.0f]
+    ImVector<ImGuiItemFlags>ItemFlagsStack;
+    ImVector<float>         ItemWidthStack;
+    ImVector<float>         TextWrapPosStack;
+    ImVector<ImGuiGroupData>GroupStack;
+    int                     StackSizesBackup[6];    // Store size of various stacks for asserting
+
+    ImVec1                  Indent;                 // Indentation / start position from left of window (increased by TreePush/TreePop, etc.)
+    ImVec1                  GroupOffset;
+    ImVec1                  ColumnsOffset;          // Offset to the current column (if ColumnsCurrent > 0). FIXME: This and the above should be a stack to allow use cases like Tree->Column->Tree. Need revamp columns API.
+    ImGuiColumnsSet*        ColumnsSet;             // Current columns set
+
+    ImGuiWindowTempData()
+    {
+        CursorPos = CursorPosPrevLine = CursorStartPos = CursorMaxPos = ImVec2(0.0f, 0.0f);
+        CurrentLineSize = PrevLineSize = ImVec2(0.0f, 0.0f);
+        CurrentLineTextBaseOffset = PrevLineTextBaseOffset = 0.0f;
+        LogLinePosY = -1.0f;
+        TreeDepth = 0;
+        TreeDepthMayJumpToParentOnPop = 0x00;
+        LastItemId = 0;
+        LastItemStatusFlags = 0;
+        LastItemRect = LastItemDisplayRect = ImRect();
+        NavHideHighlightOneFrame = false;
+        NavHasScroll = false;
+        NavLayerActiveMask = NavLayerActiveMaskNext = 0x00;
+        NavLayerCurrent = 0;
+        NavLayerCurrentMask = 1 << 0;
+        MenuBarAppending = false;
+        MenuBarOffset = ImVec2(0.0f, 0.0f);
+        StateStorage = NULL;
+        LayoutType = ParentLayoutType = ImGuiLayoutType_Vertical;
+        ItemWidth = 0.0f;
+        ItemFlags = ImGuiItemFlags_Default_;
+        TextWrapPos = -1.0f;
+        memset(StackSizesBackup, 0, sizeof(StackSizesBackup));
+
+        Indent = ImVec1(0.0f);
+        GroupOffset = ImVec1(0.0f);
+        ColumnsOffset = ImVec1(0.0f);
+        ColumnsSet = NULL;
+    }
+};
+
+// Storage for one window
+struct IMGUI_API ImGuiWindow
+{
+    char*                   Name;
+    ImGuiID                 ID;                                 // == ImHash(Name)
+    ImGuiWindowFlags        Flags;                              // See enum ImGuiWindowFlags_
+    ImVec2                  Pos;                                // Position (always rounded-up to nearest pixel)
+    ImVec2                  Size;                               // Current size (==SizeFull or collapsed title bar size)
+    ImVec2                  SizeFull;                           // Size when non collapsed
+    ImVec2                  SizeFullAtLastBegin;                // Copy of SizeFull at the end of Begin. This is the reference value we'll use on the next frame to decide if we need scrollbars.
+    ImVec2                  SizeContents;                       // Size of contents (== extents reach of the drawing cursor) from previous frame. Include decoration, window title, border, menu, etc.
+    ImVec2                  SizeContentsExplicit;               // Size of contents explicitly set by the user via SetNextWindowContentSize()
+    ImVec2                  WindowPadding;                      // Window padding at the time of begin.
+    float                   WindowRounding;                     // Window rounding at the time of begin.
+    float                   WindowBorderSize;                   // Window border size at the time of begin.
+    ImGuiID                 MoveId;                             // == window->GetID("#MOVE")
+    ImGuiID                 ChildId;                            // ID of corresponding item in parent window (for navigation to return from child window to parent window)
+    ImVec2                  Scroll;
+    ImVec2                  ScrollTarget;                       // target scroll position. stored as cursor position with scrolling canceled out, so the highest point is always 0.0f. (FLT_MAX for no change)
+    ImVec2                  ScrollTargetCenterRatio;            // 0.0f = scroll so that target position is at top, 0.5f = scroll so that target position is centered
+    ImVec2                  ScrollbarSizes;                     // Size taken by scrollbars on each axis
+    bool                    ScrollbarX, ScrollbarY;
+    bool                    Active;                             // Set to true on Begin(), unless Collapsed
+    bool                    WasActive;
+    bool                    WriteAccessed;                      // Set to true when any widget access the current window
+    bool                    Collapsed;                          // Set when collapsing window to become only title-bar
+    bool                    WantCollapseToggle;
+    bool                    SkipItems;                          // Set when items can safely be all clipped (e.g. window not visible or collapsed)
+    bool                    Appearing;                          // Set during the frame where the window is appearing (or re-appearing)
+    bool                    Hidden;                             // Do not display (== (HiddenFramesForResize > 0) ||
+    bool                    HasCloseButton;                     // Set when the window has a close button (p_open != NULL)
+    int                     BeginCount;                         // Number of Begin() during the current frame (generally 0 or 1, 1+ if appending via multiple Begin/End pairs)
+    int                     BeginOrderWithinParent;             // Order within immediate parent window, if we are a child window. Otherwise 0.
+    int                     BeginOrderWithinContext;            // Order within entire imgui context. This is mostly used for debugging submission order related issues.
+    ImGuiID                 PopupId;                            // ID in the popup stack when this window is used as a popup/menu (because we use generic Name/ID for recycling)
+    int                     AutoFitFramesX, AutoFitFramesY;
+    bool                    AutoFitOnlyGrows;
+    int                     AutoFitChildAxises;
+    ImGuiDir                AutoPosLastDirection;
+    int                     HiddenFramesRegular;                // Hide the window for N frames
+    int                     HiddenFramesForResize;              // Hide the window for N frames while allowing items to be submitted so we can measure their size
+    ImGuiCond               SetWindowPosAllowFlags;             // store acceptable condition flags for SetNextWindowPos() use.
+    ImGuiCond               SetWindowSizeAllowFlags;            // store acceptable condition flags for SetNextWindowSize() use.
+    ImGuiCond               SetWindowCollapsedAllowFlags;       // store acceptable condition flags for SetNextWindowCollapsed() use.
+    ImVec2                  SetWindowPosVal;                    // store window position when using a non-zero Pivot (position set needs to be processed when we know the window size)
+    ImVec2                  SetWindowPosPivot;                  // store window pivot for positioning. ImVec2(0,0) when positioning from top-left corner; ImVec2(0.5f,0.5f) for centering; ImVec2(1,1) for bottom right.
+
+    ImGuiWindowTempData     DC;                                 // Temporary per-window data, reset at the beginning of the frame. This used to be called ImGuiDrawContext, hence the "DC" variable name.
+    ImVector<ImGuiID>       IDStack;                            // ID stack. ID are hashes seeded with the value at the top of the stack
+    ImRect                  ClipRect;                           // Current clipping rectangle. = DrawList->clip_rect_stack.back(). Scissoring / clipping rectangle. x1, y1, x2, y2.
+    ImRect                  OuterRectClipped;                   // = WindowRect just after setup in Begin(). == window->Rect() for root window.
+    ImRect                  InnerMainRect, InnerClipRect;
+    ImRect                  ContentsRegionRect;                 // FIXME: This is currently confusing/misleading. Maximum visible content position ~~ Pos + (SizeContentsExplicit ? SizeContentsExplicit : Size - ScrollbarSizes) - CursorStartPos, per axis
+    int                     LastFrameActive;                    // Last frame number the window was Active.
+    float                   ItemWidthDefault;
+    ImGuiMenuColumns        MenuColumns;                        // Simplified columns storage for menu items
+    ImGuiStorage            StateStorage;
+    ImVector<ImGuiColumnsSet> ColumnsStorage;
+    float                   FontWindowScale;                    // User scale multiplier per-window
+    int                     SettingsIdx;                        // Index into SettingsWindow[] (indices are always valid as we only grow the array from the back)
+
+    ImDrawList*             DrawList;                           // == &DrawListInst (for backward compatibility reason with code using imgui_internal.h we keep this a pointer)
+    ImDrawList              DrawListInst;
+    ImGuiWindow*            ParentWindow;                       // If we are a child _or_ popup window, this is pointing to our parent. Otherwise NULL.
+    ImGuiWindow*            RootWindow;                         // Point to ourself or first ancestor that is not a child window.
+    ImGuiWindow*            RootWindowForTitleBarHighlight;     // Point to ourself or first ancestor which will display TitleBgActive color when this window is active.
+    ImGuiWindow*            RootWindowForNav;                   // Point to ourself or first ancestor which doesn't have the NavFlattened flag.
+
+    ImGuiWindow*            NavLastChildNavWindow;              // When going to the menu bar, we remember the child window we came from. (This could probably be made implicit if we kept g.Windows sorted by last focused including child window.)
+    ImGuiID                 NavLastIds[2];                      // Last known NavId for this window, per layer (0/1)
+    ImRect                  NavRectRel[2];                      // Reference rectangle, in window relative space
+
+    // Navigation / Focus
+    // FIXME-NAV: Merge all this with the new Nav system, at least the request variables should be moved to ImGuiContext
+    int                     FocusIdxAllCounter;                 // Start at -1 and increase as assigned via FocusItemRegister()
+    int                     FocusIdxTabCounter;                 // (same, but only count widgets which you can Tab through)
+    int                     FocusIdxAllRequestCurrent;          // Item being requested for focus
+    int                     FocusIdxTabRequestCurrent;          // Tab-able item being requested for focus
+    int                     FocusIdxAllRequestNext;             // Item being requested for focus, for next update (relies on layout to be stable between the frame pressing TAB and the next frame)
+    int                     FocusIdxTabRequestNext;             // "
+
+public:
+    ImGuiWindow(ImGuiContext* context, const char* name);
+    ~ImGuiWindow();
+
+    ImGuiID     GetID(const char* str, const char* str_end = NULL);
+    ImGuiID     GetID(const void* ptr);
+    ImGuiID     GetIDNoKeepAlive(const char* str, const char* str_end = NULL);
+    ImGuiID     GetIDNoKeepAlive(const void* ptr);
+    ImGuiID     GetIDFromRectangle(const ImRect& r_abs);
+
+    // We don't use g.FontSize because the window may be != g.CurrentWidow.
+    ImRect      Rect() const                            { return ImRect(Pos.x, Pos.y, Pos.x+Size.x, Pos.y+Size.y); }
+    float       CalcFontSize() const                    { return GImGui->FontBaseSize * FontWindowScale; }
+    float       TitleBarHeight() const                  { return (Flags & ImGuiWindowFlags_NoTitleBar) ? 0.0f : CalcFontSize() + GImGui->Style.FramePadding.y * 2.0f; }
+    ImRect      TitleBarRect() const                    { return ImRect(Pos, ImVec2(Pos.x + SizeFull.x, Pos.y + TitleBarHeight())); }
+    float       MenuBarHeight() const                   { return (Flags & ImGuiWindowFlags_MenuBar) ? DC.MenuBarOffset.y + CalcFontSize() + GImGui->Style.FramePadding.y * 2.0f : 0.0f; }
+    ImRect      MenuBarRect() const                     { float y1 = Pos.y + TitleBarHeight(); return ImRect(Pos.x, y1, Pos.x + SizeFull.x, y1 + MenuBarHeight()); }
+};
+
+// Backup and restore just enough data to be able to use IsItemHovered() on item A after another B in the same window has overwritten the data.
+struct ImGuiItemHoveredDataBackup
+{
+    ImGuiID                 LastItemId;
+    ImGuiItemStatusFlags    LastItemStatusFlags;
+    ImRect                  LastItemRect;
+    ImRect                  LastItemDisplayRect;
+
+    ImGuiItemHoveredDataBackup() { Backup(); }
+    void Backup()           { ImGuiWindow* window = GImGui->CurrentWindow; LastItemId = window->DC.LastItemId; LastItemStatusFlags = window->DC.LastItemStatusFlags; LastItemRect = window->DC.LastItemRect; LastItemDisplayRect = window->DC.LastItemDisplayRect; }
+    void Restore() const    { ImGuiWindow* window = GImGui->CurrentWindow; window->DC.LastItemId = LastItemId; window->DC.LastItemStatusFlags = LastItemStatusFlags; window->DC.LastItemRect = LastItemRect; window->DC.LastItemDisplayRect = LastItemDisplayRect; }
+};
+
+//-----------------------------------------------------------------------------
+// Internal API
+// No guarantee of forward compatibility here.
+//-----------------------------------------------------------------------------
+
+namespace ImGui
+{
+    // We should always have a CurrentWindow in the stack (there is an implicit "Debug" window)
+    // If this ever crash because g.CurrentWindow is NULL it means that either
+    // - ImGui::NewFrame() has never been called, which is illegal.
+    // - You are calling ImGui functions after ImGui::EndFrame()/ImGui::Render() and before the next ImGui::NewFrame(), which is also illegal.
+    inline    ImGuiWindow*  GetCurrentWindowRead()      { ImGuiContext& g = *GImGui; return g.CurrentWindow; }
+    inline    ImGuiWindow*  GetCurrentWindow()          { ImGuiContext& g = *GImGui; g.CurrentWindow->WriteAccessed = true; return g.CurrentWindow; }
+    IMGUI_API ImGuiWindow*  FindWindowByName(const char* name);
+    IMGUI_API void          FocusWindow(ImGuiWindow* window);
+    IMGUI_API void          FocusFrontMostActiveWindowIgnoringOne(ImGuiWindow* ignore_window);
+    IMGUI_API void          BringWindowToFront(ImGuiWindow* window);
+    IMGUI_API void          BringWindowToBack(ImGuiWindow* window);
+    IMGUI_API void          UpdateWindowParentAndRootLinks(ImGuiWindow* window, ImGuiWindowFlags flags, ImGuiWindow* parent_window);
+    IMGUI_API ImVec2        CalcWindowExpectedSize(ImGuiWindow* window);
+    IMGUI_API bool          IsWindowChildOf(ImGuiWindow* window, ImGuiWindow* potential_parent);
+    IMGUI_API bool          IsWindowNavFocusable(ImGuiWindow* window);
+    IMGUI_API void          SetWindowScrollX(ImGuiWindow* window, float new_scroll_x);
+    IMGUI_API void          SetWindowScrollY(ImGuiWindow* window, float new_scroll_y);
+    IMGUI_API ImRect        GetWindowAllowedExtentRect(ImGuiWindow* window);
+    IMGUI_API void          SetCurrentFont(ImFont* font);
+    inline ImFont*          GetDefaultFont() { ImGuiContext& g = *GImGui; return g.IO.FontDefault ? g.IO.FontDefault : g.IO.Fonts->Fonts[0]; }
+
+    // Init
+    IMGUI_API void          Initialize(ImGuiContext* context);
+    IMGUI_API void          Shutdown(ImGuiContext* context);    // Since 1.60 this is a _private_ function. You can call DestroyContext() to destroy the context created by CreateContext().
+
+    // NewFrame
+    IMGUI_API void          UpdateHoveredWindowAndCaptureFlags();
+    IMGUI_API void          StartMouseMovingWindow(ImGuiWindow* window);
+    IMGUI_API void          UpdateMouseMovingWindow();
+
+    // Settings
+    IMGUI_API void                  MarkIniSettingsDirty();
+    IMGUI_API void                  MarkIniSettingsDirty(ImGuiWindow* window);
+    IMGUI_API ImGuiWindowSettings*  CreateNewWindowSettings(const char* name);
+    IMGUI_API ImGuiWindowSettings*  FindWindowSettings(ImGuiID id);
+    IMGUI_API ImGuiSettingsHandler* FindSettingsHandler(const char* type_name);
+
+    // Basic Accessors
+    inline ImGuiID          GetItemID()     { ImGuiContext& g = *GImGui; return g.CurrentWindow->DC.LastItemId; }
+    inline ImGuiID          GetActiveID()   { ImGuiContext& g = *GImGui; return g.ActiveId; }
+    inline ImGuiID          GetFocusID()    { ImGuiContext& g = *GImGui; return g.NavId; }
+    IMGUI_API void          SetActiveID(ImGuiID id, ImGuiWindow* window);
+    IMGUI_API void          SetFocusID(ImGuiID id, ImGuiWindow* window);
+    IMGUI_API void          ClearActiveID();
+    IMGUI_API ImGuiID       GetHoveredID();
+    IMGUI_API void          SetHoveredID(ImGuiID id);
+    IMGUI_API void          KeepAliveID(ImGuiID id);
+    IMGUI_API void          MarkItemEdited(ImGuiID id);
+
+    // Basic Helpers for widget code
+    IMGUI_API void          ItemSize(const ImVec2& size, float text_offset_y = 0.0f);
+    IMGUI_API void          ItemSize(const ImRect& bb, float text_offset_y = 0.0f);
+    IMGUI_API bool          ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb = NULL);
+    IMGUI_API bool          ItemHoverable(const ImRect& bb, ImGuiID id);
+    IMGUI_API bool          IsClippedEx(const ImRect& bb, ImGuiID id, bool clip_even_when_logged);
+    IMGUI_API bool          FocusableItemRegister(ImGuiWindow* window, ImGuiID id, bool tab_stop = true);      // Return true if focus is requested
+    IMGUI_API void          FocusableItemUnregister(ImGuiWindow* window);
+    IMGUI_API ImVec2        CalcItemSize(ImVec2 size, float default_x, float default_y);
+    IMGUI_API float         CalcWrapWidthForPos(const ImVec2& pos, float wrap_pos_x);
+    IMGUI_API void          PushMultiItemsWidths(int components, float width_full = 0.0f);
+    IMGUI_API void          PushItemFlag(ImGuiItemFlags option, bool enabled);
+    IMGUI_API void          PopItemFlag();
+
+    // Popups, Modals, Tooltips
+    IMGUI_API void          OpenPopupEx(ImGuiID id);
+    IMGUI_API void          ClosePopup(ImGuiID id);
+    IMGUI_API void          ClosePopupToLevel(int remaining);
+    IMGUI_API void          ClosePopupsOverWindow(ImGuiWindow* ref_window);
+    IMGUI_API bool          IsPopupOpen(ImGuiID id);
+    IMGUI_API bool          BeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_flags);
+    IMGUI_API void          BeginTooltipEx(ImGuiWindowFlags extra_flags, bool override_previous_tooltip = true);
+    IMGUI_API ImGuiWindow*  GetFrontMostPopupModal();
+    IMGUI_API ImVec2        FindBestWindowPosForPopup(ImGuiWindow* window);
+    IMGUI_API ImVec2        FindBestWindowPosForPopupEx(const ImVec2& ref_pos, const ImVec2& size, ImGuiDir* last_dir, const ImRect& r_outer, const ImRect& r_avoid, ImGuiPopupPositionPolicy policy = ImGuiPopupPositionPolicy_Default);
+
+    // Navigation
+    IMGUI_API void          NavInitWindow(ImGuiWindow* window, bool force_reinit);
+    IMGUI_API bool          NavMoveRequestButNoResultYet();
+    IMGUI_API void          NavMoveRequestCancel();
+    IMGUI_API void          NavMoveRequestForward(ImGuiDir move_dir, ImGuiDir clip_dir, const ImRect& bb_rel, ImGuiNavMoveFlags move_flags);
+    IMGUI_API void          NavMoveRequestTryWrapping(ImGuiWindow* window, ImGuiNavMoveFlags move_flags);
+    IMGUI_API float         GetNavInputAmount(ImGuiNavInput n, ImGuiInputReadMode mode);
+    IMGUI_API ImVec2        GetNavInputAmount2d(ImGuiNavDirSourceFlags dir_sources, ImGuiInputReadMode mode, float slow_factor = 0.0f, float fast_factor = 0.0f);
+    IMGUI_API int           CalcTypematicPressedRepeatAmount(float t, float t_prev, float repeat_delay, float repeat_rate);
+    IMGUI_API void          ActivateItem(ImGuiID id);   // Remotely activate a button, checkbox, tree node etc. given its unique ID. activation is queued and processed on the next frame when the item is encountered again.
+    IMGUI_API void          SetNavID(ImGuiID id, int nav_layer);
+    IMGUI_API void          SetNavIDWithRectRel(ImGuiID id, int nav_layer, const ImRect& rect_rel);
+
+    // Inputs
+    inline bool             IsKeyPressedMap(ImGuiKey key, bool repeat = true)           { const int key_index = GImGui->IO.KeyMap[key]; return (key_index >= 0) ? IsKeyPressed(key_index, repeat) : false; }
+    inline bool             IsNavInputDown(ImGuiNavInput n)                             { return GImGui->IO.NavInputs[n] > 0.0f; }
+    inline bool             IsNavInputPressed(ImGuiNavInput n, ImGuiInputReadMode mode) { return GetNavInputAmount(n, mode) > 0.0f; }
+    inline bool             IsNavInputPressedAnyOfTwo(ImGuiNavInput n1, ImGuiNavInput n2, ImGuiInputReadMode mode) { return (GetNavInputAmount(n1, mode) + GetNavInputAmount(n2, mode)) > 0.0f; }
+
+    // Drag and Drop
+    IMGUI_API bool          BeginDragDropTargetCustom(const ImRect& bb, ImGuiID id);
+    IMGUI_API void          ClearDragDrop();
+    IMGUI_API bool          IsDragDropPayloadBeingAccepted();
+
+    // New Columns API (FIXME-WIP)
+    IMGUI_API void          BeginColumns(const char* str_id, int count, ImGuiColumnsFlags flags = 0); // setup number of columns. use an identifier to distinguish multiple column sets. close with EndColumns().
+    IMGUI_API void          EndColumns();                                                             // close columns
+    IMGUI_API void          PushColumnClipRect(int column_index = -1);
+
+    // Render helpers
+    // AVOID USING OUTSIDE OF IMGUI.CPP! NOT FOR PUBLIC CONSUMPTION. THOSE FUNCTIONS ARE A MESS. THEIR SIGNATURE AND BEHAVIOR WILL CHANGE, THEY NEED TO BE REFACTORED INTO SOMETHING DECENT.
+    // NB: All position are in absolute pixels coordinates (we are never using window coordinates internally)
+    IMGUI_API void          RenderText(ImVec2 pos, const char* text, const char* text_end = NULL, bool hide_text_after_hash = true);
+    IMGUI_API void          RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end, float wrap_width);
+    IMGUI_API void          RenderTextClipped(const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_end, const ImVec2* text_size_if_known, const ImVec2& align = ImVec2(0,0), const ImRect* clip_rect = NULL);
+    IMGUI_API void          RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool border = true, float rounding = 0.0f);
+    IMGUI_API void          RenderFrameBorder(ImVec2 p_min, ImVec2 p_max, float rounding = 0.0f);
+    IMGUI_API void          RenderColorRectWithAlphaCheckerboard(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, float grid_step, ImVec2 grid_off, float rounding = 0.0f, int rounding_corners_flags = ~0);
+    IMGUI_API void          RenderArrow(ImVec2 pos, ImGuiDir dir, float scale = 1.0f);
+    IMGUI_API void          RenderBullet(ImVec2 pos);
+    IMGUI_API void          RenderCheckMark(ImVec2 pos, ImU32 col, float sz);
+    IMGUI_API void          RenderNavHighlight(const ImRect& bb, ImGuiID id, ImGuiNavHighlightFlags flags = ImGuiNavHighlightFlags_TypeDefault); // Navigation highlight
+    IMGUI_API const char*   FindRenderedTextEnd(const char* text, const char* text_end = NULL); // Find the optional ## from which we stop displaying text.
+    IMGUI_API void          LogRenderedText(const ImVec2* ref_pos, const char* text, const char* text_end = NULL);
+
+    // Render helpers (those functions don't access any ImGui state!)
+    IMGUI_API void          RenderMouseCursor(ImDrawList* draw_list, ImVec2 pos, float scale, ImGuiMouseCursor mouse_cursor = ImGuiMouseCursor_Arrow);
+    IMGUI_API void          RenderArrowPointingAt(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, ImGuiDir direction, ImU32 col);
+    IMGUI_API void          RenderRectFilledRangeH(ImDrawList* draw_list, const ImRect& rect, ImU32 col, float x_start_norm, float x_end_norm, float rounding);
+
+    // Widgets
+    IMGUI_API bool          ButtonEx(const char* label, const ImVec2& size_arg = ImVec2(0,0), ImGuiButtonFlags flags = 0);
+    IMGUI_API bool          CloseButton(ImGuiID id, const ImVec2& pos, float radius);
+    IMGUI_API bool          CollapseButton(ImGuiID id, const ImVec2& pos);
+    IMGUI_API bool          ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size_arg, ImGuiButtonFlags flags);
+    IMGUI_API void          Scrollbar(ImGuiLayoutType direction);
+    IMGUI_API void          VerticalSeparator();        // Vertical separator, for menu bars (use current line height). Not exposed because it is misleading and it doesn't have an effect on regular layout.
+
+    // Widgets low-level behaviors
+    IMGUI_API bool          ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags = 0);
+    IMGUI_API bool          DragBehavior(ImGuiID id, ImGuiDataType data_type, void* v, float v_speed, const void* v_min, const void* v_max, const char* format, float power);
+    IMGUI_API bool          SliderBehavior(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, void* v, const void* v_min, const void* v_max, const char* format, float power, ImGuiSliderFlags flags, ImRect* out_grab_bb);
+    IMGUI_API bool          SplitterBehavior(const ImRect& bb, ImGuiID id, ImGuiAxis axis, float* size1, float* size2, float min_size1, float min_size2, float hover_extend = 0.0f, float hover_visibility_delay = 0.0f);
+    IMGUI_API bool          TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end = NULL);
+    IMGUI_API bool          TreeNodeBehaviorIsOpen(ImGuiID id, ImGuiTreeNodeFlags flags = 0);                     // Consume previous SetNextTreeNodeOpened() data, if any. May return true when logging
+    IMGUI_API void          TreePushRawID(ImGuiID id);
+
+    // Template functions are instantiated in imgui_widgets.cpp for a finite number of types. 
+    // To use them externally (for custom widget) you may need an "extern template" statement in your code in order to link to existing instances and silence Clang warnings (see #2036).
+    // e.g. " extern template IMGUI_API float RoundScalarWithFormatT<float, float>(const char* format, ImGuiDataType data_type, float v); "
+    template<typename T, typename SIGNED_T, typename FLOAT_T>   IMGUI_API bool  DragBehaviorT(ImGuiDataType data_type, T* v, float v_speed, const T v_min, const T v_max, const char* format, float power);
+    template<typename T, typename SIGNED_T, typename FLOAT_T>   IMGUI_API bool  SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, T* v, const T v_min, const T v_max, const char* format, float power, ImGuiSliderFlags flags, ImRect* out_grab_bb);
+    template<typename T, typename FLOAT_T>                      IMGUI_API float SliderCalcRatioFromValueT(ImGuiDataType data_type, T v, T v_min, T v_max, float power, float linear_zero_pos);
+    template<typename T, typename SIGNED_T>                     IMGUI_API T     RoundScalarWithFormatT(const char* format, ImGuiDataType data_type, T v);
+
+    // InputText
+    IMGUI_API bool          InputTextEx(const char* label, char* buf, int buf_size, const ImVec2& size_arg, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback = NULL, void* user_data = NULL);
+    IMGUI_API bool          InputScalarAsWidgetReplacement(const ImRect& bb, ImGuiID id, const char* label, ImGuiDataType data_type, void* data_ptr, const char* format);
+
+    // Color
+    IMGUI_API void          ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags flags);
+    IMGUI_API void          ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags);
+    IMGUI_API void          ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags flags);
+
+    // Plot
+    IMGUI_API void          PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size);
+
+    // Shade functions (write over already created vertices)
+    IMGUI_API void          ShadeVertsLinearColorGradientKeepAlpha(ImDrawList* draw_list, int vert_start_idx, int vert_end_idx, ImVec2 gradient_p0, ImVec2 gradient_p1, ImU32 col0, ImU32 col1);
+    IMGUI_API void          ShadeVertsLinearUV(ImDrawList* draw_list, int vert_start_idx, int vert_end_idx, const ImVec2& a, const ImVec2& b, const ImVec2& uv_a, const ImVec2& uv_b, bool clamp);
+
+} // namespace ImGui
+
+// ImFontAtlas internals
+IMGUI_API bool              ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas);
+IMGUI_API void              ImFontAtlasBuildRegisterDefaultCustomRects(ImFontAtlas* atlas);
+IMGUI_API void              ImFontAtlasBuildSetupFont(ImFontAtlas* atlas, ImFont* font, ImFontConfig* font_config, float ascent, float descent);
+IMGUI_API void              ImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas, void* spc);
+IMGUI_API void              ImFontAtlasBuildFinish(ImFontAtlas* atlas);
+IMGUI_API void              ImFontAtlasBuildMultiplyCalcLookupTable(unsigned char out_table[256], float in_multiply_factor);
+IMGUI_API void              ImFontAtlasBuildMultiplyRectAlpha8(const unsigned char table[256], unsigned char* pixels, int x, int y, int w, int h, int stride);
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+#ifdef _MSC_VER
+#pragma warning (pop)
+#endif
diff --git a/external/Vulkan/external/imgui/imgui_widgets.cpp b/external/Vulkan/external/imgui/imgui_widgets.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..110c4f86466d846aad1cf907e00573dd262f6281
--- /dev/null
+++ b/external/Vulkan/external/imgui/imgui_widgets.cpp
@@ -0,0 +1,5725 @@
+// dear imgui, v1.65
+// (widgets code)
+
+/*
+
+Index of this file:
+
+// [SECTION] Forward Declarations
+// [SECTION] Widgets: Text, etc.
+// [SECTION] Widgets: Main (Button, Image, Checkbox, RadioButton, ProgressBar, Bullet, etc.)
+// [SECTION] Widgets: Low-level Layout helpers (Spacing, Dummy, NewLine, Separator, etc.)
+// [SECTION] Widgets: ComboBox
+// [SECTION] Data Type and Data Formatting Helpers
+// [SECTION] Widgets: DragScalar, DragFloat, DragInt, etc.
+// [SECTION] Widgets: SliderScalar, SliderFloat, SliderInt, etc.
+// [SECTION] Widgets: InputScalar, InputFloat, InputInt, etc.
+// [SECTION] Widgets: InputText, InputTextMultiline
+// [SECTION] Widgets: ColorEdit, ColorPicker, ColorButton, etc.
+// [SECTION] Widgets: TreeNode, CollapsingHeader, etc.
+// [SECTION] Widgets: Selectable
+// [SECTION] Widgets: ListBox
+// [SECTION] Widgets: PlotLines, PlotHistogram
+// [SECTION] Widgets: Value helpers
+// [SECTION] Widgets: MenuItem, BeginMenu, EndMenu, etc.
+
+*/
+
+#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
+#define _CRT_SECURE_NO_WARNINGS
+#endif
+
+#include "imgui.h"
+#ifndef IMGUI_DEFINE_MATH_OPERATORS
+#define IMGUI_DEFINE_MATH_OPERATORS
+#endif
+#include "imgui_internal.h"
+
+#include <ctype.h>      // toupper, isprint
+#if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier
+#include <stddef.h>     // intptr_t
+#else
+#include <stdint.h>     // intptr_t
+#endif
+
+// Visual Studio warnings
+#ifdef _MSC_VER
+#pragma warning (disable: 4127) // condition expression is constant
+#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen
+#endif
+
+// Clang/GCC warnings with -Weverything
+#ifdef __clang__
+#pragma clang diagnostic ignored "-Wformat-nonliteral"      // warning : format string is not a string literal              // passing non-literal to vsnformat(). yes, user passing incorrect format strings can crash the code.
+#pragma clang diagnostic ignored "-Wsign-conversion"        // warning : implicit conversion changes signedness             //
+#elif defined(__GNUC__)
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"        // warning: format not a string literal, format string not checked
+#if __GNUC__ >= 8
+#pragma GCC diagnostic ignored "-Wclass-memaccess"          // warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead
+#endif
+#endif
+
+//-------------------------------------------------------------------------
+// Data
+//-------------------------------------------------------------------------
+
+// Those MIN/MAX values are not define because we need to point to them
+static const ImS32  IM_S32_MIN = INT_MIN;    // (-2147483647 - 1), (0x80000000);
+static const ImS32  IM_S32_MAX = INT_MAX;    // (2147483647), (0x7FFFFFFF)
+static const ImU32  IM_U32_MIN = 0;
+static const ImU32  IM_U32_MAX = UINT_MAX;   // (0xFFFFFFFF)
+#ifdef LLONG_MIN
+static const ImS64  IM_S64_MIN = LLONG_MIN;  // (-9223372036854775807ll - 1ll);
+static const ImS64  IM_S64_MAX = LLONG_MAX;  // (9223372036854775807ll);
+#else
+static const ImS64  IM_S64_MIN = -9223372036854775807LL - 1;
+static const ImS64  IM_S64_MAX = 9223372036854775807LL;
+#endif
+static const ImU64  IM_U64_MIN = 0;
+#ifdef ULLONG_MAX
+static const ImU64  IM_U64_MAX = ULLONG_MAX; // (0xFFFFFFFFFFFFFFFFull);
+#else
+static const ImU64  IM_U64_MAX = (2ULL * 9223372036854775807LL + 1);
+#endif
+
+//-------------------------------------------------------------------------
+// [SECTION] Forward Declarations
+//-------------------------------------------------------------------------
+
+// Data Type helpers
+static inline int       DataTypeFormatString(char* buf, int buf_size, ImGuiDataType data_type, const void* data_ptr, const char* format);
+static void             DataTypeApplyOp(ImGuiDataType data_type, int op, void* output, void* arg_1, const void* arg_2);
+static bool             DataTypeApplyOpFromText(const char* buf, const char* initial_value_buf, ImGuiDataType data_type, void* data_ptr, const char* format);
+
+// For InputTextEx()
+static bool             InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data);
+static int              InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end);
+static ImVec2           InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false);
+
+//-------------------------------------------------------------------------
+// [SECTION] Widgets: Text, etc.
+//-------------------------------------------------------------------------
+// - TextUnformatted()
+// - Text()
+// - TextV()
+// - TextColored()
+// - TextColoredV()
+// - TextDisabled()
+// - TextDisabledV()
+// - TextWrapped()
+// - TextWrappedV()
+// - LabelText()
+// - LabelTextV()
+// - BulletText()
+// - BulletTextV()
+//-------------------------------------------------------------------------
+
+void ImGui::TextUnformatted(const char* text, const char* text_end)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    if (window->SkipItems)
+        return;
+
+    ImGuiContext& g = *GImGui;
+    IM_ASSERT(text != NULL);
+    const char* text_begin = text;
+    if (text_end == NULL)
+        text_end = text + strlen(text); // FIXME-OPT
+
+    const ImVec2 text_pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrentLineTextBaseOffset);
+    const float wrap_pos_x = window->DC.TextWrapPos;
+    const bool wrap_enabled = wrap_pos_x >= 0.0f;
+    if (text_end - text > 2000 && !wrap_enabled)
+    {
+        // Long text!
+        // Perform manual coarse clipping to optimize for long multi-line text
+        // From this point we will only compute the width of lines that are visible. Optimization only available when word-wrapping is disabled.
+        // We also don't vertically center the text within the line full height, which is unlikely to matter because we are likely the biggest and only item on the line.
+        const char* line = text;
+        const float line_height = GetTextLineHeight();
+        const ImRect clip_rect = window->ClipRect;
+        ImVec2 text_size(0,0);
+
+        if (text_pos.y <= clip_rect.Max.y)
+        {
+            ImVec2 pos = text_pos;
+
+            // Lines to skip (can't skip when logging text)
+            if (!g.LogEnabled)
+            {
+                int lines_skippable = (int)((clip_rect.Min.y - text_pos.y) / line_height);
+                if (lines_skippable > 0)
+                {
+                    int lines_skipped = 0;
+                    while (line < text_end && lines_skipped < lines_skippable)
+                    {
+                        const char* line_end = strchr(line, '\n');
+                        if (!line_end)
+                            line_end = text_end;
+                        line = line_end + 1;
+                        lines_skipped++;
+                    }
+                    pos.y += lines_skipped * line_height;
+                }
+            }
+
+            // Lines to render
+            if (line < text_end)
+            {
+                ImRect line_rect(pos, pos + ImVec2(FLT_MAX, line_height));
+                while (line < text_end)
+                {
+                    const char* line_end = strchr(line, '\n');
+                    if (IsClippedEx(line_rect, 0, false))
+                        break;
+
+                    const ImVec2 line_size = CalcTextSize(line, line_end, false);
+                    text_size.x = ImMax(text_size.x, line_size.x);
+                    RenderText(pos, line, line_end, false);
+                    if (!line_end)
+                        line_end = text_end;
+                    line = line_end + 1;
+                    line_rect.Min.y += line_height;
+                    line_rect.Max.y += line_height;
+                    pos.y += line_height;
+                }
+
+                // Count remaining lines
+                int lines_skipped = 0;
+                while (line < text_end)
+                {
+                    const char* line_end = strchr(line, '\n');
+                    if (!line_end)
+                        line_end = text_end;
+                    line = line_end + 1;
+                    lines_skipped++;
+                }
+                pos.y += lines_skipped * line_height;
+            }
+
+            text_size.y += (pos - text_pos).y;
+        }
+
+        ImRect bb(text_pos, text_pos + text_size);
+        ItemSize(bb);
+        ItemAdd(bb, 0);
+    }
+    else
+    {
+        const float wrap_width = wrap_enabled ? CalcWrapWidthForPos(window->DC.CursorPos, wrap_pos_x) : 0.0f;
+        const ImVec2 text_size = CalcTextSize(text_begin, text_end, false, wrap_width);
+
+        // Account of baseline offset
+        ImRect bb(text_pos, text_pos + text_size);
+        ItemSize(text_size);
+        if (!ItemAdd(bb, 0))
+            return;
+
+        // Render (we don't hide text after ## in this end-user function)
+        RenderTextWrapped(bb.Min, text_begin, text_end, wrap_width);
+    }
+}
+
+void ImGui::Text(const char* fmt, ...)
+{
+    va_list args;
+    va_start(args, fmt);
+    TextV(fmt, args);
+    va_end(args);
+}
+
+void ImGui::TextV(const char* fmt, va_list args)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    if (window->SkipItems)
+        return;
+
+    ImGuiContext& g = *GImGui;
+    const char* text_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
+    TextUnformatted(g.TempBuffer, text_end);
+}
+
+void ImGui::TextColored(const ImVec4& col, const char* fmt, ...)
+{
+    va_list args;
+    va_start(args, fmt);
+    TextColoredV(col, fmt, args);
+    va_end(args);
+}
+
+void ImGui::TextColoredV(const ImVec4& col, const char* fmt, va_list args)
+{
+    PushStyleColor(ImGuiCol_Text, col);
+    TextV(fmt, args);
+    PopStyleColor();
+}
+
+void ImGui::TextDisabled(const char* fmt, ...)
+{
+    va_list args;
+    va_start(args, fmt);
+    TextDisabledV(fmt, args);
+    va_end(args);
+}
+
+void ImGui::TextDisabledV(const char* fmt, va_list args)
+{
+    PushStyleColor(ImGuiCol_Text, GImGui->Style.Colors[ImGuiCol_TextDisabled]);
+    TextV(fmt, args);
+    PopStyleColor();
+}
+
+void ImGui::TextWrapped(const char* fmt, ...)
+{
+    va_list args;
+    va_start(args, fmt);
+    TextWrappedV(fmt, args);
+    va_end(args);
+}
+
+void ImGui::TextWrappedV(const char* fmt, va_list args)
+{
+    bool need_wrap = (GImGui->CurrentWindow->DC.TextWrapPos < 0.0f);    // Keep existing wrap position is one ia already set
+    if (need_wrap) PushTextWrapPos(0.0f);
+    TextV(fmt, args);
+    if (need_wrap) PopTextWrapPos();
+}
+
+void ImGui::LabelText(const char* label, const char* fmt, ...)
+{
+    va_list args;
+    va_start(args, fmt);
+    LabelTextV(label, fmt, args);
+    va_end(args);
+}
+
+// Add a label+text combo aligned to other label+value widgets
+void ImGui::LabelTextV(const char* label, const char* fmt, va_list args)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    if (window->SkipItems)
+        return;
+
+    ImGuiContext& g = *GImGui;
+    const ImGuiStyle& style = g.Style;
+    const float w = CalcItemWidth();
+
+    const ImVec2 label_size = CalcTextSize(label, NULL, true);
+    const ImRect value_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2));
+    const ImRect total_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w + (label_size.x > 0.0f ? style.ItemInnerSpacing.x : 0.0f), style.FramePadding.y*2) + label_size);
+    ItemSize(total_bb, style.FramePadding.y);
+    if (!ItemAdd(total_bb, 0))
+        return;
+
+    // Render
+    const char* value_text_begin = &g.TempBuffer[0];
+    const char* value_text_end = value_text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
+    RenderTextClipped(value_bb.Min, value_bb.Max, value_text_begin, value_text_end, NULL, ImVec2(0.0f,0.5f));
+    if (label_size.x > 0.0f)
+        RenderText(ImVec2(value_bb.Max.x + style.ItemInnerSpacing.x, value_bb.Min.y + style.FramePadding.y), label);
+}
+
+void ImGui::BulletText(const char* fmt, ...)
+{
+    va_list args;
+    va_start(args, fmt);
+    BulletTextV(fmt, args);
+    va_end(args);
+}
+
+// Text with a little bullet aligned to the typical tree node.
+void ImGui::BulletTextV(const char* fmt, va_list args)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    if (window->SkipItems)
+        return;
+
+    ImGuiContext& g = *GImGui;
+    const ImGuiStyle& style = g.Style;
+
+    const char* text_begin = g.TempBuffer;
+    const char* text_end = text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
+    const ImVec2 label_size = CalcTextSize(text_begin, text_end, false);
+    const float text_base_offset_y = ImMax(0.0f, window->DC.CurrentLineTextBaseOffset); // Latch before ItemSize changes it
+    const float line_height = ImMax(ImMin(window->DC.CurrentLineSize.y, g.FontSize + g.Style.FramePadding.y*2), g.FontSize);
+    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize + (label_size.x > 0.0f ? (label_size.x + style.FramePadding.x*2) : 0.0f), ImMax(line_height, label_size.y)));  // Empty text doesn't add padding
+    ItemSize(bb);
+    if (!ItemAdd(bb, 0))
+        return;
+
+    // Render
+    RenderBullet(bb.Min + ImVec2(style.FramePadding.x + g.FontSize*0.5f, line_height*0.5f));
+    RenderText(bb.Min+ImVec2(g.FontSize + style.FramePadding.x*2, text_base_offset_y), text_begin, text_end, false);
+}
+
+//-------------------------------------------------------------------------
+// [SECTION] Widgets: Main
+//-------------------------------------------------------------------------
+// - ButtonBehavior() [Internal]
+// - Button()
+// - SmallButton()
+// - InvisibleButton()
+// - ArrowButton()
+// - CloseButton() [Internal]
+// - CollapseButton() [Internal]
+// - Scrollbar() [Internal]
+// - Image()
+// - ImageButton()
+// - Checkbox()
+// - CheckboxFlags()
+// - RadioButton()
+// - ProgressBar()
+// - Bullet()
+//-------------------------------------------------------------------------
+
+bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags)
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiWindow* window = GetCurrentWindow();
+
+    if (flags & ImGuiButtonFlags_Disabled)
+    {
+        if (out_hovered) *out_hovered = false;
+        if (out_held) *out_held = false;
+        if (g.ActiveId == id) ClearActiveID();
+        return false;
+    }
+
+    // Default behavior requires click+release on same spot
+    if ((flags & (ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnRelease | ImGuiButtonFlags_PressedOnDoubleClick)) == 0)
+        flags |= ImGuiButtonFlags_PressedOnClickRelease;
+
+    ImGuiWindow* backup_hovered_window = g.HoveredWindow;
+    if ((flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredRootWindow == window)
+        g.HoveredWindow = window;
+
+    bool pressed = false;
+    bool hovered = ItemHoverable(bb, id);
+
+    // Drag source doesn't report as hovered
+    if (hovered && g.DragDropActive && g.DragDropPayload.SourceId == id && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoDisableHover))
+        hovered = false;
+
+    // Special mode for Drag and Drop where holding button pressed for a long time while dragging another item triggers the button
+    if (g.DragDropActive && (flags & ImGuiButtonFlags_PressedOnDragDropHold) && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoHoldToOpenOthers))
+        if (IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem))
+        {
+            hovered = true;
+            SetHoveredID(id);
+            if (CalcTypematicPressedRepeatAmount(g.HoveredIdTimer + 0.0001f, g.HoveredIdTimer + 0.0001f - g.IO.DeltaTime, 0.01f, 0.70f)) // FIXME: Our formula for CalcTypematicPressedRepeatAmount() is fishy
+            {
+                pressed = true;
+                FocusWindow(window);
+            }
+        }
+
+    if ((flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredRootWindow == window)
+        g.HoveredWindow = backup_hovered_window;
+
+    // AllowOverlap mode (rarely used) requires previous frame HoveredId to be null or to match. This allows using patterns where a later submitted widget overlaps a previous one.
+    if (hovered && (flags & ImGuiButtonFlags_AllowItemOverlap) && (g.HoveredIdPreviousFrame != id && g.HoveredIdPreviousFrame != 0))
+        hovered = false;
+
+    // Mouse
+    if (hovered)
+    {
+        if (!(flags & ImGuiButtonFlags_NoKeyModifiers) || (!g.IO.KeyCtrl && !g.IO.KeyShift && !g.IO.KeyAlt))
+        {
+            //                        | CLICKING        | HOLDING with ImGuiButtonFlags_Repeat
+            // PressedOnClickRelease  |  <on release>*  |  <on repeat> <on repeat> .. (NOT on release)  <-- MOST COMMON! (*) only if both click/release were over bounds
+            // PressedOnClick         |  <on click>     |  <on click> <on repeat> <on repeat> ..
+            // PressedOnRelease       |  <on release>   |  <on repeat> <on repeat> .. (NOT on release)
+            // PressedOnDoubleClick   |  <on dclick>    |  <on dclick> <on repeat> <on repeat> ..
+            // FIXME-NAV: We don't honor those different behaviors.
+            if ((flags & ImGuiButtonFlags_PressedOnClickRelease) && g.IO.MouseClicked[0])
+            {
+                SetActiveID(id, window);
+                if (!(flags & ImGuiButtonFlags_NoNavFocus))
+                    SetFocusID(id, window);
+                FocusWindow(window);
+            }
+            if (((flags & ImGuiButtonFlags_PressedOnClick) && g.IO.MouseClicked[0]) || ((flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseDoubleClicked[0]))
+            {
+                pressed = true;
+                if (flags & ImGuiButtonFlags_NoHoldingActiveID)
+                    ClearActiveID();
+                else
+                    SetActiveID(id, window); // Hold on ID
+                FocusWindow(window);
+            }
+            if ((flags & ImGuiButtonFlags_PressedOnRelease) && g.IO.MouseReleased[0])
+            {
+                if (!((flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[0] >= g.IO.KeyRepeatDelay))  // Repeat mode trumps <on release>
+                    pressed = true;
+                ClearActiveID();
+            }
+
+            // 'Repeat' mode acts when held regardless of _PressedOn flags (see table above).
+            // Relies on repeat logic of IsMouseClicked() but we may as well do it ourselves if we end up exposing finer RepeatDelay/RepeatRate settings.
+            if ((flags & ImGuiButtonFlags_Repeat) && g.ActiveId == id && g.IO.MouseDownDuration[0] > 0.0f && IsMouseClicked(0, true))
+                pressed = true;
+        }
+
+        if (pressed)
+            g.NavDisableHighlight = true;
+    }
+
+    // Gamepad/Keyboard navigation
+    // We report navigated item as hovered but we don't set g.HoveredId to not interfere with mouse.
+    if (g.NavId == id && !g.NavDisableHighlight && g.NavDisableMouseHover && (g.ActiveId == 0 || g.ActiveId == id || g.ActiveId == window->MoveId))
+        hovered = true;
+
+    if (g.NavActivateDownId == id)
+    {
+        bool nav_activated_by_code = (g.NavActivateId == id);
+        bool nav_activated_by_inputs = IsNavInputPressed(ImGuiNavInput_Activate, (flags & ImGuiButtonFlags_Repeat) ? ImGuiInputReadMode_Repeat : ImGuiInputReadMode_Pressed);
+        if (nav_activated_by_code || nav_activated_by_inputs)
+            pressed = true;
+        if (nav_activated_by_code || nav_activated_by_inputs || g.ActiveId == id)
+        {
+            // Set active id so it can be queried by user via IsItemActive(), equivalent of holding the mouse button.
+            g.NavActivateId = id; // This is so SetActiveId assign a Nav source
+            SetActiveID(id, window);
+            if (!(flags & ImGuiButtonFlags_NoNavFocus))
+                SetFocusID(id, window);
+            g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right) | (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
+        }
+    }
+
+    bool held = false;
+    if (g.ActiveId == id)
+    {
+        if (g.ActiveIdSource == ImGuiInputSource_Mouse)
+        {
+            if (g.ActiveIdIsJustActivated)
+                g.ActiveIdClickOffset = g.IO.MousePos - bb.Min;
+            if (g.IO.MouseDown[0])
+            {
+                held = true;
+            }
+            else
+            {
+                if (hovered && (flags & ImGuiButtonFlags_PressedOnClickRelease))
+                    if (!((flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[0] >= g.IO.KeyRepeatDelay))  // Repeat mode trumps <on release>
+                        if (!g.DragDropActive)
+                            pressed = true;
+                ClearActiveID();
+            }
+            if (!(flags & ImGuiButtonFlags_NoNavFocus))
+                g.NavDisableHighlight = true;
+        }
+        else if (g.ActiveIdSource == ImGuiInputSource_Nav)
+        {
+            if (g.NavActivateDownId != id)
+                ClearActiveID();
+        }
+    }
+
+    if (out_hovered) *out_hovered = hovered;
+    if (out_held) *out_held = held;
+
+    return pressed;
+}
+
+bool ImGui::ButtonEx(const char* label, const ImVec2& size_arg, ImGuiButtonFlags flags)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    if (window->SkipItems)
+        return false;
+
+    ImGuiContext& g = *GImGui;
+    const ImGuiStyle& style = g.Style;
+    const ImGuiID id = window->GetID(label);
+    const ImVec2 label_size = CalcTextSize(label, NULL, true);
+
+    ImVec2 pos = window->DC.CursorPos;
+    if ((flags & ImGuiButtonFlags_AlignTextBaseLine) && style.FramePadding.y < window->DC.CurrentLineTextBaseOffset) // Try to vertically align buttons that are smaller/have no padding so that text baseline matches (bit hacky, since it shouldn't be a flag)
+        pos.y += window->DC.CurrentLineTextBaseOffset - style.FramePadding.y;
+    ImVec2 size = CalcItemSize(size_arg, label_size.x + style.FramePadding.x * 2.0f, label_size.y + style.FramePadding.y * 2.0f);
+
+    const ImRect bb(pos, pos + size);
+    ItemSize(bb, style.FramePadding.y);
+    if (!ItemAdd(bb, id))
+        return false;
+
+    if (window->DC.ItemFlags & ImGuiItemFlags_ButtonRepeat)
+        flags |= ImGuiButtonFlags_Repeat;
+    bool hovered, held;
+    bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
+    if (pressed)
+        MarkItemEdited(id);
+
+    // Render
+    const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
+    RenderNavHighlight(bb, id);
+    RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding);
+    RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, NULL, &label_size, style.ButtonTextAlign, &bb);
+
+    // Automatically close popups
+    //if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup))
+    //    CloseCurrentPopup();
+
+    return pressed;
+}
+
+bool ImGui::Button(const char* label, const ImVec2& size_arg)
+{
+    return ButtonEx(label, size_arg, 0);
+}
+
+// Small buttons fits within text without additional vertical spacing.
+bool ImGui::SmallButton(const char* label)
+{
+    ImGuiContext& g = *GImGui;
+    float backup_padding_y = g.Style.FramePadding.y;
+    g.Style.FramePadding.y = 0.0f;
+    bool pressed = ButtonEx(label, ImVec2(0, 0), ImGuiButtonFlags_AlignTextBaseLine);
+    g.Style.FramePadding.y = backup_padding_y;
+    return pressed;
+}
+
+// Tip: use ImGui::PushID()/PopID() to push indices or pointers in the ID stack.
+// Then you can keep 'str_id' empty or the same for all your buttons (instead of creating a string based on a non-string id)
+bool ImGui::InvisibleButton(const char* str_id, const ImVec2& size_arg)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    if (window->SkipItems)
+        return false;
+
+    // Cannot use zero-size for InvisibleButton(). Unlike Button() there is not way to fallback using the label size.
+    IM_ASSERT(size_arg.x != 0.0f && size_arg.y != 0.0f);
+
+    const ImGuiID id = window->GetID(str_id);
+    ImVec2 size = CalcItemSize(size_arg, 0.0f, 0.0f);
+    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
+    ItemSize(bb);
+    if (!ItemAdd(bb, id))
+        return false;
+
+    bool hovered, held;
+    bool pressed = ButtonBehavior(bb, id, &hovered, &held);
+
+    return pressed;
+}
+
+bool ImGui::ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size, ImGuiButtonFlags flags)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    if (window->SkipItems)
+        return false;
+
+    ImGuiContext& g = *GImGui;
+    const ImGuiID id = window->GetID(str_id);
+    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
+    const float default_size = GetFrameHeight();
+    ItemSize(bb, (size.y >= default_size) ? g.Style.FramePadding.y : 0.0f);
+    if (!ItemAdd(bb, id))
+        return false;
+
+    if (window->DC.ItemFlags & ImGuiItemFlags_ButtonRepeat)
+        flags |= ImGuiButtonFlags_Repeat;
+
+    bool hovered, held;
+    bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
+
+    // Render
+    const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
+    RenderNavHighlight(bb, id);
+    RenderFrame(bb.Min, bb.Max, col, true, g.Style.FrameRounding);
+    RenderArrow(bb.Min + ImVec2(ImMax(0.0f, size.x - g.FontSize - g.Style.FramePadding.x), ImMax(0.0f, size.y - g.FontSize - g.Style.FramePadding.y)), dir);
+
+    return pressed;
+}
+
+bool ImGui::ArrowButton(const char* str_id, ImGuiDir dir)
+{
+    float sz = GetFrameHeight();
+    return ArrowButtonEx(str_id, dir, ImVec2(sz, sz), 0);
+}
+
+// Button to close a window
+bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos, float radius)
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiWindow* window = g.CurrentWindow;
+
+    // We intentionally allow interaction when clipped so that a mechanical Alt,Right,Validate sequence close a window.
+    // (this isn't the regular behavior of buttons, but it doesn't affect the user much because navigation tends to keep items visible).
+    const ImRect bb(pos - ImVec2(radius,radius), pos + ImVec2(radius,radius));
+    bool is_clipped = !ItemAdd(bb, id);
+
+    bool hovered, held;
+    bool pressed = ButtonBehavior(bb, id, &hovered, &held);
+    if (is_clipped)
+        return pressed;
+
+    // Render
+    ImVec2 center = bb.GetCenter();
+    if (hovered)
+        window->DrawList->AddCircleFilled(center, ImMax(2.0f, radius), GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered), 9);
+
+    float cross_extent = (radius * 0.7071f) - 1.0f;
+    ImU32 cross_col = GetColorU32(ImGuiCol_Text);
+    center -= ImVec2(0.5f, 0.5f);
+    window->DrawList->AddLine(center + ImVec2(+cross_extent,+cross_extent), center + ImVec2(-cross_extent,-cross_extent), cross_col, 1.0f);
+    window->DrawList->AddLine(center + ImVec2(+cross_extent,-cross_extent), center + ImVec2(-cross_extent,+cross_extent), cross_col, 1.0f);
+
+    return pressed;
+}
+
+bool ImGui::CollapseButton(ImGuiID id, const ImVec2& pos)
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiWindow* window = g.CurrentWindow;
+
+    ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize) + g.Style.FramePadding * 2.0f);
+    ItemAdd(bb, id);
+    bool hovered, held;
+    bool pressed = ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_None);
+
+    ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
+    if (hovered || held)
+        window->DrawList->AddCircleFilled(bb.GetCenter() + ImVec2(0.0f, -0.5f), g.FontSize * 0.5f + 1.0f, col, 9);
+    RenderArrow(bb.Min + g.Style.FramePadding, window->Collapsed ? ImGuiDir_Right : ImGuiDir_Down, 1.0f);
+
+    // Switch to moving the window after mouse is moved beyond the initial drag threshold
+    if (IsItemActive() && IsMouseDragging())
+        StartMouseMovingWindow(window);
+
+    return pressed;
+}
+
+// Vertical/Horizontal scrollbar
+// The entire piece of code below is rather confusing because:
+// - We handle absolute seeking (when first clicking outside the grab) and relative manipulation (afterward or when clicking inside the grab)
+// - We store values as normalized ratio and in a form that allows the window content to change while we are holding on a scrollbar
+// - We handle both horizontal and vertical scrollbars, which makes the terminology not ideal.
+void ImGui::Scrollbar(ImGuiLayoutType direction)
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiWindow* window = g.CurrentWindow;
+
+    const bool horizontal = (direction == ImGuiLayoutType_Horizontal);
+    const ImGuiStyle& style = g.Style;
+    const ImGuiID id = window->GetID(horizontal ? "#SCROLLX" : "#SCROLLY");
+
+    // Render background
+    bool other_scrollbar = (horizontal ? window->ScrollbarY : window->ScrollbarX);
+    float other_scrollbar_size_w = other_scrollbar ? style.ScrollbarSize : 0.0f;
+    const ImRect window_rect = window->Rect();
+    const float border_size = window->WindowBorderSize;
+    ImRect bb = horizontal
+        ? ImRect(window->Pos.x + border_size, window_rect.Max.y - style.ScrollbarSize, window_rect.Max.x - other_scrollbar_size_w - border_size, window_rect.Max.y - border_size)
+        : ImRect(window_rect.Max.x - style.ScrollbarSize, window->Pos.y + border_size, window_rect.Max.x - border_size, window_rect.Max.y - other_scrollbar_size_w - border_size);
+    if (!horizontal)
+        bb.Min.y += window->TitleBarHeight() + ((window->Flags & ImGuiWindowFlags_MenuBar) ? window->MenuBarHeight() : 0.0f);
+    if (bb.GetWidth() <= 0.0f || bb.GetHeight() <= 0.0f)
+        return;
+
+    int window_rounding_corners;
+    if (horizontal)
+        window_rounding_corners = ImDrawCornerFlags_BotLeft | (other_scrollbar ? 0 : ImDrawCornerFlags_BotRight);
+    else
+        window_rounding_corners = (((window->Flags & ImGuiWindowFlags_NoTitleBar) && !(window->Flags & ImGuiWindowFlags_MenuBar)) ? ImDrawCornerFlags_TopRight : 0) | (other_scrollbar ? 0 : ImDrawCornerFlags_BotRight);
+    window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_ScrollbarBg), window->WindowRounding, window_rounding_corners);
+    bb.Expand(ImVec2(-ImClamp((float)(int)((bb.Max.x - bb.Min.x - 2.0f) * 0.5f), 0.0f, 3.0f), -ImClamp((float)(int)((bb.Max.y - bb.Min.y - 2.0f) * 0.5f), 0.0f, 3.0f)));
+
+    // V denote the main, longer axis of the scrollbar (= height for a vertical scrollbar)
+    float scrollbar_size_v = horizontal ? bb.GetWidth() : bb.GetHeight();
+    float scroll_v = horizontal ? window->Scroll.x : window->Scroll.y;
+    float win_size_avail_v = (horizontal ? window->SizeFull.x : window->SizeFull.y) - other_scrollbar_size_w;
+    float win_size_contents_v = horizontal ? window->SizeContents.x : window->SizeContents.y;
+
+    // Calculate the height of our grabbable box. It generally represent the amount visible (vs the total scrollable amount)
+    // But we maintain a minimum size in pixel to allow for the user to still aim inside.
+    IM_ASSERT(ImMax(win_size_contents_v, win_size_avail_v) > 0.0f); // Adding this assert to check if the ImMax(XXX,1.0f) is still needed. PLEASE CONTACT ME if this triggers.
+    const float win_size_v = ImMax(ImMax(win_size_contents_v, win_size_avail_v), 1.0f);
+    const float grab_h_pixels = ImClamp(scrollbar_size_v * (win_size_avail_v / win_size_v), style.GrabMinSize, scrollbar_size_v);
+    const float grab_h_norm = grab_h_pixels / scrollbar_size_v;
+
+    // Handle input right away. None of the code of Begin() is relying on scrolling position before calling Scrollbar().
+    bool held = false;
+    bool hovered = false;
+    const bool previously_held = (g.ActiveId == id);
+    ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_NoNavFocus);
+
+    float scroll_max = ImMax(1.0f, win_size_contents_v - win_size_avail_v);
+    float scroll_ratio = ImSaturate(scroll_v / scroll_max);
+    float grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v;
+    if (held && grab_h_norm < 1.0f)
+    {
+        float scrollbar_pos_v = horizontal ? bb.Min.x : bb.Min.y;
+        float mouse_pos_v = horizontal ? g.IO.MousePos.x : g.IO.MousePos.y;
+        float* click_delta_to_grab_center_v = horizontal ? &g.ScrollbarClickDeltaToGrabCenter.x : &g.ScrollbarClickDeltaToGrabCenter.y;
+
+        // Click position in scrollbar normalized space (0.0f->1.0f)
+        const float clicked_v_norm = ImSaturate((mouse_pos_v - scrollbar_pos_v) / scrollbar_size_v);
+        SetHoveredID(id);
+
+        bool seek_absolute = false;
+        if (!previously_held)
+        {
+            // On initial click calculate the distance between mouse and the center of the grab
+            if (clicked_v_norm >= grab_v_norm && clicked_v_norm <= grab_v_norm + grab_h_norm)
+            {
+                *click_delta_to_grab_center_v = clicked_v_norm - grab_v_norm - grab_h_norm*0.5f;
+            }
+            else
+            {
+                seek_absolute = true;
+                *click_delta_to_grab_center_v = 0.0f;
+            }
+        }
+
+        // Apply scroll
+        // It is ok to modify Scroll here because we are being called in Begin() after the calculation of SizeContents and before setting up our starting position
+        const float scroll_v_norm = ImSaturate((clicked_v_norm - *click_delta_to_grab_center_v - grab_h_norm*0.5f) / (1.0f - grab_h_norm));
+        scroll_v = (float)(int)(0.5f + scroll_v_norm * scroll_max);//(win_size_contents_v - win_size_v));
+        if (horizontal)
+            window->Scroll.x = scroll_v;
+        else
+            window->Scroll.y = scroll_v;
+
+        // Update values for rendering
+        scroll_ratio = ImSaturate(scroll_v / scroll_max);
+        grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v;
+
+        // Update distance to grab now that we have seeked and saturated
+        if (seek_absolute)
+            *click_delta_to_grab_center_v = clicked_v_norm - grab_v_norm - grab_h_norm*0.5f;
+    }
+
+    // Render
+    const ImU32 grab_col = GetColorU32(held ? ImGuiCol_ScrollbarGrabActive : hovered ? ImGuiCol_ScrollbarGrabHovered : ImGuiCol_ScrollbarGrab);
+    ImRect grab_rect;
+    if (horizontal)
+        grab_rect = ImRect(ImLerp(bb.Min.x, bb.Max.x, grab_v_norm), bb.Min.y, ImMin(ImLerp(bb.Min.x, bb.Max.x, grab_v_norm) + grab_h_pixels, window_rect.Max.x), bb.Max.y);
+    else
+        grab_rect = ImRect(bb.Min.x, ImLerp(bb.Min.y, bb.Max.y, grab_v_norm), bb.Max.x, ImMin(ImLerp(bb.Min.y, bb.Max.y, grab_v_norm) + grab_h_pixels, window_rect.Max.y));
+    window->DrawList->AddRectFilled(grab_rect.Min, grab_rect.Max, grab_col, style.ScrollbarRounding);
+}
+
+void ImGui::Image(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    if (window->SkipItems)
+        return;
+
+    ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
+    if (border_col.w > 0.0f)
+        bb.Max += ImVec2(2, 2);
+    ItemSize(bb);
+    if (!ItemAdd(bb, 0))
+        return;
+
+    if (border_col.w > 0.0f)
+    {
+        window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(border_col), 0.0f);
+        window->DrawList->AddImage(user_texture_id, bb.Min + ImVec2(1, 1), bb.Max - ImVec2(1, 1), uv0, uv1, GetColorU32(tint_col));
+    }
+    else
+    {
+        window->DrawList->AddImage(user_texture_id, bb.Min, bb.Max, uv0, uv1, GetColorU32(tint_col));
+    }
+}
+
+// frame_padding < 0: uses FramePadding from style (default)
+// frame_padding = 0: no framing
+// frame_padding > 0: set framing size
+// The color used are the button colors.
+bool ImGui::ImageButton(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, int frame_padding, const ImVec4& bg_col, const ImVec4& tint_col)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    if (window->SkipItems)
+        return false;
+
+    ImGuiContext& g = *GImGui;
+    const ImGuiStyle& style = g.Style;
+
+    // Default to using texture ID as ID. User can still push string/integer prefixes.
+    // We could hash the size/uv to create a unique ID but that would prevent the user from animating UV.
+    PushID((void*)user_texture_id);
+    const ImGuiID id = window->GetID("#image");
+    PopID();
+
+    const ImVec2 padding = (frame_padding >= 0) ? ImVec2((float)frame_padding, (float)frame_padding) : style.FramePadding;
+    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size + padding * 2);
+    const ImRect image_bb(window->DC.CursorPos + padding, window->DC.CursorPos + padding + size);
+    ItemSize(bb);
+    if (!ItemAdd(bb, id))
+        return false;
+
+    bool hovered, held;
+    bool pressed = ButtonBehavior(bb, id, &hovered, &held);
+
+    // Render
+    const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
+    RenderNavHighlight(bb, id);
+    RenderFrame(bb.Min, bb.Max, col, true, ImClamp((float)ImMin(padding.x, padding.y), 0.0f, style.FrameRounding));
+    if (bg_col.w > 0.0f)
+        window->DrawList->AddRectFilled(image_bb.Min, image_bb.Max, GetColorU32(bg_col));
+    window->DrawList->AddImage(user_texture_id, image_bb.Min, image_bb.Max, uv0, uv1, GetColorU32(tint_col));
+
+    return pressed;
+}
+
+bool ImGui::Checkbox(const char* label, bool* v)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    if (window->SkipItems)
+        return false;
+
+    ImGuiContext& g = *GImGui;
+    const ImGuiStyle& style = g.Style;
+    const ImGuiID id = window->GetID(label);
+    const ImVec2 label_size = CalcTextSize(label, NULL, true);
+
+    const ImRect check_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(label_size.y + style.FramePadding.y*2, label_size.y + style.FramePadding.y*2)); // We want a square shape to we use Y twice
+    ItemSize(check_bb, style.FramePadding.y);
+
+    ImRect total_bb = check_bb;
+    if (label_size.x > 0)
+        SameLine(0, style.ItemInnerSpacing.x);
+    const ImRect text_bb(window->DC.CursorPos + ImVec2(0,style.FramePadding.y), window->DC.CursorPos + ImVec2(0,style.FramePadding.y) + label_size);
+    if (label_size.x > 0)
+    {
+        ItemSize(ImVec2(text_bb.GetWidth(), check_bb.GetHeight()), style.FramePadding.y);
+        total_bb = ImRect(ImMin(check_bb.Min, text_bb.Min), ImMax(check_bb.Max, text_bb.Max));
+    }
+
+    if (!ItemAdd(total_bb, id))
+        return false;
+
+    bool hovered, held;
+    bool pressed = ButtonBehavior(total_bb, id, &hovered, &held);
+    if (pressed)
+    {
+        *v = !(*v);
+        MarkItemEdited(id);
+    }
+
+    RenderNavHighlight(total_bb, id);
+    RenderFrame(check_bb.Min, check_bb.Max, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), true, style.FrameRounding);
+    if (*v)
+    {
+        const float check_sz = ImMin(check_bb.GetWidth(), check_bb.GetHeight());
+        const float pad = ImMax(1.0f, (float)(int)(check_sz / 6.0f));
+        RenderCheckMark(check_bb.Min + ImVec2(pad,pad), GetColorU32(ImGuiCol_CheckMark), check_bb.GetWidth() - pad*2.0f);
+    }
+
+    if (g.LogEnabled)
+        LogRenderedText(&text_bb.Min, *v ? "[x]" : "[ ]");
+    if (label_size.x > 0.0f)
+        RenderText(text_bb.Min, label);
+
+    return pressed;
+}
+
+bool ImGui::CheckboxFlags(const char* label, unsigned int* flags, unsigned int flags_value)
+{
+    bool v = ((*flags & flags_value) == flags_value);
+    bool pressed = Checkbox(label, &v);
+    if (pressed)
+    {
+        if (v)
+            *flags |= flags_value;
+        else
+            *flags &= ~flags_value;
+    }
+
+    return pressed;
+}
+
+bool ImGui::RadioButton(const char* label, bool active)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    if (window->SkipItems)
+        return false;
+
+    ImGuiContext& g = *GImGui;
+    const ImGuiStyle& style = g.Style;
+    const ImGuiID id = window->GetID(label);
+    const ImVec2 label_size = CalcTextSize(label, NULL, true);
+
+    const ImRect check_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(label_size.y + style.FramePadding.y*2-1, label_size.y + style.FramePadding.y*2-1));
+    ItemSize(check_bb, style.FramePadding.y);
+
+    ImRect total_bb = check_bb;
+    if (label_size.x > 0)
+        SameLine(0, style.ItemInnerSpacing.x);
+    const ImRect text_bb(window->DC.CursorPos + ImVec2(0, style.FramePadding.y), window->DC.CursorPos + ImVec2(0, style.FramePadding.y) + label_size);
+    if (label_size.x > 0)
+    {
+        ItemSize(ImVec2(text_bb.GetWidth(), check_bb.GetHeight()), style.FramePadding.y);
+        total_bb.Add(text_bb);
+    }
+
+    if (!ItemAdd(total_bb, id))
+        return false;
+
+    ImVec2 center = check_bb.GetCenter();
+    center.x = (float)(int)center.x + 0.5f;
+    center.y = (float)(int)center.y + 0.5f;
+    const float radius = check_bb.GetHeight() * 0.5f;
+
+    bool hovered, held;
+    bool pressed = ButtonBehavior(total_bb, id, &hovered, &held);
+    if (pressed)
+        MarkItemEdited(id);
+
+    RenderNavHighlight(total_bb, id);
+    window->DrawList->AddCircleFilled(center, radius, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), 16);
+    if (active)
+    {
+        const float check_sz = ImMin(check_bb.GetWidth(), check_bb.GetHeight());
+        const float pad = ImMax(1.0f, (float)(int)(check_sz / 6.0f));
+        window->DrawList->AddCircleFilled(center, radius-pad, GetColorU32(ImGuiCol_CheckMark), 16);
+    }
+
+    if (style.FrameBorderSize > 0.0f)
+    {
+        window->DrawList->AddCircle(center+ImVec2(1,1), radius, GetColorU32(ImGuiCol_BorderShadow), 16, style.FrameBorderSize);
+        window->DrawList->AddCircle(center, radius, GetColorU32(ImGuiCol_Border), 16, style.FrameBorderSize);
+    }
+
+    if (g.LogEnabled)
+        LogRenderedText(&text_bb.Min, active ? "(x)" : "( )");
+    if (label_size.x > 0.0f)
+        RenderText(text_bb.Min, label);
+
+    return pressed;
+}
+
+bool ImGui::RadioButton(const char* label, int* v, int v_button)
+{
+    const bool pressed = RadioButton(label, *v == v_button);
+    if (pressed)
+        *v = v_button;
+    return pressed;
+}
+
+// size_arg (for each axis) < 0.0f: align to end, 0.0f: auto, > 0.0f: specified size
+void ImGui::ProgressBar(float fraction, const ImVec2& size_arg, const char* overlay)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    if (window->SkipItems)
+        return;
+
+    ImGuiContext& g = *GImGui;
+    const ImGuiStyle& style = g.Style;
+
+    ImVec2 pos = window->DC.CursorPos;
+    ImRect bb(pos, pos + CalcItemSize(size_arg, CalcItemWidth(), g.FontSize + style.FramePadding.y*2.0f));
+    ItemSize(bb, style.FramePadding.y);
+    if (!ItemAdd(bb, 0))
+        return;
+
+    // Render
+    fraction = ImSaturate(fraction);
+    RenderFrame(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
+    bb.Expand(ImVec2(-style.FrameBorderSize, -style.FrameBorderSize));
+    const ImVec2 fill_br = ImVec2(ImLerp(bb.Min.x, bb.Max.x, fraction), bb.Max.y);
+    RenderRectFilledRangeH(window->DrawList, bb, GetColorU32(ImGuiCol_PlotHistogram), 0.0f, fraction, style.FrameRounding);
+
+    // Default displaying the fraction as percentage string, but user can override it
+    char overlay_buf[32];
+    if (!overlay)
+    {
+        ImFormatString(overlay_buf, IM_ARRAYSIZE(overlay_buf), "%.0f%%", fraction*100+0.01f);
+        overlay = overlay_buf;
+    }
+
+    ImVec2 overlay_size = CalcTextSize(overlay, NULL);
+    if (overlay_size.x > 0.0f)
+        RenderTextClipped(ImVec2(ImClamp(fill_br.x + style.ItemSpacing.x, bb.Min.x, bb.Max.x - overlay_size.x - style.ItemInnerSpacing.x), bb.Min.y), bb.Max, overlay, NULL, &overlay_size, ImVec2(0.0f,0.5f), &bb);
+}
+
+void ImGui::Bullet()
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    if (window->SkipItems)
+        return;
+
+    ImGuiContext& g = *GImGui;
+    const ImGuiStyle& style = g.Style;
+    const float line_height = ImMax(ImMin(window->DC.CurrentLineSize.y, g.FontSize + g.Style.FramePadding.y*2), g.FontSize);
+    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize, line_height));
+    ItemSize(bb);
+    if (!ItemAdd(bb, 0))
+    {
+        SameLine(0, style.FramePadding.x*2);
+        return;
+    }
+
+    // Render and stay on same line
+    RenderBullet(bb.Min + ImVec2(style.FramePadding.x + g.FontSize*0.5f, line_height*0.5f));
+    SameLine(0, style.FramePadding.x*2);
+}
+
+//-------------------------------------------------------------------------
+// [SECTION] Widgets: Low-level Layout helpers
+//-------------------------------------------------------------------------
+// - Spacing()
+// - Dummy()
+// - NewLine()
+// - AlignTextToFramePadding()
+// - Separator()
+// - VerticalSeparator() [Internal]
+// - SplitterBehavior() [Internal]
+//-------------------------------------------------------------------------
+
+void ImGui::Spacing()
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    if (window->SkipItems)
+        return;
+    ItemSize(ImVec2(0,0));
+}
+
+void ImGui::Dummy(const ImVec2& size)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    if (window->SkipItems)
+        return;
+
+    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
+    ItemSize(bb);
+    ItemAdd(bb, 0);
+}
+
+void ImGui::NewLine()
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    if (window->SkipItems)
+        return;
+
+    ImGuiContext& g = *GImGui;
+    const ImGuiLayoutType backup_layout_type = window->DC.LayoutType;
+    window->DC.LayoutType = ImGuiLayoutType_Vertical;
+    if (window->DC.CurrentLineSize.y > 0.0f)     // In the event that we are on a line with items that is smaller that FontSize high, we will preserve its height.
+        ItemSize(ImVec2(0,0));
+    else
+        ItemSize(ImVec2(0.0f, g.FontSize));
+    window->DC.LayoutType = backup_layout_type;
+}
+
+void ImGui::AlignTextToFramePadding()
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    if (window->SkipItems)
+        return;
+
+    ImGuiContext& g = *GImGui;
+    window->DC.CurrentLineSize.y = ImMax(window->DC.CurrentLineSize.y, g.FontSize + g.Style.FramePadding.y * 2);
+    window->DC.CurrentLineTextBaseOffset = ImMax(window->DC.CurrentLineTextBaseOffset, g.Style.FramePadding.y);
+}
+
+// Horizontal/vertical separating line
+void ImGui::Separator()
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    if (window->SkipItems)
+        return;
+    ImGuiContext& g = *GImGui;
+
+    // Those flags should eventually be overridable by the user
+    ImGuiSeparatorFlags flags = (window->DC.LayoutType == ImGuiLayoutType_Horizontal) ? ImGuiSeparatorFlags_Vertical : ImGuiSeparatorFlags_Horizontal;
+    IM_ASSERT(ImIsPowerOfTwo((int)(flags & (ImGuiSeparatorFlags_Horizontal | ImGuiSeparatorFlags_Vertical))));   // Check that only 1 option is selected
+    if (flags & ImGuiSeparatorFlags_Vertical)
+    {
+        VerticalSeparator();
+        return;
+    }
+
+    // Horizontal Separator
+    if (window->DC.ColumnsSet)
+        PopClipRect();
+
+    float x1 = window->Pos.x;
+    float x2 = window->Pos.x + window->Size.x;
+    if (!window->DC.GroupStack.empty())
+        x1 += window->DC.Indent.x;
+
+    const ImRect bb(ImVec2(x1, window->DC.CursorPos.y), ImVec2(x2, window->DC.CursorPos.y+1.0f));
+    ItemSize(ImVec2(0.0f, 0.0f)); // NB: we don't provide our width so that it doesn't get feed back into AutoFit, we don't provide height to not alter layout.
+    if (!ItemAdd(bb, 0))
+    {
+        if (window->DC.ColumnsSet)
+            PushColumnClipRect();
+        return;
+    }
+
+    window->DrawList->AddLine(bb.Min, ImVec2(bb.Max.x,bb.Min.y), GetColorU32(ImGuiCol_Separator));
+
+    if (g.LogEnabled)
+        LogRenderedText(NULL, IM_NEWLINE "--------------------------------");
+
+    if (window->DC.ColumnsSet)
+    {
+        PushColumnClipRect();
+        window->DC.ColumnsSet->LineMinY = window->DC.CursorPos.y;
+    }
+}
+
+void ImGui::VerticalSeparator()
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    if (window->SkipItems)
+        return;
+    ImGuiContext& g = *GImGui;
+
+    float y1 = window->DC.CursorPos.y;
+    float y2 = window->DC.CursorPos.y + window->DC.CurrentLineSize.y;
+    const ImRect bb(ImVec2(window->DC.CursorPos.x, y1), ImVec2(window->DC.CursorPos.x + 1.0f, y2));
+    ItemSize(ImVec2(bb.GetWidth(), 0.0f));
+    if (!ItemAdd(bb, 0))
+        return;
+
+    window->DrawList->AddLine(ImVec2(bb.Min.x, bb.Min.y), ImVec2(bb.Min.x, bb.Max.y), GetColorU32(ImGuiCol_Separator));
+    if (g.LogEnabled)
+        LogText(" |");
+}
+
+// Using 'hover_visibility_delay' allows us to hide the highlight and mouse cursor for a short time, which can be convenient to reduce visual noise.
+bool ImGui::SplitterBehavior(const ImRect& bb, ImGuiID id, ImGuiAxis axis, float* size1, float* size2, float min_size1, float min_size2, float hover_extend, float hover_visibility_delay)
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiWindow* window = g.CurrentWindow;
+
+    const ImGuiItemFlags item_flags_backup = window->DC.ItemFlags;
+    window->DC.ItemFlags |= ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus;
+    bool item_add = ItemAdd(bb, id);
+    window->DC.ItemFlags = item_flags_backup;
+    if (!item_add)
+        return false;
+
+    bool hovered, held;
+    ImRect bb_interact = bb;
+    bb_interact.Expand(axis == ImGuiAxis_Y ? ImVec2(0.0f, hover_extend) : ImVec2(hover_extend, 0.0f));
+    ButtonBehavior(bb_interact, id, &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_AllowItemOverlap);
+    if (g.ActiveId != id)
+        SetItemAllowOverlap();
+
+    if (held || (g.HoveredId == id && g.HoveredIdPreviousFrame == id && g.HoveredIdTimer >= hover_visibility_delay))
+        SetMouseCursor(axis == ImGuiAxis_Y ? ImGuiMouseCursor_ResizeNS : ImGuiMouseCursor_ResizeEW);
+
+    ImRect bb_render = bb;
+    if (held)
+    {
+        ImVec2 mouse_delta_2d = g.IO.MousePos - g.ActiveIdClickOffset - bb_interact.Min;
+        float mouse_delta = (axis == ImGuiAxis_Y) ? mouse_delta_2d.y : mouse_delta_2d.x;
+
+        // Minimum pane size
+        float size_1_maximum_delta = ImMax(0.0f, *size1 - min_size1);
+        float size_2_maximum_delta = ImMax(0.0f, *size2 - min_size2);
+        if (mouse_delta < -size_1_maximum_delta)
+            mouse_delta = -size_1_maximum_delta;
+        if (mouse_delta > size_2_maximum_delta)
+            mouse_delta = size_2_maximum_delta;
+
+        // Apply resize
+        if (mouse_delta != 0.0f)
+        {
+            if (mouse_delta < 0.0f)
+                IM_ASSERT(*size1 + mouse_delta >= min_size1);
+            if (mouse_delta > 0.0f)
+                IM_ASSERT(*size2 - mouse_delta >= min_size2);
+            *size1 += mouse_delta;
+            *size2 -= mouse_delta;
+            bb_render.Translate((axis == ImGuiAxis_X) ? ImVec2(mouse_delta, 0.0f) : ImVec2(0.0f, mouse_delta));
+            MarkItemEdited(id);
+        }
+    }
+
+    // Render
+    const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : (hovered && g.HoveredIdTimer >= hover_visibility_delay) ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator);
+    window->DrawList->AddRectFilled(bb_render.Min, bb_render.Max, col, g.Style.FrameRounding);
+
+    return held;
+}
+
+
+//-------------------------------------------------------------------------
+// [SECTION] Widgets: Combo Box
+//-------------------------------------------------------------------------
+// - BeginCombo()
+// - EndCombo()
+// - Combo()
+//-------------------------------------------------------------------------
+
+static float CalcMaxPopupHeightFromItemCount(int items_count)
+{
+    ImGuiContext& g = *GImGui;
+    if (items_count <= 0)
+        return FLT_MAX;
+    return (g.FontSize + g.Style.ItemSpacing.y) * items_count - g.Style.ItemSpacing.y + (g.Style.WindowPadding.y * 2);
+}
+
+bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags)
+{
+    // Always consume the SetNextWindowSizeConstraint() call in our early return paths
+    ImGuiContext& g = *GImGui;
+    ImGuiCond backup_next_window_size_constraint = g.NextWindowData.SizeConstraintCond;
+    g.NextWindowData.SizeConstraintCond = 0;
+
+    ImGuiWindow* window = GetCurrentWindow();
+    if (window->SkipItems)
+        return false;
+
+    IM_ASSERT((flags & (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)) != (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)); // Can't use both flags together
+
+    const ImGuiStyle& style = g.Style;
+    const ImGuiID id = window->GetID(label);
+
+    const float arrow_size = (flags & ImGuiComboFlags_NoArrowButton) ? 0.0f : GetFrameHeight();
+    const ImVec2 label_size = CalcTextSize(label, NULL, true);
+    const float w = (flags & ImGuiComboFlags_NoPreview) ? arrow_size : CalcItemWidth();
+    const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f));
+    const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
+    ItemSize(total_bb, style.FramePadding.y);
+    if (!ItemAdd(total_bb, id, &frame_bb))
+        return false;
+
+    bool hovered, held;
+    bool pressed = ButtonBehavior(frame_bb, id, &hovered, &held);
+    bool popup_open = IsPopupOpen(id);
+
+    const ImRect value_bb(frame_bb.Min, frame_bb.Max - ImVec2(arrow_size, 0.0f));
+    const ImU32 frame_col = GetColorU32(hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
+    RenderNavHighlight(frame_bb, id);
+    if (!(flags & ImGuiComboFlags_NoPreview))
+        window->DrawList->AddRectFilled(frame_bb.Min, ImVec2(frame_bb.Max.x - arrow_size, frame_bb.Max.y), frame_col, style.FrameRounding, ImDrawCornerFlags_Left);
+    if (!(flags & ImGuiComboFlags_NoArrowButton))
+    {
+        window->DrawList->AddRectFilled(ImVec2(frame_bb.Max.x - arrow_size, frame_bb.Min.y), frame_bb.Max, GetColorU32((popup_open || hovered) ? ImGuiCol_ButtonHovered : ImGuiCol_Button), style.FrameRounding, (w <= arrow_size) ? ImDrawCornerFlags_All : ImDrawCornerFlags_Right);
+        RenderArrow(ImVec2(frame_bb.Max.x - arrow_size + style.FramePadding.y, frame_bb.Min.y + style.FramePadding.y), ImGuiDir_Down);
+    }
+    RenderFrameBorder(frame_bb.Min, frame_bb.Max, style.FrameRounding);
+    if (preview_value != NULL && !(flags & ImGuiComboFlags_NoPreview))
+        RenderTextClipped(frame_bb.Min + style.FramePadding, value_bb.Max, preview_value, NULL, NULL, ImVec2(0.0f,0.0f));
+    if (label_size.x > 0)
+        RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
+
+    if ((pressed || g.NavActivateId == id) && !popup_open)
+    {
+        if (window->DC.NavLayerCurrent == 0)
+            window->NavLastIds[0] = id;
+        OpenPopupEx(id);
+        popup_open = true;
+    }
+
+    if (!popup_open)
+        return false;
+
+    if (backup_next_window_size_constraint)
+    {
+        g.NextWindowData.SizeConstraintCond = backup_next_window_size_constraint;
+        g.NextWindowData.SizeConstraintRect.Min.x = ImMax(g.NextWindowData.SizeConstraintRect.Min.x, w);
+    }
+    else
+    {
+        if ((flags & ImGuiComboFlags_HeightMask_) == 0)
+            flags |= ImGuiComboFlags_HeightRegular;
+        IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiComboFlags_HeightMask_));    // Only one
+        int popup_max_height_in_items = -1;
+        if (flags & ImGuiComboFlags_HeightRegular)     popup_max_height_in_items = 8;
+        else if (flags & ImGuiComboFlags_HeightSmall)  popup_max_height_in_items = 4;
+        else if (flags & ImGuiComboFlags_HeightLarge)  popup_max_height_in_items = 20;
+        SetNextWindowSizeConstraints(ImVec2(w, 0.0f), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items)));
+    }
+
+    char name[16];
+    ImFormatString(name, IM_ARRAYSIZE(name), "##Combo_%02d", g.CurrentPopupStack.Size); // Recycle windows based on depth
+
+    // Peak into expected window size so we can position it
+    if (ImGuiWindow* popup_window = FindWindowByName(name))
+        if (popup_window->WasActive)
+        {
+            ImVec2 size_expected = CalcWindowExpectedSize(popup_window);
+            if (flags & ImGuiComboFlags_PopupAlignLeft)
+                popup_window->AutoPosLastDirection = ImGuiDir_Left;
+            ImRect r_outer = GetWindowAllowedExtentRect(popup_window);
+            ImVec2 pos = FindBestWindowPosForPopupEx(frame_bb.GetBL(), size_expected, &popup_window->AutoPosLastDirection, r_outer, frame_bb, ImGuiPopupPositionPolicy_ComboBox);
+            SetNextWindowPos(pos);
+        }
+
+    // Horizontally align ourselves with the framed text
+    ImGuiWindowFlags window_flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings;
+    PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(style.FramePadding.x, style.WindowPadding.y));
+    bool ret = Begin(name, NULL, window_flags);
+    PopStyleVar();
+    if (!ret)
+    {
+        EndPopup();
+        IM_ASSERT(0);   // This should never happen as we tested for IsPopupOpen() above
+        return false;
+    }
+    return true;
+}
+
+void ImGui::EndCombo()
+{
+    EndPopup();
+}
+
+// Getter for the old Combo() API: const char*[]
+static bool Items_ArrayGetter(void* data, int idx, const char** out_text)
+{
+    const char* const* items = (const char* const*)data;
+    if (out_text)
+        *out_text = items[idx];
+    return true;
+}
+
+// Getter for the old Combo() API: "item1\0item2\0item3\0"
+static bool Items_SingleStringGetter(void* data, int idx, const char** out_text)
+{
+    // FIXME-OPT: we could pre-compute the indices to fasten this. But only 1 active combo means the waste is limited.
+    const char* items_separated_by_zeros = (const char*)data;
+    int items_count = 0;
+    const char* p = items_separated_by_zeros;
+    while (*p)
+    {
+        if (idx == items_count)
+            break;
+        p += strlen(p) + 1;
+        items_count++;
+    }
+    if (!*p)
+        return false;
+    if (out_text)
+        *out_text = p;
+    return true;
+}
+
+// Old API, prefer using BeginCombo() nowadays if you can.
+bool ImGui::Combo(const char* label, int* current_item, bool (*items_getter)(void*, int, const char**), void* data, int items_count, int popup_max_height_in_items)
+{
+    ImGuiContext& g = *GImGui;
+
+    // Call the getter to obtain the preview string which is a parameter to BeginCombo()
+    const char* preview_value = NULL;
+    if (*current_item >= 0 && *current_item < items_count)
+        items_getter(data, *current_item, &preview_value);
+
+    // The old Combo() API exposed "popup_max_height_in_items". The new more general BeginCombo() API doesn't have/need it, but we emulate it here.
+    if (popup_max_height_in_items != -1 && !g.NextWindowData.SizeConstraintCond)
+        SetNextWindowSizeConstraints(ImVec2(0,0), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items)));
+
+    if (!BeginCombo(label, preview_value, ImGuiComboFlags_None))
+        return false;
+
+    // Display items
+    // FIXME-OPT: Use clipper (but we need to disable it on the appearing frame to make sure our call to SetItemDefaultFocus() is processed)
+    bool value_changed = false;
+    for (int i = 0; i < items_count; i++)
+    {
+        PushID((void*)(intptr_t)i);
+        const bool item_selected = (i == *current_item);
+        const char* item_text;
+        if (!items_getter(data, i, &item_text))
+            item_text = "*Unknown item*";
+        if (Selectable(item_text, item_selected))
+        {
+            value_changed = true;
+            *current_item = i;
+        }
+        if (item_selected)
+            SetItemDefaultFocus();
+        PopID();
+    }
+
+    EndCombo();
+    return value_changed;
+}
+
+// Combo box helper allowing to pass an array of strings.
+bool ImGui::Combo(const char* label, int* current_item, const char* const items[], int items_count, int height_in_items)
+{
+    const bool value_changed = Combo(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_in_items);
+    return value_changed;
+}
+
+// Combo box helper allowing to pass all items in a single string literal holding multiple zero-terminated items "item1\0item2\0" 
+bool ImGui::Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int height_in_items)
+{
+    int items_count = 0;
+    const char* p = items_separated_by_zeros;       // FIXME-OPT: Avoid computing this, or at least only when combo is open
+    while (*p)
+    {
+        p += strlen(p) + 1;
+        items_count++;
+    }
+    bool value_changed = Combo(label, current_item, Items_SingleStringGetter, (void*)items_separated_by_zeros, items_count, height_in_items);
+    return value_changed;
+}
+
+//-------------------------------------------------------------------------
+// [SECTION] Data Type and Data Formatting Helpers [Internal]
+//-------------------------------------------------------------------------
+// - PatchFormatStringFloatToInt()
+// - DataTypeFormatString()
+// - DataTypeApplyOp()
+// - DataTypeApplyOpFromText()
+// - GetMinimumStepAtDecimalPrecision
+// - RoundScalarWithFormat<>()
+//-------------------------------------------------------------------------
+
+struct ImGuiDataTypeInfo
+{
+    size_t      Size;
+    const char* PrintFmt;   // Unused
+    const char* ScanFmt;
+};
+
+static const ImGuiDataTypeInfo GDataTypeInfo[] =
+{
+    { sizeof(int),          "%d",   "%d"    },
+    { sizeof(unsigned int), "%u",   "%u"    },
+#ifdef _MSC_VER
+    { sizeof(ImS64),        "%I64d","%I64d" },
+    { sizeof(ImU64),        "%I64u","%I64u" },
+#else
+    { sizeof(ImS64),        "%lld", "%lld"  },
+    { sizeof(ImU64),        "%llu", "%llu"  },
+#endif
+    { sizeof(float),        "%f",   "%f"    },  // float are promoted to double in va_arg
+    { sizeof(double),       "%f",   "%lf"   },
+};
+IM_STATIC_ASSERT(IM_ARRAYSIZE(GDataTypeInfo) == ImGuiDataType_COUNT);
+
+// FIXME-LEGACY: Prior to 1.61 our DragInt() function internally used floats and because of this the compile-time default value for format was "%.0f".
+// Even though we changed the compile-time default, we expect users to have carried %f around, which would break the display of DragInt() calls.
+// To honor backward compatibility we are rewriting the format string, unless IMGUI_DISABLE_OBSOLETE_FUNCTIONS is enabled. What could possibly go wrong?!
+static const char* PatchFormatStringFloatToInt(const char* fmt)
+{
+    if (fmt[0] == '%' && fmt[1] == '.' && fmt[2] == '0' && fmt[3] == 'f' && fmt[4] == 0) // Fast legacy path for "%.0f" which is expected to be the most common case.
+        return "%d";
+    const char* fmt_start = ImParseFormatFindStart(fmt);    // Find % (if any, and ignore %%)
+    const char* fmt_end = ImParseFormatFindEnd(fmt_start);  // Find end of format specifier, which itself is an exercise of confidence/recklessness (because snprintf is dependent on libc or user).
+    if (fmt_end > fmt_start && fmt_end[-1] == 'f')
+    {
+#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
+        if (fmt_start == fmt && fmt_end[0] == 0)
+            return "%d";
+        ImGuiContext& g = *GImGui;
+        ImFormatString(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), "%.*s%%d%s", (int)(fmt_start - fmt), fmt, fmt_end); // Honor leading and trailing decorations, but lose alignment/precision.
+        return g.TempBuffer;
+#else
+        IM_ASSERT(0 && "DragInt(): Invalid format string!"); // Old versions used a default parameter of "%.0f", please replace with e.g. "%d"
+#endif
+    }
+    return fmt;
+}
+
+static inline int DataTypeFormatString(char* buf, int buf_size, ImGuiDataType data_type, const void* data_ptr, const char* format)
+{
+    if (data_type == ImGuiDataType_S32 || data_type == ImGuiDataType_U32)   // Signedness doesn't matter when pushing the argument
+        return ImFormatString(buf, buf_size, format, *(const ImU32*)data_ptr);
+    if (data_type == ImGuiDataType_S64 || data_type == ImGuiDataType_U64)   // Signedness doesn't matter when pushing the argument
+        return ImFormatString(buf, buf_size, format, *(const ImU64*)data_ptr);
+    if (data_type == ImGuiDataType_Float)
+        return ImFormatString(buf, buf_size, format, *(const float*)data_ptr);
+    if (data_type == ImGuiDataType_Double)
+        return ImFormatString(buf, buf_size, format, *(const double*)data_ptr);
+    IM_ASSERT(0);
+    return 0;
+}
+
+// FIXME: Adding support for clamping on boundaries of the data type would be nice.
+static void DataTypeApplyOp(ImGuiDataType data_type, int op, void* output, void* arg1, const void* arg2)
+{
+    IM_ASSERT(op == '+' || op == '-');
+    switch (data_type)
+    {
+        case ImGuiDataType_S32:
+            if (op == '+')      *(int*)output = *(const int*)arg1 + *(const int*)arg2;
+            else if (op == '-') *(int*)output = *(const int*)arg1 - *(const int*)arg2;
+            return;
+        case ImGuiDataType_U32:
+            if (op == '+')      *(unsigned int*)output = *(const unsigned int*)arg1 + *(const ImU32*)arg2;
+            else if (op == '-') *(unsigned int*)output = *(const unsigned int*)arg1 - *(const ImU32*)arg2;
+            return;
+        case ImGuiDataType_S64:
+            if (op == '+')      *(ImS64*)output = *(const ImS64*)arg1 + *(const ImS64*)arg2;
+            else if (op == '-') *(ImS64*)output = *(const ImS64*)arg1 - *(const ImS64*)arg2;
+            return;
+        case ImGuiDataType_U64:
+            if (op == '+')      *(ImU64*)output = *(const ImU64*)arg1 + *(const ImU64*)arg2;
+            else if (op == '-') *(ImU64*)output = *(const ImU64*)arg1 - *(const ImU64*)arg2;
+            return;
+        case ImGuiDataType_Float:
+            if (op == '+')      *(float*)output = *(const float*)arg1 + *(const float*)arg2;
+            else if (op == '-') *(float*)output = *(const float*)arg1 - *(const float*)arg2;
+            return;
+        case ImGuiDataType_Double:
+            if (op == '+')      *(double*)output = *(const double*)arg1 + *(const double*)arg2;
+            else if (op == '-') *(double*)output = *(const double*)arg1 - *(const double*)arg2;
+            return;
+        case ImGuiDataType_COUNT: break;
+    }
+    IM_ASSERT(0);
+}
+
+// User can input math operators (e.g. +100) to edit a numerical values.
+// NB: This is _not_ a full expression evaluator. We should probably add one and replace this dumb mess..
+static bool DataTypeApplyOpFromText(const char* buf, const char* initial_value_buf, ImGuiDataType data_type, void* data_ptr, const char* format)
+{
+    while (ImCharIsBlankA(*buf))
+        buf++;
+
+    // We don't support '-' op because it would conflict with inputing negative value.
+    // Instead you can use +-100 to subtract from an existing value
+    char op = buf[0];
+    if (op == '+' || op == '*' || op == '/')
+    {
+        buf++;
+        while (ImCharIsBlankA(*buf))
+            buf++;
+    }
+    else
+    {
+        op = 0;
+    }
+    if (!buf[0])
+        return false;
+
+    // Copy the value in an opaque buffer so we can compare at the end of the function if it changed at all.
+    IM_ASSERT(data_type < ImGuiDataType_COUNT);
+    int data_backup[2];
+    IM_ASSERT(GDataTypeInfo[data_type].Size <= sizeof(data_backup));
+    memcpy(data_backup, data_ptr, GDataTypeInfo[data_type].Size);
+
+    if (format == NULL)
+        format = GDataTypeInfo[data_type].ScanFmt;
+
+    int arg1i = 0;
+    if (data_type == ImGuiDataType_S32)
+    {
+        int* v = (int*)data_ptr;
+        int arg0i = *v;
+        float arg1f = 0.0f;
+        if (op && sscanf(initial_value_buf, format, &arg0i) < 1)
+            return false;
+        // Store operand in a float so we can use fractional value for multipliers (*1.1), but constant always parsed as integer so we can fit big integers (e.g. 2000000003) past float precision
+        if (op == '+')      { if (sscanf(buf, "%d", &arg1i)) *v = (int)(arg0i + arg1i); }                   // Add (use "+-" to subtract)
+        else if (op == '*') { if (sscanf(buf, "%f", &arg1f)) *v = (int)(arg0i * arg1f); }                   // Multiply
+        else if (op == '/') { if (sscanf(buf, "%f", &arg1f) && arg1f != 0.0f) *v = (int)(arg0i / arg1f); }  // Divide
+        else                { if (sscanf(buf, format, &arg1i) == 1) *v = arg1i; }                           // Assign constant
+    }
+    else if (data_type == ImGuiDataType_U32 || data_type == ImGuiDataType_S64 || data_type == ImGuiDataType_U64)
+    {
+        // Assign constant
+        // FIXME: We don't bother handling support for legacy operators since they are a little too crappy. Instead we may implement a proper expression evaluator in the future.
+        sscanf(buf, format, data_ptr);
+    }
+    else if (data_type == ImGuiDataType_Float)
+    {
+        // For floats we have to ignore format with precision (e.g. "%.2f") because sscanf doesn't take them in
+        format = "%f";
+        float* v = (float*)data_ptr;
+        float arg0f = *v, arg1f = 0.0f;
+        if (op && sscanf(initial_value_buf, format, &arg0f) < 1)
+            return false;
+        if (sscanf(buf, format, &arg1f) < 1)
+            return false;
+        if (op == '+')      { *v = arg0f + arg1f; }                    // Add (use "+-" to subtract)
+        else if (op == '*') { *v = arg0f * arg1f; }                    // Multiply
+        else if (op == '/') { if (arg1f != 0.0f) *v = arg0f / arg1f; } // Divide
+        else                { *v = arg1f; }                            // Assign constant
+    }
+    else if (data_type == ImGuiDataType_Double)
+    {
+        format = "%lf"; // scanf differentiate float/double unlike printf which forces everything to double because of ellipsis
+        double* v = (double*)data_ptr;
+        double arg0f = *v, arg1f = 0.0;
+        if (op && sscanf(initial_value_buf, format, &arg0f) < 1)
+            return false;
+        if (sscanf(buf, format, &arg1f) < 1)
+            return false;
+        if (op == '+')      { *v = arg0f + arg1f; }                    // Add (use "+-" to subtract)
+        else if (op == '*') { *v = arg0f * arg1f; }                    // Multiply
+        else if (op == '/') { if (arg1f != 0.0f) *v = arg0f / arg1f; } // Divide
+        else                { *v = arg1f; }                            // Assign constant
+    }
+    return memcmp(data_backup, data_ptr, GDataTypeInfo[data_type].Size) != 0;
+}
+
+static float GetMinimumStepAtDecimalPrecision(int decimal_precision)
+{
+    static const float min_steps[10] = { 1.0f, 0.1f, 0.01f, 0.001f, 0.0001f, 0.00001f, 0.000001f, 0.0000001f, 0.00000001f, 0.000000001f };
+    if (decimal_precision < 0)
+        return FLT_MIN;
+    return (decimal_precision >= 0 && decimal_precision < 10) ? min_steps[decimal_precision] : ImPow(10.0f, (float)-decimal_precision);
+}
+
+template<typename TYPE>
+static const char* ImAtoi(const char* src, TYPE* output)
+{
+    int negative = 0;
+    if (*src == '-') { negative = 1; src++; }
+    if (*src == '+') { src++; }
+    TYPE v = 0;
+    while (*src >= '0' && *src <= '9')
+        v = (v * 10) + (*src++ - '0');
+    *output = negative ? -v : v;
+    return src;
+}
+
+template<typename TYPE, typename SIGNEDTYPE>
+TYPE ImGui::RoundScalarWithFormatT(const char* format, ImGuiDataType data_type, TYPE v)
+{
+    const char* fmt_start = ImParseFormatFindStart(format);
+    if (fmt_start[0] != '%' || fmt_start[1] == '%') // Don't apply if the value is not visible in the format string
+        return v;
+    char v_str[64];
+    ImFormatString(v_str, IM_ARRAYSIZE(v_str), fmt_start, v);
+    const char* p = v_str;
+    while (*p == ' ')
+        p++;
+    if (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double)
+        v = (TYPE)ImAtof(p);
+    else
+        ImAtoi(p, (SIGNEDTYPE*)&v);
+    return v;
+}
+
+//-------------------------------------------------------------------------
+// [SECTION] Widgets: DragScalar, DragFloat, DragInt, etc.
+//-------------------------------------------------------------------------
+// - DragBehaviorT<>() [Internal]
+// - DragBehavior() [Internal]
+// - DragScalar()
+// - DragScalarN()
+// - DragFloat()
+// - DragFloat2()
+// - DragFloat3()
+// - DragFloat4()
+// - DragFloatRange2()
+// - DragInt()
+// - DragInt2()
+// - DragInt3()
+// - DragInt4()
+// - DragIntRange2()
+//-------------------------------------------------------------------------
+
+// This is called by DragBehavior() when the widget is active (held by mouse or being manipulated with Nav controls)
+template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
+bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const TYPE v_min, const TYPE v_max, const char* format, float power)
+{
+    ImGuiContext& g = *GImGui;
+
+    // Default tweak speed
+    bool has_min_max = (v_min != v_max) && (v_max - v_max < FLT_MAX);
+    if (v_speed == 0.0f && has_min_max)
+        v_speed = (float)((v_max - v_min) * g.DragSpeedDefaultRatio);
+
+    // Inputs accumulates into g.DragCurrentAccum, which is flushed into the current value as soon as it makes a difference with our precision settings
+    float adjust_delta = 0.0f;
+    if (g.ActiveIdSource == ImGuiInputSource_Mouse && IsMousePosValid() && g.IO.MouseDragMaxDistanceSqr[0] > 1.0f*1.0f)
+    {
+        adjust_delta = g.IO.MouseDelta.x;
+        if (g.IO.KeyAlt)
+            adjust_delta *= 1.0f/100.0f;
+        if (g.IO.KeyShift)
+            adjust_delta *= 10.0f;
+    }
+    else if (g.ActiveIdSource == ImGuiInputSource_Nav)
+    {
+        int decimal_precision = (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double) ? ImParseFormatPrecision(format, 3) : 0;
+        adjust_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard|ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_RepeatFast, 1.0f/10.0f, 10.0f).x;
+        v_speed = ImMax(v_speed, GetMinimumStepAtDecimalPrecision(decimal_precision));
+    }
+    adjust_delta *= v_speed;
+
+    // Clear current value on activation
+    // Avoid altering values and clamping when we are _already_ past the limits and heading in the same direction, so e.g. if range is 0..255, current value is 300 and we are pushing to the right side, keep the 300.
+    bool is_just_activated = g.ActiveIdIsJustActivated;
+    bool is_already_past_limits_and_pushing_outward = has_min_max && ((*v >= v_max && adjust_delta > 0.0f) || (*v <= v_min && adjust_delta < 0.0f));
+    if (is_just_activated || is_already_past_limits_and_pushing_outward)
+    {
+        g.DragCurrentAccum = 0.0f;
+        g.DragCurrentAccumDirty = false;
+    }
+    else if (adjust_delta != 0.0f)
+    {
+        g.DragCurrentAccum += adjust_delta;
+        g.DragCurrentAccumDirty = true;
+    }
+
+    if (!g.DragCurrentAccumDirty)
+        return false;
+
+    TYPE v_cur = *v;
+    FLOATTYPE v_old_ref_for_accum_remainder = (FLOATTYPE)0.0f;
+
+    const bool is_power = (power != 1.0f && (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double) && has_min_max);
+    if (is_power)
+    {
+        // Offset + round to user desired precision, with a curve on the v_min..v_max range to get more precision on one side of the range
+        FLOATTYPE v_old_norm_curved = ImPow((FLOATTYPE)(v_cur - v_min) / (FLOATTYPE)(v_max - v_min), (FLOATTYPE)1.0f / power);
+        FLOATTYPE v_new_norm_curved = v_old_norm_curved + (g.DragCurrentAccum / (v_max - v_min));
+        v_cur = v_min + (TYPE)ImPow(ImSaturate((float)v_new_norm_curved), power) * (v_max - v_min);
+        v_old_ref_for_accum_remainder = v_old_norm_curved;
+    }
+    else
+    {
+        v_cur += (TYPE)g.DragCurrentAccum;
+    }
+
+    // Round to user desired precision based on format string
+    v_cur = RoundScalarWithFormatT<TYPE, SIGNEDTYPE>(format, data_type, v_cur);
+
+    // Preserve remainder after rounding has been applied. This also allow slow tweaking of values.
+    g.DragCurrentAccumDirty = false;
+    if (is_power)
+    {
+        FLOATTYPE v_cur_norm_curved = ImPow((FLOATTYPE)(v_cur - v_min) / (FLOATTYPE)(v_max - v_min), (FLOATTYPE)1.0f / power);
+        g.DragCurrentAccum -= (float)(v_cur_norm_curved - v_old_ref_for_accum_remainder);
+    }
+    else
+    {
+        g.DragCurrentAccum -= (float)((SIGNEDTYPE)v_cur - (SIGNEDTYPE)*v);
+    }
+
+    // Lose zero sign for float/double
+    if (v_cur == (TYPE)-0)
+        v_cur = (TYPE)0;
+
+    // Clamp values (handle overflow/wrap-around)
+    if (*v != v_cur && has_min_max)
+    {
+        if (v_cur < v_min || (v_cur > *v && adjust_delta < 0.0f))
+            v_cur = v_min;
+        if (v_cur > v_max || (v_cur < *v && adjust_delta > 0.0f))
+            v_cur = v_max;
+    }
+
+    // Apply result
+    if (*v == v_cur)
+        return false;
+    *v = v_cur;
+    return true;
+}
+
+bool ImGui::DragBehavior(ImGuiID id, ImGuiDataType data_type, void* v, float v_speed, const void* v_min, const void* v_max, const char* format, float power)
+{
+    ImGuiContext& g = *GImGui;
+    if (g.ActiveId == id)
+    {
+        if (g.ActiveIdSource == ImGuiInputSource_Mouse && !g.IO.MouseDown[0])
+            ClearActiveID();
+        else if (g.ActiveIdSource == ImGuiInputSource_Nav && g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated)
+            ClearActiveID();
+    }
+    if (g.ActiveId != id)
+        return false;
+
+    switch (data_type)
+    {
+    case ImGuiDataType_S32:    return DragBehaviorT<ImS32, ImS32, float >(data_type, (ImS32*)v,  v_speed, v_min ? *(const ImS32* )v_min : IM_S32_MIN, v_max ? *(const ImS32* )v_max : IM_S32_MAX, format, power);
+    case ImGuiDataType_U32:    return DragBehaviorT<ImU32, ImS32, float >(data_type, (ImU32*)v,  v_speed, v_min ? *(const ImU32* )v_min : IM_U32_MIN, v_max ? *(const ImU32* )v_max : IM_U32_MAX, format, power);
+    case ImGuiDataType_S64:    return DragBehaviorT<ImS64, ImS64, double>(data_type, (ImS64*)v,  v_speed, v_min ? *(const ImS64* )v_min : IM_S64_MIN, v_max ? *(const ImS64* )v_max : IM_S64_MAX, format, power);
+    case ImGuiDataType_U64:    return DragBehaviorT<ImU64, ImS64, double>(data_type, (ImU64*)v,  v_speed, v_min ? *(const ImU64* )v_min : IM_U64_MIN, v_max ? *(const ImU64* )v_max : IM_U64_MAX, format, power);
+    case ImGuiDataType_Float:  return DragBehaviorT<float, float, float >(data_type, (float*)v,  v_speed, v_min ? *(const float* )v_min : -FLT_MAX,   v_max ? *(const float* )v_max : FLT_MAX,    format, power);
+    case ImGuiDataType_Double: return DragBehaviorT<double,double,double>(data_type, (double*)v, v_speed, v_min ? *(const double*)v_min : -DBL_MAX,   v_max ? *(const double*)v_max : DBL_MAX,    format, power);
+    case ImGuiDataType_COUNT:  break;
+    }
+    IM_ASSERT(0);
+    return false;
+}
+
+bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* v, float v_speed, const void* v_min, const void* v_max, const char* format, float power)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    if (window->SkipItems)
+        return false;
+
+    if (power != 1.0f)
+        IM_ASSERT(v_min != NULL && v_max != NULL); // When using a power curve the drag needs to have known bounds
+
+    ImGuiContext& g = *GImGui;
+    const ImGuiStyle& style = g.Style;
+    const ImGuiID id = window->GetID(label);
+    const float w = CalcItemWidth();
+
+    const ImVec2 label_size = CalcTextSize(label, NULL, true);
+    const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f));
+    const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding);
+    const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
+
+    // NB- we don't call ItemSize() yet because we may turn into a text edit box below
+    if (!ItemAdd(total_bb, id, &frame_bb))
+    {
+        ItemSize(total_bb, style.FramePadding.y);
+        return false;
+    }
+    const bool hovered = ItemHoverable(frame_bb, id);
+
+    // Default format string when passing NULL
+    // Patch old "%.0f" format string to use "%d", read function comments for more details.
+    IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
+    if (format == NULL)
+        format = GDataTypeInfo[data_type].PrintFmt;
+    else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0)
+        format = PatchFormatStringFloatToInt(format);
+
+    // Tabbing or CTRL-clicking on Drag turns it into an input box
+    bool start_text_input = false;
+    const bool tab_focus_requested = FocusableItemRegister(window, id);
+    if (tab_focus_requested || (hovered && (g.IO.MouseClicked[0] || g.IO.MouseDoubleClicked[0])) || g.NavActivateId == id || (g.NavInputId == id && g.ScalarAsInputTextId != id))
+    {
+        SetActiveID(id, window);
+        SetFocusID(id, window);
+        FocusWindow(window);
+        g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
+        if (tab_focus_requested || g.IO.KeyCtrl || g.IO.MouseDoubleClicked[0] || g.NavInputId == id)
+        {
+            start_text_input = true;
+            g.ScalarAsInputTextId = 0;
+        }
+    }
+    if (start_text_input || (g.ActiveId == id && g.ScalarAsInputTextId == id))
+        return InputScalarAsWidgetReplacement(frame_bb, id, label, data_type, v, format);
+
+    // Actual drag behavior
+    ItemSize(total_bb, style.FramePadding.y);
+    const bool value_changed = DragBehavior(id, data_type, v, v_speed, v_min, v_max, format, power);
+    if (value_changed)
+        MarkItemEdited(id);
+
+    // Draw frame
+    const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
+    RenderNavHighlight(frame_bb, id);
+    RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, style.FrameRounding);
+
+    // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
+    char value_buf[64];
+    const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, v, format);
+    RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f));
+
+    if (label_size.x > 0.0f)
+        RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label);
+
+    return value_changed;
+}
+
+bool ImGui::DragScalarN(const char* label, ImGuiDataType data_type, void* v, int components, float v_speed, const void* v_min, const void* v_max, const char* format, float power)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    if (window->SkipItems)
+        return false;
+
+    ImGuiContext& g = *GImGui;
+    bool value_changed = false;
+    BeginGroup();
+    PushID(label);
+    PushMultiItemsWidths(components);
+    size_t type_size = GDataTypeInfo[data_type].Size;
+    for (int i = 0; i < components; i++)
+    {
+        PushID(i);
+        value_changed |= DragScalar("##v", data_type, v, v_speed, v_min, v_max, format, power);
+        SameLine(0, g.Style.ItemInnerSpacing.x);
+        PopID();
+        PopItemWidth();
+        v = (void*)((char*)v + type_size);
+    }
+    PopID();
+
+    TextUnformatted(label, FindRenderedTextEnd(label));
+    EndGroup();
+    return value_changed;
+}
+
+bool ImGui::DragFloat(const char* label, float* v, float v_speed, float v_min, float v_max, const char* format, float power)
+{
+    return DragScalar(label, ImGuiDataType_Float, v, v_speed, &v_min, &v_max, format, power);
+}
+
+bool ImGui::DragFloat2(const char* label, float v[2], float v_speed, float v_min, float v_max, const char* format, float power)
+{
+    return DragScalarN(label, ImGuiDataType_Float, v, 2, v_speed, &v_min, &v_max, format, power);
+}
+
+bool ImGui::DragFloat3(const char* label, float v[3], float v_speed, float v_min, float v_max, const char* format, float power)
+{
+    return DragScalarN(label, ImGuiDataType_Float, v, 3, v_speed, &v_min, &v_max, format, power);
+}
+
+bool ImGui::DragFloat4(const char* label, float v[4], float v_speed, float v_min, float v_max, const char* format, float power)
+{
+    return DragScalarN(label, ImGuiDataType_Float, v, 4, v_speed, &v_min, &v_max, format, power);
+}
+
+bool ImGui::DragFloatRange2(const char* label, float* v_current_min, float* v_current_max, float v_speed, float v_min, float v_max, const char* format, const char* format_max, float power)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    if (window->SkipItems)
+        return false;
+
+    ImGuiContext& g = *GImGui;
+    PushID(label);
+    BeginGroup();
+    PushMultiItemsWidths(2);
+
+    bool value_changed = DragFloat("##min", v_current_min, v_speed, (v_min >= v_max) ? -FLT_MAX : v_min, (v_min >= v_max) ? *v_current_max : ImMin(v_max, *v_current_max), format, power);
+    PopItemWidth();
+    SameLine(0, g.Style.ItemInnerSpacing.x);
+    value_changed |= DragFloat("##max", v_current_max, v_speed, (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min), (v_min >= v_max) ? FLT_MAX : v_max, format_max ? format_max : format, power);
+    PopItemWidth();
+    SameLine(0, g.Style.ItemInnerSpacing.x);
+
+    TextUnformatted(label, FindRenderedTextEnd(label));
+    EndGroup();
+    PopID();
+    return value_changed;
+}
+
+// NB: v_speed is float to allow adjusting the drag speed with more precision
+bool ImGui::DragInt(const char* label, int* v, float v_speed, int v_min, int v_max, const char* format)
+{
+    return DragScalar(label, ImGuiDataType_S32, v, v_speed, &v_min, &v_max, format);
+}
+
+bool ImGui::DragInt2(const char* label, int v[2], float v_speed, int v_min, int v_max, const char* format)
+{
+    return DragScalarN(label, ImGuiDataType_S32, v, 2, v_speed, &v_min, &v_max, format);
+}
+
+bool ImGui::DragInt3(const char* label, int v[3], float v_speed, int v_min, int v_max, const char* format)
+{
+    return DragScalarN(label, ImGuiDataType_S32, v, 3, v_speed, &v_min, &v_max, format);
+}
+
+bool ImGui::DragInt4(const char* label, int v[4], float v_speed, int v_min, int v_max, const char* format)
+{
+    return DragScalarN(label, ImGuiDataType_S32, v, 4, v_speed, &v_min, &v_max, format);
+}
+
+bool ImGui::DragIntRange2(const char* label, int* v_current_min, int* v_current_max, float v_speed, int v_min, int v_max, const char* format, const char* format_max)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    if (window->SkipItems)
+        return false;
+
+    ImGuiContext& g = *GImGui;
+    PushID(label);
+    BeginGroup();
+    PushMultiItemsWidths(2);
+
+    bool value_changed = DragInt("##min", v_current_min, v_speed, (v_min >= v_max) ? INT_MIN : v_min, (v_min >= v_max) ? *v_current_max : ImMin(v_max, *v_current_max), format);
+    PopItemWidth();
+    SameLine(0, g.Style.ItemInnerSpacing.x);
+    value_changed |= DragInt("##max", v_current_max, v_speed, (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min), (v_min >= v_max) ? INT_MAX : v_max, format_max ? format_max : format);
+    PopItemWidth();
+    SameLine(0, g.Style.ItemInnerSpacing.x);
+
+    TextUnformatted(label, FindRenderedTextEnd(label));
+    EndGroup();
+    PopID();
+
+    return value_changed;
+}
+
+//-------------------------------------------------------------------------
+// [SECTION] Widgets: SliderScalar, SliderFloat, SliderInt, etc.
+//-------------------------------------------------------------------------
+// - SliderBehaviorT<>() [Internal]
+// - SliderBehavior() [Internal]
+// - SliderScalar()
+// - SliderScalarN()
+// - SliderFloat()
+// - SliderFloat2()
+// - SliderFloat3()
+// - SliderFloat4()
+// - SliderAngle()
+// - SliderInt()
+// - SliderInt2()
+// - SliderInt3()
+// - SliderInt4()
+// - VSliderScalar()
+// - VSliderFloat()
+// - VSliderInt()
+//-------------------------------------------------------------------------
+
+template<typename TYPE, typename FLOATTYPE>
+float ImGui::SliderCalcRatioFromValueT(ImGuiDataType data_type, TYPE v, TYPE v_min, TYPE v_max, float power, float linear_zero_pos)
+{
+    if (v_min == v_max)
+        return 0.0f;
+
+    const bool is_power = (power != 1.0f) && (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double);
+    const TYPE v_clamped = (v_min < v_max) ? ImClamp(v, v_min, v_max) : ImClamp(v, v_max, v_min);
+    if (is_power)
+    {
+        if (v_clamped < 0.0f)
+        {
+            const float f = 1.0f - (float)((v_clamped - v_min) / (ImMin((TYPE)0, v_max) - v_min));
+            return (1.0f - ImPow(f, 1.0f/power)) * linear_zero_pos;
+        }
+        else
+        {
+            const float f = (float)((v_clamped - ImMax((TYPE)0, v_min)) / (v_max - ImMax((TYPE)0, v_min)));
+            return linear_zero_pos + ImPow(f, 1.0f/power) * (1.0f - linear_zero_pos);
+        }
+    }
+
+    // Linear slider
+    return (float)((FLOATTYPE)(v_clamped - v_min) / (FLOATTYPE)(v_max - v_min));
+}
+
+// FIXME: Move some of the code into SliderBehavior(). Current responsability is larger than what the equivalent DragBehaviorT<> does, we also do some rendering, etc.
+template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
+bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, TYPE* v, const TYPE v_min, const TYPE v_max, const char* format, float power, ImGuiSliderFlags flags, ImRect* out_grab_bb)
+{
+    ImGuiContext& g = *GImGui;
+    const ImGuiStyle& style = g.Style;
+
+    const bool is_horizontal = (flags & ImGuiSliderFlags_Vertical) == 0;
+    const bool is_decimal = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double);
+    const bool is_power = (power != 1.0f) && is_decimal;
+
+    const float grab_padding = 2.0f;
+    const float slider_sz = is_horizontal ? (bb.GetWidth() - grab_padding * 2.0f) : (bb.GetHeight() - grab_padding * 2.0f);
+    float grab_sz = style.GrabMinSize;
+    SIGNEDTYPE v_range = (v_min < v_max ? v_max - v_min : v_min - v_max);
+    if (!is_decimal && v_range >= 0)                                             // v_range < 0 may happen on integer overflows
+        grab_sz = ImMax((float)(slider_sz / (v_range + 1)), style.GrabMinSize);  // For integer sliders: if possible have the grab size represent 1 unit
+    grab_sz = ImMin(grab_sz, slider_sz);
+    const float slider_usable_sz = slider_sz - grab_sz;
+    const float slider_usable_pos_min = (is_horizontal ? bb.Min.x : bb.Min.y) + grab_padding + grab_sz*0.5f;
+    const float slider_usable_pos_max = (is_horizontal ? bb.Max.x : bb.Max.y) - grab_padding - grab_sz*0.5f;
+
+    // For power curve sliders that cross over sign boundary we want the curve to be symmetric around 0.0f
+    float linear_zero_pos;   // 0.0->1.0f
+    if (is_power && v_min * v_max < 0.0f)
+    {
+        // Different sign
+        const FLOATTYPE linear_dist_min_to_0 = ImPow(v_min >= 0 ? (FLOATTYPE)v_min : -(FLOATTYPE)v_min, (FLOATTYPE)1.0f/power);
+        const FLOATTYPE linear_dist_max_to_0 = ImPow(v_max >= 0 ? (FLOATTYPE)v_max : -(FLOATTYPE)v_max, (FLOATTYPE)1.0f/power);
+        linear_zero_pos = (float)(linear_dist_min_to_0 / (linear_dist_min_to_0 + linear_dist_max_to_0));
+    }
+    else
+    {
+        // Same sign
+        linear_zero_pos = v_min < 0.0f ? 1.0f : 0.0f;
+    }
+
+    // Process interacting with the slider
+    bool value_changed = false;
+    if (g.ActiveId == id)
+    {
+        bool set_new_value = false;
+        float clicked_t = 0.0f;
+        if (g.ActiveIdSource == ImGuiInputSource_Mouse)
+        {
+            if (!g.IO.MouseDown[0])
+            {
+                ClearActiveID();
+            }
+            else
+            {
+                const float mouse_abs_pos = is_horizontal ? g.IO.MousePos.x : g.IO.MousePos.y;
+                clicked_t = (slider_usable_sz > 0.0f) ? ImClamp((mouse_abs_pos - slider_usable_pos_min) / slider_usable_sz, 0.0f, 1.0f) : 0.0f;
+                if (!is_horizontal)
+                    clicked_t = 1.0f - clicked_t;
+                set_new_value = true;
+            }
+        }
+        else if (g.ActiveIdSource == ImGuiInputSource_Nav)
+        {
+            const ImVec2 delta2 = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard | ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_RepeatFast, 0.0f, 0.0f);
+            float delta = is_horizontal ? delta2.x : -delta2.y;
+            if (g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated)
+            {
+                ClearActiveID();
+            }
+            else if (delta != 0.0f)
+            {
+                clicked_t = SliderCalcRatioFromValueT<TYPE,FLOATTYPE>(data_type, *v, v_min, v_max, power, linear_zero_pos);
+                const int decimal_precision = is_decimal ? ImParseFormatPrecision(format, 3) : 0;
+                if ((decimal_precision > 0) || is_power)
+                {
+                    delta /= 100.0f;    // Gamepad/keyboard tweak speeds in % of slider bounds
+                    if (IsNavInputDown(ImGuiNavInput_TweakSlow))
+                        delta /= 10.0f;
+                }
+                else
+                {
+                    if ((v_range >= -100.0f && v_range <= 100.0f) || IsNavInputDown(ImGuiNavInput_TweakSlow))
+                        delta = ((delta < 0.0f) ? -1.0f : +1.0f) / (float)v_range; // Gamepad/keyboard tweak speeds in integer steps
+                    else
+                        delta /= 100.0f;
+                }
+                if (IsNavInputDown(ImGuiNavInput_TweakFast))
+                    delta *= 10.0f;
+                set_new_value = true;
+                if ((clicked_t >= 1.0f && delta > 0.0f) || (clicked_t <= 0.0f && delta < 0.0f)) // This is to avoid applying the saturation when already past the limits
+                    set_new_value = false;
+                else
+                    clicked_t = ImSaturate(clicked_t + delta);
+            }
+        }
+
+        if (set_new_value)
+        {
+            TYPE v_new;
+            if (is_power)
+            {
+                // Account for power curve scale on both sides of the zero
+                if (clicked_t < linear_zero_pos)
+                {
+                    // Negative: rescale to the negative range before powering
+                    float a = 1.0f - (clicked_t / linear_zero_pos);
+                    a = ImPow(a, power);
+                    v_new = ImLerp(ImMin(v_max, (TYPE)0), v_min, a);
+                }
+                else
+                {
+                    // Positive: rescale to the positive range before powering
+                    float a;
+                    if (ImFabs(linear_zero_pos - 1.0f) > 1.e-6f)
+                        a = (clicked_t - linear_zero_pos) / (1.0f - linear_zero_pos);
+                    else
+                        a = clicked_t;
+                    a = ImPow(a, power);
+                    v_new = ImLerp(ImMax(v_min, (TYPE)0), v_max, a);
+                }
+            }
+            else
+            {
+                // Linear slider
+                if (is_decimal)
+                {
+                    v_new = ImLerp(v_min, v_max, clicked_t);
+                }
+                else
+                {
+                    // For integer values we want the clicking position to match the grab box so we round above
+                    // This code is carefully tuned to work with large values (e.g. high ranges of U64) while preserving this property..
+                    FLOATTYPE v_new_off_f = (v_max - v_min) * clicked_t;
+                    TYPE v_new_off_floor = (TYPE)(v_new_off_f);
+                    TYPE v_new_off_round = (TYPE)(v_new_off_f + (FLOATTYPE)0.5);
+                    if (!is_decimal && v_new_off_floor < v_new_off_round)
+                        v_new = v_min + v_new_off_round;
+                    else
+                        v_new = v_min + v_new_off_floor;
+                }
+            }
+
+            // Round to user desired precision based on format string
+            v_new = RoundScalarWithFormatT<TYPE,SIGNEDTYPE>(format, data_type, v_new);
+
+            // Apply result
+            if (*v != v_new)
+            {
+                *v = v_new;
+                value_changed = true;
+            }
+        }
+    }
+
+    // Output grab position so it can be displayed by the caller
+    float grab_t = SliderCalcRatioFromValueT<TYPE,FLOATTYPE>(data_type, *v, v_min, v_max, power, linear_zero_pos);
+    if (!is_horizontal)
+        grab_t = 1.0f - grab_t;
+    const float grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t);
+    if (is_horizontal)
+        *out_grab_bb = ImRect(grab_pos - grab_sz*0.5f, bb.Min.y + grab_padding, grab_pos + grab_sz*0.5f, bb.Max.y - grab_padding);
+    else
+        *out_grab_bb = ImRect(bb.Min.x + grab_padding, grab_pos - grab_sz*0.5f, bb.Max.x - grab_padding, grab_pos + grab_sz*0.5f);
+
+    return value_changed;
+}
+
+// For 32-bits and larger types, slider bounds are limited to half the natural type range.
+// So e.g. an integer Slider between INT_MAX-10 and INT_MAX will fail, but an integer Slider between INT_MAX/2-10 and INT_MAX/2 will be ok.
+// It would be possible to lift that limitation with some work but it doesn't seem to be worth it for sliders.
+bool ImGui::SliderBehavior(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, void* v, const void* v_min, const void* v_max, const char* format, float power, ImGuiSliderFlags flags, ImRect* out_grab_bb)
+{
+    switch (data_type)
+    {
+    case ImGuiDataType_S32:
+        IM_ASSERT(*(const ImS32*)v_min >= IM_S32_MIN/2 && *(const ImS32*)v_max <= IM_S32_MAX/2);
+        return SliderBehaviorT<ImS32, ImS32, float >(bb, id, data_type, (ImS32*)v,  *(const ImS32*)v_min,  *(const ImS32*)v_max,  format, power, flags, out_grab_bb);
+    case ImGuiDataType_U32:
+        IM_ASSERT(*(const ImU32*)v_min <= IM_U32_MAX/2);
+        return SliderBehaviorT<ImU32, ImS32, float >(bb, id, data_type, (ImU32*)v,  *(const ImU32*)v_min,  *(const ImU32*)v_max,  format, power, flags, out_grab_bb);
+    case ImGuiDataType_S64:
+        IM_ASSERT(*(const ImS64*)v_min >= IM_S64_MIN/2 && *(const ImS64*)v_max <= IM_S64_MAX/2);
+        return SliderBehaviorT<ImS64, ImS64, double>(bb, id, data_type, (ImS64*)v,  *(const ImS64*)v_min,  *(const ImS64*)v_max,  format, power, flags, out_grab_bb);
+    case ImGuiDataType_U64:
+        IM_ASSERT(*(const ImU64*)v_min <= IM_U64_MAX/2);
+        return SliderBehaviorT<ImU64, ImS64, double>(bb, id, data_type, (ImU64*)v,  *(const ImU64*)v_min,  *(const ImU64*)v_max,  format, power, flags, out_grab_bb);
+    case ImGuiDataType_Float:
+        IM_ASSERT(*(const float*)v_min >= -FLT_MAX/2.0f && *(const float*)v_max <= FLT_MAX/2.0f);
+        return SliderBehaviorT<float, float, float >(bb, id, data_type, (float*)v,  *(const float*)v_min,  *(const float*)v_max,  format, power, flags, out_grab_bb);
+    case ImGuiDataType_Double:
+        IM_ASSERT(*(const double*)v_min >= -DBL_MAX/2.0f && *(const double*)v_max <= DBL_MAX/2.0f);
+        return SliderBehaviorT<double,double,double>(bb, id, data_type, (double*)v, *(const double*)v_min, *(const double*)v_max, format, power, flags, out_grab_bb);
+    case ImGuiDataType_COUNT: break;
+    }
+    IM_ASSERT(0);
+    return false;
+}
+
+bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* v, const void* v_min, const void* v_max, const char* format, float power)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    if (window->SkipItems)
+        return false;
+
+    ImGuiContext& g = *GImGui;
+    const ImGuiStyle& style = g.Style;
+    const ImGuiID id = window->GetID(label);
+    const float w = CalcItemWidth();
+
+    const ImVec2 label_size = CalcTextSize(label, NULL, true);
+    const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f));
+    const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
+
+    // NB- we don't call ItemSize() yet because we may turn into a text edit box below
+    if (!ItemAdd(total_bb, id, &frame_bb))
+    {
+        ItemSize(total_bb, style.FramePadding.y);
+        return false;
+    }
+
+    // Default format string when passing NULL
+    // Patch old "%.0f" format string to use "%d", read function comments for more details.
+    IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
+    if (format == NULL)
+        format = GDataTypeInfo[data_type].PrintFmt;
+    else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0)
+        format = PatchFormatStringFloatToInt(format);
+
+    // Tabbing or CTRL-clicking on Slider turns it into an input box
+    bool start_text_input = false;
+    const bool tab_focus_requested = FocusableItemRegister(window, id);
+    const bool hovered = ItemHoverable(frame_bb, id);
+    if (tab_focus_requested || (hovered && g.IO.MouseClicked[0]) || g.NavActivateId == id || (g.NavInputId == id && g.ScalarAsInputTextId != id))
+    {
+        SetActiveID(id, window);
+        SetFocusID(id, window);
+        FocusWindow(window);
+        g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
+        if (tab_focus_requested || g.IO.KeyCtrl || g.NavInputId == id)
+        {
+            start_text_input = true;
+            g.ScalarAsInputTextId = 0;
+        }
+    }
+    if (start_text_input || (g.ActiveId == id && g.ScalarAsInputTextId == id))
+        return InputScalarAsWidgetReplacement(frame_bb, id, label, data_type, v, format);
+
+    ItemSize(total_bb, style.FramePadding.y);
+
+    // Draw frame
+    const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
+    RenderNavHighlight(frame_bb, id);
+    RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding);
+
+    // Slider behavior
+    ImRect grab_bb;
+    const bool value_changed = SliderBehavior(frame_bb, id, data_type, v, v_min, v_max, format, power, ImGuiSliderFlags_None, &grab_bb);
+    if (value_changed)
+        MarkItemEdited(id);
+
+    // Render grab
+    window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding);
+
+    // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
+    char value_buf[64];
+    const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, v, format);
+    RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f,0.5f));
+
+    if (label_size.x > 0.0f)
+        RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
+
+    return value_changed;
+}
+
+// Add multiple sliders on 1 line for compact edition of multiple components
+bool ImGui::SliderScalarN(const char* label, ImGuiDataType data_type, void* v, int components, const void* v_min, const void* v_max, const char* format, float power)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    if (window->SkipItems)
+        return false;
+
+    ImGuiContext& g = *GImGui;
+    bool value_changed = false;
+    BeginGroup();
+    PushID(label);
+    PushMultiItemsWidths(components);
+    size_t type_size = GDataTypeInfo[data_type].Size;
+    for (int i = 0; i < components; i++)
+    {
+        PushID(i);
+        value_changed |= SliderScalar("##v", data_type, v, v_min, v_max, format, power);
+        SameLine(0, g.Style.ItemInnerSpacing.x);
+        PopID();
+        PopItemWidth();
+        v = (void*)((char*)v + type_size);
+    }
+    PopID();
+
+    TextUnformatted(label, FindRenderedTextEnd(label));
+    EndGroup();
+    return value_changed;
+}
+
+bool ImGui::SliderFloat(const char* label, float* v, float v_min, float v_max, const char* format, float power)
+{
+    return SliderScalar(label, ImGuiDataType_Float, v, &v_min, &v_max, format, power);
+}
+
+bool ImGui::SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* format, float power)
+{
+    return SliderScalarN(label, ImGuiDataType_Float, v, 2, &v_min, &v_max, format, power);
+}
+
+bool ImGui::SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* format, float power)
+{
+    return SliderScalarN(label, ImGuiDataType_Float, v, 3, &v_min, &v_max, format, power);
+}
+
+bool ImGui::SliderFloat4(const char* label, float v[4], float v_min, float v_max, const char* format, float power)
+{
+    return SliderScalarN(label, ImGuiDataType_Float, v, 4, &v_min, &v_max, format, power);
+}
+
+bool ImGui::SliderAngle(const char* label, float* v_rad, float v_degrees_min, float v_degrees_max)
+{
+    float v_deg = (*v_rad) * 360.0f / (2*IM_PI);
+    bool value_changed = SliderFloat(label, &v_deg, v_degrees_min, v_degrees_max, "%.0f deg", 1.0f);
+    *v_rad = v_deg * (2*IM_PI) / 360.0f;
+    return value_changed;
+}
+
+bool ImGui::SliderInt(const char* label, int* v, int v_min, int v_max, const char* format)
+{
+    return SliderScalar(label, ImGuiDataType_S32, v, &v_min, &v_max, format);
+}
+
+bool ImGui::SliderInt2(const char* label, int v[2], int v_min, int v_max, const char* format)
+{
+    return SliderScalarN(label, ImGuiDataType_S32, v, 2, &v_min, &v_max, format);
+}
+
+bool ImGui::SliderInt3(const char* label, int v[3], int v_min, int v_max, const char* format)
+{
+    return SliderScalarN(label, ImGuiDataType_S32, v, 3, &v_min, &v_max, format);
+}
+
+bool ImGui::SliderInt4(const char* label, int v[4], int v_min, int v_max, const char* format)
+{
+    return SliderScalarN(label, ImGuiDataType_S32, v, 4, &v_min, &v_max, format);
+}
+
+bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType data_type, void* v, const void* v_min, const void* v_max, const char* format, float power)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    if (window->SkipItems)
+        return false;
+
+    ImGuiContext& g = *GImGui;
+    const ImGuiStyle& style = g.Style;
+    const ImGuiID id = window->GetID(label);
+
+    const ImVec2 label_size = CalcTextSize(label, NULL, true);
+    const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size);
+    const ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
+
+    ItemSize(bb, style.FramePadding.y);
+    if (!ItemAdd(frame_bb, id))
+        return false;
+
+    // Default format string when passing NULL
+    // Patch old "%.0f" format string to use "%d", read function comments for more details.
+    IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
+    if (format == NULL)
+        format = GDataTypeInfo[data_type].PrintFmt;
+    else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0)
+        format = PatchFormatStringFloatToInt(format);
+
+    const bool hovered = ItemHoverable(frame_bb, id);
+    if ((hovered && g.IO.MouseClicked[0]) || g.NavActivateId == id || g.NavInputId == id)
+    {
+        SetActiveID(id, window);
+        SetFocusID(id, window);
+        FocusWindow(window);
+        g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
+    }
+
+    // Draw frame
+    const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
+    RenderNavHighlight(frame_bb, id);
+    RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding);
+
+    // Slider behavior
+    ImRect grab_bb;
+    const bool value_changed = SliderBehavior(frame_bb, id, data_type, v, v_min, v_max, format, power, ImGuiSliderFlags_Vertical, &grab_bb);
+    if (value_changed)
+        MarkItemEdited(id);
+
+    // Render grab
+    window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding);
+
+    // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
+    // For the vertical slider we allow centered text to overlap the frame padding
+    char value_buf[64];
+    const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, v, format);
+    RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f,0.0f));
+    if (label_size.x > 0.0f)
+        RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
+
+    return value_changed;
+}
+
+bool ImGui::VSliderFloat(const char* label, const ImVec2& size, float* v, float v_min, float v_max, const char* format, float power)
+{
+    return VSliderScalar(label, size, ImGuiDataType_Float, v, &v_min, &v_max, format, power);
+}
+
+bool ImGui::VSliderInt(const char* label, const ImVec2& size, int* v, int v_min, int v_max, const char* format)
+{
+    return VSliderScalar(label, size, ImGuiDataType_S32, v, &v_min, &v_max, format);
+}
+
+//-------------------------------------------------------------------------
+// [SECTION] Widgets: InputScalar, InputFloat, InputInt, etc.
+//-------------------------------------------------------------------------
+// - ImParseFormatFindStart() [Internal]
+// - ImParseFormatFindEnd() [Internal]
+// - ImParseFormatTrimDecorations() [Internal]
+// - ImParseFormatPrecision() [Internal]
+// - InputScalarAsWidgetReplacement() [Internal]
+// - InputScalar()
+// - InputScalarN()
+// - InputFloat()
+// - InputFloat2()
+// - InputFloat3()
+// - InputFloat4()
+// - InputInt()
+// - InputInt2()
+// - InputInt3()
+// - InputInt4()
+// - InputDouble()
+//-------------------------------------------------------------------------
+
+// We don't use strchr() because our strings are usually very short and often start with '%'
+const char* ImParseFormatFindStart(const char* fmt)
+{
+    while (char c = fmt[0])
+    {
+        if (c == '%' && fmt[1] != '%')
+            return fmt;
+        else if (c == '%')
+            fmt++;
+        fmt++;
+    }
+    return fmt;
+}
+
+const char* ImParseFormatFindEnd(const char* fmt)
+{
+    // Printf/scanf types modifiers: I/L/h/j/l/t/w/z. Other uppercase letters qualify as types aka end of the format.
+    if (fmt[0] != '%')
+        return fmt;
+    const unsigned int ignored_uppercase_mask = (1 << ('I'-'A')) | (1 << ('L'-'A'));
+    const unsigned int ignored_lowercase_mask = (1 << ('h'-'a')) | (1 << ('j'-'a')) | (1 << ('l'-'a')) | (1 << ('t'-'a')) | (1 << ('w'-'a')) | (1 << ('z'-'a'));
+    for (char c; (c = *fmt) != 0; fmt++)
+    {
+        if (c >= 'A' && c <= 'Z' && ((1 << (c - 'A')) & ignored_uppercase_mask) == 0)
+            return fmt + 1;
+        if (c >= 'a' && c <= 'z' && ((1 << (c - 'a')) & ignored_lowercase_mask) == 0)
+            return fmt + 1;
+    }
+    return fmt;
+}
+
+// Extract the format out of a format string with leading or trailing decorations
+//  fmt = "blah blah"  -> return fmt
+//  fmt = "%.3f"       -> return fmt
+//  fmt = "hello %.3f" -> return fmt + 6
+//  fmt = "%.3f hello" -> return buf written with "%.3f"
+const char* ImParseFormatTrimDecorations(const char* fmt, char* buf, int buf_size)
+{
+    const char* fmt_start = ImParseFormatFindStart(fmt);
+    if (fmt_start[0] != '%')
+        return fmt;
+    const char* fmt_end = ImParseFormatFindEnd(fmt_start);
+    if (fmt_end[0] == 0) // If we only have leading decoration, we don't need to copy the data.
+        return fmt_start;
+    ImStrncpy(buf, fmt_start, ImMin((int)(fmt_end + 1 - fmt_start), buf_size));
+    return buf;
+}
+
+// Parse display precision back from the display format string
+// FIXME: This is still used by some navigation code path to infer a minimum tweak step, but we should aim to rework widgets so it isn't needed.
+int ImParseFormatPrecision(const char* fmt, int default_precision)
+{
+    fmt = ImParseFormatFindStart(fmt);
+    if (fmt[0] != '%')
+        return default_precision;
+    fmt++;
+    while (*fmt >= '0' && *fmt <= '9')
+        fmt++;
+    int precision = INT_MAX;
+    if (*fmt == '.')
+    {
+        fmt = ImAtoi<int>(fmt + 1, &precision);
+        if (precision < 0 || precision > 99)
+            precision = default_precision;
+    }
+    if (*fmt == 'e' || *fmt == 'E') // Maximum precision with scientific notation
+        precision = -1;
+    if ((*fmt == 'g' || *fmt == 'G') && precision == INT_MAX)
+        precision = -1;
+    return (precision == INT_MAX) ? default_precision : precision;
+}
+
+// Create text input in place of a slider (when CTRL+Clicking on slider)
+// FIXME: Logic is messy and confusing.
+bool ImGui::InputScalarAsWidgetReplacement(const ImRect& bb, ImGuiID id, const char* label, ImGuiDataType data_type, void* data_ptr, const char* format)
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiWindow* window = GetCurrentWindow();
+
+    // Our replacement widget will override the focus ID (registered previously to allow for a TAB focus to happen)
+    // On the first frame, g.ScalarAsInputTextId == 0, then on subsequent frames it becomes == id
+    SetActiveID(g.ScalarAsInputTextId, window);
+    g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
+    SetHoveredID(0);
+    FocusableItemUnregister(window);
+
+    char fmt_buf[32];
+    char data_buf[32];
+    format = ImParseFormatTrimDecorations(format, fmt_buf, IM_ARRAYSIZE(fmt_buf));
+    DataTypeFormatString(data_buf, IM_ARRAYSIZE(data_buf), data_type, data_ptr, format);
+    ImStrTrimBlanks(data_buf);
+    ImGuiInputTextFlags flags = ImGuiInputTextFlags_AutoSelectAll | ((data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double) ? ImGuiInputTextFlags_CharsScientific : ImGuiInputTextFlags_CharsDecimal);
+    bool value_changed = InputTextEx(label, data_buf, IM_ARRAYSIZE(data_buf), bb.GetSize(), flags);
+    if (g.ScalarAsInputTextId == 0)     // First frame we started displaying the InputText widget
+    {
+        IM_ASSERT(g.ActiveId == id);    // InputText ID expected to match the Slider ID
+        g.ScalarAsInputTextId = g.ActiveId;
+        SetHoveredID(id);
+    }
+    if (value_changed)
+        return DataTypeApplyOpFromText(data_buf, g.InputTextState.InitialText.Data, data_type, data_ptr, NULL);
+    return false;
+}
+
+// NB: format here must be a simple "%xx" format string with no prefix/suffix (unlike the Drag/Slider functions "format" argument)
+bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* data_ptr, const void* step, const void* step_fast, const char* format, ImGuiInputTextFlags extra_flags)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    if (window->SkipItems)
+        return false;
+
+    ImGuiContext& g = *GImGui;
+    const ImGuiStyle& style = g.Style;
+
+    IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
+    if (format == NULL)
+        format = GDataTypeInfo[data_type].PrintFmt;
+
+    char buf[64];
+    DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, data_ptr, format);
+
+    bool value_changed = false;
+    if ((extra_flags & (ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsScientific)) == 0)
+        extra_flags |= ImGuiInputTextFlags_CharsDecimal;
+    extra_flags |= ImGuiInputTextFlags_AutoSelectAll;
+
+    if (step != NULL)
+    {
+        const float button_size = GetFrameHeight();
+
+        BeginGroup(); // The only purpose of the group here is to allow the caller to query item data e.g. IsItemActive()
+        PushID(label);
+        PushItemWidth(ImMax(1.0f, CalcItemWidth() - (button_size + style.ItemInnerSpacing.x) * 2));
+        if (InputText("", buf, IM_ARRAYSIZE(buf), extra_flags)) // PushId(label) + "" gives us the expected ID from outside point of view
+            value_changed = DataTypeApplyOpFromText(buf, g.InputTextState.InitialText.Data, data_type, data_ptr, format);
+        PopItemWidth();
+
+        // Step buttons
+        SameLine(0, style.ItemInnerSpacing.x);
+        if (ButtonEx("-", ImVec2(button_size, button_size), ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups))
+        {
+            DataTypeApplyOp(data_type, '-', data_ptr, data_ptr, g.IO.KeyCtrl && step_fast ? step_fast : step);
+            value_changed = true;
+        }
+        SameLine(0, style.ItemInnerSpacing.x);
+        if (ButtonEx("+", ImVec2(button_size, button_size), ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups))
+        {
+            DataTypeApplyOp(data_type, '+', data_ptr, data_ptr, g.IO.KeyCtrl && step_fast ? step_fast : step);
+            value_changed = true;
+        }
+        SameLine(0, style.ItemInnerSpacing.x);
+        TextUnformatted(label, FindRenderedTextEnd(label));
+
+        PopID();
+        EndGroup();
+    }
+    else
+    {
+        if (InputText(label, buf, IM_ARRAYSIZE(buf), extra_flags))
+            value_changed = DataTypeApplyOpFromText(buf, g.InputTextState.InitialText.Data, data_type, data_ptr, format);
+    }
+
+    return value_changed;
+}
+
+bool ImGui::InputScalarN(const char* label, ImGuiDataType data_type, void* v, int components, const void* step, const void* step_fast, const char* format, ImGuiInputTextFlags extra_flags)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    if (window->SkipItems)
+        return false;
+
+    ImGuiContext& g = *GImGui;
+    bool value_changed = false;
+    BeginGroup();
+    PushID(label);
+    PushMultiItemsWidths(components);
+    size_t type_size = GDataTypeInfo[data_type].Size;
+    for (int i = 0; i < components; i++)
+    {
+        PushID(i);
+        value_changed |= InputScalar("##v", data_type, v, step, step_fast, format, extra_flags);
+        SameLine(0, g.Style.ItemInnerSpacing.x);
+        PopID();
+        PopItemWidth();
+        v = (void*)((char*)v + type_size);
+    }
+    PopID();
+
+    TextUnformatted(label, FindRenderedTextEnd(label));
+    EndGroup();
+    return value_changed;
+}
+
+bool ImGui::InputFloat(const char* label, float* v, float step, float step_fast, const char* format, ImGuiInputTextFlags extra_flags)
+{
+    extra_flags |= ImGuiInputTextFlags_CharsScientific;
+    return InputScalar(label, ImGuiDataType_Float, (void*)v, (void*)(step>0.0f ? &step : NULL), (void*)(step_fast>0.0f ? &step_fast : NULL), format, extra_flags);
+}
+
+bool ImGui::InputFloat2(const char* label, float v[2], const char* format, ImGuiInputTextFlags extra_flags)
+{
+    return InputScalarN(label, ImGuiDataType_Float, v, 2, NULL, NULL, format, extra_flags);
+}
+
+bool ImGui::InputFloat3(const char* label, float v[3], const char* format, ImGuiInputTextFlags extra_flags)
+{
+    return InputScalarN(label, ImGuiDataType_Float, v, 3, NULL, NULL, format, extra_flags);
+}
+
+bool ImGui::InputFloat4(const char* label, float v[4], const char* format, ImGuiInputTextFlags extra_flags)
+{
+    return InputScalarN(label, ImGuiDataType_Float, v, 4, NULL, NULL, format, extra_flags);
+}
+
+// Prefer using "const char* format" directly, which is more flexible and consistent with other API.
+#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
+bool ImGui::InputFloat(const char* label, float* v, float step, float step_fast, int decimal_precision, ImGuiInputTextFlags extra_flags)
+{
+    char format[16] = "%f";
+    if (decimal_precision >= 0)
+        ImFormatString(format, IM_ARRAYSIZE(format), "%%.%df", decimal_precision);
+    return InputFloat(label, v, step, step_fast, format, extra_flags);
+}
+
+bool ImGui::InputFloat2(const char* label, float v[2], int decimal_precision, ImGuiInputTextFlags extra_flags)
+{
+    char format[16] = "%f";
+    if (decimal_precision >= 0)
+        ImFormatString(format, IM_ARRAYSIZE(format), "%%.%df", decimal_precision);
+    return InputScalarN(label, ImGuiDataType_Float, v, 2, NULL, NULL, format, extra_flags);
+}
+
+bool ImGui::InputFloat3(const char* label, float v[3], int decimal_precision, ImGuiInputTextFlags extra_flags)
+{
+    char format[16] = "%f";
+    if (decimal_precision >= 0)
+        ImFormatString(format, IM_ARRAYSIZE(format), "%%.%df", decimal_precision);
+    return InputScalarN(label, ImGuiDataType_Float, v, 3, NULL, NULL, format, extra_flags);
+}
+
+bool ImGui::InputFloat4(const char* label, float v[4], int decimal_precision, ImGuiInputTextFlags extra_flags)
+{
+    char format[16] = "%f";
+    if (decimal_precision >= 0)
+        ImFormatString(format, IM_ARRAYSIZE(format), "%%.%df", decimal_precision);
+    return InputScalarN(label, ImGuiDataType_Float, v, 4, NULL, NULL, format, extra_flags);
+}
+#endif // IMGUI_DISABLE_OBSOLETE_FUNCTIONS
+
+bool ImGui::InputInt(const char* label, int* v, int step, int step_fast, ImGuiInputTextFlags extra_flags)
+{
+    // Hexadecimal input provided as a convenience but the flag name is awkward. Typically you'd use InputText() to parse your own data, if you want to handle prefixes.
+    const char* format = (extra_flags & ImGuiInputTextFlags_CharsHexadecimal) ? "%08X" : "%d";
+    return InputScalar(label, ImGuiDataType_S32, (void*)v, (void*)(step>0 ? &step : NULL), (void*)(step_fast>0 ? &step_fast : NULL), format, extra_flags);
+}
+
+bool ImGui::InputInt2(const char* label, int v[2], ImGuiInputTextFlags extra_flags)
+{
+    return InputScalarN(label, ImGuiDataType_S32, v, 2, NULL, NULL, "%d", extra_flags);
+}
+
+bool ImGui::InputInt3(const char* label, int v[3], ImGuiInputTextFlags extra_flags)
+{
+    return InputScalarN(label, ImGuiDataType_S32, v, 3, NULL, NULL, "%d", extra_flags);
+}
+
+bool ImGui::InputInt4(const char* label, int v[4], ImGuiInputTextFlags extra_flags)
+{
+    return InputScalarN(label, ImGuiDataType_S32, v, 4, NULL, NULL, "%d", extra_flags);
+}
+
+bool ImGui::InputDouble(const char* label, double* v, double step, double step_fast, const char* format, ImGuiInputTextFlags extra_flags)
+{
+    extra_flags |= ImGuiInputTextFlags_CharsScientific;
+    return InputScalar(label, ImGuiDataType_Double, (void*)v, (void*)(step>0.0 ? &step : NULL), (void*)(step_fast>0.0 ? &step_fast : NULL), format, extra_flags);
+}
+
+//-------------------------------------------------------------------------
+// [SECTION] Widgets: InputText, InputTextMultiline
+//-------------------------------------------------------------------------
+// - InputText()
+// - InputTextMultiline()
+// - InputTextEx() [Internal]
+//-------------------------------------------------------------------------
+
+bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
+{
+    IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline()
+    return InputTextEx(label, buf, (int)buf_size, ImVec2(0,0), flags, callback, user_data);
+}
+
+bool ImGui::InputTextMultiline(const char* label, char* buf, size_t buf_size, const ImVec2& size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
+{
+    return InputTextEx(label, buf, (int)buf_size, size, flags | ImGuiInputTextFlags_Multiline, callback, user_data);
+}
+
+static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end)
+{
+    int line_count = 0;
+    const char* s = text_begin;
+    while (char c = *s++) // We are only matching for \n so we can ignore UTF-8 decoding
+        if (c == '\n')
+            line_count++;
+    s--;
+    if (s[0] != '\n' && s[0] != '\r')
+        line_count++;
+    *out_text_end = s;
+    return line_count;
+}
+
+static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining, ImVec2* out_offset, bool stop_on_new_line)
+{
+    ImFont* font = GImGui->Font;
+    const float line_height = GImGui->FontSize;
+    const float scale = line_height / font->FontSize;
+
+    ImVec2 text_size = ImVec2(0,0);
+    float line_width = 0.0f;
+
+    const ImWchar* s = text_begin;
+    while (s < text_end)
+    {
+        unsigned int c = (unsigned int)(*s++);
+        if (c == '\n')
+        {
+            text_size.x = ImMax(text_size.x, line_width);
+            text_size.y += line_height;
+            line_width = 0.0f;
+            if (stop_on_new_line)
+                break;
+            continue;
+        }
+        if (c == '\r')
+            continue;
+
+        const float char_width = font->GetCharAdvance((unsigned short)c) * scale;
+        line_width += char_width;
+    }
+
+    if (text_size.x < line_width)
+        text_size.x = line_width;
+
+    if (out_offset)
+        *out_offset = ImVec2(line_width, text_size.y + line_height);  // offset allow for the possibility of sitting after a trailing \n
+
+    if (line_width > 0 || text_size.y == 0.0f)                        // whereas size.y will ignore the trailing \n
+        text_size.y += line_height;
+
+    if (remaining)
+        *remaining = s;
+
+    return text_size;
+}
+
+// Wrapper for stb_textedit.h to edit text (our wrapper is for: statically sized buffer, single-line, wchar characters. InputText converts between UTF-8 and wchar)
+namespace ImGuiStb
+{
+
+static int     STB_TEXTEDIT_STRINGLEN(const STB_TEXTEDIT_STRING* obj)                             { return obj->CurLenW; }
+static ImWchar STB_TEXTEDIT_GETCHAR(const STB_TEXTEDIT_STRING* obj, int idx)                      { return obj->TextW[idx]; }
+static float   STB_TEXTEDIT_GETWIDTH(STB_TEXTEDIT_STRING* obj, int line_start_idx, int char_idx)  { ImWchar c = obj->TextW[line_start_idx+char_idx]; if (c == '\n') return STB_TEXTEDIT_GETWIDTH_NEWLINE; return GImGui->Font->GetCharAdvance(c) * (GImGui->FontSize / GImGui->Font->FontSize); }
+static int     STB_TEXTEDIT_KEYTOTEXT(int key)                                                    { return key >= 0x10000 ? 0 : key; }
+static ImWchar STB_TEXTEDIT_NEWLINE = '\n';
+static void    STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, STB_TEXTEDIT_STRING* obj, int line_start_idx)
+{
+    const ImWchar* text = obj->TextW.Data;
+    const ImWchar* text_remaining = NULL;
+    const ImVec2 size = InputTextCalcTextSizeW(text + line_start_idx, text + obj->CurLenW, &text_remaining, NULL, true);
+    r->x0 = 0.0f;
+    r->x1 = size.x;
+    r->baseline_y_delta = size.y;
+    r->ymin = 0.0f;
+    r->ymax = size.y;
+    r->num_chars = (int)(text_remaining - (text + line_start_idx));
+}
+
+static bool is_separator(unsigned int c)                                        { return ImCharIsBlankW(c) || c==',' || c==';' || c=='(' || c==')' || c=='{' || c=='}' || c=='[' || c==']' || c=='|'; }
+static int  is_word_boundary_from_right(STB_TEXTEDIT_STRING* obj, int idx)      { return idx > 0 ? (is_separator( obj->TextW[idx-1] ) && !is_separator( obj->TextW[idx] ) ) : 1; }
+static int  STB_TEXTEDIT_MOVEWORDLEFT_IMPL(STB_TEXTEDIT_STRING* obj, int idx)   { idx--; while (idx >= 0 && !is_word_boundary_from_right(obj, idx)) idx--; return idx < 0 ? 0 : idx; }
+#ifdef __APPLE__    // FIXME: Move setting to IO structure
+static int  is_word_boundary_from_left(STB_TEXTEDIT_STRING* obj, int idx)       { return idx > 0 ? (!is_separator( obj->TextW[idx-1] ) && is_separator( obj->TextW[idx] ) ) : 1; }
+static int  STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(STB_TEXTEDIT_STRING* obj, int idx)  { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_left(obj, idx)) idx++; return idx > len ? len : idx; }
+#else
+static int  STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(STB_TEXTEDIT_STRING* obj, int idx)  { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_right(obj, idx)) idx++; return idx > len ? len : idx; }
+#endif
+#define STB_TEXTEDIT_MOVEWORDLEFT   STB_TEXTEDIT_MOVEWORDLEFT_IMPL    // They need to be #define for stb_textedit.h
+#define STB_TEXTEDIT_MOVEWORDRIGHT  STB_TEXTEDIT_MOVEWORDRIGHT_IMPL
+
+static void STB_TEXTEDIT_DELETECHARS(STB_TEXTEDIT_STRING* obj, int pos, int n)
+{
+    ImWchar* dst = obj->TextW.Data + pos;
+
+    // We maintain our buffer length in both UTF-8 and wchar formats
+    obj->CurLenA -= ImTextCountUtf8BytesFromStr(dst, dst + n);
+    obj->CurLenW -= n;
+
+    // Offset remaining text
+    const ImWchar* src = obj->TextW.Data + pos + n;
+    while (ImWchar c = *src++)
+        *dst++ = c;
+    *dst = '\0';
+}
+
+static bool STB_TEXTEDIT_INSERTCHARS(STB_TEXTEDIT_STRING* obj, int pos, const ImWchar* new_text, int new_text_len)
+{
+    const bool is_resizable = (obj->UserFlags & ImGuiInputTextFlags_CallbackResize) != 0;
+    const int text_len = obj->CurLenW;
+    IM_ASSERT(pos <= text_len);
+
+    const int new_text_len_utf8 = ImTextCountUtf8BytesFromStr(new_text, new_text + new_text_len);
+    if (!is_resizable && (new_text_len_utf8 + obj->CurLenA + 1 > obj->BufCapacityA))
+        return false;
+
+    // Grow internal buffer if needed
+    if (new_text_len + text_len + 1 > obj->TextW.Size)
+    {
+        if (!is_resizable)
+            return false;
+        IM_ASSERT(text_len < obj->TextW.Size);
+        obj->TextW.resize(text_len + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1);
+    }
+
+    ImWchar* text = obj->TextW.Data;
+    if (pos != text_len)
+        memmove(text + pos + new_text_len, text + pos, (size_t)(text_len - pos) * sizeof(ImWchar));
+    memcpy(text + pos, new_text, (size_t)new_text_len * sizeof(ImWchar));
+
+    obj->CurLenW += new_text_len;
+    obj->CurLenA += new_text_len_utf8;
+    obj->TextW[obj->CurLenW] = '\0';
+
+    return true;
+}
+
+// We don't use an enum so we can build even with conflicting symbols (if another user of stb_textedit.h leak their STB_TEXTEDIT_K_* symbols)
+#define STB_TEXTEDIT_K_LEFT         0x10000 // keyboard input to move cursor left
+#define STB_TEXTEDIT_K_RIGHT        0x10001 // keyboard input to move cursor right
+#define STB_TEXTEDIT_K_UP           0x10002 // keyboard input to move cursor up
+#define STB_TEXTEDIT_K_DOWN         0x10003 // keyboard input to move cursor down
+#define STB_TEXTEDIT_K_LINESTART    0x10004 // keyboard input to move cursor to start of line
+#define STB_TEXTEDIT_K_LINEEND      0x10005 // keyboard input to move cursor to end of line
+#define STB_TEXTEDIT_K_TEXTSTART    0x10006 // keyboard input to move cursor to start of text
+#define STB_TEXTEDIT_K_TEXTEND      0x10007 // keyboard input to move cursor to end of text
+#define STB_TEXTEDIT_K_DELETE       0x10008 // keyboard input to delete selection or character under cursor
+#define STB_TEXTEDIT_K_BACKSPACE    0x10009 // keyboard input to delete selection or character left of cursor
+#define STB_TEXTEDIT_K_UNDO         0x1000A // keyboard input to perform undo
+#define STB_TEXTEDIT_K_REDO         0x1000B // keyboard input to perform redo
+#define STB_TEXTEDIT_K_WORDLEFT     0x1000C // keyboard input to move cursor left one word
+#define STB_TEXTEDIT_K_WORDRIGHT    0x1000D // keyboard input to move cursor right one word
+#define STB_TEXTEDIT_K_SHIFT        0x20000
+
+#define STB_TEXTEDIT_IMPLEMENTATION
+#include "imstb_textedit.h"
+
+}
+
+void ImGuiInputTextState::OnKeyPressed(int key)
+{
+    stb_textedit_key(this, &StbState, key);
+    CursorFollow = true;
+    CursorAnimReset();
+}
+
+ImGuiInputTextCallbackData::ImGuiInputTextCallbackData()
+{
+    memset(this, 0, sizeof(*this));
+}
+
+// Public API to manipulate UTF-8 text
+// We expose UTF-8 to the user (unlike the STB_TEXTEDIT_* functions which are manipulating wchar)
+// FIXME: The existence of this rarely exercised code path is a bit of a nuisance.
+void ImGuiInputTextCallbackData::DeleteChars(int pos, int bytes_count)
+{
+    IM_ASSERT(pos + bytes_count <= BufTextLen);
+    char* dst = Buf + pos;
+    const char* src = Buf + pos + bytes_count;
+    while (char c = *src++)
+        *dst++ = c;
+    *dst = '\0';
+
+    if (CursorPos + bytes_count >= pos)
+        CursorPos -= bytes_count;
+    else if (CursorPos >= pos)
+        CursorPos = pos;
+    SelectionStart = SelectionEnd = CursorPos;
+    BufDirty = true;
+    BufTextLen -= bytes_count;
+}
+
+void ImGuiInputTextCallbackData::InsertChars(int pos, const char* new_text, const char* new_text_end)
+{
+    const bool is_resizable = (Flags & ImGuiInputTextFlags_CallbackResize) != 0;
+    const int new_text_len = new_text_end ? (int)(new_text_end - new_text) : (int)strlen(new_text);
+    if (new_text_len + BufTextLen >= BufSize)
+    {
+        if (!is_resizable)
+            return;
+
+        // Contrary to STB_TEXTEDIT_INSERTCHARS() this is working in the UTF8 buffer, hence the midly similar code (until we remove the U16 buffer alltogether!)
+        ImGuiContext& g = *GImGui;
+        ImGuiInputTextState* edit_state = &g.InputTextState;
+        IM_ASSERT(edit_state->ID != 0 && g.ActiveId == edit_state->ID);
+        IM_ASSERT(Buf == edit_state->TempBuffer.Data);
+        int new_buf_size = BufTextLen + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1;
+        edit_state->TempBuffer.reserve(new_buf_size + 1);
+        Buf = edit_state->TempBuffer.Data;
+        BufSize = edit_state->BufCapacityA = new_buf_size;
+    }
+
+    if (BufTextLen != pos)
+        memmove(Buf + pos + new_text_len, Buf + pos, (size_t)(BufTextLen - pos));
+    memcpy(Buf + pos, new_text, (size_t)new_text_len * sizeof(char));
+    Buf[BufTextLen + new_text_len] = '\0';
+
+    if (CursorPos >= pos)
+        CursorPos += new_text_len;
+    SelectionStart = SelectionEnd = CursorPos;
+    BufDirty = true;
+    BufTextLen += new_text_len;
+}
+
+// Return false to discard a character.
+static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
+{
+    unsigned int c = *p_char;
+
+    if (c < 128 && c != ' ' && !isprint((int)(c & 0xFF)))
+    {
+        bool pass = false;
+        pass |= (c == '\n' && (flags & ImGuiInputTextFlags_Multiline));
+        pass |= (c == '\t' && (flags & ImGuiInputTextFlags_AllowTabInput));
+        if (!pass)
+            return false;
+    }
+
+    if (c >= 0xE000 && c <= 0xF8FF) // Filter private Unicode range. I don't imagine anybody would want to input them. GLFW on OSX seems to send private characters for special keys like arrow keys.
+        return false;
+
+    if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_CharsScientific))
+    {
+        if (flags & ImGuiInputTextFlags_CharsDecimal)
+            if (!(c >= '0' && c <= '9') && (c != '.') && (c != '-') && (c != '+') && (c != '*') && (c != '/'))
+                return false;
+
+        if (flags & ImGuiInputTextFlags_CharsScientific)
+            if (!(c >= '0' && c <= '9') && (c != '.') && (c != '-') && (c != '+') && (c != '*') && (c != '/') && (c != 'e') && (c != 'E'))
+                return false;
+
+        if (flags & ImGuiInputTextFlags_CharsHexadecimal)
+            if (!(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f') && !(c >= 'A' && c <= 'F'))
+                return false;
+
+        if (flags & ImGuiInputTextFlags_CharsUppercase)
+            if (c >= 'a' && c <= 'z')
+                *p_char = (c += (unsigned int)('A'-'a'));
+
+        if (flags & ImGuiInputTextFlags_CharsNoBlank)
+            if (ImCharIsBlankW(c))
+                return false;
+    }
+
+    if (flags & ImGuiInputTextFlags_CallbackCharFilter)
+    {
+        ImGuiInputTextCallbackData callback_data;
+        memset(&callback_data, 0, sizeof(ImGuiInputTextCallbackData));
+        callback_data.EventFlag = ImGuiInputTextFlags_CallbackCharFilter;
+        callback_data.EventChar = (ImWchar)c;
+        callback_data.Flags = flags;
+        callback_data.UserData = user_data;
+        if (callback(&callback_data) != 0)
+            return false;
+        *p_char = callback_data.EventChar;
+        if (!callback_data.EventChar)
+            return false;
+    }
+
+    return true;
+}
+
+// Edit a string of text
+// - buf_size account for the zero-terminator, so a buf_size of 6 can hold "Hello" but not "Hello!".
+//   This is so we can easily call InputText() on static arrays using ARRAYSIZE() and to match 
+//   Note that in std::string world, capacity() would omit 1 byte used by the zero-terminator.
+// - When active, hold on a privately held copy of the text (and apply back to 'buf'). So changing 'buf' while the InputText is active has no effect.
+// - If you want to use ImGui::InputText() with std::string, see misc/stl/imgui_stl.h
+// (FIXME: Rather messy function partly because we are doing UTF8 > u16 > UTF8 conversions on the go to more easily handle stb_textedit calls. Ideally we should stay in UTF-8 all the time. See https://github.com/nothings/stb/issues/188)
+bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2& size_arg, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* callback_user_data)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    if (window->SkipItems)
+        return false;
+
+    IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackHistory) && (flags & ImGuiInputTextFlags_Multiline)));        // Can't use both together (they both use up/down keys)
+    IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackCompletion) && (flags & ImGuiInputTextFlags_AllowTabInput))); // Can't use both together (they both use tab key)
+
+    ImGuiContext& g = *GImGui;
+    const ImGuiIO& io = g.IO;
+    const ImGuiStyle& style = g.Style;
+
+    const bool is_multiline = (flags & ImGuiInputTextFlags_Multiline) != 0;
+    const bool is_editable = (flags & ImGuiInputTextFlags_ReadOnly) == 0;
+    const bool is_password = (flags & ImGuiInputTextFlags_Password) != 0;
+    const bool is_undoable = (flags & ImGuiInputTextFlags_NoUndoRedo) == 0;
+    const bool is_resizable = (flags & ImGuiInputTextFlags_CallbackResize) != 0;
+    if (is_resizable)
+        IM_ASSERT(callback != NULL); // Must provide a callback if you set the ImGuiInputTextFlags_CallbackResize flag!
+
+    if (is_multiline) // Open group before calling GetID() because groups tracks id created within their scope, 
+        BeginGroup();
+    const ImGuiID id = window->GetID(label);
+    const ImVec2 label_size = CalcTextSize(label, NULL, true);
+    ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), (is_multiline ? GetTextLineHeight() * 8.0f : label_size.y) + style.FramePadding.y*2.0f); // Arbitrary default of 8 lines high for multi-line
+    const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size);
+    const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? (style.ItemInnerSpacing.x + label_size.x) : 0.0f, 0.0f));
+
+    ImGuiWindow* draw_window = window;
+    if (is_multiline)
+    {
+        ItemAdd(total_bb, id, &frame_bb);
+        if (!BeginChildFrame(id, frame_bb.GetSize()))
+        {
+            EndChildFrame();
+            EndGroup();
+            return false;
+        }
+        draw_window = GetCurrentWindow();
+        draw_window->DC.NavLayerActiveMaskNext |= draw_window->DC.NavLayerCurrentMask; // This is to ensure that EndChild() will display a navigation highlight
+        size.x -= draw_window->ScrollbarSizes.x;
+    }
+    else
+    {
+        ItemSize(total_bb, style.FramePadding.y);
+        if (!ItemAdd(total_bb, id, &frame_bb))
+            return false;
+    }
+    const bool hovered = ItemHoverable(frame_bb, id);
+    if (hovered)
+        g.MouseCursor = ImGuiMouseCursor_TextInput;
+
+    // Password pushes a temporary font with only a fallback glyph
+    if (is_password)
+    {
+        const ImFontGlyph* glyph = g.Font->FindGlyph('*');
+        ImFont* password_font = &g.InputTextPasswordFont;
+        password_font->FontSize = g.Font->FontSize;
+        password_font->Scale = g.Font->Scale;
+        password_font->DisplayOffset = g.Font->DisplayOffset;
+        password_font->Ascent = g.Font->Ascent;
+        password_font->Descent = g.Font->Descent;
+        password_font->ContainerAtlas = g.Font->ContainerAtlas;
+        password_font->FallbackGlyph = glyph;
+        password_font->FallbackAdvanceX = glyph->AdvanceX;
+        IM_ASSERT(password_font->Glyphs.empty() && password_font->IndexAdvanceX.empty() && password_font->IndexLookup.empty());
+        PushFont(password_font);
+    }
+
+    // NB: we are only allowed to access 'edit_state' if we are the active widget.
+    ImGuiInputTextState& edit_state = g.InputTextState;
+
+    const bool focus_requested = FocusableItemRegister(window, id, (flags & (ImGuiInputTextFlags_CallbackCompletion|ImGuiInputTextFlags_AllowTabInput)) == 0);    // Using completion callback disable keyboard tabbing
+    const bool focus_requested_by_code = focus_requested && (window->FocusIdxAllCounter == window->FocusIdxAllRequestCurrent);
+    const bool focus_requested_by_tab = focus_requested && !focus_requested_by_code;
+
+    const bool user_clicked = hovered && io.MouseClicked[0];
+    const bool user_scrolled = is_multiline && g.ActiveId == 0 && edit_state.ID == id && g.ActiveIdPreviousFrame == draw_window->GetIDNoKeepAlive("#SCROLLY");
+    const bool user_nav_input_start = (g.ActiveId != id) && ((g.NavInputId == id) || (g.NavActivateId == id && g.NavInputSource == ImGuiInputSource_NavKeyboard));
+
+    bool clear_active_id = false;
+
+    bool select_all = (g.ActiveId != id) && ((flags & ImGuiInputTextFlags_AutoSelectAll) != 0 || user_nav_input_start) && (!is_multiline);
+    if (focus_requested || user_clicked || user_scrolled || user_nav_input_start)
+    {
+        if (g.ActiveId != id)
+        {
+            // Start edition
+            // Take a copy of the initial buffer value (both in original UTF-8 format and converted to wchar)
+            // From the moment we focused we are ignoring the content of 'buf' (unless we are in read-only mode)
+            const int prev_len_w = edit_state.CurLenW;
+            const int init_buf_len = (int)strlen(buf);
+            edit_state.TextW.resize(buf_size+1);             // wchar count <= UTF-8 count. we use +1 to make sure that .Data isn't NULL so it doesn't crash.
+            edit_state.InitialText.resize(init_buf_len + 1); // UTF-8. we use +1 to make sure that .Data isn't NULL so it doesn't crash.
+            memcpy(edit_state.InitialText.Data, buf, init_buf_len + 1);
+            const char* buf_end = NULL;
+            edit_state.CurLenW = ImTextStrFromUtf8(edit_state.TextW.Data, buf_size, buf, NULL, &buf_end);
+            edit_state.CurLenA = (int)(buf_end - buf); // We can't get the result from ImStrncpy() above because it is not UTF-8 aware. Here we'll cut off malformed UTF-8.
+            edit_state.CursorAnimReset();
+
+            // Preserve cursor position and undo/redo stack if we come back to same widget
+            // FIXME: We should probably compare the whole buffer to be on the safety side. Comparing buf (utf8) and edit_state.Text (wchar).
+            const bool recycle_state = (edit_state.ID == id) && (prev_len_w == edit_state.CurLenW);
+            if (recycle_state)
+            {
+                // Recycle existing cursor/selection/undo stack but clamp position
+                // Note a single mouse click will override the cursor/position immediately by calling stb_textedit_click handler.
+                edit_state.CursorClamp();
+            }
+            else
+            {
+                edit_state.ID = id;
+                edit_state.ScrollX = 0.0f;
+                stb_textedit_initialize_state(&edit_state.StbState, !is_multiline);
+                if (!is_multiline && focus_requested_by_code)
+                    select_all = true;
+            }
+            if (flags & ImGuiInputTextFlags_AlwaysInsertMode)
+                edit_state.StbState.insert_mode = true;
+            if (!is_multiline && (focus_requested_by_tab || (user_clicked && io.KeyCtrl)))
+                select_all = true;
+        }
+        SetActiveID(id, window);
+        SetFocusID(id, window);
+        FocusWindow(window);
+        if (!is_multiline && !(flags & ImGuiInputTextFlags_CallbackHistory))
+            g.ActiveIdAllowNavDirFlags |= ((1 << ImGuiDir_Up) | (1 << ImGuiDir_Down));
+    }
+    else if (io.MouseClicked[0])
+    {
+        // Release focus when we click outside
+        clear_active_id = true;
+    }
+
+    bool value_changed = false;
+    bool enter_pressed = false;
+    int backup_current_text_length = 0;
+
+    if (g.ActiveId == id)
+    {
+        if (!is_editable && !g.ActiveIdIsJustActivated)
+        {
+            // When read-only we always use the live data passed to the function
+            edit_state.TextW.resize(buf_size+1);
+            const char* buf_end = NULL;
+            edit_state.CurLenW = ImTextStrFromUtf8(edit_state.TextW.Data, edit_state.TextW.Size, buf, NULL, &buf_end);
+            edit_state.CurLenA = (int)(buf_end - buf);
+            edit_state.CursorClamp();
+        }
+
+        backup_current_text_length = edit_state.CurLenA;
+        edit_state.BufCapacityA = buf_size;
+        edit_state.UserFlags = flags;
+        edit_state.UserCallback = callback;
+        edit_state.UserCallbackData = callback_user_data;
+
+        // Although we are active we don't prevent mouse from hovering other elements unless we are interacting right now with the widget.
+        // Down the line we should have a cleaner library-wide concept of Selected vs Active.
+        g.ActiveIdAllowOverlap = !io.MouseDown[0];
+        g.WantTextInputNextFrame = 1;
+
+        // Edit in progress
+        const float mouse_x = (io.MousePos.x - frame_bb.Min.x - style.FramePadding.x) + edit_state.ScrollX;
+        const float mouse_y = (is_multiline ? (io.MousePos.y - draw_window->DC.CursorPos.y - style.FramePadding.y) : (g.FontSize*0.5f));
+
+        const bool is_osx = io.ConfigMacOSXBehaviors;
+        if (select_all || (hovered && !is_osx && io.MouseDoubleClicked[0]))
+        {
+            edit_state.SelectAll();
+            edit_state.SelectedAllMouseLock = true;
+        }
+        else if (hovered && is_osx && io.MouseDoubleClicked[0])
+        {
+            // Double-click select a word only, OS X style (by simulating keystrokes)
+            edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT);
+            edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT);
+        }
+        else if (io.MouseClicked[0] && !edit_state.SelectedAllMouseLock)
+        {
+            if (hovered)
+            {
+                stb_textedit_click(&edit_state, &edit_state.StbState, mouse_x, mouse_y);
+                edit_state.CursorAnimReset();
+            }
+        }
+        else if (io.MouseDown[0] && !edit_state.SelectedAllMouseLock && (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f))
+        {
+            stb_textedit_drag(&edit_state, &edit_state.StbState, mouse_x, mouse_y);
+            edit_state.CursorAnimReset();
+            edit_state.CursorFollow = true;
+        }
+        if (edit_state.SelectedAllMouseLock && !io.MouseDown[0])
+            edit_state.SelectedAllMouseLock = false;
+
+        if (io.InputCharacters[0])
+        {
+            // Process text input (before we check for Return because using some IME will effectively send a Return?)
+            // We ignore CTRL inputs, but need to allow ALT+CTRL as some keyboards (e.g. German) use AltGR (which _is_ Alt+Ctrl) to input certain characters.
+            bool ignore_inputs = (io.KeyCtrl && !io.KeyAlt) || (is_osx && io.KeySuper);
+            if (!ignore_inputs && is_editable && !user_nav_input_start)
+                for (int n = 0; n < IM_ARRAYSIZE(io.InputCharacters) && io.InputCharacters[n]; n++)
+                {
+                    // Insert character if they pass filtering
+                    unsigned int c = (unsigned int)io.InputCharacters[n];
+                    if (InputTextFilterCharacter(&c, flags, callback, callback_user_data))
+                        edit_state.OnKeyPressed((int)c);
+                }
+
+            // Consume characters
+            memset(g.IO.InputCharacters, 0, sizeof(g.IO.InputCharacters));
+        }
+    }
+
+    bool cancel_edit = false;
+    if (g.ActiveId == id && !g.ActiveIdIsJustActivated && !clear_active_id)
+    {
+        // Handle key-presses
+        const int k_mask = (io.KeyShift ? STB_TEXTEDIT_K_SHIFT : 0);
+        const bool is_osx = io.ConfigMacOSXBehaviors;
+        const bool is_shortcut_key = (is_osx ? (io.KeySuper && !io.KeyCtrl) : (io.KeyCtrl && !io.KeySuper)) && !io.KeyAlt && !io.KeyShift; // OS X style: Shortcuts using Cmd/Super instead of Ctrl
+        const bool is_osx_shift_shortcut = is_osx && io.KeySuper && io.KeyShift && !io.KeyCtrl && !io.KeyAlt;
+        const bool is_wordmove_key_down = is_osx ? io.KeyAlt : io.KeyCtrl;                     // OS X style: Text editing cursor movement using Alt instead of Ctrl
+        const bool is_startend_key_down = is_osx && io.KeySuper && !io.KeyCtrl && !io.KeyAlt;  // OS X style: Line/Text Start and End using Cmd+Arrows instead of Home/End
+        const bool is_ctrl_key_only = io.KeyCtrl && !io.KeyShift && !io.KeyAlt && !io.KeySuper;
+        const bool is_shift_key_only = io.KeyShift && !io.KeyCtrl && !io.KeyAlt && !io.KeySuper;
+
+        const bool is_cut   = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_X)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Delete))) && is_editable && !is_password && (!is_multiline || edit_state.HasSelection());
+        const bool is_copy  = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_C)) || (is_ctrl_key_only  && IsKeyPressedMap(ImGuiKey_Insert))) && !is_password && (!is_multiline || edit_state.HasSelection());
+        const bool is_paste = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_V)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Insert))) && is_editable;
+        const bool is_undo  = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_Z)) && is_editable && is_undoable);
+        const bool is_redo  = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_Y)) || (is_osx_shift_shortcut && IsKeyPressedMap(ImGuiKey_Z))) && is_editable && is_undoable;
+
+        if (IsKeyPressedMap(ImGuiKey_LeftArrow))                        { edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINESTART : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDLEFT : STB_TEXTEDIT_K_LEFT) | k_mask); }
+        else if (IsKeyPressedMap(ImGuiKey_RightArrow))                  { edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINEEND : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDRIGHT : STB_TEXTEDIT_K_RIGHT) | k_mask); }
+        else if (IsKeyPressedMap(ImGuiKey_UpArrow) && is_multiline)     { if (io.KeyCtrl) SetWindowScrollY(draw_window, ImMax(draw_window->Scroll.y - g.FontSize, 0.0f)); else edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTSTART : STB_TEXTEDIT_K_UP) | k_mask); }
+        else if (IsKeyPressedMap(ImGuiKey_DownArrow) && is_multiline)   { if (io.KeyCtrl) SetWindowScrollY(draw_window, ImMin(draw_window->Scroll.y + g.FontSize, GetScrollMaxY())); else edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTEND : STB_TEXTEDIT_K_DOWN) | k_mask); }
+        else if (IsKeyPressedMap(ImGuiKey_Home))                        { edit_state.OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask); }
+        else if (IsKeyPressedMap(ImGuiKey_End))                         { edit_state.OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask); }
+        else if (IsKeyPressedMap(ImGuiKey_Delete) && is_editable)       { edit_state.OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask); }
+        else if (IsKeyPressedMap(ImGuiKey_Backspace) && is_editable)
+        {
+            if (!edit_state.HasSelection())
+            {
+                if (is_wordmove_key_down) edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT|STB_TEXTEDIT_K_SHIFT);
+                else if (is_osx && io.KeySuper && !io.KeyAlt && !io.KeyCtrl) edit_state.OnKeyPressed(STB_TEXTEDIT_K_LINESTART|STB_TEXTEDIT_K_SHIFT);
+            }
+            edit_state.OnKeyPressed(STB_TEXTEDIT_K_BACKSPACE | k_mask);
+        }
+        else if (IsKeyPressedMap(ImGuiKey_Enter))
+        {
+            bool ctrl_enter_for_new_line = (flags & ImGuiInputTextFlags_CtrlEnterForNewLine) != 0;
+            if (!is_multiline || (ctrl_enter_for_new_line && !io.KeyCtrl) || (!ctrl_enter_for_new_line && io.KeyCtrl))
+            {
+                enter_pressed = clear_active_id = true;
+            }
+            else if (is_editable)
+            {
+                unsigned int c = '\n'; // Insert new line
+                if (InputTextFilterCharacter(&c, flags, callback, callback_user_data))
+                    edit_state.OnKeyPressed((int)c);
+            }
+        }
+        else if ((flags & ImGuiInputTextFlags_AllowTabInput) && IsKeyPressedMap(ImGuiKey_Tab) && !io.KeyCtrl && !io.KeyShift && !io.KeyAlt && is_editable)
+        {
+            unsigned int c = '\t'; // Insert TAB
+            if (InputTextFilterCharacter(&c, flags, callback, callback_user_data))
+                edit_state.OnKeyPressed((int)c);
+        }
+        else if (IsKeyPressedMap(ImGuiKey_Escape))
+        {
+            clear_active_id = cancel_edit = true;
+        }
+        else if (is_undo || is_redo)
+        {
+            edit_state.OnKeyPressed(is_undo ? STB_TEXTEDIT_K_UNDO : STB_TEXTEDIT_K_REDO);
+            edit_state.ClearSelection();
+        }
+        else if (is_shortcut_key && IsKeyPressedMap(ImGuiKey_A))
+        {
+            edit_state.SelectAll();
+            edit_state.CursorFollow = true;
+        }
+        else if (is_cut || is_copy)
+        {
+            // Cut, Copy
+            if (io.SetClipboardTextFn)
+            {
+                const int ib = edit_state.HasSelection() ? ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end) : 0;
+                const int ie = edit_state.HasSelection() ? ImMax(edit_state.StbState.select_start, edit_state.StbState.select_end) : edit_state.CurLenW;
+                edit_state.TempBuffer.resize((ie-ib) * 4 + 1);
+                ImTextStrToUtf8(edit_state.TempBuffer.Data, edit_state.TempBuffer.Size, edit_state.TextW.Data+ib, edit_state.TextW.Data+ie);
+                SetClipboardText(edit_state.TempBuffer.Data);
+            }
+            if (is_cut)
+            {
+                if (!edit_state.HasSelection())
+                    edit_state.SelectAll();
+                edit_state.CursorFollow = true;
+                stb_textedit_cut(&edit_state, &edit_state.StbState);
+            }
+        }
+        else if (is_paste)
+        {
+            if (const char* clipboard = GetClipboardText())
+            {
+                // Filter pasted buffer
+                const int clipboard_len = (int)strlen(clipboard);
+                ImWchar* clipboard_filtered = (ImWchar*)ImGui::MemAlloc((clipboard_len+1) * sizeof(ImWchar));
+                int clipboard_filtered_len = 0;
+                for (const char* s = clipboard; *s; )
+                {
+                    unsigned int c;
+                    s += ImTextCharFromUtf8(&c, s, NULL);
+                    if (c == 0)
+                        break;
+                    if (c >= 0x10000 || !InputTextFilterCharacter(&c, flags, callback, callback_user_data))
+                        continue;
+                    clipboard_filtered[clipboard_filtered_len++] = (ImWchar)c;
+                }
+                clipboard_filtered[clipboard_filtered_len] = 0;
+                if (clipboard_filtered_len > 0) // If everything was filtered, ignore the pasting operation
+                {
+                    stb_textedit_paste(&edit_state, &edit_state.StbState, clipboard_filtered, clipboard_filtered_len);
+                    edit_state.CursorFollow = true;
+                }
+                ImGui::MemFree(clipboard_filtered);
+            }
+        }
+    }
+
+    if (g.ActiveId == id)
+    {
+        const char* apply_new_text = NULL;
+        int apply_new_text_length = 0;
+        if (cancel_edit)
+        {
+            // Restore initial value. Only return true if restoring to the initial value changes the current buffer contents.
+            if (is_editable && strcmp(buf, edit_state.InitialText.Data) != 0)
+            {
+                apply_new_text = edit_state.InitialText.Data;
+                apply_new_text_length = edit_state.InitialText.Size - 1;
+            }
+        }
+
+        // When using 'ImGuiInputTextFlags_EnterReturnsTrue' as a special case we reapply the live buffer back to the input buffer before clearing ActiveId, even though strictly speaking it wasn't modified on this frame.
+        // If we didn't do that, code like InputInt() with ImGuiInputTextFlags_EnterReturnsTrue would fail. Also this allows the user to use InputText() with ImGuiInputTextFlags_EnterReturnsTrue without maintaining any user-side storage.
+        bool apply_edit_back_to_user_buffer = !cancel_edit || (enter_pressed && (flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0);
+        if (apply_edit_back_to_user_buffer)
+        {
+            // Apply new value immediately - copy modified buffer back
+            // Note that as soon as the input box is active, the in-widget value gets priority over any underlying modification of the input buffer
+            // FIXME: We actually always render 'buf' when calling DrawList->AddText, making the comment above incorrect.
+            // FIXME-OPT: CPU waste to do this every time the widget is active, should mark dirty state from the stb_textedit callbacks.
+            if (is_editable)
+            {
+                edit_state.TempBuffer.resize(edit_state.TextW.Size * 4 + 1);
+                ImTextStrToUtf8(edit_state.TempBuffer.Data, edit_state.TempBuffer.Size, edit_state.TextW.Data, NULL);
+            }
+
+            // User callback
+            if ((flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackAlways)) != 0)
+            {
+                IM_ASSERT(callback != NULL);
+
+                // The reason we specify the usage semantic (Completion/History) is that Completion needs to disable keyboard TABBING at the moment.
+                ImGuiInputTextFlags event_flag = 0;
+                ImGuiKey event_key = ImGuiKey_COUNT;
+                if ((flags & ImGuiInputTextFlags_CallbackCompletion) != 0 && IsKeyPressedMap(ImGuiKey_Tab))
+                {
+                    event_flag = ImGuiInputTextFlags_CallbackCompletion;
+                    event_key = ImGuiKey_Tab;
+                }
+                else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_UpArrow))
+                {
+                    event_flag = ImGuiInputTextFlags_CallbackHistory;
+                    event_key = ImGuiKey_UpArrow;
+                }
+                else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_DownArrow))
+                {
+                    event_flag = ImGuiInputTextFlags_CallbackHistory;
+                    event_key = ImGuiKey_DownArrow;
+                }
+                else if (flags & ImGuiInputTextFlags_CallbackAlways)
+                    event_flag = ImGuiInputTextFlags_CallbackAlways;
+
+                if (event_flag)
+                {
+                    ImGuiInputTextCallbackData callback_data;
+                    memset(&callback_data, 0, sizeof(ImGuiInputTextCallbackData));
+                    callback_data.EventFlag = event_flag;
+                    callback_data.Flags = flags;
+                    callback_data.UserData = callback_user_data;
+
+                    callback_data.EventKey = event_key;
+                    callback_data.Buf = edit_state.TempBuffer.Data;
+                    callback_data.BufTextLen = edit_state.CurLenA;
+                    callback_data.BufSize = edit_state.BufCapacityA;
+                    callback_data.BufDirty = false;
+
+                    // We have to convert from wchar-positions to UTF-8-positions, which can be pretty slow (an incentive to ditch the ImWchar buffer, see https://github.com/nothings/stb/issues/188)
+                    ImWchar* text = edit_state.TextW.Data;
+                    const int utf8_cursor_pos = callback_data.CursorPos = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.cursor);
+                    const int utf8_selection_start = callback_data.SelectionStart = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.select_start);
+                    const int utf8_selection_end = callback_data.SelectionEnd = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.select_end);
+
+                    // Call user code
+                    callback(&callback_data);
+
+                    // Read back what user may have modified
+                    IM_ASSERT(callback_data.Buf == edit_state.TempBuffer.Data);  // Invalid to modify those fields
+                    IM_ASSERT(callback_data.BufSize == edit_state.BufCapacityA);
+                    IM_ASSERT(callback_data.Flags == flags);
+                    if (callback_data.CursorPos != utf8_cursor_pos)            { edit_state.StbState.cursor = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.CursorPos); edit_state.CursorFollow = true; }
+                    if (callback_data.SelectionStart != utf8_selection_start)  { edit_state.StbState.select_start = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionStart); }
+                    if (callback_data.SelectionEnd != utf8_selection_end)      { edit_state.StbState.select_end = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionEnd); }
+                    if (callback_data.BufDirty)
+                    {
+                        IM_ASSERT(callback_data.BufTextLen == (int)strlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text!
+                        if (callback_data.BufTextLen > backup_current_text_length && is_resizable)
+                            edit_state.TextW.resize(edit_state.TextW.Size + (callback_data.BufTextLen - backup_current_text_length));
+                        edit_state.CurLenW = ImTextStrFromUtf8(edit_state.TextW.Data, edit_state.TextW.Size, callback_data.Buf, NULL);
+                        edit_state.CurLenA = callback_data.BufTextLen;  // Assume correct length and valid UTF-8 from user, saves us an extra strlen()
+                        edit_state.CursorAnimReset();
+                    }
+                }
+            }
+
+            // Will copy result string if modified
+            if (is_editable && strcmp(edit_state.TempBuffer.Data, buf) != 0)
+            {
+                apply_new_text = edit_state.TempBuffer.Data;
+                apply_new_text_length = edit_state.CurLenA;
+            }
+        }
+
+        // Copy result to user buffer
+        if (apply_new_text)
+        {
+            IM_ASSERT(apply_new_text_length >= 0);
+            if (backup_current_text_length != apply_new_text_length && is_resizable)
+            {
+                ImGuiInputTextCallbackData callback_data;
+                callback_data.EventFlag = ImGuiInputTextFlags_CallbackResize;
+                callback_data.Flags = flags;
+                callback_data.Buf = buf;
+                callback_data.BufTextLen = apply_new_text_length;
+                callback_data.BufSize = ImMax(buf_size, apply_new_text_length + 1);
+                callback_data.UserData = callback_user_data;
+                callback(&callback_data);
+                buf = callback_data.Buf;
+                buf_size = callback_data.BufSize;
+                apply_new_text_length = ImMin(callback_data.BufTextLen, buf_size - 1);
+                IM_ASSERT(apply_new_text_length <= buf_size);
+            }
+
+            // If the underlying buffer resize was denied or not carried to the next frame, apply_new_text_length+1 may be >= buf_size.
+            ImStrncpy(buf, edit_state.TempBuffer.Data, ImMin(apply_new_text_length + 1, buf_size));
+            value_changed = true;
+        }
+
+        // Clear temporary user storage
+        edit_state.UserFlags = 0;
+        edit_state.UserCallback = NULL;
+        edit_state.UserCallbackData = NULL;
+    }
+
+    // Release active ID at the end of the function (so e.g. pressing Return still does a final application of the value)
+    if (clear_active_id && g.ActiveId == id)
+        ClearActiveID();
+
+    // Render
+    // Select which buffer we are going to display. When ImGuiInputTextFlags_NoLiveEdit is set 'buf' might still be the old value. We set buf to NULL to prevent accidental usage from now on.
+    const char* buf_display = (g.ActiveId == id && is_editable) ? edit_state.TempBuffer.Data : buf; buf = NULL;
+
+    // Set upper limit of single-line InputTextEx() at 2 million characters strings. The current pathological worst case is a long line
+    // without any carriage return, which would makes ImFont::RenderText() reserve too many vertices and probably crash. Avoid it altogether.
+    // Note that we only use this limit on single-line InputText(), so a pathologically large line on a InputTextMultiline() would still crash.
+    const int buf_display_max_length = 2 * 1024 * 1024;
+
+    if (!is_multiline)
+    {
+        RenderNavHighlight(frame_bb, id);
+        RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
+    }
+
+    const ImVec4 clip_rect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + size.x, frame_bb.Min.y + size.y); // Not using frame_bb.Max because we have adjusted size
+    ImVec2 render_pos = is_multiline ? draw_window->DC.CursorPos : frame_bb.Min + style.FramePadding;
+    ImVec2 text_size(0.f, 0.f);
+    const bool is_currently_scrolling = (edit_state.ID == id && is_multiline && g.ActiveId == draw_window->GetIDNoKeepAlive("#SCROLLY"));
+    if (g.ActiveId == id || is_currently_scrolling)
+    {
+        edit_state.CursorAnim += io.DeltaTime;
+
+        // This is going to be messy. We need to:
+        // - Display the text (this alone can be more easily clipped)
+        // - Handle scrolling, highlight selection, display cursor (those all requires some form of 1d->2d cursor position calculation)
+        // - Measure text height (for scrollbar)
+        // We are attempting to do most of that in **one main pass** to minimize the computation cost (non-negligible for large amount of text) + 2nd pass for selection rendering (we could merge them by an extra refactoring effort)
+        // FIXME: This should occur on buf_display but we'd need to maintain cursor/select_start/select_end for UTF-8.
+        const ImWchar* text_begin = edit_state.TextW.Data;
+        ImVec2 cursor_offset, select_start_offset;
+
+        {
+            // Count lines + find lines numbers straddling 'cursor' and 'select_start' position.
+            const ImWchar* searches_input_ptr[2];
+            searches_input_ptr[0] = text_begin + edit_state.StbState.cursor;
+            searches_input_ptr[1] = NULL;
+            int searches_remaining = 1;
+            int searches_result_line_number[2] = { -1, -999 };
+            if (edit_state.StbState.select_start != edit_state.StbState.select_end)
+            {
+                searches_input_ptr[1] = text_begin + ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end);
+                searches_result_line_number[1] = -1;
+                searches_remaining++;
+            }
+
+            // Iterate all lines to find our line numbers
+            // In multi-line mode, we never exit the loop until all lines are counted, so add one extra to the searches_remaining counter.
+            searches_remaining += is_multiline ? 1 : 0;
+            int line_count = 0;
+            for (const ImWchar* s = text_begin; *s != 0; s++)
+                if (*s == '\n')
+                {
+                    line_count++;
+                    if (searches_result_line_number[0] == -1 && s >= searches_input_ptr[0]) { searches_result_line_number[0] = line_count; if (--searches_remaining <= 0) break; }
+                    if (searches_result_line_number[1] == -1 && s >= searches_input_ptr[1]) { searches_result_line_number[1] = line_count; if (--searches_remaining <= 0) break; }
+                }
+            line_count++;
+            if (searches_result_line_number[0] == -1) searches_result_line_number[0] = line_count;
+            if (searches_result_line_number[1] == -1) searches_result_line_number[1] = line_count;
+
+            // Calculate 2d position by finding the beginning of the line and measuring distance
+            cursor_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[0], text_begin), searches_input_ptr[0]).x;
+            cursor_offset.y = searches_result_line_number[0] * g.FontSize;
+            if (searches_result_line_number[1] >= 0)
+            {
+                select_start_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[1], text_begin), searches_input_ptr[1]).x;
+                select_start_offset.y = searches_result_line_number[1] * g.FontSize;
+            }
+
+            // Store text height (note that we haven't calculated text width at all, see GitHub issues #383, #1224)
+            if (is_multiline)
+                text_size = ImVec2(size.x, line_count * g.FontSize);
+        }
+
+        // Scroll
+        if (edit_state.CursorFollow)
+        {
+            // Horizontal scroll in chunks of quarter width
+            if (!(flags & ImGuiInputTextFlags_NoHorizontalScroll))
+            {
+                const float scroll_increment_x = size.x * 0.25f;
+                if (cursor_offset.x < edit_state.ScrollX)
+                    edit_state.ScrollX = (float)(int)ImMax(0.0f, cursor_offset.x - scroll_increment_x);
+                else if (cursor_offset.x - size.x >= edit_state.ScrollX)
+                    edit_state.ScrollX = (float)(int)(cursor_offset.x - size.x + scroll_increment_x);
+            }
+            else
+            {
+                edit_state.ScrollX = 0.0f;
+            }
+
+            // Vertical scroll
+            if (is_multiline)
+            {
+                float scroll_y = draw_window->Scroll.y;
+                if (cursor_offset.y - g.FontSize < scroll_y)
+                    scroll_y = ImMax(0.0f, cursor_offset.y - g.FontSize);
+                else if (cursor_offset.y - size.y >= scroll_y)
+                    scroll_y = cursor_offset.y - size.y;
+                draw_window->DC.CursorPos.y += (draw_window->Scroll.y - scroll_y);   // To avoid a frame of lag
+                draw_window->Scroll.y = scroll_y;
+                render_pos.y = draw_window->DC.CursorPos.y;
+            }
+        }
+        edit_state.CursorFollow = false;
+        const ImVec2 render_scroll = ImVec2(edit_state.ScrollX, 0.0f);
+
+        // Draw selection
+        if (edit_state.StbState.select_start != edit_state.StbState.select_end)
+        {
+            const ImWchar* text_selected_begin = text_begin + ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end);
+            const ImWchar* text_selected_end = text_begin + ImMax(edit_state.StbState.select_start, edit_state.StbState.select_end);
+
+            float bg_offy_up = is_multiline ? 0.0f : -1.0f;    // FIXME: those offsets should be part of the style? they don't play so well with multi-line selection.
+            float bg_offy_dn = is_multiline ? 0.0f : 2.0f;
+            ImU32 bg_color = GetColorU32(ImGuiCol_TextSelectedBg);
+            ImVec2 rect_pos = render_pos + select_start_offset - render_scroll;
+            for (const ImWchar* p = text_selected_begin; p < text_selected_end; )
+            {
+                if (rect_pos.y > clip_rect.w + g.FontSize)
+                    break;
+                if (rect_pos.y < clip_rect.y)
+                {
+                    while (p < text_selected_end)
+                        if (*p++ == '\n')
+                            break;
+                }
+                else
+                {
+                    ImVec2 rect_size = InputTextCalcTextSizeW(p, text_selected_end, &p, NULL, true);
+                    if (rect_size.x <= 0.0f) rect_size.x = (float)(int)(g.Font->GetCharAdvance((unsigned short)' ') * 0.50f); // So we can see selected empty lines
+                    ImRect rect(rect_pos + ImVec2(0.0f, bg_offy_up - g.FontSize), rect_pos +ImVec2(rect_size.x, bg_offy_dn));
+                    rect.ClipWith(clip_rect);
+                    if (rect.Overlaps(clip_rect))
+                        draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color);
+                }
+                rect_pos.x = render_pos.x - render_scroll.x;
+                rect_pos.y += g.FontSize;
+            }
+        }
+
+        const int buf_display_len = edit_state.CurLenA;
+        if (is_multiline || buf_display_len < buf_display_max_length)
+            draw_window->DrawList->AddText(g.Font, g.FontSize, render_pos - render_scroll, GetColorU32(ImGuiCol_Text), buf_display, buf_display + buf_display_len, 0.0f, is_multiline ? NULL : &clip_rect);
+
+        // Draw blinking cursor
+        bool cursor_is_visible = (!g.IO.ConfigInputTextCursorBlink) || (g.InputTextState.CursorAnim <= 0.0f) || ImFmod(g.InputTextState.CursorAnim, 1.20f) <= 0.80f;
+        ImVec2 cursor_screen_pos = render_pos + cursor_offset - render_scroll;
+        ImRect cursor_screen_rect(cursor_screen_pos.x, cursor_screen_pos.y-g.FontSize+0.5f, cursor_screen_pos.x+1.0f, cursor_screen_pos.y-1.5f);
+        if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect))
+            draw_window->DrawList->AddLine(cursor_screen_rect.Min, cursor_screen_rect.GetBL(), GetColorU32(ImGuiCol_Text));
+
+        // Notify OS of text input position for advanced IME (-1 x offset so that Windows IME can cover our cursor. Bit of an extra nicety.)
+        if (is_editable)
+            g.PlatformImePos = ImVec2(cursor_screen_pos.x - 1, cursor_screen_pos.y - g.FontSize);
+    }
+    else
+    {
+        // Render text only
+        const char* buf_end = NULL;
+        if (is_multiline)
+            text_size = ImVec2(size.x, InputTextCalcTextLenAndLineCount(buf_display, &buf_end) * g.FontSize); // We don't need width
+        else
+            buf_end = buf_display + strlen(buf_display);
+        if (is_multiline || (buf_end - buf_display) < buf_display_max_length)
+            draw_window->DrawList->AddText(g.Font, g.FontSize, render_pos, GetColorU32(ImGuiCol_Text), buf_display, buf_end, 0.0f, is_multiline ? NULL : &clip_rect);
+    }
+
+    if (is_multiline)
+    {
+        Dummy(text_size + ImVec2(0.0f, g.FontSize)); // Always add room to scroll an extra line
+        EndChildFrame();
+        EndGroup();
+    }
+
+    if (is_password)
+        PopFont();
+
+    // Log as text
+    if (g.LogEnabled && !is_password)
+        LogRenderedText(&render_pos, buf_display, NULL);
+
+    if (label_size.x > 0)
+        RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
+
+    if (value_changed)
+        MarkItemEdited(id);
+
+    if ((flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0)
+        return enter_pressed;
+    else
+        return value_changed;
+}
+
+//-------------------------------------------------------------------------
+// [SECTION] Widgets: ColorEdit, ColorPicker, ColorButton, etc.
+//-------------------------------------------------------------------------
+// - ColorEdit3()
+// - ColorEdit4()
+// - ColorPicker3()
+// - RenderColorRectWithAlphaCheckerboard() [Internal]
+// - ColorPicker4()
+// - ColorButton()
+// - SetColorEditOptions()
+// - ColorTooltip() [Internal]
+// - ColorEditOptionsPopup() [Internal]
+// - ColorPickerOptionsPopup() [Internal]
+//-------------------------------------------------------------------------
+
+bool ImGui::ColorEdit3(const char* label, float col[3], ImGuiColorEditFlags flags)
+{
+    return ColorEdit4(label, col, flags | ImGuiColorEditFlags_NoAlpha);
+}
+
+// Edit colors components (each component in 0.0f..1.0f range).
+// See enum ImGuiColorEditFlags_ for available options. e.g. Only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
+// With typical options: Left-click on colored square to open color picker. Right-click to open option menu. CTRL-Click over input fields to edit them and TAB to go to next item.
+bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    if (window->SkipItems)
+        return false;
+
+    ImGuiContext& g = *GImGui;
+    const ImGuiStyle& style = g.Style;
+    const float square_sz = GetFrameHeight();
+    const float w_extra = (flags & ImGuiColorEditFlags_NoSmallPreview) ? 0.0f : (square_sz + style.ItemInnerSpacing.x);
+    const float w_items_all = CalcItemWidth() - w_extra;
+    const char* label_display_end = FindRenderedTextEnd(label);
+
+    BeginGroup();
+    PushID(label);
+
+    // If we're not showing any slider there's no point in doing any HSV conversions
+    const ImGuiColorEditFlags flags_untouched = flags;
+    if (flags & ImGuiColorEditFlags_NoInputs)
+        flags = (flags & (~ImGuiColorEditFlags__InputsMask)) | ImGuiColorEditFlags_RGB | ImGuiColorEditFlags_NoOptions;
+
+    // Context menu: display and modify options (before defaults are applied)
+    if (!(flags & ImGuiColorEditFlags_NoOptions))
+        ColorEditOptionsPopup(col, flags);
+
+    // Read stored options
+    if (!(flags & ImGuiColorEditFlags__InputsMask))
+        flags |= (g.ColorEditOptions & ImGuiColorEditFlags__InputsMask);
+    if (!(flags & ImGuiColorEditFlags__DataTypeMask))
+        flags |= (g.ColorEditOptions & ImGuiColorEditFlags__DataTypeMask);
+    if (!(flags & ImGuiColorEditFlags__PickerMask))
+        flags |= (g.ColorEditOptions & ImGuiColorEditFlags__PickerMask);
+    flags |= (g.ColorEditOptions & ~(ImGuiColorEditFlags__InputsMask | ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__PickerMask));
+
+    const bool alpha = (flags & ImGuiColorEditFlags_NoAlpha) == 0;
+    const bool hdr = (flags & ImGuiColorEditFlags_HDR) != 0;
+    const int components = alpha ? 4 : 3;
+
+    // Convert to the formats we need
+    float f[4] = { col[0], col[1], col[2], alpha ? col[3] : 1.0f };
+    if (flags & ImGuiColorEditFlags_HSV)
+        ColorConvertRGBtoHSV(f[0], f[1], f[2], f[0], f[1], f[2]);
+    int i[4] = { IM_F32_TO_INT8_UNBOUND(f[0]), IM_F32_TO_INT8_UNBOUND(f[1]), IM_F32_TO_INT8_UNBOUND(f[2]), IM_F32_TO_INT8_UNBOUND(f[3]) };
+
+    bool value_changed = false;
+    bool value_changed_as_float = false;
+
+    if ((flags & (ImGuiColorEditFlags_RGB | ImGuiColorEditFlags_HSV)) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0)
+    {
+        // RGB/HSV 0..255 Sliders
+        const float w_item_one  = ImMax(1.0f, (float)(int)((w_items_all - (style.ItemInnerSpacing.x) * (components-1)) / (float)components));
+        const float w_item_last = ImMax(1.0f, (float)(int)(w_items_all - (w_item_one + style.ItemInnerSpacing.x) * (components-1)));
+
+        const bool hide_prefix = (w_item_one <= CalcTextSize((flags & ImGuiColorEditFlags_Float) ? "M:0.000" : "M:000").x);
+        const char* ids[4] = { "##X", "##Y", "##Z", "##W" };
+        const char* fmt_table_int[3][4] =
+        {
+            {   "%3d",   "%3d",   "%3d",   "%3d" }, // Short display
+            { "R:%3d", "G:%3d", "B:%3d", "A:%3d" }, // Long display for RGBA
+            { "H:%3d", "S:%3d", "V:%3d", "A:%3d" }  // Long display for HSVA
+        };
+        const char* fmt_table_float[3][4] =
+        {
+            {   "%0.3f",   "%0.3f",   "%0.3f",   "%0.3f" }, // Short display
+            { "R:%0.3f", "G:%0.3f", "B:%0.3f", "A:%0.3f" }, // Long display for RGBA
+            { "H:%0.3f", "S:%0.3f", "V:%0.3f", "A:%0.3f" }  // Long display for HSVA
+        };
+        const int fmt_idx = hide_prefix ? 0 : (flags & ImGuiColorEditFlags_HSV) ? 2 : 1;
+
+        PushItemWidth(w_item_one);
+        for (int n = 0; n < components; n++)
+        {
+            if (n > 0)
+                SameLine(0, style.ItemInnerSpacing.x);
+            if (n + 1 == components)
+                PushItemWidth(w_item_last);
+            if (flags & ImGuiColorEditFlags_Float)
+                value_changed = value_changed_as_float = value_changed | DragFloat(ids[n], &f[n], 1.0f/255.0f, 0.0f, hdr ? 0.0f : 1.0f, fmt_table_float[fmt_idx][n]);
+            else
+                value_changed |= DragInt(ids[n], &i[n], 1.0f, 0, hdr ? 0 : 255, fmt_table_int[fmt_idx][n]);
+            if (!(flags & ImGuiColorEditFlags_NoOptions))
+                OpenPopupOnItemClick("context");
+        }
+        PopItemWidth();
+        PopItemWidth();
+    }
+    else if ((flags & ImGuiColorEditFlags_HEX) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0)
+    {
+        // RGB Hexadecimal Input
+        char buf[64];
+        if (alpha)
+            ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X%02X", ImClamp(i[0],0,255), ImClamp(i[1],0,255), ImClamp(i[2],0,255), ImClamp(i[3],0,255));
+        else
+            ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", ImClamp(i[0],0,255), ImClamp(i[1],0,255), ImClamp(i[2],0,255));
+        PushItemWidth(w_items_all);
+        if (InputText("##Text", buf, IM_ARRAYSIZE(buf), ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase))
+        {
+            value_changed = true;
+            char* p = buf;
+            while (*p == '#' || ImCharIsBlankA(*p))
+                p++;
+            i[0] = i[1] = i[2] = i[3] = 0;
+            if (alpha)
+                sscanf(p, "%02X%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2], (unsigned int*)&i[3]); // Treat at unsigned (%X is unsigned)
+            else
+                sscanf(p, "%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2]);
+        }
+        if (!(flags & ImGuiColorEditFlags_NoOptions))
+            OpenPopupOnItemClick("context");
+        PopItemWidth();
+    }
+
+    ImGuiWindow* picker_active_window = NULL;
+    if (!(flags & ImGuiColorEditFlags_NoSmallPreview))
+    {
+        if (!(flags & ImGuiColorEditFlags_NoInputs))
+            SameLine(0, style.ItemInnerSpacing.x);
+
+        const ImVec4 col_v4(col[0], col[1], col[2], alpha ? col[3] : 1.0f);
+        if (ColorButton("##ColorButton", col_v4, flags))
+        {
+            if (!(flags & ImGuiColorEditFlags_NoPicker))
+            {
+                // Store current color and open a picker
+                g.ColorPickerRef = col_v4;
+                OpenPopup("picker");
+                SetNextWindowPos(window->DC.LastItemRect.GetBL() + ImVec2(-1,style.ItemSpacing.y));
+            }
+        }
+        if (!(flags & ImGuiColorEditFlags_NoOptions))
+            OpenPopupOnItemClick("context");
+
+        if (BeginPopup("picker"))
+        {
+            picker_active_window = g.CurrentWindow;
+            if (label != label_display_end)
+            {
+                TextUnformatted(label, label_display_end);
+                Separator();
+            }
+            ImGuiColorEditFlags picker_flags_to_forward = ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__PickerMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaBar;
+            ImGuiColorEditFlags picker_flags = (flags_untouched & picker_flags_to_forward) | ImGuiColorEditFlags__InputsMask | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaPreviewHalf;
+            PushItemWidth(square_sz * 12.0f); // Use 256 + bar sizes?
+            value_changed |= ColorPicker4("##picker", col, picker_flags, &g.ColorPickerRef.x);
+            PopItemWidth();
+            EndPopup();
+        }
+    }
+
+    if (label != label_display_end && !(flags & ImGuiColorEditFlags_NoLabel))
+    {
+        SameLine(0, style.ItemInnerSpacing.x);
+        TextUnformatted(label, label_display_end);
+    }
+
+    // Convert back
+    if (picker_active_window == NULL)
+    {
+        if (!value_changed_as_float)
+            for (int n = 0; n < 4; n++)
+                f[n] = i[n] / 255.0f;
+        if (flags & ImGuiColorEditFlags_HSV)
+            ColorConvertHSVtoRGB(f[0], f[1], f[2], f[0], f[1], f[2]);
+        if (value_changed)
+        {
+            col[0] = f[0];
+            col[1] = f[1];
+            col[2] = f[2];
+            if (alpha)
+                col[3] = f[3];
+        }
+    }
+
+    PopID();
+    EndGroup();
+
+    // Drag and Drop Target
+    // NB: The flag test is merely an optional micro-optimization, BeginDragDropTarget() does the same test.
+    if ((window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect) && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropTarget())
+    {
+        if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))
+        {
+            memcpy((float*)col, payload->Data, sizeof(float) * 3);
+            value_changed = true;
+        }
+        if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))
+        {
+            memcpy((float*)col, payload->Data, sizeof(float) * components);
+            value_changed = true;
+        }
+        EndDragDropTarget();
+    }
+
+    // When picker is being actively used, use its active id so IsItemActive() will function on ColorEdit4().
+    if (picker_active_window && g.ActiveId != 0 && g.ActiveIdWindow == picker_active_window)
+        window->DC.LastItemId = g.ActiveId;
+
+    if (value_changed)
+        MarkItemEdited(window->DC.LastItemId);
+
+    return value_changed;
+}
+
+bool ImGui::ColorPicker3(const char* label, float col[3], ImGuiColorEditFlags flags)
+{
+    float col4[4] = { col[0], col[1], col[2], 1.0f };
+    if (!ColorPicker4(label, col4, flags | ImGuiColorEditFlags_NoAlpha))
+        return false;
+    col[0] = col4[0]; col[1] = col4[1]; col[2] = col4[2];
+    return true;
+}
+
+static inline ImU32 ImAlphaBlendColor(ImU32 col_a, ImU32 col_b)
+{
+    float t = ((col_b >> IM_COL32_A_SHIFT) & 0xFF) / 255.f;
+    int r = ImLerp((int)(col_a >> IM_COL32_R_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_R_SHIFT) & 0xFF, t);
+    int g = ImLerp((int)(col_a >> IM_COL32_G_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_G_SHIFT) & 0xFF, t);
+    int b = ImLerp((int)(col_a >> IM_COL32_B_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_B_SHIFT) & 0xFF, t);
+    return IM_COL32(r, g, b, 0xFF);
+}
+
+// Helper for ColorPicker4()
+// NB: This is rather brittle and will show artifact when rounding this enabled if rounded corners overlap multiple cells. Caller currently responsible for avoiding that.
+// I spent a non reasonable amount of time trying to getting this right for ColorButton with rounding+anti-aliasing+ImGuiColorEditFlags_HalfAlphaPreview flag + various grid sizes and offsets, and eventually gave up... probably more reasonable to disable rounding alltogether.
+void ImGui::RenderColorRectWithAlphaCheckerboard(ImVec2 p_min, ImVec2 p_max, ImU32 col, float grid_step, ImVec2 grid_off, float rounding, int rounding_corners_flags)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    if (((col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT) < 0xFF)
+    {
+        ImU32 col_bg1 = GetColorU32(ImAlphaBlendColor(IM_COL32(204,204,204,255), col));
+        ImU32 col_bg2 = GetColorU32(ImAlphaBlendColor(IM_COL32(128,128,128,255), col));
+        window->DrawList->AddRectFilled(p_min, p_max, col_bg1, rounding, rounding_corners_flags);
+
+        int yi = 0;
+        for (float y = p_min.y + grid_off.y; y < p_max.y; y += grid_step, yi++)
+        {
+            float y1 = ImClamp(y, p_min.y, p_max.y), y2 = ImMin(y + grid_step, p_max.y);
+            if (y2 <= y1)
+                continue;
+            for (float x = p_min.x + grid_off.x + (yi & 1) * grid_step; x < p_max.x; x += grid_step * 2.0f)
+            {
+                float x1 = ImClamp(x, p_min.x, p_max.x), x2 = ImMin(x + grid_step, p_max.x);
+                if (x2 <= x1)
+                    continue;
+                int rounding_corners_flags_cell = 0;
+                if (y1 <= p_min.y) { if (x1 <= p_min.x) rounding_corners_flags_cell |= ImDrawCornerFlags_TopLeft; if (x2 >= p_max.x) rounding_corners_flags_cell |= ImDrawCornerFlags_TopRight; }
+                if (y2 >= p_max.y) { if (x1 <= p_min.x) rounding_corners_flags_cell |= ImDrawCornerFlags_BotLeft; if (x2 >= p_max.x) rounding_corners_flags_cell |= ImDrawCornerFlags_BotRight; }
+                rounding_corners_flags_cell &= rounding_corners_flags;
+                window->DrawList->AddRectFilled(ImVec2(x1,y1), ImVec2(x2,y2), col_bg2, rounding_corners_flags_cell ? rounding : 0.0f, rounding_corners_flags_cell);
+            }
+        }
+    }
+    else
+    {
+        window->DrawList->AddRectFilled(p_min, p_max, col, rounding, rounding_corners_flags);
+    }
+}
+
+// Helper for ColorPicker4()
+static void RenderArrowsForVerticalBar(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, float bar_w)
+{
+    ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + half_sz.x + 1,         pos.y), ImVec2(half_sz.x + 2, half_sz.y + 1), ImGuiDir_Right, IM_COL32_BLACK);
+    ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + half_sz.x,             pos.y), half_sz,                              ImGuiDir_Right, IM_COL32_WHITE);
+    ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + bar_w - half_sz.x - 1, pos.y), ImVec2(half_sz.x + 2, half_sz.y + 1), ImGuiDir_Left,  IM_COL32_BLACK);
+    ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + bar_w - half_sz.x,     pos.y), half_sz,                              ImGuiDir_Left,  IM_COL32_WHITE);
+}
+
+// Note: ColorPicker4() only accesses 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
+// FIXME: we adjust the big color square height based on item width, which may cause a flickering feedback loop (if automatic height makes a vertical scrollbar appears, affecting automatic width..)
+bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags flags, const float* ref_col)
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiWindow* window = GetCurrentWindow();
+    ImDrawList* draw_list = window->DrawList;
+
+    ImGuiStyle& style = g.Style;
+    ImGuiIO& io = g.IO;
+
+    PushID(label);
+    BeginGroup();
+
+    if (!(flags & ImGuiColorEditFlags_NoSidePreview))
+        flags |= ImGuiColorEditFlags_NoSmallPreview;
+
+    // Context menu: display and store options.
+    if (!(flags & ImGuiColorEditFlags_NoOptions))
+        ColorPickerOptionsPopup(col, flags);
+
+    // Read stored options
+    if (!(flags & ImGuiColorEditFlags__PickerMask))
+        flags |= ((g.ColorEditOptions & ImGuiColorEditFlags__PickerMask) ? g.ColorEditOptions : ImGuiColorEditFlags__OptionsDefault) & ImGuiColorEditFlags__PickerMask;
+    IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__PickerMask))); // Check that only 1 is selected
+    if (!(flags & ImGuiColorEditFlags_NoOptions))
+        flags |= (g.ColorEditOptions & ImGuiColorEditFlags_AlphaBar);
+
+    // Setup
+    int components = (flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4;
+    bool alpha_bar = (flags & ImGuiColorEditFlags_AlphaBar) && !(flags & ImGuiColorEditFlags_NoAlpha);
+    ImVec2 picker_pos = window->DC.CursorPos;
+    float square_sz = GetFrameHeight();
+    float bars_width = square_sz; // Arbitrary smallish width of Hue/Alpha picking bars
+    float sv_picker_size = ImMax(bars_width * 1, CalcItemWidth() - (alpha_bar ? 2 : 1) * (bars_width + style.ItemInnerSpacing.x)); // Saturation/Value picking box
+    float bar0_pos_x = picker_pos.x + sv_picker_size + style.ItemInnerSpacing.x;
+    float bar1_pos_x = bar0_pos_x + bars_width + style.ItemInnerSpacing.x;
+    float bars_triangles_half_sz = (float)(int)(bars_width * 0.20f);
+
+    float backup_initial_col[4];
+    memcpy(backup_initial_col, col, components * sizeof(float));
+
+    float wheel_thickness = sv_picker_size * 0.08f;
+    float wheel_r_outer = sv_picker_size * 0.50f;
+    float wheel_r_inner = wheel_r_outer - wheel_thickness;
+    ImVec2 wheel_center(picker_pos.x + (sv_picker_size + bars_width)*0.5f, picker_pos.y + sv_picker_size*0.5f);
+
+    // Note: the triangle is displayed rotated with triangle_pa pointing to Hue, but most coordinates stays unrotated for logic.
+    float triangle_r = wheel_r_inner - (int)(sv_picker_size * 0.027f);
+    ImVec2 triangle_pa = ImVec2(triangle_r, 0.0f); // Hue point.
+    ImVec2 triangle_pb = ImVec2(triangle_r * -0.5f, triangle_r * -0.866025f); // Black point.
+    ImVec2 triangle_pc = ImVec2(triangle_r * -0.5f, triangle_r * +0.866025f); // White point.
+
+    float H,S,V;
+    ColorConvertRGBtoHSV(col[0], col[1], col[2], H, S, V);
+
+    bool value_changed = false, value_changed_h = false, value_changed_sv = false;
+
+    PushItemFlag(ImGuiItemFlags_NoNav, true);
+    if (flags & ImGuiColorEditFlags_PickerHueWheel)
+    {
+        // Hue wheel + SV triangle logic
+        InvisibleButton("hsv", ImVec2(sv_picker_size + style.ItemInnerSpacing.x + bars_width, sv_picker_size));
+        if (IsItemActive())
+        {
+            ImVec2 initial_off = g.IO.MouseClickedPos[0] - wheel_center;
+            ImVec2 current_off = g.IO.MousePos - wheel_center;
+            float initial_dist2 = ImLengthSqr(initial_off);
+            if (initial_dist2 >= (wheel_r_inner-1)*(wheel_r_inner-1) && initial_dist2 <= (wheel_r_outer+1)*(wheel_r_outer+1))
+            {
+                // Interactive with Hue wheel
+                H = ImAtan2(current_off.y, current_off.x) / IM_PI*0.5f;
+                if (H < 0.0f)
+                    H += 1.0f;
+                value_changed = value_changed_h = true;
+            }
+            float cos_hue_angle = ImCos(-H * 2.0f * IM_PI);
+            float sin_hue_angle = ImSin(-H * 2.0f * IM_PI);
+            if (ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, ImRotate(initial_off, cos_hue_angle, sin_hue_angle)))
+            {
+                // Interacting with SV triangle
+                ImVec2 current_off_unrotated = ImRotate(current_off, cos_hue_angle, sin_hue_angle);
+                if (!ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated))
+                    current_off_unrotated = ImTriangleClosestPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated);
+                float uu, vv, ww;
+                ImTriangleBarycentricCoords(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated, uu, vv, ww);
+                V = ImClamp(1.0f - vv, 0.0001f, 1.0f);
+                S = ImClamp(uu / V, 0.0001f, 1.0f);
+                value_changed = value_changed_sv = true;
+            }
+        }
+        if (!(flags & ImGuiColorEditFlags_NoOptions))
+            OpenPopupOnItemClick("context");
+    }
+    else if (flags & ImGuiColorEditFlags_PickerHueBar)
+    {
+        // SV rectangle logic
+        InvisibleButton("sv", ImVec2(sv_picker_size, sv_picker_size));
+        if (IsItemActive())
+        {
+            S = ImSaturate((io.MousePos.x - picker_pos.x) / (sv_picker_size-1));
+            V = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size-1));
+            value_changed = value_changed_sv = true;
+        }
+        if (!(flags & ImGuiColorEditFlags_NoOptions))
+            OpenPopupOnItemClick("context");
+
+        // Hue bar logic
+        SetCursorScreenPos(ImVec2(bar0_pos_x, picker_pos.y));
+        InvisibleButton("hue", ImVec2(bars_width, sv_picker_size));
+        if (IsItemActive())
+        {
+            H = ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size-1));
+            value_changed = value_changed_h = true;
+        }
+    }
+
+    // Alpha bar logic
+    if (alpha_bar)
+    {
+        SetCursorScreenPos(ImVec2(bar1_pos_x, picker_pos.y));
+        InvisibleButton("alpha", ImVec2(bars_width, sv_picker_size));
+        if (IsItemActive())
+        {
+            col[3] = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size-1));
+            value_changed = true;
+        }
+    }
+    PopItemFlag(); // ImGuiItemFlags_NoNav
+
+    if (!(flags & ImGuiColorEditFlags_NoSidePreview))
+    {
+        SameLine(0, style.ItemInnerSpacing.x);
+        BeginGroup();
+    }
+
+    if (!(flags & ImGuiColorEditFlags_NoLabel))
+    {
+        const char* label_display_end = FindRenderedTextEnd(label);
+        if (label != label_display_end)
+        {
+            if ((flags & ImGuiColorEditFlags_NoSidePreview))
+                SameLine(0, style.ItemInnerSpacing.x);
+            TextUnformatted(label, label_display_end);
+        }
+    }
+
+    if (!(flags & ImGuiColorEditFlags_NoSidePreview))
+    {
+        PushItemFlag(ImGuiItemFlags_NoNavDefaultFocus, true);
+        ImVec4 col_v4(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);
+        if ((flags & ImGuiColorEditFlags_NoLabel))
+            Text("Current");
+        ColorButton("##current", col_v4, (flags & (ImGuiColorEditFlags_HDR|ImGuiColorEditFlags_AlphaPreview|ImGuiColorEditFlags_AlphaPreviewHalf|ImGuiColorEditFlags_NoTooltip)), ImVec2(square_sz * 3, square_sz * 2));
+        if (ref_col != NULL)
+        {
+            Text("Original");
+            ImVec4 ref_col_v4(ref_col[0], ref_col[1], ref_col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : ref_col[3]);
+            if (ColorButton("##original", ref_col_v4, (flags & (ImGuiColorEditFlags_HDR|ImGuiColorEditFlags_AlphaPreview|ImGuiColorEditFlags_AlphaPreviewHalf|ImGuiColorEditFlags_NoTooltip)), ImVec2(square_sz * 3, square_sz * 2)))
+            {
+                memcpy(col, ref_col, components * sizeof(float));
+                value_changed = true;
+            }
+        }
+        PopItemFlag();
+        EndGroup();
+    }
+
+    // Convert back color to RGB
+    if (value_changed_h || value_changed_sv)
+        ColorConvertHSVtoRGB(H >= 1.0f ? H - 10 * 1e-6f : H, S > 0.0f ? S : 10*1e-6f, V > 0.0f ? V : 1e-6f, col[0], col[1], col[2]);
+
+    // R,G,B and H,S,V slider color editor
+    bool value_changed_fix_hue_wrap = false;
+    if ((flags & ImGuiColorEditFlags_NoInputs) == 0)
+    {
+        PushItemWidth((alpha_bar ? bar1_pos_x : bar0_pos_x) + bars_width - picker_pos.x);
+        ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoSmallPreview | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf;
+        ImGuiColorEditFlags sub_flags = (flags & sub_flags_to_forward) | ImGuiColorEditFlags_NoPicker;
+        if (flags & ImGuiColorEditFlags_RGB || (flags & ImGuiColorEditFlags__InputsMask) == 0)
+            if (ColorEdit4("##rgb", col, sub_flags | ImGuiColorEditFlags_RGB))
+            {
+                // FIXME: Hackily differenciating using the DragInt (ActiveId != 0 && !ActiveIdAllowOverlap) vs. using the InputText or DropTarget.
+                // For the later we don't want to run the hue-wrap canceling code. If you are well versed in HSV picker please provide your input! (See #2050)
+                value_changed_fix_hue_wrap = (g.ActiveId != 0 && !g.ActiveIdAllowOverlap);
+                value_changed = true;
+            }
+        if (flags & ImGuiColorEditFlags_HSV || (flags & ImGuiColorEditFlags__InputsMask) == 0)
+            value_changed |= ColorEdit4("##hsv", col, sub_flags | ImGuiColorEditFlags_HSV);
+        if (flags & ImGuiColorEditFlags_HEX || (flags & ImGuiColorEditFlags__InputsMask) == 0)
+            value_changed |= ColorEdit4("##hex", col, sub_flags | ImGuiColorEditFlags_HEX);
+        PopItemWidth();
+    }
+
+    // Try to cancel hue wrap (after ColorEdit4 call), if any
+    if (value_changed_fix_hue_wrap)
+    {
+        float new_H, new_S, new_V;
+        ColorConvertRGBtoHSV(col[0], col[1], col[2], new_H, new_S, new_V);
+        if (new_H <= 0 && H > 0)
+        {
+            if (new_V <= 0 && V != new_V)
+                ColorConvertHSVtoRGB(H, S, new_V <= 0 ? V * 0.5f : new_V, col[0], col[1], col[2]);
+            else if (new_S <= 0)
+                ColorConvertHSVtoRGB(H, new_S <= 0 ? S * 0.5f : new_S, new_V, col[0], col[1], col[2]);
+        }
+    }
+
+    ImVec4 hue_color_f(1, 1, 1, 1); ColorConvertHSVtoRGB(H, 1, 1, hue_color_f.x, hue_color_f.y, hue_color_f.z);
+    ImU32 hue_color32 = ColorConvertFloat4ToU32(hue_color_f);
+    ImU32 col32_no_alpha = ColorConvertFloat4ToU32(ImVec4(col[0], col[1], col[2], 1.0f));
+
+    const ImU32 hue_colors[6+1] = { IM_COL32(255,0,0,255), IM_COL32(255,255,0,255), IM_COL32(0,255,0,255), IM_COL32(0,255,255,255), IM_COL32(0,0,255,255), IM_COL32(255,0,255,255), IM_COL32(255,0,0,255) };
+    ImVec2 sv_cursor_pos;
+
+    if (flags & ImGuiColorEditFlags_PickerHueWheel)
+    {
+        // Render Hue Wheel
+        const float aeps = 1.5f / wheel_r_outer; // Half a pixel arc length in radians (2pi cancels out).
+        const int segment_per_arc = ImMax(4, (int)wheel_r_outer / 12);
+        for (int n = 0; n < 6; n++)
+        {
+            const float a0 = (n)     /6.0f * 2.0f * IM_PI - aeps;
+            const float a1 = (n+1.0f)/6.0f * 2.0f * IM_PI + aeps;
+            const int vert_start_idx = draw_list->VtxBuffer.Size;
+            draw_list->PathArcTo(wheel_center, (wheel_r_inner + wheel_r_outer)*0.5f, a0, a1, segment_per_arc);
+            draw_list->PathStroke(IM_COL32_WHITE, false, wheel_thickness);
+            const int vert_end_idx = draw_list->VtxBuffer.Size;
+
+            // Paint colors over existing vertices
+            ImVec2 gradient_p0(wheel_center.x + ImCos(a0) * wheel_r_inner, wheel_center.y + ImSin(a0) * wheel_r_inner);
+            ImVec2 gradient_p1(wheel_center.x + ImCos(a1) * wheel_r_inner, wheel_center.y + ImSin(a1) * wheel_r_inner);
+            ShadeVertsLinearColorGradientKeepAlpha(draw_list, vert_start_idx, vert_end_idx, gradient_p0, gradient_p1, hue_colors[n], hue_colors[n+1]);
+        }
+
+        // Render Cursor + preview on Hue Wheel
+        float cos_hue_angle = ImCos(H * 2.0f * IM_PI);
+        float sin_hue_angle = ImSin(H * 2.0f * IM_PI);
+        ImVec2 hue_cursor_pos(wheel_center.x + cos_hue_angle * (wheel_r_inner+wheel_r_outer)*0.5f, wheel_center.y + sin_hue_angle * (wheel_r_inner+wheel_r_outer)*0.5f);
+        float hue_cursor_rad = value_changed_h ? wheel_thickness * 0.65f : wheel_thickness * 0.55f;
+        int hue_cursor_segments = ImClamp((int)(hue_cursor_rad / 1.4f), 9, 32);
+        draw_list->AddCircleFilled(hue_cursor_pos, hue_cursor_rad, hue_color32, hue_cursor_segments);
+        draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad+1, IM_COL32(128,128,128,255), hue_cursor_segments);
+        draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad, IM_COL32_WHITE, hue_cursor_segments);
+
+        // Render SV triangle (rotated according to hue)
+        ImVec2 tra = wheel_center + ImRotate(triangle_pa, cos_hue_angle, sin_hue_angle);
+        ImVec2 trb = wheel_center + ImRotate(triangle_pb, cos_hue_angle, sin_hue_angle);
+        ImVec2 trc = wheel_center + ImRotate(triangle_pc, cos_hue_angle, sin_hue_angle);
+        ImVec2 uv_white = GetFontTexUvWhitePixel();
+        draw_list->PrimReserve(6, 6);
+        draw_list->PrimVtx(tra, uv_white, hue_color32);
+        draw_list->PrimVtx(trb, uv_white, hue_color32);
+        draw_list->PrimVtx(trc, uv_white, IM_COL32_WHITE);
+        draw_list->PrimVtx(tra, uv_white, IM_COL32_BLACK_TRANS);
+        draw_list->PrimVtx(trb, uv_white, IM_COL32_BLACK);
+        draw_list->PrimVtx(trc, uv_white, IM_COL32_BLACK_TRANS);
+        draw_list->AddTriangle(tra, trb, trc, IM_COL32(128,128,128,255), 1.5f);
+        sv_cursor_pos = ImLerp(ImLerp(trc, tra, ImSaturate(S)), trb, ImSaturate(1 - V));
+    }
+    else if (flags & ImGuiColorEditFlags_PickerHueBar)
+    {
+        // Render SV Square
+        draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size,sv_picker_size), IM_COL32_WHITE, hue_color32, hue_color32, IM_COL32_WHITE);
+        draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size,sv_picker_size), IM_COL32_BLACK_TRANS, IM_COL32_BLACK_TRANS, IM_COL32_BLACK, IM_COL32_BLACK);
+        RenderFrameBorder(picker_pos, picker_pos + ImVec2(sv_picker_size,sv_picker_size), 0.0f);
+        sv_cursor_pos.x = ImClamp((float)(int)(picker_pos.x + ImSaturate(S)     * sv_picker_size + 0.5f), picker_pos.x + 2, picker_pos.x + sv_picker_size - 2); // Sneakily prevent the circle to stick out too much
+        sv_cursor_pos.y = ImClamp((float)(int)(picker_pos.y + ImSaturate(1 - V) * sv_picker_size + 0.5f), picker_pos.y + 2, picker_pos.y + sv_picker_size - 2);
+
+        // Render Hue Bar
+        for (int i = 0; i < 6; ++i)
+            draw_list->AddRectFilledMultiColor(ImVec2(bar0_pos_x, picker_pos.y + i * (sv_picker_size / 6)), ImVec2(bar0_pos_x + bars_width, picker_pos.y + (i + 1) * (sv_picker_size / 6)), hue_colors[i], hue_colors[i], hue_colors[i + 1], hue_colors[i + 1]);
+        float bar0_line_y = (float)(int)(picker_pos.y + H * sv_picker_size + 0.5f);
+        RenderFrameBorder(ImVec2(bar0_pos_x, picker_pos.y), ImVec2(bar0_pos_x + bars_width, picker_pos.y + sv_picker_size), 0.0f);
+        RenderArrowsForVerticalBar(draw_list, ImVec2(bar0_pos_x - 1, bar0_line_y), ImVec2(bars_triangles_half_sz + 1, bars_triangles_half_sz), bars_width + 2.0f);
+    }
+
+    // Render cursor/preview circle (clamp S/V within 0..1 range because floating points colors may lead HSV values to be out of range)
+    float sv_cursor_rad = value_changed_sv ? 10.0f : 6.0f;
+    draw_list->AddCircleFilled(sv_cursor_pos, sv_cursor_rad, col32_no_alpha, 12);
+    draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad+1, IM_COL32(128,128,128,255), 12);
+    draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad, IM_COL32_WHITE, 12);
+
+    // Render alpha bar
+    if (alpha_bar)
+    {
+        float alpha = ImSaturate(col[3]);
+        ImRect bar1_bb(bar1_pos_x, picker_pos.y, bar1_pos_x + bars_width, picker_pos.y + sv_picker_size);
+        RenderColorRectWithAlphaCheckerboard(bar1_bb.Min, bar1_bb.Max, IM_COL32(0,0,0,0), bar1_bb.GetWidth() / 2.0f, ImVec2(0.0f, 0.0f));
+        draw_list->AddRectFilledMultiColor(bar1_bb.Min, bar1_bb.Max, col32_no_alpha, col32_no_alpha, col32_no_alpha & ~IM_COL32_A_MASK, col32_no_alpha & ~IM_COL32_A_MASK);
+        float bar1_line_y = (float)(int)(picker_pos.y + (1.0f - alpha) * sv_picker_size + 0.5f);
+        RenderFrameBorder(bar1_bb.Min, bar1_bb.Max, 0.0f);
+        RenderArrowsForVerticalBar(draw_list, ImVec2(bar1_pos_x - 1, bar1_line_y), ImVec2(bars_triangles_half_sz + 1, bars_triangles_half_sz), bars_width + 2.0f);
+    }
+
+    EndGroup();
+
+    if (value_changed && memcmp(backup_initial_col, col, components * sizeof(float)) == 0)
+        value_changed = false;
+    if (value_changed)
+        MarkItemEdited(window->DC.LastItemId);
+
+    PopID();
+
+    return value_changed;
+}
+
+// A little colored square. Return true when clicked.
+// FIXME: May want to display/ignore the alpha component in the color display? Yet show it in the tooltip.
+// 'desc_id' is not called 'label' because we don't display it next to the button, but only in the tooltip.
+bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFlags flags, ImVec2 size)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    if (window->SkipItems)
+        return false;
+
+    ImGuiContext& g = *GImGui;
+    const ImGuiID id = window->GetID(desc_id);
+    float default_size = GetFrameHeight();
+    if (size.x == 0.0f)
+        size.x = default_size;
+    if (size.y == 0.0f)
+        size.y = default_size;
+    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
+    ItemSize(bb, (size.y >= default_size) ? g.Style.FramePadding.y : 0.0f);
+    if (!ItemAdd(bb, id))
+        return false;
+
+    bool hovered, held;
+    bool pressed = ButtonBehavior(bb, id, &hovered, &held);
+
+    if (flags & ImGuiColorEditFlags_NoAlpha)
+        flags &= ~(ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf);
+
+    ImVec4 col_without_alpha(col.x, col.y, col.z, 1.0f);
+    float grid_step = ImMin(size.x, size.y) / 2.99f;
+    float rounding = ImMin(g.Style.FrameRounding, grid_step * 0.5f);
+    ImRect bb_inner = bb;
+    float off = -0.75f; // The border (using Col_FrameBg) tends to look off when color is near-opaque and rounding is enabled. This offset seemed like a good middle ground to reduce those artifacts.
+    bb_inner.Expand(off);
+    if ((flags & ImGuiColorEditFlags_AlphaPreviewHalf) && col.w < 1.0f)
+    {
+        float mid_x = (float)(int)((bb_inner.Min.x + bb_inner.Max.x) * 0.5f + 0.5f);
+        RenderColorRectWithAlphaCheckerboard(ImVec2(bb_inner.Min.x + grid_step, bb_inner.Min.y), bb_inner.Max, GetColorU32(col), grid_step, ImVec2(-grid_step + off, off), rounding, ImDrawCornerFlags_TopRight| ImDrawCornerFlags_BotRight);
+        window->DrawList->AddRectFilled(bb_inner.Min, ImVec2(mid_x, bb_inner.Max.y), GetColorU32(col_without_alpha), rounding, ImDrawCornerFlags_TopLeft|ImDrawCornerFlags_BotLeft);
+    }
+    else
+    {
+        // Because GetColorU32() multiplies by the global style Alpha and we don't want to display a checkerboard if the source code had no alpha
+        ImVec4 col_source = (flags & ImGuiColorEditFlags_AlphaPreview) ? col : col_without_alpha;
+        if (col_source.w < 1.0f)
+            RenderColorRectWithAlphaCheckerboard(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), grid_step, ImVec2(off, off), rounding);
+        else
+            window->DrawList->AddRectFilled(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), rounding, ImDrawCornerFlags_All);
+    }
+    RenderNavHighlight(bb, id);
+    if (g.Style.FrameBorderSize > 0.0f)
+        RenderFrameBorder(bb.Min, bb.Max, rounding);
+    else
+        window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), rounding); // Color button are often in need of some sort of border
+
+    // Drag and Drop Source
+    // NB: The ActiveId test is merely an optional micro-optimization, BeginDragDropSource() does the same test.
+    if (g.ActiveId == id && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropSource())
+    {
+        if (flags & ImGuiColorEditFlags_NoAlpha)
+            SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F, &col, sizeof(float) * 3, ImGuiCond_Once);
+        else
+            SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F, &col, sizeof(float) * 4, ImGuiCond_Once);
+        ColorButton(desc_id, col, flags);
+        SameLine();
+        TextUnformatted("Color");
+        EndDragDropSource();
+    }
+
+    // Tooltip
+    if (!(flags & ImGuiColorEditFlags_NoTooltip) && hovered)
+        ColorTooltip(desc_id, &col.x, flags & (ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf));
+
+    if (pressed)
+        MarkItemEdited(id);
+
+    return pressed;
+}
+
+void ImGui::SetColorEditOptions(ImGuiColorEditFlags flags)
+{
+    ImGuiContext& g = *GImGui;
+    if ((flags & ImGuiColorEditFlags__InputsMask) == 0)
+        flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__InputsMask;
+    if ((flags & ImGuiColorEditFlags__DataTypeMask) == 0)
+        flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__DataTypeMask;
+    if ((flags & ImGuiColorEditFlags__PickerMask) == 0)
+        flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__PickerMask;
+    IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__InputsMask)));   // Check only 1 option is selected
+    IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__DataTypeMask))); // Check only 1 option is selected
+    IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__PickerMask)));   // Check only 1 option is selected
+    g.ColorEditOptions = flags;
+}
+
+// Note: only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
+void ImGui::ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags flags)
+{
+    ImGuiContext& g = *GImGui;
+
+    int cr = IM_F32_TO_INT8_SAT(col[0]), cg = IM_F32_TO_INT8_SAT(col[1]), cb = IM_F32_TO_INT8_SAT(col[2]), ca = (flags & ImGuiColorEditFlags_NoAlpha) ? 255 : IM_F32_TO_INT8_SAT(col[3]);
+    BeginTooltipEx(0, true);
+
+    const char* text_end = text ? FindRenderedTextEnd(text, NULL) : text;
+    if (text_end > text)
+    {
+        TextUnformatted(text, text_end);
+        Separator();
+    }
+
+    ImVec2 sz(g.FontSize * 3 + g.Style.FramePadding.y * 2, g.FontSize * 3 + g.Style.FramePadding.y * 2);
+    ColorButton("##preview", ImVec4(col[0], col[1], col[2], col[3]), (flags & (ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf)) | ImGuiColorEditFlags_NoTooltip, sz);
+    SameLine();
+    if (flags & ImGuiColorEditFlags_NoAlpha)
+        Text("#%02X%02X%02X\nR: %d, G: %d, B: %d\n(%.3f, %.3f, %.3f)", cr, cg, cb, cr, cg, cb, col[0], col[1], col[2]);
+    else
+        Text("#%02X%02X%02X%02X\nR:%d, G:%d, B:%d, A:%d\n(%.3f, %.3f, %.3f, %.3f)", cr, cg, cb, ca, cr, cg, cb, ca, col[0], col[1], col[2], col[3]);
+    EndTooltip();
+}
+
+void ImGui::ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags)
+{
+    bool allow_opt_inputs = !(flags & ImGuiColorEditFlags__InputsMask);
+    bool allow_opt_datatype = !(flags & ImGuiColorEditFlags__DataTypeMask);
+    if ((!allow_opt_inputs && !allow_opt_datatype) || !BeginPopup("context"))
+        return;
+    ImGuiContext& g = *GImGui;
+    ImGuiColorEditFlags opts = g.ColorEditOptions;
+    if (allow_opt_inputs)
+    {
+        if (RadioButton("RGB", (opts & ImGuiColorEditFlags_RGB) != 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_RGB;
+        if (RadioButton("HSV", (opts & ImGuiColorEditFlags_HSV) != 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_HSV;
+        if (RadioButton("HEX", (opts & ImGuiColorEditFlags_HEX) != 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_HEX;
+    }
+    if (allow_opt_datatype)
+    {
+        if (allow_opt_inputs) Separator();
+        if (RadioButton("0..255",     (opts & ImGuiColorEditFlags_Uint8) != 0)) opts = (opts & ~ImGuiColorEditFlags__DataTypeMask) | ImGuiColorEditFlags_Uint8;
+        if (RadioButton("0.00..1.00", (opts & ImGuiColorEditFlags_Float) != 0)) opts = (opts & ~ImGuiColorEditFlags__DataTypeMask) | ImGuiColorEditFlags_Float;
+    }
+
+    if (allow_opt_inputs || allow_opt_datatype)
+        Separator();
+    if (Button("Copy as..", ImVec2(-1,0)))
+        OpenPopup("Copy");
+    if (BeginPopup("Copy"))
+    {
+        int cr = IM_F32_TO_INT8_SAT(col[0]), cg = IM_F32_TO_INT8_SAT(col[1]), cb = IM_F32_TO_INT8_SAT(col[2]), ca = (flags & ImGuiColorEditFlags_NoAlpha) ? 255 : IM_F32_TO_INT8_SAT(col[3]);
+        char buf[64];
+        ImFormatString(buf, IM_ARRAYSIZE(buf), "(%.3ff, %.3ff, %.3ff, %.3ff)", col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);
+        if (Selectable(buf))
+            SetClipboardText(buf);
+        ImFormatString(buf, IM_ARRAYSIZE(buf), "(%d,%d,%d,%d)", cr, cg, cb, ca);
+        if (Selectable(buf))
+            SetClipboardText(buf);
+        if (flags & ImGuiColorEditFlags_NoAlpha)
+            ImFormatString(buf, IM_ARRAYSIZE(buf), "0x%02X%02X%02X", cr, cg, cb);
+        else
+            ImFormatString(buf, IM_ARRAYSIZE(buf), "0x%02X%02X%02X%02X", cr, cg, cb, ca);
+        if (Selectable(buf))
+            SetClipboardText(buf);
+        EndPopup();
+    }
+
+    g.ColorEditOptions = opts;
+    EndPopup();
+}
+
+void ImGui::ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags flags)
+{
+    bool allow_opt_picker = !(flags & ImGuiColorEditFlags__PickerMask);
+    bool allow_opt_alpha_bar = !(flags & ImGuiColorEditFlags_NoAlpha) && !(flags & ImGuiColorEditFlags_AlphaBar);
+    if ((!allow_opt_picker && !allow_opt_alpha_bar) || !ImGui::BeginPopup("context"))
+        return;
+    ImGuiContext& g = *GImGui;
+    if (allow_opt_picker)
+    {
+        ImVec2 picker_size(g.FontSize * 8, ImMax(g.FontSize * 8 - (ImGui::GetFrameHeight() + g.Style.ItemInnerSpacing.x), 1.0f)); // FIXME: Picker size copied from main picker function
+        ImGui::PushItemWidth(picker_size.x);
+        for (int picker_type = 0; picker_type < 2; picker_type++)
+        {
+            // Draw small/thumbnail version of each picker type (over an invisible button for selection)
+            if (picker_type > 0) ImGui::Separator();
+            ImGui::PushID(picker_type);
+            ImGuiColorEditFlags picker_flags = ImGuiColorEditFlags_NoInputs|ImGuiColorEditFlags_NoOptions|ImGuiColorEditFlags_NoLabel|ImGuiColorEditFlags_NoSidePreview|(flags & ImGuiColorEditFlags_NoAlpha);
+            if (picker_type == 0) picker_flags |= ImGuiColorEditFlags_PickerHueBar;
+            if (picker_type == 1) picker_flags |= ImGuiColorEditFlags_PickerHueWheel;
+            ImVec2 backup_pos = ImGui::GetCursorScreenPos();
+            if (ImGui::Selectable("##selectable", false, 0, picker_size)) // By default, Selectable() is closing popup
+                g.ColorEditOptions = (g.ColorEditOptions & ~ImGuiColorEditFlags__PickerMask) | (picker_flags & ImGuiColorEditFlags__PickerMask);
+            ImGui::SetCursorScreenPos(backup_pos);
+            ImVec4 dummy_ref_col;
+            memcpy(&dummy_ref_col.x, ref_col, sizeof(float) * (picker_flags & ImGuiColorEditFlags_NoAlpha ? 3 : 4));
+            ImGui::ColorPicker4("##dummypicker", &dummy_ref_col.x, picker_flags);
+            ImGui::PopID();
+        }
+        ImGui::PopItemWidth();
+    }
+    if (allow_opt_alpha_bar)
+    {
+        if (allow_opt_picker) ImGui::Separator();
+        ImGui::CheckboxFlags("Alpha Bar", (unsigned int*)&g.ColorEditOptions, ImGuiColorEditFlags_AlphaBar);
+    }
+    ImGui::EndPopup();
+}
+
+//-------------------------------------------------------------------------
+// [SECTION] Widgets: TreeNode, CollapsingHeader, etc.
+//-------------------------------------------------------------------------
+// - TreeNode()
+// - TreeNodeV()
+// - TreeNodeEx()
+// - TreeNodeExV()
+// - TreeNodeBehavior() [Internal]  
+// - TreePush()
+// - TreePop()
+// - TreeAdvanceToLabelPos()
+// - GetTreeNodeToLabelSpacing()
+// - SetNextTreeNodeOpen()
+// - CollapsingHeader()
+//-------------------------------------------------------------------------
+
+bool ImGui::TreeNode(const char* str_id, const char* fmt, ...)
+{
+    va_list args;
+    va_start(args, fmt);
+    bool is_open = TreeNodeExV(str_id, 0, fmt, args);
+    va_end(args);
+    return is_open;
+}
+
+bool ImGui::TreeNode(const void* ptr_id, const char* fmt, ...)
+{
+    va_list args;
+    va_start(args, fmt);
+    bool is_open = TreeNodeExV(ptr_id, 0, fmt, args);
+    va_end(args);
+    return is_open;
+}
+
+bool ImGui::TreeNode(const char* label)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    if (window->SkipItems)
+        return false;
+    return TreeNodeBehavior(window->GetID(label), 0, label, NULL);
+}
+
+bool ImGui::TreeNodeV(const char* str_id, const char* fmt, va_list args)
+{
+    return TreeNodeExV(str_id, 0, fmt, args);
+}
+
+bool ImGui::TreeNodeV(const void* ptr_id, const char* fmt, va_list args)
+{
+    return TreeNodeExV(ptr_id, 0, fmt, args);
+}
+
+bool ImGui::TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    if (window->SkipItems)
+        return false;
+
+    return TreeNodeBehavior(window->GetID(label), flags, label, NULL);
+}
+
+bool ImGui::TreeNodeEx(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, ...)
+{
+    va_list args;
+    va_start(args, fmt);
+    bool is_open = TreeNodeExV(str_id, flags, fmt, args);
+    va_end(args);
+    return is_open;
+}
+
+bool ImGui::TreeNodeEx(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, ...)
+{
+    va_list args;
+    va_start(args, fmt);
+    bool is_open = TreeNodeExV(ptr_id, flags, fmt, args);
+    va_end(args);
+    return is_open;
+}
+
+bool ImGui::TreeNodeExV(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    if (window->SkipItems)
+        return false;
+
+    ImGuiContext& g = *GImGui;
+    const char* label_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
+    return TreeNodeBehavior(window->GetID(str_id), flags, g.TempBuffer, label_end);
+}
+
+bool ImGui::TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    if (window->SkipItems)
+        return false;
+
+    ImGuiContext& g = *GImGui;
+    const char* label_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
+    return TreeNodeBehavior(window->GetID(ptr_id), flags, g.TempBuffer, label_end);
+}
+
+bool ImGui::TreeNodeBehaviorIsOpen(ImGuiID id, ImGuiTreeNodeFlags flags)
+{
+    if (flags & ImGuiTreeNodeFlags_Leaf)
+        return true;
+
+    // We only write to the tree storage if the user clicks (or explicitly use SetNextTreeNode*** functions)
+    ImGuiContext& g = *GImGui;
+    ImGuiWindow* window = g.CurrentWindow;
+    ImGuiStorage* storage = window->DC.StateStorage;
+
+    bool is_open;
+    if (g.NextTreeNodeOpenCond != 0)
+    {
+        if (g.NextTreeNodeOpenCond & ImGuiCond_Always)
+        {
+            is_open = g.NextTreeNodeOpenVal;
+            storage->SetInt(id, is_open);
+        }
+        else
+        {
+            // We treat ImGuiCond_Once and ImGuiCond_FirstUseEver the same because tree node state are not saved persistently.
+            const int stored_value = storage->GetInt(id, -1);
+            if (stored_value == -1)
+            {
+                is_open = g.NextTreeNodeOpenVal;
+                storage->SetInt(id, is_open);
+            }
+            else
+            {
+                is_open = stored_value != 0;
+            }
+        }
+        g.NextTreeNodeOpenCond = 0;
+    }
+    else
+    {
+        is_open = storage->GetInt(id, (flags & ImGuiTreeNodeFlags_DefaultOpen) ? 1 : 0) != 0;
+    }
+
+    // When logging is enabled, we automatically expand tree nodes (but *NOT* collapsing headers.. seems like sensible behavior).
+    // NB- If we are above max depth we still allow manually opened nodes to be logged.
+    if (g.LogEnabled && !(flags & ImGuiTreeNodeFlags_NoAutoOpenOnLog) && window->DC.TreeDepth < g.LogAutoExpandMaxDepth)
+        is_open = true;
+
+    return is_open;
+}
+
+bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    if (window->SkipItems)
+        return false;
+
+    ImGuiContext& g = *GImGui;
+    const ImGuiStyle& style = g.Style;
+    const bool display_frame = (flags & ImGuiTreeNodeFlags_Framed) != 0;
+    const ImVec2 padding = (display_frame || (flags & ImGuiTreeNodeFlags_FramePadding)) ? style.FramePadding : ImVec2(style.FramePadding.x, 0.0f);
+
+    if (!label_end)
+        label_end = FindRenderedTextEnd(label);
+    const ImVec2 label_size = CalcTextSize(label, label_end, false);
+
+    // We vertically grow up to current line height up the typical widget height.
+    const float text_base_offset_y = ImMax(padding.y, window->DC.CurrentLineTextBaseOffset); // Latch before ItemSize changes it
+    const float frame_height = ImMax(ImMin(window->DC.CurrentLineSize.y, g.FontSize + style.FramePadding.y*2), label_size.y + padding.y*2);
+    ImRect frame_bb = ImRect(window->DC.CursorPos, ImVec2(window->Pos.x + GetContentRegionMax().x, window->DC.CursorPos.y + frame_height));
+    if (display_frame)
+    {
+        // Framed header expand a little outside the default padding
+        frame_bb.Min.x -= (float)(int)(window->WindowPadding.x*0.5f) - 1;
+        frame_bb.Max.x += (float)(int)(window->WindowPadding.x*0.5f) - 1;
+    }
+
+    const float text_offset_x = (g.FontSize + (display_frame ? padding.x*3 : padding.x*2));   // Collapser arrow width + Spacing
+    const float text_width = g.FontSize + (label_size.x > 0.0f ? label_size.x + padding.x*2 : 0.0f);   // Include collapser
+    ItemSize(ImVec2(text_width, frame_height), text_base_offset_y);
+
+    // For regular tree nodes, we arbitrary allow to click past 2 worth of ItemSpacing
+    // (Ideally we'd want to add a flag for the user to specify if we want the hit test to be done up to the right side of the content or not)
+    const ImRect interact_bb = display_frame ? frame_bb : ImRect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + text_width + style.ItemSpacing.x*2, frame_bb.Max.y);
+    bool is_open = TreeNodeBehaviorIsOpen(id, flags);
+
+    // Store a flag for the current depth to tell if we will allow closing this node when navigating one of its child.
+    // For this purpose we essentially compare if g.NavIdIsAlive went from 0 to 1 between TreeNode() and TreePop().
+    // This is currently only support 32 level deep and we are fine with (1 << Depth) overflowing into a zero.
+    if (is_open && !g.NavIdIsAlive && (flags & ImGuiTreeNodeFlags_NavLeftJumpsBackHere) && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
+        window->DC.TreeDepthMayJumpToParentOnPop |= (1 << window->DC.TreeDepth);
+
+    bool item_add = ItemAdd(interact_bb, id);
+    window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_HasDisplayRect;
+    window->DC.LastItemDisplayRect = frame_bb;
+
+    if (!item_add)
+    {
+        if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
+            TreePushRawID(id);
+        return is_open;
+    }
+
+    // Flags that affects opening behavior:
+    // - 0(default) ..................... single-click anywhere to open
+    // - OpenOnDoubleClick .............. double-click anywhere to open
+    // - OpenOnArrow .................... single-click on arrow to open
+    // - OpenOnDoubleClick|OpenOnArrow .. single-click on arrow or double-click anywhere to open
+    ImGuiButtonFlags button_flags = ImGuiButtonFlags_NoKeyModifiers | ((flags & ImGuiTreeNodeFlags_AllowItemOverlap) ? ImGuiButtonFlags_AllowItemOverlap : 0);
+    if (!(flags & ImGuiTreeNodeFlags_Leaf))
+        button_flags |= ImGuiButtonFlags_PressedOnDragDropHold;
+    if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick)
+        button_flags |= ImGuiButtonFlags_PressedOnDoubleClick | ((flags & ImGuiTreeNodeFlags_OpenOnArrow) ? ImGuiButtonFlags_PressedOnClickRelease : 0);
+
+    bool hovered, held, pressed = ButtonBehavior(interact_bb, id, &hovered, &held, button_flags);
+    if (!(flags & ImGuiTreeNodeFlags_Leaf))
+    {
+        bool toggled = false;
+        if (pressed)
+        {
+            toggled = !(flags & (ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick)) || (g.NavActivateId == id);
+            if (flags & ImGuiTreeNodeFlags_OpenOnArrow)
+                toggled |= IsMouseHoveringRect(interact_bb.Min, ImVec2(interact_bb.Min.x + text_offset_x, interact_bb.Max.y)) && (!g.NavDisableMouseHover);
+            if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick)
+                toggled |= g.IO.MouseDoubleClicked[0];
+            if (g.DragDropActive && is_open) // When using Drag and Drop "hold to open" we keep the node highlighted after opening, but never close it again.
+                toggled = false;
+        }
+
+        if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Left && is_open)
+        {
+            toggled = true;
+            NavMoveRequestCancel();
+        }
+        if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Right && !is_open) // If there's something upcoming on the line we may want to give it the priority?
+        {
+            toggled = true;
+            NavMoveRequestCancel();
+        }
+
+        if (toggled)
+        {
+            is_open = !is_open;
+            window->DC.StateStorage->SetInt(id, is_open);
+        }
+    }
+    if (flags & ImGuiTreeNodeFlags_AllowItemOverlap)
+        SetItemAllowOverlap();
+
+    // Render
+    const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
+    const ImVec2 text_pos = frame_bb.Min + ImVec2(text_offset_x, text_base_offset_y);
+    if (display_frame)
+    {
+        // Framed type
+        RenderFrame(frame_bb.Min, frame_bb.Max, col, true, style.FrameRounding);
+        RenderNavHighlight(frame_bb, id, ImGuiNavHighlightFlags_TypeThin);
+        RenderArrow(frame_bb.Min + ImVec2(padding.x, text_base_offset_y), is_open ? ImGuiDir_Down : ImGuiDir_Right, 1.0f);
+        if (g.LogEnabled)
+        {
+            // NB: '##' is normally used to hide text (as a library-wide feature), so we need to specify the text range to make sure the ## aren't stripped out here.
+            const char log_prefix[] = "\n##";
+            const char log_suffix[] = "##";
+            LogRenderedText(&text_pos, log_prefix, log_prefix+3);
+            RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size);
+            LogRenderedText(&text_pos, log_suffix+1, log_suffix+3);
+        }
+        else
+        {
+            RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size);
+        }
+    }
+    else
+    {
+        // Unframed typed for tree nodes
+        if (hovered || (flags & ImGuiTreeNodeFlags_Selected))
+        {
+            RenderFrame(frame_bb.Min, frame_bb.Max, col, false);
+            RenderNavHighlight(frame_bb, id, ImGuiNavHighlightFlags_TypeThin);
+        }
+
+        if (flags & ImGuiTreeNodeFlags_Bullet)
+            RenderBullet(frame_bb.Min + ImVec2(text_offset_x * 0.5f, g.FontSize*0.50f + text_base_offset_y));
+        else if (!(flags & ImGuiTreeNodeFlags_Leaf))
+            RenderArrow(frame_bb.Min + ImVec2(padding.x, g.FontSize*0.15f + text_base_offset_y), is_open ? ImGuiDir_Down : ImGuiDir_Right, 0.70f);
+        if (g.LogEnabled)
+            LogRenderedText(&text_pos, ">");
+        RenderText(text_pos, label, label_end, false);
+    }
+
+    if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
+        TreePushRawID(id);
+    return is_open;
+}
+
+void ImGui::TreePush(const char* str_id)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    Indent();
+    window->DC.TreeDepth++;
+    PushID(str_id ? str_id : "#TreePush");
+}
+
+void ImGui::TreePush(const void* ptr_id)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    Indent();
+    window->DC.TreeDepth++;
+    PushID(ptr_id ? ptr_id : (const void*)"#TreePush");
+}
+
+void ImGui::TreePushRawID(ImGuiID id)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    Indent();
+    window->DC.TreeDepth++;
+    window->IDStack.push_back(id);
+}
+
+void ImGui::TreePop()
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiWindow* window = g.CurrentWindow;
+    Unindent();
+
+    window->DC.TreeDepth--;
+    if (g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet())
+        if (g.NavIdIsAlive && (window->DC.TreeDepthMayJumpToParentOnPop & (1 << window->DC.TreeDepth)))
+        {
+            SetNavID(window->IDStack.back(), g.NavLayer);
+            NavMoveRequestCancel();
+        }
+    window->DC.TreeDepthMayJumpToParentOnPop &= (1 << window->DC.TreeDepth) - 1;
+
+    IM_ASSERT(window->IDStack.Size > 1); // There should always be 1 element in the IDStack (pushed during window creation). If this triggers you called TreePop/PopID too much.
+    PopID();
+}
+
+void ImGui::TreeAdvanceToLabelPos()
+{
+    ImGuiContext& g = *GImGui;
+    g.CurrentWindow->DC.CursorPos.x += GetTreeNodeToLabelSpacing();
+}
+
+// Horizontal distance preceding label when using TreeNode() or Bullet()
+float ImGui::GetTreeNodeToLabelSpacing()
+{
+    ImGuiContext& g = *GImGui;
+    return g.FontSize + (g.Style.FramePadding.x * 2.0f);
+}
+
+void ImGui::SetNextTreeNodeOpen(bool is_open, ImGuiCond cond)
+{
+    ImGuiContext& g = *GImGui;
+    if (g.CurrentWindow->SkipItems)
+        return;
+    g.NextTreeNodeOpenVal = is_open;
+    g.NextTreeNodeOpenCond = cond ? cond : ImGuiCond_Always;
+}
+
+// CollapsingHeader returns true when opened but do not indent nor push into the ID stack (because of the ImGuiTreeNodeFlags_NoTreePushOnOpen flag).
+// This is basically the same as calling TreeNodeEx(label, ImGuiTreeNodeFlags_CollapsingHeader). You can remove the _NoTreePushOnOpen flag if you want behavior closer to normal TreeNode().
+bool ImGui::CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    if (window->SkipItems)
+        return false;
+
+    return TreeNodeBehavior(window->GetID(label), flags | ImGuiTreeNodeFlags_CollapsingHeader, label);
+}
+
+bool ImGui::CollapsingHeader(const char* label, bool* p_open, ImGuiTreeNodeFlags flags)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    if (window->SkipItems)
+        return false;
+
+    if (p_open && !*p_open)
+        return false;
+
+    ImGuiID id = window->GetID(label);
+    bool is_open = TreeNodeBehavior(id, flags | ImGuiTreeNodeFlags_CollapsingHeader | (p_open ? ImGuiTreeNodeFlags_AllowItemOverlap : 0), label);
+    if (p_open)
+    {
+        // Create a small overlapping close button // FIXME: We can evolve this into user accessible helpers to add extra buttons on title bars, headers, etc.
+        ImGuiContext& g = *GImGui;
+        ImGuiItemHoveredDataBackup last_item_backup;
+        float button_radius = g.FontSize * 0.5f;
+        ImVec2 button_center = ImVec2(ImMin(window->DC.LastItemRect.Max.x, window->ClipRect.Max.x) - g.Style.FramePadding.x - button_radius, window->DC.LastItemRect.GetCenter().y);
+        if (CloseButton(window->GetID((void*)(intptr_t)(id+1)), button_center, button_radius))
+            *p_open = false;
+        last_item_backup.Restore();
+    }
+
+    return is_open;
+}
+
+//-------------------------------------------------------------------------
+// [SECTION] Widgets: Selectable
+//-------------------------------------------------------------------------
+// - Selectable()
+//-------------------------------------------------------------------------
+
+// Tip: pass an empty label (e.g. "##dummy") then you can use the space to draw other text or image.
+// But you need to make sure the ID is unique, e.g. enclose calls in PushID/PopID or use ##unique_id.
+bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags flags, const ImVec2& size_arg)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    if (window->SkipItems)
+        return false;
+
+    ImGuiContext& g = *GImGui;
+    const ImGuiStyle& style = g.Style;
+
+    if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet) // FIXME-OPT: Avoid if vertically clipped.
+        PopClipRect();
+
+    ImGuiID id = window->GetID(label);
+    ImVec2 label_size = CalcTextSize(label, NULL, true);
+    ImVec2 size(size_arg.x != 0.0f ? size_arg.x : label_size.x, size_arg.y != 0.0f ? size_arg.y : label_size.y);
+    ImVec2 pos = window->DC.CursorPos;
+    pos.y += window->DC.CurrentLineTextBaseOffset;
+    ImRect bb_inner(pos, pos + size);
+    ItemSize(bb_inner);
+
+    // Fill horizontal space.
+    ImVec2 window_padding = window->WindowPadding;
+    float max_x = (flags & ImGuiSelectableFlags_SpanAllColumns) ? GetWindowContentRegionMax().x : GetContentRegionMax().x;
+    float w_draw = ImMax(label_size.x, window->Pos.x + max_x - window_padding.x - window->DC.CursorPos.x);
+    ImVec2 size_draw((size_arg.x != 0 && !(flags & ImGuiSelectableFlags_DrawFillAvailWidth)) ? size_arg.x : w_draw, size_arg.y != 0.0f ? size_arg.y : size.y);
+    ImRect bb(pos, pos + size_draw);
+    if (size_arg.x == 0.0f || (flags & ImGuiSelectableFlags_DrawFillAvailWidth))
+        bb.Max.x += window_padding.x;
+
+    // Selectables are tightly packed together, we extend the box to cover spacing between selectable.
+    float spacing_L = (float)(int)(style.ItemSpacing.x * 0.5f);
+    float spacing_U = (float)(int)(style.ItemSpacing.y * 0.5f);
+    float spacing_R = style.ItemSpacing.x - spacing_L;
+    float spacing_D = style.ItemSpacing.y - spacing_U;
+    bb.Min.x -= spacing_L;
+    bb.Min.y -= spacing_U;
+    bb.Max.x += spacing_R;
+    bb.Max.y += spacing_D;
+    if (!ItemAdd(bb, (flags & ImGuiSelectableFlags_Disabled) ? 0 : id))
+    {
+        if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet)
+            PushColumnClipRect();
+        return false;
+    }
+
+    // We use NoHoldingActiveID on menus so user can click and _hold_ on a menu then drag to browse child entries
+    ImGuiButtonFlags button_flags = 0;
+    if (flags & ImGuiSelectableFlags_NoHoldingActiveID) button_flags |= ImGuiButtonFlags_NoHoldingActiveID;
+    if (flags & ImGuiSelectableFlags_PressedOnClick) button_flags |= ImGuiButtonFlags_PressedOnClick;
+    if (flags & ImGuiSelectableFlags_PressedOnRelease) button_flags |= ImGuiButtonFlags_PressedOnRelease;
+    if (flags & ImGuiSelectableFlags_Disabled) button_flags |= ImGuiButtonFlags_Disabled;
+    if (flags & ImGuiSelectableFlags_AllowDoubleClick) button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick;
+    bool hovered, held;
+    bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags);
+    if (flags & ImGuiSelectableFlags_Disabled)
+        selected = false;
+
+    // Hovering selectable with mouse updates NavId accordingly so navigation can be resumed with gamepad/keyboard (this doesn't happen on most widgets)
+    if (pressed || hovered)
+        if (!g.NavDisableMouseHover && g.NavWindow == window && g.NavLayer == window->DC.NavLayerCurrent)
+        {
+            g.NavDisableHighlight = true;
+            SetNavID(id, window->DC.NavLayerCurrent);
+        }
+    if (pressed)
+        MarkItemEdited(id);
+
+    // Render
+    if (hovered || selected)
+    {
+        const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
+        RenderFrame(bb.Min, bb.Max, col, false, 0.0f);
+        RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_TypeThin | ImGuiNavHighlightFlags_NoRounding);
+    }
+
+    if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet)
+    {
+        PushColumnClipRect();
+        bb.Max.x -= (GetContentRegionMax().x - max_x);
+    }
+
+    if (flags & ImGuiSelectableFlags_Disabled) PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]);
+    RenderTextClipped(bb_inner.Min, bb.Max, label, NULL, &label_size, ImVec2(0.0f,0.0f));
+    if (flags & ImGuiSelectableFlags_Disabled) PopStyleColor();
+
+    // Automatically close popups
+    if (pressed && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_DontClosePopups) && !(window->DC.ItemFlags & ImGuiItemFlags_SelectableDontClosePopup))
+        CloseCurrentPopup();
+    return pressed;
+}
+
+bool ImGui::Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags flags, const ImVec2& size_arg)
+{
+    if (Selectable(label, *p_selected, flags, size_arg))
+    {
+        *p_selected = !*p_selected;
+        return true;
+    }
+    return false;
+}
+
+//-------------------------------------------------------------------------
+// [SECTION] Widgets: ListBox
+//-------------------------------------------------------------------------
+// - ListBox()
+// - ListBoxHeader()
+// - ListBoxFooter()
+//-------------------------------------------------------------------------
+
+// FIXME: Rename to BeginListBox()
+// Helper to calculate the size of a listbox and display a label on the right.
+// Tip: To have a list filling the entire window width, PushItemWidth(-1) and pass an empty label "##empty"
+bool ImGui::ListBoxHeader(const char* label, const ImVec2& size_arg)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    if (window->SkipItems)
+        return false;
+
+    const ImGuiStyle& style = GetStyle();
+    const ImGuiID id = GetID(label);
+    const ImVec2 label_size = CalcTextSize(label, NULL, true);
+
+    // Size default to hold ~7 items. Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar.
+    ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), GetTextLineHeightWithSpacing() * 7.4f + style.ItemSpacing.y);
+    ImVec2 frame_size = ImVec2(size.x, ImMax(size.y, label_size.y));
+    ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);
+    ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
+    window->DC.LastItemRect = bb; // Forward storage for ListBoxFooter.. dodgy.
+
+    BeginGroup();
+    if (label_size.x > 0)
+        RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
+
+    BeginChildFrame(id, frame_bb.GetSize());
+    return true;
+}
+
+// FIXME: Rename to BeginListBox()
+bool ImGui::ListBoxHeader(const char* label, int items_count, int height_in_items)
+{
+    // Size default to hold ~7 items. Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar.
+    // We don't add +0.40f if items_count <= height_in_items. It is slightly dodgy, because it means a dynamic list of items will make the widget resize occasionally when it crosses that size.
+    // I am expecting that someone will come and complain about this behavior in a remote future, then we can advise on a better solution.
+    if (height_in_items < 0)
+        height_in_items = ImMin(items_count, 7);
+    float height_in_items_f = height_in_items < items_count ? (height_in_items + 0.40f) : (height_in_items + 0.00f);
+
+    // We include ItemSpacing.y so that a list sized for the exact number of items doesn't make a scrollbar appears. We could also enforce that by passing a flag to BeginChild().
+    ImVec2 size;
+    size.x = 0.0f;
+    size.y = GetTextLineHeightWithSpacing() * height_in_items_f + GetStyle().ItemSpacing.y;
+    return ListBoxHeader(label, size);
+}
+
+// FIXME: Rename to EndListBox()
+void ImGui::ListBoxFooter()
+{
+    ImGuiWindow* parent_window = GetCurrentWindow()->ParentWindow;
+    const ImRect bb = parent_window->DC.LastItemRect;
+    const ImGuiStyle& style = GetStyle();
+
+    EndChildFrame();
+
+    // Redeclare item size so that it includes the label (we have stored the full size in LastItemRect)
+    // We call SameLine() to restore DC.CurrentLine* data
+    SameLine();
+    parent_window->DC.CursorPos = bb.Min;
+    ItemSize(bb, style.FramePadding.y);
+    EndGroup();
+}
+
+bool ImGui::ListBox(const char* label, int* current_item, const char* const items[], int items_count, int height_items)
+{
+    const bool value_changed = ListBox(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_items);
+    return value_changed;
+}
+
+bool ImGui::ListBox(const char* label, int* current_item, bool (*items_getter)(void*, int, const char**), void* data, int items_count, int height_in_items)
+{
+    if (!ListBoxHeader(label, items_count, height_in_items))
+        return false;
+
+    // Assume all items have even height (= 1 line of text). If you need items of different or variable sizes you can create a custom version of ListBox() in your code without using the clipper.
+    ImGuiContext& g = *GImGui;
+    bool value_changed = false;
+    ImGuiListClipper clipper(items_count, GetTextLineHeightWithSpacing()); // We know exactly our line height here so we pass it as a minor optimization, but generally you don't need to.
+    while (clipper.Step())
+        for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
+        {
+            const bool item_selected = (i == *current_item);
+            const char* item_text;
+            if (!items_getter(data, i, &item_text))
+                item_text = "*Unknown item*";
+
+            PushID(i);
+            if (Selectable(item_text, item_selected))
+            {
+                *current_item = i;
+                value_changed = true;
+            }
+            if (item_selected)
+                SetItemDefaultFocus();
+            PopID();
+        }
+    ListBoxFooter();
+    if (value_changed)
+        MarkItemEdited(g.CurrentWindow->DC.LastItemId);
+
+    return value_changed;
+}
+
+//-------------------------------------------------------------------------
+// [SECTION] Widgets: PlotLines, PlotHistogram
+//-------------------------------------------------------------------------
+// - PlotEx() [Internal]
+// - PlotLines()
+// - PlotHistogram()
+//-------------------------------------------------------------------------
+
+void ImGui::PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    if (window->SkipItems)
+        return;
+
+    ImGuiContext& g = *GImGui;
+    const ImGuiStyle& style = g.Style;
+
+    const ImVec2 label_size = CalcTextSize(label, NULL, true);
+    if (graph_size.x == 0.0f)
+        graph_size.x = CalcItemWidth();
+    if (graph_size.y == 0.0f)
+        graph_size.y = label_size.y + (style.FramePadding.y * 2);
+
+    const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(graph_size.x, graph_size.y));
+    const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding);
+    const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0));
+    ItemSize(total_bb, style.FramePadding.y);
+    if (!ItemAdd(total_bb, 0, &frame_bb))
+        return;
+    const bool hovered = ItemHoverable(inner_bb, 0);
+
+    // Determine scale from values if not specified
+    if (scale_min == FLT_MAX || scale_max == FLT_MAX)
+    {
+        float v_min = FLT_MAX;
+        float v_max = -FLT_MAX;
+        for (int i = 0; i < values_count; i++)
+        {
+            const float v = values_getter(data, i);
+            v_min = ImMin(v_min, v);
+            v_max = ImMax(v_max, v);
+        }
+        if (scale_min == FLT_MAX)
+            scale_min = v_min;
+        if (scale_max == FLT_MAX)
+            scale_max = v_max;
+    }
+
+    RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
+
+    if (values_count > 0)
+    {
+        int res_w = ImMin((int)graph_size.x, values_count) + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0);
+        int item_count = values_count + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0);
+
+        // Tooltip on hover
+        int v_hovered = -1;
+        if (hovered)
+        {
+            const float t = ImClamp((g.IO.MousePos.x - inner_bb.Min.x) / (inner_bb.Max.x - inner_bb.Min.x), 0.0f, 0.9999f);
+            const int v_idx = (int)(t * item_count);
+            IM_ASSERT(v_idx >= 0 && v_idx < values_count);
+
+            const float v0 = values_getter(data, (v_idx + values_offset) % values_count);
+            const float v1 = values_getter(data, (v_idx + 1 + values_offset) % values_count);
+            if (plot_type == ImGuiPlotType_Lines)
+                SetTooltip("%d: %8.4g\n%d: %8.4g", v_idx, v0, v_idx+1, v1);
+            else if (plot_type == ImGuiPlotType_Histogram)
+                SetTooltip("%d: %8.4g", v_idx, v0);
+            v_hovered = v_idx;
+        }
+
+        const float t_step = 1.0f / (float)res_w;
+        const float inv_scale = (scale_min == scale_max) ? 0.0f : (1.0f / (scale_max - scale_min));
+
+        float v0 = values_getter(data, (0 + values_offset) % values_count);
+        float t0 = 0.0f;
+        ImVec2 tp0 = ImVec2( t0, 1.0f - ImSaturate((v0 - scale_min) * inv_scale) );                       // Point in the normalized space of our target rectangle
+        float histogram_zero_line_t = (scale_min * scale_max < 0.0f) ? (-scale_min * inv_scale) : (scale_min < 0.0f ? 0.0f : 1.0f);   // Where does the zero line stands
+
+        const ImU32 col_base = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLines : ImGuiCol_PlotHistogram);
+        const ImU32 col_hovered = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLinesHovered : ImGuiCol_PlotHistogramHovered);
+
+        for (int n = 0; n < res_w; n++)
+        {
+            const float t1 = t0 + t_step;
+            const int v1_idx = (int)(t0 * item_count + 0.5f);
+            IM_ASSERT(v1_idx >= 0 && v1_idx < values_count);
+            const float v1 = values_getter(data, (v1_idx + values_offset + 1) % values_count);
+            const ImVec2 tp1 = ImVec2( t1, 1.0f - ImSaturate((v1 - scale_min) * inv_scale) );
+
+            // NB: Draw calls are merged together by the DrawList system. Still, we should render our batch are lower level to save a bit of CPU.
+            ImVec2 pos0 = ImLerp(inner_bb.Min, inner_bb.Max, tp0);
+            ImVec2 pos1 = ImLerp(inner_bb.Min, inner_bb.Max, (plot_type == ImGuiPlotType_Lines) ? tp1 : ImVec2(tp1.x, histogram_zero_line_t));
+            if (plot_type == ImGuiPlotType_Lines)
+            {
+                window->DrawList->AddLine(pos0, pos1, v_hovered == v1_idx ? col_hovered : col_base);
+            }
+            else if (plot_type == ImGuiPlotType_Histogram)
+            {
+                if (pos1.x >= pos0.x + 2.0f)
+                    pos1.x -= 1.0f;
+                window->DrawList->AddRectFilled(pos0, pos1, v_hovered == v1_idx ? col_hovered : col_base);
+            }
+
+            t0 = t1;
+            tp0 = tp1;
+        }
+    }
+
+    // Text overlay
+    if (overlay_text)
+        RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, overlay_text, NULL, NULL, ImVec2(0.5f,0.0f));
+
+    if (label_size.x > 0.0f)
+        RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label);
+}
+
+struct ImGuiPlotArrayGetterData
+{
+    const float* Values;
+    int Stride;
+
+    ImGuiPlotArrayGetterData(const float* values, int stride) { Values = values; Stride = stride; }
+};
+
+static float Plot_ArrayGetter(void* data, int idx)
+{
+    ImGuiPlotArrayGetterData* plot_data = (ImGuiPlotArrayGetterData*)data;
+    const float v = *(const float*)(const void*)((const unsigned char*)plot_data->Values + (size_t)idx * plot_data->Stride);
+    return v;
+}
+
+void ImGui::PlotLines(const char* label, const float* values, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size, int stride)
+{
+    ImGuiPlotArrayGetterData data(values, stride);
+    PlotEx(ImGuiPlotType_Lines, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
+}
+
+void ImGui::PlotLines(const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size)
+{
+    PlotEx(ImGuiPlotType_Lines, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
+}
+
+void ImGui::PlotHistogram(const char* label, const float* values, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size, int stride)
+{
+    ImGuiPlotArrayGetterData data(values, stride);
+    PlotEx(ImGuiPlotType_Histogram, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
+}
+
+void ImGui::PlotHistogram(const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size)
+{
+    PlotEx(ImGuiPlotType_Histogram, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
+}
+
+//-------------------------------------------------------------------------
+// [SECTION] Widgets: Value helpers
+// Those is not very useful, legacy API.
+//-------------------------------------------------------------------------
+// - Value()
+//-------------------------------------------------------------------------
+
+void ImGui::Value(const char* prefix, bool b)
+{
+    Text("%s: %s", prefix, (b ? "true" : "false"));
+}
+
+void ImGui::Value(const char* prefix, int v)
+{
+    Text("%s: %d", prefix, v);
+}
+
+void ImGui::Value(const char* prefix, unsigned int v)
+{
+    Text("%s: %d", prefix, v);
+}
+
+void ImGui::Value(const char* prefix, float v, const char* float_format)
+{
+    if (float_format)
+    {
+        char fmt[64];
+        ImFormatString(fmt, IM_ARRAYSIZE(fmt), "%%s: %s", float_format);
+        Text(fmt, prefix, v);
+    }
+    else
+    {
+        Text("%s: %.3f", prefix, v);
+    }
+}
+
+//-------------------------------------------------------------------------
+// [SECTION] MenuItem, BeginMenu, EndMenu, etc.
+//-------------------------------------------------------------------------
+// - ImGuiMenuColumns [Internal]
+// - BeginMainMenuBar()
+// - EndMainMenuBar()
+// - BeginMenuBar()
+// - EndMenuBar()
+// - BeginMenu()
+// - EndMenu()
+// - MenuItem()
+//-------------------------------------------------------------------------
+
+// Helpers for internal use
+ImGuiMenuColumns::ImGuiMenuColumns()
+{
+    Count = 0;
+    Spacing = Width = NextWidth = 0.0f;
+    memset(Pos, 0, sizeof(Pos));
+    memset(NextWidths, 0, sizeof(NextWidths));
+}
+
+void ImGuiMenuColumns::Update(int count, float spacing, bool clear)
+{
+    IM_ASSERT(Count <= IM_ARRAYSIZE(Pos));
+    Count = count;
+    Width = NextWidth = 0.0f;
+    Spacing = spacing;
+    if (clear) memset(NextWidths, 0, sizeof(NextWidths));
+    for (int i = 0; i < Count; i++)
+    {
+        if (i > 0 && NextWidths[i] > 0.0f)
+            Width += Spacing;
+        Pos[i] = (float)(int)Width;
+        Width += NextWidths[i];
+        NextWidths[i] = 0.0f;
+    }
+}
+
+float ImGuiMenuColumns::DeclColumns(float w0, float w1, float w2) // not using va_arg because they promote float to double
+{
+    NextWidth = 0.0f;
+    NextWidths[0] = ImMax(NextWidths[0], w0);
+    NextWidths[1] = ImMax(NextWidths[1], w1);
+    NextWidths[2] = ImMax(NextWidths[2], w2);
+    for (int i = 0; i < 3; i++)
+        NextWidth += NextWidths[i] + ((i > 0 && NextWidths[i] > 0.0f) ? Spacing : 0.0f);
+    return ImMax(Width, NextWidth);
+}
+
+float ImGuiMenuColumns::CalcExtraSpace(float avail_w)
+{
+    return ImMax(0.0f, avail_w - Width);
+}
+
+// For the main menu bar, which cannot be moved, we honor g.Style.DisplaySafeAreaPadding to ensure text can be visible on a TV set.
+bool ImGui::BeginMainMenuBar()
+{
+    ImGuiContext& g = *GImGui;
+    g.NextWindowData.MenuBarOffsetMinVal = ImVec2(g.Style.DisplaySafeAreaPadding.x, ImMax(g.Style.DisplaySafeAreaPadding.y - g.Style.FramePadding.y, 0.0f));
+    SetNextWindowPos(ImVec2(0.0f, 0.0f));
+    SetNextWindowSize(ImVec2(g.IO.DisplaySize.x, g.NextWindowData.MenuBarOffsetMinVal.y + g.FontBaseSize + g.Style.FramePadding.y));
+    PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
+    PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0,0));
+    ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar;
+    bool is_open = Begin("##MainMenuBar", NULL, window_flags) && BeginMenuBar();
+    PopStyleVar(2);
+    g.NextWindowData.MenuBarOffsetMinVal = ImVec2(0.0f, 0.0f);
+    if (!is_open)
+    {
+        End();
+        return false;
+    }
+    return true;
+}
+
+void ImGui::EndMainMenuBar()
+{
+    EndMenuBar();
+
+    // When the user has left the menu layer (typically: closed menus through activation of an item), we restore focus to the previous window
+    ImGuiContext& g = *GImGui;
+    if (g.CurrentWindow == g.NavWindow && g.NavLayer == 0)
+        FocusFrontMostActiveWindowIgnoringOne(g.NavWindow);
+
+    End();
+}
+
+bool ImGui::BeginMenuBar()
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    if (window->SkipItems)
+        return false;
+    if (!(window->Flags & ImGuiWindowFlags_MenuBar))
+        return false;
+
+    IM_ASSERT(!window->DC.MenuBarAppending);
+    BeginGroup(); // Backup position on layer 0
+    PushID("##menubar");
+
+    // We don't clip with current window clipping rectangle as it is already set to the area below. However we clip with window full rect.
+    // We remove 1 worth of rounding to Max.x to that text in long menus and small windows don't tend to display over the lower-right rounded area, which looks particularly glitchy.
+    ImRect bar_rect = window->MenuBarRect();
+    ImRect clip_rect(ImFloor(bar_rect.Min.x + 0.5f), ImFloor(bar_rect.Min.y + window->WindowBorderSize + 0.5f), ImFloor(ImMax(bar_rect.Min.x, bar_rect.Max.x - window->WindowRounding) + 0.5f), ImFloor(bar_rect.Max.y + 0.5f));
+    clip_rect.ClipWith(window->OuterRectClipped);
+    PushClipRect(clip_rect.Min, clip_rect.Max, false);
+
+    window->DC.CursorPos = ImVec2(bar_rect.Min.x + window->DC.MenuBarOffset.x, bar_rect.Min.y + window->DC.MenuBarOffset.y);
+    window->DC.LayoutType = ImGuiLayoutType_Horizontal;
+    window->DC.NavLayerCurrent++;
+    window->DC.NavLayerCurrentMask <<= 1;
+    window->DC.MenuBarAppending = true;
+    AlignTextToFramePadding();
+    return true;
+}
+
+void ImGui::EndMenuBar()
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    if (window->SkipItems)
+        return;
+    ImGuiContext& g = *GImGui;
+
+    // Nav: When a move request within one of our child menu failed, capture the request to navigate among our siblings.
+    if (NavMoveRequestButNoResultYet() && (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right) && (g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu))
+    {
+        ImGuiWindow* nav_earliest_child = g.NavWindow;
+        while (nav_earliest_child->ParentWindow && (nav_earliest_child->ParentWindow->Flags & ImGuiWindowFlags_ChildMenu))
+            nav_earliest_child = nav_earliest_child->ParentWindow;
+        if (nav_earliest_child->ParentWindow == window && nav_earliest_child->DC.ParentLayoutType == ImGuiLayoutType_Horizontal && g.NavMoveRequestForward == ImGuiNavForward_None)
+        {
+            // To do so we claim focus back, restore NavId and then process the movement request for yet another frame.
+            // This involve a one-frame delay which isn't very problematic in this situation. We could remove it by scoring in advance for multiple window (probably not worth the hassle/cost)
+            IM_ASSERT(window->DC.NavLayerActiveMaskNext & 0x02); // Sanity check
+            FocusWindow(window);
+            SetNavIDWithRectRel(window->NavLastIds[1], 1, window->NavRectRel[1]);
+            g.NavLayer = 1;
+            g.NavDisableHighlight = true; // Hide highlight for the current frame so we don't see the intermediary selection.
+            g.NavMoveRequestForward = ImGuiNavForward_ForwardQueued;
+            NavMoveRequestCancel();
+        }
+    }
+
+    IM_ASSERT(window->Flags & ImGuiWindowFlags_MenuBar);
+    IM_ASSERT(window->DC.MenuBarAppending);
+    PopClipRect();
+    PopID();
+    window->DC.MenuBarOffset.x = window->DC.CursorPos.x - window->MenuBarRect().Min.x; // Save horizontal position so next append can reuse it. This is kinda equivalent to a per-layer CursorPos.
+    window->DC.GroupStack.back().AdvanceCursor = false;
+    EndGroup(); // Restore position on layer 0
+    window->DC.LayoutType = ImGuiLayoutType_Vertical;
+    window->DC.NavLayerCurrent--;
+    window->DC.NavLayerCurrentMask >>= 1;
+    window->DC.MenuBarAppending = false;
+}
+
+bool ImGui::BeginMenu(const char* label, bool enabled)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    if (window->SkipItems)
+        return false;
+
+    ImGuiContext& g = *GImGui;
+    const ImGuiStyle& style = g.Style;
+    const ImGuiID id = window->GetID(label);
+
+    ImVec2 label_size = CalcTextSize(label, NULL, true);
+
+    bool pressed;
+    bool menu_is_open = IsPopupOpen(id);
+    bool menuset_is_open = !(window->Flags & ImGuiWindowFlags_Popup) && (g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].OpenParentId == window->IDStack.back());
+    ImGuiWindow* backed_nav_window = g.NavWindow;
+    if (menuset_is_open)
+        g.NavWindow = window;  // Odd hack to allow hovering across menus of a same menu-set (otherwise we wouldn't be able to hover parent)
+
+    // The reference position stored in popup_pos will be used by Begin() to find a suitable position for the child menu (using FindBestWindowPosForPopup).
+    ImVec2 popup_pos, pos = window->DC.CursorPos;
+    if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
+    {
+        // Menu inside an horizontal menu bar
+        // Selectable extend their highlight by half ItemSpacing in each direction.
+        // For ChildMenu, the popup position will be overwritten by the call to FindBestWindowPosForPopup() in Begin()
+        popup_pos = ImVec2(pos.x - window->WindowPadding.x, pos.y - style.FramePadding.y + window->MenuBarHeight());
+        window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * 0.5f);
+        PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing * 2.0f);
+        float w = label_size.x;
+        pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_PressedOnClick | ImGuiSelectableFlags_DontClosePopups | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(w, 0.0f));
+        PopStyleVar();
+        window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar().
+    }
+    else
+    {
+        // Menu inside a menu
+        popup_pos = ImVec2(pos.x, pos.y - style.WindowPadding.y);
+        float w = window->MenuColumns.DeclColumns(label_size.x, 0.0f, (float)(int)(g.FontSize * 1.20f)); // Feedback to next frame
+        float extra_w = ImMax(0.0f, GetContentRegionAvail().x - w);
+        pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_PressedOnClick | ImGuiSelectableFlags_DontClosePopups | ImGuiSelectableFlags_DrawFillAvailWidth | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(w, 0.0f));
+        if (!enabled) PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]);
+        RenderArrow(pos + ImVec2(window->MenuColumns.Pos[2] + extra_w + g.FontSize * 0.30f, 0.0f), ImGuiDir_Right);
+        if (!enabled) PopStyleColor();
+    }
+
+    const bool hovered = enabled && ItemHoverable(window->DC.LastItemRect, id);
+    if (menuset_is_open)
+        g.NavWindow = backed_nav_window;
+
+    bool want_open = false, want_close = false;
+    if (window->DC.LayoutType == ImGuiLayoutType_Vertical) // (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu))
+    {
+        // Implement http://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown to avoid using timers, so menus feels more reactive.
+        bool moving_within_opened_triangle = false;
+        if (g.HoveredWindow == window && g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].ParentWindow == window && !(window->Flags & ImGuiWindowFlags_MenuBar))
+        {
+            if (ImGuiWindow* next_window = g.OpenPopupStack[g.CurrentPopupStack.Size].Window)
+            {
+                ImRect next_window_rect = next_window->Rect();
+                ImVec2 ta = g.IO.MousePos - g.IO.MouseDelta;
+                ImVec2 tb = (window->Pos.x < next_window->Pos.x) ? next_window_rect.GetTL() : next_window_rect.GetTR();
+                ImVec2 tc = (window->Pos.x < next_window->Pos.x) ? next_window_rect.GetBL() : next_window_rect.GetBR();
+                float extra = ImClamp(ImFabs(ta.x - tb.x) * 0.30f, 5.0f, 30.0f); // add a bit of extra slack.
+                ta.x += (window->Pos.x < next_window->Pos.x) ? -0.5f : +0.5f;   // to avoid numerical issues
+                tb.y = ta.y + ImMax((tb.y - extra) - ta.y, -100.0f);            // triangle is maximum 200 high to limit the slope and the bias toward large sub-menus // FIXME: Multiply by fb_scale?
+                tc.y = ta.y + ImMin((tc.y + extra) - ta.y, +100.0f);
+                moving_within_opened_triangle = ImTriangleContainsPoint(ta, tb, tc, g.IO.MousePos);
+                //window->DrawList->PushClipRectFullScreen(); window->DrawList->AddTriangleFilled(ta, tb, tc, moving_within_opened_triangle ? IM_COL32(0,128,0,128) : IM_COL32(128,0,0,128)); window->DrawList->PopClipRect(); // Debug
+            }
+        }
+
+        want_close = (menu_is_open && !hovered && g.HoveredWindow == window && g.HoveredIdPreviousFrame != 0 && g.HoveredIdPreviousFrame != id && !moving_within_opened_triangle);
+        want_open = (!menu_is_open && hovered && !moving_within_opened_triangle) || (!menu_is_open && hovered && pressed);
+
+        if (g.NavActivateId == id)
+        {
+            want_close = menu_is_open;
+            want_open = !menu_is_open;
+        }
+        if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Right) // Nav-Right to open
+        {
+            want_open = true;
+            NavMoveRequestCancel();
+        }
+    }
+    else
+    {
+        // Menu bar
+        if (menu_is_open && pressed && menuset_is_open) // Click an open menu again to close it
+        {
+            want_close = true;
+            want_open = menu_is_open = false;
+        }
+        else if (pressed || (hovered && menuset_is_open && !menu_is_open)) // First click to open, then hover to open others
+        {
+            want_open = true;
+        }
+        else if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Down) // Nav-Down to open
+        {
+            want_open = true;
+            NavMoveRequestCancel();
+        }
+    }
+
+    if (!enabled) // explicitly close if an open menu becomes disabled, facilitate users code a lot in pattern such as 'if (BeginMenu("options", has_object)) { ..use object.. }'
+        want_close = true;
+    if (want_close && IsPopupOpen(id))
+        ClosePopupToLevel(g.CurrentPopupStack.Size);
+
+    if (!menu_is_open && want_open && g.OpenPopupStack.Size > g.CurrentPopupStack.Size)
+    {
+        // Don't recycle same menu level in the same frame, first close the other menu and yield for a frame.
+        OpenPopup(label);
+        return false;
+    }
+
+    menu_is_open |= want_open;
+    if (want_open)
+        OpenPopup(label);
+
+    if (menu_is_open)
+    {
+        // Sub-menus are ChildWindow so that mouse can be hovering across them (otherwise top-most popup menu would steal focus and not allow hovering on parent menu)
+        SetNextWindowPos(popup_pos, ImGuiCond_Always);
+        ImGuiWindowFlags flags = ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNavFocus;
+        if (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu))
+            flags |= ImGuiWindowFlags_ChildWindow;
+        menu_is_open = BeginPopupEx(id, flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display)
+    }
+
+    return menu_is_open;
+}
+
+void ImGui::EndMenu()
+{
+    // Nav: When a left move request _within our child menu_ failed, close the menu.
+    // A menu doesn't close itself because EndMenuBar() wants the catch the last Left<>Right inputs.
+    // However, it means that with the current code, a BeginMenu() from outside another menu or a menu-bar won't be closable with the Left direction.
+    ImGuiContext& g = *GImGui;
+    ImGuiWindow* window = g.CurrentWindow;
+    if (g.NavWindow && g.NavWindow->ParentWindow == window && g.NavMoveDir == ImGuiDir_Left && NavMoveRequestButNoResultYet() && window->DC.LayoutType == ImGuiLayoutType_Vertical)
+    {
+        ClosePopupToLevel(g.OpenPopupStack.Size - 1);
+        NavMoveRequestCancel();
+    }
+
+    EndPopup();
+}
+
+bool ImGui::MenuItem(const char* label, const char* shortcut, bool selected, bool enabled)
+{
+    ImGuiWindow* window = GetCurrentWindow();
+    if (window->SkipItems)
+        return false;
+
+    ImGuiContext& g = *GImGui;
+    ImGuiStyle& style = g.Style;
+    ImVec2 pos = window->DC.CursorPos;
+    ImVec2 label_size = CalcTextSize(label, NULL, true);
+
+    ImGuiSelectableFlags flags = ImGuiSelectableFlags_PressedOnRelease | (enabled ? 0 : ImGuiSelectableFlags_Disabled);
+    bool pressed;
+    if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
+    {
+        // Mimic the exact layout spacing of BeginMenu() to allow MenuItem() inside a menu bar, which is a little misleading but may be useful
+        // Note that in this situation we render neither the shortcut neither the selected tick mark
+        float w = label_size.x;
+        window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * 0.5f);
+        PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing * 2.0f);
+        pressed = Selectable(label, false, flags, ImVec2(w, 0.0f));
+        PopStyleVar();
+        window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar().
+    }
+    else
+    {
+        ImVec2 shortcut_size = shortcut ? CalcTextSize(shortcut, NULL) : ImVec2(0.0f, 0.0f);
+        float w = window->MenuColumns.DeclColumns(label_size.x, shortcut_size.x, (float)(int)(g.FontSize * 1.20f)); // Feedback for next frame
+        float extra_w = ImMax(0.0f, GetContentRegionAvail().x - w);
+        pressed = Selectable(label, false, flags | ImGuiSelectableFlags_DrawFillAvailWidth, ImVec2(w, 0.0f));
+        if (shortcut_size.x > 0.0f)
+        {
+            PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]);
+            RenderText(pos + ImVec2(window->MenuColumns.Pos[1] + extra_w, 0.0f), shortcut, NULL, false);
+            PopStyleColor();
+        }
+        if (selected)
+            RenderCheckMark(pos + ImVec2(window->MenuColumns.Pos[2] + extra_w + g.FontSize * 0.40f, g.FontSize * 0.134f * 0.5f), GetColorU32(enabled ? ImGuiCol_Text : ImGuiCol_TextDisabled), g.FontSize  * 0.866f);
+    }
+    return pressed;
+}
+
+bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled)
+{
+    if (MenuItem(label, shortcut, p_selected ? *p_selected : false, enabled))
+    {
+        if (p_selected)
+            *p_selected = !*p_selected;
+        return true;
+    }
+    return false;
+}
diff --git a/external/Vulkan/external/imgui/imstb_rectpack.h b/external/Vulkan/external/imgui/imstb_rectpack.h
new file mode 100644
index 0000000000000000000000000000000000000000..2b07dcc82c8cc558dec3510fcd77b5fb8e937d80
--- /dev/null
+++ b/external/Vulkan/external/imgui/imstb_rectpack.h
@@ -0,0 +1,623 @@
+// stb_rect_pack.h - v0.11 - public domain - rectangle packing
+// Sean Barrett 2014
+//
+// Useful for e.g. packing rectangular textures into an atlas.
+// Does not do rotation.
+//
+// Not necessarily the awesomest packing method, but better than
+// the totally naive one in stb_truetype (which is primarily what
+// this is meant to replace).
+//
+// Has only had a few tests run, may have issues.
+//
+// More docs to come.
+//
+// No memory allocations; uses qsort() and assert() from stdlib.
+// Can override those by defining STBRP_SORT and STBRP_ASSERT.
+//
+// This library currently uses the Skyline Bottom-Left algorithm.
+//
+// Please note: better rectangle packers are welcome! Please
+// implement them to the same API, but with a different init
+// function.
+//
+// Credits
+//
+//  Library
+//    Sean Barrett
+//  Minor features
+//    Martins Mozeiko
+//    github:IntellectualKitty
+//    
+//  Bugfixes / warning fixes
+//    Jeremy Jaussaud
+//
+// Version history:
+//
+//     0.11  (2017-03-03)  return packing success/fail result
+//     0.10  (2016-10-25)  remove cast-away-const to avoid warnings
+//     0.09  (2016-08-27)  fix compiler warnings
+//     0.08  (2015-09-13)  really fix bug with empty rects (w=0 or h=0)
+//     0.07  (2015-09-13)  fix bug with empty rects (w=0 or h=0)
+//     0.06  (2015-04-15)  added STBRP_SORT to allow replacing qsort
+//     0.05:  added STBRP_ASSERT to allow replacing assert
+//     0.04:  fixed minor bug in STBRP_LARGE_RECTS support
+//     0.01:  initial release
+//
+// LICENSE
+//
+//   See end of file for license information.
+
+//////////////////////////////////////////////////////////////////////////////
+//
+//       INCLUDE SECTION
+//
+
+#ifndef STB_INCLUDE_STB_RECT_PACK_H
+#define STB_INCLUDE_STB_RECT_PACK_H
+
+#define STB_RECT_PACK_VERSION  1
+
+#ifdef STBRP_STATIC
+#define STBRP_DEF static
+#else
+#define STBRP_DEF extern
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct stbrp_context stbrp_context;
+typedef struct stbrp_node    stbrp_node;
+typedef struct stbrp_rect    stbrp_rect;
+
+#ifdef STBRP_LARGE_RECTS
+typedef int            stbrp_coord;
+#else
+typedef unsigned short stbrp_coord;
+#endif
+
+STBRP_DEF int stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects);
+// Assign packed locations to rectangles. The rectangles are of type
+// 'stbrp_rect' defined below, stored in the array 'rects', and there
+// are 'num_rects' many of them.
+//
+// Rectangles which are successfully packed have the 'was_packed' flag
+// set to a non-zero value and 'x' and 'y' store the minimum location
+// on each axis (i.e. bottom-left in cartesian coordinates, top-left
+// if you imagine y increasing downwards). Rectangles which do not fit
+// have the 'was_packed' flag set to 0.
+//
+// You should not try to access the 'rects' array from another thread
+// while this function is running, as the function temporarily reorders
+// the array while it executes.
+//
+// To pack into another rectangle, you need to call stbrp_init_target
+// again. To continue packing into the same rectangle, you can call
+// this function again. Calling this multiple times with multiple rect
+// arrays will probably produce worse packing results than calling it
+// a single time with the full rectangle array, but the option is
+// available.
+//
+// The function returns 1 if all of the rectangles were successfully
+// packed and 0 otherwise.
+
+struct stbrp_rect
+{
+   // reserved for your use:
+   int            id;
+
+   // input:
+   stbrp_coord    w, h;
+
+   // output:
+   stbrp_coord    x, y;
+   int            was_packed;  // non-zero if valid packing
+
+}; // 16 bytes, nominally
+
+
+STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes);
+// Initialize a rectangle packer to:
+//    pack a rectangle that is 'width' by 'height' in dimensions
+//    using temporary storage provided by the array 'nodes', which is 'num_nodes' long
+//
+// You must call this function every time you start packing into a new target.
+//
+// There is no "shutdown" function. The 'nodes' memory must stay valid for
+// the following stbrp_pack_rects() call (or calls), but can be freed after
+// the call (or calls) finish.
+//
+// Note: to guarantee best results, either:
+//       1. make sure 'num_nodes' >= 'width'
+//   or  2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1'
+//
+// If you don't do either of the above things, widths will be quantized to multiples
+// of small integers to guarantee the algorithm doesn't run out of temporary storage.
+//
+// If you do #2, then the non-quantized algorithm will be used, but the algorithm
+// may run out of temporary storage and be unable to pack some rectangles.
+
+STBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem);
+// Optionally call this function after init but before doing any packing to
+// change the handling of the out-of-temp-memory scenario, described above.
+// If you call init again, this will be reset to the default (false).
+
+
+STBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic);
+// Optionally select which packing heuristic the library should use. Different
+// heuristics will produce better/worse results for different data sets.
+// If you call init again, this will be reset to the default.
+
+enum
+{
+   STBRP_HEURISTIC_Skyline_default=0,
+   STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default,
+   STBRP_HEURISTIC_Skyline_BF_sortHeight
+};
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// the details of the following structures don't matter to you, but they must
+// be visible so you can handle the memory allocations for them
+
+struct stbrp_node
+{
+   stbrp_coord  x,y;
+   stbrp_node  *next;
+};
+
+struct stbrp_context
+{
+   int width;
+   int height;
+   int align;
+   int init_mode;
+   int heuristic;
+   int num_nodes;
+   stbrp_node *active_head;
+   stbrp_node *free_head;
+   stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2'
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
+//////////////////////////////////////////////////////////////////////////////
+//
+//     IMPLEMENTATION SECTION
+//
+
+#ifdef STB_RECT_PACK_IMPLEMENTATION
+#ifndef STBRP_SORT
+#include <stdlib.h>
+#define STBRP_SORT qsort
+#endif
+
+#ifndef STBRP_ASSERT
+#include <assert.h>
+#define STBRP_ASSERT assert
+#endif
+
+#ifdef _MSC_VER
+#define STBRP__NOTUSED(v)  (void)(v)
+#define STBRP__CDECL __cdecl
+#else
+#define STBRP__NOTUSED(v)  (void)sizeof(v)
+#define STBRP__CDECL
+#endif
+
+enum
+{
+   STBRP__INIT_skyline = 1
+};
+
+STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic)
+{
+   switch (context->init_mode) {
+      case STBRP__INIT_skyline:
+         STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight);
+         context->heuristic = heuristic;
+         break;
+      default:
+         STBRP_ASSERT(0);
+   }
+}
+
+STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem)
+{
+   if (allow_out_of_mem)
+      // if it's ok to run out of memory, then don't bother aligning them;
+      // this gives better packing, but may fail due to OOM (even though
+      // the rectangles easily fit). @TODO a smarter approach would be to only
+      // quantize once we've hit OOM, then we could get rid of this parameter.
+      context->align = 1;
+   else {
+      // if it's not ok to run out of memory, then quantize the widths
+      // so that num_nodes is always enough nodes.
+      //
+      // I.e. num_nodes * align >= width
+      //                  align >= width / num_nodes
+      //                  align = ceil(width/num_nodes)
+
+      context->align = (context->width + context->num_nodes-1) / context->num_nodes;
+   }
+}
+
+STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes)
+{
+   int i;
+#ifndef STBRP_LARGE_RECTS
+   STBRP_ASSERT(width <= 0xffff && height <= 0xffff);
+#endif
+
+   for (i=0; i < num_nodes-1; ++i)
+      nodes[i].next = &nodes[i+1];
+   nodes[i].next = NULL;
+   context->init_mode = STBRP__INIT_skyline;
+   context->heuristic = STBRP_HEURISTIC_Skyline_default;
+   context->free_head = &nodes[0];
+   context->active_head = &context->extra[0];
+   context->width = width;
+   context->height = height;
+   context->num_nodes = num_nodes;
+   stbrp_setup_allow_out_of_mem(context, 0);
+
+   // node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly)
+   context->extra[0].x = 0;
+   context->extra[0].y = 0;
+   context->extra[0].next = &context->extra[1];
+   context->extra[1].x = (stbrp_coord) width;
+#ifdef STBRP_LARGE_RECTS
+   context->extra[1].y = (1<<30);
+#else
+   context->extra[1].y = 65535;
+#endif
+   context->extra[1].next = NULL;
+}
+
+// find minimum y position if it starts at x1
+static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste)
+{
+   stbrp_node *node = first;
+   int x1 = x0 + width;
+   int min_y, visited_width, waste_area;
+
+   STBRP__NOTUSED(c);
+
+   STBRP_ASSERT(first->x <= x0);
+
+   #if 0
+   // skip in case we're past the node
+   while (node->next->x <= x0)
+      ++node;
+   #else
+   STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency
+   #endif
+
+   STBRP_ASSERT(node->x <= x0);
+
+   min_y = 0;
+   waste_area = 0;
+   visited_width = 0;
+   while (node->x < x1) {
+      if (node->y > min_y) {
+         // raise min_y higher.
+         // we've accounted for all waste up to min_y,
+         // but we'll now add more waste for everything we've visted
+         waste_area += visited_width * (node->y - min_y);
+         min_y = node->y;
+         // the first time through, visited_width might be reduced
+         if (node->x < x0)
+            visited_width += node->next->x - x0;
+         else
+            visited_width += node->next->x - node->x;
+      } else {
+         // add waste area
+         int under_width = node->next->x - node->x;
+         if (under_width + visited_width > width)
+            under_width = width - visited_width;
+         waste_area += under_width * (min_y - node->y);
+         visited_width += under_width;
+      }
+      node = node->next;
+   }
+
+   *pwaste = waste_area;
+   return min_y;
+}
+
+typedef struct
+{
+   int x,y;
+   stbrp_node **prev_link;
+} stbrp__findresult;
+
+static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height)
+{
+   int best_waste = (1<<30), best_x, best_y = (1 << 30);
+   stbrp__findresult fr;
+   stbrp_node **prev, *node, *tail, **best = NULL;
+
+   // align to multiple of c->align
+   width = (width + c->align - 1);
+   width -= width % c->align;
+   STBRP_ASSERT(width % c->align == 0);
+
+   node = c->active_head;
+   prev = &c->active_head;
+   while (node->x + width <= c->width) {
+      int y,waste;
+      y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste);
+      if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL
+         // bottom left
+         if (y < best_y) {
+            best_y = y;
+            best = prev;
+         }
+      } else {
+         // best-fit
+         if (y + height <= c->height) {
+            // can only use it if it first vertically
+            if (y < best_y || (y == best_y && waste < best_waste)) {
+               best_y = y;
+               best_waste = waste;
+               best = prev;
+            }
+         }
+      }
+      prev = &node->next;
+      node = node->next;
+   }
+
+   best_x = (best == NULL) ? 0 : (*best)->x;
+
+   // if doing best-fit (BF), we also have to try aligning right edge to each node position
+   //
+   // e.g, if fitting
+   //
+   //     ____________________
+   //    |____________________|
+   //
+   //            into
+   //
+   //   |                         |
+   //   |             ____________|
+   //   |____________|
+   //
+   // then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned
+   //
+   // This makes BF take about 2x the time
+
+   if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) {
+      tail = c->active_head;
+      node = c->active_head;
+      prev = &c->active_head;
+      // find first node that's admissible
+      while (tail->x < width)
+         tail = tail->next;
+      while (tail) {
+         int xpos = tail->x - width;
+         int y,waste;
+         STBRP_ASSERT(xpos >= 0);
+         // find the left position that matches this
+         while (node->next->x <= xpos) {
+            prev = &node->next;
+            node = node->next;
+         }
+         STBRP_ASSERT(node->next->x > xpos && node->x <= xpos);
+         y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste);
+         if (y + height < c->height) {
+            if (y <= best_y) {
+               if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) {
+                  best_x = xpos;
+                  STBRP_ASSERT(y <= best_y);
+                  best_y = y;
+                  best_waste = waste;
+                  best = prev;
+               }
+            }
+         }
+         tail = tail->next;
+      }         
+   }
+
+   fr.prev_link = best;
+   fr.x = best_x;
+   fr.y = best_y;
+   return fr;
+}
+
+static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height)
+{
+   // find best position according to heuristic
+   stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height);
+   stbrp_node *node, *cur;
+
+   // bail if:
+   //    1. it failed
+   //    2. the best node doesn't fit (we don't always check this)
+   //    3. we're out of memory
+   if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) {
+      res.prev_link = NULL;
+      return res;
+   }
+
+   // on success, create new node
+   node = context->free_head;
+   node->x = (stbrp_coord) res.x;
+   node->y = (stbrp_coord) (res.y + height);
+
+   context->free_head = node->next;
+
+   // insert the new node into the right starting point, and
+   // let 'cur' point to the remaining nodes needing to be
+   // stiched back in
+
+   cur = *res.prev_link;
+   if (cur->x < res.x) {
+      // preserve the existing one, so start testing with the next one
+      stbrp_node *next = cur->next;
+      cur->next = node;
+      cur = next;
+   } else {
+      *res.prev_link = node;
+   }
+
+   // from here, traverse cur and free the nodes, until we get to one
+   // that shouldn't be freed
+   while (cur->next && cur->next->x <= res.x + width) {
+      stbrp_node *next = cur->next;
+      // move the current node to the free list
+      cur->next = context->free_head;
+      context->free_head = cur;
+      cur = next;
+   }
+
+   // stitch the list back in
+   node->next = cur;
+
+   if (cur->x < res.x + width)
+      cur->x = (stbrp_coord) (res.x + width);
+
+#ifdef _DEBUG
+   cur = context->active_head;
+   while (cur->x < context->width) {
+      STBRP_ASSERT(cur->x < cur->next->x);
+      cur = cur->next;
+   }
+   STBRP_ASSERT(cur->next == NULL);
+
+   {
+      int count=0;
+      cur = context->active_head;
+      while (cur) {
+         cur = cur->next;
+         ++count;
+      }
+      cur = context->free_head;
+      while (cur) {
+         cur = cur->next;
+         ++count;
+      }
+      STBRP_ASSERT(count == context->num_nodes+2);
+   }
+#endif
+
+   return res;
+}
+
+static int STBRP__CDECL rect_height_compare(const void *a, const void *b)
+{
+   const stbrp_rect *p = (const stbrp_rect *) a;
+   const stbrp_rect *q = (const stbrp_rect *) b;
+   if (p->h > q->h)
+      return -1;
+   if (p->h < q->h)
+      return  1;
+   return (p->w > q->w) ? -1 : (p->w < q->w);
+}
+
+static int STBRP__CDECL rect_original_order(const void *a, const void *b)
+{
+   const stbrp_rect *p = (const stbrp_rect *) a;
+   const stbrp_rect *q = (const stbrp_rect *) b;
+   return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed);
+}
+
+#ifdef STBRP_LARGE_RECTS
+#define STBRP__MAXVAL  0xffffffff
+#else
+#define STBRP__MAXVAL  0xffff
+#endif
+
+STBRP_DEF int stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects)
+{
+   int i, all_rects_packed = 1;
+
+   // we use the 'was_packed' field internally to allow sorting/unsorting
+   for (i=0; i < num_rects; ++i) {
+      rects[i].was_packed = i;
+      #ifndef STBRP_LARGE_RECTS
+      STBRP_ASSERT(rects[i].w <= 0xffff && rects[i].h <= 0xffff);
+      #endif
+   }
+
+   // sort according to heuristic
+   STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare);
+
+   for (i=0; i < num_rects; ++i) {
+      if (rects[i].w == 0 || rects[i].h == 0) {
+         rects[i].x = rects[i].y = 0;  // empty rect needs no space
+      } else {
+         stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h);
+         if (fr.prev_link) {
+            rects[i].x = (stbrp_coord) fr.x;
+            rects[i].y = (stbrp_coord) fr.y;
+         } else {
+            rects[i].x = rects[i].y = STBRP__MAXVAL;
+         }
+      }
+   }
+
+   // unsort
+   STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order);
+
+   // set was_packed flags and all_rects_packed status
+   for (i=0; i < num_rects; ++i) {
+      rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL);
+      if (!rects[i].was_packed)
+         all_rects_packed = 0;
+   }
+
+   // return the all_rects_packed status
+   return all_rects_packed;
+}
+#endif
+
+/*
+------------------------------------------------------------------------------
+This software is available under 2 licenses -- choose whichever you prefer.
+------------------------------------------------------------------------------
+ALTERNATIVE A - MIT License
+Copyright (c) 2017 Sean Barrett
+Permission is hereby granted, free of charge, to any person obtaining a copy of 
+this software and associated documentation files (the "Software"), to deal in 
+the Software without restriction, including without limitation the rights to 
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 
+of the Software, and to permit persons to whom the Software is furnished to do 
+so, subject to the following conditions:
+The above copyright notice and this permission notice shall be included in all 
+copies or substantial portions of the Software.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
+SOFTWARE.
+------------------------------------------------------------------------------
+ALTERNATIVE B - Public Domain (www.unlicense.org)
+This is free and unencumbered software released into the public domain.
+Anyone is free to copy, modify, publish, use, compile, sell, or distribute this 
+software, either in source code form or as a compiled binary, for any purpose, 
+commercial or non-commercial, and by any means.
+In jurisdictions that recognize copyright laws, the author or authors of this 
+software dedicate any and all copyright interest in the software to the public 
+domain. We make this dedication for the benefit of the public at large and to 
+the detriment of our heirs and successors. We intend this dedication to be an 
+overt act of relinquishment in perpetuity of all present and future rights to 
+this software under copyright law.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
+AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 
+ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+------------------------------------------------------------------------------
+*/
diff --git a/external/Vulkan/external/imgui/imstb_textedit.h b/external/Vulkan/external/imgui/imstb_textedit.h
new file mode 100644
index 0000000000000000000000000000000000000000..9e12469be702b1a2ced338d777411103ffd47520
--- /dev/null
+++ b/external/Vulkan/external/imgui/imstb_textedit.h
@@ -0,0 +1,1409 @@
+// [ImGui] this is a slightly modified version of stb_textedit.h 1.12. Those changes would need to be pushed into nothings/stb
+// [ImGui] - 2018-06: fixed undo/redo after pasting large amount of text (over 32 kb). Redo will still fail when undo buffers are exhausted, but text won't be corrupted (see nothings/stb issue #620)
+// [ImGui] - 2018-06: fix in stb_textedit_discard_redo (see https://github.com/nothings/stb/issues/321)
+// [ImGui] - fixed some minor warnings
+
+// stb_textedit.h - v1.12  - public domain - Sean Barrett
+// Development of this library was sponsored by RAD Game Tools
+//
+// This C header file implements the guts of a multi-line text-editing
+// widget; you implement display, word-wrapping, and low-level string
+// insertion/deletion, and stb_textedit will map user inputs into
+// insertions & deletions, plus updates to the cursor position,
+// selection state, and undo state.
+//
+// It is intended for use in games and other systems that need to build
+// their own custom widgets and which do not have heavy text-editing
+// requirements (this library is not recommended for use for editing large
+// texts, as its performance does not scale and it has limited undo).
+//
+// Non-trivial behaviors are modelled after Windows text controls.
+// 
+//
+// LICENSE
+//
+// See end of file for license information.
+//
+//
+// DEPENDENCIES
+//
+// Uses the C runtime function 'memmove', which you can override
+// by defining STB_TEXTEDIT_memmove before the implementation.
+// Uses no other functions. Performs no runtime allocations.
+//
+//
+// VERSION HISTORY
+//
+//   1.12 (2018-01-29) user can change STB_TEXTEDIT_KEYTYPE, fix redo to avoid crash
+//   1.11 (2017-03-03) fix HOME on last line, dragging off single-line textfield
+//   1.10 (2016-10-25) supress warnings about casting away const with -Wcast-qual
+//   1.9  (2016-08-27) customizable move-by-word
+//   1.8  (2016-04-02) better keyboard handling when mouse button is down
+//   1.7  (2015-09-13) change y range handling in case baseline is non-0
+//   1.6  (2015-04-15) allow STB_TEXTEDIT_memmove
+//   1.5  (2014-09-10) add support for secondary keys for OS X
+//   1.4  (2014-08-17) fix signed/unsigned warnings
+//   1.3  (2014-06-19) fix mouse clicking to round to nearest char boundary
+//   1.2  (2014-05-27) fix some RAD types that had crept into the new code
+//   1.1  (2013-12-15) move-by-word (requires STB_TEXTEDIT_IS_SPACE )
+//   1.0  (2012-07-26) improve documentation, initial public release
+//   0.3  (2012-02-24) bugfixes, single-line mode; insert mode
+//   0.2  (2011-11-28) fixes to undo/redo
+//   0.1  (2010-07-08) initial version
+//
+// ADDITIONAL CONTRIBUTORS
+//
+//   Ulf Winklemann: move-by-word in 1.1
+//   Fabian Giesen: secondary key inputs in 1.5
+//   Martins Mozeiko: STB_TEXTEDIT_memmove in 1.6
+//
+//   Bugfixes:
+//      Scott Graham
+//      Daniel Keller
+//      Omar Cornut
+//      Dan Thompson
+//
+// USAGE
+//
+// This file behaves differently depending on what symbols you define
+// before including it.
+//
+//
+// Header-file mode:
+//
+//   If you do not define STB_TEXTEDIT_IMPLEMENTATION before including this,
+//   it will operate in "header file" mode. In this mode, it declares a
+//   single public symbol, STB_TexteditState, which encapsulates the current
+//   state of a text widget (except for the string, which you will store
+//   separately).
+//
+//   To compile in this mode, you must define STB_TEXTEDIT_CHARTYPE to a
+//   primitive type that defines a single character (e.g. char, wchar_t, etc).
+//
+//   To save space or increase undo-ability, you can optionally define the
+//   following things that are used by the undo system:
+//
+//      STB_TEXTEDIT_POSITIONTYPE         small int type encoding a valid cursor position
+//      STB_TEXTEDIT_UNDOSTATECOUNT       the number of undo states to allow
+//      STB_TEXTEDIT_UNDOCHARCOUNT        the number of characters to store in the undo buffer
+//
+//   If you don't define these, they are set to permissive types and
+//   moderate sizes. The undo system does no memory allocations, so
+//   it grows STB_TexteditState by the worst-case storage which is (in bytes):
+//
+//        [4 + 3 * sizeof(STB_TEXTEDIT_POSITIONTYPE)] * STB_TEXTEDIT_UNDOSTATE_COUNT
+//      +          sizeof(STB_TEXTEDIT_CHARTYPE)      * STB_TEXTEDIT_UNDOCHAR_COUNT
+//
+//
+// Implementation mode:
+//
+//   If you define STB_TEXTEDIT_IMPLEMENTATION before including this, it
+//   will compile the implementation of the text edit widget, depending
+//   on a large number of symbols which must be defined before the include.
+//
+//   The implementation is defined only as static functions. You will then
+//   need to provide your own APIs in the same file which will access the
+//   static functions.
+//
+//   The basic concept is that you provide a "string" object which
+//   behaves like an array of characters. stb_textedit uses indices to
+//   refer to positions in the string, implicitly representing positions
+//   in the displayed textedit. This is true for both plain text and
+//   rich text; even with rich text stb_truetype interacts with your
+//   code as if there was an array of all the displayed characters.
+//
+// Symbols that must be the same in header-file and implementation mode:
+//
+//     STB_TEXTEDIT_CHARTYPE             the character type
+//     STB_TEXTEDIT_POSITIONTYPE         small type that is a valid cursor position
+//     STB_TEXTEDIT_UNDOSTATECOUNT       the number of undo states to allow
+//     STB_TEXTEDIT_UNDOCHARCOUNT        the number of characters to store in the undo buffer
+//
+// Symbols you must define for implementation mode:
+//
+//    STB_TEXTEDIT_STRING               the type of object representing a string being edited,
+//                                      typically this is a wrapper object with other data you need
+//
+//    STB_TEXTEDIT_STRINGLEN(obj)       the length of the string (ideally O(1))
+//    STB_TEXTEDIT_LAYOUTROW(&r,obj,n)  returns the results of laying out a line of characters
+//                                        starting from character #n (see discussion below)
+//    STB_TEXTEDIT_GETWIDTH(obj,n,i)    returns the pixel delta from the xpos of the i'th character
+//                                        to the xpos of the i+1'th char for a line of characters
+//                                        starting at character #n (i.e. accounts for kerning
+//                                        with previous char)
+//    STB_TEXTEDIT_KEYTOTEXT(k)         maps a keyboard input to an insertable character
+//                                        (return type is int, -1 means not valid to insert)
+//    STB_TEXTEDIT_GETCHAR(obj,i)       returns the i'th character of obj, 0-based
+//    STB_TEXTEDIT_NEWLINE              the character returned by _GETCHAR() we recognize
+//                                        as manually wordwrapping for end-of-line positioning
+//
+//    STB_TEXTEDIT_DELETECHARS(obj,i,n)      delete n characters starting at i
+//    STB_TEXTEDIT_INSERTCHARS(obj,i,c*,n)   insert n characters at i (pointed to by STB_TEXTEDIT_CHARTYPE*)
+//
+//    STB_TEXTEDIT_K_SHIFT       a power of two that is or'd in to a keyboard input to represent the shift key
+//
+//    STB_TEXTEDIT_K_LEFT        keyboard input to move cursor left
+//    STB_TEXTEDIT_K_RIGHT       keyboard input to move cursor right
+//    STB_TEXTEDIT_K_UP          keyboard input to move cursor up
+//    STB_TEXTEDIT_K_DOWN        keyboard input to move cursor down
+//    STB_TEXTEDIT_K_LINESTART   keyboard input to move cursor to start of line  // e.g. HOME
+//    STB_TEXTEDIT_K_LINEEND     keyboard input to move cursor to end of line    // e.g. END
+//    STB_TEXTEDIT_K_TEXTSTART   keyboard input to move cursor to start of text  // e.g. ctrl-HOME
+//    STB_TEXTEDIT_K_TEXTEND     keyboard input to move cursor to end of text    // e.g. ctrl-END
+//    STB_TEXTEDIT_K_DELETE      keyboard input to delete selection or character under cursor
+//    STB_TEXTEDIT_K_BACKSPACE   keyboard input to delete selection or character left of cursor
+//    STB_TEXTEDIT_K_UNDO        keyboard input to perform undo
+//    STB_TEXTEDIT_K_REDO        keyboard input to perform redo
+//
+// Optional:
+//    STB_TEXTEDIT_K_INSERT              keyboard input to toggle insert mode
+//    STB_TEXTEDIT_IS_SPACE(ch)          true if character is whitespace (e.g. 'isspace'),
+//                                          required for default WORDLEFT/WORDRIGHT handlers
+//    STB_TEXTEDIT_MOVEWORDLEFT(obj,i)   custom handler for WORDLEFT, returns index to move cursor to
+//    STB_TEXTEDIT_MOVEWORDRIGHT(obj,i)  custom handler for WORDRIGHT, returns index to move cursor to
+//    STB_TEXTEDIT_K_WORDLEFT            keyboard input to move cursor left one word // e.g. ctrl-LEFT
+//    STB_TEXTEDIT_K_WORDRIGHT           keyboard input to move cursor right one word // e.g. ctrl-RIGHT
+//    STB_TEXTEDIT_K_LINESTART2          secondary keyboard input to move cursor to start of line
+//    STB_TEXTEDIT_K_LINEEND2            secondary keyboard input to move cursor to end of line
+//    STB_TEXTEDIT_K_TEXTSTART2          secondary keyboard input to move cursor to start of text
+//    STB_TEXTEDIT_K_TEXTEND2            secondary keyboard input to move cursor to end of text
+//
+// Todo:
+//    STB_TEXTEDIT_K_PGUP        keyboard input to move cursor up a page
+//    STB_TEXTEDIT_K_PGDOWN      keyboard input to move cursor down a page
+//
+// Keyboard input must be encoded as a single integer value; e.g. a character code
+// and some bitflags that represent shift states. to simplify the interface, SHIFT must
+// be a bitflag, so we can test the shifted state of cursor movements to allow selection,
+// i.e. (STB_TEXTED_K_RIGHT|STB_TEXTEDIT_K_SHIFT) should be shifted right-arrow.
+//
+// You can encode other things, such as CONTROL or ALT, in additional bits, and
+// then test for their presence in e.g. STB_TEXTEDIT_K_WORDLEFT. For example,
+// my Windows implementations add an additional CONTROL bit, and an additional KEYDOWN
+// bit. Then all of the STB_TEXTEDIT_K_ values bitwise-or in the KEYDOWN bit,
+// and I pass both WM_KEYDOWN and WM_CHAR events to the "key" function in the
+// API below. The control keys will only match WM_KEYDOWN events because of the
+// keydown bit I add, and STB_TEXTEDIT_KEYTOTEXT only tests for the KEYDOWN
+// bit so it only decodes WM_CHAR events.
+//
+// STB_TEXTEDIT_LAYOUTROW returns information about the shape of one displayed
+// row of characters assuming they start on the i'th character--the width and
+// the height and the number of characters consumed. This allows this library
+// to traverse the entire layout incrementally. You need to compute word-wrapping
+// here.
+//
+// Each textfield keeps its own insert mode state, which is not how normal
+// applications work. To keep an app-wide insert mode, update/copy the
+// "insert_mode" field of STB_TexteditState before/after calling API functions.
+//
+// API
+//
+//    void stb_textedit_initialize_state(STB_TexteditState *state, int is_single_line)
+//
+//    void stb_textedit_click(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y)
+//    void stb_textedit_drag(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y)
+//    int  stb_textedit_cut(STB_TEXTEDIT_STRING *str, STB_TexteditState *state)
+//    int  stb_textedit_paste(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE *text, int len)
+//    void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXEDIT_KEYTYPE key)
+//
+//    Each of these functions potentially updates the string and updates the
+//    state.
+//
+//      initialize_state:
+//          set the textedit state to a known good default state when initially
+//          constructing the textedit.
+//
+//      click:
+//          call this with the mouse x,y on a mouse down; it will update the cursor
+//          and reset the selection start/end to the cursor point. the x,y must
+//          be relative to the text widget, with (0,0) being the top left.
+//     
+//      drag:
+//          call this with the mouse x,y on a mouse drag/up; it will update the
+//          cursor and the selection end point
+//     
+//      cut:
+//          call this to delete the current selection; returns true if there was
+//          one. you should FIRST copy the current selection to the system paste buffer.
+//          (To copy, just copy the current selection out of the string yourself.)
+//     
+//      paste:
+//          call this to paste text at the current cursor point or over the current
+//          selection if there is one.
+//     
+//      key:
+//          call this for keyboard inputs sent to the textfield. you can use it
+//          for "key down" events or for "translated" key events. if you need to
+//          do both (as in Win32), or distinguish Unicode characters from control
+//          inputs, set a high bit to distinguish the two; then you can define the
+//          various definitions like STB_TEXTEDIT_K_LEFT have the is-key-event bit
+//          set, and make STB_TEXTEDIT_KEYTOCHAR check that the is-key-event bit is
+//          clear. STB_TEXTEDIT_KEYTYPE defaults to int, but you can #define it to
+//          anything other type you wante before including.
+//
+//     
+//   When rendering, you can read the cursor position and selection state from
+//   the STB_TexteditState.
+//
+//
+// Notes:
+//
+// This is designed to be usable in IMGUI, so it allows for the possibility of
+// running in an IMGUI that has NOT cached the multi-line layout. For this
+// reason, it provides an interface that is compatible with computing the
+// layout incrementally--we try to make sure we make as few passes through
+// as possible. (For example, to locate the mouse pointer in the text, we
+// could define functions that return the X and Y positions of characters
+// and binary search Y and then X, but if we're doing dynamic layout this
+// will run the layout algorithm many times, so instead we manually search
+// forward in one pass. Similar logic applies to e.g. up-arrow and
+// down-arrow movement.)
+//
+// If it's run in a widget that *has* cached the layout, then this is less
+// efficient, but it's not horrible on modern computers. But you wouldn't
+// want to edit million-line files with it.
+
+
+////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////
+////
+////   Header-file mode
+////
+////
+
+#ifndef INCLUDE_STB_TEXTEDIT_H
+#define INCLUDE_STB_TEXTEDIT_H
+
+////////////////////////////////////////////////////////////////////////
+//
+//     STB_TexteditState
+//
+// Definition of STB_TexteditState which you should store
+// per-textfield; it includes cursor position, selection state,
+// and undo state.
+//
+
+#ifndef STB_TEXTEDIT_UNDOSTATECOUNT
+#define STB_TEXTEDIT_UNDOSTATECOUNT   99
+#endif
+#ifndef STB_TEXTEDIT_UNDOCHARCOUNT
+#define STB_TEXTEDIT_UNDOCHARCOUNT   999
+#endif
+#ifndef STB_TEXTEDIT_CHARTYPE
+#define STB_TEXTEDIT_CHARTYPE        int
+#endif
+#ifndef STB_TEXTEDIT_POSITIONTYPE
+#define STB_TEXTEDIT_POSITIONTYPE    int
+#endif
+
+typedef struct
+{
+   // private data
+   STB_TEXTEDIT_POSITIONTYPE  where;
+   STB_TEXTEDIT_POSITIONTYPE  insert_length;
+   STB_TEXTEDIT_POSITIONTYPE  delete_length;
+   int                        char_storage;
+} StbUndoRecord;
+
+typedef struct
+{
+   // private data
+   StbUndoRecord          undo_rec [STB_TEXTEDIT_UNDOSTATECOUNT];
+   STB_TEXTEDIT_CHARTYPE  undo_char[STB_TEXTEDIT_UNDOCHARCOUNT];
+   short undo_point, redo_point;
+   int undo_char_point, redo_char_point;
+} StbUndoState;
+
+typedef struct
+{
+   /////////////////////
+   //
+   // public data
+   //
+
+   int cursor;
+   // position of the text cursor within the string
+
+   int select_start;          // selection start point
+   int select_end;
+   // selection start and end point in characters; if equal, no selection.
+   // note that start may be less than or greater than end (e.g. when
+   // dragging the mouse, start is where the initial click was, and you
+   // can drag in either direction)
+
+   unsigned char insert_mode;
+   // each textfield keeps its own insert mode state. to keep an app-wide
+   // insert mode, copy this value in/out of the app state
+
+   /////////////////////
+   //
+   // private data
+   //
+   unsigned char cursor_at_end_of_line; // not implemented yet
+   unsigned char initialized;
+   unsigned char has_preferred_x;
+   unsigned char single_line;
+   unsigned char padding1, padding2, padding3;
+   float preferred_x; // this determines where the cursor up/down tries to seek to along x
+   StbUndoState undostate;
+} STB_TexteditState;
+
+
+////////////////////////////////////////////////////////////////////////
+//
+//     StbTexteditRow
+//
+// Result of layout query, used by stb_textedit to determine where
+// the text in each row is.
+
+// result of layout query
+typedef struct
+{
+   float x0,x1;             // starting x location, end x location (allows for align=right, etc)
+   float baseline_y_delta;  // position of baseline relative to previous row's baseline
+   float ymin,ymax;         // height of row above and below baseline
+   int num_chars;
+} StbTexteditRow;
+#endif //INCLUDE_STB_TEXTEDIT_H
+
+
+////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////
+////
+////   Implementation mode
+////
+////
+
+
+// implementation isn't include-guarded, since it might have indirectly
+// included just the "header" portion
+#ifdef STB_TEXTEDIT_IMPLEMENTATION
+
+#ifndef STB_TEXTEDIT_memmove
+#include <string.h>
+#define STB_TEXTEDIT_memmove memmove
+#endif
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+//      Mouse input handling
+//
+
+// traverse the layout to locate the nearest character to a display position
+static int stb_text_locate_coord(STB_TEXTEDIT_STRING *str, float x, float y)
+{
+   StbTexteditRow r;
+   int n = STB_TEXTEDIT_STRINGLEN(str);
+   float base_y = 0, prev_x;
+   int i=0, k;
+
+   r.x0 = r.x1 = 0;
+   r.ymin = r.ymax = 0;
+   r.num_chars = 0;
+
+   // search rows to find one that straddles 'y'
+   while (i < n) {
+      STB_TEXTEDIT_LAYOUTROW(&r, str, i);
+      if (r.num_chars <= 0)
+         return n;
+
+      if (i==0 && y < base_y + r.ymin)
+         return 0;
+
+      if (y < base_y + r.ymax)
+         break;
+
+      i += r.num_chars;
+      base_y += r.baseline_y_delta;
+   }
+
+   // below all text, return 'after' last character
+   if (i >= n)
+      return n;
+
+   // check if it's before the beginning of the line
+   if (x < r.x0)
+      return i;
+
+   // check if it's before the end of the line
+   if (x < r.x1) {
+      // search characters in row for one that straddles 'x'
+      prev_x = r.x0;
+      for (k=0; k < r.num_chars; ++k) {
+         float w = STB_TEXTEDIT_GETWIDTH(str, i, k);
+         if (x < prev_x+w) {
+            if (x < prev_x+w/2)
+               return k+i;
+            else
+               return k+i+1;
+         }
+         prev_x += w;
+      }
+      // shouldn't happen, but if it does, fall through to end-of-line case
+   }
+
+   // if the last character is a newline, return that. otherwise return 'after' the last character
+   if (STB_TEXTEDIT_GETCHAR(str, i+r.num_chars-1) == STB_TEXTEDIT_NEWLINE)
+      return i+r.num_chars-1;
+   else
+      return i+r.num_chars;
+}
+
+// API click: on mouse down, move the cursor to the clicked location, and reset the selection
+static void stb_textedit_click(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y)
+{
+   // In single-line mode, just always make y = 0. This lets the drag keep working if the mouse
+   // goes off the top or bottom of the text
+   if( state->single_line )
+   {
+      StbTexteditRow r;
+      STB_TEXTEDIT_LAYOUTROW(&r, str, 0);
+      y = r.ymin;
+   }
+
+   state->cursor = stb_text_locate_coord(str, x, y);
+   state->select_start = state->cursor;
+   state->select_end = state->cursor;
+   state->has_preferred_x = 0;
+}
+
+// API drag: on mouse drag, move the cursor and selection endpoint to the clicked location
+static void stb_textedit_drag(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y)
+{
+   int p = 0;
+
+   // In single-line mode, just always make y = 0. This lets the drag keep working if the mouse
+   // goes off the top or bottom of the text
+   if( state->single_line )
+   {
+      StbTexteditRow r;
+      STB_TEXTEDIT_LAYOUTROW(&r, str, 0);
+      y = r.ymin;
+   }
+
+   if (state->select_start == state->select_end)
+      state->select_start = state->cursor;
+
+   p = stb_text_locate_coord(str, x, y);
+   state->cursor = state->select_end = p;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+//      Keyboard input handling
+//
+
+// forward declarations
+static void stb_text_undo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state);
+static void stb_text_redo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state);
+static void stb_text_makeundo_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int length);
+static void stb_text_makeundo_insert(STB_TexteditState *state, int where, int length);
+static void stb_text_makeundo_replace(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int old_length, int new_length);
+
+typedef struct
+{
+   float x,y;    // position of n'th character
+   float height; // height of line
+   int first_char, length; // first char of row, and length
+   int prev_first;  // first char of previous row
+} StbFindState;
+
+// find the x/y location of a character, and remember info about the previous row in
+// case we get a move-up event (for page up, we'll have to rescan)
+static void stb_textedit_find_charpos(StbFindState *find, STB_TEXTEDIT_STRING *str, int n, int single_line)
+{
+   StbTexteditRow r;
+   int prev_start = 0;
+   int z = STB_TEXTEDIT_STRINGLEN(str);
+   int i=0, first;
+
+   if (n == z) {
+      // if it's at the end, then find the last line -- simpler than trying to
+      // explicitly handle this case in the regular code
+      if (single_line) {
+         STB_TEXTEDIT_LAYOUTROW(&r, str, 0);
+         find->y = 0;
+         find->first_char = 0;
+         find->length = z;
+         find->height = r.ymax - r.ymin;
+         find->x = r.x1;
+      } else {
+         find->y = 0;
+         find->x = 0;
+         find->height = 1;
+         while (i < z) {
+            STB_TEXTEDIT_LAYOUTROW(&r, str, i);
+            prev_start = i;
+            i += r.num_chars;
+         }
+         find->first_char = i;
+         find->length = 0;
+         find->prev_first = prev_start;
+      }
+      return;
+   }
+
+   // search rows to find the one that straddles character n
+   find->y = 0;
+
+   for(;;) {
+      STB_TEXTEDIT_LAYOUTROW(&r, str, i);
+      if (n < i + r.num_chars)
+         break;
+      prev_start = i;
+      i += r.num_chars;
+      find->y += r.baseline_y_delta;
+   }
+
+   find->first_char = first = i;
+   find->length = r.num_chars;
+   find->height = r.ymax - r.ymin;
+   find->prev_first = prev_start;
+
+   // now scan to find xpos
+   find->x = r.x0;
+   i = 0;
+   for (i=0; first+i < n; ++i)
+      find->x += STB_TEXTEDIT_GETWIDTH(str, first, i);
+}
+
+#define STB_TEXT_HAS_SELECTION(s)   ((s)->select_start != (s)->select_end)
+
+// make the selection/cursor state valid if client altered the string
+static void stb_textedit_clamp(STB_TEXTEDIT_STRING *str, STB_TexteditState *state)
+{
+   int n = STB_TEXTEDIT_STRINGLEN(str);
+   if (STB_TEXT_HAS_SELECTION(state)) {
+      if (state->select_start > n) state->select_start = n;
+      if (state->select_end   > n) state->select_end = n;
+      // if clamping forced them to be equal, move the cursor to match
+      if (state->select_start == state->select_end)
+         state->cursor = state->select_start;
+   }
+   if (state->cursor > n) state->cursor = n;
+}
+
+// delete characters while updating undo
+static void stb_textedit_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int len)
+{
+   stb_text_makeundo_delete(str, state, where, len);
+   STB_TEXTEDIT_DELETECHARS(str, where, len);
+   state->has_preferred_x = 0;
+}
+
+// delete the section
+static void stb_textedit_delete_selection(STB_TEXTEDIT_STRING *str, STB_TexteditState *state)
+{
+   stb_textedit_clamp(str, state);
+   if (STB_TEXT_HAS_SELECTION(state)) {
+      if (state->select_start < state->select_end) {
+         stb_textedit_delete(str, state, state->select_start, state->select_end - state->select_start);
+         state->select_end = state->cursor = state->select_start;
+      } else {
+         stb_textedit_delete(str, state, state->select_end, state->select_start - state->select_end);
+         state->select_start = state->cursor = state->select_end;
+      }
+      state->has_preferred_x = 0;
+   }
+}
+
+// canoncialize the selection so start <= end
+static void stb_textedit_sortselection(STB_TexteditState *state)
+{
+   if (state->select_end < state->select_start) {
+      int temp = state->select_end;
+      state->select_end = state->select_start;
+      state->select_start = temp;
+   }
+}
+
+// move cursor to first character of selection
+static void stb_textedit_move_to_first(STB_TexteditState *state)
+{
+   if (STB_TEXT_HAS_SELECTION(state)) {
+      stb_textedit_sortselection(state);
+      state->cursor = state->select_start;
+      state->select_end = state->select_start;
+      state->has_preferred_x = 0;
+   }
+}
+
+// move cursor to last character of selection
+static void stb_textedit_move_to_last(STB_TEXTEDIT_STRING *str, STB_TexteditState *state)
+{
+   if (STB_TEXT_HAS_SELECTION(state)) {
+      stb_textedit_sortselection(state);
+      stb_textedit_clamp(str, state);
+      state->cursor = state->select_end;
+      state->select_start = state->select_end;
+      state->has_preferred_x = 0;
+   }
+}
+
+#ifdef STB_TEXTEDIT_IS_SPACE
+static int is_word_boundary( STB_TEXTEDIT_STRING *str, int idx )
+{
+   return idx > 0 ? (STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(str,idx-1) ) && !STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(str, idx) ) ) : 1;
+}
+
+#ifndef STB_TEXTEDIT_MOVEWORDLEFT
+static int stb_textedit_move_to_word_previous( STB_TEXTEDIT_STRING *str, int c )
+{
+   --c; // always move at least one character
+   while( c >= 0 && !is_word_boundary( str, c ) )
+      --c;
+
+   if( c < 0 )
+      c = 0;
+
+   return c;
+}
+#define STB_TEXTEDIT_MOVEWORDLEFT stb_textedit_move_to_word_previous
+#endif
+
+#ifndef STB_TEXTEDIT_MOVEWORDRIGHT
+static int stb_textedit_move_to_word_next( STB_TEXTEDIT_STRING *str, int c )
+{
+   const int len = STB_TEXTEDIT_STRINGLEN(str);
+   ++c; // always move at least one character
+   while( c < len && !is_word_boundary( str, c ) )
+      ++c;
+
+   if( c > len )
+      c = len;
+
+   return c;
+}
+#define STB_TEXTEDIT_MOVEWORDRIGHT stb_textedit_move_to_word_next
+#endif
+
+#endif
+
+// update selection and cursor to match each other
+static void stb_textedit_prep_selection_at_cursor(STB_TexteditState *state)
+{
+   if (!STB_TEXT_HAS_SELECTION(state))
+      state->select_start = state->select_end = state->cursor;
+   else
+      state->cursor = state->select_end;
+}
+
+// API cut: delete selection
+static int stb_textedit_cut(STB_TEXTEDIT_STRING *str, STB_TexteditState *state)
+{
+   if (STB_TEXT_HAS_SELECTION(state)) {
+      stb_textedit_delete_selection(str,state); // implicity clamps
+      state->has_preferred_x = 0;
+      return 1;
+   }
+   return 0;
+}
+
+// API paste: replace existing selection with passed-in text
+static int stb_textedit_paste_internal(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE *text, int len)
+{
+   // if there's a selection, the paste should delete it
+   stb_textedit_clamp(str, state);
+   stb_textedit_delete_selection(str,state);
+   // try to insert the characters
+   if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, len)) {
+      stb_text_makeundo_insert(state, state->cursor, len);
+      state->cursor += len;
+      state->has_preferred_x = 0;
+      return 1;
+   }
+   // remove the undo since we didn't actually insert the characters
+   if (state->undostate.undo_point)
+      --state->undostate.undo_point;
+   return 0;
+}
+
+#ifndef STB_TEXTEDIT_KEYTYPE
+#define STB_TEXTEDIT_KEYTYPE int
+#endif
+
+// API key: process a keyboard input
+static void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_KEYTYPE key)
+{
+retry:
+   switch (key) {
+      default: {
+         int c = STB_TEXTEDIT_KEYTOTEXT(key);
+         if (c > 0) {
+            STB_TEXTEDIT_CHARTYPE ch = (STB_TEXTEDIT_CHARTYPE) c;
+
+            // can't add newline in single-line mode
+            if (c == '\n' && state->single_line)
+               break;
+
+            if (state->insert_mode && !STB_TEXT_HAS_SELECTION(state) && state->cursor < STB_TEXTEDIT_STRINGLEN(str)) {
+               stb_text_makeundo_replace(str, state, state->cursor, 1, 1);
+               STB_TEXTEDIT_DELETECHARS(str, state->cursor, 1);
+               if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, &ch, 1)) {
+                  ++state->cursor;
+                  state->has_preferred_x = 0;
+               }
+            } else {
+               stb_textedit_delete_selection(str,state); // implicity clamps
+               if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, &ch, 1)) {
+                  stb_text_makeundo_insert(state, state->cursor, 1);
+                  ++state->cursor;
+                  state->has_preferred_x = 0;
+               }
+            }
+         }
+         break;
+      }
+
+#ifdef STB_TEXTEDIT_K_INSERT
+      case STB_TEXTEDIT_K_INSERT:
+         state->insert_mode = !state->insert_mode;
+         break;
+#endif
+         
+      case STB_TEXTEDIT_K_UNDO:
+         stb_text_undo(str, state);
+         state->has_preferred_x = 0;
+         break;
+
+      case STB_TEXTEDIT_K_REDO:
+         stb_text_redo(str, state);
+         state->has_preferred_x = 0;
+         break;
+
+      case STB_TEXTEDIT_K_LEFT:
+         // if currently there's a selection, move cursor to start of selection
+         if (STB_TEXT_HAS_SELECTION(state))
+            stb_textedit_move_to_first(state);
+         else 
+            if (state->cursor > 0)
+               --state->cursor;
+         state->has_preferred_x = 0;
+         break;
+
+      case STB_TEXTEDIT_K_RIGHT:
+         // if currently there's a selection, move cursor to end of selection
+         if (STB_TEXT_HAS_SELECTION(state))
+            stb_textedit_move_to_last(str, state);
+         else
+            ++state->cursor;
+         stb_textedit_clamp(str, state);
+         state->has_preferred_x = 0;
+         break;
+
+      case STB_TEXTEDIT_K_LEFT | STB_TEXTEDIT_K_SHIFT:
+         stb_textedit_clamp(str, state);
+         stb_textedit_prep_selection_at_cursor(state);
+         // move selection left
+         if (state->select_end > 0)
+            --state->select_end;
+         state->cursor = state->select_end;
+         state->has_preferred_x = 0;
+         break;
+
+#ifdef STB_TEXTEDIT_MOVEWORDLEFT
+      case STB_TEXTEDIT_K_WORDLEFT:
+         if (STB_TEXT_HAS_SELECTION(state))
+            stb_textedit_move_to_first(state);
+         else {
+            state->cursor = STB_TEXTEDIT_MOVEWORDLEFT(str, state->cursor);
+            stb_textedit_clamp( str, state );
+         }
+         break;
+
+      case STB_TEXTEDIT_K_WORDLEFT | STB_TEXTEDIT_K_SHIFT:
+         if( !STB_TEXT_HAS_SELECTION( state ) )
+            stb_textedit_prep_selection_at_cursor(state);
+
+         state->cursor = STB_TEXTEDIT_MOVEWORDLEFT(str, state->cursor);
+         state->select_end = state->cursor;
+
+         stb_textedit_clamp( str, state );
+         break;
+#endif
+
+#ifdef STB_TEXTEDIT_MOVEWORDRIGHT
+      case STB_TEXTEDIT_K_WORDRIGHT:
+         if (STB_TEXT_HAS_SELECTION(state)) 
+            stb_textedit_move_to_last(str, state);
+         else {
+            state->cursor = STB_TEXTEDIT_MOVEWORDRIGHT(str, state->cursor);
+            stb_textedit_clamp( str, state );
+         }
+         break;
+
+      case STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT:
+         if( !STB_TEXT_HAS_SELECTION( state ) )
+            stb_textedit_prep_selection_at_cursor(state);
+
+         state->cursor = STB_TEXTEDIT_MOVEWORDRIGHT(str, state->cursor);
+         state->select_end = state->cursor;
+
+         stb_textedit_clamp( str, state );
+         break;
+#endif
+
+      case STB_TEXTEDIT_K_RIGHT | STB_TEXTEDIT_K_SHIFT:
+         stb_textedit_prep_selection_at_cursor(state);
+         // move selection right
+         ++state->select_end;
+         stb_textedit_clamp(str, state);
+         state->cursor = state->select_end;
+         state->has_preferred_x = 0;
+         break;
+
+      case STB_TEXTEDIT_K_DOWN:
+      case STB_TEXTEDIT_K_DOWN | STB_TEXTEDIT_K_SHIFT: {
+         StbFindState find;
+         StbTexteditRow row;
+         int i, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0;
+
+         if (state->single_line) {
+            // on windows, up&down in single-line behave like left&right
+            key = STB_TEXTEDIT_K_RIGHT | (key & STB_TEXTEDIT_K_SHIFT);
+            goto retry;
+         }
+
+         if (sel)
+            stb_textedit_prep_selection_at_cursor(state);
+         else if (STB_TEXT_HAS_SELECTION(state))
+            stb_textedit_move_to_last(str,state);
+
+         // compute current position of cursor point
+         stb_textedit_clamp(str, state);
+         stb_textedit_find_charpos(&find, str, state->cursor, state->single_line);
+
+         // now find character position down a row
+         if (find.length) {
+            float goal_x = state->has_preferred_x ? state->preferred_x : find.x;
+            float x;
+            int start = find.first_char + find.length;
+            state->cursor = start;
+            STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor);
+            x = row.x0;
+            for (i=0; i < row.num_chars; ++i) {
+               float dx = STB_TEXTEDIT_GETWIDTH(str, start, i);
+               #ifdef STB_TEXTEDIT_GETWIDTH_NEWLINE
+               if (dx == STB_TEXTEDIT_GETWIDTH_NEWLINE)
+                  break;
+               #endif
+               x += dx;
+               if (x > goal_x)
+                  break;
+               ++state->cursor;
+            }
+            stb_textedit_clamp(str, state);
+
+            state->has_preferred_x = 1;
+            state->preferred_x = goal_x;
+
+            if (sel)
+               state->select_end = state->cursor;
+         }
+         break;
+      }
+         
+      case STB_TEXTEDIT_K_UP:
+      case STB_TEXTEDIT_K_UP | STB_TEXTEDIT_K_SHIFT: {
+         StbFindState find;
+         StbTexteditRow row;
+         int i, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0;
+
+         if (state->single_line) {
+            // on windows, up&down become left&right
+            key = STB_TEXTEDIT_K_LEFT | (key & STB_TEXTEDIT_K_SHIFT);
+            goto retry;
+         }
+
+         if (sel)
+            stb_textedit_prep_selection_at_cursor(state);
+         else if (STB_TEXT_HAS_SELECTION(state))
+            stb_textedit_move_to_first(state);
+
+         // compute current position of cursor point
+         stb_textedit_clamp(str, state);
+         stb_textedit_find_charpos(&find, str, state->cursor, state->single_line);
+
+         // can only go up if there's a previous row
+         if (find.prev_first != find.first_char) {
+            // now find character position up a row
+            float goal_x = state->has_preferred_x ? state->preferred_x : find.x;
+            float x;
+            state->cursor = find.prev_first;
+            STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor);
+            x = row.x0;
+            for (i=0; i < row.num_chars; ++i) {
+               float dx = STB_TEXTEDIT_GETWIDTH(str, find.prev_first, i);
+               #ifdef STB_TEXTEDIT_GETWIDTH_NEWLINE
+               if (dx == STB_TEXTEDIT_GETWIDTH_NEWLINE)
+                  break;
+               #endif
+               x += dx;
+               if (x > goal_x)
+                  break;
+               ++state->cursor;
+            }
+            stb_textedit_clamp(str, state);
+
+            state->has_preferred_x = 1;
+            state->preferred_x = goal_x;
+
+            if (sel)
+               state->select_end = state->cursor;
+         }
+         break;
+      }
+
+      case STB_TEXTEDIT_K_DELETE:
+      case STB_TEXTEDIT_K_DELETE | STB_TEXTEDIT_K_SHIFT:
+         if (STB_TEXT_HAS_SELECTION(state))
+            stb_textedit_delete_selection(str, state);
+         else {
+            int n = STB_TEXTEDIT_STRINGLEN(str);
+            if (state->cursor < n)
+               stb_textedit_delete(str, state, state->cursor, 1);
+         }
+         state->has_preferred_x = 0;
+         break;
+
+      case STB_TEXTEDIT_K_BACKSPACE:
+      case STB_TEXTEDIT_K_BACKSPACE | STB_TEXTEDIT_K_SHIFT:
+         if (STB_TEXT_HAS_SELECTION(state))
+            stb_textedit_delete_selection(str, state);
+         else {
+            stb_textedit_clamp(str, state);
+            if (state->cursor > 0) {
+               stb_textedit_delete(str, state, state->cursor-1, 1);
+               --state->cursor;
+            }
+         }
+         state->has_preferred_x = 0;
+         break;
+         
+#ifdef STB_TEXTEDIT_K_TEXTSTART2
+      case STB_TEXTEDIT_K_TEXTSTART2:
+#endif
+      case STB_TEXTEDIT_K_TEXTSTART:
+         state->cursor = state->select_start = state->select_end = 0;
+         state->has_preferred_x = 0;
+         break;
+
+#ifdef STB_TEXTEDIT_K_TEXTEND2
+      case STB_TEXTEDIT_K_TEXTEND2:
+#endif
+      case STB_TEXTEDIT_K_TEXTEND:
+         state->cursor = STB_TEXTEDIT_STRINGLEN(str);
+         state->select_start = state->select_end = 0;
+         state->has_preferred_x = 0;
+         break;
+        
+#ifdef STB_TEXTEDIT_K_TEXTSTART2
+      case STB_TEXTEDIT_K_TEXTSTART2 | STB_TEXTEDIT_K_SHIFT:
+#endif
+      case STB_TEXTEDIT_K_TEXTSTART | STB_TEXTEDIT_K_SHIFT:
+         stb_textedit_prep_selection_at_cursor(state);
+         state->cursor = state->select_end = 0;
+         state->has_preferred_x = 0;
+         break;
+
+#ifdef STB_TEXTEDIT_K_TEXTEND2
+      case STB_TEXTEDIT_K_TEXTEND2 | STB_TEXTEDIT_K_SHIFT:
+#endif
+      case STB_TEXTEDIT_K_TEXTEND | STB_TEXTEDIT_K_SHIFT:
+         stb_textedit_prep_selection_at_cursor(state);
+         state->cursor = state->select_end = STB_TEXTEDIT_STRINGLEN(str);
+         state->has_preferred_x = 0;
+         break;
+
+
+#ifdef STB_TEXTEDIT_K_LINESTART2
+      case STB_TEXTEDIT_K_LINESTART2:
+#endif
+      case STB_TEXTEDIT_K_LINESTART:
+         stb_textedit_clamp(str, state);
+         stb_textedit_move_to_first(state);
+         if (state->single_line)
+            state->cursor = 0;
+         else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE)
+            --state->cursor;
+         state->has_preferred_x = 0;
+         break;
+
+#ifdef STB_TEXTEDIT_K_LINEEND2
+      case STB_TEXTEDIT_K_LINEEND2:
+#endif
+      case STB_TEXTEDIT_K_LINEEND: {
+         int n = STB_TEXTEDIT_STRINGLEN(str);
+         stb_textedit_clamp(str, state);
+         stb_textedit_move_to_first(state);
+         if (state->single_line)
+             state->cursor = n;
+         else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE)
+             ++state->cursor;
+         state->has_preferred_x = 0;
+         break;
+      }
+
+#ifdef STB_TEXTEDIT_K_LINESTART2
+      case STB_TEXTEDIT_K_LINESTART2 | STB_TEXTEDIT_K_SHIFT:
+#endif
+      case STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT:
+         stb_textedit_clamp(str, state);
+         stb_textedit_prep_selection_at_cursor(state);
+         if (state->single_line)
+            state->cursor = 0;
+         else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE)
+            --state->cursor;
+         state->select_end = state->cursor;
+         state->has_preferred_x = 0;
+         break;
+
+#ifdef STB_TEXTEDIT_K_LINEEND2
+      case STB_TEXTEDIT_K_LINEEND2 | STB_TEXTEDIT_K_SHIFT:
+#endif
+      case STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_SHIFT: {
+         int n = STB_TEXTEDIT_STRINGLEN(str);
+         stb_textedit_clamp(str, state);
+         stb_textedit_prep_selection_at_cursor(state);
+         if (state->single_line)
+             state->cursor = n;
+         else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE)
+            ++state->cursor;
+         state->select_end = state->cursor;
+         state->has_preferred_x = 0;
+         break;
+      }
+
+// @TODO:
+//    STB_TEXTEDIT_K_PGUP      - move cursor up a page
+//    STB_TEXTEDIT_K_PGDOWN    - move cursor down a page
+   }
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+//      Undo processing
+//
+// @OPTIMIZE: the undo/redo buffer should be circular
+
+static void stb_textedit_flush_redo(StbUndoState *state)
+{
+   state->redo_point = STB_TEXTEDIT_UNDOSTATECOUNT;
+   state->redo_char_point = STB_TEXTEDIT_UNDOCHARCOUNT;
+}
+
+// discard the oldest entry in the undo list
+static void stb_textedit_discard_undo(StbUndoState *state)
+{
+   if (state->undo_point > 0) {
+      // if the 0th undo state has characters, clean those up
+      if (state->undo_rec[0].char_storage >= 0) {
+         int n = state->undo_rec[0].insert_length, i;
+         // delete n characters from all other records
+         state->undo_char_point -= n;
+         STB_TEXTEDIT_memmove(state->undo_char, state->undo_char + n, (size_t) (state->undo_char_point*sizeof(STB_TEXTEDIT_CHARTYPE)));
+         for (i=0; i < state->undo_point; ++i)
+            if (state->undo_rec[i].char_storage >= 0)
+               state->undo_rec[i].char_storage -= n; // @OPTIMIZE: get rid of char_storage and infer it
+      }
+      --state->undo_point;
+      STB_TEXTEDIT_memmove(state->undo_rec, state->undo_rec+1, (size_t) (state->undo_point*sizeof(state->undo_rec[0])));
+   }
+}
+
+// discard the oldest entry in the redo list--it's bad if this
+// ever happens, but because undo & redo have to store the actual
+// characters in different cases, the redo character buffer can
+// fill up even though the undo buffer didn't
+static void stb_textedit_discard_redo(StbUndoState *state)
+{
+   int k = STB_TEXTEDIT_UNDOSTATECOUNT-1;
+
+   if (state->redo_point <= k) {
+      // if the k'th undo state has characters, clean those up
+      if (state->undo_rec[k].char_storage >= 0) {
+         int n = state->undo_rec[k].insert_length, i;
+         // move the remaining redo character data to the end of the buffer
+         state->redo_char_point += n;
+         STB_TEXTEDIT_memmove(state->undo_char + state->redo_char_point, state->undo_char + state->redo_char_point-n, (size_t) ((STB_TEXTEDIT_UNDOCHARCOUNT - state->redo_char_point)*sizeof(STB_TEXTEDIT_CHARTYPE)));
+         // adjust the position of all the other records to account for above memmove
+         for (i=state->redo_point; i < k; ++i)
+            if (state->undo_rec[i].char_storage >= 0)
+               state->undo_rec[i].char_storage += n;
+      }
+      // now move all the redo records towards the end of the buffer; the first one is at 'redo_point'
+      STB_TEXTEDIT_memmove(state->undo_rec + state->redo_point+1, state->undo_rec + state->redo_point, (size_t) ((STB_TEXTEDIT_UNDOSTATECOUNT - state->redo_point)*sizeof(state->undo_rec[0])));
+      // now move redo_point to point to the new one
+      ++state->redo_point;
+   }
+}
+
+static StbUndoRecord *stb_text_create_undo_record(StbUndoState *state, int numchars)
+{
+   // any time we create a new undo record, we discard redo
+   stb_textedit_flush_redo(state);
+
+   // if we have no free records, we have to make room, by sliding the
+   // existing records down
+   if (state->undo_point == STB_TEXTEDIT_UNDOSTATECOUNT)
+      stb_textedit_discard_undo(state);
+
+   // if the characters to store won't possibly fit in the buffer, we can't undo
+   if (numchars > STB_TEXTEDIT_UNDOCHARCOUNT) {
+      state->undo_point = 0;
+      state->undo_char_point = 0;
+      return NULL;
+   }
+
+   // if we don't have enough free characters in the buffer, we have to make room
+   while (state->undo_char_point + numchars > STB_TEXTEDIT_UNDOCHARCOUNT)
+      stb_textedit_discard_undo(state);
+
+   return &state->undo_rec[state->undo_point++];
+}
+
+static STB_TEXTEDIT_CHARTYPE *stb_text_createundo(StbUndoState *state, int pos, int insert_len, int delete_len)
+{
+   StbUndoRecord *r = stb_text_create_undo_record(state, insert_len);
+   if (r == NULL)
+      return NULL;
+
+   r->where = pos;
+   r->insert_length = (STB_TEXTEDIT_POSITIONTYPE) insert_len;
+   r->delete_length = (STB_TEXTEDIT_POSITIONTYPE) delete_len;
+
+   if (insert_len == 0) {
+      r->char_storage = -1;
+      return NULL;
+   } else {
+      r->char_storage = state->undo_char_point;
+      state->undo_char_point += insert_len;
+      return &state->undo_char[r->char_storage];
+   }
+}
+
+static void stb_text_undo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state)
+{
+   StbUndoState *s = &state->undostate;
+   StbUndoRecord u, *r;
+   if (s->undo_point == 0)
+      return;
+
+   // we need to do two things: apply the undo record, and create a redo record
+   u = s->undo_rec[s->undo_point-1];
+   r = &s->undo_rec[s->redo_point-1];
+   r->char_storage = -1;
+
+   r->insert_length = u.delete_length;
+   r->delete_length = u.insert_length;
+   r->where = u.where;
+
+   if (u.delete_length) {
+      // if the undo record says to delete characters, then the redo record will
+      // need to re-insert the characters that get deleted, so we need to store
+      // them.
+
+      // there are three cases:
+      //    there's enough room to store the characters
+      //    characters stored for *redoing* don't leave room for redo
+      //    characters stored for *undoing* don't leave room for redo
+      // if the last is true, we have to bail
+
+      if (s->undo_char_point + u.delete_length >= STB_TEXTEDIT_UNDOCHARCOUNT) {
+         // the undo records take up too much character space; there's no space to store the redo characters
+         r->insert_length = 0;
+      } else {
+         int i;
+
+         // there's definitely room to store the characters eventually
+         while (s->undo_char_point + u.delete_length > s->redo_char_point) {
+            // should never happen:
+            if (s->redo_point == STB_TEXTEDIT_UNDOSTATECOUNT)
+               return;
+            // there's currently not enough room, so discard a redo record
+            stb_textedit_discard_redo(s);
+         }
+         r = &s->undo_rec[s->redo_point-1];
+
+         r->char_storage = s->redo_char_point - u.delete_length;
+         s->redo_char_point = s->redo_char_point - u.delete_length;
+
+         // now save the characters
+         for (i=0; i < u.delete_length; ++i)
+            s->undo_char[r->char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u.where + i);
+      }
+
+      // now we can carry out the deletion
+      STB_TEXTEDIT_DELETECHARS(str, u.where, u.delete_length);
+   }
+
+   // check type of recorded action:
+   if (u.insert_length) {
+      // easy case: was a deletion, so we need to insert n characters
+      STB_TEXTEDIT_INSERTCHARS(str, u.where, &s->undo_char[u.char_storage], u.insert_length);
+      s->undo_char_point -= u.insert_length;
+   }
+
+   state->cursor = u.where + u.insert_length;
+
+   s->undo_point--;
+   s->redo_point--;
+}
+
+static void stb_text_redo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state)
+{
+   StbUndoState *s = &state->undostate;
+   StbUndoRecord *u, r;
+   if (s->redo_point == STB_TEXTEDIT_UNDOSTATECOUNT)
+      return;
+
+   // we need to do two things: apply the redo record, and create an undo record
+   u = &s->undo_rec[s->undo_point];
+   r = s->undo_rec[s->redo_point];
+
+   // we KNOW there must be room for the undo record, because the redo record
+   // was derived from an undo record
+
+   u->delete_length = r.insert_length;
+   u->insert_length = r.delete_length;
+   u->where = r.where;
+   u->char_storage = -1;
+
+   if (r.delete_length) {
+      // the redo record requires us to delete characters, so the undo record
+      // needs to store the characters
+
+      if (s->undo_char_point + u->insert_length > s->redo_char_point) {
+         u->insert_length = 0;
+         u->delete_length = 0;
+      } else {
+         int i;
+         u->char_storage = s->undo_char_point;
+         s->undo_char_point = s->undo_char_point + u->insert_length;
+
+         // now save the characters
+         for (i=0; i < u->insert_length; ++i)
+            s->undo_char[u->char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u->where + i);
+      }
+
+      STB_TEXTEDIT_DELETECHARS(str, r.where, r.delete_length);
+   }
+
+   if (r.insert_length) {
+      // easy case: need to insert n characters
+      STB_TEXTEDIT_INSERTCHARS(str, r.where, &s->undo_char[r.char_storage], r.insert_length);
+      s->redo_char_point += r.insert_length;
+   }
+
+   state->cursor = r.where + r.insert_length;
+
+   s->undo_point++;
+   s->redo_point++;
+}
+
+static void stb_text_makeundo_insert(STB_TexteditState *state, int where, int length)
+{
+   stb_text_createundo(&state->undostate, where, 0, length);
+}
+
+static void stb_text_makeundo_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int length)
+{
+   int i;
+   STB_TEXTEDIT_CHARTYPE *p = stb_text_createundo(&state->undostate, where, length, 0);
+   if (p) {
+      for (i=0; i < length; ++i)
+         p[i] = STB_TEXTEDIT_GETCHAR(str, where+i);
+   }
+}
+
+static void stb_text_makeundo_replace(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int old_length, int new_length)
+{
+   int i;
+   STB_TEXTEDIT_CHARTYPE *p = stb_text_createundo(&state->undostate, where, old_length, new_length);
+   if (p) {
+      for (i=0; i < old_length; ++i)
+         p[i] = STB_TEXTEDIT_GETCHAR(str, where+i);
+   }
+}
+
+// reset the state to default
+static void stb_textedit_clear_state(STB_TexteditState *state, int is_single_line)
+{
+   state->undostate.undo_point = 0;
+   state->undostate.undo_char_point = 0;
+   state->undostate.redo_point = STB_TEXTEDIT_UNDOSTATECOUNT;
+   state->undostate.redo_char_point = STB_TEXTEDIT_UNDOCHARCOUNT;
+   state->select_end = state->select_start = 0;
+   state->cursor = 0;
+   state->has_preferred_x = 0;
+   state->preferred_x = 0;
+   state->cursor_at_end_of_line = 0;
+   state->initialized = 1;
+   state->single_line = (unsigned char) is_single_line;
+   state->insert_mode = 0;
+}
+
+// API initialize
+static void stb_textedit_initialize_state(STB_TexteditState *state, int is_single_line)
+{
+   stb_textedit_clear_state(state, is_single_line);
+}
+
+#if defined(__GNUC__) || defined(__clang__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wcast-qual"
+#endif
+
+static int stb_textedit_paste(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE const *ctext, int len)
+{
+   return stb_textedit_paste_internal(str, state, (STB_TEXTEDIT_CHARTYPE *) ctext, len);
+}
+
+#if defined(__GNUC__) || defined(__clang__)
+#pragma GCC diagnostic pop
+#endif
+
+#endif//STB_TEXTEDIT_IMPLEMENTATION
+
+/*
+------------------------------------------------------------------------------
+This software is available under 2 licenses -- choose whichever you prefer.
+------------------------------------------------------------------------------
+ALTERNATIVE A - MIT License
+Copyright (c) 2017 Sean Barrett
+Permission is hereby granted, free of charge, to any person obtaining a copy of 
+this software and associated documentation files (the "Software"), to deal in 
+the Software without restriction, including without limitation the rights to 
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 
+of the Software, and to permit persons to whom the Software is furnished to do 
+so, subject to the following conditions:
+The above copyright notice and this permission notice shall be included in all 
+copies or substantial portions of the Software.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
+SOFTWARE.
+------------------------------------------------------------------------------
+ALTERNATIVE B - Public Domain (www.unlicense.org)
+This is free and unencumbered software released into the public domain.
+Anyone is free to copy, modify, publish, use, compile, sell, or distribute this 
+software, either in source code form or as a compiled binary, for any purpose, 
+commercial or non-commercial, and by any means.
+In jurisdictions that recognize copyright laws, the author or authors of this 
+software dedicate any and all copyright interest in the software to the public 
+domain. We make this dedication for the benefit of the public at large and to 
+the detriment of our heirs and successors. We intend this dedication to be an 
+overt act of relinquishment in perpetuity of all present and future rights to 
+this software under copyright law.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
+AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 
+ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+------------------------------------------------------------------------------
+*/
diff --git a/external/Vulkan/external/imgui/imstb_truetype.h b/external/Vulkan/external/imgui/imstb_truetype.h
new file mode 100644
index 0000000000000000000000000000000000000000..f65deb50346e328ccd75b4ef91f12c946b438f3d
--- /dev/null
+++ b/external/Vulkan/external/imgui/imstb_truetype.h
@@ -0,0 +1,4854 @@
+// stb_truetype.h - v1.19 - public domain
+// authored from 2009-2016 by Sean Barrett / RAD Game Tools
+//
+//   This library processes TrueType files:
+//        parse files
+//        extract glyph metrics
+//        extract glyph shapes
+//        render glyphs to one-channel bitmaps with antialiasing (box filter)
+//        render glyphs to one-channel SDF bitmaps (signed-distance field/function)
+//
+//   Todo:
+//        non-MS cmaps
+//        crashproof on bad data
+//        hinting? (no longer patented)
+//        cleartype-style AA?
+//        optimize: use simple memory allocator for intermediates
+//        optimize: build edge-list directly from curves
+//        optimize: rasterize directly from curves?
+//
+// ADDITIONAL CONTRIBUTORS
+//
+//   Mikko Mononen: compound shape support, more cmap formats
+//   Tor Andersson: kerning, subpixel rendering
+//   Dougall Johnson: OpenType / Type 2 font handling
+//   Daniel Ribeiro Maciel: basic GPOS-based kerning
+//
+//   Misc other:
+//       Ryan Gordon
+//       Simon Glass
+//       github:IntellectualKitty
+//       Imanol Celaya
+//       Daniel Ribeiro Maciel
+//
+//   Bug/warning reports/fixes:
+//       "Zer" on mollyrocket       Fabian "ryg" Giesen
+//       Cass Everitt               Martins Mozeiko
+//       stoiko (Haemimont Games)   Cap Petschulat
+//       Brian Hook                 Omar Cornut
+//       Walter van Niftrik         github:aloucks
+//       David Gow                  Peter LaValle
+//       David Given                Sergey Popov
+//       Ivan-Assen Ivanov          Giumo X. Clanjor
+//       Anthony Pesch              Higor Euripedes
+//       Johan Duparc               Thomas Fields
+//       Hou Qiming                 Derek Vinyard
+//       Rob Loach                  Cort Stratton
+//       Kenney Phillis Jr.         github:oyvindjam
+//       Brian Costabile            github:vassvik
+//       
+// VERSION HISTORY
+//
+//   1.19 (2018-02-11) GPOS kerning, STBTT_fmod
+//   1.18 (2018-01-29) add missing function
+//   1.17 (2017-07-23) make more arguments const; doc fix
+//   1.16 (2017-07-12) SDF support
+//   1.15 (2017-03-03) make more arguments const
+//   1.14 (2017-01-16) num-fonts-in-TTC function
+//   1.13 (2017-01-02) support OpenType fonts, certain Apple fonts
+//   1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual
+//   1.11 (2016-04-02) fix unused-variable warning
+//   1.10 (2016-04-02) user-defined fabs(); rare memory leak; remove duplicate typedef
+//   1.09 (2016-01-16) warning fix; avoid crash on outofmem; use allocation userdata properly
+//   1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges
+//   1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints;
+//                     variant PackFontRanges to pack and render in separate phases;
+//                     fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?);
+//                     fixed an assert() bug in the new rasterizer
+//                     replace assert() with STBTT_assert() in new rasterizer
+//
+//   Full history can be found at the end of this file.
+//
+// LICENSE
+//
+//   See end of file for license information.
+//
+// USAGE
+//
+//   Include this file in whatever places neeed to refer to it. In ONE C/C++
+//   file, write:
+//      #define STB_TRUETYPE_IMPLEMENTATION
+//   before the #include of this file. This expands out the actual
+//   implementation into that C/C++ file.
+//
+//   To make the implementation private to the file that generates the implementation,
+//      #define STBTT_STATIC
+//
+//   Simple 3D API (don't ship this, but it's fine for tools and quick start)
+//           stbtt_BakeFontBitmap()               -- bake a font to a bitmap for use as texture
+//           stbtt_GetBakedQuad()                 -- compute quad to draw for a given char
+//
+//   Improved 3D API (more shippable):
+//           #include "stb_rect_pack.h"           -- optional, but you really want it
+//           stbtt_PackBegin()
+//           stbtt_PackSetOversampling()          -- for improved quality on small fonts
+//           stbtt_PackFontRanges()               -- pack and renders
+//           stbtt_PackEnd()
+//           stbtt_GetPackedQuad()
+//
+//   "Load" a font file from a memory buffer (you have to keep the buffer loaded)
+//           stbtt_InitFont()
+//           stbtt_GetFontOffsetForIndex()        -- indexing for TTC font collections
+//           stbtt_GetNumberOfFonts()             -- number of fonts for TTC font collections
+//
+//   Render a unicode codepoint to a bitmap
+//           stbtt_GetCodepointBitmap()           -- allocates and returns a bitmap
+//           stbtt_MakeCodepointBitmap()          -- renders into bitmap you provide
+//           stbtt_GetCodepointBitmapBox()        -- how big the bitmap must be
+//
+//   Character advance/positioning
+//           stbtt_GetCodepointHMetrics()
+//           stbtt_GetFontVMetrics()
+//           stbtt_GetFontVMetricsOS2()
+//           stbtt_GetCodepointKernAdvance()
+//
+//   Starting with version 1.06, the rasterizer was replaced with a new,
+//   faster and generally-more-precise rasterizer. The new rasterizer more
+//   accurately measures pixel coverage for anti-aliasing, except in the case
+//   where multiple shapes overlap, in which case it overestimates the AA pixel
+//   coverage. Thus, anti-aliasing of intersecting shapes may look wrong. If
+//   this turns out to be a problem, you can re-enable the old rasterizer with
+//        #define STBTT_RASTERIZER_VERSION 1
+//   which will incur about a 15% speed hit.
+//
+// ADDITIONAL DOCUMENTATION
+//
+//   Immediately after this block comment are a series of sample programs.
+//
+//   After the sample programs is the "header file" section. This section
+//   includes documentation for each API function.
+//
+//   Some important concepts to understand to use this library:
+//
+//      Codepoint
+//         Characters are defined by unicode codepoints, e.g. 65 is
+//         uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is
+//         the hiragana for "ma".
+//
+//      Glyph
+//         A visual character shape (every codepoint is rendered as
+//         some glyph)
+//
+//      Glyph index
+//         A font-specific integer ID representing a glyph
+//
+//      Baseline
+//         Glyph shapes are defined relative to a baseline, which is the
+//         bottom of uppercase characters. Characters extend both above
+//         and below the baseline.
+//
+//      Current Point
+//         As you draw text to the screen, you keep track of a "current point"
+//         which is the origin of each character. The current point's vertical
+//         position is the baseline. Even "baked fonts" use this model.
+//
+//      Vertical Font Metrics
+//         The vertical qualities of the font, used to vertically position
+//         and space the characters. See docs for stbtt_GetFontVMetrics.
+//
+//      Font Size in Pixels or Points
+//         The preferred interface for specifying font sizes in stb_truetype
+//         is to specify how tall the font's vertical extent should be in pixels.
+//         If that sounds good enough, skip the next paragraph.
+//
+//         Most font APIs instead use "points", which are a common typographic
+//         measurement for describing font size, defined as 72 points per inch.
+//         stb_truetype provides a point API for compatibility. However, true
+//         "per inch" conventions don't make much sense on computer displays
+//         since different monitors have different number of pixels per
+//         inch. For example, Windows traditionally uses a convention that
+//         there are 96 pixels per inch, thus making 'inch' measurements have
+//         nothing to do with inches, and thus effectively defining a point to
+//         be 1.333 pixels. Additionally, the TrueType font data provides
+//         an explicit scale factor to scale a given font's glyphs to points,
+//         but the author has observed that this scale factor is often wrong
+//         for non-commercial fonts, thus making fonts scaled in points
+//         according to the TrueType spec incoherently sized in practice.
+//
+// DETAILED USAGE:
+//
+//  Scale:
+//    Select how high you want the font to be, in points or pixels.
+//    Call ScaleForPixelHeight or ScaleForMappingEmToPixels to compute
+//    a scale factor SF that will be used by all other functions.
+//
+//  Baseline:
+//    You need to select a y-coordinate that is the baseline of where
+//    your text will appear. Call GetFontBoundingBox to get the baseline-relative
+//    bounding box for all characters. SF*-y0 will be the distance in pixels
+//    that the worst-case character could extend above the baseline, so if
+//    you want the top edge of characters to appear at the top of the
+//    screen where y=0, then you would set the baseline to SF*-y0.
+//
+//  Current point:
+//    Set the current point where the first character will appear. The
+//    first character could extend left of the current point; this is font
+//    dependent. You can either choose a current point that is the leftmost
+//    point and hope, or add some padding, or check the bounding box or
+//    left-side-bearing of the first character to be displayed and set
+//    the current point based on that.
+//
+//  Displaying a character:
+//    Compute the bounding box of the character. It will contain signed values
+//    relative to <current_point, baseline>. I.e. if it returns x0,y0,x1,y1,
+//    then the character should be displayed in the rectangle from
+//    <current_point+SF*x0, baseline+SF*y0> to <current_point+SF*x1,baseline+SF*y1).
+//
+//  Advancing for the next character:
+//    Call GlyphHMetrics, and compute 'current_point += SF * advance'.
+// 
+//
+// ADVANCED USAGE
+//
+//   Quality:
+//
+//    - Use the functions with Subpixel at the end to allow your characters
+//      to have subpixel positioning. Since the font is anti-aliased, not
+//      hinted, this is very import for quality. (This is not possible with
+//      baked fonts.)
+//
+//    - Kerning is now supported, and if you're supporting subpixel rendering
+//      then kerning is worth using to give your text a polished look.
+//
+//   Performance:
+//
+//    - Convert Unicode codepoints to glyph indexes and operate on the glyphs;
+//      if you don't do this, stb_truetype is forced to do the conversion on
+//      every call.
+//
+//    - There are a lot of memory allocations. We should modify it to take
+//      a temp buffer and allocate from the temp buffer (without freeing),
+//      should help performance a lot.
+//
+// NOTES
+//
+//   The system uses the raw data found in the .ttf file without changing it
+//   and without building auxiliary data structures. This is a bit inefficient
+//   on little-endian systems (the data is big-endian), but assuming you're
+//   caching the bitmaps or glyph shapes this shouldn't be a big deal.
+//
+//   It appears to be very hard to programmatically determine what font a
+//   given file is in a general way. I provide an API for this, but I don't
+//   recommend it.
+//
+//
+// SOURCE STATISTICS (based on v0.6c, 2050 LOC)
+//
+//   Documentation & header file        520 LOC  \___ 660 LOC documentation
+//   Sample code                        140 LOC  /
+//   Truetype parsing                   620 LOC  ---- 620 LOC TrueType
+//   Software rasterization             240 LOC  \                           .
+//   Curve tesselation                  120 LOC   \__ 550 LOC Bitmap creation
+//   Bitmap management                  100 LOC   /
+//   Baked bitmap interface              70 LOC  /
+//   Font name matching & access        150 LOC  ---- 150 
+//   C runtime library abstraction       60 LOC  ----  60
+//
+//
+// PERFORMANCE MEASUREMENTS FOR 1.06:
+//
+//                      32-bit     64-bit
+//   Previous release:  8.83 s     7.68 s
+//   Pool allocations:  7.72 s     6.34 s
+//   Inline sort     :  6.54 s     5.65 s
+//   New rasterizer  :  5.63 s     5.00 s
+
+//////////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////////
+////
+////  SAMPLE PROGRAMS
+////
+//
+//  Incomplete text-in-3d-api example, which draws quads properly aligned to be lossless
+//
+#if 0
+#define STB_TRUETYPE_IMPLEMENTATION  // force following include to generate implementation
+#include "stb_truetype.h"
+
+unsigned char ttf_buffer[1<<20];
+unsigned char temp_bitmap[512*512];
+
+stbtt_bakedchar cdata[96]; // ASCII 32..126 is 95 glyphs
+GLuint ftex;
+
+void my_stbtt_initfont(void)
+{
+   fread(ttf_buffer, 1, 1<<20, fopen("c:/windows/fonts/times.ttf", "rb"));
+   stbtt_BakeFontBitmap(ttf_buffer,0, 32.0, temp_bitmap,512,512, 32,96, cdata); // no guarantee this fits!
+   // can free ttf_buffer at this point
+   glGenTextures(1, &ftex);
+   glBindTexture(GL_TEXTURE_2D, ftex);
+   glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, 512,512, 0, GL_ALPHA, GL_UNSIGNED_BYTE, temp_bitmap);
+   // can free temp_bitmap at this point
+   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+}
+
+void my_stbtt_print(float x, float y, char *text)
+{
+   // assume orthographic projection with units = screen pixels, origin at top left
+   glEnable(GL_TEXTURE_2D);
+   glBindTexture(GL_TEXTURE_2D, ftex);
+   glBegin(GL_QUADS);
+   while (*text) {
+      if (*text >= 32 && *text < 128) {
+         stbtt_aligned_quad q;
+         stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);//1=opengl & d3d10+,0=d3d9
+         glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y0);
+         glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y0);
+         glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y1);
+         glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y1);
+      }
+      ++text;
+   }
+   glEnd();
+}
+#endif
+//
+//
+//////////////////////////////////////////////////////////////////////////////
+//
+// Complete program (this compiles): get a single bitmap, print as ASCII art
+//
+#if 0
+#include <stdio.h>
+#define STB_TRUETYPE_IMPLEMENTATION  // force following include to generate implementation
+#include "stb_truetype.h"
+
+char ttf_buffer[1<<25];
+
+int main(int argc, char **argv)
+{
+   stbtt_fontinfo font;
+   unsigned char *bitmap;
+   int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20);
+
+   fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb"));
+
+   stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0));
+   bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0,0);
+
+   for (j=0; j < h; ++j) {
+      for (i=0; i < w; ++i)
+         putchar(" .:ioVM@"[bitmap[j*w+i]>>5]);
+      putchar('\n');
+   }
+   return 0;
+}
+#endif 
+//
+// Output:
+//
+//     .ii.
+//    @@@@@@.
+//   V@Mio@@o
+//   :i.  V@V
+//     :oM@@M
+//   :@@@MM@M
+//   @@o  o@M
+//  :@@.  M@M
+//   @@@o@@@@
+//   :M@@V:@@.
+//  
+//////////////////////////////////////////////////////////////////////////////
+// 
+// Complete program: print "Hello World!" banner, with bugs
+//
+#if 0
+char buffer[24<<20];
+unsigned char screen[20][79];
+
+int main(int arg, char **argv)
+{
+   stbtt_fontinfo font;
+   int i,j,ascent,baseline,ch=0;
+   float scale, xpos=2; // leave a little padding in case the character extends left
+   char *text = "Heljo World!"; // intentionally misspelled to show 'lj' brokenness
+
+   fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb"));
+   stbtt_InitFont(&font, buffer, 0);
+
+   scale = stbtt_ScaleForPixelHeight(&font, 15);
+   stbtt_GetFontVMetrics(&font, &ascent,0,0);
+   baseline = (int) (ascent*scale);
+
+   while (text[ch]) {
+      int advance,lsb,x0,y0,x1,y1;
+      float x_shift = xpos - (float) floor(xpos);
+      stbtt_GetCodepointHMetrics(&font, text[ch], &advance, &lsb);
+      stbtt_GetCodepointBitmapBoxSubpixel(&font, text[ch], scale,scale,x_shift,0, &x0,&y0,&x1,&y1);
+      stbtt_MakeCodepointBitmapSubpixel(&font, &screen[baseline + y0][(int) xpos + x0], x1-x0,y1-y0, 79, scale,scale,x_shift,0, text[ch]);
+      // note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong
+      // because this API is really for baking character bitmaps into textures. if you want to render
+      // a sequence of characters, you really need to render each bitmap to a temp buffer, then
+      // "alpha blend" that into the working buffer
+      xpos += (advance * scale);
+      if (text[ch+1])
+         xpos += scale*stbtt_GetCodepointKernAdvance(&font, text[ch],text[ch+1]);
+      ++ch;
+   }
+
+   for (j=0; j < 20; ++j) {
+      for (i=0; i < 78; ++i)
+         putchar(" .:ioVM@"[screen[j][i]>>5]);
+      putchar('\n');
+   }
+
+   return 0;
+}
+#endif
+
+
+//////////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////////
+////
+////   INTEGRATION WITH YOUR CODEBASE
+////
+////   The following sections allow you to supply alternate definitions
+////   of C library functions used by stb_truetype, e.g. if you don't
+////   link with the C runtime library.
+
+#ifdef STB_TRUETYPE_IMPLEMENTATION
+   // #define your own (u)stbtt_int8/16/32 before including to override this
+   #ifndef stbtt_uint8
+   typedef unsigned char   stbtt_uint8;
+   typedef signed   char   stbtt_int8;
+   typedef unsigned short  stbtt_uint16;
+   typedef signed   short  stbtt_int16;
+   typedef unsigned int    stbtt_uint32;
+   typedef signed   int    stbtt_int32;
+   #endif
+
+   typedef char stbtt__check_size32[sizeof(stbtt_int32)==4 ? 1 : -1];
+   typedef char stbtt__check_size16[sizeof(stbtt_int16)==2 ? 1 : -1];
+
+   // e.g. #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h
+   #ifndef STBTT_ifloor
+   #include <math.h>
+   #define STBTT_ifloor(x)   ((int) floor(x))
+   #define STBTT_iceil(x)    ((int) ceil(x))
+   #endif
+
+   #ifndef STBTT_sqrt
+   #include <math.h>
+   #define STBTT_sqrt(x)      sqrt(x)
+   #define STBTT_pow(x,y)     pow(x,y)
+   #endif
+
+   #ifndef STBTT_fmod
+   #include <math.h>
+   #define STBTT_fmod(x,y)    fmod(x,y)
+   #endif
+
+   #ifndef STBTT_cos
+   #include <math.h>
+   #define STBTT_cos(x)       cos(x)
+   #define STBTT_acos(x)      acos(x)
+   #endif
+
+   #ifndef STBTT_fabs
+   #include <math.h>
+   #define STBTT_fabs(x)      fabs(x)
+   #endif
+
+   // #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h
+   #ifndef STBTT_malloc
+   #include <stdlib.h>
+   #define STBTT_malloc(x,u)  ((void)(u),malloc(x))
+   #define STBTT_free(x,u)    ((void)(u),free(x))
+   #endif
+
+   #ifndef STBTT_assert
+   #include <assert.h>
+   #define STBTT_assert(x)    assert(x)
+   #endif
+
+   #ifndef STBTT_strlen
+   #include <string.h>
+   #define STBTT_strlen(x)    strlen(x)
+   #endif
+
+   #ifndef STBTT_memcpy
+   #include <string.h>
+   #define STBTT_memcpy       memcpy
+   #define STBTT_memset       memset
+   #endif
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+////
+////   INTERFACE
+////
+////
+
+#ifndef __STB_INCLUDE_STB_TRUETYPE_H__
+#define __STB_INCLUDE_STB_TRUETYPE_H__
+
+#ifdef STBTT_STATIC
+#define STBTT_DEF static
+#else
+#define STBTT_DEF extern
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// private structure
+typedef struct
+{
+   unsigned char *data;
+   int cursor;
+   int size;
+} stbtt__buf;
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// TEXTURE BAKING API
+//
+// If you use this API, you only have to call two functions ever.
+//
+
+typedef struct
+{
+   unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap
+   float xoff,yoff,xadvance;
+} stbtt_bakedchar;
+
+STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset,  // font location (use offset=0 for plain .ttf)
+                                float pixel_height,                     // height of font in pixels
+                                unsigned char *pixels, int pw, int ph,  // bitmap to be filled in
+                                int first_char, int num_chars,          // characters to bake
+                                stbtt_bakedchar *chardata);             // you allocate this, it's num_chars long
+// if return is positive, the first unused row of the bitmap
+// if return is negative, returns the negative of the number of characters that fit
+// if return is 0, no characters fit and no rows were used
+// This uses a very crappy packing.
+
+typedef struct
+{
+   float x0,y0,s0,t0; // top-left
+   float x1,y1,s1,t1; // bottom-right
+} stbtt_aligned_quad;
+
+STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph,  // same data as above
+                               int char_index,             // character to display
+                               float *xpos, float *ypos,   // pointers to current position in screen pixel space
+                               stbtt_aligned_quad *q,      // output: quad to draw
+                               int opengl_fillrule);       // true if opengl fill rule; false if DX9 or earlier
+// Call GetBakedQuad with char_index = 'character - first_char', and it
+// creates the quad you need to draw and advances the current position.
+//
+// The coordinate system used assumes y increases downwards.
+//
+// Characters will extend both above and below the current position;
+// see discussion of "BASELINE" above.
+//
+// It's inefficient; you might want to c&p it and optimize it.
+
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// NEW TEXTURE BAKING API
+//
+// This provides options for packing multiple fonts into one atlas, not
+// perfectly but better than nothing.
+
+typedef struct
+{
+   unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap
+   float xoff,yoff,xadvance;
+   float xoff2,yoff2;
+} stbtt_packedchar;
+
+typedef struct stbtt_pack_context stbtt_pack_context;
+typedef struct stbtt_fontinfo stbtt_fontinfo;
+#ifndef STB_RECT_PACK_VERSION
+typedef struct stbrp_rect stbrp_rect;
+#endif
+
+STBTT_DEF int  stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int width, int height, int stride_in_bytes, int padding, void *alloc_context);
+// Initializes a packing context stored in the passed-in stbtt_pack_context.
+// Future calls using this context will pack characters into the bitmap passed
+// in here: a 1-channel bitmap that is width * height. stride_in_bytes is
+// the distance from one row to the next (or 0 to mean they are packed tightly
+// together). "padding" is the amount of padding to leave between each
+// character (normally you want '1' for bitmaps you'll use as textures with
+// bilinear filtering).
+//
+// Returns 0 on failure, 1 on success.
+
+STBTT_DEF void stbtt_PackEnd  (stbtt_pack_context *spc);
+// Cleans up the packing context and frees all memory.
+
+#define STBTT_POINT_SIZE(x)   (-(x))
+
+STBTT_DEF int  stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size,
+                                int first_unicode_char_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range);
+// Creates character bitmaps from the font_index'th font found in fontdata (use
+// font_index=0 if you don't know what that is). It creates num_chars_in_range
+// bitmaps for characters with unicode values starting at first_unicode_char_in_range
+// and increasing. Data for how to render them is stored in chardata_for_range;
+// pass these to stbtt_GetPackedQuad to get back renderable quads.
+//
+// font_size is the full height of the character from ascender to descender,
+// as computed by stbtt_ScaleForPixelHeight. To use a point size as computed
+// by stbtt_ScaleForMappingEmToPixels, wrap the point size in STBTT_POINT_SIZE()
+// and pass that result as 'font_size':
+//       ...,                  20 , ... // font max minus min y is 20 pixels tall
+//       ..., STBTT_POINT_SIZE(20), ... // 'M' is 20 pixels tall
+
+typedef struct
+{
+   float font_size;
+   int first_unicode_codepoint_in_range;  // if non-zero, then the chars are continuous, and this is the first codepoint
+   int *array_of_unicode_codepoints;       // if non-zero, then this is an array of unicode codepoints
+   int num_chars;
+   stbtt_packedchar *chardata_for_range; // output
+   unsigned char h_oversample, v_oversample; // don't set these, they're used internally
+} stbtt_pack_range;
+
+STBTT_DEF int  stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges);
+// Creates character bitmaps from multiple ranges of characters stored in
+// ranges. This will usually create a better-packed bitmap than multiple
+// calls to stbtt_PackFontRange. Note that you can call this multiple
+// times within a single PackBegin/PackEnd.
+
+STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample);
+// Oversampling a font increases the quality by allowing higher-quality subpixel
+// positioning, and is especially valuable at smaller text sizes.
+//
+// This function sets the amount of oversampling for all following calls to
+// stbtt_PackFontRange(s) or stbtt_PackFontRangesGatherRects for a given
+// pack context. The default (no oversampling) is achieved by h_oversample=1
+// and v_oversample=1. The total number of pixels required is
+// h_oversample*v_oversample larger than the default; for example, 2x2
+// oversampling requires 4x the storage of 1x1. For best results, render
+// oversampled textures with bilinear filtering. Look at the readme in
+// stb/tests/oversample for information about oversampled fonts
+//
+// To use with PackFontRangesGather etc., you must set it before calls
+// call to PackFontRangesGatherRects.
+
+STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph,  // same data as above
+                               int char_index,             // character to display
+                               float *xpos, float *ypos,   // pointers to current position in screen pixel space
+                               stbtt_aligned_quad *q,      // output: quad to draw
+                               int align_to_integer);
+
+STBTT_DEF int  stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects);
+STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects);
+STBTT_DEF int  stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects);
+// Calling these functions in sequence is roughly equivalent to calling
+// stbtt_PackFontRanges(). If you more control over the packing of multiple
+// fonts, or if you want to pack custom data into a font texture, take a look
+// at the source to of stbtt_PackFontRanges() and create a custom version 
+// using these functions, e.g. call GatherRects multiple times,
+// building up a single array of rects, then call PackRects once,
+// then call RenderIntoRects repeatedly. This may result in a
+// better packing than calling PackFontRanges multiple times
+// (or it may not).
+
+// this is an opaque structure that you shouldn't mess with which holds
+// all the context needed from PackBegin to PackEnd.
+struct stbtt_pack_context {
+   void *user_allocator_context;
+   void *pack_info;
+   int   width;
+   int   height;
+   int   stride_in_bytes;
+   int   padding;
+   unsigned int   h_oversample, v_oversample;
+   unsigned char *pixels;
+   void  *nodes;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// FONT LOADING
+//
+//
+
+STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data);
+// This function will determine the number of fonts in a font file.  TrueType
+// collection (.ttc) files may contain multiple fonts, while TrueType font
+// (.ttf) files only contain one font. The number of fonts can be used for
+// indexing with the previous function where the index is between zero and one
+// less than the total fonts. If an error occurs, -1 is returned.
+
+STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index);
+// Each .ttf/.ttc file may have more than one font. Each font has a sequential
+// index number starting from 0. Call this function to get the font offset for
+// a given index; it returns -1 if the index is out of range. A regular .ttf
+// file will only define one font and it always be at offset 0, so it will
+// return '0' for index 0, and -1 for all other indices.
+
+// The following structure is defined publically so you can declare one on
+// the stack or as a global or etc, but you should treat it as opaque.
+struct stbtt_fontinfo
+{
+   void           * userdata;
+   unsigned char  * data;              // pointer to .ttf file
+   int              fontstart;         // offset of start of font
+
+   int numGlyphs;                     // number of glyphs, needed for range checking
+
+   int loca,head,glyf,hhea,hmtx,kern,gpos; // table locations as offset from start of .ttf
+   int index_map;                     // a cmap mapping for our chosen character encoding
+   int indexToLocFormat;              // format needed to map from glyph index to glyph
+
+   stbtt__buf cff;                    // cff font data
+   stbtt__buf charstrings;            // the charstring index
+   stbtt__buf gsubrs;                 // global charstring subroutines index
+   stbtt__buf subrs;                  // private charstring subroutines index
+   stbtt__buf fontdicts;              // array of font dicts
+   stbtt__buf fdselect;               // map from glyph to fontdict
+};
+
+STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset);
+// Given an offset into the file that defines a font, this function builds
+// the necessary cached info for the rest of the system. You must allocate
+// the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't
+// need to do anything special to free it, because the contents are pure
+// value data with no additional data structures. Returns 0 on failure.
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// CHARACTER TO GLYPH-INDEX CONVERSIOn
+
+STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint);
+// If you're going to perform multiple operations on the same character
+// and you want a speed-up, call this function with the character you're
+// going to process, then use glyph-based functions instead of the
+// codepoint-based functions.
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// CHARACTER PROPERTIES
+//
+
+STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixels);
+// computes a scale factor to produce a font whose "height" is 'pixels' tall.
+// Height is measured as the distance from the highest ascender to the lowest
+// descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics
+// and computing:
+//       scale = pixels / (ascent - descent)
+// so if you prefer to measure height by the ascent only, use a similar calculation.
+
+STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels);
+// computes a scale factor to produce a font whose EM size is mapped to
+// 'pixels' tall. This is probably what traditional APIs compute, but
+// I'm not positive.
+
+STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap);
+// ascent is the coordinate above the baseline the font extends; descent
+// is the coordinate below the baseline the font extends (i.e. it is typically negative)
+// lineGap is the spacing between one row's descent and the next row's ascent...
+// so you should advance the vertical position by "*ascent - *descent + *lineGap"
+//   these are expressed in unscaled coordinates, so you must multiply by
+//   the scale factor for a given size
+
+STBTT_DEF int  stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap);
+// analogous to GetFontVMetrics, but returns the "typographic" values from the OS/2
+// table (specific to MS/Windows TTF files).
+//
+// Returns 1 on success (table present), 0 on failure.
+
+STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1);
+// the bounding box around all possible characters
+
+STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing);
+// leftSideBearing is the offset from the current horizontal position to the left edge of the character
+// advanceWidth is the offset from the current horizontal position to the next horizontal position
+//   these are expressed in unscaled coordinates
+
+STBTT_DEF int  stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2);
+// an additional amount to add to the 'advance' value between ch1 and ch2
+
+STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1);
+// Gets the bounding box of the visible part of the glyph, in unscaled coordinates
+
+STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing);
+STBTT_DEF int  stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2);
+STBTT_DEF int  stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1);
+// as above, but takes one or more glyph indices for greater efficiency
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// GLYPH SHAPES (you probably don't need these, but they have to go before
+// the bitmaps for C declaration-order reasons)
+//
+
+#ifndef STBTT_vmove // you can predefine these to use different values (but why?)
+   enum {
+      STBTT_vmove=1,
+      STBTT_vline,
+      STBTT_vcurve,
+      STBTT_vcubic
+   };
+#endif
+
+#ifndef stbtt_vertex // you can predefine this to use different values
+                   // (we share this with other code at RAD)
+   #define stbtt_vertex_type short // can't use stbtt_int16 because that's not visible in the header file
+   typedef struct
+   {
+      stbtt_vertex_type x,y,cx,cy,cx1,cy1;
+      unsigned char type,padding;
+   } stbtt_vertex;
+#endif
+
+STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index);
+// returns non-zero if nothing is drawn for this glyph
+
+STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices);
+STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices);
+// returns # of vertices and fills *vertices with the pointer to them
+//   these are expressed in "unscaled" coordinates
+//
+// The shape is a series of countours. Each one starts with
+// a STBTT_moveto, then consists of a series of mixed
+// STBTT_lineto and STBTT_curveto segments. A lineto
+// draws a line from previous endpoint to its x,y; a curveto
+// draws a quadratic bezier from previous endpoint to
+// its x,y, using cx,cy as the bezier control point.
+
+STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices);
+// frees the data allocated above
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// BITMAP RENDERING
+//
+
+STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata);
+// frees the bitmap allocated below
+
+STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff);
+// allocates a large-enough single-channel 8bpp bitmap and renders the
+// specified character/glyph at the specified scale into it, with
+// antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque).
+// *width & *height are filled out with the width & height of the bitmap,
+// which is stored left-to-right, top-to-bottom.
+//
+// xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap
+
+STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff);
+// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel
+// shift for the character
+
+STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint);
+// the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap
+// in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap
+// is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the
+// width and height and positioning info for it first.
+
+STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint);
+// same as stbtt_MakeCodepointBitmap, but you can specify a subpixel
+// shift for the character
+
+STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint);
+// same as stbtt_MakeCodepointBitmapSubpixel, but prefiltering
+// is performed (see stbtt_PackSetOversampling)
+
+STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1);
+// get the bbox of the bitmap centered around the glyph origin; so the
+// bitmap width is ix1-ix0, height is iy1-iy0, and location to place
+// the bitmap top left is (leftSideBearing*scale,iy0).
+// (Note that the bitmap uses y-increases-down, but the shape uses
+// y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.)
+
+STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1);
+// same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel
+// shift for the character
+
+// the following functions are equivalent to the above functions, but operate
+// on glyph indices instead of Unicode codepoints (for efficiency)
+STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff);
+STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff);
+STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph);
+STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph);
+STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int glyph);
+STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1);
+STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1);
+
+
+// @TODO: don't expose this structure
+typedef struct
+{
+   int w,h,stride;
+   unsigned char *pixels;
+} stbtt__bitmap;
+
+// rasterize a shape with quadratic beziers into a bitmap
+STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result,        // 1-channel bitmap to draw into
+                               float flatness_in_pixels,     // allowable error of curve in pixels
+                               stbtt_vertex *vertices,       // array of vertices defining shape
+                               int num_verts,                // number of vertices in above array
+                               float scale_x, float scale_y, // scale applied to input vertices
+                               float shift_x, float shift_y, // translation applied to input vertices
+                               int x_off, int y_off,         // another translation applied to input
+                               int invert,                   // if non-zero, vertically flip shape
+                               void *userdata);              // context for to STBTT_MALLOC
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// Signed Distance Function (or Field) rendering
+
+STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata);
+// frees the SDF bitmap allocated below
+
+STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff);
+STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff);
+// These functions compute a discretized SDF field for a single character, suitable for storing
+// in a single-channel texture, sampling with bilinear filtering, and testing against
+// larger than some threshhold to produce scalable fonts.
+//        info              --  the font
+//        scale             --  controls the size of the resulting SDF bitmap, same as it would be creating a regular bitmap
+//        glyph/codepoint   --  the character to generate the SDF for
+//        padding           --  extra "pixels" around the character which are filled with the distance to the character (not 0),
+//                                 which allows effects like bit outlines
+//        onedge_value      --  value 0-255 to test the SDF against to reconstruct the character (i.e. the isocontour of the character)
+//        pixel_dist_scale  --  what value the SDF should increase by when moving one SDF "pixel" away from the edge (on the 0..255 scale)
+//                                 if positive, > onedge_value is inside; if negative, < onedge_value is inside
+//        width,height      --  output height & width of the SDF bitmap (including padding)
+//        xoff,yoff         --  output origin of the character
+//        return value      --  a 2D array of bytes 0..255, width*height in size
+//
+// pixel_dist_scale & onedge_value are a scale & bias that allows you to make
+// optimal use of the limited 0..255 for your application, trading off precision
+// and special effects. SDF values outside the range 0..255 are clamped to 0..255.
+//
+// Example:
+//      scale = stbtt_ScaleForPixelHeight(22)
+//      padding = 5
+//      onedge_value = 180
+//      pixel_dist_scale = 180/5.0 = 36.0
+//
+//      This will create an SDF bitmap in which the character is about 22 pixels
+//      high but the whole bitmap is about 22+5+5=32 pixels high. To produce a filled
+//      shape, sample the SDF at each pixel and fill the pixel if the SDF value
+//      is greater than or equal to 180/255. (You'll actually want to antialias,
+//      which is beyond the scope of this example.) Additionally, you can compute
+//      offset outlines (e.g. to stroke the character border inside & outside,
+//      or only outside). For example, to fill outside the character up to 3 SDF
+//      pixels, you would compare against (180-36.0*3)/255 = 72/255. The above
+//      choice of variables maps a range from 5 pixels outside the shape to
+//      2 pixels inside the shape to 0..255; this is intended primarily for apply
+//      outside effects only (the interior range is needed to allow proper
+//      antialiasing of the font at *smaller* sizes)
+//
+// The function computes the SDF analytically at each SDF pixel, not by e.g.
+// building a higher-res bitmap and approximating it. In theory the quality
+// should be as high as possible for an SDF of this size & representation, but
+// unclear if this is true in practice (perhaps building a higher-res bitmap
+// and computing from that can allow drop-out prevention).
+//
+// The algorithm has not been optimized at all, so expect it to be slow
+// if computing lots of characters or very large sizes. 
+
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// Finding the right font...
+//
+// You should really just solve this offline, keep your own tables
+// of what font is what, and don't try to get it out of the .ttf file.
+// That's because getting it out of the .ttf file is really hard, because
+// the names in the file can appear in many possible encodings, in many
+// possible languages, and e.g. if you need a case-insensitive comparison,
+// the details of that depend on the encoding & language in a complex way
+// (actually underspecified in truetype, but also gigantic).
+//
+// But you can use the provided functions in two possible ways:
+//     stbtt_FindMatchingFont() will use *case-sensitive* comparisons on
+//             unicode-encoded names to try to find the font you want;
+//             you can run this before calling stbtt_InitFont()
+//
+//     stbtt_GetFontNameString() lets you get any of the various strings
+//             from the file yourself and do your own comparisons on them.
+//             You have to have called stbtt_InitFont() first.
+
+
+STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags);
+// returns the offset (not index) of the font that matches, or -1 if none
+//   if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold".
+//   if you use any other flag, use a font name like "Arial"; this checks
+//     the 'macStyle' header field; i don't know if fonts set this consistently
+#define STBTT_MACSTYLE_DONTCARE     0
+#define STBTT_MACSTYLE_BOLD         1
+#define STBTT_MACSTYLE_ITALIC       2
+#define STBTT_MACSTYLE_UNDERSCORE   4
+#define STBTT_MACSTYLE_NONE         8   // <= not same as 0, this makes us check the bitfield is 0
+
+STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2);
+// returns 1/0 whether the first string interpreted as utf8 is identical to
+// the second string interpreted as big-endian utf16... useful for strings from next func
+
+STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID);
+// returns the string (which may be big-endian double byte, e.g. for unicode)
+// and puts the length in bytes in *length.
+//
+// some of the values for the IDs are below; for more see the truetype spec:
+//     http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html
+//     http://www.microsoft.com/typography/otspec/name.htm
+
+enum { // platformID
+   STBTT_PLATFORM_ID_UNICODE   =0,
+   STBTT_PLATFORM_ID_MAC       =1,
+   STBTT_PLATFORM_ID_ISO       =2,
+   STBTT_PLATFORM_ID_MICROSOFT =3
+};
+
+enum { // encodingID for STBTT_PLATFORM_ID_UNICODE
+   STBTT_UNICODE_EID_UNICODE_1_0    =0,
+   STBTT_UNICODE_EID_UNICODE_1_1    =1,
+   STBTT_UNICODE_EID_ISO_10646      =2,
+   STBTT_UNICODE_EID_UNICODE_2_0_BMP=3,
+   STBTT_UNICODE_EID_UNICODE_2_0_FULL=4
+};
+
+enum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT
+   STBTT_MS_EID_SYMBOL        =0,
+   STBTT_MS_EID_UNICODE_BMP   =1,
+   STBTT_MS_EID_SHIFTJIS      =2,
+   STBTT_MS_EID_UNICODE_FULL  =10
+};
+
+enum { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes
+   STBTT_MAC_EID_ROMAN        =0,   STBTT_MAC_EID_ARABIC       =4,
+   STBTT_MAC_EID_JAPANESE     =1,   STBTT_MAC_EID_HEBREW       =5,
+   STBTT_MAC_EID_CHINESE_TRAD =2,   STBTT_MAC_EID_GREEK        =6,
+   STBTT_MAC_EID_KOREAN       =3,   STBTT_MAC_EID_RUSSIAN      =7
+};
+
+enum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID...
+       // problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs
+   STBTT_MS_LANG_ENGLISH     =0x0409,   STBTT_MS_LANG_ITALIAN     =0x0410,
+   STBTT_MS_LANG_CHINESE     =0x0804,   STBTT_MS_LANG_JAPANESE    =0x0411,
+   STBTT_MS_LANG_DUTCH       =0x0413,   STBTT_MS_LANG_KOREAN      =0x0412,
+   STBTT_MS_LANG_FRENCH      =0x040c,   STBTT_MS_LANG_RUSSIAN     =0x0419,
+   STBTT_MS_LANG_GERMAN      =0x0407,   STBTT_MS_LANG_SPANISH     =0x0409,
+   STBTT_MS_LANG_HEBREW      =0x040d,   STBTT_MS_LANG_SWEDISH     =0x041D
+};
+
+enum { // languageID for STBTT_PLATFORM_ID_MAC
+   STBTT_MAC_LANG_ENGLISH      =0 ,   STBTT_MAC_LANG_JAPANESE     =11,
+   STBTT_MAC_LANG_ARABIC       =12,   STBTT_MAC_LANG_KOREAN       =23,
+   STBTT_MAC_LANG_DUTCH        =4 ,   STBTT_MAC_LANG_RUSSIAN      =32,
+   STBTT_MAC_LANG_FRENCH       =1 ,   STBTT_MAC_LANG_SPANISH      =6 ,
+   STBTT_MAC_LANG_GERMAN       =2 ,   STBTT_MAC_LANG_SWEDISH      =5 ,
+   STBTT_MAC_LANG_HEBREW       =10,   STBTT_MAC_LANG_CHINESE_SIMPLIFIED =33,
+   STBTT_MAC_LANG_ITALIAN      =3 ,   STBTT_MAC_LANG_CHINESE_TRAD =19
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // __STB_INCLUDE_STB_TRUETYPE_H__
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+////
+////   IMPLEMENTATION
+////
+////
+
+#ifdef STB_TRUETYPE_IMPLEMENTATION
+
+#ifndef STBTT_MAX_OVERSAMPLE
+#define STBTT_MAX_OVERSAMPLE   8
+#endif
+
+#if STBTT_MAX_OVERSAMPLE > 255
+#error "STBTT_MAX_OVERSAMPLE cannot be > 255"
+#endif
+
+typedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERSAMPLE-1)) == 0 ? 1 : -1];
+
+#ifndef STBTT_RASTERIZER_VERSION
+#define STBTT_RASTERIZER_VERSION 2
+#endif
+
+#ifdef _MSC_VER
+#define STBTT__NOTUSED(v)  (void)(v)
+#else
+#define STBTT__NOTUSED(v)  (void)sizeof(v)
+#endif
+
+//////////////////////////////////////////////////////////////////////////
+//
+// stbtt__buf helpers to parse data from file
+//
+
+static stbtt_uint8 stbtt__buf_get8(stbtt__buf *b)
+{
+   if (b->cursor >= b->size)
+      return 0;
+   return b->data[b->cursor++];
+}
+
+static stbtt_uint8 stbtt__buf_peek8(stbtt__buf *b)
+{
+   if (b->cursor >= b->size)
+      return 0;
+   return b->data[b->cursor];
+}
+
+static void stbtt__buf_seek(stbtt__buf *b, int o)
+{
+   STBTT_assert(!(o > b->size || o < 0));
+   b->cursor = (o > b->size || o < 0) ? b->size : o;
+}
+
+static void stbtt__buf_skip(stbtt__buf *b, int o)
+{
+   stbtt__buf_seek(b, b->cursor + o);
+}
+
+static stbtt_uint32 stbtt__buf_get(stbtt__buf *b, int n)
+{
+   stbtt_uint32 v = 0;
+   int i;
+   STBTT_assert(n >= 1 && n <= 4);
+   for (i = 0; i < n; i++)
+      v = (v << 8) | stbtt__buf_get8(b);
+   return v;
+}
+
+static stbtt__buf stbtt__new_buf(const void *p, size_t size)
+{
+   stbtt__buf r;
+   STBTT_assert(size < 0x40000000);
+   r.data = (stbtt_uint8*) p;
+   r.size = (int) size;
+   r.cursor = 0;
+   return r;
+}
+
+#define stbtt__buf_get16(b)  stbtt__buf_get((b), 2)
+#define stbtt__buf_get32(b)  stbtt__buf_get((b), 4)
+
+static stbtt__buf stbtt__buf_range(const stbtt__buf *b, int o, int s)
+{
+   stbtt__buf r = stbtt__new_buf(NULL, 0);
+   if (o < 0 || s < 0 || o > b->size || s > b->size - o) return r;
+   r.data = b->data + o;
+   r.size = s;
+   return r;
+}
+
+static stbtt__buf stbtt__cff_get_index(stbtt__buf *b)
+{
+   int count, start, offsize;
+   start = b->cursor;
+   count = stbtt__buf_get16(b);
+   if (count) {
+      offsize = stbtt__buf_get8(b);
+      STBTT_assert(offsize >= 1 && offsize <= 4);
+      stbtt__buf_skip(b, offsize * count);
+      stbtt__buf_skip(b, stbtt__buf_get(b, offsize) - 1);
+   }
+   return stbtt__buf_range(b, start, b->cursor - start);
+}
+
+static stbtt_uint32 stbtt__cff_int(stbtt__buf *b)
+{
+   int b0 = stbtt__buf_get8(b);
+   if (b0 >= 32 && b0 <= 246)       return b0 - 139;
+   else if (b0 >= 247 && b0 <= 250) return (b0 - 247)*256 + stbtt__buf_get8(b) + 108;
+   else if (b0 >= 251 && b0 <= 254) return -(b0 - 251)*256 - stbtt__buf_get8(b) - 108;
+   else if (b0 == 28)               return stbtt__buf_get16(b);
+   else if (b0 == 29)               return stbtt__buf_get32(b);
+   STBTT_assert(0);
+   return 0;
+}
+
+static void stbtt__cff_skip_operand(stbtt__buf *b) {
+   int v, b0 = stbtt__buf_peek8(b);
+   STBTT_assert(b0 >= 28);
+   if (b0 == 30) {
+      stbtt__buf_skip(b, 1);
+      while (b->cursor < b->size) {
+         v = stbtt__buf_get8(b);
+         if ((v & 0xF) == 0xF || (v >> 4) == 0xF)
+            break;
+      }
+   } else {
+      stbtt__cff_int(b);
+   }
+}
+
+static stbtt__buf stbtt__dict_get(stbtt__buf *b, int key)
+{
+   stbtt__buf_seek(b, 0);
+   while (b->cursor < b->size) {
+      int start = b->cursor, end, op;
+      while (stbtt__buf_peek8(b) >= 28)
+         stbtt__cff_skip_operand(b);
+      end = b->cursor;
+      op = stbtt__buf_get8(b);
+      if (op == 12)  op = stbtt__buf_get8(b) | 0x100;
+      if (op == key) return stbtt__buf_range(b, start, end-start);
+   }
+   return stbtt__buf_range(b, 0, 0);
+}
+
+static void stbtt__dict_get_ints(stbtt__buf *b, int key, int outcount, stbtt_uint32 *out)
+{
+   int i;
+   stbtt__buf operands = stbtt__dict_get(b, key);
+   for (i = 0; i < outcount && operands.cursor < operands.size; i++)
+      out[i] = stbtt__cff_int(&operands);
+}
+
+static int stbtt__cff_index_count(stbtt__buf *b)
+{
+   stbtt__buf_seek(b, 0);
+   return stbtt__buf_get16(b);
+}
+
+static stbtt__buf stbtt__cff_index_get(stbtt__buf b, int i)
+{
+   int count, offsize, start, end;
+   stbtt__buf_seek(&b, 0);
+   count = stbtt__buf_get16(&b);
+   offsize = stbtt__buf_get8(&b);
+   STBTT_assert(i >= 0 && i < count);
+   STBTT_assert(offsize >= 1 && offsize <= 4);
+   stbtt__buf_skip(&b, i*offsize);
+   start = stbtt__buf_get(&b, offsize);
+   end = stbtt__buf_get(&b, offsize);
+   return stbtt__buf_range(&b, 2+(count+1)*offsize+start, end - start);
+}
+
+//////////////////////////////////////////////////////////////////////////
+//
+// accessors to parse data from file
+//
+
+// on platforms that don't allow misaligned reads, if we want to allow
+// truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE
+
+#define ttBYTE(p)     (* (stbtt_uint8 *) (p))
+#define ttCHAR(p)     (* (stbtt_int8 *) (p))
+#define ttFixed(p)    ttLONG(p)
+
+static stbtt_uint16 ttUSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; }
+static stbtt_int16 ttSHORT(stbtt_uint8 *p)   { return p[0]*256 + p[1]; }
+static stbtt_uint32 ttULONG(stbtt_uint8 *p)  { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; }
+static stbtt_int32 ttLONG(stbtt_uint8 *p)    { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; }
+
+#define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3))
+#define stbtt_tag(p,str)           stbtt_tag4(p,str[0],str[1],str[2],str[3])
+
+static int stbtt__isfont(stbtt_uint8 *font)
+{
+   // check the version number
+   if (stbtt_tag4(font, '1',0,0,0))  return 1; // TrueType 1
+   if (stbtt_tag(font, "typ1"))   return 1; // TrueType with type 1 font -- we don't support this!
+   if (stbtt_tag(font, "OTTO"))   return 1; // OpenType with CFF
+   if (stbtt_tag4(font, 0,1,0,0)) return 1; // OpenType 1.0
+   if (stbtt_tag(font, "true"))   return 1; // Apple specification for TrueType fonts
+   return 0;
+}
+
+// @OPTIMIZE: binary search
+static stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, const char *tag)
+{
+   stbtt_int32 num_tables = ttUSHORT(data+fontstart+4);
+   stbtt_uint32 tabledir = fontstart + 12;
+   stbtt_int32 i;
+   for (i=0; i < num_tables; ++i) {
+      stbtt_uint32 loc = tabledir + 16*i;
+      if (stbtt_tag(data+loc+0, tag))
+         return ttULONG(data+loc+8);
+   }
+   return 0;
+}
+
+static int stbtt_GetFontOffsetForIndex_internal(unsigned char *font_collection, int index)
+{
+   // if it's just a font, there's only one valid index
+   if (stbtt__isfont(font_collection))
+      return index == 0 ? 0 : -1;
+
+   // check if it's a TTC
+   if (stbtt_tag(font_collection, "ttcf")) {
+      // version 1?
+      if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) {
+         stbtt_int32 n = ttLONG(font_collection+8);
+         if (index >= n)
+            return -1;
+         return ttULONG(font_collection+12+index*4);
+      }
+   }
+   return -1;
+}
+
+static int stbtt_GetNumberOfFonts_internal(unsigned char *font_collection)
+{
+   // if it's just a font, there's only one valid font
+   if (stbtt__isfont(font_collection))
+      return 1;
+
+   // check if it's a TTC
+   if (stbtt_tag(font_collection, "ttcf")) {
+      // version 1?
+      if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) {
+         return ttLONG(font_collection+8);
+      }
+   }
+   return 0;
+}
+
+static stbtt__buf stbtt__get_subrs(stbtt__buf cff, stbtt__buf fontdict)
+{
+   stbtt_uint32 subrsoff = 0, private_loc[2] = { 0, 0 };
+   stbtt__buf pdict;
+   stbtt__dict_get_ints(&fontdict, 18, 2, private_loc);
+   if (!private_loc[1] || !private_loc[0]) return stbtt__new_buf(NULL, 0);
+   pdict = stbtt__buf_range(&cff, private_loc[1], private_loc[0]);
+   stbtt__dict_get_ints(&pdict, 19, 1, &subrsoff);
+   if (!subrsoff) return stbtt__new_buf(NULL, 0);
+   stbtt__buf_seek(&cff, private_loc[1]+subrsoff);
+   return stbtt__cff_get_index(&cff);
+}
+
+static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, int fontstart)
+{
+   stbtt_uint32 cmap, t;
+   stbtt_int32 i,numTables;
+
+   info->data = data;
+   info->fontstart = fontstart;
+   info->cff = stbtt__new_buf(NULL, 0);
+
+   cmap = stbtt__find_table(data, fontstart, "cmap");       // required
+   info->loca = stbtt__find_table(data, fontstart, "loca"); // required
+   info->head = stbtt__find_table(data, fontstart, "head"); // required
+   info->glyf = stbtt__find_table(data, fontstart, "glyf"); // required
+   info->hhea = stbtt__find_table(data, fontstart, "hhea"); // required
+   info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required
+   info->kern = stbtt__find_table(data, fontstart, "kern"); // not required
+   info->gpos = stbtt__find_table(data, fontstart, "GPOS"); // not required
+
+   if (!cmap || !info->head || !info->hhea || !info->hmtx)
+      return 0;
+   if (info->glyf) {
+      // required for truetype
+      if (!info->loca) return 0;
+   } else {
+      // initialization for CFF / Type2 fonts (OTF)
+      stbtt__buf b, topdict, topdictidx;
+      stbtt_uint32 cstype = 2, charstrings = 0, fdarrayoff = 0, fdselectoff = 0;
+      stbtt_uint32 cff;
+
+      cff = stbtt__find_table(data, fontstart, "CFF ");
+      if (!cff) return 0;
+
+      info->fontdicts = stbtt__new_buf(NULL, 0);
+      info->fdselect = stbtt__new_buf(NULL, 0);
+
+      // @TODO this should use size from table (not 512MB)
+      info->cff = stbtt__new_buf(data+cff, 512*1024*1024);
+      b = info->cff;
+
+      // read the header
+      stbtt__buf_skip(&b, 2);
+      stbtt__buf_seek(&b, stbtt__buf_get8(&b)); // hdrsize
+
+      // @TODO the name INDEX could list multiple fonts,
+      // but we just use the first one.
+      stbtt__cff_get_index(&b);  // name INDEX
+      topdictidx = stbtt__cff_get_index(&b);
+      topdict = stbtt__cff_index_get(topdictidx, 0);
+      stbtt__cff_get_index(&b);  // string INDEX
+      info->gsubrs = stbtt__cff_get_index(&b);
+
+      stbtt__dict_get_ints(&topdict, 17, 1, &charstrings);
+      stbtt__dict_get_ints(&topdict, 0x100 | 6, 1, &cstype);
+      stbtt__dict_get_ints(&topdict, 0x100 | 36, 1, &fdarrayoff);
+      stbtt__dict_get_ints(&topdict, 0x100 | 37, 1, &fdselectoff);
+      info->subrs = stbtt__get_subrs(b, topdict);
+
+      // we only support Type 2 charstrings
+      if (cstype != 2) return 0;
+      if (charstrings == 0) return 0;
+
+      if (fdarrayoff) {
+         // looks like a CID font
+         if (!fdselectoff) return 0;
+         stbtt__buf_seek(&b, fdarrayoff);
+         info->fontdicts = stbtt__cff_get_index(&b);
+         info->fdselect = stbtt__buf_range(&b, fdselectoff, b.size-fdselectoff);
+      }
+
+      stbtt__buf_seek(&b, charstrings);
+      info->charstrings = stbtt__cff_get_index(&b);
+   }
+
+   t = stbtt__find_table(data, fontstart, "maxp");
+   if (t)
+      info->numGlyphs = ttUSHORT(data+t+4);
+   else
+      info->numGlyphs = 0xffff;
+
+   // find a cmap encoding table we understand *now* to avoid searching
+   // later. (todo: could make this installable)
+   // the same regardless of glyph.
+   numTables = ttUSHORT(data + cmap + 2);
+   info->index_map = 0;
+   for (i=0; i < numTables; ++i) {
+      stbtt_uint32 encoding_record = cmap + 4 + 8 * i;
+      // find an encoding we understand:
+      switch(ttUSHORT(data+encoding_record)) {
+         case STBTT_PLATFORM_ID_MICROSOFT:
+            switch (ttUSHORT(data+encoding_record+2)) {
+               case STBTT_MS_EID_UNICODE_BMP:
+               case STBTT_MS_EID_UNICODE_FULL:
+                  // MS/Unicode
+                  info->index_map = cmap + ttULONG(data+encoding_record+4);
+                  break;
+            }
+            break;
+        case STBTT_PLATFORM_ID_UNICODE:
+            // Mac/iOS has these
+            // all the encodingIDs are unicode, so we don't bother to check it
+            info->index_map = cmap + ttULONG(data+encoding_record+4);
+            break;
+      }
+   }
+   if (info->index_map == 0)
+      return 0;
+
+   info->indexToLocFormat = ttUSHORT(data+info->head + 50);
+   return 1;
+}
+
+STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint)
+{
+   stbtt_uint8 *data = info->data;
+   stbtt_uint32 index_map = info->index_map;
+
+   stbtt_uint16 format = ttUSHORT(data + index_map + 0);
+   if (format == 0) { // apple byte encoding
+      stbtt_int32 bytes = ttUSHORT(data + index_map + 2);
+      if (unicode_codepoint < bytes-6)
+         return ttBYTE(data + index_map + 6 + unicode_codepoint);
+      return 0;
+   } else if (format == 6) {
+      stbtt_uint32 first = ttUSHORT(data + index_map + 6);
+      stbtt_uint32 count = ttUSHORT(data + index_map + 8);
+      if ((stbtt_uint32) unicode_codepoint >= first && (stbtt_uint32) unicode_codepoint < first+count)
+         return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2);
+      return 0;
+   } else if (format == 2) {
+      STBTT_assert(0); // @TODO: high-byte mapping for japanese/chinese/korean
+      return 0;
+   } else if (format == 4) { // standard mapping for windows fonts: binary search collection of ranges
+      stbtt_uint16 segcount = ttUSHORT(data+index_map+6) >> 1;
+      stbtt_uint16 searchRange = ttUSHORT(data+index_map+8) >> 1;
+      stbtt_uint16 entrySelector = ttUSHORT(data+index_map+10);
+      stbtt_uint16 rangeShift = ttUSHORT(data+index_map+12) >> 1;
+
+      // do a binary search of the segments
+      stbtt_uint32 endCount = index_map + 14;
+      stbtt_uint32 search = endCount;
+
+      if (unicode_codepoint > 0xffff)
+         return 0;
+
+      // they lie from endCount .. endCount + segCount
+      // but searchRange is the nearest power of two, so...
+      if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2))
+         search += rangeShift*2;
+
+      // now decrement to bias correctly to find smallest
+      search -= 2;
+      while (entrySelector) {
+         stbtt_uint16 end;
+         searchRange >>= 1;
+         end = ttUSHORT(data + search + searchRange*2);
+         if (unicode_codepoint > end)
+            search += searchRange*2;
+         --entrySelector;
+      }
+      search += 2;
+
+      {
+         stbtt_uint16 offset, start;
+         stbtt_uint16 item = (stbtt_uint16) ((search - endCount) >> 1);
+
+         STBTT_assert(unicode_codepoint <= ttUSHORT(data + endCount + 2*item));
+         start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item);
+         if (unicode_codepoint < start)
+            return 0;
+
+         offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item);
+         if (offset == 0)
+            return (stbtt_uint16) (unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item));
+
+         return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item);
+      }
+   } else if (format == 12 || format == 13) {
+      stbtt_uint32 ngroups = ttULONG(data+index_map+12);
+      stbtt_int32 low,high;
+      low = 0; high = (stbtt_int32)ngroups;
+      // Binary search the right group.
+      while (low < high) {
+         stbtt_int32 mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high
+         stbtt_uint32 start_char = ttULONG(data+index_map+16+mid*12);
+         stbtt_uint32 end_char = ttULONG(data+index_map+16+mid*12+4);
+         if ((stbtt_uint32) unicode_codepoint < start_char)
+            high = mid;
+         else if ((stbtt_uint32) unicode_codepoint > end_char)
+            low = mid+1;
+         else {
+            stbtt_uint32 start_glyph = ttULONG(data+index_map+16+mid*12+8);
+            if (format == 12)
+               return start_glyph + unicode_codepoint-start_char;
+            else // format == 13
+               return start_glyph;
+         }
+      }
+      return 0; // not found
+   }
+   // @TODO
+   STBTT_assert(0);
+   return 0;
+}
+
+STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices)
+{
+   return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices);
+}
+
+static void stbtt_setvertex(stbtt_vertex *v, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy)
+{
+   v->type = type;
+   v->x = (stbtt_int16) x;
+   v->y = (stbtt_int16) y;
+   v->cx = (stbtt_int16) cx;
+   v->cy = (stbtt_int16) cy;
+}
+
+static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index)
+{
+   int g1,g2;
+
+   STBTT_assert(!info->cff.size);
+
+   if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range
+   if (info->indexToLocFormat >= 2)    return -1; // unknown index->glyph map format
+
+   if (info->indexToLocFormat == 0) {
+      g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2;
+      g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2;
+   } else {
+      g1 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4);
+      g2 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4 + 4);
+   }
+
+   return g1==g2 ? -1 : g1; // if length is 0, return -1
+}
+
+static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1);
+
+STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1)
+{
+   if (info->cff.size) {
+      stbtt__GetGlyphInfoT2(info, glyph_index, x0, y0, x1, y1);
+   } else {
+      int g = stbtt__GetGlyfOffset(info, glyph_index);
+      if (g < 0) return 0;
+
+      if (x0) *x0 = ttSHORT(info->data + g + 2);
+      if (y0) *y0 = ttSHORT(info->data + g + 4);
+      if (x1) *x1 = ttSHORT(info->data + g + 6);
+      if (y1) *y1 = ttSHORT(info->data + g + 8);
+   }
+   return 1;
+}
+
+STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1)
+{
+   return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info,codepoint), x0,y0,x1,y1);
+}
+
+STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index)
+{
+   stbtt_int16 numberOfContours;
+   int g;
+   if (info->cff.size)
+      return stbtt__GetGlyphInfoT2(info, glyph_index, NULL, NULL, NULL, NULL) == 0;
+   g = stbtt__GetGlyfOffset(info, glyph_index);
+   if (g < 0) return 1;
+   numberOfContours = ttSHORT(info->data + g);
+   return numberOfContours == 0;
+}
+
+static int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_off, int start_off,
+    stbtt_int32 sx, stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, stbtt_int32 cx, stbtt_int32 cy)
+{
+   if (start_off) {
+      if (was_off)
+         stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+scx)>>1, (cy+scy)>>1, cx,cy);
+      stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx,sy,scx,scy);
+   } else {
+      if (was_off)
+         stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve,sx,sy,cx,cy);
+      else
+         stbtt_setvertex(&vertices[num_vertices++], STBTT_vline,sx,sy,0,0);
+   }
+   return num_vertices;
+}
+
+static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices)
+{
+   stbtt_int16 numberOfContours;
+   stbtt_uint8 *endPtsOfContours;
+   stbtt_uint8 *data = info->data;
+   stbtt_vertex *vertices=0;
+   int num_vertices=0;
+   int g = stbtt__GetGlyfOffset(info, glyph_index);
+
+   *pvertices = NULL;
+
+   if (g < 0) return 0;
+
+   numberOfContours = ttSHORT(data + g);
+
+   if (numberOfContours > 0) {
+      stbtt_uint8 flags=0,flagcount;
+      stbtt_int32 ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0;
+      stbtt_int32 x,y,cx,cy,sx,sy, scx,scy;
+      stbtt_uint8 *points;
+      endPtsOfContours = (data + g + 10);
+      ins = ttUSHORT(data + g + 10 + numberOfContours * 2);
+      points = data + g + 10 + numberOfContours * 2 + 2 + ins;
+
+      n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2);
+
+      m = n + 2*numberOfContours;  // a loose bound on how many vertices we might need
+      vertices = (stbtt_vertex *) STBTT_malloc(m * sizeof(vertices[0]), info->userdata);
+      if (vertices == 0)
+         return 0;
+
+      next_move = 0;
+      flagcount=0;
+
+      // in first pass, we load uninterpreted data into the allocated array
+      // above, shifted to the end of the array so we won't overwrite it when
+      // we create our final data starting from the front
+
+      off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated
+
+      // first load flags
+
+      for (i=0; i < n; ++i) {
+         if (flagcount == 0) {
+            flags = *points++;
+            if (flags & 8)
+               flagcount = *points++;
+         } else
+            --flagcount;
+         vertices[off+i].type = flags;
+      }
+
+      // now load x coordinates
+      x=0;
+      for (i=0; i < n; ++i) {
+         flags = vertices[off+i].type;
+         if (flags & 2) {
+            stbtt_int16 dx = *points++;
+            x += (flags & 16) ? dx : -dx; // ???
+         } else {
+            if (!(flags & 16)) {
+               x = x + (stbtt_int16) (points[0]*256 + points[1]);
+               points += 2;
+            }
+         }
+         vertices[off+i].x = (stbtt_int16) x;
+      }
+
+      // now load y coordinates
+      y=0;
+      for (i=0; i < n; ++i) {
+         flags = vertices[off+i].type;
+         if (flags & 4) {
+            stbtt_int16 dy = *points++;
+            y += (flags & 32) ? dy : -dy; // ???
+         } else {
+            if (!(flags & 32)) {
+               y = y + (stbtt_int16) (points[0]*256 + points[1]);
+               points += 2;
+            }
+         }
+         vertices[off+i].y = (stbtt_int16) y;
+      }
+
+      // now convert them to our format
+      num_vertices=0;
+      sx = sy = cx = cy = scx = scy = 0;
+      for (i=0; i < n; ++i) {
+         flags = vertices[off+i].type;
+         x     = (stbtt_int16) vertices[off+i].x;
+         y     = (stbtt_int16) vertices[off+i].y;
+
+         if (next_move == i) {
+            if (i != 0)
+               num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy);
+
+            // now start the new one               
+            start_off = !(flags & 1);
+            if (start_off) {
+               // if we start off with an off-curve point, then when we need to find a point on the curve
+               // where we can start, and we need to save some state for when we wraparound.
+               scx = x;
+               scy = y;
+               if (!(vertices[off+i+1].type & 1)) {
+                  // next point is also a curve point, so interpolate an on-point curve
+                  sx = (x + (stbtt_int32) vertices[off+i+1].x) >> 1;
+                  sy = (y + (stbtt_int32) vertices[off+i+1].y) >> 1;
+               } else {
+                  // otherwise just use the next point as our start point
+                  sx = (stbtt_int32) vertices[off+i+1].x;
+                  sy = (stbtt_int32) vertices[off+i+1].y;
+                  ++i; // we're using point i+1 as the starting point, so skip it
+               }
+            } else {
+               sx = x;
+               sy = y;
+            }
+            stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove,sx,sy,0,0);
+            was_off = 0;
+            next_move = 1 + ttUSHORT(endPtsOfContours+j*2);
+            ++j;
+         } else {
+            if (!(flags & 1)) { // if it's a curve
+               if (was_off) // two off-curve control points in a row means interpolate an on-curve midpoint
+                  stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+x)>>1, (cy+y)>>1, cx, cy);
+               cx = x;
+               cy = y;
+               was_off = 1;
+            } else {
+               if (was_off)
+                  stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x,y, cx, cy);
+               else
+                  stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x,y,0,0);
+               was_off = 0;
+            }
+         }
+      }
+      num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy);
+   } else if (numberOfContours == -1) {
+      // Compound shapes.
+      int more = 1;
+      stbtt_uint8 *comp = data + g + 10;
+      num_vertices = 0;
+      vertices = 0;
+      while (more) {
+         stbtt_uint16 flags, gidx;
+         int comp_num_verts = 0, i;
+         stbtt_vertex *comp_verts = 0, *tmp = 0;
+         float mtx[6] = {1,0,0,1,0,0}, m, n;
+         
+         flags = ttSHORT(comp); comp+=2;
+         gidx = ttSHORT(comp); comp+=2;
+
+         if (flags & 2) { // XY values
+            if (flags & 1) { // shorts
+               mtx[4] = ttSHORT(comp); comp+=2;
+               mtx[5] = ttSHORT(comp); comp+=2;
+            } else {
+               mtx[4] = ttCHAR(comp); comp+=1;
+               mtx[5] = ttCHAR(comp); comp+=1;
+            }
+         }
+         else {
+            // @TODO handle matching point
+            STBTT_assert(0);
+         }
+         if (flags & (1<<3)) { // WE_HAVE_A_SCALE
+            mtx[0] = mtx[3] = ttSHORT(comp)/16384.0f; comp+=2;
+            mtx[1] = mtx[2] = 0;
+         } else if (flags & (1<<6)) { // WE_HAVE_AN_X_AND_YSCALE
+            mtx[0] = ttSHORT(comp)/16384.0f; comp+=2;
+            mtx[1] = mtx[2] = 0;
+            mtx[3] = ttSHORT(comp)/16384.0f; comp+=2;
+         } else if (flags & (1<<7)) { // WE_HAVE_A_TWO_BY_TWO
+            mtx[0] = ttSHORT(comp)/16384.0f; comp+=2;
+            mtx[1] = ttSHORT(comp)/16384.0f; comp+=2;
+            mtx[2] = ttSHORT(comp)/16384.0f; comp+=2;
+            mtx[3] = ttSHORT(comp)/16384.0f; comp+=2;
+         }
+         
+         // Find transformation scales.
+         m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]);
+         n = (float) STBTT_sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]);
+
+         // Get indexed glyph.
+         comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts);
+         if (comp_num_verts > 0) {
+            // Transform vertices.
+            for (i = 0; i < comp_num_verts; ++i) {
+               stbtt_vertex* v = &comp_verts[i];
+               stbtt_vertex_type x,y;
+               x=v->x; y=v->y;
+               v->x = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4]));
+               v->y = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5]));
+               x=v->cx; y=v->cy;
+               v->cx = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4]));
+               v->cy = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5]));
+            }
+            // Append vertices.
+            tmp = (stbtt_vertex*)STBTT_malloc((num_vertices+comp_num_verts)*sizeof(stbtt_vertex), info->userdata);
+            if (!tmp) {
+               if (vertices) STBTT_free(vertices, info->userdata);
+               if (comp_verts) STBTT_free(comp_verts, info->userdata);
+               return 0;
+            }
+            if (num_vertices > 0) STBTT_memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex));
+            STBTT_memcpy(tmp+num_vertices, comp_verts, comp_num_verts*sizeof(stbtt_vertex));
+            if (vertices) STBTT_free(vertices, info->userdata);
+            vertices = tmp;
+            STBTT_free(comp_verts, info->userdata);
+            num_vertices += comp_num_verts;
+         }
+         // More components ?
+         more = flags & (1<<5);
+      }
+   } else if (numberOfContours < 0) {
+      // @TODO other compound variations?
+      STBTT_assert(0);
+   } else {
+      // numberOfCounters == 0, do nothing
+   }
+
+   *pvertices = vertices;
+   return num_vertices;
+}
+
+typedef struct
+{
+   int bounds;
+   int started;
+   float first_x, first_y;
+   float x, y;
+   stbtt_int32 min_x, max_x, min_y, max_y;
+
+   stbtt_vertex *pvertices;
+   int num_vertices;
+} stbtt__csctx;
+
+#define STBTT__CSCTX_INIT(bounds) {bounds,0, 0,0, 0,0, 0,0,0,0, NULL, 0}
+
+static void stbtt__track_vertex(stbtt__csctx *c, stbtt_int32 x, stbtt_int32 y)
+{
+   if (x > c->max_x || !c->started) c->max_x = x;
+   if (y > c->max_y || !c->started) c->max_y = y;
+   if (x < c->min_x || !c->started) c->min_x = x;
+   if (y < c->min_y || !c->started) c->min_y = y;
+   c->started = 1;
+}
+
+static void stbtt__csctx_v(stbtt__csctx *c, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy, stbtt_int32 cx1, stbtt_int32 cy1)
+{
+   if (c->bounds) {
+      stbtt__track_vertex(c, x, y);
+      if (type == STBTT_vcubic) {
+         stbtt__track_vertex(c, cx, cy);
+         stbtt__track_vertex(c, cx1, cy1);
+      }
+   } else {
+      stbtt_setvertex(&c->pvertices[c->num_vertices], type, x, y, cx, cy);
+      c->pvertices[c->num_vertices].cx1 = (stbtt_int16) cx1;
+      c->pvertices[c->num_vertices].cy1 = (stbtt_int16) cy1;
+   }
+   c->num_vertices++;
+}
+
+static void stbtt__csctx_close_shape(stbtt__csctx *ctx)
+{
+   if (ctx->first_x != ctx->x || ctx->first_y != ctx->y)
+      stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->first_x, (int)ctx->first_y, 0, 0, 0, 0);
+}
+
+static void stbtt__csctx_rmove_to(stbtt__csctx *ctx, float dx, float dy)
+{
+   stbtt__csctx_close_shape(ctx);
+   ctx->first_x = ctx->x = ctx->x + dx;
+   ctx->first_y = ctx->y = ctx->y + dy;
+   stbtt__csctx_v(ctx, STBTT_vmove, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0);
+}
+
+static void stbtt__csctx_rline_to(stbtt__csctx *ctx, float dx, float dy)
+{
+   ctx->x += dx;
+   ctx->y += dy;
+   stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0);
+}
+
+static void stbtt__csctx_rccurve_to(stbtt__csctx *ctx, float dx1, float dy1, float dx2, float dy2, float dx3, float dy3)
+{
+   float cx1 = ctx->x + dx1;
+   float cy1 = ctx->y + dy1;
+   float cx2 = cx1 + dx2;
+   float cy2 = cy1 + dy2;
+   ctx->x = cx2 + dx3;
+   ctx->y = cy2 + dy3;
+   stbtt__csctx_v(ctx, STBTT_vcubic, (int)ctx->x, (int)ctx->y, (int)cx1, (int)cy1, (int)cx2, (int)cy2);
+}
+
+static stbtt__buf stbtt__get_subr(stbtt__buf idx, int n)
+{
+   int count = stbtt__cff_index_count(&idx);
+   int bias = 107;
+   if (count >= 33900)
+      bias = 32768;
+   else if (count >= 1240)
+      bias = 1131;
+   n += bias;
+   if (n < 0 || n >= count)
+      return stbtt__new_buf(NULL, 0);
+   return stbtt__cff_index_get(idx, n);
+}
+
+static stbtt__buf stbtt__cid_get_glyph_subrs(const stbtt_fontinfo *info, int glyph_index)
+{
+   stbtt__buf fdselect = info->fdselect;
+   int nranges, start, end, v, fmt, fdselector = -1, i;
+
+   stbtt__buf_seek(&fdselect, 0);
+   fmt = stbtt__buf_get8(&fdselect);
+   if (fmt == 0) {
+      // untested
+      stbtt__buf_skip(&fdselect, glyph_index);
+      fdselector = stbtt__buf_get8(&fdselect);
+   } else if (fmt == 3) {
+      nranges = stbtt__buf_get16(&fdselect);
+      start = stbtt__buf_get16(&fdselect);
+      for (i = 0; i < nranges; i++) {
+         v = stbtt__buf_get8(&fdselect);
+         end = stbtt__buf_get16(&fdselect);
+         if (glyph_index >= start && glyph_index < end) {
+            fdselector = v;
+            break;
+         }
+         start = end;
+      }
+   }
+   if (fdselector == -1) stbtt__new_buf(NULL, 0);
+   return stbtt__get_subrs(info->cff, stbtt__cff_index_get(info->fontdicts, fdselector));
+}
+
+static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, stbtt__csctx *c)
+{
+   int in_header = 1, maskbits = 0, subr_stack_height = 0, sp = 0, v, i, b0;
+   int has_subrs = 0, clear_stack;
+   float s[48];
+   stbtt__buf subr_stack[10], subrs = info->subrs, b;
+   float f;
+
+#define STBTT__CSERR(s) (0)
+
+   // this currently ignores the initial width value, which isn't needed if we have hmtx
+   b = stbtt__cff_index_get(info->charstrings, glyph_index);
+   while (b.cursor < b.size) {
+      i = 0;
+      clear_stack = 1;
+      b0 = stbtt__buf_get8(&b);
+      switch (b0) {
+      // @TODO implement hinting
+      case 0x13: // hintmask
+      case 0x14: // cntrmask
+         if (in_header)
+            maskbits += (sp / 2); // implicit "vstem"
+         in_header = 0;
+         stbtt__buf_skip(&b, (maskbits + 7) / 8);
+         break;
+
+      case 0x01: // hstem
+      case 0x03: // vstem
+      case 0x12: // hstemhm
+      case 0x17: // vstemhm
+         maskbits += (sp / 2);
+         break;
+
+      case 0x15: // rmoveto
+         in_header = 0;
+         if (sp < 2) return STBTT__CSERR("rmoveto stack");
+         stbtt__csctx_rmove_to(c, s[sp-2], s[sp-1]);
+         break;
+      case 0x04: // vmoveto
+         in_header = 0;
+         if (sp < 1) return STBTT__CSERR("vmoveto stack");
+         stbtt__csctx_rmove_to(c, 0, s[sp-1]);
+         break;
+      case 0x16: // hmoveto
+         in_header = 0;
+         if (sp < 1) return STBTT__CSERR("hmoveto stack");
+         stbtt__csctx_rmove_to(c, s[sp-1], 0);
+         break;
+
+      case 0x05: // rlineto
+         if (sp < 2) return STBTT__CSERR("rlineto stack");
+         for (; i + 1 < sp; i += 2)
+            stbtt__csctx_rline_to(c, s[i], s[i+1]);
+         break;
+
+      // hlineto/vlineto and vhcurveto/hvcurveto alternate horizontal and vertical
+      // starting from a different place.
+
+      case 0x07: // vlineto
+         if (sp < 1) return STBTT__CSERR("vlineto stack");
+         goto vlineto;
+      case 0x06: // hlineto
+         if (sp < 1) return STBTT__CSERR("hlineto stack");
+         for (;;) {
+            if (i >= sp) break;
+            stbtt__csctx_rline_to(c, s[i], 0);
+            i++;
+      vlineto:
+            if (i >= sp) break;
+            stbtt__csctx_rline_to(c, 0, s[i]);
+            i++;
+         }
+         break;
+
+      case 0x1F: // hvcurveto
+         if (sp < 4) return STBTT__CSERR("hvcurveto stack");
+         goto hvcurveto;
+      case 0x1E: // vhcurveto
+         if (sp < 4) return STBTT__CSERR("vhcurveto stack");
+         for (;;) {
+            if (i + 3 >= sp) break;
+            stbtt__csctx_rccurve_to(c, 0, s[i], s[i+1], s[i+2], s[i+3], (sp - i == 5) ? s[i + 4] : 0.0f);
+            i += 4;
+      hvcurveto:
+            if (i + 3 >= sp) break;
+            stbtt__csctx_rccurve_to(c, s[i], 0, s[i+1], s[i+2], (sp - i == 5) ? s[i+4] : 0.0f, s[i+3]);
+            i += 4;
+         }
+         break;
+
+      case 0x08: // rrcurveto
+         if (sp < 6) return STBTT__CSERR("rcurveline stack");
+         for (; i + 5 < sp; i += 6)
+            stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]);
+         break;
+
+      case 0x18: // rcurveline
+         if (sp < 8) return STBTT__CSERR("rcurveline stack");
+         for (; i + 5 < sp - 2; i += 6)
+            stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]);
+         if (i + 1 >= sp) return STBTT__CSERR("rcurveline stack");
+         stbtt__csctx_rline_to(c, s[i], s[i+1]);
+         break;
+
+      case 0x19: // rlinecurve
+         if (sp < 8) return STBTT__CSERR("rlinecurve stack");
+         for (; i + 1 < sp - 6; i += 2)
+            stbtt__csctx_rline_to(c, s[i], s[i+1]);
+         if (i + 5 >= sp) return STBTT__CSERR("rlinecurve stack");
+         stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]);
+         break;
+
+      case 0x1A: // vvcurveto
+      case 0x1B: // hhcurveto
+         if (sp < 4) return STBTT__CSERR("(vv|hh)curveto stack");
+         f = 0.0;
+         if (sp & 1) { f = s[i]; i++; }
+         for (; i + 3 < sp; i += 4) {
+            if (b0 == 0x1B)
+               stbtt__csctx_rccurve_to(c, s[i], f, s[i+1], s[i+2], s[i+3], 0.0);
+            else
+               stbtt__csctx_rccurve_to(c, f, s[i], s[i+1], s[i+2], 0.0, s[i+3]);
+            f = 0.0;
+         }
+         break;
+
+      case 0x0A: // callsubr
+         if (!has_subrs) {
+            if (info->fdselect.size)
+               subrs = stbtt__cid_get_glyph_subrs(info, glyph_index);
+            has_subrs = 1;
+         }
+         // fallthrough
+      case 0x1D: // callgsubr
+         if (sp < 1) return STBTT__CSERR("call(g|)subr stack");
+         v = (int) s[--sp];
+         if (subr_stack_height >= 10) return STBTT__CSERR("recursion limit");
+         subr_stack[subr_stack_height++] = b;
+         b = stbtt__get_subr(b0 == 0x0A ? subrs : info->gsubrs, v);
+         if (b.size == 0) return STBTT__CSERR("subr not found");
+         b.cursor = 0;
+         clear_stack = 0;
+         break;
+
+      case 0x0B: // return
+         if (subr_stack_height <= 0) return STBTT__CSERR("return outside subr");
+         b = subr_stack[--subr_stack_height];
+         clear_stack = 0;
+         break;
+
+      case 0x0E: // endchar
+         stbtt__csctx_close_shape(c);
+         return 1;
+
+      case 0x0C: { // two-byte escape
+         float dx1, dx2, dx3, dx4, dx5, dx6, dy1, dy2, dy3, dy4, dy5, dy6;
+         float dx, dy;
+         int b1 = stbtt__buf_get8(&b);
+         switch (b1) {
+         // @TODO These "flex" implementations ignore the flex-depth and resolution,
+         // and always draw beziers.
+         case 0x22: // hflex
+            if (sp < 7) return STBTT__CSERR("hflex stack");
+            dx1 = s[0];
+            dx2 = s[1];
+            dy2 = s[2];
+            dx3 = s[3];
+            dx4 = s[4];
+            dx5 = s[5];
+            dx6 = s[6];
+            stbtt__csctx_rccurve_to(c, dx1, 0, dx2, dy2, dx3, 0);
+            stbtt__csctx_rccurve_to(c, dx4, 0, dx5, -dy2, dx6, 0);
+            break;
+
+         case 0x23: // flex
+            if (sp < 13) return STBTT__CSERR("flex stack");
+            dx1 = s[0];
+            dy1 = s[1];
+            dx2 = s[2];
+            dy2 = s[3];
+            dx3 = s[4];
+            dy3 = s[5];
+            dx4 = s[6];
+            dy4 = s[7];
+            dx5 = s[8];
+            dy5 = s[9];
+            dx6 = s[10];
+            dy6 = s[11];
+            //fd is s[12]
+            stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3);
+            stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6);
+            break;
+
+         case 0x24: // hflex1
+            if (sp < 9) return STBTT__CSERR("hflex1 stack");
+            dx1 = s[0];
+            dy1 = s[1];
+            dx2 = s[2];
+            dy2 = s[3];
+            dx3 = s[4];
+            dx4 = s[5];
+            dx5 = s[6];
+            dy5 = s[7];
+            dx6 = s[8];
+            stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, 0);
+            stbtt__csctx_rccurve_to(c, dx4, 0, dx5, dy5, dx6, -(dy1+dy2+dy5));
+            break;
+
+         case 0x25: // flex1
+            if (sp < 11) return STBTT__CSERR("flex1 stack");
+            dx1 = s[0];
+            dy1 = s[1];
+            dx2 = s[2];
+            dy2 = s[3];
+            dx3 = s[4];
+            dy3 = s[5];
+            dx4 = s[6];
+            dy4 = s[7];
+            dx5 = s[8];
+            dy5 = s[9];
+            dx6 = dy6 = s[10];
+            dx = dx1+dx2+dx3+dx4+dx5;
+            dy = dy1+dy2+dy3+dy4+dy5;
+            if (STBTT_fabs(dx) > STBTT_fabs(dy))
+               dy6 = -dy;
+            else
+               dx6 = -dx;
+            stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3);
+            stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6);
+            break;
+
+         default:
+            return STBTT__CSERR("unimplemented");
+         }
+      } break;
+
+      default:
+         if (b0 != 255 && b0 != 28 && (b0 < 32 || b0 > 254))
+            return STBTT__CSERR("reserved operator");
+
+         // push immediate
+         if (b0 == 255) {
+            f = (float)(stbtt_int32)stbtt__buf_get32(&b) / 0x10000;
+         } else {
+            stbtt__buf_skip(&b, -1);
+            f = (float)(stbtt_int16)stbtt__cff_int(&b);
+         }
+         if (sp >= 48) return STBTT__CSERR("push stack overflow");
+         s[sp++] = f;
+         clear_stack = 0;
+         break;
+      }
+      if (clear_stack) sp = 0;
+   }
+   return STBTT__CSERR("no endchar");
+
+#undef STBTT__CSERR
+}
+
+static int stbtt__GetGlyphShapeT2(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices)
+{
+   // runs the charstring twice, once to count and once to output (to avoid realloc)
+   stbtt__csctx count_ctx = STBTT__CSCTX_INIT(1);
+   stbtt__csctx output_ctx = STBTT__CSCTX_INIT(0);
+   if (stbtt__run_charstring(info, glyph_index, &count_ctx)) {
+      *pvertices = (stbtt_vertex*)STBTT_malloc(count_ctx.num_vertices*sizeof(stbtt_vertex), info->userdata);
+      output_ctx.pvertices = *pvertices;
+      if (stbtt__run_charstring(info, glyph_index, &output_ctx)) {
+         STBTT_assert(output_ctx.num_vertices == count_ctx.num_vertices);
+         return output_ctx.num_vertices;
+      }
+   }
+   *pvertices = NULL;
+   return 0;
+}
+
+static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1)
+{
+   stbtt__csctx c = STBTT__CSCTX_INIT(1);
+   int r = stbtt__run_charstring(info, glyph_index, &c);
+   if (x0)  *x0 = r ? c.min_x : 0;
+   if (y0)  *y0 = r ? c.min_y : 0;
+   if (x1)  *x1 = r ? c.max_x : 0;
+   if (y1)  *y1 = r ? c.max_y : 0;
+   return r ? c.num_vertices : 0;
+}
+
+STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices)
+{
+   if (!info->cff.size)
+      return stbtt__GetGlyphShapeTT(info, glyph_index, pvertices);
+   else
+      return stbtt__GetGlyphShapeT2(info, glyph_index, pvertices);
+}
+
+STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing)
+{
+   stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data+info->hhea + 34);
+   if (glyph_index < numOfLongHorMetrics) {
+      if (advanceWidth)     *advanceWidth    = ttSHORT(info->data + info->hmtx + 4*glyph_index);
+      if (leftSideBearing)  *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*glyph_index + 2);
+   } else {
+      if (advanceWidth)     *advanceWidth    = ttSHORT(info->data + info->hmtx + 4*(numOfLongHorMetrics-1));
+      if (leftSideBearing)  *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics));
+   }
+}
+
+static int  stbtt__GetGlyphKernInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2)
+{
+   stbtt_uint8 *data = info->data + info->kern;
+   stbtt_uint32 needle, straw;
+   int l, r, m;
+
+   // we only look at the first table. it must be 'horizontal' and format 0.
+   if (!info->kern)
+      return 0;
+   if (ttUSHORT(data+2) < 1) // number of tables, need at least 1
+      return 0;
+   if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format
+      return 0;
+
+   l = 0;
+   r = ttUSHORT(data+10) - 1;
+   needle = glyph1 << 16 | glyph2;
+   while (l <= r) {
+      m = (l + r) >> 1;
+      straw = ttULONG(data+18+(m*6)); // note: unaligned read
+      if (needle < straw)
+         r = m - 1;
+      else if (needle > straw)
+         l = m + 1;
+      else
+         return ttSHORT(data+22+(m*6));
+   }
+   return 0;
+}
+
+static stbtt_int32  stbtt__GetCoverageIndex(stbtt_uint8 *coverageTable, int glyph)
+{
+    stbtt_uint16 coverageFormat = ttUSHORT(coverageTable);
+    switch(coverageFormat) {
+        case 1: {
+            stbtt_uint16 glyphCount = ttUSHORT(coverageTable + 2);
+
+            // Binary search.
+            stbtt_int32 l=0, r=glyphCount-1, m;
+            int straw, needle=glyph;
+            while (l <= r) {
+                stbtt_uint8 *glyphArray = coverageTable + 4;
+                stbtt_uint16 glyphID;
+                m = (l + r) >> 1;
+                glyphID = ttUSHORT(glyphArray + 2 * m);
+                straw = glyphID;
+                if (needle < straw)
+                    r = m - 1;
+                else if (needle > straw)
+                    l = m + 1;
+                else {
+                     return m;
+                }
+            }
+        } break;
+
+        case 2: {
+            stbtt_uint16 rangeCount = ttUSHORT(coverageTable + 2);
+            stbtt_uint8 *rangeArray = coverageTable + 4;
+
+            // Binary search.
+            stbtt_int32 l=0, r=rangeCount-1, m;
+            int strawStart, strawEnd, needle=glyph;
+            while (l <= r) {
+                stbtt_uint8 *rangeRecord;
+                m = (l + r) >> 1;
+                rangeRecord = rangeArray + 6 * m;
+                strawStart = ttUSHORT(rangeRecord);
+                strawEnd = ttUSHORT(rangeRecord + 2);
+                if (needle < strawStart)
+                    r = m - 1;
+                else if (needle > strawEnd)
+                    l = m + 1;
+                else {
+                    stbtt_uint16 startCoverageIndex = ttUSHORT(rangeRecord + 4);
+                    return startCoverageIndex + glyph - strawStart;
+                }
+            }
+        } break;
+
+        default: {
+            // There are no other cases.
+            STBTT_assert(0);
+        } break;
+    }
+
+    return -1;
+}
+
+static stbtt_int32  stbtt__GetGlyphClass(stbtt_uint8 *classDefTable, int glyph)
+{
+    stbtt_uint16 classDefFormat = ttUSHORT(classDefTable);
+    switch(classDefFormat)
+    {
+        case 1: {
+            stbtt_uint16 startGlyphID = ttUSHORT(classDefTable + 2);
+            stbtt_uint16 glyphCount = ttUSHORT(classDefTable + 4);
+            stbtt_uint8 *classDef1ValueArray = classDefTable + 6;
+
+            if (glyph >= startGlyphID && glyph < startGlyphID + glyphCount)
+                return (stbtt_int32)ttUSHORT(classDef1ValueArray + 2 * (glyph - startGlyphID));
+
+            classDefTable = classDef1ValueArray + 2 * glyphCount;
+        } break;
+
+        case 2: {
+            stbtt_uint16 classRangeCount = ttUSHORT(classDefTable + 2);
+            stbtt_uint8 *classRangeRecords = classDefTable + 4;
+
+            // Binary search.
+            stbtt_int32 l=0, r=classRangeCount-1, m;
+            int strawStart, strawEnd, needle=glyph;
+            while (l <= r) {
+                stbtt_uint8 *classRangeRecord;
+                m = (l + r) >> 1;
+                classRangeRecord = classRangeRecords + 6 * m;
+                strawStart = ttUSHORT(classRangeRecord);
+                strawEnd = ttUSHORT(classRangeRecord + 2);
+                if (needle < strawStart)
+                    r = m - 1;
+                else if (needle > strawEnd)
+                    l = m + 1;
+                else
+                    return (stbtt_int32)ttUSHORT(classRangeRecord + 4);
+            }
+
+            classDefTable = classRangeRecords + 6 * classRangeCount;
+        } break;
+
+        default: {
+            // There are no other cases.
+            STBTT_assert(0);
+        } break;
+    }
+
+    return -1;
+}
+
+// Define to STBTT_assert(x) if you want to break on unimplemented formats.
+#define STBTT_GPOS_TODO_assert(x)
+
+static stbtt_int32  stbtt__GetGlyphGPOSInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2)
+{
+    stbtt_uint16 lookupListOffset;
+    stbtt_uint8 *lookupList;
+    stbtt_uint16 lookupCount;
+    stbtt_uint8 *data;
+    stbtt_int32 i;
+
+    if (!info->gpos) return 0;
+
+    data = info->data + info->gpos;
+
+    if (ttUSHORT(data+0) != 1) return 0; // Major version 1
+    if (ttUSHORT(data+2) != 0) return 0; // Minor version 0
+
+    lookupListOffset = ttUSHORT(data+8);
+    lookupList = data + lookupListOffset;
+    lookupCount = ttUSHORT(lookupList);
+
+    for (i=0; i<lookupCount; ++i) {
+        stbtt_uint16 lookupOffset = ttUSHORT(lookupList + 2 + 2 * i);
+        stbtt_uint8 *lookupTable = lookupList + lookupOffset;
+
+        stbtt_uint16 lookupType = ttUSHORT(lookupTable);
+        stbtt_uint16 subTableCount = ttUSHORT(lookupTable + 4);
+        stbtt_uint8 *subTableOffsets = lookupTable + 6;
+        switch(lookupType) {
+            case 2: { // Pair Adjustment Positioning Subtable
+                stbtt_int32 sti;
+                for (sti=0; sti<subTableCount; sti++) {
+                    stbtt_uint16 subtableOffset = ttUSHORT(subTableOffsets + 2 * sti);
+                    stbtt_uint8 *table = lookupTable + subtableOffset;
+                    stbtt_uint16 posFormat = ttUSHORT(table);
+                    stbtt_uint16 coverageOffset = ttUSHORT(table + 2);
+                    stbtt_int32 coverageIndex = stbtt__GetCoverageIndex(table + coverageOffset, glyph1);
+                    if (coverageIndex == -1) continue;
+
+                    switch (posFormat) {
+                        case 1: {
+                            stbtt_int32 l, r, m;
+                            int straw, needle;
+                            stbtt_uint16 valueFormat1 = ttUSHORT(table + 4);
+                            stbtt_uint16 valueFormat2 = ttUSHORT(table + 6);
+                            stbtt_int32 valueRecordPairSizeInBytes = 2;
+                            stbtt_uint16 pairSetCount = ttUSHORT(table + 8);
+                            stbtt_uint16 pairPosOffset = ttUSHORT(table + 10 + 2 * coverageIndex);
+                            stbtt_uint8 *pairValueTable = table + pairPosOffset;
+                            stbtt_uint16 pairValueCount = ttUSHORT(pairValueTable);
+                            stbtt_uint8 *pairValueArray = pairValueTable + 2;
+                            // TODO: Support more formats.
+                            STBTT_GPOS_TODO_assert(valueFormat1 == 4);
+                            if (valueFormat1 != 4) return 0;
+                            STBTT_GPOS_TODO_assert(valueFormat2 == 0);
+                            if (valueFormat2 != 0) return 0;
+
+                            STBTT_assert(coverageIndex < pairSetCount);
+                            STBTT__NOTUSED(pairSetCount);
+
+                            needle=glyph2;
+                            r=pairValueCount-1;
+                            l=0;
+
+                            // Binary search.
+                            while (l <= r) {
+                                stbtt_uint16 secondGlyph;
+                                stbtt_uint8 *pairValue;
+                                m = (l + r) >> 1;
+                                pairValue = pairValueArray + (2 + valueRecordPairSizeInBytes) * m;
+                                secondGlyph = ttUSHORT(pairValue);
+                                straw = secondGlyph;
+                                if (needle < straw)
+                                    r = m - 1;
+                                else if (needle > straw)
+                                    l = m + 1;
+                                else {
+                                    stbtt_int16 xAdvance = ttSHORT(pairValue + 2);
+                                    return xAdvance;
+                                }
+                            }
+                        } break;
+
+                        case 2: {
+                            stbtt_uint16 valueFormat1 = ttUSHORT(table + 4);
+                            stbtt_uint16 valueFormat2 = ttUSHORT(table + 6);
+
+                            stbtt_uint16 classDef1Offset = ttUSHORT(table + 8);
+                            stbtt_uint16 classDef2Offset = ttUSHORT(table + 10);
+                            int glyph1class = stbtt__GetGlyphClass(table + classDef1Offset, glyph1);
+                            int glyph2class = stbtt__GetGlyphClass(table + classDef2Offset, glyph2);
+
+                            stbtt_uint16 class1Count = ttUSHORT(table + 12);
+                            stbtt_uint16 class2Count = ttUSHORT(table + 14);
+                            STBTT_assert(glyph1class < class1Count);
+                            STBTT_assert(glyph2class < class2Count);
+
+                            // TODO: Support more formats.
+                            STBTT_GPOS_TODO_assert(valueFormat1 == 4);
+                            if (valueFormat1 != 4) return 0;
+                            STBTT_GPOS_TODO_assert(valueFormat2 == 0);
+                            if (valueFormat2 != 0) return 0;
+
+                            if (glyph1class >= 0 && glyph1class < class1Count && glyph2class >= 0 && glyph2class < class2Count) {
+                                stbtt_uint8 *class1Records = table + 16;
+                                stbtt_uint8 *class2Records = class1Records + 2 * (glyph1class * class2Count);
+                                stbtt_int16 xAdvance = ttSHORT(class2Records + 2 * glyph2class);
+                                return xAdvance;
+                            }
+                        } break;
+
+                        default: {
+                            // There are no other cases.
+                            STBTT_assert(0);
+                            break;
+                        };
+                    }
+                }
+                break;
+            };
+
+            default:
+                // TODO: Implement other stuff.
+                break;
+        }
+    }
+
+    return 0;
+}
+
+STBTT_DEF int  stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int g1, int g2)
+{
+   int xAdvance = 0;
+
+   if (info->gpos)
+      xAdvance += stbtt__GetGlyphGPOSInfoAdvance(info, g1, g2);
+
+   if (info->kern)
+      xAdvance += stbtt__GetGlyphKernInfoAdvance(info, g1, g2);
+
+   return xAdvance;
+}
+
+STBTT_DEF int  stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2)
+{
+   if (!info->kern && !info->gpos) // if no kerning table, don't waste time looking up both codepoint->glyphs
+      return 0;
+   return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2));
+}
+
+STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing)
+{
+   stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing);
+}
+
+STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap)
+{
+   if (ascent ) *ascent  = ttSHORT(info->data+info->hhea + 4);
+   if (descent) *descent = ttSHORT(info->data+info->hhea + 6);
+   if (lineGap) *lineGap = ttSHORT(info->data+info->hhea + 8);
+}
+
+STBTT_DEF int  stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap)
+{
+   int tab = stbtt__find_table(info->data, info->fontstart, "OS/2");
+   if (!tab)
+      return 0;
+   if (typoAscent ) *typoAscent  = ttSHORT(info->data+tab + 68);
+   if (typoDescent) *typoDescent = ttSHORT(info->data+tab + 70);
+   if (typoLineGap) *typoLineGap = ttSHORT(info->data+tab + 72);
+   return 1;
+}
+
+STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1)
+{
+   *x0 = ttSHORT(info->data + info->head + 36);
+   *y0 = ttSHORT(info->data + info->head + 38);
+   *x1 = ttSHORT(info->data + info->head + 40);
+   *y1 = ttSHORT(info->data + info->head + 42);
+}
+
+STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float height)
+{
+   int fheight = ttSHORT(info->data + info->hhea + 4) - ttSHORT(info->data + info->hhea + 6);
+   return (float) height / fheight;
+}
+
+STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels)
+{
+   int unitsPerEm = ttUSHORT(info->data + info->head + 18);
+   return pixels / unitsPerEm;
+}
+
+STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v)
+{
+   STBTT_free(v, info->userdata);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// antialiasing software rasterizer
+//
+
+STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1)
+{
+   int x0=0,y0=0,x1,y1; // =0 suppresses compiler warning
+   if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) {
+      // e.g. space character
+      if (ix0) *ix0 = 0;
+      if (iy0) *iy0 = 0;
+      if (ix1) *ix1 = 0;
+      if (iy1) *iy1 = 0;
+   } else {
+      // move to integral bboxes (treating pixels as little squares, what pixels get touched)?
+      if (ix0) *ix0 = STBTT_ifloor( x0 * scale_x + shift_x);
+      if (iy0) *iy0 = STBTT_ifloor(-y1 * scale_y + shift_y);
+      if (ix1) *ix1 = STBTT_iceil ( x1 * scale_x + shift_x);
+      if (iy1) *iy1 = STBTT_iceil (-y0 * scale_y + shift_y);
+   }
+}
+
+STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1)
+{
+   stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y,0.0f,0.0f, ix0, iy0, ix1, iy1);
+}
+
+STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1)
+{
+   stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font,codepoint), scale_x, scale_y,shift_x,shift_y, ix0,iy0,ix1,iy1);
+}
+
+STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1)
+{
+   stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y,0.0f,0.0f, ix0,iy0,ix1,iy1);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+//
+//  Rasterizer
+
+typedef struct stbtt__hheap_chunk
+{
+   struct stbtt__hheap_chunk *next;
+} stbtt__hheap_chunk;
+
+typedef struct stbtt__hheap
+{
+   struct stbtt__hheap_chunk *head;
+   void   *first_free;
+   int    num_remaining_in_head_chunk;
+} stbtt__hheap;
+
+static void *stbtt__hheap_alloc(stbtt__hheap *hh, size_t size, void *userdata)
+{
+   if (hh->first_free) {
+      void *p = hh->first_free;
+      hh->first_free = * (void **) p;
+      return p;
+   } else {
+      if (hh->num_remaining_in_head_chunk == 0) {
+         int count = (size < 32 ? 2000 : size < 128 ? 800 : 100);
+         stbtt__hheap_chunk *c = (stbtt__hheap_chunk *) STBTT_malloc(sizeof(stbtt__hheap_chunk) + size * count, userdata);
+         if (c == NULL)
+            return NULL;
+         c->next = hh->head;
+         hh->head = c;
+         hh->num_remaining_in_head_chunk = count;
+      }
+      --hh->num_remaining_in_head_chunk;
+      return (char *) (hh->head) + sizeof(stbtt__hheap_chunk) + size * hh->num_remaining_in_head_chunk;
+   }
+}
+
+static void stbtt__hheap_free(stbtt__hheap *hh, void *p)
+{
+   *(void **) p = hh->first_free;
+   hh->first_free = p;
+}
+
+static void stbtt__hheap_cleanup(stbtt__hheap *hh, void *userdata)
+{
+   stbtt__hheap_chunk *c = hh->head;
+   while (c) {
+      stbtt__hheap_chunk *n = c->next;
+      STBTT_free(c, userdata);
+      c = n;
+   }
+}
+
+typedef struct stbtt__edge {
+   float x0,y0, x1,y1;
+   int invert;
+} stbtt__edge;
+
+
+typedef struct stbtt__active_edge
+{
+   struct stbtt__active_edge *next;
+   #if STBTT_RASTERIZER_VERSION==1
+   int x,dx;
+   float ey;
+   int direction;
+   #elif STBTT_RASTERIZER_VERSION==2
+   float fx,fdx,fdy;
+   float direction;
+   float sy;
+   float ey;
+   #else
+   #error "Unrecognized value of STBTT_RASTERIZER_VERSION"
+   #endif
+} stbtt__active_edge;
+
+#if STBTT_RASTERIZER_VERSION == 1
+#define STBTT_FIXSHIFT   10
+#define STBTT_FIX        (1 << STBTT_FIXSHIFT)
+#define STBTT_FIXMASK    (STBTT_FIX-1)
+
+static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata)
+{
+   stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata);
+   float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0);
+   STBTT_assert(z != NULL);
+   if (!z) return z;
+   
+   // round dx down to avoid overshooting
+   if (dxdy < 0)
+      z->dx = -STBTT_ifloor(STBTT_FIX * -dxdy);
+   else
+      z->dx = STBTT_ifloor(STBTT_FIX * dxdy);
+
+   z->x = STBTT_ifloor(STBTT_FIX * e->x0 + z->dx * (start_point - e->y0)); // use z->dx so when we offset later it's by the same amount
+   z->x -= off_x * STBTT_FIX;
+
+   z->ey = e->y1;
+   z->next = 0;
+   z->direction = e->invert ? 1 : -1;
+   return z;
+}
+#elif STBTT_RASTERIZER_VERSION == 2
+static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata)
+{
+   stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata);
+   float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0);
+   STBTT_assert(z != NULL);
+   //STBTT_assert(e->y0 <= start_point);
+   if (!z) return z;
+   z->fdx = dxdy;
+   z->fdy = dxdy != 0.0f ? (1.0f/dxdy) : 0.0f;
+   z->fx = e->x0 + dxdy * (start_point - e->y0);
+   z->fx -= off_x;
+   z->direction = e->invert ? 1.0f : -1.0f;
+   z->sy = e->y0;
+   z->ey = e->y1;
+   z->next = 0;
+   return z;
+}
+#else
+#error "Unrecognized value of STBTT_RASTERIZER_VERSION"
+#endif
+
+#if STBTT_RASTERIZER_VERSION == 1
+// note: this routine clips fills that extend off the edges... ideally this
+// wouldn't happen, but it could happen if the truetype glyph bounding boxes
+// are wrong, or if the user supplies a too-small bitmap
+static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__active_edge *e, int max_weight)
+{
+   // non-zero winding fill
+   int x0=0, w=0;
+
+   while (e) {
+      if (w == 0) {
+         // if we're currently at zero, we need to record the edge start point
+         x0 = e->x; w += e->direction;
+      } else {
+         int x1 = e->x; w += e->direction;
+         // if we went to zero, we need to draw
+         if (w == 0) {
+            int i = x0 >> STBTT_FIXSHIFT;
+            int j = x1 >> STBTT_FIXSHIFT;
+
+            if (i < len && j >= 0) {
+               if (i == j) {
+                  // x0,x1 are the same pixel, so compute combined coverage
+                  scanline[i] = scanline[i] + (stbtt_uint8) ((x1 - x0) * max_weight >> STBTT_FIXSHIFT);
+               } else {
+                  if (i >= 0) // add antialiasing for x0
+                     scanline[i] = scanline[i] + (stbtt_uint8) (((STBTT_FIX - (x0 & STBTT_FIXMASK)) * max_weight) >> STBTT_FIXSHIFT);
+                  else
+                     i = -1; // clip
+
+                  if (j < len) // add antialiasing for x1
+                     scanline[j] = scanline[j] + (stbtt_uint8) (((x1 & STBTT_FIXMASK) * max_weight) >> STBTT_FIXSHIFT);
+                  else
+                     j = len; // clip
+
+                  for (++i; i < j; ++i) // fill pixels between x0 and x1
+                     scanline[i] = scanline[i] + (stbtt_uint8) max_weight;
+               }
+            }
+         }
+      }
+      
+      e = e->next;
+   }
+}
+
+static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata)
+{
+   stbtt__hheap hh = { 0, 0, 0 };
+   stbtt__active_edge *active = NULL;
+   int y,j=0;
+   int max_weight = (255 / vsubsample);  // weight per vertical scanline
+   int s; // vertical subsample index
+   unsigned char scanline_data[512], *scanline;
+
+   if (result->w > 512)
+      scanline = (unsigned char *) STBTT_malloc(result->w, userdata);
+   else
+      scanline = scanline_data;
+
+   y = off_y * vsubsample;
+   e[n].y0 = (off_y + result->h) * (float) vsubsample + 1;
+
+   while (j < result->h) {
+      STBTT_memset(scanline, 0, result->w);
+      for (s=0; s < vsubsample; ++s) {
+         // find center of pixel for this scanline
+         float scan_y = y + 0.5f;
+         stbtt__active_edge **step = &active;
+
+         // update all active edges;
+         // remove all active edges that terminate before the center of this scanline
+         while (*step) {
+            stbtt__active_edge * z = *step;
+            if (z->ey <= scan_y) {
+               *step = z->next; // delete from list
+               STBTT_assert(z->direction);
+               z->direction = 0;
+               stbtt__hheap_free(&hh, z);
+            } else {
+               z->x += z->dx; // advance to position for current scanline
+               step = &((*step)->next); // advance through list
+            }
+         }
+
+         // resort the list if needed
+         for(;;) {
+            int changed=0;
+            step = &active;
+            while (*step && (*step)->next) {
+               if ((*step)->x > (*step)->next->x) {
+                  stbtt__active_edge *t = *step;
+                  stbtt__active_edge *q = t->next;
+
+                  t->next = q->next;
+                  q->next = t;
+                  *step = q;
+                  changed = 1;
+               }
+               step = &(*step)->next;
+            }
+            if (!changed) break;
+         }
+
+         // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline
+         while (e->y0 <= scan_y) {
+            if (e->y1 > scan_y) {
+               stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y, userdata);
+               if (z != NULL) {
+                  // find insertion point
+                  if (active == NULL)
+                     active = z;
+                  else if (z->x < active->x) {
+                     // insert at front
+                     z->next = active;
+                     active = z;
+                  } else {
+                     // find thing to insert AFTER
+                     stbtt__active_edge *p = active;
+                     while (p->next && p->next->x < z->x)
+                        p = p->next;
+                     // at this point, p->next->x is NOT < z->x
+                     z->next = p->next;
+                     p->next = z;
+                  }
+               }
+            }
+            ++e;
+         }
+
+         // now process all active edges in XOR fashion
+         if (active)
+            stbtt__fill_active_edges(scanline, result->w, active, max_weight);
+
+         ++y;
+      }
+      STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w);
+      ++j;
+   }
+
+   stbtt__hheap_cleanup(&hh, userdata);
+
+   if (scanline != scanline_data)
+      STBTT_free(scanline, userdata);
+}
+
+#elif STBTT_RASTERIZER_VERSION == 2
+
+// the edge passed in here does not cross the vertical line at x or the vertical line at x+1
+// (i.e. it has already been clipped to those)
+static void stbtt__handle_clipped_edge(float *scanline, int x, stbtt__active_edge *e, float x0, float y0, float x1, float y1)
+{
+   if (y0 == y1) return;
+   STBTT_assert(y0 < y1);
+   STBTT_assert(e->sy <= e->ey);
+   if (y0 > e->ey) return;
+   if (y1 < e->sy) return;
+   if (y0 < e->sy) {
+      x0 += (x1-x0) * (e->sy - y0) / (y1-y0);
+      y0 = e->sy;
+   }
+   if (y1 > e->ey) {
+      x1 += (x1-x0) * (e->ey - y1) / (y1-y0);
+      y1 = e->ey;
+   }
+
+   if (x0 == x)
+      STBTT_assert(x1 <= x+1);
+   else if (x0 == x+1)
+      STBTT_assert(x1 >= x);
+   else if (x0 <= x)
+      STBTT_assert(x1 <= x);
+   else if (x0 >= x+1)
+      STBTT_assert(x1 >= x+1);
+   else
+      STBTT_assert(x1 >= x && x1 <= x+1);
+
+   if (x0 <= x && x1 <= x)
+      scanline[x] += e->direction * (y1-y0);
+   else if (x0 >= x+1 && x1 >= x+1)
+      ;
+   else {
+      STBTT_assert(x0 >= x && x0 <= x+1 && x1 >= x && x1 <= x+1);
+      scanline[x] += e->direction * (y1-y0) * (1-((x0-x)+(x1-x))/2); // coverage = 1 - average x position
+   }
+}
+
+static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, int len, stbtt__active_edge *e, float y_top)
+{
+   float y_bottom = y_top+1;
+
+   while (e) {
+      // brute force every pixel
+
+      // compute intersection points with top & bottom
+      STBTT_assert(e->ey >= y_top);
+
+      if (e->fdx == 0) {
+         float x0 = e->fx;
+         if (x0 < len) {
+            if (x0 >= 0) {
+               stbtt__handle_clipped_edge(scanline,(int) x0,e, x0,y_top, x0,y_bottom);
+               stbtt__handle_clipped_edge(scanline_fill-1,(int) x0+1,e, x0,y_top, x0,y_bottom);
+            } else {
+               stbtt__handle_clipped_edge(scanline_fill-1,0,e, x0,y_top, x0,y_bottom);
+            }
+         }
+      } else {
+         float x0 = e->fx;
+         float dx = e->fdx;
+         float xb = x0 + dx;
+         float x_top, x_bottom;
+         float sy0,sy1;
+         float dy = e->fdy;
+         STBTT_assert(e->sy <= y_bottom && e->ey >= y_top);
+
+         // compute endpoints of line segment clipped to this scanline (if the
+         // line segment starts on this scanline. x0 is the intersection of the
+         // line with y_top, but that may be off the line segment.
+         if (e->sy > y_top) {
+            x_top = x0 + dx * (e->sy - y_top);
+            sy0 = e->sy;
+         } else {
+            x_top = x0;
+            sy0 = y_top;
+         }
+         if (e->ey < y_bottom) {
+            x_bottom = x0 + dx * (e->ey - y_top);
+            sy1 = e->ey;
+         } else {
+            x_bottom = xb;
+            sy1 = y_bottom;
+         }
+
+         if (x_top >= 0 && x_bottom >= 0 && x_top < len && x_bottom < len) {
+            // from here on, we don't have to range check x values
+
+            if ((int) x_top == (int) x_bottom) {
+               float height;
+               // simple case, only spans one pixel
+               int x = (int) x_top;
+               height = sy1 - sy0;
+               STBTT_assert(x >= 0 && x < len);
+               scanline[x] += e->direction * (1-((x_top - x) + (x_bottom-x))/2)  * height;
+               scanline_fill[x] += e->direction * height; // everything right of this pixel is filled
+            } else {
+               int x,x1,x2;
+               float y_crossing, step, sign, area;
+               // covers 2+ pixels
+               if (x_top > x_bottom) {
+                  // flip scanline vertically; signed area is the same
+                  float t;
+                  sy0 = y_bottom - (sy0 - y_top);
+                  sy1 = y_bottom - (sy1 - y_top);
+                  t = sy0, sy0 = sy1, sy1 = t;
+                  t = x_bottom, x_bottom = x_top, x_top = t;
+                  dx = -dx;
+                  dy = -dy;
+                  t = x0, x0 = xb, xb = t;
+               }
+
+               x1 = (int) x_top;
+               x2 = (int) x_bottom;
+               // compute intersection with y axis at x1+1
+               y_crossing = (x1+1 - x0) * dy + y_top;
+
+               sign = e->direction;
+               // area of the rectangle covered from y0..y_crossing
+               area = sign * (y_crossing-sy0);
+               // area of the triangle (x_top,y0), (x+1,y0), (x+1,y_crossing)
+               scanline[x1] += area * (1-((x_top - x1)+(x1+1-x1))/2);
+
+               step = sign * dy;
+               for (x = x1+1; x < x2; ++x) {
+                  scanline[x] += area + step/2;
+                  area += step;
+               }
+               y_crossing += dy * (x2 - (x1+1));
+
+               STBTT_assert(STBTT_fabs(area) <= 1.01f);
+
+               scanline[x2] += area + sign * (1-((x2-x2)+(x_bottom-x2))/2) * (sy1-y_crossing);
+
+               scanline_fill[x2] += sign * (sy1-sy0);
+            }
+         } else {
+            // if edge goes outside of box we're drawing, we require
+            // clipping logic. since this does not match the intended use
+            // of this library, we use a different, very slow brute
+            // force implementation
+            int x;
+            for (x=0; x < len; ++x) {
+               // cases:
+               //
+               // there can be up to two intersections with the pixel. any intersection
+               // with left or right edges can be handled by splitting into two (or three)
+               // regions. intersections with top & bottom do not necessitate case-wise logic.
+               //
+               // the old way of doing this found the intersections with the left & right edges,
+               // then used some simple logic to produce up to three segments in sorted order
+               // from top-to-bottom. however, this had a problem: if an x edge was epsilon
+               // across the x border, then the corresponding y position might not be distinct
+               // from the other y segment, and it might ignored as an empty segment. to avoid
+               // that, we need to explicitly produce segments based on x positions.
+
+               // rename variables to clearly-defined pairs
+               float y0 = y_top;
+               float x1 = (float) (x);
+               float x2 = (float) (x+1);
+               float x3 = xb;
+               float y3 = y_bottom;
+
+               // x = e->x + e->dx * (y-y_top)
+               // (y-y_top) = (x - e->x) / e->dx
+               // y = (x - e->x) / e->dx + y_top
+               float y1 = (x - x0) / dx + y_top;
+               float y2 = (x+1 - x0) / dx + y_top;
+
+               if (x0 < x1 && x3 > x2) {         // three segments descending down-right
+                  stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1);
+                  stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x2,y2);
+                  stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3);
+               } else if (x3 < x1 && x0 > x2) {  // three segments descending down-left
+                  stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2);
+                  stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x1,y1);
+                  stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3);
+               } else if (x0 < x1 && x3 > x1) {  // two segments across x, down-right
+                  stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1);
+                  stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3);
+               } else if (x3 < x1 && x0 > x1) {  // two segments across x, down-left
+                  stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1);
+                  stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3);
+               } else if (x0 < x2 && x3 > x2) {  // two segments across x+1, down-right
+                  stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2);
+                  stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3);
+               } else if (x3 < x2 && x0 > x2) {  // two segments across x+1, down-left
+                  stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2);
+                  stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3);
+               } else {  // one segment
+                  stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x3,y3);
+               }
+            }
+         }
+      }
+      e = e->next;
+   }
+}
+
+// directly AA rasterize edges w/o supersampling
+static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata)
+{
+   stbtt__hheap hh = { 0, 0, 0 };
+   stbtt__active_edge *active = NULL;
+   int y,j=0, i;
+   float scanline_data[129], *scanline, *scanline2;
+
+   STBTT__NOTUSED(vsubsample);
+
+   if (result->w > 64)
+      scanline = (float *) STBTT_malloc((result->w*2+1) * sizeof(float), userdata);
+   else
+      scanline = scanline_data;
+
+   scanline2 = scanline + result->w;
+
+   y = off_y;
+   e[n].y0 = (float) (off_y + result->h) + 1;
+
+   while (j < result->h) {
+      // find center of pixel for this scanline
+      float scan_y_top    = y + 0.0f;
+      float scan_y_bottom = y + 1.0f;
+      stbtt__active_edge **step = &active;
+
+      STBTT_memset(scanline , 0, result->w*sizeof(scanline[0]));
+      STBTT_memset(scanline2, 0, (result->w+1)*sizeof(scanline[0]));
+
+      // update all active edges;
+      // remove all active edges that terminate before the top of this scanline
+      while (*step) {
+         stbtt__active_edge * z = *step;
+         if (z->ey <= scan_y_top) {
+            *step = z->next; // delete from list
+            STBTT_assert(z->direction);
+            z->direction = 0;
+            stbtt__hheap_free(&hh, z);
+         } else {
+            step = &((*step)->next); // advance through list
+         }
+      }
+
+      // insert all edges that start before the bottom of this scanline
+      while (e->y0 <= scan_y_bottom) {
+         if (e->y0 != e->y1) {
+            stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y_top, userdata);
+            if (z != NULL) {
+               STBTT_assert(z->ey >= scan_y_top);
+               // insert at front
+               z->next = active;
+               active = z;
+            }
+         }
+         ++e;
+      }
+
+      // now process all active edges
+      if (active)
+         stbtt__fill_active_edges_new(scanline, scanline2+1, result->w, active, scan_y_top);
+
+      {
+         float sum = 0;
+         for (i=0; i < result->w; ++i) {
+            float k;
+            int m;
+            sum += scanline2[i];
+            k = scanline[i] + sum;
+            k = (float) STBTT_fabs(k)*255 + 0.5f;
+            m = (int) k;
+            if (m > 255) m = 255;
+            result->pixels[j*result->stride + i] = (unsigned char) m;
+         }
+      }
+      // advance all the edges
+      step = &active;
+      while (*step) {
+         stbtt__active_edge *z = *step;
+         z->fx += z->fdx; // advance to position for current scanline
+         step = &((*step)->next); // advance through list
+      }
+
+      ++y;
+      ++j;
+   }
+
+   stbtt__hheap_cleanup(&hh, userdata);
+
+   if (scanline != scanline_data)
+      STBTT_free(scanline, userdata);
+}
+#else
+#error "Unrecognized value of STBTT_RASTERIZER_VERSION"
+#endif
+
+#define STBTT__COMPARE(a,b)  ((a)->y0 < (b)->y0)
+
+static void stbtt__sort_edges_ins_sort(stbtt__edge *p, int n)
+{
+   int i,j;
+   for (i=1; i < n; ++i) {
+      stbtt__edge t = p[i], *a = &t;
+      j = i;
+      while (j > 0) {
+         stbtt__edge *b = &p[j-1];
+         int c = STBTT__COMPARE(a,b);
+         if (!c) break;
+         p[j] = p[j-1];
+         --j;
+      }
+      if (i != j)
+         p[j] = t;
+   }
+}
+
+static void stbtt__sort_edges_quicksort(stbtt__edge *p, int n)
+{
+   /* threshhold for transitioning to insertion sort */
+   while (n > 12) {
+      stbtt__edge t;
+      int c01,c12,c,m,i,j;
+
+      /* compute median of three */
+      m = n >> 1;
+      c01 = STBTT__COMPARE(&p[0],&p[m]);
+      c12 = STBTT__COMPARE(&p[m],&p[n-1]);
+      /* if 0 >= mid >= end, or 0 < mid < end, then use mid */
+      if (c01 != c12) {
+         /* otherwise, we'll need to swap something else to middle */
+         int z;
+         c = STBTT__COMPARE(&p[0],&p[n-1]);
+         /* 0>mid && mid<n:  0>n => n; 0<n => 0 */
+         /* 0<mid && mid>n:  0>n => 0; 0<n => n */
+         z = (c == c12) ? 0 : n-1;
+         t = p[z];
+         p[z] = p[m];
+         p[m] = t;
+      }
+      /* now p[m] is the median-of-three */
+      /* swap it to the beginning so it won't move around */
+      t = p[0];
+      p[0] = p[m];
+      p[m] = t;
+
+      /* partition loop */
+      i=1;
+      j=n-1;
+      for(;;) {
+         /* handling of equality is crucial here */
+         /* for sentinels & efficiency with duplicates */
+         for (;;++i) {
+            if (!STBTT__COMPARE(&p[i], &p[0])) break;
+         }
+         for (;;--j) {
+            if (!STBTT__COMPARE(&p[0], &p[j])) break;
+         }
+         /* make sure we haven't crossed */
+         if (i >= j) break;
+         t = p[i];
+         p[i] = p[j];
+         p[j] = t;
+
+         ++i;
+         --j;
+      }
+      /* recurse on smaller side, iterate on larger */
+      if (j < (n-i)) {
+         stbtt__sort_edges_quicksort(p,j);
+         p = p+i;
+         n = n-i;
+      } else {
+         stbtt__sort_edges_quicksort(p+i, n-i);
+         n = j;
+      }
+   }
+}
+
+static void stbtt__sort_edges(stbtt__edge *p, int n)
+{
+   stbtt__sort_edges_quicksort(p, n);
+   stbtt__sort_edges_ins_sort(p, n);
+}
+
+typedef struct
+{
+   float x,y;
+} stbtt__point;
+
+static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, float shift_x, float shift_y, int off_x, int off_y, int invert, void *userdata)
+{
+   float y_scale_inv = invert ? -scale_y : scale_y;
+   stbtt__edge *e;
+   int n,i,j,k,m;
+#if STBTT_RASTERIZER_VERSION == 1
+   int vsubsample = result->h < 8 ? 15 : 5;
+#elif STBTT_RASTERIZER_VERSION == 2
+   int vsubsample = 1;
+#else
+   #error "Unrecognized value of STBTT_RASTERIZER_VERSION"
+#endif
+   // vsubsample should divide 255 evenly; otherwise we won't reach full opacity
+
+   // now we have to blow out the windings into explicit edge lists
+   n = 0;
+   for (i=0; i < windings; ++i)
+      n += wcount[i];
+
+   e = (stbtt__edge *) STBTT_malloc(sizeof(*e) * (n+1), userdata); // add an extra one as a sentinel
+   if (e == 0) return;
+   n = 0;
+
+   m=0;
+   for (i=0; i < windings; ++i) {
+      stbtt__point *p = pts + m;
+      m += wcount[i];
+      j = wcount[i]-1;
+      for (k=0; k < wcount[i]; j=k++) {
+         int a=k,b=j;
+         // skip the edge if horizontal
+         if (p[j].y == p[k].y)
+            continue;
+         // add edge from j to k to the list
+         e[n].invert = 0;
+         if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) {
+            e[n].invert = 1;
+            a=j,b=k;
+         }
+         e[n].x0 = p[a].x * scale_x + shift_x;
+         e[n].y0 = (p[a].y * y_scale_inv + shift_y) * vsubsample;
+         e[n].x1 = p[b].x * scale_x + shift_x;
+         e[n].y1 = (p[b].y * y_scale_inv + shift_y) * vsubsample;
+         ++n;
+      }
+   }
+
+   // now sort the edges by their highest point (should snap to integer, and then by x)
+   //STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare);
+   stbtt__sort_edges(e, n);
+
+   // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule
+   stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata);
+
+   STBTT_free(e, userdata);
+}
+
+static void stbtt__add_point(stbtt__point *points, int n, float x, float y)
+{
+   if (!points) return; // during first pass, it's unallocated
+   points[n].x = x;
+   points[n].y = y;
+}
+
+// tesselate until threshhold p is happy... @TODO warped to compensate for non-linear stretching
+static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n)
+{
+   // midpoint
+   float mx = (x0 + 2*x1 + x2)/4;
+   float my = (y0 + 2*y1 + y2)/4;
+   // versus directly drawn line
+   float dx = (x0+x2)/2 - mx;
+   float dy = (y0+y2)/2 - my;
+   if (n > 16) // 65536 segments on one curve better be enough!
+      return 1;
+   if (dx*dx+dy*dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA
+      stbtt__tesselate_curve(points, num_points, x0,y0, (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1);
+      stbtt__tesselate_curve(points, num_points, mx,my, (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1);
+   } else {
+      stbtt__add_point(points, *num_points,x2,y2);
+      *num_points = *num_points+1;
+   }
+   return 1;
+}
+
+static void stbtt__tesselate_cubic(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3, float objspace_flatness_squared, int n)
+{
+   // @TODO this "flatness" calculation is just made-up nonsense that seems to work well enough
+   float dx0 = x1-x0;
+   float dy0 = y1-y0;
+   float dx1 = x2-x1;
+   float dy1 = y2-y1;
+   float dx2 = x3-x2;
+   float dy2 = y3-y2;
+   float dx = x3-x0;
+   float dy = y3-y0;
+   float longlen = (float) (STBTT_sqrt(dx0*dx0+dy0*dy0)+STBTT_sqrt(dx1*dx1+dy1*dy1)+STBTT_sqrt(dx2*dx2+dy2*dy2));
+   float shortlen = (float) STBTT_sqrt(dx*dx+dy*dy);
+   float flatness_squared = longlen*longlen-shortlen*shortlen;
+
+   if (n > 16) // 65536 segments on one curve better be enough!
+      return;
+
+   if (flatness_squared > objspace_flatness_squared) {
+      float x01 = (x0+x1)/2;
+      float y01 = (y0+y1)/2;
+      float x12 = (x1+x2)/2;
+      float y12 = (y1+y2)/2;
+      float x23 = (x2+x3)/2;
+      float y23 = (y2+y3)/2;
+
+      float xa = (x01+x12)/2;
+      float ya = (y01+y12)/2;
+      float xb = (x12+x23)/2;
+      float yb = (y12+y23)/2;
+
+      float mx = (xa+xb)/2;
+      float my = (ya+yb)/2;
+
+      stbtt__tesselate_cubic(points, num_points, x0,y0, x01,y01, xa,ya, mx,my, objspace_flatness_squared,n+1);
+      stbtt__tesselate_cubic(points, num_points, mx,my, xb,yb, x23,y23, x3,y3, objspace_flatness_squared,n+1);
+   } else {
+      stbtt__add_point(points, *num_points,x3,y3);
+      *num_points = *num_points+1;
+   }
+}
+
+// returns number of contours
+static stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata)
+{
+   stbtt__point *points=0;
+   int num_points=0;
+
+   float objspace_flatness_squared = objspace_flatness * objspace_flatness;
+   int i,n=0,start=0, pass;
+
+   // count how many "moves" there are to get the contour count
+   for (i=0; i < num_verts; ++i)
+      if (vertices[i].type == STBTT_vmove)
+         ++n;
+
+   *num_contours = n;
+   if (n == 0) return 0;
+
+   *contour_lengths = (int *) STBTT_malloc(sizeof(**contour_lengths) * n, userdata);
+
+   if (*contour_lengths == 0) {
+      *num_contours = 0;
+      return 0;
+   }
+
+   // make two passes through the points so we don't need to realloc
+   for (pass=0; pass < 2; ++pass) {
+      float x=0,y=0;
+      if (pass == 1) {
+         points = (stbtt__point *) STBTT_malloc(num_points * sizeof(points[0]), userdata);
+         if (points == NULL) goto error;
+      }
+      num_points = 0;
+      n= -1;
+      for (i=0; i < num_verts; ++i) {
+         switch (vertices[i].type) {
+            case STBTT_vmove:
+               // start the next contour
+               if (n >= 0)
+                  (*contour_lengths)[n] = num_points - start;
+               ++n;
+               start = num_points;
+
+               x = vertices[i].x, y = vertices[i].y;
+               stbtt__add_point(points, num_points++, x,y);
+               break;
+            case STBTT_vline:
+               x = vertices[i].x, y = vertices[i].y;
+               stbtt__add_point(points, num_points++, x, y);
+               break;
+            case STBTT_vcurve:
+               stbtt__tesselate_curve(points, &num_points, x,y,
+                                        vertices[i].cx, vertices[i].cy,
+                                        vertices[i].x,  vertices[i].y,
+                                        objspace_flatness_squared, 0);
+               x = vertices[i].x, y = vertices[i].y;
+               break;
+            case STBTT_vcubic:
+               stbtt__tesselate_cubic(points, &num_points, x,y,
+                                        vertices[i].cx, vertices[i].cy,
+                                        vertices[i].cx1, vertices[i].cy1,
+                                        vertices[i].x,  vertices[i].y,
+                                        objspace_flatness_squared, 0);
+               x = vertices[i].x, y = vertices[i].y;
+               break;
+         }
+      }
+      (*contour_lengths)[n] = num_points - start;
+   }
+
+   return points;
+error:
+   STBTT_free(points, userdata);
+   STBTT_free(*contour_lengths, userdata);
+   *contour_lengths = 0;
+   *num_contours = 0;
+   return NULL;
+}
+
+STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata)
+{
+   float scale            = scale_x > scale_y ? scale_y : scale_x;
+   int winding_count      = 0;
+   int *winding_lengths   = NULL;
+   stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata);
+   if (windings) {
+      stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert, userdata);
+      STBTT_free(winding_lengths, userdata);
+      STBTT_free(windings, userdata);
+   }
+}
+
+STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata)
+{
+   STBTT_free(bitmap, userdata);
+}
+
+STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff)
+{
+   int ix0,iy0,ix1,iy1;
+   stbtt__bitmap gbm;
+   stbtt_vertex *vertices;   
+   int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices);
+
+   if (scale_x == 0) scale_x = scale_y;
+   if (scale_y == 0) {
+      if (scale_x == 0) {
+         STBTT_free(vertices, info->userdata);
+         return NULL;
+      }
+      scale_y = scale_x;
+   }
+
+   stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,&ix1,&iy1);
+
+   // now we get the size
+   gbm.w = (ix1 - ix0);
+   gbm.h = (iy1 - iy0);
+   gbm.pixels = NULL; // in case we error
+
+   if (width ) *width  = gbm.w;
+   if (height) *height = gbm.h;
+   if (xoff  ) *xoff   = ix0;
+   if (yoff  ) *yoff   = iy0;
+   
+   if (gbm.w && gbm.h) {
+      gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata);
+      if (gbm.pixels) {
+         gbm.stride = gbm.w;
+
+         stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata);
+      }
+   }
+   STBTT_free(vertices, info->userdata);
+   return gbm.pixels;
+}   
+
+STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff)
+{
+   return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff);
+}
+
+STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph)
+{
+   int ix0,iy0;
+   stbtt_vertex *vertices;
+   int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices);
+   stbtt__bitmap gbm;   
+
+   stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0);
+   gbm.pixels = output;
+   gbm.w = out_w;
+   gbm.h = out_h;
+   gbm.stride = out_stride;
+
+   if (gbm.w && gbm.h)
+      stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0,iy0, 1, info->userdata);
+
+   STBTT_free(vertices, info->userdata);
+}
+
+STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph)
+{
+   stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, glyph);
+}
+
+STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff)
+{
+   return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff);
+}   
+
+STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint)
+{
+   stbtt_MakeGlyphBitmapSubpixelPrefilter(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, oversample_x, oversample_y, sub_x, sub_y, stbtt_FindGlyphIndex(info,codepoint));
+}
+
+STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint)
+{
+   stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(info,codepoint));
+}
+
+STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff)
+{
+   return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff);
+}   
+
+STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint)
+{
+   stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, codepoint);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// bitmap baking
+//
+// This is SUPER-CRAPPY packing to keep source code small
+
+static int stbtt_BakeFontBitmap_internal(unsigned char *data, int offset,  // font location (use offset=0 for plain .ttf)
+                                float pixel_height,                     // height of font in pixels
+                                unsigned char *pixels, int pw, int ph,  // bitmap to be filled in
+                                int first_char, int num_chars,          // characters to bake
+                                stbtt_bakedchar *chardata)
+{
+   float scale;
+   int x,y,bottom_y, i;
+   stbtt_fontinfo f;
+   f.userdata = NULL;
+   if (!stbtt_InitFont(&f, data, offset))
+      return -1;
+   STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels
+   x=y=1;
+   bottom_y = 1;
+
+   scale = stbtt_ScaleForPixelHeight(&f, pixel_height);
+
+   for (i=0; i < num_chars; ++i) {
+      int advance, lsb, x0,y0,x1,y1,gw,gh;
+      int g = stbtt_FindGlyphIndex(&f, first_char + i);
+      stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb);
+      stbtt_GetGlyphBitmapBox(&f, g, scale,scale, &x0,&y0,&x1,&y1);
+      gw = x1-x0;
+      gh = y1-y0;
+      if (x + gw + 1 >= pw)
+         y = bottom_y, x = 1; // advance to next row
+      if (y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row
+         return -i;
+      STBTT_assert(x+gw < pw);
+      STBTT_assert(y+gh < ph);
+      stbtt_MakeGlyphBitmap(&f, pixels+x+y*pw, gw,gh,pw, scale,scale, g);
+      chardata[i].x0 = (stbtt_int16) x;
+      chardata[i].y0 = (stbtt_int16) y;
+      chardata[i].x1 = (stbtt_int16) (x + gw);
+      chardata[i].y1 = (stbtt_int16) (y + gh);
+      chardata[i].xadvance = scale * advance;
+      chardata[i].xoff     = (float) x0;
+      chardata[i].yoff     = (float) y0;
+      x = x + gw + 1;
+      if (y+gh+1 > bottom_y)
+         bottom_y = y+gh+1;
+   }
+   return bottom_y;
+}
+
+STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule)
+{
+   float d3d_bias = opengl_fillrule ? 0 : -0.5f;
+   float ipw = 1.0f / pw, iph = 1.0f / ph;
+   const stbtt_bakedchar *b = chardata + char_index;
+   int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5f);
+   int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5f);
+
+   q->x0 = round_x + d3d_bias;
+   q->y0 = round_y + d3d_bias;
+   q->x1 = round_x + b->x1 - b->x0 + d3d_bias;
+   q->y1 = round_y + b->y1 - b->y0 + d3d_bias;
+
+   q->s0 = b->x0 * ipw;
+   q->t0 = b->y0 * iph;
+   q->s1 = b->x1 * ipw;
+   q->t1 = b->y1 * iph;
+
+   *xpos += b->xadvance;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// rectangle packing replacement routines if you don't have stb_rect_pack.h
+//
+
+#ifndef STB_RECT_PACK_VERSION
+
+typedef int stbrp_coord;
+
+////////////////////////////////////////////////////////////////////////////////////
+//                                                                                //
+//                                                                                //
+// COMPILER WARNING ?!?!?                                                         //
+//                                                                                //
+//                                                                                //
+// if you get a compile warning due to these symbols being defined more than      //
+// once, move #include "stb_rect_pack.h" before #include "stb_truetype.h"         //
+//                                                                                //
+////////////////////////////////////////////////////////////////////////////////////
+
+typedef struct
+{
+   int width,height;
+   int x,y,bottom_y;
+} stbrp_context;
+
+typedef struct
+{
+   unsigned char x;
+} stbrp_node;
+
+struct stbrp_rect
+{
+   stbrp_coord x,y;
+   int id,w,h,was_packed;
+};
+
+static void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *nodes, int num_nodes)
+{
+   con->width  = pw;
+   con->height = ph;
+   con->x = 0;
+   con->y = 0;
+   con->bottom_y = 0;
+   STBTT__NOTUSED(nodes);
+   STBTT__NOTUSED(num_nodes);   
+}
+
+static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects)
+{
+   int i;
+   for (i=0; i < num_rects; ++i) {
+      if (con->x + rects[i].w > con->width) {
+         con->x = 0;
+         con->y = con->bottom_y;
+      }
+      if (con->y + rects[i].h > con->height)
+         break;
+      rects[i].x = con->x;
+      rects[i].y = con->y;
+      rects[i].was_packed = 1;
+      con->x += rects[i].w;
+      if (con->y + rects[i].h > con->bottom_y)
+         con->bottom_y = con->y + rects[i].h;
+   }
+   for (   ; i < num_rects; ++i)
+      rects[i].was_packed = 0;
+}
+#endif
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// bitmap baking
+//
+// This is SUPER-AWESOME (tm Ryan Gordon) packing using stb_rect_pack.h. If
+// stb_rect_pack.h isn't available, it uses the BakeFontBitmap strategy.
+
+STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int pw, int ph, int stride_in_bytes, int padding, void *alloc_context)
+{
+   stbrp_context *context = (stbrp_context *) STBTT_malloc(sizeof(*context)            ,alloc_context);
+   int            num_nodes = pw - padding;
+   stbrp_node    *nodes   = (stbrp_node    *) STBTT_malloc(sizeof(*nodes  ) * num_nodes,alloc_context);
+
+   if (context == NULL || nodes == NULL) {
+      if (context != NULL) STBTT_free(context, alloc_context);
+      if (nodes   != NULL) STBTT_free(nodes  , alloc_context);
+      return 0;
+   }
+
+   spc->user_allocator_context = alloc_context;
+   spc->width = pw;
+   spc->height = ph;
+   spc->pixels = pixels;
+   spc->pack_info = context;
+   spc->nodes = nodes;
+   spc->padding = padding;
+   spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw;
+   spc->h_oversample = 1;
+   spc->v_oversample = 1;
+
+   stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes);
+
+   if (pixels)
+      STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels
+
+   return 1;
+}
+
+STBTT_DEF void stbtt_PackEnd  (stbtt_pack_context *spc)
+{
+   STBTT_free(spc->nodes    , spc->user_allocator_context);
+   STBTT_free(spc->pack_info, spc->user_allocator_context);
+}
+
+STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample)
+{
+   STBTT_assert(h_oversample <= STBTT_MAX_OVERSAMPLE);
+   STBTT_assert(v_oversample <= STBTT_MAX_OVERSAMPLE);
+   if (h_oversample <= STBTT_MAX_OVERSAMPLE)
+      spc->h_oversample = h_oversample;
+   if (v_oversample <= STBTT_MAX_OVERSAMPLE)
+      spc->v_oversample = v_oversample;
+}
+
+#define STBTT__OVER_MASK  (STBTT_MAX_OVERSAMPLE-1)
+
+static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width)
+{
+   unsigned char buffer[STBTT_MAX_OVERSAMPLE];
+   int safe_w = w - kernel_width;
+   int j;
+   STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze
+   for (j=0; j < h; ++j) {
+      int i;
+      unsigned int total;
+      STBTT_memset(buffer, 0, kernel_width);
+
+      total = 0;
+
+      // make kernel_width a constant in common cases so compiler can optimize out the divide
+      switch (kernel_width) {
+         case 2:
+            for (i=0; i <= safe_w; ++i) {
+               total += pixels[i] - buffer[i & STBTT__OVER_MASK];
+               buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i];
+               pixels[i] = (unsigned char) (total / 2);
+            }
+            break;
+         case 3:
+            for (i=0; i <= safe_w; ++i) {
+               total += pixels[i] - buffer[i & STBTT__OVER_MASK];
+               buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i];
+               pixels[i] = (unsigned char) (total / 3);
+            }
+            break;
+         case 4:
+            for (i=0; i <= safe_w; ++i) {
+               total += pixels[i] - buffer[i & STBTT__OVER_MASK];
+               buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i];
+               pixels[i] = (unsigned char) (total / 4);
+            }
+            break;
+         case 5:
+            for (i=0; i <= safe_w; ++i) {
+               total += pixels[i] - buffer[i & STBTT__OVER_MASK];
+               buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i];
+               pixels[i] = (unsigned char) (total / 5);
+            }
+            break;
+         default:
+            for (i=0; i <= safe_w; ++i) {
+               total += pixels[i] - buffer[i & STBTT__OVER_MASK];
+               buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i];
+               pixels[i] = (unsigned char) (total / kernel_width);
+            }
+            break;
+      }
+
+      for (; i < w; ++i) {
+         STBTT_assert(pixels[i] == 0);
+         total -= buffer[i & STBTT__OVER_MASK];
+         pixels[i] = (unsigned char) (total / kernel_width);
+      }
+
+      pixels += stride_in_bytes;
+   }
+}
+
+static void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width)
+{
+   unsigned char buffer[STBTT_MAX_OVERSAMPLE];
+   int safe_h = h - kernel_width;
+   int j;
+   STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze
+   for (j=0; j < w; ++j) {
+      int i;
+      unsigned int total;
+      STBTT_memset(buffer, 0, kernel_width);
+
+      total = 0;
+
+      // make kernel_width a constant in common cases so compiler can optimize out the divide
+      switch (kernel_width) {
+         case 2:
+            for (i=0; i <= safe_h; ++i) {
+               total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK];
+               buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes];
+               pixels[i*stride_in_bytes] = (unsigned char) (total / 2);
+            }
+            break;
+         case 3:
+            for (i=0; i <= safe_h; ++i) {
+               total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK];
+               buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes];
+               pixels[i*stride_in_bytes] = (unsigned char) (total / 3);
+            }
+            break;
+         case 4:
+            for (i=0; i <= safe_h; ++i) {
+               total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK];
+               buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes];
+               pixels[i*stride_in_bytes] = (unsigned char) (total / 4);
+            }
+            break;
+         case 5:
+            for (i=0; i <= safe_h; ++i) {
+               total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK];
+               buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes];
+               pixels[i*stride_in_bytes] = (unsigned char) (total / 5);
+            }
+            break;
+         default:
+            for (i=0; i <= safe_h; ++i) {
+               total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK];
+               buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes];
+               pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width);
+            }
+            break;
+      }
+
+      for (; i < h; ++i) {
+         STBTT_assert(pixels[i*stride_in_bytes] == 0);
+         total -= buffer[i & STBTT__OVER_MASK];
+         pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width);
+      }
+
+      pixels += 1;
+   }
+}
+
+static float stbtt__oversample_shift(int oversample)
+{
+   if (!oversample)
+      return 0.0f;
+
+   // The prefilter is a box filter of width "oversample",
+   // which shifts phase by (oversample - 1)/2 pixels in
+   // oversampled space. We want to shift in the opposite
+   // direction to counter this.
+   return (float)-(oversample - 1) / (2.0f * (float)oversample);
+}
+
+// rects array must be big enough to accommodate all characters in the given ranges
+STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects)
+{
+   int i,j,k;
+
+   k=0;
+   for (i=0; i < num_ranges; ++i) {
+      float fh = ranges[i].font_size;
+      float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh);
+      ranges[i].h_oversample = (unsigned char) spc->h_oversample;
+      ranges[i].v_oversample = (unsigned char) spc->v_oversample;
+      for (j=0; j < ranges[i].num_chars; ++j) {
+         int x0,y0,x1,y1;
+         int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j];
+         int glyph = stbtt_FindGlyphIndex(info, codepoint);
+         stbtt_GetGlyphBitmapBoxSubpixel(info,glyph,
+                                         scale * spc->h_oversample,
+                                         scale * spc->v_oversample,
+                                         0,0,
+                                         &x0,&y0,&x1,&y1);
+         rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1);
+         rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1);
+         ++k;
+      }
+   }
+
+   return k;
+}
+
+STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int prefilter_x, int prefilter_y, float *sub_x, float *sub_y, int glyph)
+{
+   stbtt_MakeGlyphBitmapSubpixel(info,
+                                 output,
+                                 out_w - (prefilter_x - 1),
+                                 out_h - (prefilter_y - 1),
+                                 out_stride,
+                                 scale_x,
+                                 scale_y,
+                                 shift_x,
+                                 shift_y,
+                                 glyph);
+
+   if (prefilter_x > 1)
+      stbtt__h_prefilter(output, out_w, out_h, out_stride, prefilter_x);
+
+   if (prefilter_y > 1)
+      stbtt__v_prefilter(output, out_w, out_h, out_stride, prefilter_y);
+
+   *sub_x = stbtt__oversample_shift(prefilter_x);
+   *sub_y = stbtt__oversample_shift(prefilter_y);
+}
+
+// rects array must be big enough to accommodate all characters in the given ranges
+STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects)
+{
+   int i,j,k, return_value = 1;
+
+   // save current values
+   int old_h_over = spc->h_oversample;
+   int old_v_over = spc->v_oversample;
+
+   k = 0;
+   for (i=0; i < num_ranges; ++i) {
+      float fh = ranges[i].font_size;
+      float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh);
+      float recip_h,recip_v,sub_x,sub_y;
+      spc->h_oversample = ranges[i].h_oversample;
+      spc->v_oversample = ranges[i].v_oversample;
+      recip_h = 1.0f / spc->h_oversample;
+      recip_v = 1.0f / spc->v_oversample;
+      sub_x = stbtt__oversample_shift(spc->h_oversample);
+      sub_y = stbtt__oversample_shift(spc->v_oversample);
+      for (j=0; j < ranges[i].num_chars; ++j) {
+         stbrp_rect *r = &rects[k];
+         if (r->was_packed) {
+            stbtt_packedchar *bc = &ranges[i].chardata_for_range[j];
+            int advance, lsb, x0,y0,x1,y1;
+            int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j];
+            int glyph = stbtt_FindGlyphIndex(info, codepoint);
+            stbrp_coord pad = (stbrp_coord) spc->padding;
+
+            // pad on left and top
+            r->x += pad;
+            r->y += pad;
+            r->w -= pad;
+            r->h -= pad;
+            stbtt_GetGlyphHMetrics(info, glyph, &advance, &lsb);
+            stbtt_GetGlyphBitmapBox(info, glyph,
+                                    scale * spc->h_oversample,
+                                    scale * spc->v_oversample,
+                                    &x0,&y0,&x1,&y1);
+            stbtt_MakeGlyphBitmapSubpixel(info,
+                                          spc->pixels + r->x + r->y*spc->stride_in_bytes,
+                                          r->w - spc->h_oversample+1,
+                                          r->h - spc->v_oversample+1,
+                                          spc->stride_in_bytes,
+                                          scale * spc->h_oversample,
+                                          scale * spc->v_oversample,
+                                          0,0,
+                                          glyph);
+
+            if (spc->h_oversample > 1)
+               stbtt__h_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes,
+                                  r->w, r->h, spc->stride_in_bytes,
+                                  spc->h_oversample);
+
+            if (spc->v_oversample > 1)
+               stbtt__v_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes,
+                                  r->w, r->h, spc->stride_in_bytes,
+                                  spc->v_oversample);
+
+            bc->x0       = (stbtt_int16)  r->x;
+            bc->y0       = (stbtt_int16)  r->y;
+            bc->x1       = (stbtt_int16) (r->x + r->w);
+            bc->y1       = (stbtt_int16) (r->y + r->h);
+            bc->xadvance =                scale * advance;
+            bc->xoff     =       (float)  x0 * recip_h + sub_x;
+            bc->yoff     =       (float)  y0 * recip_v + sub_y;
+            bc->xoff2    =                (x0 + r->w) * recip_h + sub_x;
+            bc->yoff2    =                (y0 + r->h) * recip_v + sub_y;
+         } else {
+            return_value = 0; // if any fail, report failure
+         }
+
+         ++k;
+      }
+   }
+
+   // restore original values
+   spc->h_oversample = old_h_over;
+   spc->v_oversample = old_v_over;
+
+   return return_value;
+}
+
+STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects)
+{
+   stbrp_pack_rects((stbrp_context *) spc->pack_info, rects, num_rects);
+}
+
+STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges)
+{
+   stbtt_fontinfo info;
+   int i,j,n, return_value = 1;
+   //stbrp_context *context = (stbrp_context *) spc->pack_info;
+   stbrp_rect    *rects;
+
+   // flag all characters as NOT packed
+   for (i=0; i < num_ranges; ++i)
+      for (j=0; j < ranges[i].num_chars; ++j)
+         ranges[i].chardata_for_range[j].x0 =
+         ranges[i].chardata_for_range[j].y0 =
+         ranges[i].chardata_for_range[j].x1 =
+         ranges[i].chardata_for_range[j].y1 = 0;
+
+   n = 0;
+   for (i=0; i < num_ranges; ++i)
+      n += ranges[i].num_chars;
+         
+   rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context);
+   if (rects == NULL)
+      return 0;
+
+   info.userdata = spc->user_allocator_context;
+   stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata,font_index));
+
+   n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects);
+
+   stbtt_PackFontRangesPackRects(spc, rects, n);
+  
+   return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects);
+
+   STBTT_free(rects, spc->user_allocator_context);
+   return return_value;
+}
+
+STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size,
+            int first_unicode_codepoint_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range)
+{
+   stbtt_pack_range range;
+   range.first_unicode_codepoint_in_range = first_unicode_codepoint_in_range;
+   range.array_of_unicode_codepoints = NULL;
+   range.num_chars                   = num_chars_in_range;
+   range.chardata_for_range          = chardata_for_range;
+   range.font_size                   = font_size;
+   return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1);
+}
+
+STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer)
+{
+   float ipw = 1.0f / pw, iph = 1.0f / ph;
+   const stbtt_packedchar *b = chardata + char_index;
+
+   if (align_to_integer) {
+      float x = (float) STBTT_ifloor((*xpos + b->xoff) + 0.5f);
+      float y = (float) STBTT_ifloor((*ypos + b->yoff) + 0.5f);
+      q->x0 = x;
+      q->y0 = y;
+      q->x1 = x + b->xoff2 - b->xoff;
+      q->y1 = y + b->yoff2 - b->yoff;
+   } else {
+      q->x0 = *xpos + b->xoff;
+      q->y0 = *ypos + b->yoff;
+      q->x1 = *xpos + b->xoff2;
+      q->y1 = *ypos + b->yoff2;
+   }
+
+   q->s0 = b->x0 * ipw;
+   q->t0 = b->y0 * iph;
+   q->s1 = b->x1 * ipw;
+   q->t1 = b->y1 * iph;
+
+   *xpos += b->xadvance;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// sdf computation
+//
+
+#define STBTT_min(a,b)  ((a) < (b) ? (a) : (b))
+#define STBTT_max(a,b)  ((a) < (b) ? (b) : (a))
+
+static int stbtt__ray_intersect_bezier(float orig[2], float ray[2], float q0[2], float q1[2], float q2[2], float hits[2][2])
+{
+   float q0perp = q0[1]*ray[0] - q0[0]*ray[1];
+   float q1perp = q1[1]*ray[0] - q1[0]*ray[1];
+   float q2perp = q2[1]*ray[0] - q2[0]*ray[1];
+   float roperp = orig[1]*ray[0] - orig[0]*ray[1];
+
+   float a = q0perp - 2*q1perp + q2perp;
+   float b = q1perp - q0perp;
+   float c = q0perp - roperp;
+
+   float s0 = 0., s1 = 0.;
+   int num_s = 0;
+
+   if (a != 0.0) {
+      float discr = b*b - a*c;
+      if (discr > 0.0) {
+         float rcpna = -1 / a;
+         float d = (float) STBTT_sqrt(discr);
+         s0 = (b+d) * rcpna;
+         s1 = (b-d) * rcpna;
+         if (s0 >= 0.0 && s0 <= 1.0)
+            num_s = 1;
+         if (d > 0.0 && s1 >= 0.0 && s1 <= 1.0) {
+            if (num_s == 0) s0 = s1;
+            ++num_s;
+         }
+      }
+   } else {
+      // 2*b*s + c = 0
+      // s = -c / (2*b)
+      s0 = c / (-2 * b);
+      if (s0 >= 0.0 && s0 <= 1.0)
+         num_s = 1;
+   }
+
+   if (num_s == 0)
+      return 0;
+   else {
+      float rcp_len2 = 1 / (ray[0]*ray[0] + ray[1]*ray[1]);
+      float rayn_x = ray[0] * rcp_len2, rayn_y = ray[1] * rcp_len2;
+
+      float q0d =   q0[0]*rayn_x +   q0[1]*rayn_y;
+      float q1d =   q1[0]*rayn_x +   q1[1]*rayn_y;
+      float q2d =   q2[0]*rayn_x +   q2[1]*rayn_y;
+      float rod = orig[0]*rayn_x + orig[1]*rayn_y;
+
+      float q10d = q1d - q0d;
+      float q20d = q2d - q0d;
+      float q0rd = q0d - rod;
+
+      hits[0][0] = q0rd + s0*(2.0f - 2.0f*s0)*q10d + s0*s0*q20d;
+      hits[0][1] = a*s0+b;
+
+      if (num_s > 1) {
+         hits[1][0] = q0rd + s1*(2.0f - 2.0f*s1)*q10d + s1*s1*q20d;
+         hits[1][1] = a*s1+b;
+         return 2;
+      } else {
+         return 1;
+      }
+   }
+}
+
+static int equal(float *a, float *b)
+{
+   return (a[0] == b[0] && a[1] == b[1]);
+}
+
+static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex *verts)
+{
+   int i;
+   float orig[2], ray[2] = { 1, 0 };
+   float y_frac;
+   int winding = 0;
+
+   orig[0] = x;
+   orig[1] = y;
+
+   // make sure y never passes through a vertex of the shape
+   y_frac = (float) STBTT_fmod(y, 1.0f);
+   if (y_frac < 0.01f)
+      y += 0.01f;
+   else if (y_frac > 0.99f)
+      y -= 0.01f;
+   orig[1] = y;
+
+   // test a ray from (-infinity,y) to (x,y)
+   for (i=0; i < nverts; ++i) {
+      if (verts[i].type == STBTT_vline) {
+         int x0 = (int) verts[i-1].x, y0 = (int) verts[i-1].y;
+         int x1 = (int) verts[i  ].x, y1 = (int) verts[i  ].y;
+         if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) {
+            float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0;
+            if (x_inter < x)  
+               winding += (y0 < y1) ? 1 : -1;
+         }
+      }
+      if (verts[i].type == STBTT_vcurve) {
+         int x0 = (int) verts[i-1].x , y0 = (int) verts[i-1].y ;
+         int x1 = (int) verts[i  ].cx, y1 = (int) verts[i  ].cy;
+         int x2 = (int) verts[i  ].x , y2 = (int) verts[i  ].y ;
+         int ax = STBTT_min(x0,STBTT_min(x1,x2)), ay = STBTT_min(y0,STBTT_min(y1,y2));
+         int by = STBTT_max(y0,STBTT_max(y1,y2));
+         if (y > ay && y < by && x > ax) {
+            float q0[2],q1[2],q2[2];
+            float hits[2][2];
+            q0[0] = (float)x0;
+            q0[1] = (float)y0;
+            q1[0] = (float)x1;
+            q1[1] = (float)y1;
+            q2[0] = (float)x2;
+            q2[1] = (float)y2;
+            if (equal(q0,q1) || equal(q1,q2)) {
+               x0 = (int)verts[i-1].x;
+               y0 = (int)verts[i-1].y;
+               x1 = (int)verts[i  ].x;
+               y1 = (int)verts[i  ].y;
+               if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) {
+                  float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0;
+                  if (x_inter < x)  
+                     winding += (y0 < y1) ? 1 : -1;
+               }
+            } else {
+               int num_hits = stbtt__ray_intersect_bezier(orig, ray, q0, q1, q2, hits);
+               if (num_hits >= 1)
+                  if (hits[0][0] < 0)
+                     winding += (hits[0][1] < 0 ? -1 : 1);
+               if (num_hits >= 2)
+                  if (hits[1][0] < 0)
+                     winding += (hits[1][1] < 0 ? -1 : 1);
+            }
+         } 
+      }
+   }
+   return winding;
+}
+
+static float stbtt__cuberoot( float x )
+{
+   if (x<0)
+      return -(float) STBTT_pow(-x,1.0f/3.0f);
+   else
+      return  (float) STBTT_pow( x,1.0f/3.0f);
+}
+
+// x^3 + c*x^2 + b*x + a = 0
+static int stbtt__solve_cubic(float a, float b, float c, float* r)
+{
+	float s = -a / 3;
+	float p = b - a*a / 3;
+	float q = a * (2*a*a - 9*b) / 27 + c;
+   float p3 = p*p*p;
+	float d = q*q + 4*p3 / 27;
+	if (d >= 0) {
+		float z = (float) STBTT_sqrt(d);
+		float u = (-q + z) / 2;
+		float v = (-q - z) / 2;
+		u = stbtt__cuberoot(u);
+		v = stbtt__cuberoot(v);
+		r[0] = s + u + v;
+		return 1;
+	} else {
+	   float u = (float) STBTT_sqrt(-p/3);
+	   float v = (float) STBTT_acos(-STBTT_sqrt(-27/p3) * q / 2) / 3; // p3 must be negative, since d is negative
+	   float m = (float) STBTT_cos(v);
+      float n = (float) STBTT_cos(v-3.141592/2)*1.732050808f;
+	   r[0] = s + u * 2 * m;
+	   r[1] = s - u * (m + n);
+	   r[2] = s - u * (m - n);
+
+      //STBTT_assert( STBTT_fabs(((r[0]+a)*r[0]+b)*r[0]+c) < 0.05f);  // these asserts may not be safe at all scales, though they're in bezier t parameter units so maybe?
+      //STBTT_assert( STBTT_fabs(((r[1]+a)*r[1]+b)*r[1]+c) < 0.05f);
+      //STBTT_assert( STBTT_fabs(((r[2]+a)*r[2]+b)*r[2]+c) < 0.05f);
+   	return 3;
+   }
+}
+
+STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff)
+{
+   float scale_x = scale, scale_y = scale;
+   int ix0,iy0,ix1,iy1;
+   int w,h;
+   unsigned char *data;
+
+   // if one scale is 0, use same scale for both
+   if (scale_x == 0) scale_x = scale_y;
+   if (scale_y == 0) {
+      if (scale_x == 0) return NULL;  // if both scales are 0, return NULL
+      scale_y = scale_x;
+   }
+
+   stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale, scale, 0.0f,0.0f, &ix0,&iy0,&ix1,&iy1);
+
+   // if empty, return NULL
+   if (ix0 == ix1 || iy0 == iy1)
+      return NULL;
+
+   ix0 -= padding;
+   iy0 -= padding;
+   ix1 += padding;
+   iy1 += padding;
+
+   w = (ix1 - ix0);
+   h = (iy1 - iy0);
+
+   if (width ) *width  = w;
+   if (height) *height = h;
+   if (xoff  ) *xoff   = ix0;
+   if (yoff  ) *yoff   = iy0;
+
+   // invert for y-downwards bitmaps
+   scale_y = -scale_y;
+      
+   {
+      int x,y,i,j;
+      float *precompute;
+      stbtt_vertex *verts;
+      int num_verts = stbtt_GetGlyphShape(info, glyph, &verts);
+      data = (unsigned char *) STBTT_malloc(w * h, info->userdata);
+      precompute = (float *) STBTT_malloc(num_verts * sizeof(float), info->userdata);
+
+      for (i=0,j=num_verts-1; i < num_verts; j=i++) {
+         if (verts[i].type == STBTT_vline) {
+            float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y;
+            float x1 = verts[j].x*scale_x, y1 = verts[j].y*scale_y;
+            float dist = (float) STBTT_sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0));
+            precompute[i] = (dist == 0) ? 0.0f : 1.0f / dist;
+         } else if (verts[i].type == STBTT_vcurve) {
+            float x2 = verts[j].x *scale_x, y2 = verts[j].y *scale_y;
+            float x1 = verts[i].cx*scale_x, y1 = verts[i].cy*scale_y;
+            float x0 = verts[i].x *scale_x, y0 = verts[i].y *scale_y;
+            float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2;
+            float len2 = bx*bx + by*by;
+            if (len2 != 0.0f)
+               precompute[i] = 1.0f / (bx*bx + by*by);
+            else
+               precompute[i] = 0.0f;
+         } else
+            precompute[i] = 0.0f;
+      }
+
+      for (y=iy0; y < iy1; ++y) {
+         for (x=ix0; x < ix1; ++x) {
+            float val;
+            float min_dist = 999999.0f;
+            float sx = (float) x + 0.5f;
+            float sy = (float) y + 0.5f;
+            float x_gspace = (sx / scale_x);
+            float y_gspace = (sy / scale_y);
+
+            int winding = stbtt__compute_crossings_x(x_gspace, y_gspace, num_verts, verts); // @OPTIMIZE: this could just be a rasterization, but needs to be line vs. non-tesselated curves so a new path
+
+            for (i=0; i < num_verts; ++i) {
+               float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y;
+
+               // check against every point here rather than inside line/curve primitives -- @TODO: wrong if multiple 'moves' in a row produce a garbage point, and given culling, probably more efficient to do within line/curve
+               float dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy);
+               if (dist2 < min_dist*min_dist)
+                  min_dist = (float) STBTT_sqrt(dist2);
+
+               if (verts[i].type == STBTT_vline) {
+                  float x1 = verts[i-1].x*scale_x, y1 = verts[i-1].y*scale_y;
+
+                  // coarse culling against bbox
+                  //if (sx > STBTT_min(x0,x1)-min_dist && sx < STBTT_max(x0,x1)+min_dist &&
+                  //    sy > STBTT_min(y0,y1)-min_dist && sy < STBTT_max(y0,y1)+min_dist)
+                  float dist = (float) STBTT_fabs((x1-x0)*(y0-sy) - (y1-y0)*(x0-sx)) * precompute[i];
+                  STBTT_assert(i != 0);
+                  if (dist < min_dist) {
+                     // check position along line
+                     // x' = x0 + t*(x1-x0), y' = y0 + t*(y1-y0)
+                     // minimize (x'-sx)*(x'-sx)+(y'-sy)*(y'-sy)
+                     float dx = x1-x0, dy = y1-y0;
+                     float px = x0-sx, py = y0-sy;
+                     // minimize (px+t*dx)^2 + (py+t*dy)^2 = px*px + 2*px*dx*t + t^2*dx*dx + py*py + 2*py*dy*t + t^2*dy*dy
+                     // derivative: 2*px*dx + 2*py*dy + (2*dx*dx+2*dy*dy)*t, set to 0 and solve
+                     float t = -(px*dx + py*dy) / (dx*dx + dy*dy);
+                     if (t >= 0.0f && t <= 1.0f)
+                        min_dist = dist;
+                  }
+               } else if (verts[i].type == STBTT_vcurve) {
+                  float x2 = verts[i-1].x *scale_x, y2 = verts[i-1].y *scale_y;
+                  float x1 = verts[i  ].cx*scale_x, y1 = verts[i  ].cy*scale_y;
+                  float box_x0 = STBTT_min(STBTT_min(x0,x1),x2);
+                  float box_y0 = STBTT_min(STBTT_min(y0,y1),y2);
+                  float box_x1 = STBTT_max(STBTT_max(x0,x1),x2);
+                  float box_y1 = STBTT_max(STBTT_max(y0,y1),y2);
+                  // coarse culling against bbox to avoid computing cubic unnecessarily
+                  if (sx > box_x0-min_dist && sx < box_x1+min_dist && sy > box_y0-min_dist && sy < box_y1+min_dist) {
+                     int num=0;
+                     float ax = x1-x0, ay = y1-y0;
+                     float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2;
+                     float mx = x0 - sx, my = y0 - sy;
+                     float res[3],px,py,t,it;
+                     float a_inv = precompute[i];
+                     if (a_inv == 0.0) { // if a_inv is 0, it's 2nd degree so use quadratic formula
+                        float a = 3*(ax*bx + ay*by);
+                        float b = 2*(ax*ax + ay*ay) + (mx*bx+my*by);
+                        float c = mx*ax+my*ay;
+                        if (a == 0.0) { // if a is 0, it's linear
+                           if (b != 0.0) {
+                              res[num++] = -c/b;
+                           }
+                        } else {
+                           float discriminant = b*b - 4*a*c;
+                           if (discriminant < 0)
+                              num = 0;
+                           else {
+                              float root = (float) STBTT_sqrt(discriminant);
+                              res[0] = (-b - root)/(2*a);
+                              res[1] = (-b + root)/(2*a);
+                              num = 2; // don't bother distinguishing 1-solution case, as code below will still work
+                           }
+                        }
+                     } else {
+                        float b = 3*(ax*bx + ay*by) * a_inv; // could precompute this as it doesn't depend on sample point
+                        float c = (2*(ax*ax + ay*ay) + (mx*bx+my*by)) * a_inv;
+                        float d = (mx*ax+my*ay) * a_inv;
+                        num = stbtt__solve_cubic(b, c, d, res);
+                     }
+                     if (num >= 1 && res[0] >= 0.0f && res[0] <= 1.0f) {
+                        t = res[0], it = 1.0f - t;
+                        px = it*it*x0 + 2*t*it*x1 + t*t*x2;
+                        py = it*it*y0 + 2*t*it*y1 + t*t*y2;
+                        dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy);
+                        if (dist2 < min_dist * min_dist)
+                           min_dist = (float) STBTT_sqrt(dist2);
+                     }
+                     if (num >= 2 && res[1] >= 0.0f && res[1] <= 1.0f) {
+                        t = res[1], it = 1.0f - t;
+                        px = it*it*x0 + 2*t*it*x1 + t*t*x2;
+                        py = it*it*y0 + 2*t*it*y1 + t*t*y2;
+                        dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy);
+                        if (dist2 < min_dist * min_dist)
+                           min_dist = (float) STBTT_sqrt(dist2);
+                     }
+                     if (num >= 3 && res[2] >= 0.0f && res[2] <= 1.0f) {
+                        t = res[2], it = 1.0f - t;
+                        px = it*it*x0 + 2*t*it*x1 + t*t*x2;
+                        py = it*it*y0 + 2*t*it*y1 + t*t*y2;
+                        dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy);
+                        if (dist2 < min_dist * min_dist)
+                           min_dist = (float) STBTT_sqrt(dist2);
+                     }
+                  }
+               }
+            }
+            if (winding == 0)
+               min_dist = -min_dist;  // if outside the shape, value is negative
+            val = onedge_value + pixel_dist_scale * min_dist;
+            if (val < 0)
+               val = 0;
+            else if (val > 255)
+               val = 255;
+            data[(y-iy0)*w+(x-ix0)] = (unsigned char) val;
+         }
+      }
+      STBTT_free(precompute, info->userdata);
+      STBTT_free(verts, info->userdata);
+   }
+   return data;
+}   
+
+STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff)
+{
+   return stbtt_GetGlyphSDF(info, scale, stbtt_FindGlyphIndex(info, codepoint), padding, onedge_value, pixel_dist_scale, width, height, xoff, yoff);
+}
+
+STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata)
+{
+   STBTT_free(bitmap, userdata);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// font name matching -- recommended not to use this
+//
+
+// check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string
+static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, stbtt_int32 len1, stbtt_uint8 *s2, stbtt_int32 len2) 
+{
+   stbtt_int32 i=0;
+
+   // convert utf16 to utf8 and compare the results while converting
+   while (len2) {
+      stbtt_uint16 ch = s2[0]*256 + s2[1];
+      if (ch < 0x80) {
+         if (i >= len1) return -1;
+         if (s1[i++] != ch) return -1;
+      } else if (ch < 0x800) {
+         if (i+1 >= len1) return -1;
+         if (s1[i++] != 0xc0 + (ch >> 6)) return -1;
+         if (s1[i++] != 0x80 + (ch & 0x3f)) return -1;
+      } else if (ch >= 0xd800 && ch < 0xdc00) {
+         stbtt_uint32 c;
+         stbtt_uint16 ch2 = s2[2]*256 + s2[3];
+         if (i+3 >= len1) return -1;
+         c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000;
+         if (s1[i++] != 0xf0 + (c >> 18)) return -1;
+         if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1;
+         if (s1[i++] != 0x80 + ((c >>  6) & 0x3f)) return -1;
+         if (s1[i++] != 0x80 + ((c      ) & 0x3f)) return -1;
+         s2 += 2; // plus another 2 below
+         len2 -= 2;
+      } else if (ch >= 0xdc00 && ch < 0xe000) {
+         return -1;
+      } else {
+         if (i+2 >= len1) return -1;
+         if (s1[i++] != 0xe0 + (ch >> 12)) return -1;
+         if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1;
+         if (s1[i++] != 0x80 + ((ch     ) & 0x3f)) return -1;
+      }
+      s2 += 2;
+      len2 -= 2;
+   }
+   return i;
+}
+
+static int stbtt_CompareUTF8toUTF16_bigendian_internal(char *s1, int len1, char *s2, int len2) 
+{
+   return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((stbtt_uint8*) s1, len1, (stbtt_uint8*) s2, len2);
+}
+
+// returns results in whatever encoding you request... but note that 2-byte encodings
+// will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare
+STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID)
+{
+   stbtt_int32 i,count,stringOffset;
+   stbtt_uint8 *fc = font->data;
+   stbtt_uint32 offset = font->fontstart;
+   stbtt_uint32 nm = stbtt__find_table(fc, offset, "name");
+   if (!nm) return NULL;
+
+   count = ttUSHORT(fc+nm+2);
+   stringOffset = nm + ttUSHORT(fc+nm+4);
+   for (i=0; i < count; ++i) {
+      stbtt_uint32 loc = nm + 6 + 12 * i;
+      if (platformID == ttUSHORT(fc+loc+0) && encodingID == ttUSHORT(fc+loc+2)
+          && languageID == ttUSHORT(fc+loc+4) && nameID == ttUSHORT(fc+loc+6)) {
+         *length = ttUSHORT(fc+loc+8);
+         return (const char *) (fc+stringOffset+ttUSHORT(fc+loc+10));
+      }
+   }
+   return NULL;
+}
+
+static int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, stbtt_int32 nlen, stbtt_int32 target_id, stbtt_int32 next_id)
+{
+   stbtt_int32 i;
+   stbtt_int32 count = ttUSHORT(fc+nm+2);
+   stbtt_int32 stringOffset = nm + ttUSHORT(fc+nm+4);
+
+   for (i=0; i < count; ++i) {
+      stbtt_uint32 loc = nm + 6 + 12 * i;
+      stbtt_int32 id = ttUSHORT(fc+loc+6);
+      if (id == target_id) {
+         // find the encoding
+         stbtt_int32 platform = ttUSHORT(fc+loc+0), encoding = ttUSHORT(fc+loc+2), language = ttUSHORT(fc+loc+4);
+
+         // is this a Unicode encoding?
+         if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) {
+            stbtt_int32 slen = ttUSHORT(fc+loc+8);
+            stbtt_int32 off = ttUSHORT(fc+loc+10);
+
+            // check if there's a prefix match
+            stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc+stringOffset+off,slen);
+            if (matchlen >= 0) {
+               // check for target_id+1 immediately following, with same encoding & language
+               if (i+1 < count && ttUSHORT(fc+loc+12+6) == next_id && ttUSHORT(fc+loc+12) == platform && ttUSHORT(fc+loc+12+2) == encoding && ttUSHORT(fc+loc+12+4) == language) {
+                  slen = ttUSHORT(fc+loc+12+8);
+                  off = ttUSHORT(fc+loc+12+10);
+                  if (slen == 0) {
+                     if (matchlen == nlen)
+                        return 1;
+                  } else if (matchlen < nlen && name[matchlen] == ' ') {
+                     ++matchlen;
+                     if (stbtt_CompareUTF8toUTF16_bigendian_internal((char*) (name+matchlen), nlen-matchlen, (char*)(fc+stringOffset+off),slen))
+                        return 1;
+                  }
+               } else {
+                  // if nothing immediately following
+                  if (matchlen == nlen)
+                     return 1;
+               }
+            }
+         }
+
+         // @TODO handle other encodings
+      }
+   }
+   return 0;
+}
+
+static int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *name, stbtt_int32 flags)
+{
+   stbtt_int32 nlen = (stbtt_int32) STBTT_strlen((char *) name);
+   stbtt_uint32 nm,hd;
+   if (!stbtt__isfont(fc+offset)) return 0;
+
+   // check italics/bold/underline flags in macStyle...
+   if (flags) {
+      hd = stbtt__find_table(fc, offset, "head");
+      if ((ttUSHORT(fc+hd+44) & 7) != (flags & 7)) return 0;
+   }
+
+   nm = stbtt__find_table(fc, offset, "name");
+   if (!nm) return 0;
+
+   if (flags) {
+      // if we checked the macStyle flags, then just check the family and ignore the subfamily
+      if (stbtt__matchpair(fc, nm, name, nlen, 16, -1))  return 1;
+      if (stbtt__matchpair(fc, nm, name, nlen,  1, -1))  return 1;
+      if (stbtt__matchpair(fc, nm, name, nlen,  3, -1))  return 1;
+   } else {
+      if (stbtt__matchpair(fc, nm, name, nlen, 16, 17))  return 1;
+      if (stbtt__matchpair(fc, nm, name, nlen,  1,  2))  return 1;
+      if (stbtt__matchpair(fc, nm, name, nlen,  3, -1))  return 1;
+   }
+
+   return 0;
+}
+
+static int stbtt_FindMatchingFont_internal(unsigned char *font_collection, char *name_utf8, stbtt_int32 flags)
+{
+   stbtt_int32 i;
+   for (i=0;;++i) {
+      stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i);
+      if (off < 0) return off;
+      if (stbtt__matches((stbtt_uint8 *) font_collection, off, (stbtt_uint8*) name_utf8, flags))
+         return off;
+   }
+}
+
+#if defined(__GNUC__) || defined(__clang__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wcast-qual"
+#endif
+
+STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset,
+                                float pixel_height, unsigned char *pixels, int pw, int ph,
+                                int first_char, int num_chars, stbtt_bakedchar *chardata)
+{
+   return stbtt_BakeFontBitmap_internal((unsigned char *) data, offset, pixel_height, pixels, pw, ph, first_char, num_chars, chardata);
+}
+
+STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index)
+{
+   return stbtt_GetFontOffsetForIndex_internal((unsigned char *) data, index);   
+}
+
+STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data)
+{
+   return stbtt_GetNumberOfFonts_internal((unsigned char *) data);
+}
+
+STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset)
+{
+   return stbtt_InitFont_internal(info, (unsigned char *) data, offset);
+}
+
+STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags)
+{
+   return stbtt_FindMatchingFont_internal((unsigned char *) fontdata, (char *) name, flags);
+}
+
+STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2)
+{
+   return stbtt_CompareUTF8toUTF16_bigendian_internal((char *) s1, len1, (char *) s2, len2);
+}
+
+#if defined(__GNUC__) || defined(__clang__)
+#pragma GCC diagnostic pop
+#endif
+
+#endif // STB_TRUETYPE_IMPLEMENTATION
+
+
+// FULL VERSION HISTORY
+//
+//   1.19 (2018-02-11) OpenType GPOS kerning (horizontal only), STBTT_fmod
+//   1.18 (2018-01-29) add missing function
+//   1.17 (2017-07-23) make more arguments const; doc fix
+//   1.16 (2017-07-12) SDF support
+//   1.15 (2017-03-03) make more arguments const
+//   1.14 (2017-01-16) num-fonts-in-TTC function
+//   1.13 (2017-01-02) support OpenType fonts, certain Apple fonts
+//   1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual
+//   1.11 (2016-04-02) fix unused-variable warning
+//   1.10 (2016-04-02) allow user-defined fabs() replacement
+//                     fix memory leak if fontsize=0.0
+//                     fix warning from duplicate typedef
+//   1.09 (2016-01-16) warning fix; avoid crash on outofmem; use alloc userdata for PackFontRanges
+//   1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges
+//   1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints;
+//                     allow PackFontRanges to pack and render in separate phases;
+//                     fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?);
+//                     fixed an assert() bug in the new rasterizer
+//                     replace assert() with STBTT_assert() in new rasterizer
+//   1.06 (2015-07-14) performance improvements (~35% faster on x86 and x64 on test machine)
+//                     also more precise AA rasterizer, except if shapes overlap
+//                     remove need for STBTT_sort
+//   1.05 (2015-04-15) fix misplaced definitions for STBTT_STATIC
+//   1.04 (2015-04-15) typo in example
+//   1.03 (2015-04-12) STBTT_STATIC, fix memory leak in new packing, various fixes
+//   1.02 (2014-12-10) fix various warnings & compile issues w/ stb_rect_pack, C++
+//   1.01 (2014-12-08) fix subpixel position when oversampling to exactly match
+//                        non-oversampled; STBTT_POINT_SIZE for packed case only
+//   1.00 (2014-12-06) add new PackBegin etc. API, w/ support for oversampling
+//   0.99 (2014-09-18) fix multiple bugs with subpixel rendering (ryg)
+//   0.9  (2014-08-07) support certain mac/iOS fonts without an MS platformID
+//   0.8b (2014-07-07) fix a warning
+//   0.8  (2014-05-25) fix a few more warnings
+//   0.7  (2013-09-25) bugfix: subpixel glyph bug fixed in 0.5 had come back
+//   0.6c (2012-07-24) improve documentation
+//   0.6b (2012-07-20) fix a few more warnings
+//   0.6  (2012-07-17) fix warnings; added stbtt_ScaleForMappingEmToPixels,
+//                        stbtt_GetFontBoundingBox, stbtt_IsGlyphEmpty
+//   0.5  (2011-12-09) bugfixes:
+//                        subpixel glyph renderer computed wrong bounding box
+//                        first vertex of shape can be off-curve (FreeSans)
+//   0.4b (2011-12-03) fixed an error in the font baking example
+//   0.4  (2011-12-01) kerning, subpixel rendering (tor)
+//                    bugfixes for:
+//                        codepoint-to-glyph conversion using table fmt=12
+//                        codepoint-to-glyph conversion using table fmt=4
+//                        stbtt_GetBakedQuad with non-square texture (Zer)
+//                    updated Hello World! sample to use kerning and subpixel
+//                    fixed some warnings
+//   0.3  (2009-06-24) cmap fmt=12, compound shapes (MM)
+//                    userdata, malloc-from-userdata, non-zero fill (stb)
+//   0.2  (2009-03-11) Fix unsigned/signed char warnings
+//   0.1  (2009-03-09) First public release
+//
+
+/*
+------------------------------------------------------------------------------
+This software is available under 2 licenses -- choose whichever you prefer.
+------------------------------------------------------------------------------
+ALTERNATIVE A - MIT License
+Copyright (c) 2017 Sean Barrett
+Permission is hereby granted, free of charge, to any person obtaining a copy of 
+this software and associated documentation files (the "Software"), to deal in 
+the Software without restriction, including without limitation the rights to 
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 
+of the Software, and to permit persons to whom the Software is furnished to do 
+so, subject to the following conditions:
+The above copyright notice and this permission notice shall be included in all 
+copies or substantial portions of the Software.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
+SOFTWARE.
+------------------------------------------------------------------------------
+ALTERNATIVE B - Public Domain (www.unlicense.org)
+This is free and unencumbered software released into the public domain.
+Anyone is free to copy, modify, publish, use, compile, sell, or distribute this 
+software, either in source code form or as a compiled binary, for any purpose, 
+commercial or non-commercial, and by any means.
+In jurisdictions that recognize copyright laws, the author or authors of this 
+software dedicate any and all copyright interest in the software to the public 
+domain. We make this dedication for the benefit of the public at large and to 
+the detriment of our heirs and successors. We intend this dedication to be an 
+overt act of relinquishment in perpetuity of all present and future rights to 
+this software under copyright law.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
+AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 
+ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+------------------------------------------------------------------------------
+*/
diff --git a/external/Vulkan/external/ktx/LICENSE.md b/external/Vulkan/external/ktx/LICENSE.md
new file mode 100644
index 0000000000000000000000000000000000000000..0a0090d8fd7a16fb656960d7f49a60c1c1ccac1f
--- /dev/null
+++ b/external/Vulkan/external/ktx/LICENSE.md
@@ -0,0 +1,228 @@
+License and Attribution Notices           {#license}
+=======================
+
+-----------------
+
+This file has two names: LICENSE.md and NOTICE.md. The former to tell GiHub users this
+is the license. The latter to tell creators of derived works that they need to distribute this
+with or make it available in a display generated by any Derivative Works, except for any
+parts that do not pertain to the Derivative Work, in accordance with clause 4d of the
+Apache 2.0 license, i.e. this is the "NOTICE" file.
+
+-----------------
+
+## Default License
+
+With the exception of the files listed explicitly below, the source
+is made available under the Apache License, Version 2.0 (the "License");
+you may not use these files except in compliance with the License.
+You may obtain a copy of the License at
+
+&ensp;&ensp;&ensp;&ensp;http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+libKTX is the work of Mark Callow based on work by Georg Kolling and Jacob
+Ström with contributions borrowed from Troy Hanson and Johannes van Waveren.
+The source contains code
+
+    © 2010 & © 2013 The Khronos Group Inc.
+    © 2008 and © 2010 HI Corporation
+    © 2005 Ericsson AB
+    © 2003-2010, Troy D. Hanson
+    © 2015-2018 Mark Callow
+    © 2016 Oculus VR, LLC.
+
+The KTX load tests are the work of Mark Callow with a few small portions borrowed
+from Sascha Willems' Vulkan examples and use Sam Lantinga's libSDL for portability.
+The source contains code
+
+    © 2013 The Khronos Group Inc.
+    © 2008 and © 2010 HI Corporation
+    © 1997-2018 Sam Lantinga
+    © 2016 Sascha Willems
+    © 2015-2018 Mark Callow
+
+-----------------
+
+**IMPORTANT:** Due to GitHub Markdown limitations license text has
+been copied into this file from the files named below. In the event
+of a discrepancy, the licenses in the files shall be deemed correct.
+
+## libktx Exceptions
+### etcdec.cxx
+
+etcdec.cxx is made available under the terms and conditions of the following
+License Agreement.
+
+Software License Agreement
+
+PLEASE REVIEW THE FOLLOWING TERMS AND CONDITIONS PRIOR TO USING THE
+ERICSSON TEXTURE COMPRESSION CODEC SOFTWARE (THE "SOFTWARE"). THE USE
+OF THE SOFTWARE IS SUBJECT TO THE TERMS AND CONDITIONS OF THE
+FOLLOWING SOFTWARE LICENSE AGREEMENT (THE "SLA"). IF YOU DO NOT ACCEPT
+SUCH TERMS AND CONDITIONS YOU MAY NOT USE THE SOFTWARE.
+
+Subject to the terms and conditions of the SLA, the licensee of the
+Software (the "Licensee") hereby, receives a non-exclusive,
+non-transferable, limited, free-of-charge, perpetual and worldwide
+license, to copy, use, distribute and modify the Software, but only
+for the purpose of developing, manufacturing, selling, using and
+distributing products including the Software in binary form, which
+products are used for compression and/or decompression according to
+the Khronos standard specifications OpenGL, OpenGL ES and
+WebGL. Notwithstanding anything of the above, Licensee may distribute
+[etcdec.cxx] in source code form provided (i) it is in unmodified
+form; and (ii) it is included in software owned by Licensee.
+
+If Licensee institutes, or threatens to institute, patent litigation
+against Ericsson or Ericsson's affiliates for using the Software for
+developing, having developed, manufacturing, having manufactured,
+selling, offer for sale, importing, using, leasing, operating,
+repairing and/or distributing products (i) within the scope of the
+Khronos framework; or (ii) using software or other intellectual
+property rights owned by Ericsson or its affiliates and provided under
+the Khronos framework, Ericsson shall have the right to terminate this
+SLA with immediate effect. Moreover, if Licensee institutes, or
+threatens to institute, patent litigation against any other licensee
+of the Software for using the Software in products within the scope of
+the Khronos framework, Ericsson shall have the right to terminate this
+SLA with immediate effect. However, should Licensee institute, or
+threaten to institute, patent litigation against any other licensee of
+the Software based on such other licensee's use of any other software
+together with the Software, then Ericsson shall have no right to
+terminate this SLA.
+
+This SLA does not transfer to Licensee any ownership to any Ericsson
+or third party intellectual property rights. All rights not expressly
+granted by Ericsson under this SLA are hereby expressly
+reserved. Furthermore, nothing in this SLA shall be construed as a
+right to use or sell products in a manner which conveys or purports to
+convey whether explicitly, by principles of implied license, or
+otherwise, any rights to any third party, under any patent of Ericsson
+or of Ericsson's affiliates covering or relating to any combination of
+the Software with any other software or product (not licensed
+hereunder) where the right applies specifically to the combination and
+not to the software or product itself.
+
+THE SOFTWARE IS PROVIDED "AS IS". ERICSSON MAKES NO REPRESENTATIONS OF
+ANY KIND, EXTENDS NO WARRANTIES OR CONDITIONS OF ANY KIND, EITHER
+EXPRESS, IMPLIED OR STATUTORY; INCLUDING, BUT NOT LIMITED TO, EXPRESS,
+IMPLIED OR STATUTORY WARRANTIES OR CONDITIONS OF TITLE,
+MERCHANTABILITY, SATISFACTORY QUALITY, SUITABILITY, AND FITNESS FOR A
+PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE
+OF THE SOFTWARE IS WITH THE LICENSEE. SHOULD THE SOFTWARE PROVE
+DEFECTIVE, THE LICENSEE ASSUMES THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION. ERICSSON MAKES NO WARRANTY THAT THE MANUFACTURE,
+SALE, OFFERING FOR SALE, DISTRIBUTION, LEASE, USE OR IMPORTATION UNDER
+THE SLA WILL BE FREE FROM INFRINGEMENT OF PATENTS, COPYRIGHTS OR OTHER
+INTELLECTUAL PROPERTY RIGHTS OF OTHERS, AND THE VALIDITY OF THE
+LICENSE AND THE SLA ARE SUBJECT TO LICENSEE'S SOLE RESPONSIBILITY TO
+MAKE SUCH DETERMINATION AND ACQUIRE SUCH LICENSES AS MAY BE NECESSARY
+WITH RESPECT TO PATENTS, COPYRIGHT AND OTHER INTELLECTUAL PROPERTY OF
+THIRD PARTIES.
+
+THE LICENSEE ACKNOWLEDGES AND ACCEPTS THAT THE SOFTWARE (I) IS NOT
+LICENSED FOR; (II) IS NOT DESIGNED FOR OR INTENDED FOR; AND (III) MAY
+NOT BE USED FOR; ANY MISSION CRITICAL APPLICATIONS SUCH AS, BUT NOT
+LIMITED TO OPERATION OF NUCLEAR OR HEALTHCARE COMPUTER SYSTEMS AND/OR
+NETWORKS, AIRCRAFT OR TRAIN CONTROL AND/OR COMMUNICATION SYSTEMS OR
+ANY OTHER COMPUTER SYSTEMS AND/OR NETWORKS OR CONTROL AND/OR
+COMMUNICATION SYSTEMS ALL IN WHICH CASE THE FAILURE OF THE SOFTWARE
+COULD LEAD TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL, MATERIAL OR
+ENVIRONMENTAL DAMAGE. LICENSEE'S RIGHTS UNDER THIS LICENSE WILL
+TERMINATE AUTOMATICALLY AND IMMEDIATELY WITHOUT NOTICE IF LICENSEE
+FAILS TO COMPLY WITH THIS PARAGRAPH.
+
+IN NO EVENT SHALL ERICSSON BE LIABLE FOR ANY DAMAGES WHATSOEVER,
+INCLUDING BUT NOT LIMITED TO PERSONAL INJURY, ANY GENERAL, SPECIAL,
+INDIRECT, INCIDENTAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF OR IN
+CONNECTION WITH THE USE OR INABILITY TO USE THE SOFTWARE (INCLUDING
+BUT NOT LIMITED TO LOSS OF PROFITS, BUSINESS INTERUPTIONS, OR ANY
+OTHER COMMERCIAL DAMAGES OR LOSSES, LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY THE LICENSEE OR THIRD
+PARTIES OR A FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER
+SOFTWARE) REGARDLESS OF THE THEORY OF LIABILITY (CONTRACT, TORT, OR
+OTHERWISE), EVEN IF THE LICENSEE OR ANY OTHER PARTY HAS BEEN ADVISED
+OF THE POSSIBILITY OF SUCH DAMAGES.
+
+Licensee acknowledges that "ERICSSON ///" is the corporate trademark
+of Telefonaktiebolaget LM Ericsson and that both "Ericsson" and the
+figure "///" are important features of the trade names of
+Telefonaktiebolaget LM Ericsson. Nothing contained in these terms and
+conditions shall be deemed to grant Licensee any right, title or
+interest in the word "Ericsson" or the figure "///". No delay or
+omission by Ericsson to exercise any right or power shall impair any
+such right or power to be construed to be a waiver thereof. Consent by
+Ericsson to, or waiver of, a breach by the Licensee shall not
+constitute consent to, waiver of, or excuse for any other different or
+subsequent breach.
+
+This SLA shall be governed by the substantive law of Sweden. Any
+dispute, controversy or claim arising out of or in connection with
+this SLA, or the breach, termination or invalidity thereof, shall be
+submitted to the exclusive jurisdiction of the Swedish Courts.
+
+### uthash.h
+
+uthash.h is made available under the following revised BSD license.
+
+Copyright &copy; 2003-2010, Troy D. Hanson   http://uthash.sourceforge.net
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+## Load Tests Exceptions
+
+### icons/{ios/CommonIcons*,mac,win}/ktx_{app,document}.*
+
+These KTX application and document icons are &copy; & &trade; The Khronos Group, Inc.
+and may not be used without specific prior written permission from The Khronos Group,
+except that the ktx_app icon may be distributed in a Derived Work as the icon for
+the `glloadtests` and `vkloadtests` applications.
+
+### other_include/SDL2/*
+
+These files are part of the SDL2 source distributed by the [SDL project]
+(http://libsdl.org) under the terms of the [zlib license]
+(http://www.zlib.net/zlib_license.html).
+
+### other_include/glm
+
+OpenGL Mathematics is licensed under the [Happy Bunny (Modified MIT) License](https://github.com/g-truc/glm/blob/master/manual.md#section0)
+
+### testimages/hi_mark{,_sq}.ktx
+
+The HI logo textures are &copy; & &trade; HI Corporation and are
+provided for use only in testing the KTX loader. Any other use requires
+specific prior written permission from HI. Furthermore the name HI may
+not be used to endorse or promote products derived from this software
+without specific prior written permission.
+
+### {VulkanMeshLoader,vulkantextoverlay}.hpp, vulkandebug.*
+
+Copyright &copy; 2016 Sascha Willems - www.saschawillems.de
+
+{VulkanMeshLoader,vulkantextoverlay}.hpp and vulkandebug.* are licensed
+under the [MIT license](http://opensource.org/licenses/MIT)
+
diff --git a/external/Vulkan/external/ktx/NOTICE.md b/external/Vulkan/external/ktx/NOTICE.md
new file mode 100644
index 0000000000000000000000000000000000000000..f0c4298632f957385e61afc91647f6faeb78e804
--- /dev/null
+++ b/external/Vulkan/external/ktx/NOTICE.md
@@ -0,0 +1 @@
+LICENSE.md
\ No newline at end of file
diff --git a/external/Vulkan/external/ktx/README.md b/external/Vulkan/external/ktx/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..ee604b0181bca726379bbbca733571bbda2419b0
--- /dev/null
+++ b/external/Vulkan/external/ktx/README.md
@@ -0,0 +1,70 @@
+<img src="https://www.khronos.org/assets/images/api_logos/khronos.svg" width="300"/>
+
+The Official Khronos KTX Software Repository
+---
+
+| GNU/Linux, iOS & OSX |  Windows | Documentation | 
+|----------------------| :------: | :-----------: |
+| [![Build Status](https://travis-ci.org/KhronosGroup/KTX-Software.svg?branch=master)](https://travis-ci.org/KhronosGroup/KTX-Software) | [![Build status](https://ci.appveyor.com/api/projects/status/rj9bg8g2jphg3rc0/branch/master?svg=true)](https://ci.appveyor.com/project/msc-/ktx/branch/master) | [![Build status](https://codedocs.xyz/KhronosGroup/KTX-Software.svg)](https://codedocs.xyz/KhronosGroup/KTX-Software/) |
+
+This is the official home of the source code
+for the Khronos KTX library and tools. KTX is a lightweight file format
+for OpenGL textures, designed around how textures are loaded in OpenGL.
+
+See the Doxygen generated live documentation for
+[`master`](https://github.khronos.org/KTX-Software/)
+for API usage information.
+
+See [CONTRIBUTING](CONTRIBUTING.md) for information about contributing.
+
+See [LICENSE](LICENSE.md) for information about licensing.
+
+See [BUILDING](BUILDING.md) for information about building the code.
+
+More information about KTX and links to tools that support it can be
+found on the
+[KTX page](http://www.khronos.org/opengles/sdk/tools/KTX/) of
+the [OpenGL ES SDK](http://www.khronos.org/opengles/sdk) on
+[khronos.org](http://www.khronos.org).
+
+If you need help with using the KTX library or KTX tools, please use the
+[KTX forum](https://forums.khronos.org/forumdisplay.php/103-KTX-file-format-for-OpenGL-OpenGL-ES-and-WebGL-textures).
+To report problems use GitHub [issues](https://github.com/KhronosGroup/KTX/issues).
+
+**IMPORTANT:** you **must** install the [Git LFS](https://github.com/github/git-lfs)
+command line extension in order to fully checkout this repository after cloning. You
+need at least version 1.1.
+
+A few files have `$Date$` keywords. If you care about having the proper
+dates shown or will be generating the documentation or preparing
+distribution archives, you **must** follow the instructions below.
+
+#### <a id="kwexpansion"></a>$Date$ keyword expansion
+
+$Date$ keywords are expanded via a smudge & clean filter. To install
+the filter, issue the following commands in the root of your clone.
+
+On Unix (Linux, Mac OS X, etc.) platforms and Windows using Git for Windows'
+Git Bash or Cygwin's bash terminal:
+
+```bash
+./install-gitconfig.sh
+rm TODO.md include/ktx.h tools/toktx/toktx.cpp
+git checkout TODO.md include/ktx.h tools/toktx/toktx.cpp
+```
+
+On Windows with the Command Prompt (requires `git.exe` in a directory
+on your %PATH%):
+
+```cmd
+install-gitconfig.bat
+del TODO.md include/ktx.h tools/toktx/toktx.cpp
+git checkout TODO.md include/ktx.h tools/toktx/toktx.cpp 
+```
+
+The first command adds an [include] of the repo's `.gitconfig` to the
+local git config file `.git/config`, i.e. the one in your clone of the repo.
+`.gitconfig` contains the config of the "keyworder" filter. The remaining
+commands force a new checkout of the affected files to smudge them with the
+date. These two are unnecessary if you plan to edit these files.
+
diff --git a/external/Vulkan/external/ktx/include/ktx.h b/external/Vulkan/external/ktx/include/ktx.h
new file mode 100644
index 0000000000000000000000000000000000000000..f1cfa37aebd906b14aafcb8d649428993b87f1c6
--- /dev/null
+++ b/external/Vulkan/external/ktx/include/ktx.h
@@ -0,0 +1,855 @@
+/* -*- tab-width: 4; -*- */
+/* vi: set sw=2 ts=4 expandtab: */
+
+#ifndef KTX_H_A55A6F00956F42F3A137C11929827FE1
+#define KTX_H_A55A6F00956F42F3A137C11929827FE1
+
+/*
+ * ©2010-2018 The Khronos Group, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * See the accompanying LICENSE.md for licensing details for all files in
+ * the KTX library and KTX loader tests.
+ */
+
+/**
+ * @file
+ * @~English
+ *
+ * @brief Declares the public functions and structures of the
+ *        KTX API.
+ *
+ * @author Mark Callow, Edgewise Consulting and while at HI Corporation
+ * @author Based on original work by Georg Kolling, Imagination Technology
+ *
+ * @version 3.0
+ *
+ * @todo Find a way so that applications do not have to define KTX_OPENGL{,_ES*}
+ *       when using the library.
+ */
+
+#include <stdio.h>
+#include <stdbool.h>
+
+/* To avoid including <KHR/khrplatform.h> define our own types. */
+typedef unsigned char ktx_uint8_t;
+typedef bool ktx_bool_t;
+#ifdef _MSC_VER
+typedef unsigned short ktx_uint16_t;
+typedef   signed short ktx_int16_t;
+typedef unsigned int   ktx_uint32_t;
+typedef   signed int   ktx_int32_t;
+typedef       size_t   ktx_size_t;
+#else
+#include <stdint.h>
+typedef uint16_t ktx_uint16_t;
+typedef  int16_t ktx_int16_t;
+typedef uint32_t ktx_uint32_t;
+typedef  int32_t ktx_int32_t;
+typedef   size_t ktx_size_t;
+#endif
+
+/* This will cause compilation to fail if size of uint32 != 4. */
+typedef unsigned char ktx_uint32_t_SIZE_ASSERT[sizeof(ktx_uint32_t) == 4];
+
+/*
+ * This #if allows libktx to be compiled with strict c99. It avoids
+ * compiler warnings or even errors when a gl.h is already included.
+ * "Redefinition of (type) is a c11 feature". Obviously this doesn't help if
+ * gl.h comes after. However nobody has complained about the unguarded typedefs
+ * since they were introduced so this is unlikely to be a problem in practice.
+ * Presumably everybody is using platform default compilers not c99 or else
+ * they are using C++.
+ */
+#if !defined(GL_NO_ERROR)
+  /*
+   * To avoid having to including gl.h ...
+   */
+  typedef unsigned char GLboolean;
+  typedef unsigned int GLenum;
+  typedef int GLint;
+  typedef int GLsizei;
+  typedef unsigned int GLuint;
+  typedef unsigned char GLubyte;
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @~English
+ * @brief Key String for standard orientation value.
+ */
+#define KTX_ORIENTATION_KEY "KTXorientation"
+/**
+ * @~English
+ * @brief Standard format for 2D orientation value.
+ */
+#define KTX_ORIENTATION2_FMT "S=%c,T=%c"
+/**
+ * @~English
+ * @brief Standard format for 3D orientation value.
+ */
+#define KTX_ORIENTATION3_FMT "S=%c,T=%c,R=%c"
+/**
+ * @~English
+ * @brief Required unpack alignment
+ */
+#define KTX_GL_UNPACK_ALIGNMENT 4
+    
+#define KTX_TRUE  true
+#define KTX_FALSE false
+
+/**
+ * @~English
+ * @brief Error codes returned by library functions.
+ */
+typedef enum KTX_error_code_t {
+    KTX_SUCCESS = 0,         /*!< Operation was successful. */
+    KTX_FILE_DATA_ERROR,     /*!< The data in the file is inconsistent with the spec. */
+    KTX_FILE_OPEN_FAILED,    /*!< The target file could not be opened. */
+    KTX_FILE_OVERFLOW,       /*!< The operation would exceed the max file size. */
+    KTX_FILE_READ_ERROR,     /*!< An error occurred while reading from the file. */
+    KTX_FILE_SEEK_ERROR,     /*!< An error occurred while seeking in the file. */
+    KTX_FILE_UNEXPECTED_EOF, /*!< File does not have enough data to satisfy request. */
+    KTX_FILE_WRITE_ERROR,    /*!< An error occurred while writing to the file. */
+    KTX_GL_ERROR,            /*!< GL operations resulted in an error. */
+    KTX_INVALID_OPERATION,   /*!< The operation is not allowed in the current state. */
+    KTX_INVALID_VALUE,       /*!< A parameter value was not valid */
+    KTX_NOT_FOUND,           /*!< Requested key was not found */
+    KTX_OUT_OF_MEMORY,       /*!< Not enough memory to complete the operation. */
+    KTX_UNKNOWN_FILE_FORMAT, /*!< The file not a KTX file */
+    KTX_UNSUPPORTED_TEXTURE_TYPE, /*!< The KTX file specifies an unsupported texture type. */
+} KTX_error_code;
+
+#define KTX_IDENTIFIER_REF  { 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A }
+#define KTX_ENDIAN_REF      (0x04030201)
+#define KTX_ENDIAN_REF_REV  (0x01020304)
+#define KTX_HEADER_SIZE     (64)
+
+/**
+ * @~English
+ * @brief Result codes returned by library functions.
+ */
+ typedef enum KTX_error_code_t ktxResult;
+
+/**
+ * @class ktxHashList
+ * @~English
+ * @brief Opaque handle to a ktxHashList.
+ */
+typedef struct ktxKVListEntry* ktxHashList;
+
+/**
+ * @class ktxTexture
+ * @~English
+ * @brief Class representing a texture.
+ *
+ * ktxTextures should be created only by one of the ktxTexture_Create*
+ * functions and these fields should be considered read-only.
+ */
+typedef struct {
+    ktx_uint32_t glFormat; /*!< Format of the texture data, e.g., GL_RGB. */
+    ktx_uint32_t glInternalformat; /*!< Internal format of the texture data,
+                                        e.g., GL_RGB8. */
+    ktx_uint32_t glBaseInternalformat; /*!< Base format of the texture data,
+                                            e.g., GL_RGB. */
+    ktx_uint32_t glType; /*!< Type of the texture data, e.g, GL_UNSIGNED_BYTE.*/
+      ktx_bool_t isArray; /*!< KTX_TRUE if the texture is an array texture, i.e,
+                               a GL_TEXTURE_*_ARRAY target is to be used. */
+      ktx_bool_t isCubemap; /*!< KTX_TRUE if the texture is a cubemap or
+                                 cubemap array. */
+      ktx_bool_t isCompressed; /*!< KTX_TRUE if @c glInternalFormat is that of
+                                    a compressed texture. */
+      ktx_bool_t generateMipmaps; /*!< KTX_TRUE if mipmaps should be generated
+                                       for the texture by ktxTexture_GLUpload()
+                                       or ktx_Texture_VkUpload(). */
+    ktx_uint32_t baseWidth;  /*!< Width of the base level of the texture. */
+    ktx_uint32_t baseHeight; /*!< Height of the base level of the texture. */
+    ktx_uint32_t baseDepth;  /*!< Depth of the base level of the texture. */
+    ktx_uint32_t numDimensions; /*!< Number of dimensions in the texture: 1, 2
+                                     or 3. */
+    ktx_uint32_t numLevels; /*!< Number of mip levels in the texture. Should be
+                                 1, if @c generateMipmaps is KTX_TRUE. Can be
+                                 less than a full pyramid but always starts at
+                                 the base level. */
+    ktx_uint32_t numLayers; /*!< Number of array layers in the texture. */
+    ktx_uint32_t numFaces; /*!< Number of faces, 6 for cube maps, 1 otherwise.*/
+     ktxHashList kvDataHead; /*!< Head of the hash list of metadata. */
+    ktx_uint32_t kvDataLen; /*!< Length of the metadata, if it has been
+                                 extracted in its raw form, otherwise 0. */
+    ktx_uint8_t* kvData; /*!< Pointer to the metadata, if it has been extracted
+                              in its raw form, otherwise NULL. */
+      ktx_size_t dataSize; /*!< Length of the image data in bytes. */
+    ktx_uint8_t* pData; /*!< Pointer to the image data. */
+} ktxTexture;
+
+    
+/**
+ * @memberof ktxTexture
+ * @~English
+ * @brief Structure for passing texture information to ktxTexture_Create().
+ *
+ * @sa ktxTexture_Create()
+ */
+typedef struct
+{
+    ktx_uint32_t glInternalformat; /*!< Internal format for the texture, e.g.,
+                                        GL_RGB8. */
+    ktx_uint32_t baseWidth;  /*!< Width of the base level of the texture. */
+    ktx_uint32_t baseHeight; /*!< Height of the base level of the texture. */
+    ktx_uint32_t baseDepth;  /*!< Depth of the base level of the texture. */
+    ktx_uint32_t numDimensions; /*!< Number of dimensions in the texture, 1, 2
+                                     or 3. */
+    ktx_uint32_t numLevels; /*!< Number of mip levels in the texture. Should be
+                                 1 if @c generateMipmaps is KTX_TRUE; */
+    ktx_uint32_t numLayers; /*!< Number of array layers in the texture. */
+    ktx_uint32_t numFaces;  /*!< Number of faces: 6 for cube maps, 1 otherwise. */
+    ktx_bool_t   isArray;  /*!< Set to KTX_TRUE if the texture is to be an
+                                array texture. Means OpenGL will use a
+                                GL_TEXTURE_*_ARRAY target. */
+    ktx_bool_t   generateMipmaps; /*!< Set to KTX_TRUE if mipmaps should be
+                                       generated for the texture when loading
+                                       into OpenGL. */
+} ktxTextureCreateInfo;
+
+/**
+ * @memberof ktxTexture
+ * @~English
+ * @brief Enum for requesting, or not, allocation of storage for images.
+ *
+ * @sa ktxTexture_Create()
+ */
+typedef enum {
+    KTX_TEXTURE_CREATE_NO_STORAGE = 0,  /*!< Don't allocate any image storage. */
+    KTX_TEXTURE_CREATE_ALLOC_STORAGE = 1 /*!< Allocate image storage. */
+} ktxTextureCreateStorageEnum;
+
+/**
+ * @memberof ktxTexture
+ * @~English
+ * @brief Flags for requesting services during creation.
+ *
+ * @sa ktxTexture_CreateFrom*
+ */
+enum ktxTextureCreateFlagBits {
+    KTX_TEXTURE_CREATE_NO_FLAGS = 0x00,
+    KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT = 0x01,
+                                   /*!< Load the images from the KTX source. */
+    KTX_TEXTURE_CREATE_RAW_KVDATA_BIT = 0x02,
+                                   /*!< Load the raw key-value data instead of
+                                        creating a @c ktxHashList from it. */
+    KTX_TEXTURE_CREATE_SKIP_KVDATA_BIT = 0x04
+                                   /*!< Skip any key-value data. This overrides
+                                        the RAW_KVDATA_BIT. */
+};
+/**
+ * @memberof ktxTexture
+ * @~English
+ * @brief Type for TextureCreateFlags parameters.
+ *
+ * @sa ktxTexture_CreateFrom*()
+ */
+typedef ktx_uint32_t ktxTextureCreateFlags;
+
+#define KTXAPIENTRY
+#define KTXAPIENTRYP KTXAPIENTRY *
+/**
+ * @memberof ktxTexture
+ * @~English
+ * @brief Signature of function called by the <tt>ktxTexture_Iterate*</tt>
+ *        functions to receive image data.
+ *
+ * The function parameters are used to pass values which change for each image.
+ * Obtain values which are uniform across all images from the @c ktxTexture
+ * object.
+ *
+ * @param [in] miplevel        MIP level from 0 to the max level which is
+ *                             dependent on the texture size.
+ * @param [in] face            usually 0; for cube maps, one of the 6 cube
+ *                             faces in the order +X, -X, +Y, -Y, +Z, -Z,
+ *                             0 to 5.
+ * @param [in] width           width of the image.
+ * @param [in] height          height of the image or, for 1D textures
+ *                             textures, 1.
+ * @param [in] depth           depth of the image or, for 1D & 2D
+ *                             textures, 1.
+ * @param [in] faceLodSize     number of bytes of data pointed at by
+ *                             @p pixels.
+ * @param [in] pixels          pointer to the image data.
+ * @param [in,out] userdata    pointer for the application to pass data to and
+ *                             from the callback function.
+ */
+/* Don't use KTXAPIENTRYP to avoid a Doxygen bug. */
+typedef KTX_error_code (KTXAPIENTRY* PFNKTXITERCB)(int miplevel, int face,
+                                               int width, int height, int depth,
+                                               ktx_uint32_t faceLodSize,
+                                               void* pixels,
+                                               void* userdata);
+
+/*
+ * See the implementation files for the full documentation of the following
+ * functions.
+ */
+
+/*
+ * Creates an empty ktxTexture object with the characteristics described
+ * by createInfo.
+ */
+KTX_error_code
+ktxTexture_Create(ktxTextureCreateInfo* createInfo,
+                  ktxTextureCreateStorageEnum storageAllocation,
+                  ktxTexture** newTex);
+
+/*
+ * Creates a ktxTexture from a stdio stream reading from a KTX source.
+ */
+KTX_error_code
+ktxTexture_CreateFromStdioStream(FILE* stdioStream,
+                                 ktxTextureCreateFlags createFlags,
+                                 ktxTexture** newTex);
+
+/*
+ * Creates a ktxTexture from a named file containing KTX data.
+ */
+KTX_error_code
+ktxTexture_CreateFromNamedFile(const char* const filename,
+                               ktxTextureCreateFlags createFlags,
+                               ktxTexture** newTex);
+
+/*
+ * Creates a ktxTexture from a block of memory containing KTX-formatted data.
+ */
+KTX_error_code
+ktxTexture_CreateFromMemory(const ktx_uint8_t* bytes, ktx_size_t size,
+                            ktxTextureCreateFlags createFlags,
+                            ktxTexture** newTex);
+/*
+ * Destroys a ktxTexture object.
+ */
+void
+ktxTexture_Destroy(ktxTexture* This);
+
+/*
+ * Returns a pointer to the image data of a ktxTexture object.
+ */
+ktx_uint8_t*
+ktxTexture_GetData(ktxTexture* This);
+
+/*
+ * Returns the offset of the image for the specified mip level, array layer
+ * and face or depth slice within the image data of a ktxTexture object.
+ */
+KTX_error_code
+ktxTexture_GetImageOffset(ktxTexture* This, ktx_uint32_t level,
+                          ktx_uint32_t layer, ktx_uint32_t faceSlice,
+                          ktx_size_t* pOffset);
+
+/*
+ * Returns the pitch of a row of an image at the specified level.
+ * Similar to the rowPitch in a VkSubResourceLayout.
+ */
+ ktx_uint32_t
+ ktxTexture_GetRowPitch(ktxTexture* This, ktx_uint32_t level);
+
+ /*
+  * Return the element size of the texture's images.
+  */
+ ktx_uint32_t
+ ktxTexture_GetElementSize(ktxTexture* This);
+
+/*
+ * Returns the size of all the image data of a ktxTexture object in bytes.
+ */
+ktx_size_t
+ktxTexture_GetSize(ktxTexture* This);
+    
+/*
+ * Returns the size of an image at the specified level.
+ */
+ ktx_size_t
+ ktxTexture_GetImageSize(ktxTexture* This, ktx_uint32_t level);
+
+/*
+ * Uploads the image data from a ktxTexture object to an OpenGL {,ES} texture
+ * object.
+ */
+KTX_error_code
+ktxTexture_GLUpload(ktxTexture* This, GLuint* pTexture, GLenum* pTarget,
+                    GLenum* pGlerror);
+
+/*
+ * Loads the image data into a ktxTexture object from the KTX-formatted source.
+ * Used when the image data was not loaded during ktxTexture_CreateFrom*.
+ */
+KTX_error_code
+ktxTexture_LoadImageData(ktxTexture* This,
+                         ktx_uint8_t* pBuffer,
+                         ktx_size_t bufSize);
+
+/*
+ * Iterates over the already loaded level-faces in a ktxTexture object.
+ * iterCb is called for each level-face.
+ */
+KTX_error_code
+ktxTexture_IterateLevelFaces(ktxTexture* super, PFNKTXITERCB iterCb,
+                             void* userdata);
+
+/*
+ * Iterates over the level-faces of a ktxTexture object, loading each from
+ * the KTX-formatted source then calling iterCb.
+ */
+KTX_error_code
+ktxTexture_IterateLoadLevelFaces(ktxTexture* super, PFNKTXITERCB iterCb,
+                                 void* userdata);
+/*
+ * Iterates over the already loaded levels in a ktxTexture object.
+ * iterCb is called for each level. The data passed to iterCb
+ * includes all faces for each level.
+ */
+KTX_error_code
+ktxTexture_IterateLevels(ktxTexture* This, PFNKTXITERCB iterCb,
+                         void* userdata);
+
+/*
+ * Sets the image for the specified level, layer & faceSlice within a
+ * ktxTexture object from packed image data in memory. The destination image
+ * data is padded to the KTX specified row alignment of 4, if necessary.
+ */
+KTX_error_code
+ktxTexture_SetImageFromMemory(ktxTexture* This,ktx_uint32_t level,
+                              ktx_uint32_t layer, ktx_uint32_t faceSlice,
+                              const ktx_uint8_t* src, ktx_size_t srcSize);
+
+/*
+ * Sets the image for the specified level, layer & faceSlice within a
+ * ktxTexture object from a stdio stream reading from a KTX source. The
+ * destination image data is padded to the KTX specified row alignment of 4,
+ * if necessary.
+ */
+KTX_error_code
+ktxTexture_SetImageFromStdioStream(ktxTexture* This, ktx_uint32_t level,
+                                   ktx_uint32_t layer, ktx_uint32_t faceSlice,
+                                   FILE* src, ktx_size_t srcSize);
+
+/*
+ * Write a ktxTexture object to a stdio stream in KTX format.
+ */
+KTX_error_code
+ktxTexture_WriteToStdioStream(ktxTexture* This, FILE* dstsstr);
+
+/*
+ * Write a ktxTexture object to a named file in KTX format.
+ */
+KTX_error_code
+ktxTexture_WriteToNamedFile(ktxTexture* This, const char* const dstname);
+
+/*
+ * Write a ktxTexture object to a block of memory in KTX format.
+ */
+KTX_error_code
+ktxTexture_WriteToMemory(ktxTexture* This,
+                         ktx_uint8_t** bytes, ktx_size_t* size);
+
+/*
+ * Returns a string corresponding to a KTX error code.
+ */
+const char* const ktxErrorString(KTX_error_code error);
+
+KTX_error_code ktxHashList_Create(ktxHashList** ppHl);
+void ktxHashList_Construct(ktxHashList* pHl);
+void ktxHashList_Destroy(ktxHashList* head);
+void ktxHashList_Destruct(ktxHashList* head);
+/*
+ * Adds a key-value pair to a hash list.
+ */
+KTX_error_code
+ktxHashList_AddKVPair(ktxHashList* pHead, const char* key,
+                      unsigned int valueLen, const void* value);
+
+/*
+ * Looks up a key and returns the value.
+ */
+KTX_error_code
+ktxHashList_FindValue(ktxHashList* pHead, const char* key,
+                      unsigned int* pValueLen, void** pValue);
+
+/*
+ * Serializes the hash table to a block of memory suitable for
+ * writing to a KTX file.
+ */
+KTX_error_code
+ktxHashList_Serialize(ktxHashList* pHead,
+                      unsigned int* kvdLen, unsigned char** kvd);
+
+
+/*
+ * Creates a hash table from the serialized data read from a
+ * a KTX file.
+ */
+KTX_error_code
+ktxHashList_Deserialize(ktxHashList* pHead, unsigned int kvdLen, void* kvd);
+    
+
+/*===========================================================*
+ * For Versions 1 and 2 compatibility                        *
+ *===========================================================*/
+
+/**
+ * @~English
+ * @deprecated Use struct ktxTexture
+ * @brief Structure used by load functions to return texture dimensions
+ */
+typedef struct KTX_dimensions {
+    GLsizei width;  /*!< Width in texels. */
+    GLsizei height; /*!< Height in texels. */
+    GLsizei depth;  /*!< Depth in texels. */
+} KTX_dimensions;
+
+/**
+ * @~English
+ * @brief Structure to pass texture information to ktxWriteKTX.
+ * @deprecated Use ktxTexture_Create() and @c ktxTextureCreateInfo
+ *
+ * Retained for backward compatibility with Versions 1 & 2.
+ */
+typedef struct KTX_texture_info
+{
+    ktx_uint32_t glType;
+    ktx_uint32_t glTypeSize;
+    ktx_uint32_t glFormat;
+    ktx_uint32_t glInternalFormat;
+    ktx_uint32_t glBaseInternalFormat;
+    ktx_uint32_t pixelWidth;
+     ktx_uint32_t pixelHeight;
+    ktx_uint32_t pixelDepth;
+    ktx_uint32_t numberOfArrayElements;
+    ktx_uint32_t numberOfFaces;
+    ktx_uint32_t numberOfMipmapLevels;
+} KTX_texture_info;
+
+/**
+ * @var KTX_texture_info::glType
+ * @~English
+ * @brief The type of the image data.
+ *
+ * Values are the same as in the @p type parameter of
+ * glTexImage*D. Must be 0 for compressed images.
+ */
+/**
+ * @var KTX_texture_info::glTypeSize;
+ * @~English
+ * @brief The data type size to be used in case of endianness
+ *        conversion.
+ *
+ * This value is used in the event conversion is required when the
+ * KTX file is loaded. It should be the size in bytes corresponding
+ * to glType. Must be 1 for compressed images.
+ */
+/**
+ * @var KTX_texture_info::glFormat;
+ * @~English
+ * @brief The format of the image(s).
+ *
+ * Values are the same as in the format parameter
+ * of glTexImage*D. Must be 0 for compressed images.
+ */
+/**
+ * @var KTX_texture_info::glInternalFormat;
+ * @~English
+ * @brief The internalformat of the image(s).
+ *
+ * Values are the same as for the internalformat parameter of
+ * glTexImage*2D. Note: it will not be used when a KTX file
+ * containing an uncompressed texture is loaded into OpenGL ES.
+ */
+/**
+ * @var KTX_texture_info::glBaseInternalFormat;
+ * @~English
+ * @brief The base internalformat of the image(s)
+ *
+ * For non-compressed textures, should be the same as glFormat.
+ * For compressed textures specifies the base internal, e.g.
+ * GL_RGB, GL_RGBA.
+ */
+/**
+ * @var KTX_texture_info::pixelWidth;
+ * @~English
+ * @brief Width of the image for texture level 0, in pixels.
+ */
+/**
+ * @var KTX_texture_info::pixelHeight;
+ * @~English
+ * @brief Height of the texture image for level 0, in pixels.
+ *
+ * Must be 0 for 1D textures.
+ */
+/**
+ * @var KTX_texture_info::pixelDepth;
+ * @~English
+ * @brief Depth of the texture image for level 0, in pixels.
+ *
+ * Must be 0 for 1D, 2D and cube textures.
+ */
+/**
+ * @var KTX_texture_info::numberOfArrayElements;
+ * @~English
+ * @brief The number of array elements.
+ *
+ * Must be 0 if not an array texture.
+ */
+/**
+ * @var KTX_texture_info::numberOfFaces;
+ * @~English
+ * @brief The number of cubemap faces.
+ *
+ * Must be 6 for cubemaps and cubemap arrays, 1 otherwise. Cubemap
+ * faces must be provided in the order: +X, -X, +Y, -Y, +Z, -Z.
+ */
+/**
+ * @var KTX_texture_info::numberOfMipmapLevels;
+ * @~English
+ * @brief The number of mipmap levels.
+ *
+ * 1 for non-mipmapped texture. 0 indicates that a full mipmap pyramid should
+ * be generated from level 0 at load time (this is usually not allowed for
+ * compressed formats). Mipmaps must be provided in order from largest size to
+ * smallest size. The first mipmap level is always level 0.
+ */
+
+/**
+ * @~English
+ * @deprecated Use ktxTexture_Create() and @c ktxTexture_SetImageFromMemory()
+ *             or ktxTexture_SetImageFromStdioStream().
+ * @brief Structure used to pass image data to ktxWriteKTX.
+ *
+ * @sa ktxTextureCreate()
+ * @sa ktxTexture_SetImageFromMemory()
+ * @sa ktxTexture_SetImageFromStdioStream()
+ *
+ * Retained for backward compatibility with Versions 1 & 2.
+ */
+typedef struct KTX_image_info {
+    GLsizei size;    /*!< Size of the image data in bytes. */
+    GLubyte* data;  /*!< Pointer to the image data. */
+} KTX_image_info;
+
+/*
+ * Loads a texture from a stdio FILE.
+ */
+KTX_error_code
+ktxLoadTextureF(FILE*, GLuint* pTexture, GLenum* pTarget,
+                KTX_dimensions* pDimensions, GLboolean* pIsMipmapped,
+                GLenum* pGlerror,
+                unsigned int* pKvdLen, unsigned char** ppKvd);
+
+/*
+ * Loads a texture from a KTX file on disk.
+ */
+KTX_error_code
+ktxLoadTextureN(const char* const filename, GLuint* pTexture, GLenum* pTarget,
+                KTX_dimensions* pDimensions, GLboolean* pIsMipmapped,
+                GLenum* pGlerror,
+                unsigned int* pKvdLen, unsigned char** ppKvd);
+
+/*
+ * Loads a texture from a KTX file in memory.
+ */
+KTX_error_code
+ktxLoadTextureM(const void* bytes, GLsizei size, GLuint* pTexture,
+                GLenum* pTarget, KTX_dimensions* pDimensions,
+                GLboolean* pIsMipmapped, GLenum* pGlerror,
+                unsigned int* pKvdLen, unsigned char** ppKvd);
+
+/**
+ * @class KTX_hash_table
+ * @~English
+ * @deprecated Use @c ktxHashList.
+ * @brief Opaque handle to a hash table.
+ */
+typedef ktxHashList* KTX_hash_table;
+
+/*
+ * @deprecated Use ktxHashList_Create().
+ */
+KTX_hash_table ktxHashTable_Create(void);
+
+/**
+ * @~English
+ * @deprecated Use ktxHashList_Destroy().
+ * @brief Destroy a KTX_hash_table.
+ *
+ * Should be documented as a member of KTX_hash_table but a doxygen limitation
+ * prevents that.
+ */
+#define ktxHashTable_Destroy(a) ktxHashList_Destroy(a);
+/**
+ * @~English
+ * @deprecated Use ktxHashList_AddKVPair().
+ * @brief Add a key-value pair to a KTX_hash_table.
+ *
+ * Should be documented as a member of KTX_hash_table but a doxygen limitation
+ * prevents that.
+ */
+#define ktxHashTable_AddKVPair(a, b, c, d) ktxHashList_AddKVPair(a, b, c, d)
+/**
+ * @~English
+ * @deprecated Use ktxHashList_FindValue().
+ * @brief Looks up a key and returns its value.
+ *
+ * Should be documented as a member of KTX_hash_table but a doxygen limitation
+ * prevents that.
+ */
+#define ktxHashTable_FindValue(a, b, c, d) ktxHashList_FindValue(a, b, c, d)
+
+/*
+ * @deprecated Use ktxHashList_Serialize().
+ */
+KTX_error_code
+ktxHashTable_Serialize(KTX_hash_table This,
+                       unsigned int* kvdLen, unsigned char** kvd);
+
+/*
+ * @deprecated Use ktxHashList_Deserialize().
+ */
+KTX_error_code
+ktxHashTable_Deserialize(unsigned int kvdLen, void* pKvd, KTX_hash_table* pHt);
+
+/*
+ * Writes a KTX file using supplied data.
+ */
+KTX_error_code
+ktxWriteKTXF(FILE* dst, const KTX_texture_info* imageInfo,
+             GLsizei bytesOfKeyValueData, const void* keyValueData,
+             GLuint numImages, KTX_image_info images[]);
+
+/**
+ * Writes a KTX file using supplied data.
+ */
+KTX_error_code
+ktxWriteKTXN(const char* dstname, const KTX_texture_info* imageInfo,
+             GLsizei bytesOfKeyValueData, const void* keyValueData,
+             GLuint numImages, KTX_image_info images[]);
+
+/*
+ * Writes a KTX file into memory using supplied data.
+ */
+KTX_error_code
+ktxWriteKTXM(unsigned char** dst, GLsizei* size,
+             const KTX_texture_info* textureInfo, GLsizei bytesOfKeyValueData,
+             const void* keyValueData, GLuint numImages,
+             KTX_image_info images[]);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**
+@~English
+@page libktx_history Revision History
+
+@section v7 Version 3.0.1
+Fixed:
+@li GitHub issue #159: compile failure with recent Vulkan SDKs.
+@li Incorrect mapping of GL DXT3 and DXT5 formats to Vulkan equivalents.
+@li Incorrect BC4 blocksize.
+@li Missing mapping of PVRTC formats from GL to Vulkan.
+@li Incorrect block width and height calculations for sizes that are not
+    a multiple of the block size.
+@li Incorrect KTXorientation key in test images.
+
+@section v6 Version 3.0
+Added:
+@li new ktxTexture object based API for reading KTX files without an OpenGL context.
+@li Vulkan loader. @#include <ktxvulkan.h> to use it.
+
+Changed:
+@li ktx.h to not depend on KHR/khrplatform.h and GL{,ES*}/gl{corearb,}.h.
+    Applications using OpenGL must now include these files themselves.
+@li ktxLoadTexture[FMN], removing the hack of loading 1D textures as 2D textures
+    when the OpenGL context does not support 1D textures.
+    KTX_UNSUPPORTED_TEXTURE_TYPE is now returned.
+
+@section v5 Version 2.0.2
+Added:
+@li Support for cubemap arrays.
+
+Changed:
+@li New build system
+
+Fixed:
+@li GitHub issue #40: failure to byte-swap key-value lengths.
+@li GitHub issue #33: returning incorrect target when loading cubemaps.
+@li GitHub PR #42: loading of texture arrays.
+@li GitHub PR #41: compilation error when KTX_OPENGL_ES2=1 defined.
+@li GitHub issue #39: stack-buffer-overflow in toktx
+@li Don't use GL_EXTENSIONS on recent OpenGL versions.
+
+@section v4 Version 2.0.1
+Added:
+@li CMake build files. Thanks to Pavel Rotjberg for the initial version.
+
+Changed:
+@li ktxWriteKTXF to check the validity of the type & format combinations
+    passed to it.
+
+Fixed:
+@li Public Bugzilla <a href="http://www.khronos.org/bugzilla/show_bug.cgi?id=999">999</a>: 16-bit luminance texture cannot be written.
+@li compile warnings from compilers stricter than MS Visual C++. Thanks to
+    Pavel Rotjberg.
+
+@section v3 Version 2.0
+Added:
+@li support for decoding ETC2 and EAC formats in the absence of a hardware
+    decoder.
+@li support for converting textures with legacy LUMINANCE, LUMINANCE_ALPHA,
+    etc. formats to the equivalent R, RG, etc. format with an
+    appropriate swizzle, when loading in OpenGL Core Profile contexts.
+@li ktxErrorString function to return a string corresponding to an error code.
+@li tests for ktxLoadTexture[FN] that run under OpenGL ES 3.0 and OpenGL 3.3.
+    The latter includes an EGL on WGL wrapper that makes porting apps between
+    OpenGL ES and OpenGL easier on Windows.
+@li more texture formats to ktxLoadTexture[FN] and toktx tests.
+
+Changed:
+@li ktxLoadTexture[FMN] to discover the capabilities of the GL context at
+    run time and load textures, or not, according to those capabilities.
+
+Fixed:
+@li failure of ktxWriteKTXF to pad image rows to 4 bytes as required by the KTX
+    format.
+@li ktxWriteKTXF exiting with KTX_FILE_WRITE_ERROR when attempting to write
+    more than 1 byte of face-LOD padding.
+
+Although there is only a very minor API change, the addition of ktxErrorString,
+the functional changes are large enough to justify bumping the major revision
+number.
+
+@section v2 Version 1.0.1
+Implemented ktxLoadTextureM.
+Fixed the following:
+@li Public Bugzilla <a href="http://www.khronos.org/bugzilla/show_bug.cgi?id=571">571</a>: crash when null passed for pIsMipmapped.
+@li Public Bugzilla <a href="http://www.khronos.org/bugzilla/show_bug.cgi?id=572">572</a>: memory leak when unpacking ETC textures.
+@li Public Bugzilla <a href="http://www.khronos.org/bugzilla/show_bug.cgi?id=573">573</a>: potential crash when unpacking ETC textures with unused padding pixels.
+@li Public Bugzilla <a href="http://www.khronos.org/bugzilla/show_bug.cgi?id=576">576</a>: various small fixes.
+
+Thanks to Krystian Bigaj for the ktxLoadTextureM implementation and these fixes.
+
+@section v1 Version 1.0
+Initial release.
+
+*/
+
+#endif /* KTX_H_A55A6F00956F42F3A137C11929827FE1 */
diff --git a/external/Vulkan/external/ktx/include/ktxvulkan.h b/external/Vulkan/external/ktx/include/ktxvulkan.h
new file mode 100644
index 0000000000000000000000000000000000000000..23647b4c5ebfc64d092eb7d9a397ff525a2461d4
--- /dev/null
+++ b/external/Vulkan/external/ktx/include/ktxvulkan.h
@@ -0,0 +1,170 @@
+/* -*- tab-width: 4; -*- */
+/* vi: set sw=2 ts=4 expandtab: */
+
+#ifndef KTX_H_C54B42AEE39611E68E1E4FF8C51D1C66
+#define KTX_H_C54B42AEE39611E68E1E4FF8C51D1C66
+
+/*
+ * ©2017 Mark Callow.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @internal
+ * @file
+ * @~English
+ *
+ * @brief Declares the public functions and structures of the
+ *        KTX Vulkan texture loading API.
+ *
+ * A separate header file is used to avoid extra dependencies for those not
+ * using Vulkan. The nature of the Vulkan API, rampant structures and enums,
+ * means that vulkan.h must be included. The alternative is duplicating
+ * unattractively large parts of it.
+ *
+ * @author Mark Callow, Edgewise Consulting
+ *
+ * $Date$
+ */
+
+#include <ktx.h>
+#include <vulkan/vulkan.h>
+
+#if 0
+/* Avoid Vulkan include file */
+#define VK_DEFINE_HANDLE(object) typedef struct object##_T* object;
+
+#if defined(__LP64__) || defined(_WIN64) || defined(__x86_64__) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(__powerpc64__)
+        #define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object) typedef struct object##_T *object;
+#else
+        #define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object) typedef uint64_t object;
+#endif
+
+VK_DEFINE_HANDLE(VkPhysicalDevice)
+VK_DEFINE_HANDLE(VkDevice)
+VK_DEFINE_HANDLE(VkQueue)
+VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkCommandPool)
+VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkDeviceMemory)
+VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkImage)
+VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkImageView)
+VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkSampler)
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @class ktxVulkanTexture
+ * @brief Struct for returning information about the Vulkan texture image
+ *        created by the ktxTexture_VkUpload* functions.
+ *
+ * Creation of these objects is internal to the upload functions.
+ */
+typedef struct ktxVulkanTexture
+{
+    VkImage image; /*!< Handle to the Vulkan image created by the loader. */
+    VkFormat imageFormat;     /*!< Format of the image data. */
+    VkImageLayout imageLayout; /*!< Layout of the created image. Has the same
+                                    value as @p layout parameter passed to the
+                                    loader. */
+    VkDeviceMemory deviceMemory; /*!< The memory allocated for the image on
+                                  the Vulkan device. */
+    VkImageViewType viewType; /*!< ViewType corresponding to @p image. Reflects
+                                   the dimensionality, cubeness and arrayness
+                                   of the image. */
+    uint32_t width; /*!< The width of the image. */
+    uint32_t height; /*!< The height of the image. */
+    uint32_t depth; /*!< The depth of the image. */
+    uint32_t levelCount; /*!< The number of MIP levels in the image. */
+    uint32_t layerCount; /*!< The number of array layers in the image. */
+} ktxVulkanTexture;
+
+void
+ktxVulkanTexture_Destruct(ktxVulkanTexture* This, VkDevice device,
+                          const VkAllocationCallbacks* pAllocator);
+
+/**
+ * @class ktxVulkanDeviceInfo
+ * @brief Struct for passing information about the Vulkan device on which
+ *        to create images to the texture image loading functions.
+ *
+ * Avoids passing a large number of parameters to each loading function.
+ * Use of ktxVulkanDeviceInfo_create() or ktxVulkanDeviceInfo_construct() to
+ * populate this structure is highly recommended.
+ *
+ * @code
+    ktxVulkanDeviceInfo vdi;
+    ktxVulkanTexture texture;
+ 
+    vdi = ktxVulkanDeviceInfo_create(physicalDevice,
+                                     device,
+                                     queue,
+                                     cmdPool,
+                                     &allocator);
+    ktxLoadVkTextureN("texture_1.ktx", vdi, &texture, NULL, NULL);
+    // ...
+    ktxLoadVkTextureN("texture_n.ktx", vdi, &texture, NULL, NULL);
+    ktxVulkanDeviceInfo_destroy(vdi);
+ * @endcode
+ */
+typedef struct ktxVulkanDeviceInfo {
+    VkPhysicalDevice physicalDevice; /*!< Handle of the physical device. */
+    VkDevice device; /*!< Handle of the logical device. */
+    VkQueue queue; /*!< Handle to the queue to which to submit commands. */
+    VkCommandBuffer cmdBuffer; /*!< Handle of the cmdBuffer to use. */
+    /** Handle of the command pool from which to allocate the command buffer. */
+    VkCommandPool cmdPool;
+    /** Pointer to the allocator to use for the command buffer and created
+     * images.
+     */
+    const VkAllocationCallbacks* pAllocator;
+    /** Memory properties of the Vulkan physical device. */
+    VkPhysicalDeviceMemoryProperties deviceMemoryProperties;
+} ktxVulkanDeviceInfo;
+
+ktxVulkanDeviceInfo*
+ktxVulkanDeviceInfo_Create(VkPhysicalDevice physicalDevice, VkDevice device,
+                           VkQueue queue, VkCommandPool cmdPool,
+                           const VkAllocationCallbacks* pAllocator);
+KTX_error_code
+ktxVulkanDeviceInfo_Construct(ktxVulkanDeviceInfo* This,
+                         VkPhysicalDevice physicalDevice, VkDevice device,
+                         VkQueue queue, VkCommandPool cmdPool,
+                         const VkAllocationCallbacks* pAllocator);
+void
+ktxVulkanDeviceInfo_Destruct(ktxVulkanDeviceInfo* This);
+void
+ktxVulkanDeviceInfo_Destroy(ktxVulkanDeviceInfo* This);
+
+
+KTX_error_code
+ktxTexture_VkUploadEx(ktxTexture* This, ktxVulkanDeviceInfo* vdi,
+                      ktxVulkanTexture* vkTexture,
+                      VkImageTiling tiling,
+                      VkImageUsageFlags usageFlags,
+                      VkImageLayout layout);
+
+KTX_error_code
+ktxTexture_VkUpload(ktxTexture* This, ktxVulkanDeviceInfo* vdi,
+                    ktxVulkanTexture *vkTexture);
+
+VkFormat
+ktxTexture_GetVkFormat(ktxTexture* This);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* KTX_H_A55A6F00956F42F3A137C11929827FE1 */
diff --git a/external/Vulkan/external/ktx/lib/checkheader.c b/external/Vulkan/external/ktx/lib/checkheader.c
new file mode 100644
index 0000000000000000000000000000000000000000..e46e7b7f518d879b57081d3084299e62c342261e
--- /dev/null
+++ b/external/Vulkan/external/ktx/lib/checkheader.c
@@ -0,0 +1,172 @@
+/* -*- tab-width: 4; -*- */
+/* vi: set sw=2 ts=4 expandtab: */
+
+/* $Id: df002b39ae6b9b3b995e7633ff96acf0d285edcc $ */
+
+/*
+ * ©2010-2018 The khronos Group, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @internal
+ * @file checkheader.c
+ * @~English
+ *
+ * @brief Function to verify a KTX file header
+ *
+ * @author Mark Callow, HI Corporation
+ */
+
+/*
+ * Author: Georg Kolling, Imagination Technology with modifications
+ * by Mark Callow, HI Corporation.
+ */
+#include <assert.h>
+#include <string.h>
+
+#include "ktx.h"
+#include "ktxint.h"
+
+/**
+ * @internal
+ * @~English
+ * @brief Check a KTX file header.
+ *
+ * As well as checking that the header identifies a KTX file, the function
+ * sanity checks the values and returns information about the texture in a
+ * struct KTX_supplementary_info.
+ *
+ * @param pHeader   pointer to the KTX header to check
+ * @param pSuppInfo pointer to a KTX_supplementary_info structure in which to
+ *                  return information about the texture.
+ * 
+ * @author Georg Kolling, Imagination Technology
+ * @author Mark Callow, HI Corporation
+ */
+KTX_error_code _ktxCheckHeader(KTX_header* pHeader,
+                               KTX_supplemental_info* pSuppInfo)
+{
+    ktx_uint8_t identifier_reference[12] = KTX_IDENTIFIER_REF;
+    ktx_uint32_t max_dim;
+    
+    assert(pHeader != NULL && pSuppInfo != NULL);
+
+    /* Compare identifier, is this a KTX file? */
+    if (memcmp(pHeader->identifier, identifier_reference, 12) != 0)
+    {
+        return KTX_UNKNOWN_FILE_FORMAT;
+    }
+
+    if (pHeader->endianness == KTX_ENDIAN_REF_REV)
+    {
+        /* Convert endianness of pHeader fields. */
+        _ktxSwapEndian32(&pHeader->glType, 12);
+
+        if (pHeader->glTypeSize != 1 &&
+            pHeader->glTypeSize != 2 &&
+            pHeader->glTypeSize != 4)
+        {
+            /* Only 8-, 16-, and 32-bit types supported so far. */
+            return KTX_FILE_DATA_ERROR;
+        }
+    }
+    else if (pHeader->endianness != KTX_ENDIAN_REF)
+    {
+        return KTX_FILE_DATA_ERROR;
+    }
+
+    /* Check glType and glFormat */
+    pSuppInfo->compressed = 0;
+    if (pHeader->glType == 0 || pHeader->glFormat == 0)
+    {
+        if (pHeader->glType + pHeader->glFormat != 0)
+        {
+            /* either both or none of glType, glFormat must be zero */
+            return KTX_FILE_DATA_ERROR;
+        }
+        pSuppInfo->compressed = 1;
+    }
+    
+    if (pHeader->glFormat == pHeader->glInternalformat) {
+        // glInternalFormat is either unsized (which is no longer and should
+        // never have been supported by libktx) or glFormat is sized.
+        return KTX_FILE_DATA_ERROR;
+    }
+
+    /* Check texture dimensions. KTX files can store 8 types of textures:
+       1D, 2D, 3D, cube, and array variants of these. There is currently
+       no GL extension for 3D array textures. */
+    if ((pHeader->pixelWidth == 0) ||
+        (pHeader->pixelDepth > 0 && pHeader->pixelHeight == 0))
+    {
+        /* texture must have width */
+        /* texture must have height if it has depth */
+        return KTX_FILE_DATA_ERROR; 
+    }
+
+    
+    if (pHeader->pixelDepth > 0)
+    {
+        if (pHeader->numberOfArrayElements > 0)
+        {
+            /* No 3D array textures yet. */
+            return KTX_UNSUPPORTED_TEXTURE_TYPE;
+        }
+        pSuppInfo->textureDimension = 3;
+    }
+    else if (pHeader->pixelHeight > 0)
+    {
+        pSuppInfo->textureDimension = 2;
+    }
+    else
+    {
+        pSuppInfo->textureDimension = 1;
+    }
+
+    if (pHeader->numberOfFaces == 6)
+    {
+        if (pSuppInfo->textureDimension != 2)
+        {
+            /* cube map needs 2D faces */
+            return KTX_FILE_DATA_ERROR;
+        }
+    }
+    else if (pHeader->numberOfFaces != 1)
+    {
+        /* numberOfFaces must be either 1 or 6 */
+        return KTX_FILE_DATA_ERROR;
+    }
+    
+    /* Check number of mipmap levels */
+    if (pHeader->numberOfMipmapLevels == 0)
+    {
+        pSuppInfo->generateMipmaps = 1;
+        pHeader->numberOfMipmapLevels = 1;
+    }
+    else
+    {
+        pSuppInfo->generateMipmaps = 0;
+    }
+
+    /* This test works for arrays too because height or depth will be 0. */
+    max_dim = MAX(MAX(pHeader->pixelWidth, pHeader->pixelHeight), pHeader->pixelDepth);
+    if (max_dim < ((ktx_uint32_t)1 << (pHeader->numberOfMipmapLevels - 1)))
+    {
+        /* Can't have more mip levels than 1 + log2(max(width, height, depth)) */
+        return KTX_FILE_DATA_ERROR;
+    }
+
+    return KTX_SUCCESS;
+}
diff --git a/external/Vulkan/external/ktx/lib/errstr.c b/external/Vulkan/external/ktx/lib/errstr.c
new file mode 100644
index 0000000000000000000000000000000000000000..bf6a695a83455ab8411c91a8f0ea7d133f7e33db
--- /dev/null
+++ b/external/Vulkan/external/ktx/lib/errstr.c
@@ -0,0 +1,70 @@
+/* -*- tab-width: 4; -*- */
+/* vi: set sw=2 ts=4 expandtab: */
+
+/* $Id: 11f526c2587823b49cde3b437746e95b57e3200e $ */
+
+/*
+ * ©2010-2018 The khronos Group, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file errstr.c
+ * @~English
+ *
+ * @brief Function to return a string corresponding to a KTX error code.
+ *
+ * @author Mark Callow, HI Corporation
+ */
+
+#include "ktx.h"
+
+static const char* const errorStrings[] = {
+    "Operation succeeded",                            /* KTX_SUCCESS */
+    "File data is inconsistent with KTX spec.",       /* KTX_FILE_DATA_ERROR */
+    "File open failed",                               /* KTX_FILE_OPEN_FAILED */
+    "Operation would exceed the max file size",       /* KTX_FILE_OVERFLOW */
+    "File read error",                                /* KTX_FILE_READ_ERROR */
+    "File seek error",                                /* KTX_FILE_SEEK_ERROR */
+    "File does not have enough data for request",     /* KTX_FILE_UNEXPECTED_EOF */
+    "File write error",                               /* KTX_FILE_WRITE_ERROR */
+    "GL error occurred",                              /* KTX_GL_ERROR */
+    "Operation not allowed in the current state",     /* KTX_INVALID_OPERATION */
+    "Invalid parameter value",                        /* KTX_INVALID_VALUE */
+    "Key not found",                                  /* KTX_NOT_FOUND */
+    "Out of memory",                                  /* KTX_OUT_OF_MEMORY */
+    "Not a KTX file",                                 /* KTX_UNKNOWN_FILE_FORMAT */
+    "Texture type not supported by GL context"        /* KTX_UNSUPPORTED_TEXTURE_TYPE */
+};
+static const int lastErrorCode = (sizeof(errorStrings) / sizeof(char*)) - 1;
+
+
+/**
+ * @~English
+ * @brief Return a string corresponding to a KTX error code.
+ *
+ * @param error     the error code for which to return a string
+ *
+ * @return pointer to the message string.
+ *
+ * @internal Use UTF-8 for translated message strings.
+ * 
+ * @author Mark Callow, HI Corporation
+ */
+const char* const ktxErrorString(KTX_error_code error)
+{
+    if (error > lastErrorCode)
+        return "Unrecognized error code";
+    return errorStrings[error];
+}
diff --git a/external/Vulkan/external/ktx/lib/etcdec.cxx b/external/Vulkan/external/ktx/lib/etcdec.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..5731979b42879bed9701f582cdbaaf35674ff47a
--- /dev/null
+++ b/external/Vulkan/external/ktx/lib/etcdec.cxx
@@ -0,0 +1,1845 @@
+/*
+ 
+@~English
+@page license License
+ 
+@section etcdec etcdec.cxx License
+ 
+etcdec.cxx is made available under the terms and conditions of the following
+License Agreement.
+
+Software License Agreement
+
+PLEASE REVIEW THE FOLLOWING TERMS AND CONDITIONS PRIOR TO USING THE
+ERICSSON TEXTURE COMPRESSION CODEC SOFTWARE (THE "SOFTWARE"). THE USE
+OF THE SOFTWARE IS SUBJECT TO THE TERMS AND CONDITIONS OF THE
+FOLLOWING SOFTWARE LICENSE AGREEMENT (THE "SLA"). IF YOU DO NOT ACCEPT
+SUCH TERMS AND CONDITIONS YOU MAY NOT USE THE SOFTWARE.
+
+Subject to the terms and conditions of the SLA, the licensee of the
+Software (the "Licensee") hereby, receives a non-exclusive,
+non-transferable, limited, free-of-charge, perpetual and worldwide
+license, to copy, use, distribute and modify the Software, but only
+for the purpose of developing, manufacturing, selling, using and
+distributing products including the Software in binary form, which
+products are used for compression and/or decompression according to
+the Khronos standard specifications OpenGL, OpenGL ES and
+WebGL. Notwithstanding anything of the above, Licensee may distribute
+[etcdec.cxx] in source code form provided (i) it is in unmodified
+form; and (ii) it is included in software owned by Licensee.
+
+If Licensee institutes, or threatens to institute, patent litigation
+against Ericsson or Ericsson's affiliates for using the Software for
+developing, having developed, manufacturing, having manufactured,
+selling, offer for sale, importing, using, leasing, operating,
+repairing and/or distributing products (i) within the scope of the
+Khronos framework; or (ii) using software or other intellectual
+property rights owned by Ericsson or its affiliates and provided under
+the Khronos framework, Ericsson shall have the right to terminate this
+SLA with immediate effect. Moreover, if Licensee institutes, or
+threatens to institute, patent litigation against any other licensee
+of the Software for using the Software in products within the scope of
+the Khronos framework, Ericsson shall have the right to terminate this
+SLA with immediate effect. However, should Licensee institute, or
+threaten to institute, patent litigation against any other licensee of
+the Software based on such other licensee's use of any other software
+together with the Software, then Ericsson shall have no right to
+terminate this SLA.
+
+This SLA does not transfer to Licensee any ownership to any Ericsson
+or third party intellectual property rights. All rights not expressly
+granted by Ericsson under this SLA are hereby expressly
+reserved. Furthermore, nothing in this SLA shall be construed as a
+right to use or sell products in a manner which conveys or purports to
+convey whether explicitly, by principles of implied license, or
+otherwise, any rights to any third party, under any patent of Ericsson
+or of Ericsson's affiliates covering or relating to any combination of
+the Software with any other software or product (not licensed
+hereunder) where the right applies specifically to the combination and
+not to the software or product itself.
+
+THE SOFTWARE IS PROVIDED "AS IS". ERICSSON MAKES NO REPRESENTATIONS OF
+ANY KIND, EXTENDS NO WARRANTIES OR CONDITIONS OF ANY KIND, EITHER
+EXPRESS, IMPLIED OR STATUTORY; INCLUDING, BUT NOT LIMITED TO, EXPRESS,
+IMPLIED OR STATUTORY WARRANTIES OR CONDITIONS OF TITLE,
+MERCHANTABILITY, SATISFACTORY QUALITY, SUITABILITY, AND FITNESS FOR A
+PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE
+OF THE SOFTWARE IS WITH THE LICENSEE. SHOULD THE SOFTWARE PROVE
+DEFECTIVE, THE LICENSEE ASSUMES THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION. ERICSSON MAKES NO WARRANTY THAT THE MANUFACTURE,
+SALE, OFFERING FOR SALE, DISTRIBUTION, LEASE, USE OR IMPORTATION UNDER
+THE SLA WILL BE FREE FROM INFRINGEMENT OF PATENTS, COPYRIGHTS OR OTHER
+INTELLECTUAL PROPERTY RIGHTS OF OTHERS, AND THE VALIDITY OF THE
+LICENSE AND THE SLA ARE SUBJECT TO LICENSEE'S SOLE RESPONSIBILITY TO
+MAKE SUCH DETERMINATION AND ACQUIRE SUCH LICENSES AS MAY BE NECESSARY
+WITH RESPECT TO PATENTS, COPYRIGHT AND OTHER INTELLECTUAL PROPERTY OF
+THIRD PARTIES.
+
+THE LICENSEE ACKNOWLEDGES AND ACCEPTS THAT THE SOFTWARE (I) IS NOT
+LICENSED FOR; (II) IS NOT DESIGNED FOR OR INTENDED FOR; AND (III) MAY
+NOT BE USED FOR; ANY MISSION CRITICAL APPLICATIONS SUCH AS, BUT NOT
+LIMITED TO OPERATION OF NUCLEAR OR HEALTHCARE COMPUTER SYSTEMS AND/OR
+NETWORKS, AIRCRAFT OR TRAIN CONTROL AND/OR COMMUNICATION SYSTEMS OR
+ANY OTHER COMPUTER SYSTEMS AND/OR NETWORKS OR CONTROL AND/OR
+COMMUNICATION SYSTEMS ALL IN WHICH CASE THE FAILURE OF THE SOFTWARE
+COULD LEAD TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL, MATERIAL OR
+ENVIRONMENTAL DAMAGE. LICENSEE'S RIGHTS UNDER THIS LICENSE WILL
+TERMINATE AUTOMATICALLY AND IMMEDIATELY WITHOUT NOTICE IF LICENSEE
+FAILS TO COMPLY WITH THIS PARAGRAPH.
+
+IN NO EVENT SHALL ERICSSON BE LIABLE FOR ANY DAMAGES WHATSOEVER,
+INCLUDING BUT NOT LIMITED TO PERSONAL INJURY, ANY GENERAL, SPECIAL,
+INDIRECT, INCIDENTAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF OR IN
+CONNECTION WITH THE USE OR INABILITY TO USE THE SOFTWARE (INCLUDING
+BUT NOT LIMITED TO LOSS OF PROFITS, BUSINESS INTERUPTIONS, OR ANY
+OTHER COMMERCIAL DAMAGES OR LOSSES, LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY THE LICENSEE OR THIRD
+PARTIES OR A FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER
+SOFTWARE) REGARDLESS OF THE THEORY OF LIABILITY (CONTRACT, TORT, OR
+OTHERWISE), EVEN IF THE LICENSEE OR ANY OTHER PARTY HAS BEEN ADVISED
+OF THE POSSIBILITY OF SUCH DAMAGES.
+
+Licensee acknowledges that "ERICSSON ///" is the corporate trademark
+of Telefonaktiebolaget LM Ericsson and that both "Ericsson" and the
+figure "///" are important features of the trade names of
+Telefonaktiebolaget LM Ericsson. Nothing contained in these terms and
+conditions shall be deemed to grant Licensee any right, title or
+interest in the word "Ericsson" or the figure "///". No delay or
+omission by Ericsson to exercise any right or power shall impair any
+such right or power to be construed to be a waiver thereof. Consent by
+Ericsson to, or waiver of, a breach by the Licensee shall not
+constitute consent to, waiver of, or excuse for any other different or
+subsequent breach.
+
+This SLA shall be governed by the substantive law of Sweden. Any
+dispute, controversy or claim arising out of or in connection with
+this SLA, or the breach, termination or invalidity thereof, shall be
+submitted to the exclusive jurisdiction of the Swedish Courts.
+
+*/
+
+//// etcpack v2.74
+//// 
+//// NO WARRANTY 
+//// 
+//// BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE THE PROGRAM IS PROVIDED
+//// "AS IS". ERICSSON MAKES NO REPRESENTATIONS OF ANY KIND, EXTENDS NO
+//// WARRANTIES OR CONDITIONS OF ANY KIND; EITHER EXPRESS, IMPLIED OR
+//// STATUTORY; INCLUDING, BUT NOT LIMITED TO, EXPRESS, IMPLIED OR
+//// STATUTORY WARRANTIES OR CONDITIONS OF TITLE, MERCHANTABILITY,
+//// SATISFACTORY QUALITY, SUITABILITY AND FITNESS FOR A PARTICULAR
+//// PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+//// PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME
+//// THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. ERICSSON
+//// MAKES NO WARRANTY THAT THE MANUFACTURE, SALE, OFFERING FOR SALE,
+//// DISTRIBUTION, LEASE, USE OR IMPORTATION UNDER THE LICENSE WILL BE FREE
+//// FROM INFRINGEMENT OF PATENTS, COPYRIGHTS OR OTHER INTELLECTUAL
+//// PROPERTY RIGHTS OF OTHERS, AND THE VALIDITY OF THE LICENSE IS SUBJECT
+//// TO YOUR SOLE RESPONSIBILITY TO MAKE SUCH DETERMINATION AND ACQUIRE
+//// SUCH LICENSES AS MAY BE NECESSARY WITH RESPECT TO PATENTS, COPYRIGHT
+//// AND OTHER INTELLECTUAL PROPERTY OF THIRD PARTIES.
+//// 
+//// FOR THE AVOIDANCE OF DOUBT THE PROGRAM (I) IS NOT LICENSED FOR; (II)
+//// IS NOT DESIGNED FOR OR INTENDED FOR; AND (III) MAY NOT BE USED FOR;
+//// ANY MISSION CRITICAL APPLICATIONS SUCH AS, BUT NOT LIMITED TO
+//// OPERATION OF NUCLEAR OR HEALTHCARE COMPUTER SYSTEMS AND/OR NETWORKS,
+//// AIRCRAFT OR TRAIN CONTROL AND/OR COMMUNICATION SYSTEMS OR ANY OTHER
+//// COMPUTER SYSTEMS AND/OR NETWORKS OR CONTROL AND/OR COMMUNICATION
+//// SYSTEMS ALL IN WHICH CASE THE FAILURE OF THE PROGRAM COULD LEAD TO
+//// DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL, MATERIAL OR ENVIRONMENTAL
+//// DAMAGE. YOUR RIGHTS UNDER THIS LICENSE WILL TERMINATE AUTOMATICALLY
+//// AND IMMEDIATELY WITHOUT NOTICE IF YOU FAIL TO COMPLY WITH THIS
+//// PARAGRAPH.
+//// 
+//// IN NO EVENT WILL ERICSSON, BE LIABLE FOR ANY DAMAGES WHATSOEVER,
+//// INCLUDING BUT NOT LIMITED TO PERSONAL INJURY, ANY GENERAL, SPECIAL,
+//// INDIRECT, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN
+//// CONNECTION WITH THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT
+//// NOT LIMITED TO LOSS OF PROFITS, BUSINESS INTERUPTIONS, OR ANY OTHER
+//// COMMERCIAL DAMAGES OR LOSSES, LOSS OF DATA OR DATA BEING RENDERED
+//// INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF
+//// THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS) REGARDLESS OF THE
+//// THEORY OF LIABILITY (CONTRACT, TORT OR OTHERWISE), EVEN IF SUCH HOLDER
+//// OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+//// 
+//// (C) Ericsson AB 2013. All Rights Reserved.
+//// 
+
+#include <stdio.h>
+#include <stdlib.h>
+
+// Typedefs
+typedef unsigned char uint8;
+typedef unsigned short uint16;
+typedef short int16;
+
+// Macros to help with bit extraction/insertion
+#define SHIFT(size,startpos) ((startpos)-(size)+1)
+#define MASK(size, startpos) (((2<<(size-1))-1) << SHIFT(size,startpos))
+#define PUTBITS( dest, data, size, startpos) dest = ((dest & ~MASK(size, startpos)) | ((data << SHIFT(size, startpos)) & MASK(size,startpos)))
+#define SHIFTHIGH(size, startpos) (((startpos)-32)-(size)+1)
+#define MASKHIGH(size, startpos) (((1<<(size))-1) << SHIFTHIGH(size,startpos))
+#define PUTBITSHIGH(dest, data, size, startpos) dest = ((dest & ~MASKHIGH(size, startpos)) | ((data << SHIFTHIGH(size, startpos)) & MASKHIGH(size,startpos)))
+#define GETBITS(source, size, startpos)  (( (source) >> ((startpos)-(size)+1) ) & ((1<<(size)) -1))
+#define GETBITSHIGH(source, size, startpos)  (( (source) >> (((startpos)-32)-(size)+1) ) & ((1<<(size)) -1))
+#ifndef PGMOUT
+#define PGMOUT 0
+#endif
+// Thumb macros and definitions
+#define	R_BITS59T 4
+#define G_BITS59T 4
+#define	B_BITS59T 4
+#define	R_BITS58H 4
+#define G_BITS58H 4
+#define	B_BITS58H 4
+#define	MAXIMUM_ERROR (255*255*16*1000)
+#define R 0
+#define G 1
+#define B 2
+#define BLOCKHEIGHT 4
+#define BLOCKWIDTH 4
+#define BINPOW(power) (1<<(power))
+#define	TABLE_BITS_59T 3
+#define	TABLE_BITS_58H 3
+
+// Helper Macros
+#define CLAMP(ll,x,ul) (((x)<(ll)) ? (ll) : (((x)>(ul)) ? (ul) : (x)))
+#define JAS_ROUND(x) (((x) < 0.0 ) ? ((int)((x)-0.5)) : ((int)((x)+0.5)))
+
+#define RED_CHANNEL(img,width,x,y,channels)   img[channels*(y*width+x)+0]
+#define GREEN_CHANNEL(img,width,x,y,channels) img[channels*(y*width+x)+1]
+#define BLUE_CHANNEL(img,width,x,y,channels)  img[channels*(y*width+x)+2]
+#define ALPHA_CHANNEL(img,width,x,y,channels)  img[channels*(y*width+x)+3]
+
+
+// Global tables
+static uint8 table59T[8] = {3,6,11,16,23,32,41,64};  // 3-bit table for the 59 bit T-mode
+static uint8 table58H[8] = {3,6,11,16,23,32,41,64};  // 3-bit table for the 58 bit H-mode
+static int compressParams[16][4] = {{-8, -2,  2, 8}, {-8, -2,  2, 8}, {-17, -5, 5, 17}, {-17, -5, 5, 17}, {-29, -9, 9, 29}, {-29, -9, 9, 29}, {-42, -13, 13, 42}, {-42, -13, 13, 42}, {-60, -18, 18, 60}, {-60, -18, 18, 60}, {-80, -24, 24, 80}, {-80, -24, 24, 80}, {-106, -33, 33, 106}, {-106, -33, 33, 106}, {-183, -47, 47, 183}, {-183, -47, 47, 183}};
+static int unscramble[4] = {2, 3, 1, 0};
+int alphaTableInitialized = 0;
+int alphaTable[256][8];
+int alphaBase[16][4] = {	
+              {-15,-9,-6,-3},
+							{-13,-10,-7,-3},
+							{-13,-8,-5,-2},
+							{-13,-6,-4,-2},
+							{-12,-8,-6,-3},
+							{-11,-9,-7,-3},
+							{-11,-8,-7,-4},
+							{-11,-8,-5,-3},
+							{ -10,-8,-6,-2},
+							{ -10,-8,-5,-2},
+							{ -10,-8,-4,-2},
+							{ -10,-7,-5,-2},
+							{ -10,-7,-4,-3},
+							{ -10,-3,-2, -1},
+							{ -9,-8,-6,-4},
+							{ -9,-7,-5,-3}
+											};
+
+// Global variables
+int formatSigned = 0;
+
+// Enums
+ enum{PATTERN_H = 0, 
+      PATTERN_T = 1};
+
+
+// Code used to create the valtab
+// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2013. All Rights Reserved.
+void setupAlphaTable() 
+{
+  if(alphaTableInitialized)
+    return;
+  alphaTableInitialized = 1;
+
+	//read table used for alpha compression
+	int buf;
+	for(int i = 16; i<32; i++) 
+	{
+		for(int j=0; j<8; j++) 
+		{
+			buf=alphaBase[i-16][3-j%4];
+			if(j<4)
+				alphaTable[i][j]=buf;
+			else
+				alphaTable[i][j]=(-buf-1);
+		}
+	}
+	
+	//beyond the first 16 values, the rest of the table is implicit.. so calculate that!
+	for(int i=0; i<256; i++) 
+	{
+		//fill remaining slots in table with multiples of the first ones.
+		int mul = i/16;
+		int old = 16+i%16;
+		for(int j = 0; j<8; j++) 
+		{
+			alphaTable[i][j]=alphaTable[old][j]*mul;
+			//note: we don't do clamping here, though we could, because we'll be clamped afterwards anyway.
+		}
+	}
+}
+
+// Read a word in big endian style
+// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2013. All Rights Reserved.
+void read_big_endian_2byte_word(unsigned short *blockadr, FILE *f)
+{
+	uint8 bytes[2];
+	unsigned short block;
+	// This is to silence -Wunused-result from GCC 4.8+.
+	size_t numitems; 
+
+	numitems = fread(&bytes[0], 1, 1, f);
+	numitems = fread(&bytes[1], 1, 1, f);
+
+	block = 0;
+	block |= bytes[0];
+	block = block << 8;
+	block |= bytes[1];
+
+	blockadr[0] = block;
+}
+
+// Read a word in big endian style
+// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2013. All Rights Reserved.
+void read_big_endian_4byte_word(unsigned int *blockadr, FILE *f)
+{
+	uint8 bytes[4];
+	unsigned int block;
+	// This is to silence -Wunused-result from GCC 4.8+.
+	size_t numitems; 
+
+	numitems = fread(&bytes[0], 1, 1, f);
+	numitems = fread(&bytes[1], 1, 1, f);
+	numitems = fread(&bytes[2], 1, 1, f);
+	numitems = fread(&bytes[3], 1, 1, f);
+
+	block = 0;
+	block |= bytes[0];
+	block = block << 8;
+	block |= bytes[1];
+	block = block << 8;
+	block |= bytes[2];
+	block = block << 8;
+	block |= bytes[3];
+
+	blockadr[0] = block;
+}
+
+// The format stores the bits for the three extra modes in a roundabout way to be able to
+// fit them without increasing the bit rate. This function converts them into something
+// that is easier to work with. 
+// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2013. All Rights Reserved.
+void unstuff57bits(unsigned int planar_word1, unsigned int planar_word2, unsigned int &planar57_word1, unsigned int &planar57_word2)
+{
+	// Get bits from twotimer configuration for 57 bits
+	// 
+	// Go to this bit layout:
+	//
+	//      63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 
+	//      -----------------------------------------------------------------------------------------------
+	//     |R0               |G01G02              |B01B02  ;B03     |RH1           |RH2|GH                 |
+	//      -----------------------------------------------------------------------------------------------
+	//
+	//      31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10  9  8  7  6  5  4  3  2  1  0
+	//      -----------------------------------------------------------------------------------------------
+	//     |BH               |RV               |GV                  |BV                | not used          |   
+	//      -----------------------------------------------------------------------------------------------
+	//
+	//  From this:
+	// 
+	//      63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 
+	//      ------------------------------------------------------------------------------------------------
+	//     |//|R0               |G01|/|G02              |B01|/ // //|B02  |//|B03     |RH1           |df|RH2|
+	//      ------------------------------------------------------------------------------------------------
+	//
+	//      31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10  9  8  7  6  5  4  3  2  1  0
+	//      -----------------------------------------------------------------------------------------------
+	//     |GH                  |BH               |RV               |GV                   |BV              |
+	//      -----------------------------------------------------------------------------------------------
+	//
+	//      63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34  33  32 
+	//      ---------------------------------------------------------------------------------------------------
+	//     | base col1    | dcol 2 | base col1    | dcol 2 | base col 1   | dcol 2 | table  | table  |diff|flip|
+	//     | R1' (5 bits) | dR2    | G1' (5 bits) | dG2    | B1' (5 bits) | dB2    | cw 1   | cw 2   |bit |bit |
+	//      ---------------------------------------------------------------------------------------------------
+
+	uint8 RO, GO1, GO2, BO1, BO2, BO3, RH1, RH2, GH, BH, RV, GV, BV;
+
+	RO  = GETBITSHIGH( planar_word1, 6, 62);
+	GO1 = GETBITSHIGH( planar_word1, 1, 56);
+	GO2 = GETBITSHIGH( planar_word1, 6, 54);
+	BO1 = GETBITSHIGH( planar_word1, 1, 48);
+	BO2 = GETBITSHIGH( planar_word1, 2, 44);
+	BO3 = GETBITSHIGH( planar_word1, 3, 41);
+	RH1 = GETBITSHIGH( planar_word1, 5, 38);
+	RH2 = GETBITSHIGH( planar_word1, 1, 32);
+	GH  = GETBITS(     planar_word2, 7, 31);
+	BH  = GETBITS(     planar_word2, 6, 24);
+	RV  = GETBITS(     planar_word2, 6, 18);
+	GV  = GETBITS(     planar_word2, 7, 12);
+	BV  = GETBITS(     planar_word2, 6,  5);
+
+	planar57_word1 = 0; planar57_word2 = 0;
+	PUTBITSHIGH( planar57_word1, RO,  6, 63);
+	PUTBITSHIGH( planar57_word1, GO1, 1, 57);
+	PUTBITSHIGH( planar57_word1, GO2, 6, 56);
+	PUTBITSHIGH( planar57_word1, BO1, 1, 50);
+	PUTBITSHIGH( planar57_word1, BO2, 2, 49);
+	PUTBITSHIGH( planar57_word1, BO3, 3, 47);
+	PUTBITSHIGH( planar57_word1, RH1, 5, 44);
+	PUTBITSHIGH( planar57_word1, RH2, 1, 39);
+	PUTBITSHIGH( planar57_word1, GH, 7, 38);
+	PUTBITS(     planar57_word2, BH, 6, 31);
+	PUTBITS(     planar57_word2, RV, 6, 25);
+	PUTBITS(     planar57_word2, GV, 7, 19);
+	PUTBITS(     planar57_word2, BV, 6, 12);
+}
+
+// The format stores the bits for the three extra modes in a roundabout way to be able to
+// fit them without increasing the bit rate. This function converts them into something
+// that is easier to work with. 
+// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2013. All Rights Reserved.
+void unstuff58bits(unsigned int thumbH_word1, unsigned int thumbH_word2, unsigned int &thumbH58_word1, unsigned int &thumbH58_word2)
+{
+	// Go to this layout:
+	//
+	//     |63 62 61 60 59 58|57 56 55 54 53 52 51|50 49|48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33|32   |
+	//     |-------empty-----|part0---------------|part1|part2------------------------------------------|part3|
+	//
+	//  from this:
+	// 
+	//      63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 
+	//      --------------------------------------------------------------------------------------------------|
+	//     |//|part0               |// // //|part1|//|part2                                          |df|part3|
+	//      --------------------------------------------------------------------------------------------------|
+
+	unsigned int part0, part1, part2, part3;
+
+	// move parts
+	part0 = GETBITSHIGH( thumbH_word1, 7, 62);
+	part1 = GETBITSHIGH( thumbH_word1, 2, 52);
+	part2 = GETBITSHIGH( thumbH_word1,16, 49);
+	part3 = GETBITSHIGH( thumbH_word1, 1, 32);
+	thumbH58_word1 = 0;
+	PUTBITSHIGH( thumbH58_word1, part0,  7, 57);
+	PUTBITSHIGH( thumbH58_word1, part1,  2, 50);
+	PUTBITSHIGH( thumbH58_word1, part2, 16, 48);
+	PUTBITSHIGH( thumbH58_word1, part3,  1, 32);
+
+	thumbH58_word2 = thumbH_word2;
+}
+
+// The format stores the bits for the three extra modes in a roundabout way to be able to
+// fit them without increasing the bit rate. This function converts them into something
+// that is easier to work with. 
+// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2013. All Rights Reserved.
+void unstuff59bits(unsigned int thumbT_word1, unsigned int thumbT_word2, unsigned int &thumbT59_word1, unsigned int &thumbT59_word2)
+{
+	// Get bits from twotimer configuration 59 bits. 
+	// 
+	// Go to this bit layout:
+	//
+	//     |63 62 61 60 59|58 57 56 55|54 53 52 51|50 49 48 47|46 45 44 43|42 41 40 39|38 37 36 35|34 33 32|
+	//     |----empty-----|---red 0---|--green 0--|--blue 0---|---red 1---|--green 1--|--blue 1---|--dist--|
+	//
+	//     |31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00|
+	//     |----------------------------------------index bits---------------------------------------------|
+	//
+	//
+	//  From this:
+	// 
+	//      63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 
+	//      -----------------------------------------------------------------------------------------------
+	//     |// // //|R0a  |//|R0b  |G0         |B0         |R1         |G1         |B1          |da  |df|db|
+	//      -----------------------------------------------------------------------------------------------
+	//
+	//     |31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00|
+	//     |----------------------------------------index bits---------------------------------------------|
+	//
+	//      63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 
+	//      -----------------------------------------------------------------------------------------------
+	//     | base col1    | dcol 2 | base col1    | dcol 2 | base col 1   | dcol 2 | table  | table  |df|fp|
+	//     | R1' (5 bits) | dR2    | G1' (5 bits) | dG2    | B1' (5 bits) | dB2    | cw 1   | cw 2   |bt|bt|
+	//      ------------------------------------------------------------------------------------------------
+
+	uint8 R0a;
+
+	// Fix middle part
+	thumbT59_word1 = thumbT_word1 >> 1;
+	// Fix db (lowest bit of d)
+	PUTBITSHIGH( thumbT59_word1, thumbT_word1,  1, 32);
+	// Fix R0a (top two bits of R0)
+	R0a = GETBITSHIGH( thumbT_word1, 2, 60);
+	PUTBITSHIGH( thumbT59_word1, R0a,  2, 58);
+
+	// Zero top part (not needed)
+	PUTBITSHIGH( thumbT59_word1, 0,  5, 63);
+
+	thumbT59_word2 = thumbT_word2;
+}
+
+// The color bits are expanded to the full color
+// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2013. All Rights Reserved.
+void decompressColor(int R_B, int G_B, int B_B, uint8 (colors_RGB444)[2][3], uint8 (colors)[2][3]) 
+{
+	// The color should be retrieved as:
+	//
+	// c = round(255/(r_bits^2-1))*comp_color
+	//
+	// This is similar to bit replication
+	// 
+	// Note -- this code only work for bit replication from 4 bits and up --- 3 bits needs
+	// two copy operations.
+
+ 	colors[0][R] = (colors_RGB444[0][R] << (8 - R_B)) | (colors_RGB444[0][R] >> (R_B - (8-R_B)) );
+ 	colors[0][G] = (colors_RGB444[0][G] << (8 - G_B)) | (colors_RGB444[0][G] >> (G_B - (8-G_B)) );
+ 	colors[0][B] = (colors_RGB444[0][B] << (8 - B_B)) | (colors_RGB444[0][B] >> (B_B - (8-B_B)) );
+ 	colors[1][R] = (colors_RGB444[1][R] << (8 - R_B)) | (colors_RGB444[1][R] >> (R_B - (8-R_B)) );
+ 	colors[1][G] = (colors_RGB444[1][G] << (8 - G_B)) | (colors_RGB444[1][G] >> (G_B - (8-G_B)) );
+ 	colors[1][B] = (colors_RGB444[1][B] << (8 - B_B)) | (colors_RGB444[1][B] >> (B_B - (8-B_B)) );
+}
+
+void calculatePaintColors59T(uint8 d, uint8 p, uint8 (colors)[2][3], uint8 (possible_colors)[4][3]) 
+{
+	//////////////////////////////////////////////
+	//
+	//		C3      C1		C4----C1---C2
+	//		|		|			  |
+	//		|		|			  |
+	//		|-------|			  |
+	//		|		|			  |
+	//		|		|			  |
+	//		C4      C2			  C3
+	//
+	//////////////////////////////////////////////
+
+	// C4
+	possible_colors[3][R] = CLAMP(0,colors[1][R] - table59T[d],255);
+	possible_colors[3][G] = CLAMP(0,colors[1][G] - table59T[d],255);
+	possible_colors[3][B] = CLAMP(0,colors[1][B] - table59T[d],255);
+	
+	if (p == PATTERN_T) 
+	{
+		// C3
+		possible_colors[0][R] = colors[0][R];
+		possible_colors[0][G] = colors[0][G];
+		possible_colors[0][B] = colors[0][B];
+		// C2
+		possible_colors[1][R] = CLAMP(0,colors[1][R] + table59T[d],255);
+		possible_colors[1][G] = CLAMP(0,colors[1][G] + table59T[d],255);
+		possible_colors[1][B] = CLAMP(0,colors[1][B] + table59T[d],255);
+		// C1
+		possible_colors[2][R] = colors[1][R];
+		possible_colors[2][G] = colors[1][G];
+		possible_colors[2][B] = colors[1][B];
+
+	} 
+	else 
+	{
+		printf("Invalid pattern. Terminating");
+		exit(1);
+	}
+}
+// Decompress a T-mode block (simple packing)
+// Simple 59T packing:
+//|63 62 61 60 59|58 57 56 55|54 53 52 51|50 49 48 47|46 45 44 43|42 41 40 39|38 37 36 35|34 33 32|
+//|----empty-----|---red 0---|--green 0--|--blue 0---|---red 1---|--green 1--|--blue 1---|--dist--|
+//
+//|31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00|
+//|----------------------------------------index bits---------------------------------------------|
+// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2013. All Rights Reserved.
+void decompressBlockTHUMB59Tc(unsigned int block_part1, unsigned int block_part2, uint8 *img,int width,int height,int startx,int starty, int channels)
+{
+	uint8 colorsRGB444[2][3];
+	uint8 colors[2][3];
+	uint8 paint_colors[4][3];
+	uint8 distance;
+	uint8 block_mask[4][4];
+
+	// First decode left part of block.
+	colorsRGB444[0][R]= GETBITSHIGH(block_part1, 4, 58);
+	colorsRGB444[0][G]= GETBITSHIGH(block_part1, 4, 54);
+	colorsRGB444[0][B]= GETBITSHIGH(block_part1, 4, 50);
+
+	colorsRGB444[1][R]= GETBITSHIGH(block_part1, 4, 46);
+	colorsRGB444[1][G]= GETBITSHIGH(block_part1, 4, 42);
+	colorsRGB444[1][B]= GETBITSHIGH(block_part1, 4, 38);
+
+	distance   = GETBITSHIGH(block_part1, TABLE_BITS_59T, 34);
+
+	// Extend the two colors to RGB888	
+	decompressColor(R_BITS59T, G_BITS59T, B_BITS59T, colorsRGB444, colors);	
+	calculatePaintColors59T(distance, PATTERN_T, colors, paint_colors);
+	
+	// Choose one of the four paint colors for each texel
+	for (uint8 x = 0; x < BLOCKWIDTH; ++x) 
+	{
+		for (uint8 y = 0; y < BLOCKHEIGHT; ++y) 
+		{
+			//block_mask[x][y] = GETBITS(block_part2,2,31-(y*4+x)*2);
+			block_mask[x][y] = GETBITS(block_part2,1,(y+x*4)+16)<<1;
+			block_mask[x][y] |= GETBITS(block_part2,1,(y+x*4));
+			img[channels*((starty+y)*width+startx+x)+R] =
+				CLAMP(0,paint_colors[block_mask[x][y]][R],255); // RED
+			img[channels*((starty+y)*width+startx+x)+G] =
+				CLAMP(0,paint_colors[block_mask[x][y]][G],255); // GREEN
+			img[channels*((starty+y)*width+startx+x)+B] =
+				CLAMP(0,paint_colors[block_mask[x][y]][B],255); // BLUE
+		}
+	}
+}
+
+void decompressBlockTHUMB59T(unsigned int block_part1, unsigned int block_part2, uint8 *img, int width, int height, int startx, int starty)
+{
+  decompressBlockTHUMB59Tc(block_part1, block_part2, img, width, height, startx, starty, 3);
+}
+
+// Calculate the paint colors from the block colors 
+// using a distance d and one of the H- or T-patterns.
+// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2013. All Rights Reserved.
+void calculatePaintColors58H(uint8 d, uint8 p, uint8 (colors)[2][3], uint8 (possible_colors)[4][3]) 
+{
+	
+	//////////////////////////////////////////////
+	//
+	//		C3      C1		C4----C1---C2
+	//		|		|			  |
+	//		|		|			  |
+	//		|-------|			  |
+	//		|		|			  |
+	//		|		|			  |
+	//		C4      C2			  C3
+	//
+	//////////////////////////////////////////////
+
+	// C4
+	possible_colors[3][R] = CLAMP(0,colors[1][R] - table58H[d],255);
+	possible_colors[3][G] = CLAMP(0,colors[1][G] - table58H[d],255);
+	possible_colors[3][B] = CLAMP(0,colors[1][B] - table58H[d],255);
+	
+	if (p == PATTERN_H) 
+	{ 
+		// C1
+		possible_colors[0][R] = CLAMP(0,colors[0][R] + table58H[d],255);
+		possible_colors[0][G] = CLAMP(0,colors[0][G] + table58H[d],255);
+		possible_colors[0][B] = CLAMP(0,colors[0][B] + table58H[d],255);
+		// C2
+		possible_colors[1][R] = CLAMP(0,colors[0][R] - table58H[d],255);
+		possible_colors[1][G] = CLAMP(0,colors[0][G] - table58H[d],255);
+		possible_colors[1][B] = CLAMP(0,colors[0][B] - table58H[d],255);
+		// C3
+		possible_colors[2][R] = CLAMP(0,colors[1][R] + table58H[d],255);
+		possible_colors[2][G] = CLAMP(0,colors[1][G] + table58H[d],255);
+		possible_colors[2][B] = CLAMP(0,colors[1][B] + table58H[d],255);
+	} 
+	else 
+	{
+		printf("Invalid pattern. Terminating");
+		exit(1);
+	}
+}
+
+// Decompress an H-mode block 
+// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2013. All Rights Reserved.
+void decompressBlockTHUMB58Hc(unsigned int block_part1, unsigned int block_part2, uint8 *img, int width, int height, int startx, int starty, int channels)
+{
+	unsigned int col0, col1;
+	uint8 colors[2][3];
+	uint8 colorsRGB444[2][3];
+	uint8 paint_colors[4][3];
+	uint8 distance;
+	uint8 block_mask[4][4];
+	
+	// First decode left part of block.
+	colorsRGB444[0][R]= GETBITSHIGH(block_part1, 4, 57);
+	colorsRGB444[0][G]= GETBITSHIGH(block_part1, 4, 53);
+	colorsRGB444[0][B]= GETBITSHIGH(block_part1, 4, 49);
+
+	colorsRGB444[1][R]= GETBITSHIGH(block_part1, 4, 45);
+	colorsRGB444[1][G]= GETBITSHIGH(block_part1, 4, 41);
+	colorsRGB444[1][B]= GETBITSHIGH(block_part1, 4, 37);
+
+	distance = 0;
+	distance = (GETBITSHIGH(block_part1, 2, 33)) << 1;
+
+	col0 = GETBITSHIGH(block_part1, 12, 57);
+	col1 = GETBITSHIGH(block_part1, 12, 45);
+
+	if(col0 >= col1)
+	{
+		distance |= 1;
+	}
+
+	// Extend the two colors to RGB888	
+	decompressColor(R_BITS58H, G_BITS58H, B_BITS58H, colorsRGB444, colors);	
+	
+	calculatePaintColors58H(distance, PATTERN_H, colors, paint_colors);
+	
+	// Choose one of the four paint colors for each texel
+	for (uint8 x = 0; x < BLOCKWIDTH; ++x) 
+	{
+		for (uint8 y = 0; y < BLOCKHEIGHT; ++y) 
+		{
+			//block_mask[x][y] = GETBITS(block_part2,2,31-(y*4+x)*2);
+			block_mask[x][y] = GETBITS(block_part2,1,(y+x*4)+16)<<1;
+			block_mask[x][y] |= GETBITS(block_part2,1,(y+x*4));
+			img[channels*((starty+y)*width+startx+x)+R] =
+				CLAMP(0,paint_colors[block_mask[x][y]][R],255); // RED
+			img[channels*((starty+y)*width+startx+x)+G] =
+				CLAMP(0,paint_colors[block_mask[x][y]][G],255); // GREEN
+			img[channels*((starty+y)*width+startx+x)+B] =
+				CLAMP(0,paint_colors[block_mask[x][y]][B],255); // BLUE
+		}
+	}
+}
+void decompressBlockTHUMB58H(unsigned int block_part1, unsigned int block_part2, uint8 *img, int width, int height, int startx, int starty)
+{
+  decompressBlockTHUMB58Hc(block_part1, block_part2, img, width, height, startx, starty, 3);
+}
+
+// Decompress the planar mode.
+// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2013. All Rights Reserved.
+void decompressBlockPlanar57c(unsigned int compressed57_1, unsigned int compressed57_2, uint8 *img, int width, int height, int startx, int starty, int channels)
+{
+	uint8 colorO[3], colorH[3], colorV[3];
+
+	colorO[0] = GETBITSHIGH( compressed57_1, 6, 63);
+	colorO[1] = GETBITSHIGH( compressed57_1, 7, 57);
+	colorO[2] = GETBITSHIGH( compressed57_1, 6, 50);
+	colorH[0] = GETBITSHIGH( compressed57_1, 6, 44);
+	colorH[1] = GETBITSHIGH( compressed57_1, 7, 38);
+	colorH[2] = GETBITS(     compressed57_2, 6, 31);
+	colorV[0] = GETBITS(     compressed57_2, 6, 25);
+	colorV[1] = GETBITS(     compressed57_2, 7, 19);
+	colorV[2] = GETBITS(     compressed57_2, 6, 12);
+
+	colorO[0] = (colorO[0] << 2) | (colorO[0] >> 4);
+	colorO[1] = (colorO[1] << 1) | (colorO[1] >> 6);
+	colorO[2] = (colorO[2] << 2) | (colorO[2] >> 4);
+
+	colorH[0] = (colorH[0] << 2) | (colorH[0] >> 4);
+	colorH[1] = (colorH[1] << 1) | (colorH[1] >> 6);
+	colorH[2] = (colorH[2] << 2) | (colorH[2] >> 4);
+
+	colorV[0] = (colorV[0] << 2) | (colorV[0] >> 4);
+	colorV[1] = (colorV[1] << 1) | (colorV[1] >> 6);
+	colorV[2] = (colorV[2] << 2) | (colorV[2] >> 4);
+
+	int xx, yy;
+
+	for( xx=0; xx<4; xx++)
+	{
+		for( yy=0; yy<4; yy++)
+		{
+			img[channels*width*(starty+yy) + channels*(startx+xx) + 0] = CLAMP(0, ((xx*(colorH[0]-colorO[0]) + yy*(colorV[0]-colorO[0]) + 4*colorO[0] + 2) >> 2),255);
+			img[channels*width*(starty+yy) + channels*(startx+xx) + 1] = CLAMP(0, ((xx*(colorH[1]-colorO[1]) + yy*(colorV[1]-colorO[1]) + 4*colorO[1] + 2) >> 2),255);
+			img[channels*width*(starty+yy) + channels*(startx+xx) + 2] = CLAMP(0, ((xx*(colorH[2]-colorO[2]) + yy*(colorV[2]-colorO[2]) + 4*colorO[2] + 2) >> 2),255);
+
+			//Equivalent method
+			/*img[channels*width*(starty+yy) + channels*(startx+xx) + 0] = (int)CLAMP(0, JAS_ROUND((xx*(colorH[0]-colorO[0])/4.0 + yy*(colorV[0]-colorO[0])/4.0 + colorO[0])), 255);
+			img[channels*width*(starty+yy) + channels*(startx+xx) + 1] = (int)CLAMP(0, JAS_ROUND((xx*(colorH[1]-colorO[1])/4.0 + yy*(colorV[1]-colorO[1])/4.0 + colorO[1])), 255);
+			img[channels*width*(starty+yy) + channels*(startx+xx) + 2] = (int)CLAMP(0, JAS_ROUND((xx*(colorH[2]-colorO[2])/4.0 + yy*(colorV[2]-colorO[2])/4.0 + colorO[2])), 255);*/
+			
+		}
+	}
+}
+void decompressBlockPlanar57(unsigned int compressed57_1, unsigned int compressed57_2, uint8 *img, int width, int height, int startx, int starty)
+{
+  decompressBlockPlanar57c(compressed57_1, compressed57_2, img, width, height, startx, starty, 3);
+}
+// Decompress an ETC1 block (or ETC2 using individual or differential mode).
+// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2013. All Rights Reserved.
+void decompressBlockDiffFlipC(unsigned int block_part1, unsigned int block_part2, uint8 *img, int width, int height, int startx, int starty, int channels)
+{
+	uint8 avg_color[3], enc_color1[3], enc_color2[3];
+	signed char diff[3];
+	int table;
+	int index,shift;
+	int r,g,b;
+	int diffbit;
+	int flipbit;
+
+	diffbit = (GETBITSHIGH(block_part1, 1, 33));
+	flipbit = (GETBITSHIGH(block_part1, 1, 32));
+
+	if( !diffbit )
+	{
+		// We have diffbit = 0.
+
+		// First decode left part of block.
+		avg_color[0]= GETBITSHIGH(block_part1, 4, 63);
+		avg_color[1]= GETBITSHIGH(block_part1, 4, 55);
+		avg_color[2]= GETBITSHIGH(block_part1, 4, 47);
+
+		// Here, we should really multiply by 17 instead of 16. This can
+		// be done by just copying the four lower bits to the upper ones
+		// while keeping the lower bits.
+		avg_color[0] |= (avg_color[0] <<4);
+		avg_color[1] |= (avg_color[1] <<4);
+		avg_color[2] |= (avg_color[2] <<4);
+
+		table = GETBITSHIGH(block_part1, 3, 39) << 1;
+
+		unsigned int pixel_indices_MSB, pixel_indices_LSB;
+			
+		pixel_indices_MSB = GETBITS(block_part2, 16, 31);
+		pixel_indices_LSB = GETBITS(block_part2, 16, 15);
+
+		if( (flipbit) == 0 )
+		{
+			// We should not flip
+			shift = 0;
+			for(int x=startx; x<startx+2; x++)
+			{
+				for(int y=starty; y<starty+4; y++)
+				{
+					index  = ((pixel_indices_MSB >> shift) & 1) << 1;
+					index |= ((pixel_indices_LSB >> shift) & 1);
+					shift++;
+					index=unscramble[index];
+
+ 					r=RED_CHANNEL(img,width,x,y,channels)  =CLAMP(0,avg_color[0]+compressParams[table][index],255);
+ 					g=GREEN_CHANNEL(img,width,x,y,channels)=CLAMP(0,avg_color[1]+compressParams[table][index],255);
+ 					b=BLUE_CHANNEL(img,width,x,y,channels) =CLAMP(0,avg_color[2]+compressParams[table][index],255);
+				}
+			}
+		}
+		else
+		{
+			// We should flip
+			shift = 0;
+			for(int x=startx; x<startx+4; x++)
+			{
+				for(int y=starty; y<starty+2; y++)
+				{
+					index  = ((pixel_indices_MSB >> shift) & 1) << 1;
+					index |= ((pixel_indices_LSB >> shift) & 1);
+					shift++;
+					index=unscramble[index];
+
+ 					r=RED_CHANNEL(img,width,x,y,channels)  =CLAMP(0,avg_color[0]+compressParams[table][index],255);
+ 					g=GREEN_CHANNEL(img,width,x,y,channels)=CLAMP(0,avg_color[1]+compressParams[table][index],255);
+ 					b=BLUE_CHANNEL(img,width,x,y,channels) =CLAMP(0,avg_color[2]+compressParams[table][index],255);
+				}
+				shift+=2;
+			}
+		}
+
+		// Now decode other part of block. 
+		avg_color[0]= GETBITSHIGH(block_part1, 4, 59);
+		avg_color[1]= GETBITSHIGH(block_part1, 4, 51);
+		avg_color[2]= GETBITSHIGH(block_part1, 4, 43);
+
+		// Here, we should really multiply by 17 instead of 16. This can
+		// be done by just copying the four lower bits to the upper ones
+		// while keeping the lower bits.
+		avg_color[0] |= (avg_color[0] <<4);
+		avg_color[1] |= (avg_color[1] <<4);
+		avg_color[2] |= (avg_color[2] <<4);
+
+		table = GETBITSHIGH(block_part1, 3, 36) << 1;
+		pixel_indices_MSB = GETBITS(block_part2, 16, 31);
+		pixel_indices_LSB = GETBITS(block_part2, 16, 15);
+
+		if( (flipbit) == 0 )
+		{
+			// We should not flip
+			shift=8;
+			for(int x=startx+2; x<startx+4; x++)
+			{
+				for(int y=starty; y<starty+4; y++)
+				{
+					index  = ((pixel_indices_MSB >> shift) & 1) << 1;
+					index |= ((pixel_indices_LSB >> shift) & 1);
+					shift++;
+					index=unscramble[index];
+
+ 					r=RED_CHANNEL(img,width,x,y,channels)  =CLAMP(0,avg_color[0]+compressParams[table][index],255);
+ 					g=GREEN_CHANNEL(img,width,x,y,channels)=CLAMP(0,avg_color[1]+compressParams[table][index],255);
+ 					b=BLUE_CHANNEL(img,width,x,y,channels) =CLAMP(0,avg_color[2]+compressParams[table][index],255);
+				}
+			}
+		}
+		else
+		{
+			// We should flip
+			shift=2;
+			for(int x=startx; x<startx+4; x++)
+			{
+				for(int y=starty+2; y<starty+4; y++)
+				{
+					index  = ((pixel_indices_MSB >> shift) & 1) << 1;
+					index |= ((pixel_indices_LSB >> shift) & 1);
+					shift++;
+					index=unscramble[index];
+
+ 					r=RED_CHANNEL(img,width,x,y,channels)  =CLAMP(0,avg_color[0]+compressParams[table][index],255);
+ 					g=GREEN_CHANNEL(img,width,x,y,channels)=CLAMP(0,avg_color[1]+compressParams[table][index],255);
+ 					b=BLUE_CHANNEL(img,width,x,y,channels) =CLAMP(0,avg_color[2]+compressParams[table][index],255);
+				}
+				shift += 2;
+			}
+		}
+	}
+	else
+	{
+		// We have diffbit = 1. 
+
+//      63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34  33  32 
+//      ---------------------------------------------------------------------------------------------------
+//     | base col1    | dcol 2 | base col1    | dcol 2 | base col 1   | dcol 2 | table  | table  |diff|flip|
+//     | R1' (5 bits) | dR2    | G1' (5 bits) | dG2    | B1' (5 bits) | dB2    | cw 1   | cw 2   |bit |bit |
+//      ---------------------------------------------------------------------------------------------------
+// 
+// 
+//     c) bit layout in bits 31 through 0 (in both cases)
+// 
+//      31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10  9  8  7  6  5  4  3   2   1  0
+//      --------------------------------------------------------------------------------------------------
+//     |       most significant pixel index bits       |         least significant pixel index bits       |  
+//     | p| o| n| m| l| k| j| i| h| g| f| e| d| c| b| a| p| o| n| m| l| k| j| i| h| g| f| e| d| c | b | a |
+//      --------------------------------------------------------------------------------------------------      
+
+		// First decode left part of block.
+		enc_color1[0]= GETBITSHIGH(block_part1, 5, 63);
+		enc_color1[1]= GETBITSHIGH(block_part1, 5, 55);
+		enc_color1[2]= GETBITSHIGH(block_part1, 5, 47);
+
+		// Expand from 5 to 8 bits
+		avg_color[0] = (enc_color1[0] <<3) | (enc_color1[0] >> 2);
+		avg_color[1] = (enc_color1[1] <<3) | (enc_color1[1] >> 2);
+		avg_color[2] = (enc_color1[2] <<3) | (enc_color1[2] >> 2);
+
+		table = GETBITSHIGH(block_part1, 3, 39) << 1;
+
+		unsigned int pixel_indices_MSB, pixel_indices_LSB;
+			
+		pixel_indices_MSB = GETBITS(block_part2, 16, 31);
+		pixel_indices_LSB = GETBITS(block_part2, 16, 15);
+
+		if( (flipbit) == 0 )
+		{
+			// We should not flip
+			shift = 0;
+			for(int x=startx; x<startx+2; x++)
+			{
+				for(int y=starty; y<starty+4; y++)
+				{
+					index  = ((pixel_indices_MSB >> shift) & 1) << 1;
+					index |= ((pixel_indices_LSB >> shift) & 1);
+					shift++;
+					index=unscramble[index];
+
+ 					r=RED_CHANNEL(img,width,x,y,channels)  =CLAMP(0,avg_color[0]+compressParams[table][index],255);
+ 					g=GREEN_CHANNEL(img,width,x,y,channels)=CLAMP(0,avg_color[1]+compressParams[table][index],255);
+ 					b=BLUE_CHANNEL(img,width,x,y,channels) =CLAMP(0,avg_color[2]+compressParams[table][index],255);
+				}
+			}
+		}
+		else
+		{
+			// We should flip
+			shift = 0;
+			for(int x=startx; x<startx+4; x++)
+			{
+				for(int y=starty; y<starty+2; y++)
+				{
+					index  = ((pixel_indices_MSB >> shift) & 1) << 1;
+					index |= ((pixel_indices_LSB >> shift) & 1);
+					shift++;
+					index=unscramble[index];
+
+ 					r=RED_CHANNEL(img,width,x,y,channels)  =CLAMP(0,avg_color[0]+compressParams[table][index],255);
+ 					g=GREEN_CHANNEL(img,width,x,y,channels)=CLAMP(0,avg_color[1]+compressParams[table][index],255);
+ 					b=BLUE_CHANNEL(img,width,x,y,channels) =CLAMP(0,avg_color[2]+compressParams[table][index],255);
+				}
+				shift+=2;
+			}
+		}
+
+		// Now decode right part of block. 
+		diff[0]= GETBITSHIGH(block_part1, 3, 58);
+		diff[1]= GETBITSHIGH(block_part1, 3, 50);
+		diff[2]= GETBITSHIGH(block_part1, 3, 42);
+
+		// Extend sign bit to entire byte. 
+		diff[0] = (diff[0] << 5);
+		diff[1] = (diff[1] << 5);
+		diff[2] = (diff[2] << 5);
+		diff[0] = diff[0] >> 5;
+		diff[1] = diff[1] >> 5;
+		diff[2] = diff[2] >> 5;
+
+		//  Calculale second color
+		enc_color2[0]= enc_color1[0] + diff[0];
+		enc_color2[1]= enc_color1[1] + diff[1];
+		enc_color2[2]= enc_color1[2] + diff[2];
+
+		// Expand from 5 to 8 bits
+		avg_color[0] = (enc_color2[0] <<3) | (enc_color2[0] >> 2);
+		avg_color[1] = (enc_color2[1] <<3) | (enc_color2[1] >> 2);
+		avg_color[2] = (enc_color2[2] <<3) | (enc_color2[2] >> 2);
+
+		table = GETBITSHIGH(block_part1, 3, 36) << 1;
+		pixel_indices_MSB = GETBITS(block_part2, 16, 31);
+		pixel_indices_LSB = GETBITS(block_part2, 16, 15);
+
+		if( (flipbit) == 0 )
+		{
+			// We should not flip
+			shift=8;
+			for(int x=startx+2; x<startx+4; x++)
+			{
+				for(int y=starty; y<starty+4; y++)
+				{
+					index  = ((pixel_indices_MSB >> shift) & 1) << 1;
+					index |= ((pixel_indices_LSB >> shift) & 1);
+					shift++;
+					index=unscramble[index];
+
+ 					r=RED_CHANNEL(img,width,x,y,channels)  =CLAMP(0,avg_color[0]+compressParams[table][index],255);
+ 					g=GREEN_CHANNEL(img,width,x,y,channels)=CLAMP(0,avg_color[1]+compressParams[table][index],255);
+ 					b=BLUE_CHANNEL(img,width,x,y,channels) =CLAMP(0,avg_color[2]+compressParams[table][index],255);
+				}
+			}
+		}
+		else
+		{
+			// We should flip
+			shift=2;
+			for(int x=startx; x<startx+4; x++)
+			{
+				for(int y=starty+2; y<starty+4; y++)
+				{
+					index  = ((pixel_indices_MSB >> shift) & 1) << 1;
+					index |= ((pixel_indices_LSB >> shift) & 1);
+					shift++;
+					index=unscramble[index];
+
+ 					r=RED_CHANNEL(img,width,x,y,channels)  =CLAMP(0,avg_color[0]+compressParams[table][index],255);
+ 					g=GREEN_CHANNEL(img,width,x,y,channels)=CLAMP(0,avg_color[1]+compressParams[table][index],255);
+ 					b=BLUE_CHANNEL(img,width,x,y,channels) =CLAMP(0,avg_color[2]+compressParams[table][index],255);
+				}
+				shift += 2;
+			}
+		}
+	}
+}
+void decompressBlockDiffFlip(unsigned int block_part1, unsigned int block_part2, uint8 *img, int width, int height, int startx, int starty)
+{
+  decompressBlockDiffFlipC(block_part1, block_part2, img, width, height, startx, starty, 3);
+}
+
+// Decompress an ETC2 RGB block
+// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2013. All Rights Reserved.
+void decompressBlockETC2c(unsigned int block_part1, unsigned int block_part2, uint8 *img, int width, int height, int startx, int starty, int channels)
+{
+	int diffbit;
+	signed char color1[3];
+	signed char diff[3];
+	signed char red, green, blue;
+
+	diffbit = (GETBITSHIGH(block_part1, 1, 33));
+
+	if( diffbit )
+	{
+		// We have diffbit = 1;
+
+		// Base color
+		color1[0]= GETBITSHIGH(block_part1, 5, 63);
+		color1[1]= GETBITSHIGH(block_part1, 5, 55);
+		color1[2]= GETBITSHIGH(block_part1, 5, 47);
+
+		// Diff color
+		diff[0]= GETBITSHIGH(block_part1, 3, 58);
+		diff[1]= GETBITSHIGH(block_part1, 3, 50);
+		diff[2]= GETBITSHIGH(block_part1, 3, 42);
+
+		// Extend sign bit to entire byte. 
+		diff[0] = (diff[0] << 5);
+		diff[1] = (diff[1] << 5);
+		diff[2] = (diff[2] << 5);
+		diff[0] = diff[0] >> 5;
+		diff[1] = diff[1] >> 5;
+		diff[2] = diff[2] >> 5;
+
+		red   = color1[0] + diff[0];
+		green = color1[1] + diff[1];
+		blue  = color1[2] + diff[2];
+
+		if(red < 0 || red > 31)
+		{
+			unsigned int block59_part1, block59_part2;
+			unstuff59bits(block_part1, block_part2, block59_part1, block59_part2);
+			decompressBlockTHUMB59Tc(block59_part1, block59_part2, img, width, height, startx, starty, channels);
+		}
+		else if (green < 0 || green > 31)
+		{
+			unsigned int block58_part1, block58_part2;
+			unstuff58bits(block_part1, block_part2, block58_part1, block58_part2);
+			decompressBlockTHUMB58Hc(block58_part1, block58_part2, img, width, height, startx, starty, channels);
+		}
+		else if(blue < 0 || blue > 31)
+		{
+			unsigned int block57_part1, block57_part2;
+
+			unstuff57bits(block_part1, block_part2, block57_part1, block57_part2);
+			decompressBlockPlanar57c(block57_part1, block57_part2, img, width, height, startx, starty, channels);
+		}
+		else
+		{
+ 			decompressBlockDiffFlipC(block_part1, block_part2, img, width, height, startx, starty, channels);
+		}
+	}
+	else
+	{
+		// We have diffbit = 0;
+		decompressBlockDiffFlipC(block_part1, block_part2, img, width, height, startx, starty, channels);
+	}
+}
+void decompressBlockETC2(unsigned int block_part1, unsigned int block_part2, uint8 *img, int width, int height, int startx, int starty)
+{
+  decompressBlockETC2c(block_part1, block_part2, img, width, height, startx, starty, 3);
+}
+// Decompress an ETC2 block with punchthrough alpha
+// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2013. All Rights Reserved.
+void decompressBlockDifferentialWithAlphaC(unsigned int block_part1, unsigned int block_part2, uint8* img, uint8* alpha, int width, int height, int startx, int starty, int channelsRGB)
+{
+	
+	uint8 avg_color[3], enc_color1[3], enc_color2[3];
+	signed char diff[3];
+	int table;
+	int index,shift;
+	int r,g,b;
+	int diffbit;
+	int flipbit;
+  int channelsA;
+
+  if(channelsRGB == 3)
+  {
+    // We will decode the alpha data to a separate memory area. 
+    channelsA = 1;
+  }
+  else
+  {
+    // We will decode the RGB data and the alpha data to the same memory area, 
+    // interleaved as RGBA. 
+    channelsA = 4;
+    alpha = &img[0+3];
+  }
+
+	//the diffbit now encodes whether or not the entire alpha channel is 255.
+	diffbit = (GETBITSHIGH(block_part1, 1, 33));
+ 	flipbit = (GETBITSHIGH(block_part1, 1, 32));
+
+	// First decode left part of block.
+	enc_color1[0]= GETBITSHIGH(block_part1, 5, 63);
+	enc_color1[1]= GETBITSHIGH(block_part1, 5, 55);
+	enc_color1[2]= GETBITSHIGH(block_part1, 5, 47);
+
+	// Expand from 5 to 8 bits
+	avg_color[0] = (enc_color1[0] <<3) | (enc_color1[0] >> 2);
+	avg_color[1] = (enc_color1[1] <<3) | (enc_color1[1] >> 2);
+	avg_color[2] = (enc_color1[2] <<3) | (enc_color1[2] >> 2);
+
+	table = GETBITSHIGH(block_part1, 3, 39) << 1;
+
+	unsigned int pixel_indices_MSB, pixel_indices_LSB;
+		
+	pixel_indices_MSB = GETBITS(block_part2, 16, 31);
+	pixel_indices_LSB = GETBITS(block_part2, 16, 15);
+
+	if( (flipbit) == 0 )
+	{
+		// We should not flip
+		shift = 0;
+		for(int x=startx; x<startx+2; x++)
+		{
+			for(int y=starty; y<starty+4; y++)
+			{
+				index  = ((pixel_indices_MSB >> shift) & 1) << 1;
+				index |= ((pixel_indices_LSB >> shift) & 1);
+				shift++;
+				index=unscramble[index];
+
+				int mod = compressParams[table][index];
+				if(diffbit==0&&(index==1||index==2)) 
+				{
+					mod=0;
+				}
+				
+				r=RED_CHANNEL(img,width,x,y,channelsRGB)  =CLAMP(0,avg_color[0]+mod,255);
+				g=GREEN_CHANNEL(img,width,x,y,channelsRGB)=CLAMP(0,avg_color[1]+mod,255);
+				b=BLUE_CHANNEL(img,width,x,y,channelsRGB) =CLAMP(0,avg_color[2]+mod,255);
+				if(diffbit==0&&index==1) 
+				{
+					alpha[(y*width+x)*channelsA]=0;
+					r=RED_CHANNEL(img,width,x,y,channelsRGB)=0;
+					g=GREEN_CHANNEL(img,width,x,y,channelsRGB)=0;
+					b=BLUE_CHANNEL(img,width,x,y,channelsRGB)=0;
+				}
+				else 
+				{
+					alpha[(y*width+x)*channelsA]=255;
+				}
+
+			}
+		}
+	}
+	else
+	{
+		// We should flip
+		shift = 0;
+		for(int x=startx; x<startx+4; x++)
+		{
+			for(int y=starty; y<starty+2; y++)
+			{
+				index  = ((pixel_indices_MSB >> shift) & 1) << 1;
+				index |= ((pixel_indices_LSB >> shift) & 1);
+				shift++;
+				index=unscramble[index];
+				int mod = compressParams[table][index];
+				if(diffbit==0&&(index==1||index==2)) 
+				{
+					mod=0;
+				}
+				r=RED_CHANNEL(img,width,x,y,channelsRGB)  =CLAMP(0,avg_color[0]+mod,255);
+				g=GREEN_CHANNEL(img,width,x,y,channelsRGB)=CLAMP(0,avg_color[1]+mod,255);
+				b=BLUE_CHANNEL(img,width,x,y,channelsRGB) =CLAMP(0,avg_color[2]+mod,255);
+				if(diffbit==0&&index==1) 
+				{
+					alpha[(y*width+x)*channelsA]=0;
+					r=RED_CHANNEL(img,width,x,y,channelsRGB)=0;
+					g=GREEN_CHANNEL(img,width,x,y,channelsRGB)=0;
+					b=BLUE_CHANNEL(img,width,x,y,channelsRGB)=0;
+				}
+				else 
+				{
+					alpha[(y*width+x)*channelsA]=255;
+				}
+			}
+			shift+=2;
+		}
+	}
+	// Now decode right part of block. 
+	diff[0]= GETBITSHIGH(block_part1, 3, 58);
+	diff[1]= GETBITSHIGH(block_part1, 3, 50);
+	diff[2]= GETBITSHIGH(block_part1, 3, 42);
+
+	// Extend sign bit to entire byte. 
+	diff[0] = (diff[0] << 5);
+	diff[1] = (diff[1] << 5);
+	diff[2] = (diff[2] << 5);
+	diff[0] = diff[0] >> 5;
+	diff[1] = diff[1] >> 5;
+	diff[2] = diff[2] >> 5;
+
+	//  Calculate second color
+	enc_color2[0]= enc_color1[0] + diff[0];
+	enc_color2[1]= enc_color1[1] + diff[1];
+	enc_color2[2]= enc_color1[2] + diff[2];
+
+	// Expand from 5 to 8 bits
+	avg_color[0] = (enc_color2[0] <<3) | (enc_color2[0] >> 2);
+	avg_color[1] = (enc_color2[1] <<3) | (enc_color2[1] >> 2);
+	avg_color[2] = (enc_color2[2] <<3) | (enc_color2[2] >> 2);
+
+	table = GETBITSHIGH(block_part1, 3, 36) << 1;
+	pixel_indices_MSB = GETBITS(block_part2, 16, 31);
+	pixel_indices_LSB = GETBITS(block_part2, 16, 15);
+
+	if( (flipbit) == 0 )
+	{
+		// We should not flip
+		shift=8;
+		for(int x=startx+2; x<startx+4; x++)
+		{
+			for(int y=starty; y<starty+4; y++)
+			{
+				index  = ((pixel_indices_MSB >> shift) & 1) << 1;
+				index |= ((pixel_indices_LSB >> shift) & 1);
+				shift++;
+				index=unscramble[index];
+				int mod = compressParams[table][index];
+				if(diffbit==0&&(index==1||index==2)) 
+				{
+					mod=0;
+				}
+				
+				r=RED_CHANNEL(img,width,x,y,channelsRGB)  =CLAMP(0,avg_color[0]+mod,255);
+				g=GREEN_CHANNEL(img,width,x,y,channelsRGB)=CLAMP(0,avg_color[1]+mod,255);
+				b=BLUE_CHANNEL(img,width,x,y,channelsRGB) =CLAMP(0,avg_color[2]+mod,255);
+				if(diffbit==0&&index==1) 
+				{
+					alpha[(y*width+x)*channelsA]=0;
+					r=RED_CHANNEL(img,width,x,y,channelsRGB)=0;
+					g=GREEN_CHANNEL(img,width,x,y,channelsRGB)=0;
+					b=BLUE_CHANNEL(img,width,x,y,channelsRGB)=0;
+				}
+				else 
+				{
+					alpha[(y*width+x)*channelsA]=255;
+				}
+			}
+		}
+	}
+	else
+	{
+		// We should flip
+		shift=2;
+		for(int x=startx; x<startx+4; x++)
+		{
+			for(int y=starty+2; y<starty+4; y++)
+			{
+				index  = ((pixel_indices_MSB >> shift) & 1) << 1;
+				index |= ((pixel_indices_LSB >> shift) & 1);
+				shift++;
+				index=unscramble[index];
+				int mod = compressParams[table][index];
+				if(diffbit==0&&(index==1||index==2)) 
+				{
+					mod=0;
+				}
+				
+				r=RED_CHANNEL(img,width,x,y,channelsRGB)  =CLAMP(0,avg_color[0]+mod,255);
+				g=GREEN_CHANNEL(img,width,x,y,channelsRGB)=CLAMP(0,avg_color[1]+mod,255);
+				b=BLUE_CHANNEL(img,width,x,y,channelsRGB) =CLAMP(0,avg_color[2]+mod,255);
+				if(diffbit==0&&index==1) 
+				{
+					alpha[(y*width+x)*channelsA]=0;
+					r=RED_CHANNEL(img,width,x,y,channelsRGB)=0;
+					g=GREEN_CHANNEL(img,width,x,y,channelsRGB)=0;
+					b=BLUE_CHANNEL(img,width,x,y,channelsRGB)=0;
+				}
+				else 
+				{
+					alpha[(y*width+x)*channelsA]=255;
+				}
+			}
+			shift += 2;
+		}
+	}
+}
+void decompressBlockDifferentialWithAlpha(unsigned int block_part1, unsigned int block_part2, uint8* img, uint8* alpha, int width, int height, int startx, int starty)
+{
+  decompressBlockDifferentialWithAlphaC(block_part1, block_part2, img, alpha, width, height, startx, starty, 3);
+}
+
+
+// similar to regular decompression, but alpha channel is set to 0 if pixel index is 2, otherwise 255.
+// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2013. All Rights Reserved.
+void decompressBlockTHUMB59TAlphaC(unsigned int block_part1, unsigned int block_part2, uint8 *img, uint8* alpha, int width, int height, int startx, int starty, int channelsRGB)
+{
+
+	uint8 colorsRGB444[2][3];
+	uint8 colors[2][3];
+	uint8 paint_colors[4][3];
+	uint8 distance;
+	uint8 block_mask[4][4];
+  int channelsA;
+
+  if(channelsRGB == 3)
+  {
+    // We will decode the alpha data to a separate memory area. 
+    channelsA = 1;
+  }
+  else
+  {
+    // We will decode the RGB data and the alpha data to the same memory area, 
+    // interleaved as RGBA. 
+    channelsA = 4;
+    alpha = &img[0+3];
+  }
+
+	// First decode left part of block.
+	colorsRGB444[0][R]= GETBITSHIGH(block_part1, 4, 58);
+	colorsRGB444[0][G]= GETBITSHIGH(block_part1, 4, 54);
+	colorsRGB444[0][B]= GETBITSHIGH(block_part1, 4, 50);
+
+	colorsRGB444[1][R]= GETBITSHIGH(block_part1, 4, 46);
+	colorsRGB444[1][G]= GETBITSHIGH(block_part1, 4, 42);
+	colorsRGB444[1][B]= GETBITSHIGH(block_part1, 4, 38);
+
+	distance   = GETBITSHIGH(block_part1, TABLE_BITS_59T, 34);
+
+	// Extend the two colors to RGB888	
+	decompressColor(R_BITS59T, G_BITS59T, B_BITS59T, colorsRGB444, colors);	
+	calculatePaintColors59T(distance, PATTERN_T, colors, paint_colors);
+	
+	// Choose one of the four paint colors for each texel
+	for (uint8 x = 0; x < BLOCKWIDTH; ++x) 
+	{
+		for (uint8 y = 0; y < BLOCKHEIGHT; ++y) 
+		{
+			//block_mask[x][y] = GETBITS(block_part2,2,31-(y*4+x)*2);
+			block_mask[x][y] = GETBITS(block_part2,1,(y+x*4)+16)<<1;
+			block_mask[x][y] |= GETBITS(block_part2,1,(y+x*4));
+			img[channelsRGB*((starty+y)*width+startx+x)+R] = 
+				CLAMP(0,paint_colors[block_mask[x][y]][R],255); // RED
+			img[channelsRGB*((starty+y)*width+startx+x)+G] =
+				CLAMP(0,paint_colors[block_mask[x][y]][G],255); // GREEN
+			img[channelsRGB*((starty+y)*width+startx+x)+B] =
+				CLAMP(0,paint_colors[block_mask[x][y]][B],255); // BLUE
+			if(block_mask[x][y]==2)  
+			{
+				alpha[channelsA*(x+startx+(y+starty)*width)]=0;
+				img[channelsRGB*((starty+y)*width+startx+x)+R] =0;
+				img[channelsRGB*((starty+y)*width+startx+x)+G] =0;
+				img[channelsRGB*((starty+y)*width+startx+x)+B] =0;
+			}
+			else
+				alpha[channelsA*(x+startx+(y+starty)*width)]=255;
+		}
+	}
+}
+void decompressBlockTHUMB59TAlpha(unsigned int block_part1, unsigned int block_part2, uint8 *img, uint8* alpha, int width, int height, int startx, int starty)
+{
+  decompressBlockTHUMB59TAlphaC(block_part1, block_part2, img, alpha, width, height, startx, starty, 3);
+}
+
+
+// Decompress an H-mode block with alpha
+// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2013. All Rights Reserved.
+void decompressBlockTHUMB58HAlphaC(unsigned int block_part1, unsigned int block_part2, uint8 *img, uint8* alpha, int width, int height, int startx, int starty, int channelsRGB)
+{
+	unsigned int col0, col1;
+	uint8 colors[2][3];
+	uint8 colorsRGB444[2][3];
+	uint8 paint_colors[4][3];
+	uint8 distance;
+	uint8 block_mask[4][4];
+  int channelsA;	
+
+  if(channelsRGB == 3)
+  {
+    // We will decode the alpha data to a separate memory area. 
+    channelsA = 1;
+  }
+  else
+  {
+    // We will decode the RGB data and the alpha data to the same memory area, 
+    // interleaved as RGBA. 
+    channelsA = 4;
+    alpha = &img[0+3];
+  }
+
+	// First decode left part of block.
+	colorsRGB444[0][R]= GETBITSHIGH(block_part1, 4, 57);
+	colorsRGB444[0][G]= GETBITSHIGH(block_part1, 4, 53);
+	colorsRGB444[0][B]= GETBITSHIGH(block_part1, 4, 49);
+
+	colorsRGB444[1][R]= GETBITSHIGH(block_part1, 4, 45);
+	colorsRGB444[1][G]= GETBITSHIGH(block_part1, 4, 41);
+	colorsRGB444[1][B]= GETBITSHIGH(block_part1, 4, 37);
+
+  distance = 0;
+	distance = (GETBITSHIGH(block_part1, 2, 33)) << 1;
+
+	col0 = GETBITSHIGH(block_part1, 12, 57);
+	col1 = GETBITSHIGH(block_part1, 12, 45);
+
+	if(col0 >= col1)
+	{
+		distance |= 1;
+	}
+
+	// Extend the two colors to RGB888	
+	decompressColor(R_BITS58H, G_BITS58H, B_BITS58H, colorsRGB444, colors);	
+	
+	calculatePaintColors58H(distance, PATTERN_H, colors, paint_colors);
+	
+	// Choose one of the four paint colors for each texel
+	for (uint8 x = 0; x < BLOCKWIDTH; ++x) 
+	{
+		for (uint8 y = 0; y < BLOCKHEIGHT; ++y) 
+		{
+			//block_mask[x][y] = GETBITS(block_part2,2,31-(y*4+x)*2);
+			block_mask[x][y] = GETBITS(block_part2,1,(y+x*4)+16)<<1;
+			block_mask[x][y] |= GETBITS(block_part2,1,(y+x*4));
+			img[channelsRGB*((starty+y)*width+startx+x)+R] =
+				CLAMP(0,paint_colors[block_mask[x][y]][R],255); // RED
+			img[channelsRGB*((starty+y)*width+startx+x)+G] =
+				CLAMP(0,paint_colors[block_mask[x][y]][G],255); // GREEN
+			img[channelsRGB*((starty+y)*width+startx+x)+B] =
+				CLAMP(0,paint_colors[block_mask[x][y]][B],255); // BLUE
+			
+			if(block_mask[x][y]==2)  
+			{
+				alpha[channelsA*(x+startx+(y+starty)*width)]=0;
+				img[channelsRGB*((starty+y)*width+startx+x)+R] =0;
+				img[channelsRGB*((starty+y)*width+startx+x)+G] =0;
+				img[channelsRGB*((starty+y)*width+startx+x)+B] =0;
+			}
+			else
+				alpha[channelsA*(x+startx+(y+starty)*width)]=255;
+		}
+	}
+}
+void decompressBlockTHUMB58HAlpha(unsigned int block_part1, unsigned int block_part2, uint8 *img, uint8* alpha, int width, int height, int startx, int starty)
+{
+  decompressBlockTHUMB58HAlphaC(block_part1, block_part2, img, alpha, width, height, startx, starty, 3);
+}
+// Decompression function for ETC2_RGBA1 format.
+// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2013. All Rights Reserved.
+void decompressBlockETC21BitAlphaC(unsigned int block_part1, unsigned int block_part2, uint8 *img, uint8* alphaimg, int width, int height, int startx, int starty, int channelsRGB)
+{
+	int diffbit;
+	signed char color1[3];
+	signed char diff[3];
+	signed char red, green, blue;
+  int channelsA;	
+
+  if(channelsRGB == 3)
+  {
+    // We will decode the alpha data to a separate memory area. 
+    channelsA = 1;
+  }
+  else
+  {
+    // We will decode the RGB data and the alpha data to the same memory area, 
+    // interleaved as RGBA. 
+    channelsA = 4;
+    alphaimg = &img[0+3];
+  }
+
+	diffbit = (GETBITSHIGH(block_part1, 1, 33));
+
+	if( diffbit )
+	{
+		// We have diffbit = 1, meaning no transparent pixels. regular decompression.
+
+		// Base color
+		color1[0]= GETBITSHIGH(block_part1, 5, 63);
+		color1[1]= GETBITSHIGH(block_part1, 5, 55);
+		color1[2]= GETBITSHIGH(block_part1, 5, 47);
+
+		// Diff color
+		diff[0]= GETBITSHIGH(block_part1, 3, 58);
+		diff[1]= GETBITSHIGH(block_part1, 3, 50);
+		diff[2]= GETBITSHIGH(block_part1, 3, 42);
+
+		// Extend sign bit to entire byte. 
+		diff[0] = (diff[0] << 5);
+		diff[1] = (diff[1] << 5);
+		diff[2] = (diff[2] << 5);
+		diff[0] = diff[0] >> 5;
+		diff[1] = diff[1] >> 5;
+		diff[2] = diff[2] >> 5;
+
+		red   = color1[0] + diff[0];
+		green = color1[1] + diff[1];
+		blue  = color1[2] + diff[2];
+
+		if(red < 0 || red > 31)
+		{
+			unsigned int block59_part1, block59_part2;
+			unstuff59bits(block_part1, block_part2, block59_part1, block59_part2);
+			decompressBlockTHUMB59Tc(block59_part1, block59_part2, img, width, height, startx, starty, channelsRGB);
+		}
+		else if (green < 0 || green > 31)
+		{
+			unsigned int block58_part1, block58_part2;
+			unstuff58bits(block_part1, block_part2, block58_part1, block58_part2);
+			decompressBlockTHUMB58Hc(block58_part1, block58_part2, img, width, height, startx, starty, channelsRGB);
+		}
+		else if(blue < 0 || blue > 31)
+		{
+			unsigned int block57_part1, block57_part2;
+
+			unstuff57bits(block_part1, block_part2, block57_part1, block57_part2);
+			decompressBlockPlanar57c(block57_part1, block57_part2, img, width, height, startx, starty, channelsRGB);
+		}
+		else
+		{
+ 			decompressBlockDifferentialWithAlphaC(block_part1, block_part2, img, alphaimg, width, height, startx, starty, channelsRGB);
+		}
+		for(int x=startx; x<startx+4; x++) 
+		{
+			for(int y=starty; y<starty+4; y++) 
+			{
+				alphaimg[channelsA*(x+y*width)]=255;
+			}
+		}
+	}
+	else
+	{
+		// We have diffbit = 0, transparent pixels. Only T-, H- or regular diff-mode possible.
+		
+		// Base color
+		color1[0]= GETBITSHIGH(block_part1, 5, 63);
+		color1[1]= GETBITSHIGH(block_part1, 5, 55);
+		color1[2]= GETBITSHIGH(block_part1, 5, 47);
+
+		// Diff color
+		diff[0]= GETBITSHIGH(block_part1, 3, 58);
+		diff[1]= GETBITSHIGH(block_part1, 3, 50);
+		diff[2]= GETBITSHIGH(block_part1, 3, 42);
+
+		// Extend sign bit to entire byte. 
+		diff[0] = (diff[0] << 5);
+		diff[1] = (diff[1] << 5);
+		diff[2] = (diff[2] << 5);
+		diff[0] = diff[0] >> 5;
+		diff[1] = diff[1] >> 5;
+		diff[2] = diff[2] >> 5;
+
+		red   = color1[0] + diff[0];
+		green = color1[1] + diff[1];
+		blue  = color1[2] + diff[2];
+		if(red < 0 || red > 31)
+		{
+			unsigned int block59_part1, block59_part2;
+			unstuff59bits(block_part1, block_part2, block59_part1, block59_part2);
+			decompressBlockTHUMB59TAlphaC(block59_part1, block59_part2, img, alphaimg, width, height, startx, starty, channelsRGB);
+		}
+		else if(green < 0 || green > 31) 
+		{
+			unsigned int block58_part1, block58_part2;
+			unstuff58bits(block_part1, block_part2, block58_part1, block58_part2);
+			decompressBlockTHUMB58HAlphaC(block58_part1, block58_part2, img, alphaimg, width, height, startx, starty, channelsRGB);
+		}
+		else if(blue < 0 || blue > 31)
+		{
+			unsigned int block57_part1, block57_part2;
+
+			unstuff57bits(block_part1, block_part2, block57_part1, block57_part2);
+			decompressBlockPlanar57c(block57_part1, block57_part2, img, width, height, startx, starty, channelsRGB);
+			for(int x=startx; x<startx+4; x++) 
+			{
+				for(int y=starty; y<starty+4; y++) 
+				{
+					alphaimg[channelsA*(x+y*width)]=255;
+				}
+			}
+		}
+		else
+			decompressBlockDifferentialWithAlphaC(block_part1, block_part2, img,alphaimg, width, height, startx, starty, channelsRGB);
+	}
+}
+void decompressBlockETC21BitAlpha(unsigned int block_part1, unsigned int block_part2, uint8 *img, uint8* alphaimg, int width, int height, int startx, int starty)
+{
+  decompressBlockETC21BitAlphaC(block_part1, block_part2, img, alphaimg, width, height, startx, starty, 3);
+}
+//
+//	Utility functions used for alpha compression
+//
+
+// bit number frompos is extracted from input, and moved to bit number topos in the return value.
+// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2013. All Rights Reserved.
+uint8 getbit(uint8 input, int frompos, int topos) 
+{
+	if(frompos>topos)
+		return ((1<<frompos)&input)>>(frompos-topos);
+	return ((1<<frompos)&input)<<(topos-frompos);
+}
+
+// takes as input a value, returns the value clamped to the interval [0,255].
+// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2013. All Rights Reserved.
+int clamp(int val) 
+{
+	if(val<0)
+		val=0;
+	if(val>255)
+		val=255;
+	return val;
+}
+
+// Decodes tha alpha component in a block coded with GL_COMPRESSED_RGBA8_ETC2_EAC.
+// Note that this decoding is slightly different from that of GL_COMPRESSED_R11_EAC.
+// However, a hardware decoder can share gates between the two formats as explained
+// in the specification under GL_COMPRESSED_R11_EAC.
+// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2013. All Rights Reserved.
+void decompressBlockAlphaC(uint8* data, uint8* img, int width, int height, int ix, int iy, int channels) 
+{
+	int alpha = data[0];
+	int table = data[1];
+	
+	int bit=0;
+	int byte=2;
+	//extract an alpha value for each pixel.
+	for(int x=0; x<4; x++) 
+	{
+		for(int y=0; y<4; y++) 
+		{
+			//Extract table index
+			int index=0;
+			for(int bitpos=0; bitpos<3; bitpos++) 
+			{
+				index|=getbit(data[byte],7-bit,2-bitpos);
+				bit++;
+				if(bit>7) 
+				{
+					bit=0;
+					byte++;
+				}
+			}
+			img[(ix+x+(iy+y)*width)*channels]=clamp(alpha +alphaTable[table][index]);
+		}
+	}
+}
+void decompressBlockAlpha(uint8* data, uint8* img, int width, int height, int ix, int iy) 
+{
+  decompressBlockAlphaC(data, img, width, height, ix, iy, 1);
+}
+
+// Does decompression and then immediately converts from 11 bit signed to a 16-bit format.
+// 
+// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2013. All Rights Reserved.
+int16 get16bits11signed(int base, int table, int mul, int index) 
+{
+	int elevenbase = base-128;
+	if(elevenbase==-128)
+		elevenbase=-127;
+	elevenbase*=8;
+	//i want the positive value here
+	int tabVal = -alphaBase[table][3-index%4]-1;
+	//and the sign, please
+	int sign = 1-(index/4);
+	
+	if(sign)
+		tabVal=tabVal+1;
+	int elevenTabVal = tabVal*8;
+
+	if(mul!=0)
+		elevenTabVal*=mul;
+	else
+		elevenTabVal/=8;
+
+	if(sign)
+		elevenTabVal=-elevenTabVal;
+
+	//calculate sum
+	int elevenbits = elevenbase+elevenTabVal;
+
+	//clamp..
+	if(elevenbits>=1024)
+		elevenbits=1023;
+	else if(elevenbits<-1023)
+		elevenbits=-1023;
+	//this is the value we would actually output.. 
+	//but there aren't any good 11-bit file or uncompressed GL formats
+	//so we extend to 15 bits signed.
+	sign = elevenbits<0;
+	elevenbits=abs(elevenbits);
+	int16 fifteenbits = (elevenbits<<5)+(elevenbits>>5);
+	int16 sixteenbits=fifteenbits;
+
+	if(sign)
+		sixteenbits=-sixteenbits;
+	
+	return sixteenbits;
+}
+
+// Does decompression and then immediately converts from 11 bit signed to a 16-bit format 
+// Calculates the 11 bit value represented by base, table, mul and index, and extends it to 16 bits.
+// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2013. All Rights Reserved.
+uint16 get16bits11bits(int base, int table, int mul, int index) 
+{
+	int elevenbase = base*8+4;
+
+	//i want the positive value here
+	int tabVal = -alphaBase[table][3-index%4]-1;
+	//and the sign, please
+	int sign = 1-(index/4);
+	
+	if(sign)
+		tabVal=tabVal+1;
+	int elevenTabVal = tabVal*8;
+
+	if(mul!=0)
+		elevenTabVal*=mul;
+	else
+		elevenTabVal/=8;
+
+	if(sign)
+		elevenTabVal=-elevenTabVal;
+
+	//calculate sum
+	int elevenbits = elevenbase+elevenTabVal;
+
+	//clamp..
+	if(elevenbits>=256*8)
+		elevenbits=256*8-1;
+	else if(elevenbits<0)
+		elevenbits=0;
+	//elevenbits now contains the 11 bit alpha value as defined in the spec.
+
+	//extend to 16 bits before returning, since we don't have any good 11-bit file formats.
+	uint16 sixteenbits = (elevenbits<<5)+(elevenbits>>6);
+
+	return sixteenbits;
+}
+
+// Decompresses a block using one of the GL_COMPRESSED_R11_EAC or GL_COMPRESSED_SIGNED_R11_EAC-formats
+// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2013. All Rights Reserved.
+void decompressBlockAlpha16bitC(uint8* data, uint8* img, int width, int height, int ix, int iy, int channels) 
+{
+	int alpha = data[0];
+	int table = data[1];
+
+	if(formatSigned) 
+	{
+		//if we have a signed format, the base value is given as a signed byte. We convert it to (0-255) here,
+		//so more code can be shared with the unsigned mode.
+		alpha = *((signed char*)(&data[0]));
+		alpha = alpha+128;
+	}
+
+	int bit=0;
+	int byte=2;
+	//extract an alpha value for each pixel.
+	for(int x=0; x<4; x++) 
+	{
+		for(int y=0; y<4; y++) 
+		{
+			//Extract table index
+			int index=0;
+			for(int bitpos=0; bitpos<3; bitpos++) 
+			{
+				index|=getbit(data[byte],7-bit,2-bitpos);
+				bit++;
+				if(bit>7) 
+				{
+					bit=0;
+					byte++;
+				}
+			}
+			int windex = channels*(2*(ix+x+(iy+y)*width));
+#if !PGMOUT
+			if(formatSigned)
+			{
+				*(int16 *)&img[windex] = get16bits11signed(alpha,(table%16),(table/16),index);
+			}
+			else
+			{
+				*(uint16 *)&img[windex] = get16bits11bits(alpha,(table%16),(table/16),index);
+			}
+#else
+			//make data compatible with the .pgm format. See the comment in compressBlockAlpha16() for details.
+			uint16 uSixteen;
+			if (formatSigned)
+			{
+				//the pgm-format only allows unsigned images,
+				//so we add 2^15 to get a 16-bit value.
+				uSixteen = get16bits11signed(alpha,(table%16),(table/16),index) + 256*128;
+			}
+			else
+			{
+				uSixteen = get16bits11bits(alpha,(table%16),(table/16),index);
+			}
+			//byte swap for pgm
+			img[windex] = uSixteen/256;
+			img[windex+1] = uSixteen%256;
+#endif
+
+		}
+	}			
+}
+
+void decompressBlockAlpha16bit(uint8* data, uint8* img, int width, int height, int ix, int iy)
+{
+  decompressBlockAlpha16bitC(data, img, width, height, ix, iy, 1);
+}
diff --git a/external/Vulkan/external/ktx/lib/etcunpack.cxx b/external/Vulkan/external/ktx/lib/etcunpack.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..85d0c510d15564c509753d97647d9271f6ce9102
--- /dev/null
+++ b/external/Vulkan/external/ktx/lib/etcunpack.cxx
@@ -0,0 +1,288 @@
+/* -*- tab-width: 4; -*- */
+/* vi: set sw=2 ts=4: */
+
+/* $Id: 764e83ee5c8febe3233114f9ebe9a9ef21d120f1 $ */
+
+/*
+ * ©2010 The khronos Group, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* @internal
+ * @~English
+ * @file
+ *
+ * Unpack a texture compressed with ETC1
+ *
+ * @author Mark Callow, HI Corporation.
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include "GL/glcorearb.h"
+// Not defined in glcorearb.h.
+#define GL_ETC1_RGB8_OES                0x8D64
+#include "ktx.h"
+#include "ktxint.h"
+
+#if SUPPORT_SOFTWARE_ETC_UNPACK
+typedef unsigned int uint;
+typedef unsigned char uint8;
+
+extern void decompressBlockETC2c(uint block_part1, uint block_part2, uint8* img,
+								 int width, int height, int startx, int starty, int channels);
+extern void decompressBlockETC21BitAlphaC(uint block_part1, uint block_part2, uint8* img, uint8* alphaimg,
+										  int width, int height, int startx, int starty, int channels);
+extern void decompressBlockAlphaC(uint8* data, uint8* img,
+								  int width, int height, int startx, int starty, int channels);
+extern void decompressBlockAlpha16bitC(uint8* data, uint8* img,
+									   int width, int height, int startx, int starty, int channels);
+
+extern void setupAlphaTable();
+
+// This global variable affects the behaviour of decompressBlockAlpha16bitC.
+extern int formatSigned;
+
+static void
+readBigEndian4byteWord(ktx_uint32_t* pBlock, const GLubyte *s)
+{
+	*pBlock = (s[0] << 24) | (s[1] << 16) | (s[2] << 8) | s[3];
+}
+
+
+/* Unpack an ETC1_RGB8_OES format compressed texture */
+extern "C" KTX_error_code
+_ktxUnpackETC(const GLubyte* srcETC, const GLenum srcFormat,
+			  ktx_uint32_t activeWidth, ktx_uint32_t activeHeight,
+			  GLubyte** dstImage,
+			  GLenum* format, GLenum* internalFormat, GLenum* type,
+			  GLint R16Formats, GLboolean supportsSRGB)
+{
+	unsigned int width, height;
+	unsigned int block_part1, block_part2;
+	unsigned int x, y;
+	/*const*/ GLubyte* src = (GLubyte*)srcETC;
+	// AF_11BIT is used to compress R11 & RG11 though its not alpha data.
+	enum {AF_NONE, AF_1BIT, AF_8BIT, AF_11BIT} alphaFormat = AF_NONE;
+	int dstChannels, dstChannelBytes;
+
+	switch (srcFormat) {
+	  case GL_COMPRESSED_SIGNED_R11_EAC:
+		if (R16Formats & _KTX_R16_FORMATS_SNORM) {
+			dstChannelBytes = sizeof(GLshort);
+			dstChannels = 1;
+			formatSigned = GL_TRUE;
+			*internalFormat = GL_R16_SNORM;
+			*format = GL_RED;
+			*type = GL_SHORT;
+			alphaFormat = AF_11BIT;
+		} else
+			return KTX_UNSUPPORTED_TEXTURE_TYPE; 
+		break;
+
+	  case GL_COMPRESSED_R11_EAC:
+		if (R16Formats & _KTX_R16_FORMATS_NORM) {
+			dstChannelBytes = sizeof(GLshort);
+			dstChannels = 1;
+			formatSigned = GL_FALSE;
+			*internalFormat = GL_R16;
+			*format = GL_RED;
+			*type = GL_UNSIGNED_SHORT;
+			alphaFormat = AF_11BIT;
+		} else
+			return KTX_UNSUPPORTED_TEXTURE_TYPE; 
+        break;
+
+	  case GL_COMPRESSED_SIGNED_RG11_EAC:
+		if (R16Formats & _KTX_R16_FORMATS_SNORM) {
+			dstChannelBytes = sizeof(GLshort);
+			dstChannels = 2;
+			formatSigned = GL_TRUE;
+			*internalFormat = GL_RG16_SNORM;
+			*format = GL_RG;
+			*type = GL_SHORT;
+			alphaFormat = AF_11BIT;
+		} else
+			return KTX_UNSUPPORTED_TEXTURE_TYPE; 
+        break;
+
+	  case GL_COMPRESSED_RG11_EAC:
+		if (R16Formats & _KTX_R16_FORMATS_NORM) {
+			dstChannelBytes = sizeof(GLshort);
+			dstChannels = 2;
+			formatSigned = GL_FALSE;
+			*internalFormat = GL_RG16;
+			*format = GL_RG;
+			*type = GL_UNSIGNED_SHORT;
+			alphaFormat = AF_11BIT;
+		} else
+			return KTX_UNSUPPORTED_TEXTURE_TYPE; 
+        break;
+
+	  case GL_ETC1_RGB8_OES:
+	  case GL_COMPRESSED_RGB8_ETC2:
+	    dstChannelBytes = sizeof(GLubyte);
+		dstChannels = 3;
+		*internalFormat = GL_RGB8;
+		*format = GL_RGB;
+		*type = GL_UNSIGNED_BYTE;
+        break;
+
+	  case GL_COMPRESSED_RGBA8_ETC2_EAC:
+	    dstChannelBytes = sizeof(GLubyte);
+		dstChannels = 4;
+		*internalFormat = GL_RGBA8;
+		*format = GL_RGBA;
+		*type = GL_UNSIGNED_BYTE;
+	    alphaFormat = AF_8BIT;
+		break;
+
+	  case GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:
+	    dstChannelBytes = sizeof(GLubyte);
+		dstChannels = 4;
+		*internalFormat = GL_RGBA8;
+		*format = GL_RGBA;
+		*type = GL_UNSIGNED_BYTE;
+		alphaFormat = AF_1BIT;
+        break;
+
+	  case GL_COMPRESSED_SRGB8_ETC2:
+		if (supportsSRGB) {
+			dstChannelBytes = sizeof(GLubyte);
+			dstChannels = 3;
+			*internalFormat = GL_SRGB8;
+			*format = GL_RGB;
+			*type = GL_UNSIGNED_BYTE;
+		} else
+			return KTX_UNSUPPORTED_TEXTURE_TYPE; 
+        break;
+
+	  case GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC:
+		if (supportsSRGB) {
+			dstChannelBytes = sizeof(GLubyte);
+			dstChannels = 4;
+			*internalFormat = GL_SRGB8_ALPHA8;
+ 			*format = GL_RGBA;
+			*type = GL_UNSIGNED_BYTE;
+			alphaFormat = AF_8BIT;
+		} else
+			return KTX_UNSUPPORTED_TEXTURE_TYPE; 
+		break;
+
+	  case GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2:
+		if (supportsSRGB) {
+			dstChannelBytes = sizeof(GLubyte);
+			dstChannels = 4;
+			*internalFormat = GL_SRGB8_ALPHA8;
+ 			*format = GL_RGBA;
+			*type = GL_UNSIGNED_BYTE;
+			alphaFormat = AF_1BIT;
+		} else
+			return KTX_UNSUPPORTED_TEXTURE_TYPE; 
+        break;
+
+	  default:
+	    assert(0); // Upper levels should pass only one of the above srcFormats.
+        return KTX_UNSUPPORTED_TEXTURE_TYPE; // For Release configurations.
+	}
+
+    /* active_{width,height} show how many pixels contain active data,
+	 * (the rest are just for making sure we have a 2*a x 4*b size).
+	 */
+
+	/* Compute the full width & height. */
+	width = ((activeWidth+3)/4)*4;
+	height = ((activeHeight+3)/4)*4;
+
+	/* printf("Width = %d, Height = %d\n", width, height); */
+	/* printf("active pixel area: top left %d x %d area.\n", activeWidth, activeHeight); */
+
+	*dstImage = (GLubyte*)malloc(dstChannels*dstChannelBytes*width*height);
+	if (!*dstImage) {
+		return KTX_OUT_OF_MEMORY;
+	}
+	
+	if (alphaFormat != AF_NONE)
+		setupAlphaTable();
+
+	// NOTE: none of the decompress functions actually use the <height> parameter
+	if (alphaFormat == AF_11BIT) {
+		// One or two 11-bit alpha channels for R or RG.
+		for (y=0; y < height/4; y++) {
+			for (x=0; x < width/4; x++) {
+				decompressBlockAlpha16bitC(src, *dstImage, width, height, 4*x, 4*y, dstChannels);
+				src += 8;
+				if (srcFormat == GL_COMPRESSED_RG11_EAC || srcFormat == GL_COMPRESSED_SIGNED_RG11_EAC) {
+					decompressBlockAlpha16bitC(src, *dstImage + dstChannelBytes, width, height, 4*x, 4*y, dstChannels);
+					src += 8;
+				}
+			}
+		}
+	} else {
+		for (y=0; y < height/4; y++) {
+			for (x=0; x < width/4; x++) {
+				// Decode alpha channel for RGBA
+				if (alphaFormat == AF_8BIT) {
+					decompressBlockAlphaC(src, *dstImage + 3, width, height, 4*x, 4*y, dstChannels);
+					src += 8;
+				}
+				// Decode color dstChannels
+				readBigEndian4byteWord(&block_part1, src);
+				src += 4;
+				readBigEndian4byteWord(&block_part2, src);
+				src += 4;
+				if (alphaFormat == AF_1BIT)
+				    decompressBlockETC21BitAlphaC(block_part1, block_part2, *dstImage, 0, width, height, 4*x, 4*y, dstChannels);
+				else
+				    decompressBlockETC2c(block_part1, block_part2, *dstImage, width, height, 4*x, 4*y, dstChannels);
+			}
+		}
+	}
+
+	/* Ok, now write out the active pixels to the destination image.
+	 * (But only if the active pixels differ from the total pixels)
+	 */
+
+	if( !(height == activeHeight && width == activeWidth) ) {
+		int dstPixelBytes = dstChannels * dstChannelBytes;
+		int dstRowBytes = dstPixelBytes * width;
+		int activeRowBytes = activeWidth * dstPixelBytes;
+		GLubyte *newimg = (GLubyte*)malloc(dstPixelBytes * activeWidth * activeHeight);
+		unsigned int xx, yy;
+		int zz;
+
+		if (!newimg) {
+			free(*dstImage);
+			return KTX_OUT_OF_MEMORY;
+		}
+		
+		/* Convert from total area to active area: */
+
+		for (yy = 0; yy < activeHeight; yy++) {
+			for (xx = 0; xx < activeWidth; xx++) {
+				for (zz = 0; zz < dstPixelBytes; zz++) {
+					newimg[ yy*activeRowBytes + xx*dstPixelBytes + zz ] = (*dstImage)[ yy*dstRowBytes + xx*dstPixelBytes + zz];
+				}
+			}
+		}
+
+		free(*dstImage);
+		*dstImage = newimg;
+	}
+
+	return KTX_SUCCESS;
+}
+
+#endif /* SUPPORT_SOFTWARE_ETC_UNPACK */
diff --git a/external/Vulkan/external/ktx/lib/filestream.c b/external/Vulkan/external/ktx/lib/filestream.c
new file mode 100644
index 0000000000000000000000000000000000000000..dd6d09bc44566c25f60f02e831fee48f6c7b20bf
--- /dev/null
+++ b/external/Vulkan/external/ktx/lib/filestream.c
@@ -0,0 +1,324 @@
+/* -*- tab-width: 4; -*- */
+/* vi: set sw=2 ts=4 expandtab: */
+
+/*
+ * ©2010-2018 The khronos Group, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file
+ * @~English
+ *
+ * @brief Implementation of ktxStream for FILE.
+ *
+ * @author Maksim Kolesin, Under Development
+ * @author Georg Kolling, Imagination Technology
+ * @author Mark Callow, HI Corporation
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+/* I need these on Linux. Why? */
+#define __USE_LARGEFILE 1  // For declaration of ftello, etc.
+#define __USE_POSIX 1      // For declaration of fileno.
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+
+#include "ktx.h"
+#include "ktxint.h"
+#include "filestream.h"
+
+#if defined(_MSC_VER)
+  #if defined(_WIN64)
+    #define ftello _ftelli64
+    #define fseeko _fseeki64
+  #else
+    #define ftello ftell
+    #define fseeko fseek
+  #endif
+  #define fileno _fileno
+#endif
+
+#define KTX_FILE_STREAM_MAX (1 << (sizeof(ktx_off_t) - 1) - 1)
+
+/**
+ * @internal
+ * @~English
+ * @brief Read bytes from a ktxFileStream.
+ *
+ * @param [in]  str     pointer to the ktxStream from which to read.
+ * @param [out] dst     pointer to a block of memory with a size
+ *                      of at least @p size bytes, converted to a void*.
+ * @param [in,out] count   pointer to total count of bytes to be read.
+ *                         On completion set to number of bytes read.
+ *
+ * @return      KTX_SUCCESS on success, other KTX_* enum values on error.
+ *
+ * @exception KTX_INVALID_VALUE @p dst is @c NULL or @p src is @c NULL.
+ * @exception KTX_FILE_UNEXPECTED_EOF not enough data to satisfy the request.
+ */
+static
+KTX_error_code ktxFileStream_read(ktxStream* str, void* dst, const ktx_size_t count)
+{
+    ktx_size_t nread;
+
+    if (!str || !dst)
+        return KTX_INVALID_VALUE;
+
+    assert(str->type == eStreamTypeFile);
+    
+    if ((nread = fread(dst, 1, count, str->data.file)) != count) {
+        if (feof(str->data.file)) {
+            return KTX_FILE_UNEXPECTED_EOF;
+        } else
+            return KTX_FILE_READ_ERROR;
+    }
+
+    return KTX_SUCCESS;
+}
+
+/**
+ * @internal
+ * @~English
+ * @brief Skip bytes in a ktxFileStream.
+ *
+ * @param [in] str           pointer to a ktxStream object.
+ * @param [in] count         number of bytes to be skipped.
+ *
+ * @return      KTX_SUCCESS on success, other KTX_* enum values on error.
+ *
+ * @exception KTX_INVALID_VALUE @p str is @c NULL or @p count is less than zero.
+ * @exception KTX_INVALID_OPERATION skipping @p count bytes would go beyond EOF.
+ * @exception KTX_FILE_UNEXPECTED_EOF not enough data to satisfy the request.
+ *                                    @p count is set to the number of bytes
+ *                                    skipped.
+ */
+static
+KTX_error_code ktxFileStream_skip(ktxStream* str, const ktx_size_t count)
+{
+    ktx_size_t fileSize;
+    ktx_off_t pos, newpos;
+
+    if (!str)
+        return KTX_INVALID_VALUE;
+
+    assert(str->type == eStreamTypeFile);
+    
+    str->getsize(str, &fileSize);
+    str->getpos(str, &pos);
+
+    newpos = pos + count;
+    /* First clause checks for overflow. */
+    if (newpos < pos || pos + count > fileSize)
+        return KTX_FILE_UNEXPECTED_EOF;
+
+    if (fseeko(str->data.file, count, SEEK_CUR) != 0)
+        return KTX_FILE_SEEK_ERROR;
+
+    return KTX_SUCCESS;
+}
+
+/**
+ * @internal
+ * @~English
+ * @brief Write bytes to a ktxFileStream.
+ *
+ * @param [in] str      pointer to the ktxStream that is the destination of the
+ *                      write.
+ * @param [in] src      pointer to the array of elements to be written,
+ *                      converted to a const void*.
+ * @param [in] size     size in bytes of each element to be written.
+ * @param [in] count    number of elements, each one with a @p size of size
+ *                      bytes.
+ *
+ * @return      KTX_SUCCESS on success, other KTX_* enum values on error.
+ *
+ * @exception KTX_INVALID_VALUE @p str is @c NULL or @p src is @c NULL.
+ * @exception KTX_FILE_OVERFLOW the requested write would caused the file to
+ *                              exceed the maximum supported file size.
+ * @exception KTX_FILE_WRITE_ERROR a system error occurred while writing the
+ *                                 file.
+ */
+static
+KTX_error_code ktxFileStream_write(ktxStream* str, const void *src,
+                                   const ktx_size_t size,
+                                   const ktx_size_t count)
+{
+    if (!str || !src)
+        return KTX_INVALID_VALUE;
+
+    assert(str->type == eStreamTypeFile);
+    
+    if (fwrite(src, size, count, str->data.file) != count) {
+        if (errno == EFBIG || errno == EOVERFLOW)
+            return KTX_FILE_OVERFLOW;
+        else
+            return KTX_FILE_WRITE_ERROR;
+    }
+
+    return KTX_SUCCESS;
+}
+
+/**
+ * @internal
+ * @~English
+ * @brief Get the current read/write position in a ktxFileStream.
+ *
+ * @param [in] str      pointer to the ktxStream to query.
+ * @param [in,out] off  pointer to variable to receive the offset value.
+ *
+ * @return      KTX_SUCCESS on success, other KTX_* enum values on error.
+ *
+ * @exception KTX_INVALID_VALUE @p str or @p pos is @c NULL.
+ */
+static
+KTX_error_code ktxFileStream_getpos(ktxStream* str, ktx_off_t* pos)
+{
+    if (!str || !pos)
+        return KTX_INVALID_VALUE;
+    
+    assert(str->type == eStreamTypeFile);
+
+    /* The cast quiets an Xcode warning when building for "Generic iOS Device".
+     * For some reason, even when ARCHS is arm64, size_t is only a long. */
+    *pos = (ktx_off_t)ftello(str->data.file);
+    
+    return KTX_SUCCESS;
+}
+
+/**
+ * @internal
+ * @~English
+ * @brief Set the current read/write position in a ktxFileStream.
+ *
+ * Offset of 0 is the start of the file. This function operates
+ * like Linux > 3.1's @c lseek() when it is passed a @c whence
+ * of @c SEEK_DATA is it returns and error if the seek would
+ * go beyond the end of the file.
+ *
+ * @param [in] str    pointer to the ktxStream whose r/w position is to be set.
+ * @param [in] off    pointer to the offset value to set.
+ *
+ * @return      KTX_SUCCESS on success, other KTX_* enum values on error.
+ *
+ * @exception KTX_INVALID_VALUE @p str is @c NULL.
+ * @exception KTX_INVALID_OPERATION @p pos is > the size of the file or an
+ *                                  fseek error occurred.
+ */
+static
+KTX_error_code ktxFileStream_setpos(ktxStream* str, ktx_off_t pos)
+{
+    ktx_size_t fileSize;
+    
+    if (!str)
+        return KTX_INVALID_VALUE;
+
+    assert(str->type == eStreamTypeFile);
+    
+    str->getsize(str, &fileSize);
+    if (pos > fileSize)
+        return KTX_INVALID_OPERATION;
+
+    if (fseeko(str->data.file, pos, SEEK_SET) < 0)
+        return KTX_FILE_SEEK_ERROR;
+    
+    return KTX_SUCCESS;
+}
+
+/**
+ * @internal
+ * @~English
+ * @brief Get the size of a ktxFileStream in bytes.
+ *
+ * @param [in] str       pointer to the ktxStream whose size is to be queried.
+ * @param [in,out] size  pointer to a variable in which size will be written.
+ *
+ * @return      KTX_SUCCESS on success, other KTX_* enum values on error.
+ *
+ * @exception KTX_INVALID_VALUE @p str or @p size is @c NULL.
+ * @exception KTX_FILE_WRITE_ERROR a system error occurred while getting the
+ *                                 size.
+ */
+static
+KTX_error_code ktxFileStream_getsize(ktxStream* str, ktx_size_t* size)
+{
+    struct stat statbuf;
+
+    if (!str || !size)
+        return KTX_INVALID_VALUE;
+    
+    assert(str->type == eStreamTypeFile);
+ 
+    if (fstat(fileno(str->data.file), &statbuf) < 0)
+        return KTX_FILE_READ_ERROR;
+    *size = (ktx_size_t)statbuf.st_size; /* See _getpos for why this cast. */
+    
+    return KTX_SUCCESS;
+}
+
+/**
+ * @internal
+ * @~English
+ * @brief Initialize a ktxFileStream.
+ *
+ * @param [in] str      pointer to the ktxStream to initialize.
+ * @param [in] file     pointer to the underlying FILE object.
+ *
+ * @return      KTX_SUCCESS on success, KTX_INVALID_VALUE on error.
+ *
+ * @exception KTX_INVALID_VALUE @p stream is @c NULL or @p file is @c NULL.
+ */
+KTX_error_code ktxFileStream_construct(ktxStream* str, FILE* file,
+                                       ktx_bool_t closeFileOnDestruct)
+{
+    if (!str || !file)
+        return KTX_INVALID_VALUE;
+
+    str->data.file = file;
+    str->type = eStreamTypeFile;
+    str->read = ktxFileStream_read;
+    str->skip = ktxFileStream_skip;
+    str->write = ktxFileStream_write;
+    str->getpos = ktxFileStream_getpos;
+    str->setpos = ktxFileStream_setpos;
+    str->getsize = ktxFileStream_getsize;
+    str->destruct = ktxFileStream_destruct;
+    str->closeOnDestruct = closeFileOnDestruct;
+
+    return KTX_SUCCESS;
+}
+
+/**
+ * @internal
+ * @~English
+ * @brief Destruct the stream, potentially closing the underlying FILE.
+ *
+ * This only closes the underyling FILE if the @c closeOnDestruct parameter to
+ * ktxFileStream_construct() was not @c KTX_FALSE.
+ *
+ * @param [in] str pointer to the ktxStream whose FILE is to potentially
+ *             be closed.
+ */
+void
+ktxFileStream_destruct(ktxStream* str)
+{
+    assert(str && str->type == eStreamTypeFile);
+    
+    if (str->closeOnDestruct)
+        fclose(str->data.file);
+    str->data.file = 0;
+}
diff --git a/external/Vulkan/external/ktx/lib/filestream.h b/external/Vulkan/external/ktx/lib/filestream.h
new file mode 100644
index 0000000000000000000000000000000000000000..3359cb24a25300a840071842665ee36ee289388e
--- /dev/null
+++ b/external/Vulkan/external/ktx/lib/filestream.h
@@ -0,0 +1,39 @@
+/* -*- tab-width: 4; -*- */
+/* vi: set sw=2 ts=4 expandtab: */
+
+/*
+ * ©2010-2018 The khronos Group, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Author: Maksim Kolesin from original code
+ * by Mark Callow and Georg Kolling
+ */
+
+#ifndef FILESTREAM_H
+#define FILESTREAM_H
+
+#include "ktx.h"
+#include "stream.h"
+
+/*
+ * ktxFileInit: Initialize a ktxStream to a ktxFileStream with a FILE object
+ */
+KTX_error_code ktxFileStream_construct(ktxStream* str, FILE* file,
+                                       ktx_bool_t closeFileOnDestruct);
+
+void ktxFileStream_destruct(ktxStream* str);
+
+#endif /* FILESTREAM_H */
diff --git a/external/Vulkan/external/ktx/lib/gl_format.h b/external/Vulkan/external/ktx/lib/gl_format.h
new file mode 100644
index 0000000000000000000000000000000000000000..2a781244e6ac52155c65351818066e04ad05ced4
--- /dev/null
+++ b/external/Vulkan/external/ktx/lib/gl_format.h
@@ -0,0 +1,2456 @@
+/*
+================================================================================================
+
+Description	:	OpenGL formats/types and properties.
+Author		:	J.M.P. van Waveren
+Date		:	07/17/2016
+Language	:	C99
+Format		:	Real tabs with the tab size equal to 4 spaces.
+Copyright	:	Copyright (c) 2016 Oculus VR, LLC. All Rights reserved.
+
+
+LICENSE
+=======
+
+Copyright (c) 2016 Oculus VR, LLC.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+DESCRIPTION
+===========
+
+This header stores the OpenGL formats/types and two simple routines
+to derive the format/type from an internal format. These routines
+are useful to verify the data in a KTX container files. The OpenGL
+constants are generally useful to convert files like KTX and glTF
+to different graphics APIs.
+
+This header stores the OpenGL formats/types that are used as parameters
+to the following OpenGL functions:
+
+void glTexImage2D( GLenum target, GLint level, GLint internalFormat,
+	GLsizei width, GLsizei height, GLint border,
+	GLenum format, GLenum type, const GLvoid * data );
+void glTexImage3D( GLenum target, GLint level, GLint internalFormat,
+	GLsizei width, GLsizei height, GLsizei depth, GLint border,
+	GLenum format, GLenum type, const GLvoid * data );
+void glCompressedTexImage2D( GLenum target, GLint level, GLenum internalformat,
+	GLsizei width, GLsizei height, GLint border,
+	GLsizei imageSize, const GLvoid * data );
+void glCompressedTexImage3D( GLenum target, GLint level, GLenum internalformat,
+	GLsizei width, GLsizei height, GLsizei depth, GLint border,
+	GLsizei imageSize, const GLvoid * data );
+void glTexStorage2D( GLenum target, GLsizei levels, GLenum internalformat,
+	GLsizei width, GLsizei height );
+void glTexStorage3D( GLenum target, GLsizei levels, GLenum internalformat,
+	GLsizei width, GLsizei height, GLsizei depth );
+void glVertexAttribPointer( GLuint index, GLint size, GLenum type, GLboolean normalized,
+	GLsizei stride, const GLvoid * pointer);
+
+
+IMPLEMENTATION
+==============
+
+This file does not include OpenGL / OpenGL ES headers because:
+
+  1. Including OpenGL / OpenGL ES headers is platform dependent and
+     may require a separate installation of an OpenGL SDK.
+  2. The OpenGL format/type constants are the same between extensions and core.
+  3. The OpenGL format/type constants are the same between OpenGL and OpenGL ES.
+  4. The OpenGL constants in this header are also used to derive Vulkan formats
+     from the OpenGL formats/types stored in files like KTX and glTF. These file
+     formats may use OpenGL formats/types that are not supported by the OpenGL
+     implementation on the platform but are supported by the Vulkan implementation.
+
+
+ENTRY POINTS
+============
+
+static inline GLenum glGetFormatFromInternalFormat( const GLenum internalFormat );
+static inline GLenum glGetTypeFromInternalFormat( const GLenum internalFormat );
+static inline void glGetFormatSize( const GLenum internalFormat, GlFormatSize * pFormatSize );
+static inline unsigned int glGetTypeSizeFromType( const GLenum type );
+ 
+MODIFICATIONS for use in libktx
+===============================
+ 
+2018.3.23 Added glGetTypeSizeFromType. Mark Callow, Edgewise Consulting.
+2019.3.09 #if 0 around GL type declarations. Mark Callow,     〃
+
+================================================================================================
+*/
+
+#if !defined( GL_FORMAT_H )
+#define GL_FORMAT_H
+
+#include <assert.h>
+
+#if defined(_WIN32)
+#define NOMINMAX
+#ifndef __cplusplus
+#undef inline
+#define inline __inline
+#endif // __cplusplus
+#endif
+
+/*
+===========================================================================
+Avoid warnings or even errors when using strict C99. "Redefinition of
+(type) is a C11 feature." All includers in libktx also include ktx.h where
+they are also defined.
+===========================================================================
+*/
+#if 0
+typedef unsigned int GLenum;
+typedef unsigned char GLboolean;
+typedef unsigned int GLuint;
+#endif
+
+#if !defined( GL_INVALID_VALUE )
+#define GL_INVALID_VALUE								0x0501
+#endif
+
+/*
+================================================================================================================================
+
+Format to glTexImage2D and glTexImage3D.
+
+================================================================================================================================
+*/
+
+#if !defined( GL_RED )
+#define GL_RED											0x1903	// same as GL_RED_EXT
+#endif
+#if !defined( GL_GREEN )
+#define GL_GREEN										0x1904	// deprecated
+#endif
+#if !defined( GL_BLUE )
+#define GL_BLUE											0x1905	// deprecated
+#endif
+#if !defined( GL_ALPHA )
+#define GL_ALPHA										0x1906	// deprecated
+#endif
+#if !defined( GL_LUMINANCE )
+#define GL_LUMINANCE									0x1909	// deprecated
+#endif
+#if !defined( GL_SLUMINANCE )
+#define GL_SLUMINANCE									0x8C46	// deprecated, same as GL_SLUMINANCE_EXT
+#endif
+#if !defined( GL_LUMINANCE_ALPHA )
+#define GL_LUMINANCE_ALPHA								0x190A	// deprecated
+#endif
+#if !defined( GL_SLUMINANCE_ALPHA )
+#define GL_SLUMINANCE_ALPHA								0x8C44	// deprecated, same as GL_SLUMINANCE_ALPHA_EXT
+#endif
+#if !defined( GL_INTENSITY )
+#define GL_INTENSITY									0x8049	// deprecated, same as GL_INTENSITY_EXT
+#endif
+#if !defined( GL_RG )
+#define GL_RG											0x8227	// same as GL_RG_EXT
+#endif
+#if !defined( GL_RGB )
+#define GL_RGB											0x1907
+#endif
+#if !defined( GL_BGR )
+#define GL_BGR											0x80E0	// same as GL_BGR_EXT
+#endif
+#if !defined( GL_RGBA )
+#define GL_RGBA											0x1908
+#endif
+#if !defined( GL_BGRA )
+#define GL_BGRA											0x80E1	// same as GL_BGRA_EXT
+#endif
+#if !defined( GL_RED_INTEGER )
+#define GL_RED_INTEGER									0x8D94	// same as GL_RED_INTEGER_EXT
+#endif
+#if !defined( GL_GREEN_INTEGER )
+#define GL_GREEN_INTEGER								0x8D95	// deprecated, same as GL_GREEN_INTEGER_EXT
+#endif
+#if !defined( GL_BLUE_INTEGER )
+#define GL_BLUE_INTEGER									0x8D96	// deprecated, same as GL_BLUE_INTEGER_EXT
+#endif
+#if !defined( GL_ALPHA_INTEGER )
+#define GL_ALPHA_INTEGER								0x8D97	// deprecated, same as GL_ALPHA_INTEGER_EXT
+#endif
+#if !defined( GL_LUMINANCE_INTEGER )
+#define GL_LUMINANCE_INTEGER							0x8D9C	// deprecated, same as GL_LUMINANCE_INTEGER_EXT
+#endif
+#if !defined( GL_LUMINANCE_ALPHA_INTEGER )
+#define GL_LUMINANCE_ALPHA_INTEGER						0x8D9D	// deprecated, same as GL_LUMINANCE_ALPHA_INTEGER_EXT
+#endif
+#if !defined( GL_RG_INTEGER )
+#define GL_RG_INTEGER									0x8228	// same as GL_RG_INTEGER_EXT
+#endif
+#if !defined( GL_RGB_INTEGER )
+#define GL_RGB_INTEGER									0x8D98	// same as GL_RGB_INTEGER_EXT
+#endif
+#if !defined( GL_BGR_INTEGER )
+#define GL_BGR_INTEGER									0x8D9A	// same as GL_BGR_INTEGER_EXT
+#endif
+#if !defined( GL_RGBA_INTEGER )
+#define GL_RGBA_INTEGER									0x8D99	// same as GL_RGBA_INTEGER_EXT
+#endif
+#if !defined( GL_BGRA_INTEGER )
+#define GL_BGRA_INTEGER									0x8D9B	// same as GL_BGRA_INTEGER_EXT
+#endif
+#if !defined( GL_COLOR_INDEX )
+#define GL_COLOR_INDEX									0x1900	// deprecated
+#endif
+#if !defined( GL_STENCIL_INDEX )
+#define GL_STENCIL_INDEX								0x1901
+#endif
+#if !defined( GL_DEPTH_COMPONENT )
+#define GL_DEPTH_COMPONENT								0x1902
+#endif
+#if !defined( GL_DEPTH_STENCIL )
+#define GL_DEPTH_STENCIL								0x84F9	// same as GL_DEPTH_STENCIL_NV and GL_DEPTH_STENCIL_EXT and GL_DEPTH_STENCIL_OES
+#endif
+
+/*
+================================================================================================================================
+
+Type to glTexImage2D, glTexImage3D and glVertexAttribPointer.
+
+================================================================================================================================
+*/
+
+#if !defined( GL_BYTE )
+#define GL_BYTE											0x1400
+#endif
+#if !defined( GL_UNSIGNED_BYTE )
+#define GL_UNSIGNED_BYTE								0x1401
+#endif
+#if !defined( GL_SHORT )
+#define GL_SHORT										0x1402
+#endif
+#if !defined( GL_UNSIGNED_SHORT )
+#define GL_UNSIGNED_SHORT								0x1403
+#endif
+#if !defined( GL_INT )
+#define GL_INT											0x1404
+#endif
+#if !defined( GL_UNSIGNED_INT )
+#define GL_UNSIGNED_INT									0x1405
+#endif
+#if !defined( GL_INT64 )
+#define GL_INT64										0x140E	// same as GL_INT64_NV and GL_INT64_ARB
+#endif
+#if !defined( GL_UNSIGNED_INT64 )
+#define GL_UNSIGNED_INT64								0x140F	// same as GL_UNSIGNED_INT64_NV and GL_UNSIGNED_INT64_ARB
+#endif
+#if !defined( GL_HALF_FLOAT )
+#define GL_HALF_FLOAT									0x140B	// same as GL_HALF_FLOAT_NV and GL_HALF_FLOAT_ARB
+#endif
+#if !defined( GL_HALF_FLOAT_OES )
+#define GL_HALF_FLOAT_OES								0x8D61	// Note that this different from GL_HALF_FLOAT.
+#endif
+#if !defined( GL_FLOAT )
+#define GL_FLOAT										0x1406
+#endif
+#if !defined( GL_DOUBLE )
+#define GL_DOUBLE										0x140A	// same as GL_DOUBLE_EXT
+#endif
+#if !defined( GL_UNSIGNED_BYTE_3_3_2 )
+#define GL_UNSIGNED_BYTE_3_3_2							0x8032	// same as GL_UNSIGNED_BYTE_3_3_2_EXT
+#endif
+#if !defined( GL_UNSIGNED_BYTE_2_3_3_REV )
+#define GL_UNSIGNED_BYTE_2_3_3_REV						0x8362	// same as GL_UNSIGNED_BYTE_2_3_3_REV_EXT
+#endif
+#if !defined( GL_UNSIGNED_SHORT_5_6_5 )
+#define GL_UNSIGNED_SHORT_5_6_5							0x8363	// same as GL_UNSIGNED_SHORT_5_6_5_EXT
+#endif
+#if !defined( GL_UNSIGNED_SHORT_5_6_5_REV )
+#define GL_UNSIGNED_SHORT_5_6_5_REV						0x8364	// same as GL_UNSIGNED_SHORT_5_6_5_REV_EXT
+#endif
+#if !defined( GL_UNSIGNED_SHORT_4_4_4_4 )
+#define GL_UNSIGNED_SHORT_4_4_4_4						0x8033	// same as GL_UNSIGNED_SHORT_4_4_4_4_EXT
+#endif
+#if !defined( GL_UNSIGNED_SHORT_4_4_4_4_REV )
+#define GL_UNSIGNED_SHORT_4_4_4_4_REV					0x8365	// same as GL_UNSIGNED_SHORT_4_4_4_4_REV_IMG and GL_UNSIGNED_SHORT_4_4_4_4_REV_EXT
+#endif
+#if !defined( GL_UNSIGNED_SHORT_5_5_5_1 )
+#define GL_UNSIGNED_SHORT_5_5_5_1						0x8034	// same as GL_UNSIGNED_SHORT_5_5_5_1_EXT
+#endif
+#if !defined( GL_UNSIGNED_SHORT_1_5_5_5_REV )
+#define GL_UNSIGNED_SHORT_1_5_5_5_REV					0x8366	// same as GL_UNSIGNED_SHORT_1_5_5_5_REV_EXT
+#endif
+#if !defined( GL_UNSIGNED_INT_8_8_8_8 )
+#define GL_UNSIGNED_INT_8_8_8_8							0x8035	// same as GL_UNSIGNED_INT_8_8_8_8_EXT
+#endif
+#if !defined( GL_UNSIGNED_INT_8_8_8_8_REV )
+#define GL_UNSIGNED_INT_8_8_8_8_REV						0x8367	// same as GL_UNSIGNED_INT_8_8_8_8_REV_EXT
+#endif
+#if !defined( GL_UNSIGNED_INT_10_10_10_2 )
+#define GL_UNSIGNED_INT_10_10_10_2						0x8036	// same as GL_UNSIGNED_INT_10_10_10_2_EXT
+#endif
+#if !defined( GL_UNSIGNED_INT_2_10_10_10_REV )
+#define GL_UNSIGNED_INT_2_10_10_10_REV					0x8368	// same as GL_UNSIGNED_INT_2_10_10_10_REV_EXT
+#endif
+#if !defined( GL_UNSIGNED_INT_10F_11F_11F_REV )
+#define GL_UNSIGNED_INT_10F_11F_11F_REV					0x8C3B	// same as GL_UNSIGNED_INT_10F_11F_11F_REV_EXT
+#endif
+#if !defined( GL_UNSIGNED_INT_5_9_9_9_REV )
+#define GL_UNSIGNED_INT_5_9_9_9_REV						0x8C3E	// same as GL_UNSIGNED_INT_5_9_9_9_REV_EXT
+#endif
+#if !defined( GL_UNSIGNED_INT_24_8 )
+#define GL_UNSIGNED_INT_24_8							0x84FA	// same as GL_UNSIGNED_INT_24_8_NV and GL_UNSIGNED_INT_24_8_EXT and GL_UNSIGNED_INT_24_8_OES
+#endif
+#if !defined( GL_FLOAT_32_UNSIGNED_INT_24_8_REV )
+#define GL_FLOAT_32_UNSIGNED_INT_24_8_REV				0x8DAD	// same as GL_FLOAT_32_UNSIGNED_INT_24_8_REV_NV and GL_FLOAT_32_UNSIGNED_INT_24_8_REV_ARB
+#endif
+
+/*
+================================================================================================================================
+
+Internal format to glTexImage2D, glTexImage3D, glCompressedTexImage2D, glCompressedTexImage3D, glTexStorage2D, glTexStorage3D
+
+================================================================================================================================
+*/
+
+//
+// 8 bits per component
+//
+
+#if !defined( GL_R8 )
+#define GL_R8											0x8229	// same as GL_R8_EXT
+#endif
+#if !defined( GL_RG8 )
+#define GL_RG8											0x822B	// same as GL_RG8_EXT
+#endif
+#if !defined( GL_RGB8 )
+#define GL_RGB8											0x8051	// same as GL_RGB8_EXT and GL_RGB8_OES
+#endif
+#if !defined( GL_RGBA8 )
+#define GL_RGBA8										0x8058	// same as GL_RGBA8_EXT and GL_RGBA8_OES
+#endif
+
+#if !defined( GL_R8_SNORM )
+#define GL_R8_SNORM										0x8F94
+#endif
+#if !defined( GL_RG8_SNORM )
+#define GL_RG8_SNORM									0x8F95
+#endif
+#if !defined( GL_RGB8_SNORM )
+#define GL_RGB8_SNORM									0x8F96
+#endif
+#if !defined( GL_RGBA8_SNORM )
+#define GL_RGBA8_SNORM									0x8F97
+#endif
+
+#if !defined( GL_R8UI )
+#define GL_R8UI											0x8232
+#endif
+#if !defined( GL_RG8UI )
+#define GL_RG8UI										0x8238
+#endif
+#if !defined( GL_RGB8UI )
+#define GL_RGB8UI										0x8D7D	// same as GL_RGB8UI_EXT
+#endif
+#if !defined( GL_RGBA8UI )
+#define GL_RGBA8UI										0x8D7C	// same as GL_RGBA8UI_EXT
+#endif
+
+#if !defined( GL_R8I )
+#define GL_R8I											0x8231
+#endif
+#if !defined( GL_RG8I )
+#define GL_RG8I											0x8237
+#endif
+#if !defined( GL_RGB8I )
+#define GL_RGB8I										0x8D8F	// same as GL_RGB8I_EXT
+#endif
+#if !defined( GL_RGBA8I )
+#define GL_RGBA8I										0x8D8E	// same as GL_RGBA8I_EXT
+#endif
+
+#if !defined( GL_SR8 )
+#define GL_SR8											0x8FBD	// same as GL_SR8_EXT
+#endif
+#if !defined( GL_SRG8 )
+#define GL_SRG8											0x8FBE	// same as GL_SRG8_EXT
+#endif
+#if !defined( GL_SRGB8 )
+#define GL_SRGB8										0x8C41	// same as GL_SRGB8_EXT
+#endif
+#if !defined( GL_SRGB8_ALPHA8 )
+#define GL_SRGB8_ALPHA8									0x8C43	// same as GL_SRGB8_ALPHA8_EXT
+#endif
+
+//
+// 16 bits per component
+//
+
+#if !defined( GL_R16 )
+#define GL_R16											0x822A	// same as GL_R16_EXT
+#endif
+#if !defined( GL_RG16 )
+#define GL_RG16											0x822C	// same as GL_RG16_EXT
+#endif
+#if !defined( GL_RGB16 )
+#define GL_RGB16										0x8054	// same as GL_RGB16_EXT
+#endif
+#if !defined( GL_RGBA16 )
+#define GL_RGBA16										0x805B	// same as GL_RGBA16_EXT
+#endif
+
+#if !defined( GL_R16_SNORM )
+#define GL_R16_SNORM									0x8F98	// same as GL_R16_SNORM_EXT
+#endif
+#if !defined( GL_RG16_SNORM )
+#define GL_RG16_SNORM									0x8F99	// same as GL_RG16_SNORM_EXT
+#endif
+#if !defined( GL_RGB16_SNORM )
+#define GL_RGB16_SNORM									0x8F9A	// same as GL_RGB16_SNORM_EXT
+#endif
+#if !defined( GL_RGBA16_SNORM )
+#define GL_RGBA16_SNORM									0x8F9B	// same as GL_RGBA16_SNORM_EXT
+#endif
+
+#if !defined( GL_R16UI )
+#define GL_R16UI										0x8234
+#endif
+#if !defined( GL_RG16UI )
+#define GL_RG16UI										0x823A
+#endif
+#if !defined( GL_RGB16UI )
+#define GL_RGB16UI										0x8D77	// same as GL_RGB16UI_EXT
+#endif
+#if !defined( GL_RGBA16UI )
+#define GL_RGBA16UI										0x8D76	// same as GL_RGBA16UI_EXT
+#endif
+
+#if !defined( GL_R16I )
+#define GL_R16I											0x8233
+#endif
+#if !defined( GL_RG16I )
+#define GL_RG16I										0x8239
+#endif
+#if !defined( GL_RGB16I )
+#define GL_RGB16I										0x8D89	// same as GL_RGB16I_EXT
+#endif
+#if !defined( GL_RGBA16I )
+#define GL_RGBA16I										0x8D88	// same as GL_RGBA16I_EXT
+#endif
+
+#if !defined( GL_R16F )
+#define GL_R16F											0x822D	// same as GL_R16F_EXT
+#endif
+#if !defined( GL_RG16F )
+#define GL_RG16F										0x822F	// same as GL_RG16F_EXT
+#endif
+#if !defined( GL_RGB16F )
+#define GL_RGB16F										0x881B	// same as GL_RGB16F_EXT and GL_RGB16F_ARB
+#endif
+#if !defined( GL_RGBA16F )
+#define GL_RGBA16F										0x881A	// sama as GL_RGBA16F_EXT and GL_RGBA16F_ARB
+#endif
+
+//
+// 32 bits per component
+//
+
+#if !defined( GL_R32UI )
+#define GL_R32UI										0x8236
+#endif
+#if !defined( GL_RG32UI )
+#define GL_RG32UI										0x823C
+#endif
+#if !defined( GL_RGB32UI )
+#define GL_RGB32UI										0x8D71	// same as GL_RGB32UI_EXT
+#endif
+#if !defined( GL_RGBA32UI )
+#define GL_RGBA32UI										0x8D70	// same as GL_RGBA32UI_EXT
+#endif
+
+#if !defined( GL_R32I )
+#define GL_R32I											0x8235
+#endif
+#if !defined( GL_RG32I )
+#define GL_RG32I										0x823B
+#endif
+#if !defined( GL_RGB32I )
+#define GL_RGB32I										0x8D83	// same as GL_RGB32I_EXT 
+#endif
+#if !defined( GL_RGBA32I )
+#define GL_RGBA32I										0x8D82	// same as GL_RGBA32I_EXT
+#endif
+
+#if !defined( GL_R32F )
+#define GL_R32F											0x822E	// same as GL_R32F_EXT
+#endif
+#if !defined( GL_RG32F )
+#define GL_RG32F										0x8230	// same as GL_RG32F_EXT
+#endif
+#if !defined( GL_RGB32F )
+#define GL_RGB32F										0x8815	// same as GL_RGB32F_EXT and GL_RGB32F_ARB
+#endif
+#if !defined( GL_RGBA32F )
+#define GL_RGBA32F										0x8814	// same as GL_RGBA32F_EXT and GL_RGBA32F_ARB
+#endif
+
+//
+// Packed
+//
+
+#if !defined( GL_R3_G3_B2 )
+#define GL_R3_G3_B2										0x2A10
+#endif
+#if !defined( GL_RGB4 )
+#define GL_RGB4											0x804F	// same as GL_RGB4_EXT
+#endif
+#if !defined( GL_RGB5 )
+#define GL_RGB5											0x8050	// same as GL_RGB5_EXT
+#endif
+#if !defined( GL_RGB565 )
+#define GL_RGB565										0x8D62	// same as GL_RGB565_EXT and GL_RGB565_OES
+#endif
+#if !defined( GL_RGB10 )
+#define GL_RGB10										0x8052	// same as GL_RGB10_EXT
+#endif
+#if !defined( GL_RGB12 )
+#define GL_RGB12										0x8053	// same as GL_RGB12_EXT
+#endif
+#if !defined( GL_RGBA2 )
+#define GL_RGBA2										0x8055	// same as GL_RGBA2_EXT
+#endif
+#if !defined( GL_RGBA4 )
+#define GL_RGBA4										0x8056	// same as GL_RGBA4_EXT and GL_RGBA4_OES
+#endif
+#if !defined( GL_RGBA12 )
+#define GL_RGBA12										0x805A	// same as GL_RGBA12_EXT
+#endif
+#if !defined( GL_RGB5_A1 )
+#define GL_RGB5_A1										0x8057	// same as GL_RGB5_A1_EXT and GL_RGB5_A1_OES
+#endif
+#if !defined( GL_RGB10_A2 )
+#define GL_RGB10_A2										0x8059	// same as GL_RGB10_A2_EXT
+#endif
+#if !defined( GL_RGB10_A2UI )
+#define GL_RGB10_A2UI									0x906F
+#endif
+#if !defined( GL_R11F_G11F_B10F )
+#define GL_R11F_G11F_B10F								0x8C3A	// same as GL_R11F_G11F_B10F_APPLE and GL_R11F_G11F_B10F_EXT
+#endif
+#if !defined( GL_RGB9_E5 )
+#define GL_RGB9_E5										0x8C3D	// same as GL_RGB9_E5_APPLE and GL_RGB9_E5_EXT
+#endif
+
+//
+// Alpha
+//
+
+#if !defined( GL_ALPHA4 )
+#define GL_ALPHA4										0x803B	// deprecated, same as GL_ALPHA4_EXT
+#endif
+#if !defined( GL_ALPHA8 )
+#define GL_ALPHA8										0x803C	// deprecated, same as GL_ALPHA8_EXT
+#endif
+#if !defined( GL_ALPHA8_SNORM )
+#define GL_ALPHA8_SNORM									0x9014	// deprecated
+#endif
+#if !defined( GL_ALPHA8UI_EXT )
+#define GL_ALPHA8UI_EXT									0x8D7E	// deprecated
+#endif
+#if !defined( GL_ALPHA8I_EXT )
+#define GL_ALPHA8I_EXT									0x8D90	// deprecated
+#endif
+#if !defined( GL_ALPHA12 )
+#define GL_ALPHA12										0x803D	// deprecated, same as GL_ALPHA12_EXT
+#endif
+#if !defined( GL_ALPHA16 )
+#define GL_ALPHA16										0x803E	// deprecated, same as GL_ALPHA16_EXT
+#endif
+#if !defined( GL_ALPHA16_SNORM )
+#define GL_ALPHA16_SNORM								0x9018	// deprecated
+#endif
+#if !defined( GL_ALPHA16UI_EXT )
+#define GL_ALPHA16UI_EXT								0x8D78	// deprecated
+#endif
+#if !defined( GL_ALPHA16I_EXT )
+#define GL_ALPHA16I_EXT									0x8D8A	// deprecated
+#endif
+#if !defined( GL_ALPHA16F_ARB )
+#define GL_ALPHA16F_ARB									0x881C	// deprecated, same as GL_ALPHA_FLOAT16_APPLE and GL_ALPHA_FLOAT16_ATI
+#endif
+#if !defined( GL_ALPHA32UI_EXT )
+#define GL_ALPHA32UI_EXT								0x8D72	// deprecated
+#endif
+#if !defined( GL_ALPHA32I_EXT )
+#define GL_ALPHA32I_EXT									0x8D84	// deprecated
+#endif
+#if !defined( GL_ALPHA32F_ARB )
+#define GL_ALPHA32F_ARB									0x8816	// deprecated, same as GL_ALPHA_FLOAT32_APPLE and GL_ALPHA_FLOAT32_ATI
+#endif
+
+//
+// Luminance
+//
+
+#if !defined( GL_LUMINANCE4 )
+#define GL_LUMINANCE4									0x803F	// deprecated, same as GL_LUMINANCE4_EXT
+#endif
+#if !defined( GL_LUMINANCE8 )
+#define GL_LUMINANCE8									0x8040	// deprecated, same as GL_LUMINANCE8_EXT
+#endif
+#if !defined( GL_LUMINANCE8_SNORM )
+#define GL_LUMINANCE8_SNORM								0x9015	// deprecated
+#endif
+#if !defined( GL_SLUMINANCE8 )
+#define GL_SLUMINANCE8									0x8C47	// deprecated, same as GL_SLUMINANCE8_EXT
+#endif
+#if !defined( GL_LUMINANCE8UI_EXT )
+#define GL_LUMINANCE8UI_EXT								0x8D80	// deprecated
+#endif
+#if !defined( GL_LUMINANCE8I_EXT )
+#define GL_LUMINANCE8I_EXT								0x8D92	// deprecated
+#endif
+#if !defined( GL_LUMINANCE12 )
+#define GL_LUMINANCE12									0x8041	// deprecated, same as GL_LUMINANCE12_EXT
+#endif
+#if !defined( GL_LUMINANCE16 )
+#define GL_LUMINANCE16									0x8042	// deprecated, same as GL_LUMINANCE16_EXT
+#endif
+#if !defined( GL_LUMINANCE16_SNORM )
+#define GL_LUMINANCE16_SNORM							0x9019	// deprecated
+#endif
+#if !defined( GL_LUMINANCE16UI_EXT )
+#define GL_LUMINANCE16UI_EXT							0x8D7A	// deprecated
+#endif
+#if !defined( GL_LUMINANCE16I_EXT )
+#define GL_LUMINANCE16I_EXT								0x8D8C	// deprecated
+#endif
+#if !defined( GL_LUMINANCE16F_ARB )
+#define GL_LUMINANCE16F_ARB								0x881E	// deprecated, same as GL_LUMINANCE_FLOAT16_APPLE and GL_LUMINANCE_FLOAT16_ATI
+#endif
+#if !defined( GL_LUMINANCE32UI_EXT )
+#define GL_LUMINANCE32UI_EXT							0x8D74	// deprecated
+#endif
+#if !defined( GL_LUMINANCE32I_EXT )
+#define GL_LUMINANCE32I_EXT								0x8D86	// deprecated
+#endif
+#if !defined( GL_LUMINANCE32F_ARB )
+#define GL_LUMINANCE32F_ARB								0x8818	// deprecated, same as GL_LUMINANCE_FLOAT32_APPLE and GL_LUMINANCE_FLOAT32_ATI
+#endif
+
+//
+// Luminance/Alpha
+//
+
+#if !defined( GL_LUMINANCE4_ALPHA4 )
+#define GL_LUMINANCE4_ALPHA4							0x8043	// deprecated, same as GL_LUMINANCE4_ALPHA4_EXT
+#endif
+#if !defined( GL_LUMINANCE6_ALPHA2 )
+#define GL_LUMINANCE6_ALPHA2							0x8044	// deprecated, same as GL_LUMINANCE6_ALPHA2_EXT
+#endif
+#if !defined( GL_LUMINANCE8_ALPHA8 )
+#define GL_LUMINANCE8_ALPHA8							0x8045	// deprecated, same as GL_LUMINANCE8_ALPHA8_EXT
+#endif
+#if !defined( GL_LUMINANCE8_ALPHA8_SNORM )
+#define GL_LUMINANCE8_ALPHA8_SNORM						0x9016	// deprecated
+#endif
+#if !defined( GL_SLUMINANCE8_ALPHA8 )
+#define GL_SLUMINANCE8_ALPHA8							0x8C45	// deprecated, same as GL_SLUMINANCE8_ALPHA8_EXT
+#endif
+#if !defined( GL_LUMINANCE_ALPHA8UI_EXT )
+#define GL_LUMINANCE_ALPHA8UI_EXT						0x8D81	// deprecated
+#endif
+#if !defined( GL_LUMINANCE_ALPHA8I_EXT )
+#define GL_LUMINANCE_ALPHA8I_EXT						0x8D93	// deprecated
+#endif
+#if !defined( GL_LUMINANCE12_ALPHA4 )
+#define GL_LUMINANCE12_ALPHA4							0x8046	// deprecated, same as GL_LUMINANCE12_ALPHA4_EXT
+#endif
+#if !defined( GL_LUMINANCE12_ALPHA12 )
+#define GL_LUMINANCE12_ALPHA12							0x8047	// deprecated, same as GL_LUMINANCE12_ALPHA12_EXT
+#endif
+#if !defined( GL_LUMINANCE16_ALPHA16 )
+#define GL_LUMINANCE16_ALPHA16							0x8048	// deprecated, same as GL_LUMINANCE16_ALPHA16_EXT
+#endif
+#if !defined( GL_LUMINANCE16_ALPHA16_SNORM )
+#define GL_LUMINANCE16_ALPHA16_SNORM					0x901A	// deprecated
+#endif
+#if !defined( GL_LUMINANCE_ALPHA16UI_EXT )
+#define GL_LUMINANCE_ALPHA16UI_EXT						0x8D7B	// deprecated
+#endif
+#if !defined( GL_LUMINANCE_ALPHA16I_EXT )
+#define GL_LUMINANCE_ALPHA16I_EXT						0x8D8D	// deprecated
+#endif
+#if !defined( GL_LUMINANCE_ALPHA16F_ARB )
+#define GL_LUMINANCE_ALPHA16F_ARB						0x881F	// deprecated, same as GL_LUMINANCE_ALPHA_FLOAT16_APPLE and GL_LUMINANCE_ALPHA_FLOAT16_ATI
+#endif
+#if !defined( GL_LUMINANCE_ALPHA32UI_EXT )
+#define GL_LUMINANCE_ALPHA32UI_EXT						0x8D75	// deprecated
+#endif
+#if !defined( GL_LUMINANCE_ALPHA32I_EXT )
+#define GL_LUMINANCE_ALPHA32I_EXT						0x8D87	// deprecated
+#endif
+#if !defined( GL_LUMINANCE_ALPHA32F_ARB )
+#define GL_LUMINANCE_ALPHA32F_ARB						0x8819	// deprecated, same as GL_LUMINANCE_ALPHA_FLOAT32_APPLE and GL_LUMINANCE_ALPHA_FLOAT32_ATI
+#endif
+
+//
+// Intensity
+//
+
+#if !defined( GL_INTENSITY4 )
+#define GL_INTENSITY4									0x804A	// deprecated, same as GL_INTENSITY4_EXT
+#endif
+#if !defined( GL_INTENSITY8 )
+#define GL_INTENSITY8									0x804B	// deprecated, same as GL_INTENSITY8_EXT
+#endif
+#if !defined( GL_INTENSITY8_SNORM )
+#define GL_INTENSITY8_SNORM								0x9017	// deprecated
+#endif
+#if !defined( GL_INTENSITY8UI_EXT )
+#define GL_INTENSITY8UI_EXT								0x8D7F	// deprecated
+#endif
+#if !defined( GL_INTENSITY8I_EXT )
+#define GL_INTENSITY8I_EXT								0x8D91	// deprecated
+#endif
+#if !defined( GL_INTENSITY12 )
+#define GL_INTENSITY12									0x804C	// deprecated, same as GL_INTENSITY12_EXT
+#endif
+#if !defined( GL_INTENSITY16 )
+#define GL_INTENSITY16									0x804D	// deprecated, same as GL_INTENSITY16_EXT
+#endif
+#if !defined( GL_INTENSITY16_SNORM )
+#define GL_INTENSITY16_SNORM							0x901B	// deprecated
+#endif
+#if !defined( GL_INTENSITY16UI_EXT )
+#define GL_INTENSITY16UI_EXT							0x8D79	// deprecated
+#endif
+#if !defined( GL_INTENSITY16I_EXT )
+#define GL_INTENSITY16I_EXT								0x8D8B	// deprecated
+#endif
+#if !defined( GL_INTENSITY16F_ARB )
+#define GL_INTENSITY16F_ARB								0x881D	// deprecated, same as GL_INTENSITY_FLOAT16_APPLE and GL_INTENSITY_FLOAT16_ATI
+#endif
+#if !defined( GL_INTENSITY32UI_EXT )
+#define GL_INTENSITY32UI_EXT							0x8D73	// deprecated
+#endif
+#if !defined( GL_INTENSITY32I_EXT )
+#define GL_INTENSITY32I_EXT								0x8D85	// deprecated
+#endif
+#if !defined( GL_INTENSITY32F_ARB )
+#define GL_INTENSITY32F_ARB								0x8817	// deprecated, same as GL_INTENSITY_FLOAT32_APPLE and GL_INTENSITY_FLOAT32_ATI
+#endif
+
+//
+// Generic compression
+//
+
+#if !defined( GL_COMPRESSED_RED )
+#define GL_COMPRESSED_RED								0x8225
+#endif
+#if !defined( GL_COMPRESSED_ALPHA )
+#define GL_COMPRESSED_ALPHA								0x84E9	// deprecated, same as GL_COMPRESSED_ALPHA_ARB
+#endif
+#if !defined( GL_COMPRESSED_LUMINANCE )
+#define GL_COMPRESSED_LUMINANCE							0x84EA	// deprecated, same as GL_COMPRESSED_LUMINANCE_ARB
+#endif
+#if !defined( GL_COMPRESSED_SLUMINANCE )
+#define GL_COMPRESSED_SLUMINANCE						0x8C4A	// deprecated, same as GL_COMPRESSED_SLUMINANCE_EXT
+#endif
+#if !defined( GL_COMPRESSED_LUMINANCE_ALPHA )
+#define GL_COMPRESSED_LUMINANCE_ALPHA					0x84EB	// deprecated, same as GL_COMPRESSED_LUMINANCE_ALPHA_ARB
+#endif
+#if !defined( GL_COMPRESSED_SLUMINANCE_ALPHA )
+#define GL_COMPRESSED_SLUMINANCE_ALPHA					0x8C4B	// deprecated, same as GL_COMPRESSED_SLUMINANCE_ALPHA_EXT
+#endif
+#if !defined( GL_COMPRESSED_INTENSITY )
+#define GL_COMPRESSED_INTENSITY							0x84EC	// deprecated, same as GL_COMPRESSED_INTENSITY_ARB
+#endif
+#if !defined( GL_COMPRESSED_RG )
+#define GL_COMPRESSED_RG								0x8226
+#endif
+#if !defined( GL_COMPRESSED_RGB )
+#define GL_COMPRESSED_RGB								0x84ED	// same as GL_COMPRESSED_RGB_ARB
+#endif
+#if !defined( GL_COMPRESSED_RGBA )
+#define GL_COMPRESSED_RGBA								0x84EE	// same as GL_COMPRESSED_RGBA_ARB
+#endif
+#if !defined( GL_COMPRESSED_SRGB )
+#define GL_COMPRESSED_SRGB								0x8C48	// same as GL_COMPRESSED_SRGB_EXT
+#endif
+#if !defined( GL_COMPRESSED_SRGB_ALPHA )
+#define GL_COMPRESSED_SRGB_ALPHA						0x8C49	// same as GL_COMPRESSED_SRGB_ALPHA_EXT
+#endif
+
+//
+// FXT1
+//
+
+#if !defined( GL_COMPRESSED_RGB_FXT1_3DFX )
+#define GL_COMPRESSED_RGB_FXT1_3DFX						0x86B0	// deprecated
+#endif
+#if !defined( GL_COMPRESSED_RGBA_FXT1_3DFX )
+#define GL_COMPRESSED_RGBA_FXT1_3DFX					0x86B1	// deprecated
+#endif
+
+//
+// S3TC/DXT/BC
+//
+
+#if !defined( GL_COMPRESSED_RGB_S3TC_DXT1_EXT )
+#define GL_COMPRESSED_RGB_S3TC_DXT1_EXT					0x83F0
+#endif
+#if !defined( GL_COMPRESSED_RGBA_S3TC_DXT1_EXT )
+#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT				0x83F1
+#endif
+#if !defined( GL_COMPRESSED_RGBA_S3TC_DXT3_EXT )
+#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT				0x83F2
+#endif
+#if !defined( GL_COMPRESSED_RGBA_S3TC_DXT5_EXT )
+#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT				0x83F3
+#endif
+
+#if !defined( GL_COMPRESSED_SRGB_S3TC_DXT1_EXT )
+#define GL_COMPRESSED_SRGB_S3TC_DXT1_EXT				0x8C4C
+#endif
+#if !defined( GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT )
+#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT			0x8C4D
+#endif
+#if !defined( GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT )
+#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT			0x8C4E
+#endif
+#if !defined( GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT )
+#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT			0x8C4F
+#endif
+
+#if !defined( GL_COMPRESSED_LUMINANCE_LATC1_EXT )
+#define GL_COMPRESSED_LUMINANCE_LATC1_EXT				0x8C70
+#endif
+#if !defined( GL_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT )
+#define GL_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT			0x8C72
+#endif
+#if !defined( GL_COMPRESSED_SIGNED_LUMINANCE_LATC1_EXT )
+#define GL_COMPRESSED_SIGNED_LUMINANCE_LATC1_EXT		0x8C71
+#endif
+#if !defined( GL_COMPRESSED_SIGNED_LUMINANCE_ALPHA_LATC2_EXT )
+#define GL_COMPRESSED_SIGNED_LUMINANCE_ALPHA_LATC2_EXT	0x8C73
+#endif
+
+#if !defined( GL_COMPRESSED_RED_RGTC1 )
+#define GL_COMPRESSED_RED_RGTC1							0x8DBB	// same as GL_COMPRESSED_RED_RGTC1_EXT
+#endif
+#if !defined( GL_COMPRESSED_RG_RGTC2 )
+#define GL_COMPRESSED_RG_RGTC2							0x8DBD	// same as GL_COMPRESSED_RG_RGTC2_EXT
+#endif
+#if !defined( GL_COMPRESSED_SIGNED_RED_RGTC1 )
+#define GL_COMPRESSED_SIGNED_RED_RGTC1					0x8DBC	// same as GL_COMPRESSED_SIGNED_RED_RGTC1_EXT
+#endif
+#if !defined( GL_COMPRESSED_SIGNED_RG_RGTC2 )
+#define GL_COMPRESSED_SIGNED_RG_RGTC2					0x8DBE	// same as GL_COMPRESSED_SIGNED_RG_RGTC2_EXT
+#endif
+
+#if !defined( GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT )
+#define GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT				0x8E8E	// same as GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB
+#endif
+#if !defined( GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT )
+#define GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT			0x8E8F	// same as GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB
+#endif
+#if !defined( GL_COMPRESSED_RGBA_BPTC_UNORM )
+#define GL_COMPRESSED_RGBA_BPTC_UNORM					0x8E8C	// same as GL_COMPRESSED_RGBA_BPTC_UNORM_ARB	
+#endif
+#if !defined( GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM )
+#define GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM				0x8E8D	// same as GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB
+#endif
+
+//
+// ETC
+//
+
+#if !defined( GL_ETC1_RGB8_OES )
+#define GL_ETC1_RGB8_OES								0x8D64
+#endif
+
+#if !defined( GL_COMPRESSED_RGB8_ETC2 )
+#define GL_COMPRESSED_RGB8_ETC2							0x9274
+#endif
+#if !defined( GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2 )
+#define GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2		0x9276
+#endif
+#if !defined( GL_COMPRESSED_RGBA8_ETC2_EAC )
+#define GL_COMPRESSED_RGBA8_ETC2_EAC					0x9278
+#endif
+
+#if !defined( GL_COMPRESSED_SRGB8_ETC2 )
+#define GL_COMPRESSED_SRGB8_ETC2						0x9275
+#endif
+#if !defined( GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2 )
+#define GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2	0x9277
+#endif
+#if !defined( GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC )
+#define GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC				0x9279
+#endif
+
+#if !defined( GL_COMPRESSED_R11_EAC )
+#define GL_COMPRESSED_R11_EAC							0x9270
+#endif
+#if !defined( GL_COMPRESSED_RG11_EAC )
+#define GL_COMPRESSED_RG11_EAC							0x9272
+#endif
+#if !defined( GL_COMPRESSED_SIGNED_R11_EAC )
+#define GL_COMPRESSED_SIGNED_R11_EAC					0x9271
+#endif
+#if !defined( GL_COMPRESSED_SIGNED_RG11_EAC )
+#define GL_COMPRESSED_SIGNED_RG11_EAC					0x9273
+#endif
+
+//
+// PVRTC
+//
+
+#if !defined( GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG )
+#define GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG				0x8C01
+#define GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG				0x8C00
+#define GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG				0x8C03
+#define GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG				0x8C02
+#endif
+#if !defined( GL_COMPRESSED_RGBA_PVRTC_2BPPV2_IMG )
+#define GL_COMPRESSED_RGBA_PVRTC_2BPPV2_IMG				0x9137
+#define GL_COMPRESSED_RGBA_PVRTC_4BPPV2_IMG				0x9138
+#endif
+#if !defined( GL_COMPRESSED_SRGB_PVRTC_2BPPV1_EXT )
+#define GL_COMPRESSED_SRGB_PVRTC_2BPPV1_EXT				0x8A54
+#define GL_COMPRESSED_SRGB_PVRTC_4BPPV1_EXT				0x8A55
+#define GL_COMPRESSED_SRGB_ALPHA_PVRTC_2BPPV1_EXT		0x8A56
+#define GL_COMPRESSED_SRGB_ALPHA_PVRTC_4BPPV1_EXT		0x8A57
+#endif
+#if !defined( GL_COMPRESSED_SRGB_ALPHA_PVRTC_2BPPV2_IMG )
+#define GL_COMPRESSED_SRGB_ALPHA_PVRTC_2BPPV2_IMG		0x93F0
+#define GL_COMPRESSED_SRGB_ALPHA_PVRTC_4BPPV2_IMG		0x93F1
+#endif
+
+//
+// ASTC
+//
+
+#if !defined( GL_COMPRESSED_RGBA_ASTC_4x4_KHR )
+#define GL_COMPRESSED_RGBA_ASTC_4x4_KHR					0x93B0
+#define GL_COMPRESSED_RGBA_ASTC_5x4_KHR					0x93B1
+#define GL_COMPRESSED_RGBA_ASTC_5x5_KHR					0x93B2
+#define GL_COMPRESSED_RGBA_ASTC_6x5_KHR					0x93B3
+#define GL_COMPRESSED_RGBA_ASTC_6x6_KHR					0x93B4
+#define GL_COMPRESSED_RGBA_ASTC_8x5_KHR					0x93B5
+#define GL_COMPRESSED_RGBA_ASTC_8x6_KHR					0x93B6
+#define GL_COMPRESSED_RGBA_ASTC_8x8_KHR					0x93B7
+#define GL_COMPRESSED_RGBA_ASTC_10x5_KHR				0x93B8
+#define GL_COMPRESSED_RGBA_ASTC_10x6_KHR				0x93B9
+#define GL_COMPRESSED_RGBA_ASTC_10x8_KHR				0x93BA
+#define GL_COMPRESSED_RGBA_ASTC_10x10_KHR				0x93BB
+#define GL_COMPRESSED_RGBA_ASTC_12x10_KHR				0x93BC
+#define GL_COMPRESSED_RGBA_ASTC_12x12_KHR				0x93BD
+#endif
+
+#if !defined( GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR )
+#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR			0x93D0
+#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR			0x93D1
+#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR			0x93D2
+#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR			0x93D3
+#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR			0x93D4
+#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR			0x93D5
+#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR			0x93D6
+#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR			0x93D7
+#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR		0x93D8
+#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR		0x93D9
+#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR		0x93DA
+#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR		0x93DB
+#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR		0x93DC
+#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR		0x93DD
+#endif
+
+#if !defined( GL_COMPRESSED_RGBA_ASTC_3x3x3_OES )
+#define GL_COMPRESSED_RGBA_ASTC_3x3x3_OES				0x93C0
+#define GL_COMPRESSED_RGBA_ASTC_4x3x3_OES				0x93C1
+#define GL_COMPRESSED_RGBA_ASTC_4x4x3_OES				0x93C2
+#define GL_COMPRESSED_RGBA_ASTC_4x4x4_OES				0x93C3
+#define GL_COMPRESSED_RGBA_ASTC_5x4x4_OES				0x93C4
+#define GL_COMPRESSED_RGBA_ASTC_5x5x4_OES				0x93C5
+#define GL_COMPRESSED_RGBA_ASTC_5x5x5_OES				0x93C6
+#define GL_COMPRESSED_RGBA_ASTC_6x5x5_OES				0x93C7
+#define GL_COMPRESSED_RGBA_ASTC_6x6x5_OES				0x93C8
+#define GL_COMPRESSED_RGBA_ASTC_6x6x6_OES				0x93C9
+#endif
+
+#if !defined( GL_COMPRESSED_SRGB8_ALPHA8_ASTC_3x3x3_OES )
+#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_3x3x3_OES		0x93E0
+#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x3x3_OES		0x93E1
+#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4x3_OES		0x93E2
+#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4x4_OES		0x93E3
+#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4x4_OES		0x93E4
+#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5x4_OES		0x93E5
+#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5x5_OES		0x93E6
+#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5x5_OES		0x93E7
+#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6x5_OES		0x93E8
+#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6x6_OES		0x93E9
+#endif
+
+//
+// ATC
+//
+
+#if !defined( GL_ATC_RGB_AMD )
+#define GL_ATC_RGB_AMD									0x8C92
+#define GL_ATC_RGBA_EXPLICIT_ALPHA_AMD					0x8C93
+#define GL_ATC_RGBA_INTERPOLATED_ALPHA_AMD				0x87EE
+#endif
+
+//
+// Palletized (combined palette)
+//
+
+#if !defined( GL_PALETTE4_RGB8_OES )
+#define GL_PALETTE4_RGB8_OES							0x8B90
+#define GL_PALETTE4_RGBA8_OES							0x8B91
+#define GL_PALETTE4_R5_G6_B5_OES						0x8B92
+#define GL_PALETTE4_RGBA4_OES							0x8B93
+#define GL_PALETTE4_RGB5_A1_OES							0x8B94
+#define GL_PALETTE8_RGB8_OES							0x8B95
+#define GL_PALETTE8_RGBA8_OES							0x8B96
+#define GL_PALETTE8_R5_G6_B5_OES						0x8B97
+#define GL_PALETTE8_RGBA4_OES							0x8B98
+#define GL_PALETTE8_RGB5_A1_OES							0x8B99
+#endif
+
+//
+// Palletized (separate palette)
+//
+
+#if !defined( GL_COLOR_INDEX1_EXT )
+#define GL_COLOR_INDEX1_EXT								0x80E2	// deprecated
+#define GL_COLOR_INDEX2_EXT								0x80E3	// deprecated
+#define GL_COLOR_INDEX4_EXT								0x80E4	// deprecated
+#define GL_COLOR_INDEX8_EXT								0x80E5	// deprecated
+#define GL_COLOR_INDEX12_EXT							0x80E6	// deprecated
+#define GL_COLOR_INDEX16_EXT							0x80E7	// deprecated
+#endif
+
+//
+// Depth/stencil
+//
+
+#if !defined( GL_DEPTH_COMPONENT16 )
+#define GL_DEPTH_COMPONENT16							0x81A5	// same as GL_DEPTH_COMPONENT16_SGIX and GL_DEPTH_COMPONENT16_ARB
+#endif
+#if !defined( GL_DEPTH_COMPONENT24 )
+#define GL_DEPTH_COMPONENT24							0x81A6	// same as GL_DEPTH_COMPONENT24_SGIX and GL_DEPTH_COMPONENT24_ARB
+#endif
+#if !defined( GL_DEPTH_COMPONENT32 )
+#define GL_DEPTH_COMPONENT32							0x81A7	// same as GL_DEPTH_COMPONENT32_SGIX and GL_DEPTH_COMPONENT32_ARB and GL_DEPTH_COMPONENT32_OES
+#endif
+#if !defined( GL_DEPTH_COMPONENT32F )
+#define GL_DEPTH_COMPONENT32F							0x8CAC	// same as GL_DEPTH_COMPONENT32F_ARB
+#endif
+#if !defined( GL_DEPTH_COMPONENT32F_NV )
+#define GL_DEPTH_COMPONENT32F_NV						0x8DAB	// note that this is different from GL_DEPTH_COMPONENT32F
+#endif
+#if !defined( GL_STENCIL_INDEX1 )
+#define GL_STENCIL_INDEX1								0x8D46	// same as GL_STENCIL_INDEX1_EXT
+#endif
+#if !defined( GL_STENCIL_INDEX4 )
+#define GL_STENCIL_INDEX4								0x8D47	// same as GL_STENCIL_INDEX4_EXT
+#endif
+#if !defined( GL_STENCIL_INDEX8 )
+#define GL_STENCIL_INDEX8								0x8D48	// same as GL_STENCIL_INDEX8_EXT
+#endif
+#if !defined( GL_STENCIL_INDEX16 )
+#define GL_STENCIL_INDEX16								0x8D49	// same as GL_STENCIL_INDEX16_EXT
+#endif
+#if !defined( GL_DEPTH24_STENCIL8 )
+#define GL_DEPTH24_STENCIL8								0x88F0	// same as GL_DEPTH24_STENCIL8_EXT and GL_DEPTH24_STENCIL8_OES
+#endif
+#if !defined( GL_DEPTH32F_STENCIL8 )
+#define GL_DEPTH32F_STENCIL8							0x8CAD	// same as GL_DEPTH32F_STENCIL8_ARB
+#endif
+#if !defined( GL_DEPTH32F_STENCIL8_NV )
+#define GL_DEPTH32F_STENCIL8_NV							0x8DAC	// note that this is different from GL_DEPTH32F_STENCIL8
+#endif
+
+static inline GLenum glGetFormatFromInternalFormat( const GLenum internalFormat )
+{
+	switch ( internalFormat )
+	{
+		//
+		// 8 bits per component
+		//
+		case GL_R8:												return GL_RED;		// 1-component, 8-bit unsigned normalized
+		case GL_RG8:											return GL_RG;		// 2-component, 8-bit unsigned normalized
+		case GL_RGB8:											return GL_RGB;		// 3-component, 8-bit unsigned normalized
+		case GL_RGBA8:											return GL_RGBA;		// 4-component, 8-bit unsigned normalized
+
+		case GL_R8_SNORM:										return GL_RED;		// 1-component, 8-bit signed normalized
+		case GL_RG8_SNORM:										return GL_RG;		// 2-component, 8-bit signed normalized
+		case GL_RGB8_SNORM:										return GL_RGB;		// 3-component, 8-bit signed normalized
+		case GL_RGBA8_SNORM:									return GL_RGBA;		// 4-component, 8-bit signed normalized
+
+		case GL_R8UI:											return GL_RED;		// 1-component, 8-bit unsigned integer
+		case GL_RG8UI:											return GL_RG;		// 2-component, 8-bit unsigned integer
+		case GL_RGB8UI:											return GL_RGB;		// 3-component, 8-bit unsigned integer
+		case GL_RGBA8UI:										return GL_RGBA;		// 4-component, 8-bit unsigned integer
+
+		case GL_R8I:											return GL_RED;		// 1-component, 8-bit signed integer
+		case GL_RG8I:											return GL_RG;		// 2-component, 8-bit signed integer
+		case GL_RGB8I:											return GL_RGB;		// 3-component, 8-bit signed integer
+		case GL_RGBA8I:											return GL_RGBA;		// 4-component, 8-bit signed integer
+
+		case GL_SR8:											return GL_RED;		// 1-component, 8-bit sRGB
+		case GL_SRG8:											return GL_RG;		// 2-component, 8-bit sRGB
+		case GL_SRGB8:											return GL_RGB;		// 3-component, 8-bit sRGB
+		case GL_SRGB8_ALPHA8:									return GL_RGBA;		// 4-component, 8-bit sRGB
+
+		//
+		// 16 bits per component
+		//
+		case GL_R16:											return GL_RED;		// 1-component, 16-bit unsigned normalized
+		case GL_RG16:											return GL_RG;		// 2-component, 16-bit unsigned normalized
+		case GL_RGB16:											return GL_RGB;		// 3-component, 16-bit unsigned normalized
+		case GL_RGBA16:											return GL_RGBA;		// 4-component, 16-bit unsigned normalized
+
+		case GL_R16_SNORM:										return GL_RED;		// 1-component, 16-bit signed normalized
+		case GL_RG16_SNORM:										return GL_RG;		// 2-component, 16-bit signed normalized
+		case GL_RGB16_SNORM:									return GL_RGB;		// 3-component, 16-bit signed normalized
+		case GL_RGBA16_SNORM:									return GL_RGBA;		// 4-component, 16-bit signed normalized
+
+		case GL_R16UI:											return GL_RED;		// 1-component, 16-bit unsigned integer
+		case GL_RG16UI:											return GL_RG;		// 2-component, 16-bit unsigned integer
+		case GL_RGB16UI:										return GL_RGB;		// 3-component, 16-bit unsigned integer
+		case GL_RGBA16UI:										return GL_RGBA;		// 4-component, 16-bit unsigned integer
+
+		case GL_R16I:											return GL_RED;		// 1-component, 16-bit signed integer
+		case GL_RG16I:											return GL_RG;		// 2-component, 16-bit signed integer
+		case GL_RGB16I:											return GL_RGB;		// 3-component, 16-bit signed integer
+		case GL_RGBA16I:										return GL_RGBA;		// 4-component, 16-bit signed integer
+
+		case GL_R16F:											return GL_RED;		// 1-component, 16-bit floating-point
+		case GL_RG16F:											return GL_RG;		// 2-component, 16-bit floating-point
+		case GL_RGB16F:											return GL_RGB;		// 3-component, 16-bit floating-point
+		case GL_RGBA16F:										return GL_RGBA;		// 4-component, 16-bit floating-point
+
+		//
+		// 32 bits per component
+		//
+		case GL_R32UI:											return GL_RED;		// 1-component, 32-bit unsigned integer
+		case GL_RG32UI:											return GL_RG;		// 2-component, 32-bit unsigned integer
+		case GL_RGB32UI:										return GL_RGB;		// 3-component, 32-bit unsigned integer
+		case GL_RGBA32UI:										return GL_RGBA;		// 4-component, 32-bit unsigned integer
+
+		case GL_R32I:											return GL_RED;		// 1-component, 32-bit signed integer
+		case GL_RG32I:											return GL_RG;		// 2-component, 32-bit signed integer
+		case GL_RGB32I:											return GL_RGB;		// 3-component, 32-bit signed integer
+		case GL_RGBA32I:										return GL_RGBA;		// 4-component, 32-bit signed integer
+
+		case GL_R32F:											return GL_RED;		// 1-component, 32-bit floating-point
+		case GL_RG32F:											return GL_RG;		// 2-component, 32-bit floating-point
+		case GL_RGB32F:											return GL_RGB;		// 3-component, 32-bit floating-point
+		case GL_RGBA32F:										return GL_RGBA;		// 4-component, 32-bit floating-point
+
+		//
+		// Packed
+		//
+		case GL_R3_G3_B2:										return GL_RGB;		// 3-component 3:3:2,       unsigned normalized
+		case GL_RGB4:											return GL_RGB;		// 3-component 4:4:4,       unsigned normalized
+		case GL_RGB5:											return GL_RGB;		// 3-component 5:5:5,       unsigned normalized
+		case GL_RGB565:											return GL_RGB;		// 3-component 5:6:5,       unsigned normalized
+		case GL_RGB10:											return GL_RGB;		// 3-component 10:10:10,    unsigned normalized
+		case GL_RGB12:											return GL_RGB;		// 3-component 12:12:12,    unsigned normalized
+		case GL_RGBA2:											return GL_RGBA;		// 4-component 2:2:2:2,     unsigned normalized
+		case GL_RGBA4:											return GL_RGBA;		// 4-component 4:4:4:4,     unsigned normalized
+		case GL_RGBA12:											return GL_RGBA;		// 4-component 12:12:12:12, unsigned normalized
+		case GL_RGB5_A1:										return GL_RGBA;		// 4-component 5:5:5:1,     unsigned normalized
+		case GL_RGB10_A2:										return GL_RGBA;		// 4-component 10:10:10:2,  unsigned normalized
+		case GL_RGB10_A2UI:										return GL_RGBA;		// 4-component 10:10:10:2,  unsigned integer
+		case GL_R11F_G11F_B10F:									return GL_RGB;		// 3-component 11:11:10,    floating-point
+		case GL_RGB9_E5:										return GL_RGB;		// 3-component/exp 9:9:9/5, floating-point
+
+		//
+		// S3TC/DXT/BC
+		//
+
+		case GL_COMPRESSED_RGB_S3TC_DXT1_EXT:					return GL_RGB;		// line through 3D space, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:					return GL_RGBA;		// line through 3D space plus 1-bit alpha, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:					return GL_RGBA;		// line through 3D space plus line through 1D space, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:					return GL_RGBA;		// line through 3D space plus 4-bit alpha, 4x4 blocks, unsigned normalized
+
+		case GL_COMPRESSED_SRGB_S3TC_DXT1_EXT:					return GL_RGB;		// line through 3D space, 4x4 blocks, sRGB
+		case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT:			return GL_RGBA;		// line through 3D space plus 1-bit alpha, 4x4 blocks, sRGB
+		case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT:			return GL_RGBA;		// line through 3D space plus line through 1D space, 4x4 blocks, sRGB
+		case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:			return GL_RGBA;		// line through 3D space plus 4-bit alpha, 4x4 blocks, sRGB
+
+		case GL_COMPRESSED_LUMINANCE_LATC1_EXT:					return GL_RED;		// line through 1D space, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT:			return GL_RG;		// two lines through 1D space, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_SIGNED_LUMINANCE_LATC1_EXT:			return GL_RED;		// line through 1D space, 4x4 blocks, signed normalized
+		case GL_COMPRESSED_SIGNED_LUMINANCE_ALPHA_LATC2_EXT:	return GL_RG;		// two lines through 1D space, 4x4 blocks, signed normalized
+
+		case GL_COMPRESSED_RED_RGTC1:							return GL_RED;		// line through 1D space, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_RG_RGTC2:							return GL_RG;		// two lines through 1D space, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_SIGNED_RED_RGTC1:					return GL_RED;		// line through 1D space, 4x4 blocks, signed normalized
+		case GL_COMPRESSED_SIGNED_RG_RGTC2:						return GL_RG;		// two lines through 1D space, 4x4 blocks, signed normalized
+
+		case GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT:				return GL_RGB;		// 3-component, 4x4 blocks, unsigned floating-point
+		case GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT:				return GL_RGB;		// 3-component, 4x4 blocks, signed floating-point
+		case GL_COMPRESSED_RGBA_BPTC_UNORM:						return GL_RGBA;		// 4-component, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM:				return GL_RGBA;		// 4-component, 4x4 blocks, sRGB
+
+		//
+		// ETC
+		//
+		case GL_ETC1_RGB8_OES:									return GL_RGB;		// 3-component ETC1, 4x4 blocks, unsigned normalized
+
+		case GL_COMPRESSED_RGB8_ETC2:							return GL_RGB;		// 3-component ETC2, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:		return GL_RGBA;		// 4-component ETC2 with 1-bit alpha, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA8_ETC2_EAC:						return GL_RGBA;		// 4-component ETC2, 4x4 blocks, unsigned normalized
+
+		case GL_COMPRESSED_SRGB8_ETC2:							return GL_RGB;		// 3-component ETC2, 4x4 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2:		return GL_RGBA;		// 4-component ETC2 with 1-bit alpha, 4x4 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC:				return GL_RGBA;		// 4-component ETC2, 4x4 blocks, sRGB
+
+		case GL_COMPRESSED_R11_EAC:								return GL_RED;		// 1-component ETC, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_RG11_EAC:							return GL_RG;		// 2-component ETC, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_SIGNED_R11_EAC:						return GL_RED;		// 1-component ETC, 4x4 blocks, signed normalized
+		case GL_COMPRESSED_SIGNED_RG11_EAC:						return GL_RG;		// 2-component ETC, 4x4 blocks, signed normalized
+
+		//
+		// PVRTC
+		//
+		case GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG:				return GL_RGB;		// 3-component PVRTC, 16x8 blocks, unsigned normalized
+		case GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG:				return GL_RGB;		// 3-component PVRTC,  8x8 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG:				return GL_RGBA;		// 4-component PVRTC, 16x8 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG:				return GL_RGBA;		// 4-component PVRTC,  8x8 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_PVRTC_2BPPV2_IMG:				return GL_RGBA;		// 4-component PVRTC,  8x4 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_PVRTC_4BPPV2_IMG:				return GL_RGBA;		// 4-component PVRTC,  4x4 blocks, unsigned normalized
+
+		case GL_COMPRESSED_SRGB_PVRTC_2BPPV1_EXT:				return GL_RGB;		// 3-component PVRTC, 16x8 blocks, sRGB
+		case GL_COMPRESSED_SRGB_PVRTC_4BPPV1_EXT:				return GL_RGB;		// 3-component PVRTC,  8x8 blocks, sRGB
+		case GL_COMPRESSED_SRGB_ALPHA_PVRTC_2BPPV1_EXT:			return GL_RGBA;		// 4-component PVRTC, 16x8 blocks, sRGB
+		case GL_COMPRESSED_SRGB_ALPHA_PVRTC_4BPPV1_EXT:			return GL_RGBA;		// 4-component PVRTC,  8x8 blocks, sRGB
+		case GL_COMPRESSED_SRGB_ALPHA_PVRTC_2BPPV2_IMG:			return GL_RGBA;		// 4-component PVRTC,  8x4 blocks, sRGB
+		case GL_COMPRESSED_SRGB_ALPHA_PVRTC_4BPPV2_IMG:			return GL_RGBA;		// 4-component PVRTC,  4x4 blocks, sRGB
+
+		//
+		// ASTC
+		//
+		case GL_COMPRESSED_RGBA_ASTC_4x4_KHR:					return GL_RGBA;		// 4-component ASTC, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_5x4_KHR:					return GL_RGBA;		// 4-component ASTC, 5x4 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_5x5_KHR:					return GL_RGBA;		// 4-component ASTC, 5x5 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_6x5_KHR:					return GL_RGBA;		// 4-component ASTC, 6x5 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_6x6_KHR:					return GL_RGBA;		// 4-component ASTC, 6x6 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_8x5_KHR:					return GL_RGBA;		// 4-component ASTC, 8x5 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_8x6_KHR:					return GL_RGBA;		// 4-component ASTC, 8x6 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_8x8_KHR:					return GL_RGBA;		// 4-component ASTC, 8x8 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_10x5_KHR:					return GL_RGBA;		// 4-component ASTC, 10x5 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_10x6_KHR:					return GL_RGBA;		// 4-component ASTC, 10x6 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_10x8_KHR:					return GL_RGBA;		// 4-component ASTC, 10x8 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_10x10_KHR:					return GL_RGBA;		// 4-component ASTC, 10x10 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_12x10_KHR:					return GL_RGBA;		// 4-component ASTC, 12x10 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_12x12_KHR:					return GL_RGBA;		// 4-component ASTC, 12x12 blocks, unsigned normalized
+
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR:			return GL_RGBA;		// 4-component ASTC, 4x4 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR:			return GL_RGBA;		// 4-component ASTC, 5x4 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR:			return GL_RGBA;		// 4-component ASTC, 5x5 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR:			return GL_RGBA;		// 4-component ASTC, 6x5 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR:			return GL_RGBA;		// 4-component ASTC, 6x6 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR:			return GL_RGBA;		// 4-component ASTC, 8x5 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR:			return GL_RGBA;		// 4-component ASTC, 8x6 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR:			return GL_RGBA;		// 4-component ASTC, 8x8 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR:			return GL_RGBA;		// 4-component ASTC, 10x5 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR:			return GL_RGBA;		// 4-component ASTC, 10x6 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR:			return GL_RGBA;		// 4-component ASTC, 10x8 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR:			return GL_RGBA;		// 4-component ASTC, 10x10 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR:			return GL_RGBA;		// 4-component ASTC, 12x10 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR:			return GL_RGBA;		// 4-component ASTC, 12x12 blocks, sRGB
+
+		case GL_COMPRESSED_RGBA_ASTC_3x3x3_OES:					return GL_RGBA;		// 4-component ASTC, 3x3x3 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_4x3x3_OES:					return GL_RGBA;		// 4-component ASTC, 4x3x3 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_4x4x3_OES:					return GL_RGBA;		// 4-component ASTC, 4x4x3 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_4x4x4_OES:					return GL_RGBA;		// 4-component ASTC, 4x4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_5x4x4_OES:					return GL_RGBA;		// 4-component ASTC, 5x4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_5x5x4_OES:					return GL_RGBA;		// 4-component ASTC, 5x5x4 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_5x5x5_OES:					return GL_RGBA;		// 4-component ASTC, 5x5x5 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_6x5x5_OES:					return GL_RGBA;		// 4-component ASTC, 6x5x5 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_6x6x5_OES:					return GL_RGBA;		// 4-component ASTC, 6x6x5 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_6x6x6_OES:					return GL_RGBA;		// 4-component ASTC, 6x6x6 blocks, unsigned normalized
+
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_3x3x3_OES:			return GL_RGBA;		// 4-component ASTC, 3x3x3 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x3x3_OES:			return GL_RGBA;		// 4-component ASTC, 4x3x3 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4x3_OES:			return GL_RGBA;		// 4-component ASTC, 4x4x3 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4x4_OES:			return GL_RGBA;		// 4-component ASTC, 4x4x4 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4x4_OES:			return GL_RGBA;		// 4-component ASTC, 5x4x4 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5x4_OES:			return GL_RGBA;		// 4-component ASTC, 5x5x4 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5x5_OES:			return GL_RGBA;		// 4-component ASTC, 5x5x5 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5x5_OES:			return GL_RGBA;		// 4-component ASTC, 6x5x5 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6x5_OES:			return GL_RGBA;		// 4-component ASTC, 6x6x5 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6x6_OES:			return GL_RGBA;		// 4-component ASTC, 6x6x6 blocks, sRGB
+
+		//
+		// ATC
+		//
+		case GL_ATC_RGB_AMD:									return GL_RGB;		// 3-component, 4x4 blocks, unsigned normalized
+		case GL_ATC_RGBA_EXPLICIT_ALPHA_AMD:					return GL_RGBA;		// 4-component, 4x4 blocks, unsigned normalized
+		case GL_ATC_RGBA_INTERPOLATED_ALPHA_AMD:				return GL_RGBA;		// 4-component, 4x4 blocks, unsigned normalized
+
+		//
+		// Palletized
+		//
+		case GL_PALETTE4_RGB8_OES:								return GL_RGB;		// 3-component 8:8:8,   4-bit palette, unsigned normalized
+		case GL_PALETTE4_RGBA8_OES:								return GL_RGBA;		// 4-component 8:8:8:8, 4-bit palette, unsigned normalized
+		case GL_PALETTE4_R5_G6_B5_OES:							return GL_RGB;		// 3-component 5:6:5,   4-bit palette, unsigned normalized
+		case GL_PALETTE4_RGBA4_OES:								return GL_RGBA;		// 4-component 4:4:4:4, 4-bit palette, unsigned normalized
+		case GL_PALETTE4_RGB5_A1_OES:							return GL_RGBA;		// 4-component 5:5:5:1, 4-bit palette, unsigned normalized
+		case GL_PALETTE8_RGB8_OES:								return GL_RGB;		// 3-component 8:8:8,   8-bit palette, unsigned normalized
+		case GL_PALETTE8_RGBA8_OES:								return GL_RGBA;		// 4-component 8:8:8:8, 8-bit palette, unsigned normalized
+		case GL_PALETTE8_R5_G6_B5_OES:							return GL_RGB;		// 3-component 5:6:5,   8-bit palette, unsigned normalized
+		case GL_PALETTE8_RGBA4_OES:								return GL_RGBA;		// 4-component 4:4:4:4, 8-bit palette, unsigned normalized
+		case GL_PALETTE8_RGB5_A1_OES:							return GL_RGBA;		// 4-component 5:5:5:1, 8-bit palette, unsigned normalized
+
+		//
+		// Depth/stencil
+		//
+		case GL_DEPTH_COMPONENT16:								return GL_DEPTH_COMPONENT;
+		case GL_DEPTH_COMPONENT24:								return GL_DEPTH_COMPONENT;
+		case GL_DEPTH_COMPONENT32:								return GL_DEPTH_COMPONENT;
+		case GL_DEPTH_COMPONENT32F:								return GL_DEPTH_COMPONENT;
+		case GL_DEPTH_COMPONENT32F_NV:							return GL_DEPTH_COMPONENT;
+		case GL_STENCIL_INDEX1:									return GL_STENCIL_INDEX;
+		case GL_STENCIL_INDEX4:									return GL_STENCIL_INDEX;
+		case GL_STENCIL_INDEX8:									return GL_STENCIL_INDEX;
+		case GL_STENCIL_INDEX16:								return GL_STENCIL_INDEX;
+		case GL_DEPTH24_STENCIL8:								return GL_DEPTH_STENCIL;
+		case GL_DEPTH32F_STENCIL8:								return GL_DEPTH_STENCIL;
+		case GL_DEPTH32F_STENCIL8_NV:							return GL_DEPTH_STENCIL;
+
+		default:												return GL_INVALID_VALUE;
+	}
+}
+
+static inline GLenum glGetTypeFromInternalFormat( const GLenum internalFormat )
+{
+	switch ( internalFormat )
+	{
+		//
+		// 8 bits per component
+		//
+		case GL_R8:												return GL_UNSIGNED_BYTE;				// 1-component, 8-bit unsigned normalized
+		case GL_RG8:											return GL_UNSIGNED_BYTE;				// 2-component, 8-bit unsigned normalized
+		case GL_RGB8:											return GL_UNSIGNED_BYTE;				// 3-component, 8-bit unsigned normalized
+		case GL_RGBA8:											return GL_UNSIGNED_BYTE;				// 4-component, 8-bit unsigned normalized
+
+		case GL_R8_SNORM:										return GL_BYTE;							// 1-component, 8-bit signed normalized
+		case GL_RG8_SNORM:										return GL_BYTE;							// 2-component, 8-bit signed normalized
+		case GL_RGB8_SNORM:										return GL_BYTE;							// 3-component, 8-bit signed normalized
+		case GL_RGBA8_SNORM:									return GL_BYTE;							// 4-component, 8-bit signed normalized
+
+		case GL_R8UI:											return GL_UNSIGNED_BYTE;				// 1-component, 8-bit unsigned integer
+		case GL_RG8UI:											return GL_UNSIGNED_BYTE;				// 2-component, 8-bit unsigned integer
+		case GL_RGB8UI:											return GL_UNSIGNED_BYTE;				// 3-component, 8-bit unsigned integer
+		case GL_RGBA8UI:										return GL_UNSIGNED_BYTE;				// 4-component, 8-bit unsigned integer
+
+		case GL_R8I:											return GL_BYTE;							// 1-component, 8-bit signed integer
+		case GL_RG8I:											return GL_BYTE;							// 2-component, 8-bit signed integer
+		case GL_RGB8I:											return GL_BYTE;							// 3-component, 8-bit signed integer
+		case GL_RGBA8I:											return GL_BYTE;							// 4-component, 8-bit signed integer
+
+		case GL_SR8:											return GL_UNSIGNED_BYTE;				// 1-component, 8-bit sRGB
+		case GL_SRG8:											return GL_UNSIGNED_BYTE;				// 2-component, 8-bit sRGB
+		case GL_SRGB8:											return GL_UNSIGNED_BYTE;				// 3-component, 8-bit sRGB
+		case GL_SRGB8_ALPHA8:									return GL_UNSIGNED_BYTE;				// 4-component, 8-bit sRGB
+
+		//
+		// 16 bits per component
+		//
+		case GL_R16:											return GL_UNSIGNED_SHORT;				// 1-component, 16-bit unsigned normalized
+		case GL_RG16:											return GL_UNSIGNED_SHORT;				// 2-component, 16-bit unsigned normalized
+		case GL_RGB16:											return GL_UNSIGNED_SHORT;				// 3-component, 16-bit unsigned normalized
+		case GL_RGBA16:											return GL_UNSIGNED_SHORT;				// 4-component, 16-bit unsigned normalized
+
+		case GL_R16_SNORM:										return GL_SHORT;						// 1-component, 16-bit signed normalized
+		case GL_RG16_SNORM:										return GL_SHORT;						// 2-component, 16-bit signed normalized
+		case GL_RGB16_SNORM:									return GL_SHORT;						// 3-component, 16-bit signed normalized
+		case GL_RGBA16_SNORM:									return GL_SHORT;						// 4-component, 16-bit signed normalized
+
+		case GL_R16UI:											return GL_UNSIGNED_SHORT;				// 1-component, 16-bit unsigned integer
+		case GL_RG16UI:											return GL_UNSIGNED_SHORT;				// 2-component, 16-bit unsigned integer
+		case GL_RGB16UI:										return GL_UNSIGNED_SHORT;				// 3-component, 16-bit unsigned integer
+		case GL_RGBA16UI:										return GL_UNSIGNED_SHORT;				// 4-component, 16-bit unsigned integer
+
+		case GL_R16I:											return GL_SHORT;						// 1-component, 16-bit signed integer
+		case GL_RG16I:											return GL_SHORT;						// 2-component, 16-bit signed integer
+		case GL_RGB16I:											return GL_SHORT;						// 3-component, 16-bit signed integer
+		case GL_RGBA16I:										return GL_SHORT;						// 4-component, 16-bit signed integer
+
+		case GL_R16F:											return GL_HALF_FLOAT;					// 1-component, 16-bit floating-point
+		case GL_RG16F:											return GL_HALF_FLOAT;					// 2-component, 16-bit floating-point
+		case GL_RGB16F:											return GL_HALF_FLOAT;					// 3-component, 16-bit floating-point
+		case GL_RGBA16F:										return GL_HALF_FLOAT;					// 4-component, 16-bit floating-point
+
+		//
+		// 32 bits per component
+		//
+		case GL_R32UI:											return GL_UNSIGNED_INT;					// 1-component, 32-bit unsigned integer
+		case GL_RG32UI:											return GL_UNSIGNED_INT;					// 2-component, 32-bit unsigned integer
+		case GL_RGB32UI:										return GL_UNSIGNED_INT;					// 3-component, 32-bit unsigned integer
+		case GL_RGBA32UI:										return GL_UNSIGNED_INT;					// 4-component, 32-bit unsigned integer
+
+		case GL_R32I:											return GL_INT;							// 1-component, 32-bit signed integer
+		case GL_RG32I:											return GL_INT;							// 2-component, 32-bit signed integer
+		case GL_RGB32I:											return GL_INT;							// 3-component, 32-bit signed integer
+		case GL_RGBA32I:										return GL_INT;							// 4-component, 32-bit signed integer
+
+		case GL_R32F:											return GL_FLOAT;						// 1-component, 32-bit floating-point
+		case GL_RG32F:											return GL_FLOAT;						// 2-component, 32-bit floating-point
+		case GL_RGB32F:											return GL_FLOAT;						// 3-component, 32-bit floating-point
+		case GL_RGBA32F:										return GL_FLOAT;						// 4-component, 32-bit floating-point
+
+		//
+		// Packed
+		//
+		case GL_R3_G3_B2:										return GL_UNSIGNED_BYTE_2_3_3_REV;		// 3-component 3:3:2,       unsigned normalized
+		case GL_RGB4:											return GL_UNSIGNED_SHORT_4_4_4_4;		// 3-component 4:4:4,       unsigned normalized
+		case GL_RGB5:											return GL_UNSIGNED_SHORT_5_5_5_1;		// 3-component 5:5:5,       unsigned normalized
+		case GL_RGB565:											return GL_UNSIGNED_SHORT_5_6_5;			// 3-component 5:6:5,       unsigned normalized
+		case GL_RGB10:											return GL_UNSIGNED_INT_10_10_10_2;		// 3-component 10:10:10,    unsigned normalized
+		case GL_RGB12:											return GL_UNSIGNED_SHORT;				// 3-component 12:12:12,    unsigned normalized
+		case GL_RGBA2:											return GL_UNSIGNED_BYTE;				// 4-component 2:2:2:2,     unsigned normalized
+		case GL_RGBA4:											return GL_UNSIGNED_SHORT_4_4_4_4;		// 4-component 4:4:4:4,     unsigned normalized
+		case GL_RGBA12:											return GL_UNSIGNED_SHORT;				// 4-component 12:12:12:12, unsigned normalized
+		case GL_RGB5_A1:										return GL_UNSIGNED_SHORT_5_5_5_1;		// 4-component 5:5:5:1,     unsigned normalized
+		case GL_RGB10_A2:										return GL_UNSIGNED_INT_2_10_10_10_REV;	// 4-component 10:10:10:2,  unsigned normalized
+		case GL_RGB10_A2UI:										return GL_UNSIGNED_INT_2_10_10_10_REV;	// 4-component 10:10:10:2,  unsigned integer
+		case GL_R11F_G11F_B10F:									return GL_UNSIGNED_INT_10F_11F_11F_REV;	// 3-component 11:11:10,    floating-point
+		case GL_RGB9_E5:										return GL_UNSIGNED_INT_5_9_9_9_REV;		// 3-component/exp 9:9:9/5, floating-point
+
+		//
+		// S3TC/DXT/BC
+		//
+
+		case GL_COMPRESSED_RGB_S3TC_DXT1_EXT:					return GL_UNSIGNED_BYTE;				// line through 3D space, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:					return GL_UNSIGNED_BYTE;				// line through 3D space plus 1-bit alpha, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:					return GL_UNSIGNED_BYTE;				// line through 3D space plus line through 1D space, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:					return GL_UNSIGNED_BYTE;				// line through 3D space plus 4-bit alpha, 4x4 blocks, unsigned normalized
+
+		case GL_COMPRESSED_SRGB_S3TC_DXT1_EXT:					return GL_UNSIGNED_BYTE;				// line through 3D space, 4x4 blocks, sRGB
+		case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT:			return GL_UNSIGNED_BYTE;				// line through 3D space plus 1-bit alpha, 4x4 blocks, sRGB
+		case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT:			return GL_UNSIGNED_BYTE;				// line through 3D space plus line through 1D space, 4x4 blocks, sRGB
+		case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:			return GL_UNSIGNED_BYTE;				// line through 3D space plus 4-bit alpha, 4x4 blocks, sRGB
+
+		case GL_COMPRESSED_LUMINANCE_LATC1_EXT:					return GL_UNSIGNED_BYTE;				// line through 1D space, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT:			return GL_UNSIGNED_BYTE;				// two lines through 1D space, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_SIGNED_LUMINANCE_LATC1_EXT:			return GL_UNSIGNED_BYTE;				// line through 1D space, 4x4 blocks, signed normalized
+		case GL_COMPRESSED_SIGNED_LUMINANCE_ALPHA_LATC2_EXT:	return GL_UNSIGNED_BYTE;				// two lines through 1D space, 4x4 blocks, signed normalized
+
+		case GL_COMPRESSED_RED_RGTC1:							return GL_UNSIGNED_BYTE;				// line through 1D space, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_RG_RGTC2:							return GL_UNSIGNED_BYTE;				// two lines through 1D space, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_SIGNED_RED_RGTC1:					return GL_UNSIGNED_BYTE;				// line through 1D space, 4x4 blocks, signed normalized
+		case GL_COMPRESSED_SIGNED_RG_RGTC2:						return GL_UNSIGNED_BYTE;				// two lines through 1D space, 4x4 blocks, signed normalized
+
+		case GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT:				return GL_FLOAT;						// 3-component, 4x4 blocks, unsigned floating-point
+		case GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT:				return GL_FLOAT;						// 3-component, 4x4 blocks, signed floating-point
+		case GL_COMPRESSED_RGBA_BPTC_UNORM:						return GL_UNSIGNED_BYTE;				// 4-component, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM:				return GL_UNSIGNED_BYTE;				// 4-component, 4x4 blocks, sRGB
+
+		//
+		// ETC
+		//
+		case GL_ETC1_RGB8_OES:									return GL_UNSIGNED_BYTE;				// 3-component ETC1, 4x4 blocks, unsigned normalized" ),
+
+		case GL_COMPRESSED_RGB8_ETC2:							return GL_UNSIGNED_BYTE;				// 3-component ETC2, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:		return GL_UNSIGNED_BYTE;				// 4-component ETC2 with 1-bit alpha, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA8_ETC2_EAC:						return GL_UNSIGNED_BYTE;				// 4-component ETC2, 4x4 blocks, unsigned normalized
+
+		case GL_COMPRESSED_SRGB8_ETC2:							return GL_UNSIGNED_BYTE;				// 3-component ETC2, 4x4 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2:		return GL_UNSIGNED_BYTE;				// 4-component ETC2 with 1-bit alpha, 4x4 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC:				return GL_UNSIGNED_BYTE;				// 4-component ETC2, 4x4 blocks, sRGB
+
+		case GL_COMPRESSED_R11_EAC:								return GL_UNSIGNED_BYTE;				// 1-component ETC, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_RG11_EAC:							return GL_UNSIGNED_BYTE;				// 2-component ETC, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_SIGNED_R11_EAC:						return GL_UNSIGNED_BYTE;				// 1-component ETC, 4x4 blocks, signed normalized
+		case GL_COMPRESSED_SIGNED_RG11_EAC:						return GL_UNSIGNED_BYTE;				// 2-component ETC, 4x4 blocks, signed normalized
+
+		//
+		// PVRTC
+		//
+		case GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG:				return GL_UNSIGNED_BYTE;				// 3-component PVRTC, 16x8 blocks, unsigned normalized
+		case GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG:				return GL_UNSIGNED_BYTE;				// 3-component PVRTC,  8x8 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG:				return GL_UNSIGNED_BYTE;				// 4-component PVRTC, 16x8 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG:				return GL_UNSIGNED_BYTE;				// 4-component PVRTC,  8x8 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_PVRTC_2BPPV2_IMG:				return GL_UNSIGNED_BYTE;				// 4-component PVRTC,  8x4 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_PVRTC_4BPPV2_IMG:				return GL_UNSIGNED_BYTE;				// 4-component PVRTC,  4x4 blocks, unsigned normalized
+
+		case GL_COMPRESSED_SRGB_PVRTC_2BPPV1_EXT:				return GL_UNSIGNED_BYTE;				// 3-component PVRTC, 16x8 blocks, sRGB
+		case GL_COMPRESSED_SRGB_PVRTC_4BPPV1_EXT:				return GL_UNSIGNED_BYTE;				// 3-component PVRTC,  8x8 blocks, sRGB
+		case GL_COMPRESSED_SRGB_ALPHA_PVRTC_2BPPV1_EXT:			return GL_UNSIGNED_BYTE;				// 4-component PVRTC, 16x8 blocks, sRGB
+		case GL_COMPRESSED_SRGB_ALPHA_PVRTC_4BPPV1_EXT:			return GL_UNSIGNED_BYTE;				// 4-component PVRTC,  8x8 blocks, sRGB
+		case GL_COMPRESSED_SRGB_ALPHA_PVRTC_2BPPV2_IMG:			return GL_UNSIGNED_BYTE;				// 4-component PVRTC,  8x4 blocks, sRGB
+		case GL_COMPRESSED_SRGB_ALPHA_PVRTC_4BPPV2_IMG:			return GL_UNSIGNED_BYTE;				// 4-component PVRTC,  4x4 blocks, sRGB
+
+		//
+		// ASTC
+		//
+		case GL_COMPRESSED_RGBA_ASTC_4x4_KHR:					return GL_UNSIGNED_BYTE;				// 4-component ASTC, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_5x4_KHR:					return GL_UNSIGNED_BYTE;				// 4-component ASTC, 5x4 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_5x5_KHR:					return GL_UNSIGNED_BYTE;				// 4-component ASTC, 5x5 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_6x5_KHR:					return GL_UNSIGNED_BYTE;				// 4-component ASTC, 6x5 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_6x6_KHR:					return GL_UNSIGNED_BYTE;				// 4-component ASTC, 6x6 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_8x5_KHR:					return GL_UNSIGNED_BYTE;				// 4-component ASTC, 8x5 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_8x6_KHR:					return GL_UNSIGNED_BYTE;				// 4-component ASTC, 8x6 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_8x8_KHR:					return GL_UNSIGNED_BYTE;				// 4-component ASTC, 8x8 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_10x5_KHR:					return GL_UNSIGNED_BYTE;				// 4-component ASTC, 10x5 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_10x6_KHR:					return GL_UNSIGNED_BYTE;				// 4-component ASTC, 10x6 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_10x8_KHR:					return GL_UNSIGNED_BYTE;				// 4-component ASTC, 10x8 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_10x10_KHR:					return GL_UNSIGNED_BYTE;				// 4-component ASTC, 10x10 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_12x10_KHR:					return GL_UNSIGNED_BYTE;				// 4-component ASTC, 12x10 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_12x12_KHR:					return GL_UNSIGNED_BYTE;				// 4-component ASTC, 12x12 blocks, unsigned normalized
+
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR:			return GL_UNSIGNED_BYTE;				// 4-component ASTC, 4x4 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR:			return GL_UNSIGNED_BYTE;				// 4-component ASTC, 5x4 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR:			return GL_UNSIGNED_BYTE;				// 4-component ASTC, 5x5 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR:			return GL_UNSIGNED_BYTE;				// 4-component ASTC, 6x5 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR:			return GL_UNSIGNED_BYTE;				// 4-component ASTC, 6x6 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR:			return GL_UNSIGNED_BYTE;				// 4-component ASTC, 8x5 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR:			return GL_UNSIGNED_BYTE;				// 4-component ASTC, 8x6 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR:			return GL_UNSIGNED_BYTE;				// 4-component ASTC, 8x8 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR:			return GL_UNSIGNED_BYTE;				// 4-component ASTC, 10x5 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR:			return GL_UNSIGNED_BYTE;				// 4-component ASTC, 10x6 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR:			return GL_UNSIGNED_BYTE;				// 4-component ASTC, 10x8 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR:			return GL_UNSIGNED_BYTE;				// 4-component ASTC, 10x10 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR:			return GL_UNSIGNED_BYTE;				// 4-component ASTC, 12x10 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR:			return GL_UNSIGNED_BYTE;				// 4-component ASTC, 12x12 blocks, sRGB
+
+		case GL_COMPRESSED_RGBA_ASTC_3x3x3_OES:					return GL_UNSIGNED_BYTE;				// 4-component ASTC, 3x3x3 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_4x3x3_OES:					return GL_UNSIGNED_BYTE;				// 4-component ASTC, 4x3x3 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_4x4x3_OES:					return GL_UNSIGNED_BYTE;				// 4-component ASTC, 4x4x3 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_4x4x4_OES:					return GL_UNSIGNED_BYTE;				// 4-component ASTC, 4x4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_5x4x4_OES:					return GL_UNSIGNED_BYTE;				// 4-component ASTC, 5x4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_5x5x4_OES:					return GL_UNSIGNED_BYTE;				// 4-component ASTC, 5x5x4 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_5x5x5_OES:					return GL_UNSIGNED_BYTE;				// 4-component ASTC, 5x5x5 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_6x5x5_OES:					return GL_UNSIGNED_BYTE;				// 4-component ASTC, 6x5x5 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_6x6x5_OES:					return GL_UNSIGNED_BYTE;				// 4-component ASTC, 6x6x5 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_6x6x6_OES:					return GL_UNSIGNED_BYTE;				// 4-component ASTC, 6x6x6 blocks, unsigned normalized
+
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_3x3x3_OES:			return GL_UNSIGNED_BYTE;				// 4-component ASTC, 3x3x3 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x3x3_OES:			return GL_UNSIGNED_BYTE;				// 4-component ASTC, 4x3x3 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4x3_OES:			return GL_UNSIGNED_BYTE;				// 4-component ASTC, 4x4x3 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4x4_OES:			return GL_UNSIGNED_BYTE;				// 4-component ASTC, 4x4x4 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4x4_OES:			return GL_UNSIGNED_BYTE;				// 4-component ASTC, 5x4x4 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5x4_OES:			return GL_UNSIGNED_BYTE;				// 4-component ASTC, 5x5x4 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5x5_OES:			return GL_UNSIGNED_BYTE;				// 4-component ASTC, 5x5x5 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5x5_OES:			return GL_UNSIGNED_BYTE;				// 4-component ASTC, 6x5x5 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6x5_OES:			return GL_UNSIGNED_BYTE;				// 4-component ASTC, 6x6x5 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6x6_OES:			return GL_UNSIGNED_BYTE;				// 4-component ASTC, 6x6x6 blocks, sRGB
+
+		//
+		// ATC
+		//
+		case GL_ATC_RGB_AMD:									return GL_UNSIGNED_BYTE;				// 3-component, 4x4 blocks, unsigned normalized
+		case GL_ATC_RGBA_EXPLICIT_ALPHA_AMD:					return GL_UNSIGNED_BYTE;				// 4-component, 4x4 blocks, unsigned normalized
+		case GL_ATC_RGBA_INTERPOLATED_ALPHA_AMD:				return GL_UNSIGNED_BYTE;				// 4-component, 4x4 blocks, unsigned normalized
+
+		//
+		// Palletized
+		//
+		case GL_PALETTE4_RGB8_OES:								return GL_UNSIGNED_BYTE;				// 3-component 8:8:8,   4-bit palette, unsigned normalized
+		case GL_PALETTE4_RGBA8_OES:								return GL_UNSIGNED_BYTE;				// 4-component 8:8:8:8, 4-bit palette, unsigned normalized
+		case GL_PALETTE4_R5_G6_B5_OES:							return GL_UNSIGNED_SHORT_5_6_5;			// 3-component 5:6:5,   4-bit palette, unsigned normalized
+		case GL_PALETTE4_RGBA4_OES:								return GL_UNSIGNED_SHORT_4_4_4_4;		// 4-component 4:4:4:4, 4-bit palette, unsigned normalized
+		case GL_PALETTE4_RGB5_A1_OES:							return GL_UNSIGNED_SHORT_5_5_5_1;		// 4-component 5:5:5:1, 4-bit palette, unsigned normalized
+		case GL_PALETTE8_RGB8_OES:								return GL_UNSIGNED_BYTE;				// 3-component 8:8:8,   8-bit palette, unsigned normalized
+		case GL_PALETTE8_RGBA8_OES:								return GL_UNSIGNED_BYTE;				// 4-component 8:8:8:8, 8-bit palette, unsigned normalized
+		case GL_PALETTE8_R5_G6_B5_OES:							return GL_UNSIGNED_SHORT_5_6_5;			// 3-component 5:6:5,   8-bit palette, unsigned normalized
+		case GL_PALETTE8_RGBA4_OES:								return GL_UNSIGNED_SHORT_4_4_4_4;		// 4-component 4:4:4:4, 8-bit palette, unsigned normalized
+		case GL_PALETTE8_RGB5_A1_OES:							return GL_UNSIGNED_SHORT_5_5_5_1;		// 4-component 5:5:5:1, 8-bit palette, unsigned normalized
+
+		//
+		// Depth/stencil
+		//
+		case GL_DEPTH_COMPONENT16:								return GL_UNSIGNED_SHORT;
+		case GL_DEPTH_COMPONENT24:								return GL_UNSIGNED_INT_24_8;
+		case GL_DEPTH_COMPONENT32:								return GL_UNSIGNED_INT;
+		case GL_DEPTH_COMPONENT32F:								return GL_FLOAT;
+		case GL_DEPTH_COMPONENT32F_NV:							return GL_FLOAT;
+		case GL_STENCIL_INDEX1:									return GL_UNSIGNED_BYTE;
+		case GL_STENCIL_INDEX4:									return GL_UNSIGNED_BYTE;
+		case GL_STENCIL_INDEX8:									return GL_UNSIGNED_BYTE;
+		case GL_STENCIL_INDEX16:								return GL_UNSIGNED_SHORT;
+		case GL_DEPTH24_STENCIL8:								return GL_UNSIGNED_INT_24_8;
+		case GL_DEPTH32F_STENCIL8:								return GL_FLOAT_32_UNSIGNED_INT_24_8_REV;
+		case GL_DEPTH32F_STENCIL8_NV:							return GL_FLOAT_32_UNSIGNED_INT_24_8_REV;
+
+		default:												return GL_INVALID_VALUE;
+	}
+}
+
+static inline unsigned int glGetTypeSizeFromType(GLenum type)
+{
+    switch (type) {
+        case GL_BYTE:
+        case GL_UNSIGNED_BYTE:
+        case GL_UNSIGNED_BYTE_3_3_2:
+        case GL_UNSIGNED_BYTE_2_3_3_REV:
+            return 1;
+            
+        case GL_SHORT:
+        case GL_UNSIGNED_SHORT:
+        case GL_UNSIGNED_SHORT_5_6_5:
+        case GL_UNSIGNED_SHORT_4_4_4_4:
+        case GL_UNSIGNED_SHORT_5_5_5_1:
+        case GL_UNSIGNED_SHORT_5_6_5_REV:
+        case GL_UNSIGNED_SHORT_4_4_4_4_REV:
+        case GL_UNSIGNED_SHORT_1_5_5_5_REV:
+        case GL_HALF_FLOAT:
+            return 2;
+            
+        case GL_INT:
+        case GL_UNSIGNED_INT:
+        case GL_UNSIGNED_INT_8_8_8_8:
+        case GL_UNSIGNED_INT_8_8_8_8_REV:
+        case GL_UNSIGNED_INT_10_10_10_2:
+        case GL_UNSIGNED_INT_2_10_10_10_REV:
+        case GL_UNSIGNED_INT_24_8:
+        case GL_UNSIGNED_INT_10F_11F_11F_REV:
+        case GL_UNSIGNED_INT_5_9_9_9_REV:
+        case GL_FLOAT:
+        case GL_FLOAT_32_UNSIGNED_INT_24_8_REV:
+            return 4;
+
+        default:
+            return GL_INVALID_VALUE;
+    }
+}
+
+typedef enum GlFormatSizeFlagBits {
+	GL_FORMAT_SIZE_PACKED_BIT				= 0x00000001,
+	GL_FORMAT_SIZE_COMPRESSED_BIT			= 0x00000002,
+	GL_FORMAT_SIZE_PALETTIZED_BIT			= 0x00000004,
+	GL_FORMAT_SIZE_DEPTH_BIT				= 0x00000008,
+	GL_FORMAT_SIZE_STENCIL_BIT				= 0x00000010,
+} GlFormatSizeFlagBits;
+
+typedef unsigned int GlFormatSizeFlags;
+
+typedef struct GlFormatSize {
+	GlFormatSizeFlags	flags;
+	unsigned int		paletteSizeInBits;
+	unsigned int		blockSizeInBits;
+	unsigned int		blockWidth;			// in texels
+	unsigned int		blockHeight;		// in texels
+	unsigned int		blockDepth;			// in texels
+} GlFormatSize;
+
+static inline void glGetFormatSize( const GLenum internalFormat, GlFormatSize * pFormatSize )
+{
+	switch ( internalFormat )
+	{
+		//
+		// 8 bits per component
+		//
+		case GL_R8:												// 1-component, 8-bit unsigned normalized
+		case GL_R8_SNORM:										// 1-component, 8-bit signed normalized
+		case GL_R8UI:											// 1-component, 8-bit unsigned integer
+		case GL_R8I:											// 1-component, 8-bit signed integer
+		case GL_SR8:											// 1-component, 8-bit sRGB
+			pFormatSize->flags = 0;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 1 * 8;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_RG8:											// 2-component, 8-bit unsigned normalized
+		case GL_RG8_SNORM:										// 2-component, 8-bit signed normalized
+		case GL_RG8UI:											// 2-component, 8-bit unsigned integer
+		case GL_RG8I:											// 2-component, 8-bit signed integer
+		case GL_SRG8:											// 2-component, 8-bit sRGB
+			pFormatSize->flags = 0;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 2 * 8;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_RGB8:											// 3-component, 8-bit unsigned normalized
+		case GL_RGB8_SNORM:										// 3-component, 8-bit signed normalized
+		case GL_RGB8UI:											// 3-component, 8-bit unsigned integer
+		case GL_RGB8I:											// 3-component, 8-bit signed integer
+		case GL_SRGB8:											// 3-component, 8-bit sRGB
+			pFormatSize->flags = 0;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 3 * 8;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_RGBA8:											// 4-component, 8-bit unsigned normalized
+		case GL_RGBA8_SNORM:									// 4-component, 8-bit signed normalized
+		case GL_RGBA8UI:										// 4-component, 8-bit unsigned integer
+		case GL_RGBA8I:											// 4-component, 8-bit signed integer
+		case GL_SRGB8_ALPHA8:									// 4-component, 8-bit sRGB
+			pFormatSize->flags = 0;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 4 * 8;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+
+		//
+		// 16 bits per component
+		//
+		case GL_R16:											// 1-component, 16-bit unsigned normalized
+		case GL_R16_SNORM:										// 1-component, 16-bit signed normalized
+		case GL_R16UI:											// 1-component, 16-bit unsigned integer
+		case GL_R16I:											// 1-component, 16-bit signed integer
+		case GL_R16F:											// 1-component, 16-bit floating-point
+			pFormatSize->flags = 0;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 2 * 8;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_RG16:											// 2-component, 16-bit unsigned normalized
+		case GL_RG16_SNORM:										// 2-component, 16-bit signed normalized
+		case GL_RG16UI:											// 2-component, 16-bit unsigned integer
+		case GL_RG16I:											// 2-component, 16-bit signed integer
+		case GL_RG16F:											// 2-component, 16-bit floating-point
+			pFormatSize->flags = 0;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 4 * 8;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_RGB16:											// 3-component, 16-bit unsigned normalized
+		case GL_RGB16_SNORM:									// 3-component, 16-bit signed normalized
+		case GL_RGB16UI:										// 3-component, 16-bit unsigned integer
+		case GL_RGB16I:											// 3-component, 16-bit signed integer
+		case GL_RGB16F:											// 3-component, 16-bit floating-point
+			pFormatSize->flags = 0;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 6 * 8;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_RGBA16:											// 4-component, 16-bit unsigned normalized
+		case GL_RGBA16_SNORM:									// 4-component, 16-bit signed normalized
+		case GL_RGBA16UI:										// 4-component, 16-bit unsigned integer
+		case GL_RGBA16I:										// 4-component, 16-bit signed integer
+		case GL_RGBA16F:										// 4-component, 16-bit floating-point
+			pFormatSize->flags = 0;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 8 * 8;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+
+		//
+		// 32 bits per component
+		//
+		case GL_R32UI:											// 1-component, 32-bit unsigned integer
+		case GL_R32I:											// 1-component, 32-bit signed integer
+		case GL_R32F:											// 1-component, 32-bit floating-point
+			pFormatSize->flags = 0;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 4 * 8;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_RG32UI:											// 2-component, 32-bit unsigned integer
+		case GL_RG32I:											// 2-component, 32-bit signed integer
+		case GL_RG32F:											// 2-component, 32-bit floating-point
+			pFormatSize->flags = 0;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 8 * 8;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_RGB32UI:										// 3-component, 32-bit unsigned integer
+		case GL_RGB32I:											// 3-component, 32-bit signed integer
+		case GL_RGB32F:											// 3-component, 32-bit floating-point
+			pFormatSize->flags = 0;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 12 * 8;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_RGBA32UI:										// 4-component, 32-bit unsigned integer
+		case GL_RGBA32I:										// 4-component, 32-bit signed integer
+		case GL_RGBA32F:										// 4-component, 32-bit floating-point
+			pFormatSize->flags = 0;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 16 * 8;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+
+		//
+		// Packed
+		//
+		case GL_R3_G3_B2:										// 3-component 3:3:2, unsigned normalized
+			pFormatSize->flags = GL_FORMAT_SIZE_PACKED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 8;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_RGB4:											// 3-component 4:4:4, unsigned normalized
+			pFormatSize->flags = GL_FORMAT_SIZE_PACKED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 12;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_RGB5:											// 3-component 5:5:5, unsigned normalized
+			pFormatSize->flags = GL_FORMAT_SIZE_PACKED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 16;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_RGB565:											// 3-component 5:6:5, unsigned normalized
+			pFormatSize->flags = GL_FORMAT_SIZE_PACKED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 16;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_RGB10:											// 3-component 10:10:10, unsigned normalized
+			pFormatSize->flags = GL_FORMAT_SIZE_PACKED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 32;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_RGB12:											// 3-component 12:12:12, unsigned normalized
+			pFormatSize->flags = GL_FORMAT_SIZE_PACKED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 36;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_RGBA2:											// 4-component 2:2:2:2, unsigned normalized
+			pFormatSize->flags = GL_FORMAT_SIZE_PACKED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 8;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_RGBA4:											// 4-component 4:4:4:4, unsigned normalized
+			pFormatSize->flags = GL_FORMAT_SIZE_PACKED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 16;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_RGBA12:											// 4-component 12:12:12:12, unsigned normalized
+			pFormatSize->flags = GL_FORMAT_SIZE_PACKED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 48;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_RGB5_A1:										// 4-component 5:5:5:1, unsigned normalized
+			pFormatSize->flags = GL_FORMAT_SIZE_PACKED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 32;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_RGB10_A2:										// 4-component 10:10:10:2, unsigned normalized
+			pFormatSize->flags = GL_FORMAT_SIZE_PACKED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 32;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_RGB10_A2UI:										// 4-component 10:10:10:2, unsigned integer
+			pFormatSize->flags = GL_FORMAT_SIZE_PACKED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 32;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_R11F_G11F_B10F:									// 3-component 11:11:10, floating-point
+		case GL_RGB9_E5:										// 3-component/exp 9:9:9/5, floating-point
+			pFormatSize->flags = GL_FORMAT_SIZE_PACKED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 32;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+
+		//
+		// S3TC/DXT/BC
+		//
+		case GL_COMPRESSED_RGB_S3TC_DXT1_EXT:					// line through 3D space, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:					// line through 3D space plus 1-bit alpha, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_SRGB_S3TC_DXT1_EXT:					// line through 3D space, 4x4 blocks, sRGB
+		case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT:			// line through 3D space plus 1-bit alpha, 4x4 blocks, sRGB
+			pFormatSize->flags = GL_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 64;
+			pFormatSize->blockWidth = 4;
+			pFormatSize->blockHeight = 4;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:					// line through 3D space plus line through 1D space, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:					// line through 3D space plus 4-bit alpha, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT:			// line through 3D space plus line through 1D space, 4x4 blocks, sRGB
+		case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:			// line through 3D space plus 4-bit alpha, 4x4 blocks, sRGB
+			pFormatSize->flags = GL_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 128;
+			pFormatSize->blockWidth = 4;
+			pFormatSize->blockHeight = 4;
+			pFormatSize->blockDepth = 1;
+			break;
+
+		case GL_COMPRESSED_LUMINANCE_LATC1_EXT:					// line through 1D space, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_SIGNED_LUMINANCE_LATC1_EXT:			// line through 1D space, 4x4 blocks, signed normalized
+			pFormatSize->flags = GL_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 64;
+			pFormatSize->blockWidth = 4;
+			pFormatSize->blockHeight = 4;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT:			// two lines through 1D space, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_SIGNED_LUMINANCE_ALPHA_LATC2_EXT:	// two lines through 1D space, 4x4 blocks, signed normalized
+			pFormatSize->flags = GL_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 128;
+			pFormatSize->blockWidth = 4;
+			pFormatSize->blockHeight = 4;
+			pFormatSize->blockDepth = 1;
+			break;
+
+		case GL_COMPRESSED_RED_RGTC1:							// line through 1D space, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_SIGNED_RED_RGTC1:					// line through 1D space, 4x4 blocks, signed normalized
+			pFormatSize->flags = GL_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 64;
+			pFormatSize->blockWidth = 4;
+			pFormatSize->blockHeight = 4;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_COMPRESSED_RG_RGTC2:							// two lines through 1D space, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_SIGNED_RG_RGTC2:						// two lines through 1D space, 4x4 blocks, signed normalized
+			pFormatSize->flags = GL_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 128;
+			pFormatSize->blockWidth = 4;
+			pFormatSize->blockHeight = 4;
+			pFormatSize->blockDepth = 1;
+			break;
+
+		case GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT:				// 3-component, 4x4 blocks, unsigned floating-point
+		case GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT:				// 3-component, 4x4 blocks, signed floating-point
+		case GL_COMPRESSED_RGBA_BPTC_UNORM:						// 4-component, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM:				// 4-component, 4x4 blocks, sRGB
+			pFormatSize->flags = GL_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 128;
+			pFormatSize->blockWidth = 4;
+			pFormatSize->blockHeight = 4;
+			pFormatSize->blockDepth = 1;
+			break;
+
+		//
+		// ETC
+		//
+		case GL_ETC1_RGB8_OES:									// 3-component ETC1, 4x4 blocks, unsigned normalized" ),
+		case GL_COMPRESSED_RGB8_ETC2:							// 3-component ETC2, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_SRGB8_ETC2:							// 3-component ETC2, 4x4 blocks, sRGB
+		case GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:		// 4-component ETC2 with 1-bit alpha, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2:		// 4-component ETC2 with 1-bit alpha, 4x4 blocks, sRGB
+			pFormatSize->flags = GL_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 64;
+			pFormatSize->blockWidth = 4;
+			pFormatSize->blockHeight = 4;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_COMPRESSED_RGBA8_ETC2_EAC:						// 4-component ETC2, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC:				// 4-component ETC2, 4x4 blocks, sRGB
+			pFormatSize->flags = GL_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 128;
+			pFormatSize->blockWidth = 4;
+			pFormatSize->blockHeight = 4;
+			pFormatSize->blockDepth = 1;
+			break;
+
+		case GL_COMPRESSED_R11_EAC:								// 1-component ETC, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_SIGNED_R11_EAC:						// 1-component ETC, 4x4 blocks, signed normalized
+			pFormatSize->flags = GL_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 64;
+			pFormatSize->blockWidth = 4;
+			pFormatSize->blockHeight = 4;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_COMPRESSED_RG11_EAC:							// 2-component ETC, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_SIGNED_RG11_EAC:						// 2-component ETC, 4x4 blocks, signed normalized
+			pFormatSize->flags = GL_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 128;
+			pFormatSize->blockWidth = 4;
+			pFormatSize->blockHeight = 4;
+			pFormatSize->blockDepth = 1;
+			break;
+
+		//
+		// PVRTC
+		//
+		case GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG:				// 3-component PVRTC, 16x8 blocks, unsigned normalized
+		case GL_COMPRESSED_SRGB_PVRTC_2BPPV1_EXT:				// 3-component PVRTC, 16x8 blocks, sRGB
+		case GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG:				// 4-component PVRTC, 16x8 blocks, unsigned normalized
+		case GL_COMPRESSED_SRGB_ALPHA_PVRTC_2BPPV1_EXT:			// 4-component PVRTC, 16x8 blocks, sRGB
+			pFormatSize->flags = GL_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 64;
+			pFormatSize->blockWidth = 16;
+			pFormatSize->blockHeight = 8;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG:				// 3-component PVRTC, 8x8 blocks, unsigned normalized
+		case GL_COMPRESSED_SRGB_PVRTC_4BPPV1_EXT:				// 3-component PVRTC, 8x8 blocks, sRGB
+		case GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG:				// 4-component PVRTC, 8x8 blocks, unsigned normalized
+		case GL_COMPRESSED_SRGB_ALPHA_PVRTC_4BPPV1_EXT:			// 4-component PVRTC, 8x8 blocks, sRGB
+			pFormatSize->flags = GL_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 64;
+			pFormatSize->blockWidth = 8;
+			pFormatSize->blockHeight = 8;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_COMPRESSED_RGBA_PVRTC_2BPPV2_IMG:				// 4-component PVRTC, 8x4 blocks, unsigned normalized
+		case GL_COMPRESSED_SRGB_ALPHA_PVRTC_2BPPV2_IMG:			// 4-component PVRTC, 8x4 blocks, sRGB
+			pFormatSize->flags = GL_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 64;
+			pFormatSize->blockWidth = 8;
+			pFormatSize->blockHeight = 4;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_COMPRESSED_RGBA_PVRTC_4BPPV2_IMG:				// 4-component PVRTC, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_SRGB_ALPHA_PVRTC_4BPPV2_IMG:			// 4-component PVRTC, 4x4 blocks, sRGB
+			pFormatSize->flags = GL_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 64;
+			pFormatSize->blockWidth = 4;
+			pFormatSize->blockHeight = 4;
+			pFormatSize->blockDepth = 1;
+			break;
+
+		//
+		// ASTC
+		//
+		case GL_COMPRESSED_RGBA_ASTC_4x4_KHR:					// 4-component ASTC, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR:			// 4-component ASTC, 4x4 blocks, sRGB
+			pFormatSize->flags = GL_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 128;
+			pFormatSize->blockWidth = 4;
+			pFormatSize->blockHeight = 4;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_COMPRESSED_RGBA_ASTC_5x4_KHR:					// 4-component ASTC, 5x4 blocks, unsigned normalized
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR:			// 4-component ASTC, 5x4 blocks, sRGB
+			pFormatSize->flags = GL_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 128;
+			pFormatSize->blockWidth = 5;
+			pFormatSize->blockHeight = 4;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_COMPRESSED_RGBA_ASTC_5x5_KHR:					// 4-component ASTC, 5x5 blocks, unsigned normalized
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR:			// 4-component ASTC, 5x5 blocks, sRGB
+			pFormatSize->flags = GL_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 128;
+			pFormatSize->blockWidth = 5;
+			pFormatSize->blockHeight = 5;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_COMPRESSED_RGBA_ASTC_6x5_KHR:					// 4-component ASTC, 6x5 blocks, unsigned normalized
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR:			// 4-component ASTC, 6x5 blocks, sRGB
+			pFormatSize->flags = GL_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 128;
+			pFormatSize->blockWidth = 6;
+			pFormatSize->blockHeight = 5;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_COMPRESSED_RGBA_ASTC_6x6_KHR:					// 4-component ASTC, 6x6 blocks, unsigned normalized
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR:			// 4-component ASTC, 6x6 blocks, sRGB
+			pFormatSize->flags = GL_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 128;
+			pFormatSize->blockWidth = 6;
+			pFormatSize->blockHeight = 6;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_COMPRESSED_RGBA_ASTC_8x5_KHR:					// 4-component ASTC, 8x5 blocks, unsigned normalized
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR:			// 4-component ASTC, 8x5 blocks, sRGB
+			pFormatSize->flags = GL_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 128;
+			pFormatSize->blockWidth = 8;
+			pFormatSize->blockHeight = 5;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_COMPRESSED_RGBA_ASTC_8x6_KHR:					// 4-component ASTC, 8x6 blocks, unsigned normalized
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR:			// 4-component ASTC, 8x6 blocks, sRGB
+			pFormatSize->flags = GL_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 128;
+			pFormatSize->blockWidth = 8;
+			pFormatSize->blockHeight = 6;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_COMPRESSED_RGBA_ASTC_8x8_KHR:					// 4-component ASTC, 8x8 blocks, unsigned normalized
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR:			// 4-component ASTC, 8x8 blocks, sRGB
+			pFormatSize->flags = GL_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 128;
+			pFormatSize->blockWidth = 8;
+			pFormatSize->blockHeight = 8;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_COMPRESSED_RGBA_ASTC_10x5_KHR:					// 4-component ASTC, 10x5 blocks, unsigned normalized
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR:			// 4-component ASTC, 10x5 blocks, sRGB
+			pFormatSize->flags = GL_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 128;
+			pFormatSize->blockWidth = 10;
+			pFormatSize->blockHeight = 5;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_COMPRESSED_RGBA_ASTC_10x6_KHR:					// 4-component ASTC, 10x6 blocks, unsigned normalized
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR:			// 4-component ASTC, 10x6 blocks, sRGB
+			pFormatSize->flags = GL_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 128;
+			pFormatSize->blockWidth = 10;
+			pFormatSize->blockHeight = 6;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_COMPRESSED_RGBA_ASTC_10x8_KHR:					// 4-component ASTC, 10x8 blocks, unsigned normalized
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR:			// 4-component ASTC, 10x8 blocks, sRGB
+			pFormatSize->flags = GL_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 128;
+			pFormatSize->blockWidth = 10;
+			pFormatSize->blockHeight = 8;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_COMPRESSED_RGBA_ASTC_10x10_KHR:					// 4-component ASTC, 10x10 blocks, unsigned normalized
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR:			// 4-component ASTC, 10x10 blocks, sRGB
+			pFormatSize->flags = GL_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 128;
+			pFormatSize->blockWidth = 10;
+			pFormatSize->blockHeight = 10;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_COMPRESSED_RGBA_ASTC_12x10_KHR:					// 4-component ASTC, 12x10 blocks, unsigned normalized
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR:			// 4-component ASTC, 12x10 blocks, sRGB
+			pFormatSize->flags = GL_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 128;
+			pFormatSize->blockWidth = 12;
+			pFormatSize->blockHeight = 10;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_COMPRESSED_RGBA_ASTC_12x12_KHR:					// 4-component ASTC, 12x12 blocks, unsigned normalized
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR:			// 4-component ASTC, 12x12 blocks, sRGB
+			pFormatSize->flags = GL_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 128;
+			pFormatSize->blockWidth = 12;
+			pFormatSize->blockHeight = 12;
+			pFormatSize->blockDepth = 1;
+			break;
+
+		case GL_COMPRESSED_RGBA_ASTC_3x3x3_OES:					// 4-component ASTC, 3x3x3 blocks, unsigned normalized
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_3x3x3_OES:			// 4-component ASTC, 3x3x3 blocks, sRGB
+			pFormatSize->flags = GL_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 128;
+			pFormatSize->blockWidth = 3;
+			pFormatSize->blockHeight = 3;
+			pFormatSize->blockDepth = 3;
+			break;
+		case GL_COMPRESSED_RGBA_ASTC_4x3x3_OES:					// 4-component ASTC, 4x3x3 blocks, unsigned normalized
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x3x3_OES:			// 4-component ASTC, 4x3x3 blocks, sRGB
+			pFormatSize->flags = GL_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 128;
+			pFormatSize->blockWidth = 4;
+			pFormatSize->blockHeight = 3;
+			pFormatSize->blockDepth = 3;
+			break;
+		case GL_COMPRESSED_RGBA_ASTC_4x4x3_OES:					// 4-component ASTC, 4x4x3 blocks, unsigned normalized
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4x3_OES:			// 4-component ASTC, 4x4x3 blocks, sRGB
+			pFormatSize->flags = GL_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 128;
+			pFormatSize->blockWidth = 4;
+			pFormatSize->blockHeight = 4;
+			pFormatSize->blockDepth = 3;
+			break;
+		case GL_COMPRESSED_RGBA_ASTC_4x4x4_OES:					// 4-component ASTC, 4x4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4x4_OES:			// 4-component ASTC, 4x4x4 blocks, sRGB
+			pFormatSize->flags = GL_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 128;
+			pFormatSize->blockWidth = 4;
+			pFormatSize->blockHeight = 4;
+			pFormatSize->blockDepth = 4;
+			break;
+		case GL_COMPRESSED_RGBA_ASTC_5x4x4_OES:					// 4-component ASTC, 5x4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4x4_OES:			// 4-component ASTC, 5x4x4 blocks, sRGB
+			pFormatSize->flags = GL_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 128;
+			pFormatSize->blockWidth = 5;
+			pFormatSize->blockHeight = 4;
+			pFormatSize->blockDepth = 4;
+			break;
+		case GL_COMPRESSED_RGBA_ASTC_5x5x4_OES:					// 4-component ASTC, 5x5x4 blocks, unsigned normalized
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5x4_OES:			// 4-component ASTC, 5x5x4 blocks, sRGB
+			pFormatSize->flags = GL_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 128;
+			pFormatSize->blockWidth = 5;
+			pFormatSize->blockHeight = 5;
+			pFormatSize->blockDepth = 4;
+			break;
+		case GL_COMPRESSED_RGBA_ASTC_5x5x5_OES:					// 4-component ASTC, 5x5x5 blocks, unsigned normalized
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5x5_OES:			// 4-component ASTC, 5x5x5 blocks, sRGB
+			pFormatSize->flags = GL_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 128;
+			pFormatSize->blockWidth = 5;
+			pFormatSize->blockHeight = 5;
+			pFormatSize->blockDepth = 5;
+			break;
+		case GL_COMPRESSED_RGBA_ASTC_6x5x5_OES:					// 4-component ASTC, 6x5x5 blocks, unsigned normalized
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5x5_OES:			// 4-component ASTC, 6x5x5 blocks, sRGB
+			pFormatSize->flags = GL_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 128;
+			pFormatSize->blockWidth = 6;
+			pFormatSize->blockHeight = 5;
+			pFormatSize->blockDepth = 5;
+			break;
+		case GL_COMPRESSED_RGBA_ASTC_6x6x5_OES:					// 4-component ASTC, 6x6x5 blocks, unsigned normalized
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6x5_OES:			// 4-component ASTC, 6x6x5 blocks, sRGB
+			pFormatSize->flags = GL_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 128;
+			pFormatSize->blockWidth = 6;
+			pFormatSize->blockHeight = 6;
+			pFormatSize->blockDepth = 5;
+			break;
+		case GL_COMPRESSED_RGBA_ASTC_6x6x6_OES:					// 4-component ASTC, 6x6x6 blocks, unsigned normalized
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6x6_OES:			// 4-component ASTC, 6x6x6 blocks, sRGB
+			pFormatSize->flags = GL_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 128;
+			pFormatSize->blockWidth = 6;
+			pFormatSize->blockHeight = 6;
+			pFormatSize->blockDepth = 6;
+			break;
+
+		//
+		// ATC
+		//
+		case GL_ATC_RGB_AMD:									// 3-component, 4x4 blocks, unsigned normalized
+			pFormatSize->flags = GL_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 64;
+			pFormatSize->blockWidth = 4;
+			pFormatSize->blockHeight = 4;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_ATC_RGBA_EXPLICIT_ALPHA_AMD:					// 4-component, 4x4 blocks, unsigned normalized
+		case GL_ATC_RGBA_INTERPOLATED_ALPHA_AMD:				// 4-component, 4x4 blocks, unsigned normalized
+			pFormatSize->flags = GL_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 128;
+			pFormatSize->blockWidth = 4;
+			pFormatSize->blockHeight = 4;
+			pFormatSize->blockDepth = 1;
+			break;
+
+		//
+		// Palletized
+		//
+		case GL_PALETTE4_RGB8_OES:								// 3-component 8:8:8,   4-bit palette, unsigned normalized
+			pFormatSize->flags = GL_FORMAT_SIZE_PALETTIZED_BIT;
+			pFormatSize->paletteSizeInBits = 16 * 24;
+			pFormatSize->blockSizeInBits = 4;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_PALETTE4_RGBA8_OES:								// 4-component 8:8:8:8, 4-bit palette, unsigned normalized
+			pFormatSize->flags = GL_FORMAT_SIZE_PALETTIZED_BIT;
+			pFormatSize->paletteSizeInBits = 16 * 32;
+			pFormatSize->blockSizeInBits = 4;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_PALETTE4_R5_G6_B5_OES:							// 3-component 5:6:5,   4-bit palette, unsigned normalized
+		case GL_PALETTE4_RGBA4_OES:								// 4-component 4:4:4:4, 4-bit palette, unsigned normalized
+		case GL_PALETTE4_RGB5_A1_OES:							// 4-component 5:5:5:1, 4-bit palette, unsigned normalized
+			pFormatSize->flags = GL_FORMAT_SIZE_PALETTIZED_BIT;
+			pFormatSize->paletteSizeInBits = 16 * 16;
+			pFormatSize->blockSizeInBits = 4;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_PALETTE8_RGB8_OES:								// 3-component 8:8:8,   8-bit palette, unsigned normalized
+			pFormatSize->flags = GL_FORMAT_SIZE_PALETTIZED_BIT;
+			pFormatSize->paletteSizeInBits = 256 * 24;
+			pFormatSize->blockSizeInBits = 8;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_PALETTE8_RGBA8_OES:								// 4-component 8:8:8:8, 8-bit palette, unsigned normalized
+			pFormatSize->flags = GL_FORMAT_SIZE_PALETTIZED_BIT;
+			pFormatSize->paletteSizeInBits = 256 * 32;
+			pFormatSize->blockSizeInBits = 8;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_PALETTE8_R5_G6_B5_OES:							// 3-component 5:6:5,   8-bit palette, unsigned normalized
+		case GL_PALETTE8_RGBA4_OES:								// 4-component 4:4:4:4, 8-bit palette, unsigned normalized
+		case GL_PALETTE8_RGB5_A1_OES:							// 4-component 5:5:5:1, 8-bit palette, unsigned normalized
+			pFormatSize->flags = GL_FORMAT_SIZE_PALETTIZED_BIT;
+			pFormatSize->paletteSizeInBits = 256 * 16;
+			pFormatSize->blockSizeInBits = 8;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+
+		//
+		// Depth/stencil
+		//
+		case GL_DEPTH_COMPONENT16:
+			pFormatSize->flags = GL_FORMAT_SIZE_DEPTH_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 16;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_DEPTH_COMPONENT24:
+		case GL_DEPTH_COMPONENT32:
+		case GL_DEPTH_COMPONENT32F:
+		case GL_DEPTH_COMPONENT32F_NV:
+			pFormatSize->flags = GL_FORMAT_SIZE_DEPTH_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 32;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_STENCIL_INDEX1:
+			pFormatSize->flags = GL_FORMAT_SIZE_STENCIL_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 1;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_STENCIL_INDEX4:
+			pFormatSize->flags = GL_FORMAT_SIZE_STENCIL_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 4;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_STENCIL_INDEX8:
+			pFormatSize->flags = GL_FORMAT_SIZE_STENCIL_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 8;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_STENCIL_INDEX16:
+			pFormatSize->flags = GL_FORMAT_SIZE_STENCIL_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 16;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_DEPTH24_STENCIL8:
+			pFormatSize->flags = GL_FORMAT_SIZE_DEPTH_BIT | GL_FORMAT_SIZE_STENCIL_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 32;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case GL_DEPTH32F_STENCIL8:
+		case GL_DEPTH32F_STENCIL8_NV:
+			pFormatSize->flags = GL_FORMAT_SIZE_DEPTH_BIT | GL_FORMAT_SIZE_STENCIL_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 64;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+
+		default:
+			pFormatSize->flags = 0;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 8;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+	}
+}
+
+#endif // !GL_FORMAT_H
diff --git a/external/Vulkan/external/ktx/lib/gl_funcptrs.h b/external/Vulkan/external/ktx/lib/gl_funcptrs.h
new file mode 100644
index 0000000000000000000000000000000000000000..a9c644abbda7b600873dd7201d419954d3890208
--- /dev/null
+++ b/external/Vulkan/external/ktx/lib/gl_funcptrs.h
@@ -0,0 +1,69 @@
+/* -*- tab-width: 4; -*- */
+/* vi: set sw=2 ts=4 expandtab: */
+
+/* $Id: c29f4818e74655d62754e26efdab99cb2d2ca638 $ */
+
+/*
+ * ©2010-2017 The khronos Group, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Author: Mark Callow based on code from Georg Kolling
+ */
+
+#ifndef GL_FUNCPTRS_H
+#define GL_FUNCPTRS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if KTX_USE_GETPROC
+  // Not defined in glew.h.
+  typedef void (GL_APIENTRY* PFNGLTEXIMAGE1DPROC) (
+                    GLenum target, GLint level, GLint internalformat,
+                    GLsizei width, GLint border, GLenum format, GLenum type,
+                    const GLvoid *pixels
+                                                  );
+#endif
+
+extern PFNGLTEXIMAGE1DPROC pfGlTexImage1D;
+extern PFNGLTEXIMAGE3DPROC pfGlTexImage3D;
+extern PFNGLCOMPRESSEDTEXIMAGE1DPROC pfGlCompressedTexImage1D;
+extern PFNGLCOMPRESSEDTEXIMAGE3DPROC pfGlCompressedTexImage3D;
+extern PFNGLGENERATEMIPMAPPROC pfGlGenerateMipmap;
+extern PFNGLGETSTRINGIPROC pfGlGetStringi;
+
+#define DECLARE_GL_FUNCPTRS \
+    PFNGLTEXIMAGE1DPROC pfGlTexImage1D; \
+    PFNGLTEXIMAGE3DPROC pfGlTexImage3D; \
+    PFNGLCOMPRESSEDTEXIMAGE1DPROC pfGlCompressedTexImage1D; \
+    PFNGLCOMPRESSEDTEXIMAGE3DPROC pfGlCompressedTexImage3D; \
+    PFNGLGENERATEMIPMAPPROC pfGlGenerateMipmap; \
+    PFNGLGETSTRINGIPROC pfGlGetStringi;
+
+#define INITIALIZE_GL_FUNCPTRS \
+    pfGlTexImage1D = glTexImage1D; \
+    pfGlTexImage3D = glTexImage3D; \
+    pfGlCompressedTexImage1D = glCompressedTexImage1D; \
+    pfGlCompressedTexImage3D = glCompressedTexImage3D; \
+    pfGlGenerateMipmap = glGenerateMipmap; \
+    pfGlGetStringi = glGetStringi;
+    
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* GL_FUNCPTRS_H */
diff --git a/external/Vulkan/external/ktx/lib/gles1_funcptrs.h b/external/Vulkan/external/ktx/lib/gles1_funcptrs.h
new file mode 100644
index 0000000000000000000000000000000000000000..4b96da9914d73c9a9e5f910813c72249fac77f4f
--- /dev/null
+++ b/external/Vulkan/external/ktx/lib/gles1_funcptrs.h
@@ -0,0 +1,68 @@
+/* -*- tab-width: 4; -*- */
+/* vi: set sw=2 ts=4 expandtab: */
+
+/* $Id: a9dea3dfe7b8266c56bd790f15e03e8e7e1baa8e $ */
+
+/*
+ * ©2010-2017 The khronos Group, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Author: Mark Callow based on code from Georg Kolling
+ */
+
+#ifndef GLES1_FUNCPTRS_H
+#define GLES1_FUNCPTRS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* remove these where already defined as typedefs (GCC 4 complains of duplicate definitions) */
+typedef void (GL_APIENTRY* PFNGLTEXIMAGE1DPROC) (GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels);
+typedef void (GL_APIENTRY* PFNGLTEXIMAGE3DPROC) (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels);
+typedef void (GL_APIENTRY* PFNGLCOMPRESSEDTEXIMAGE1DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const GLvoid *data);
+typedef void (GL_APIENTRY* PFNGLCOMPRESSEDTEXIMAGE3DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const GLvoid *data);
+typedef void (GL_APIENTRY* PFNGLGENERATEMIPMAPPROC) (GLenum target);
+typedef const GLubyte* (GL_APIENTRY* PFNGLGETSTRINGIPROC) (GLenum name, GLint index);
+
+extern PFNGLTEXIMAGE1DPROC pfGlTexImage1D;
+extern PFNGLTEXIMAGE3DPROC pfGlTexImage3D;
+extern PFNGLCOMPRESSEDTEXIMAGE1DPROC pfGlCompressedTexImage1D;
+extern PFNGLCOMPRESSEDTEXIMAGE3DPROC pfGlCompressedTexImage3D;
+extern PFNGLGENERATEMIPMAPPROC pfGlGenerateMipmap;
+extern PFNGLGETSTRINGIPROC pfGlGetStringi;
+
+#define DECLARE_GL_FUNCPTRS \
+    PFNGLTEXIMAGE1DPROC pfGlTexImage1D; \
+    PFNGLTEXIMAGE3DPROC pfGlTexImage3D; \
+    PFNGLCOMPRESSEDTEXIMAGE1DPROC pfGlCompressedTexImage1D; \
+    PFNGLCOMPRESSEDTEXIMAGE3DPROC pfGlCompressedTexImage3D; \
+    PFNGLGENERATEMIPMAPPROC pfGlGenerateMipmap; \
+    PFNGLGETSTRINGIPROC pfGlGetStringi;
+
+#define INITIALIZE_GL_FUNCPTRS \
+    pfGlTexImage1D = 0; \
+    pfGlTexImage3D = 0; \
+    pfGlCompressedTexImage1D = 0; \
+    pfGlCompressedTexImage3D = 0; \
+    pfGlGenerateMipmap = 0; \
+    pfGlGetStringi = 0;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* GLES1_FUNCPTRS_H */
diff --git a/external/Vulkan/external/ktx/lib/gles2_funcptrs.h b/external/Vulkan/external/ktx/lib/gles2_funcptrs.h
new file mode 100644
index 0000000000000000000000000000000000000000..149469590370271936917b31ee04eb17849ad8e7
--- /dev/null
+++ b/external/Vulkan/external/ktx/lib/gles2_funcptrs.h
@@ -0,0 +1,67 @@
+/* -*- tab-width: 4; -*- */
+/* vi: set sw=2 ts=4 expandtab: */
+
+/* $Id: f780b88290b1aa6df0104020b75e21904e53ff1c $ */
+
+/*
+ * ©2010-2017 The khronos Group, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Author: Mark Callow based on code from Georg Kolling
+ */
+
+#ifndef GLES2_FUNCPTRS_H
+#define GLES2_FUNCPTRS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* remove these where already defined as typedefs (GCC 4 complains of duplicate definitions) */
+typedef void (GL_APIENTRY* PFNGLTEXIMAGE1DPROC) (GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels);
+typedef void (GL_APIENTRY* PFNGLTEXIMAGE3DPROC) (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels);
+typedef void (GL_APIENTRY* PFNGLCOMPRESSEDTEXIMAGE1DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const GLvoid *data);
+typedef void (GL_APIENTRY* PFNGLCOMPRESSEDTEXIMAGE3DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const GLvoid *data);
+typedef const GLubyte* (GL_APIENTRY* PFNGLGETSTRINGIPROC) (GLenum name, GLuint index);
+
+extern PFNGLTEXIMAGE1DPROC pfGlTexImage1D;
+extern PFNGLTEXIMAGE3DPROC pfGlTexImage3D;
+extern PFNGLCOMPRESSEDTEXIMAGE1DPROC pfGlCompressedTexImage1D;
+extern PFNGLCOMPRESSEDTEXIMAGE3DPROC pfGlCompressedTexImage3D;
+extern PFNGLGENERATEMIPMAPPROC pfGlGenerateMipmap;
+extern PFNGLGETSTRINGIPROC pfGlGetStringi;
+
+#define DECLARE_GL_FUNCPTRS \
+    PFNGLTEXIMAGE1DPROC pfGlTexImage1D; \
+    PFNGLTEXIMAGE3DPROC pfGlTexImage3D; \
+    PFNGLCOMPRESSEDTEXIMAGE1DPROC pfGlCompressedTexImage1D; \
+    PFNGLCOMPRESSEDTEXIMAGE3DPROC pfGlCompressedTexImage3D; \
+    PFNGLGENERATEMIPMAPPROC pfGlGenerateMipmap; \
+    PFNGLGETSTRINGIPROC pfGlGetStringi;
+
+#define INITIALIZE_GL_FUNCPTRS \
+    pfGlTexImage1D = 0; \
+    pfGlTexImage3D = 0; \
+    pfGlCompressedTexImage1D = 0; \
+    pfGlCompressedTexImage3D = 0; \
+    pfGlGenerateMipmap = glGenerateMipmap; \
+    pfGlGetStringi = 0;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* GLES2_FUNCPTRS_H */
diff --git a/external/Vulkan/external/ktx/lib/gles3_funcptrs.h b/external/Vulkan/external/ktx/lib/gles3_funcptrs.h
new file mode 100644
index 0000000000000000000000000000000000000000..b7ea291c40e32d37665fa8047fc65d2365673d30
--- /dev/null
+++ b/external/Vulkan/external/ktx/lib/gles3_funcptrs.h
@@ -0,0 +1,72 @@
+/* -*- tab-width: 4; -*- */
+/* vi: set sw=2 ts=4 expandtab: */
+
+/* $Id: 81315679d0a7bf28f92251adeb0cfea199e013fe $ */
+
+/*
+ * ©2010-2017 The khronos Group, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Author: Mark Callow based on code from Georg Kolling
+ */
+
+#ifndef GLES3_FUNCPTRS_H
+#define GLES3_FUNCPTRS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* remove these where already defined as typedefs */
+typedef void (GL_APIENTRY* PFNGLTEXIMAGE1DPROC) (
+                        GLenum target, GLint level, GLint internalformat,
+                        GLsizei width, GLint border, GLenum format,
+                        GLenum type, const GLvoid *pixels
+                                                );
+typedef void (GL_APIENTRY* PFNGLCOMPRESSEDTEXIMAGE1DPROC) (
+                        GLenum target, GLint level, GLenum internalformat,
+                        GLsizei width, GLint border, GLsizei imageSize,
+                        const GLvoid *data
+                                                          );
+
+extern PFNGLTEXIMAGE1DPROC pfGlTexImage1D;
+extern PFNGLTEXIMAGE3DPROC pfGlTexImage3D;
+extern PFNGLCOMPRESSEDTEXIMAGE1DPROC pfGlCompressedTexImage1D;
+extern PFNGLCOMPRESSEDTEXIMAGE3DPROC pfGlCompressedTexImage3D;
+extern PFNGLGENERATEMIPMAPPROC pfGlGenerateMipmap;
+extern PFNGLGETSTRINGIPROC pfGlGetStringi;
+    
+#define DECLARE_GL_FUNCPTRS \
+    PFNGLTEXIMAGE1DPROC pfGlTexImage1D; \
+    PFNGLTEXIMAGE3DPROC pfGlTexImage3D; \
+    PFNGLCOMPRESSEDTEXIMAGE1DPROC pfGlCompressedTexImage1D; \
+    PFNGLCOMPRESSEDTEXIMAGE3DPROC pfGlCompressedTexImage3D; \
+    PFNGLGENERATEMIPMAPPROC pfGlGenerateMipmap; \
+    PFNGLGETSTRINGIPROC pfGlGetStringi;
+
+#define INITIALIZE_GL_FUNCPTRS \
+    pfGlTexImage1D = 0; \
+    pfGlTexImage3D = glTexImage3D; \
+    pfGlCompressedTexImage1D = 0; \
+    pfGlCompressedTexImage3D = glCompressedTexImage3D; \
+    pfGlGenerateMipmap = glGenerateMipmap; \
+    pfGlGetStringi = glGetStringi;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* GLES3_FUNCPTRS_H */
diff --git a/external/Vulkan/external/ktx/lib/glloader.c b/external/Vulkan/external/ktx/lib/glloader.c
new file mode 100644
index 0000000000000000000000000000000000000000..1089898dc99763f3d5fad4cacfa02024ec10af66
--- /dev/null
+++ b/external/Vulkan/external/ktx/lib/glloader.c
@@ -0,0 +1,1115 @@
+/* -*- tab-width: 4; -*- */
+/* vi: set sw=2 ts=4 expandtab: */
+
+/*
+ * ©2010-2018 Mark Callow.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @internal
+ * @file
+ * @~English
+ *
+ * @brief Functions for instantiating GL or GLES textures from KTX files.
+ *
+ * @author Georg Kolling, Imagination Technology
+ * @author Mark Callow, HI Corporation & Edgewise Consulting
+ */
+
+#ifdef _WIN32
+#define _CRT_SECURE_NO_WARNINGS
+#endif
+
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+
+#if KTX_OPENGL
+
+  #ifdef _WIN32
+    #include <windows.h>
+    #undef KTX_USE_GETPROC  /* Must use GETPROC on Windows */
+    #define KTX_USE_GETPROC 1
+  #else
+    #if !defined(KTX_USE_GETPROC)
+      #define KTX_USE_GETPROC 0
+    #endif
+  #endif
+  #if KTX_USE_GETPROC
+    #include <GL/glew.h>
+  #else
+    #define GL_GLEXT_PROTOTYPES
+    #include <GL/glcorearb.h>
+  #endif
+
+  #define GL_APIENTRY APIENTRY
+  #include "gl_funcptrs.h"
+
+#elif KTX_OPENGL_ES1
+
+  #include <GLES/gl.h>
+  #include <GLES/glext.h>
+  #include "gles1_funcptrs.h"
+
+#elif KTX_OPENGL_ES2
+
+  #define GL_GLEXT_PROTOTYPES
+  #include <GLES2/gl2.h>
+  #include <GLES2/gl2ext.h>
+  #include "gles2_funcptrs.h"
+
+#elif KTX_OPENGL_ES3
+
+  #define GL_GLEXT_PROTOTYPES
+  #include <GLES3/gl3.h>
+  #include <GLES2/gl2ext.h>
+  #include "gles3_funcptrs.h"
+
+#else
+  #error Please #define one of KTX_OPENGL, KTX_OPENGL_ES1, KTX_OPENGL_ES2 or KTX_OPENGL_ES3 as 1
+#endif
+
+#include "ktx.h"
+#include "ktxint.h"
+#include "ktxgl.h"
+
+DECLARE_GL_FUNCPTRS
+
+/**
+ * @defgroup ktx_glloader OpenGL Texture Image Loader
+ * @brief Create texture objects in current OpenGL context.
+ * @{
+ */
+
+/**
+ * @example glloader.c
+ * This is an example of using the low-level ktxTexture API to create and load
+ * an OpenGL texture. It is a fragment of the code used by
+ * @ref ktxTexture_GLUpload which underpins the @c ktxLoadTexture* functions.
+ *
+ * @code
+ * #include <ktx.h>
+ * @endcode
+ *
+ * This structure is used to pass to a callback function data that is uniform
+ * across all images.
+ * @snippet this cbdata
+ *
+ * One of these callbacks, selected by @ref ktxTexture_GLUpload based on the
+ * dimensionality and arrayness of the texture, is called from
+ * @ref ktxTexture_IterateLevelFaces to upload the texture data to OpenGL.
+ * @snippet this imageCallbacks
+ *
+ * This function creates the GL texture object and sets up the callbacks to
+ * load the image data into it.
+ * @snippet this loadGLTexture
+ */
+
+/**
+ * @internal
+ * @~English
+ * @brief Additional contextProfile bit indicating an OpenGL ES context.
+ *
+ * This is the same value NVIDIA returns when using an OpenGL ES profile
+ * of their desktop drivers. However it is not specified in any official
+ * specification as OpenGL ES does not support the GL_CONTEXT_PROFILE_MASK
+ * query.
+ */
+#define _CONTEXT_ES_PROFILE_BIT 0x4
+
+/**
+ * @internal
+ * @~English
+ * @name Supported Sized Format Macros
+ *
+ * These macros describe values that may be used with the sizedFormats
+ * variable.
+ */
+/**@{*/
+#define _NON_LEGACY_FORMATS 0x1 /*< @internal Non-legacy sized formats are supported. */
+#define _LEGACY_FORMATS 0x2  /*< @internal Legacy sized formats are supported. */
+/**
+ * @internal
+ * @~English
+ * @brief All sized formats are supported
+ */
+#define _ALL_SIZED_FORMATS (_NON_LEGACY_FORMATS | _LEGACY_FORMATS)
+#define _NO_SIZED_FORMATS 0 /*< @internal No sized formats are supported. */
+/**@}*/
+
+/**
+ * @internal
+ * @~English
+ * @brief indicates the profile of the current context.
+ */
+static GLint contextProfile = 0;
+/**
+ * @internal
+ * @~English
+ * @brief Indicates what sized texture formats are supported
+ *        by the current context.
+ */
+static GLint sizedFormats = _ALL_SIZED_FORMATS;
+static GLboolean supportsSwizzle = GL_TRUE;
+/**
+ * @internal
+ * @~English
+ * @brief Indicates which R16 & RG16 formats are supported by the current
+ *        context.
+ */
+static GLint R16Formats = _KTX_ALL_R16_FORMATS;
+/**
+ * @internal
+ * @~English
+ * @brief Indicates if the current context supports sRGB textures.
+ */
+static GLboolean supportsSRGB = GL_TRUE;
+/**
+ * @internal
+ * @~English
+ * @brief Indicates if the current context supports cube map arrays.
+ */
+static GLboolean supportsCubeMapArrays = GL_FALSE;
+
+/**
+ * @internal
+ * @~English
+ * @brief Workaround mismatch of glGetString declaration and standard string
+ *        function parameters.
+ */
+#define glGetString(x) (const char*)glGetString(x)
+
+/**
+ * @internal
+ * @~English
+ * @brief Workaround mismatch of glGetStringi declaration and standard string
+ *        function parameters.
+ */
+#define pfGlGetStringi(x,y) (const char*)pfGlGetStringi(x,y)
+
+/**
+ * @internal
+ * @~English
+ * @brief Check for existence of OpenGL extension
+ */
+static GLboolean
+hasExtension(const char* extension) 
+{
+    if (pfGlGetStringi == NULL) {
+        if (strstr(glGetString(GL_EXTENSIONS), extension) != NULL)
+            return GL_TRUE;
+        else
+            return GL_FALSE;
+    } else {
+        int i, n;
+
+        glGetIntegerv(GL_NUM_EXTENSIONS, &n);
+        for (i = 0; i < n; i++) {
+            if (strcmp(pfGlGetStringi(GL_EXTENSIONS, i), extension) == 0)
+                return GL_TRUE;
+        }
+        return GL_FALSE;
+    }
+}
+
+/**
+ * @internal
+ * @~English
+ * @brief Discover the capabilities of the current GL context.
+ *
+ * Queries the context and sets several the following internal variables
+ * indicating the capabilities of the context:
+ *
+ * @li sizedFormats
+ * @li supportsSwizzle
+ * @li supportsSRGB
+ * @li b16Formats
+ */
+static void
+discoverContextCapabilities(void)
+{
+    GLint majorVersion = 1;
+    GLint minorVersion = 0;
+
+    // Done here so things will work when GLEW, or equivalent, is being used
+    // and GL function names are defined as pointers. Initialization at
+    // declaration would happen before these pointers have been initialized.
+    INITIALIZE_GL_FUNCPTRS
+
+    if (strstr(glGetString(GL_VERSION), "GL ES") != NULL)
+        contextProfile = _CONTEXT_ES_PROFILE_BIT;
+    // MAJOR & MINOR only introduced in GL {,ES} 3.0
+    glGetIntegerv(GL_MAJOR_VERSION, &majorVersion);
+    glGetIntegerv(GL_MINOR_VERSION, &minorVersion);
+    if (glGetError() != GL_NO_ERROR) {
+        // < v3.0; resort to the old-fashioned way.
+        if (contextProfile & _CONTEXT_ES_PROFILE_BIT)
+            sscanf(glGetString(GL_VERSION), "OpenGL ES %d.%d ",
+                   &majorVersion, &minorVersion);
+        else
+            sscanf(glGetString(GL_VERSION), "OpenGL %d.%d ",
+                   &majorVersion, &minorVersion);
+    }
+    if (contextProfile & _CONTEXT_ES_PROFILE_BIT) {
+        if (majorVersion < 3) {
+            supportsSwizzle = GL_FALSE;
+            sizedFormats = _NO_SIZED_FORMATS;
+            R16Formats = _KTX_NO_R16_FORMATS;
+            supportsSRGB = GL_FALSE;
+        } else {
+            sizedFormats = _NON_LEGACY_FORMATS;
+            if (hasExtension("GL_EXT_texture_cube_map_array")) {
+                supportsCubeMapArrays = GL_TRUE;
+            }
+        }
+        if (hasExtension("GL_OES_required_internalformat")) {
+            sizedFormats |= _ALL_SIZED_FORMATS;
+        }
+        // There are no OES extensions for sRGB textures or R16 formats.
+    } else {
+        // PROFILE_MASK was introduced in OpenGL 3.2.
+        // Profiles: CONTEXT_CORE_PROFILE_BIT 0x1,
+        //           CONTEXT_COMPATIBILITY_PROFILE_BIT 0x2.
+        glGetIntegerv(GL_CONTEXT_PROFILE_MASK, &contextProfile);
+        if (glGetError() == GL_NO_ERROR) {
+            // >= 3.2
+            if (majorVersion == 3 && minorVersion < 3)
+                supportsSwizzle = GL_FALSE;
+            if ((contextProfile & GL_CONTEXT_CORE_PROFILE_BIT))
+                sizedFormats &= ~_LEGACY_FORMATS;
+            if (majorVersion >= 4)
+                supportsCubeMapArrays = GL_TRUE;
+        } else {
+            // < 3.2
+            contextProfile = GL_CONTEXT_COMPATIBILITY_PROFILE_BIT;
+            supportsSwizzle = GL_FALSE;
+            // sRGB textures introduced in 2.0
+            if (majorVersion < 2 && hasExtension("GL_EXT_texture_sRGB")) {
+                supportsSRGB = GL_FALSE;
+            }
+            // R{,G]16 introduced in 3.0; R{,G}16_SNORM introduced in 3.1.
+            if (majorVersion == 3) {
+                if (minorVersion == 0)
+                    R16Formats &= ~_KTX_R16_FORMATS_SNORM;
+            } else if (hasExtension("GL_ARB_texture_rg")) {
+                R16Formats &= ~_KTX_R16_FORMATS_SNORM;
+            } else {
+                R16Formats = _KTX_NO_R16_FORMATS;
+            }
+        }
+        if (!supportsCubeMapArrays) {
+            if (hasExtension("GL_ARB_texture_cube_map_array")) {
+                supportsCubeMapArrays = GL_TRUE;
+            }
+        }
+    }
+}
+
+#if SUPPORT_LEGACY_FORMAT_CONVERSION
+/**
+ * @internal
+ * @~English
+ * @brief Convert deprecated legacy-format texture to modern format.
+ *
+ * The function sets the GL_TEXTURE_SWIZZLEs necessary to get the same
+ * behavior as the legacy format.
+ *
+ * @param[in] target       texture target on which the swizzle will
+ *                          be set.
+ * @param[in,out] pFormat  pointer to variable holding the base format of the
+ *                          texture. The new base format is written here.
+ * @param[in,out] pInternalformat  pointer to variable holding the
+ *                                  internalformat of the texture. The new
+ *                                  internalformat is written here.
+ * @return void unrecognized formats will be passed on to OpenGL. Any loading
+ *              error that arises will be handled in the usual way.
+ */
+static void convertFormat(GLenum target, GLenum* pFormat, GLenum* pInternalformat) {
+    switch (*pFormat) {
+      case GL_ALPHA:
+        {
+          GLint swizzle[] = {GL_ZERO, GL_ZERO, GL_ZERO, GL_RED};
+          *pFormat = GL_RED;
+          glTexParameteriv(target, GL_TEXTURE_SWIZZLE_RGBA, swizzle);
+          switch (*pInternalformat) {
+            case GL_ALPHA:
+            case GL_ALPHA4:
+            case GL_ALPHA8:
+              *pInternalformat = GL_R8;
+              break;
+            case GL_ALPHA12:
+            case GL_ALPHA16:
+              *pInternalformat = GL_R16;
+              break;
+          }
+        }
+      case GL_LUMINANCE:
+        {
+          GLint swizzle[] = {GL_RED, GL_RED, GL_RED, GL_ONE};
+          *pFormat = GL_RED;
+          glTexParameteriv(target, GL_TEXTURE_SWIZZLE_RGBA, swizzle);
+          switch (*pInternalformat) {
+            case GL_LUMINANCE:
+            case GL_LUMINANCE4:
+            case GL_LUMINANCE8:
+              *pInternalformat = GL_R8;
+              break;
+            case GL_LUMINANCE12:
+            case GL_LUMINANCE16:
+              *pInternalformat = GL_R16;
+              break;
+#if 0
+            // XXX Must avoid setting TEXTURE_SWIZZLE in these cases
+            // XXX Must manually swizzle.
+            case GL_SLUMINANCE:
+            case GL_SLUMINANCE8:
+              *pInternalformat = GL_SRGB8;
+              break;
+#endif
+          }
+          break;
+        }
+      case GL_LUMINANCE_ALPHA:
+        {
+          GLint swizzle[] = {GL_RED, GL_RED, GL_RED, GL_GREEN};
+          *pFormat = GL_RG;
+          glTexParameteriv(target, GL_TEXTURE_SWIZZLE_RGBA, swizzle);
+          switch (*pInternalformat) {
+            case GL_LUMINANCE_ALPHA:
+            case GL_LUMINANCE4_ALPHA4:
+            case GL_LUMINANCE6_ALPHA2:
+            case GL_LUMINANCE8_ALPHA8:
+              *pInternalformat = GL_RG8;
+              break;
+            case GL_LUMINANCE12_ALPHA4:
+            case GL_LUMINANCE12_ALPHA12:
+            case GL_LUMINANCE16_ALPHA16:
+              *pInternalformat = GL_RG16;
+              break;
+#if 0
+            // XXX Must avoid setting TEXTURE_SWIZZLE in these cases
+            // XXX Must manually swizzle.
+            case GL_SLUMINANCE_ALPHA:
+            case GL_SLUMINANCE8_ALPHA8:
+              *pInternalformat = GL_SRGB8_ALPHA8;
+              break;
+#endif
+          }
+          break;
+        }
+      case GL_INTENSITY:
+        {
+          GLint swizzle[] = {GL_RED, GL_RED, GL_RED, GL_RED};
+          *pFormat = GL_RED;
+          glTexParameteriv(target, GL_TEXTURE_SWIZZLE_RGBA, swizzle);
+          switch (*pInternalformat) {
+            case GL_INTENSITY:
+            case GL_INTENSITY4:
+            case GL_INTENSITY8:
+              *pInternalformat = GL_R8;
+              break;
+            case GL_INTENSITY12:
+            case GL_INTENSITY16:
+              *pInternalformat = GL_R16;
+              break;
+          }
+          break;
+        }
+      default:
+        break;
+    }
+}
+#endif /* SUPPORT_LEGACY_FORMAT_CONVERSION */
+
+/* [cbdata] */
+typedef struct ktx_cbdata {
+    GLenum glTarget;
+    GLenum glFormat;
+    GLenum glInternalformat;
+    GLenum glType;
+    GLenum glError;
+    GLuint numLayers;
+} ktx_cbdata;
+/* [cbdata] */
+
+/* [imageCallbacks] */
+
+KTX_error_code KTXAPIENTRY
+texImage1DCallback(int miplevel, int face,
+                   int width, int height,
+                   int depth,
+                   ktx_uint32_t faceLodSize,
+                   void* pixels, void* userdata)
+{
+    ktx_cbdata* cbData = (ktx_cbdata*)userdata;
+    
+    assert(pfGlTexImage1D != NULL);
+    pfGlTexImage1D(cbData->glTarget + face, miplevel,
+                   cbData->glInternalformat, width, 0,
+                   cbData->glFormat, cbData->glType, pixels);
+    
+    if ((cbData->glError = glGetError()) == GL_NO_ERROR) {
+        return KTX_SUCCESS;
+    } else {
+        return KTX_GL_ERROR;
+    }
+}
+
+KTX_error_code KTXAPIENTRY
+compressedTexImage1DCallback(int miplevel, int face,
+                             int width, int height,
+                             int depth,
+                             ktx_uint32_t faceLodSize,
+                             void* pixels, void* userdata)
+{
+    ktx_cbdata* cbData = (ktx_cbdata*)userdata;
+    
+    assert(pfGlCompressedTexImage1D != NULL);
+    pfGlCompressedTexImage1D(cbData->glTarget + face, miplevel,
+                             cbData->glInternalformat, width, 0,
+                             faceLodSize, pixels);
+    
+    if ((cbData->glError = glGetError()) == GL_NO_ERROR) {
+        return KTX_SUCCESS;
+    } else {
+        return KTX_GL_ERROR;
+    }
+}
+
+KTX_error_code KTXAPIENTRY
+texImage2DCallback(int miplevel, int face,
+                   int width, int height,
+                   int depth,
+                   ktx_uint32_t faceLodSize,
+                   void* pixels, void* userdata)
+{
+    ktx_cbdata* cbData = (ktx_cbdata*)userdata;
+ 
+    glTexImage2D(cbData->glTarget + face, miplevel,
+                 cbData->glInternalformat, width,
+                 cbData->numLayers == 0 ? height : cbData->numLayers, 0,
+                 cbData->glFormat, cbData->glType, pixels);
+
+    if ((cbData->glError = glGetError()) == GL_NO_ERROR) {
+        return KTX_SUCCESS;
+    } else {
+        return KTX_GL_ERROR;
+    }
+}
+
+
+KTX_error_code KTXAPIENTRY
+compressedTexImage2DCallback(int miplevel, int face,
+                             int width, int height,
+                             int depth,
+                             ktx_uint32_t faceLodSize,
+                             void* pixels, void* userdata)
+{
+    ktx_cbdata* cbData = (ktx_cbdata*)userdata;
+    GLenum glerror;
+    KTX_error_code result;
+    
+    // It is simpler to just attempt to load the format, rather than divine
+    // which formats are supported by the implementation. In the event of an
+    // error, software unpacking can be attempted.
+    glCompressedTexImage2D(cbData->glTarget + face, miplevel,
+                           cbData->glInternalformat, width,
+                           cbData->numLayers == 0 ? height : cbData->numLayers,
+                           0,
+                           faceLodSize, pixels);
+    
+    glerror = glGetError();
+#if SUPPORT_SOFTWARE_ETC_UNPACK
+    // Renderion is returning INVALID_VALUE. Oops!!
+    if ((glerror == GL_INVALID_ENUM || glerror == GL_INVALID_VALUE)
+        && (cbData->glInternalformat == GL_ETC1_RGB8_OES
+            || (cbData->glInternalformat >= GL_COMPRESSED_R11_EAC
+                && cbData->glInternalformat <= GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC)
+            ))
+    {
+        GLubyte* unpacked;
+        GLenum format, internalformat, type;
+        
+        result = _ktxUnpackETC((GLubyte*)pixels, cbData->glInternalformat,
+                                  width, height, &unpacked,
+                                  &format, &internalformat,
+                                  &type, R16Formats, supportsSRGB);
+        if (result != KTX_SUCCESS) {
+            return result;
+        }
+        if (!(sizedFormats & _NON_LEGACY_FORMATS)) {
+            if (internalformat == GL_RGB8)
+                internalformat = GL_RGB;
+            else if (internalformat == GL_RGBA8)
+                internalformat = GL_RGBA;
+        }
+        glTexImage2D(cbData->glTarget + face, miplevel,
+                     internalformat, width,
+                     cbData->numLayers == 0 ? height : cbData->numLayers, 0,
+                     format, type, unpacked);
+        
+        free(unpacked);
+        glerror = glGetError();
+    }
+#endif
+    
+    if ((cbData->glError = glerror) == GL_NO_ERROR) {
+        return KTX_SUCCESS;
+    } else {
+        return KTX_GL_ERROR;
+    }
+}
+
+KTX_error_code KTXAPIENTRY
+texImage3DCallback(int miplevel, int face,
+                   int width, int height,
+                   int depth,
+                   ktx_uint32_t faceLodSize,
+                   void* pixels, void* userdata)
+{
+    ktx_cbdata* cbData = (ktx_cbdata*)userdata;
+    
+    assert(pfGlTexImage3D != NULL);
+    pfGlTexImage3D(cbData->glTarget + face, miplevel,
+                   cbData->glInternalformat,
+                   width, height,
+                   cbData->numLayers == 0 ? depth : cbData->numLayers,
+                   0,
+                   cbData->glFormat, cbData->glType, pixels);
+    
+    if ((cbData->glError = glGetError()) == GL_NO_ERROR) {
+        return KTX_SUCCESS;
+    } else {
+        return KTX_GL_ERROR;
+    }
+}
+
+KTX_error_code KTXAPIENTRY
+compressedTexImage3DCallback(int miplevel, int face,
+                             int width, int height,
+                             int depth,
+                             ktx_uint32_t faceLodSize,
+                             void* pixels, void* userdata)
+{
+    ktx_cbdata* cbData = (ktx_cbdata*)userdata;
+    
+    assert(pfGlCompressedTexImage3D != NULL);
+    pfGlCompressedTexImage3D(cbData->glTarget + face, miplevel,
+                             cbData->glInternalformat,
+                             width, height,
+                             cbData->numLayers == 0 ? depth : cbData->numLayers,
+                             0,
+                             faceLodSize, pixels);
+    
+    if ((cbData->glError = glGetError()) == GL_NO_ERROR) {
+        return KTX_SUCCESS;
+    } else {
+        return KTX_GL_ERROR;
+    }
+}
+/* [imageCallbacks] */
+
+/**
+ * @memberof ktxTexture
+ * @~English
+ * @brief Create a GL texture object from a ktxTexture object.
+ *
+ * Sets the texture object's GL_TEXTURE_MAX_LEVEL parameter according to the
+ * number of levels in the KTX data, provided the library has been compiled
+ * with a version of gl.h where GL_TEXTURE_MAX_LEVEL is defined.
+ *
+ * Unpacks compressed GL_ETC1_RGB8_OES and GL_ETC2_* format
+ * textures in software when the format is not supported by the GL context,
+ * provided the library has been compiled with SUPPORT_SOFTWARE_ETC_UNPACK
+ * defined as 1.
+ *
+ * It will also convert textures with legacy formats to their modern equivalents
+ * when the format is not supported by the GL context, provided the library
+ * has been compiled with SUPPORT_LEGACY_FORMAT_CONVERSION defined as 1.
+ *
+ * @param[in] This          handle of the ktxTexture to upload.
+ * @param[in,out] pTexture  name of the GL texture object to load. If NULL or if
+ *                          <tt>*pTexture == 0</tt> the function will generate
+ *                          a texture name. The function binds either the
+ *                          generated name or the name given in @p *pTexture
+ *                          to the texture target returned in @p *pTarget,
+ *                          before loading the texture data. If @p pTexture
+ *                          is not NULL and a name was generated, the generated
+ *                          name will be returned in *pTexture.
+ * @param[out] pTarget      @p *pTarget is set to the texture target used. The
+ *                          target is chosen based on the file contents.
+ * @param[out] pGlerror     @p *pGlerror is set to the value returned by
+ *                          glGetError when this function returns the error
+ *                          KTX_GL_ERROR. glerror can be NULL.
+ *
+ * @return  KTX_SUCCESS on success, other KTX_* enum values on error.
+ *
+ * @exception KTX_INVALID_VALUE @p This or @p target is @c NULL or the size of
+ *                              a mip level is greater than the size of the
+ *                              preceding level.
+ * @exception KTX_GL_ERROR      A GL error was raised by glBindTexture,
+ *                              glGenTextures or gl*TexImage*. The GL error
+ *                              will be returned in @p *glerror, if glerror
+ *                              is not @c NULL.
+ * @exception KTX_UNSUPPORTED_TEXTURE_TYPE The type of texture is not supported
+ *                                         by the current OpenGL context.
+ */
+/* [loadGLTexture] */
+KTX_error_code
+ktxTexture_GLUpload(ktxTexture* This, GLuint* pTexture, GLenum* pTarget,
+                    GLenum* pGlerror)
+{
+    GLint                 previousUnpackAlignment;
+    GLuint                texname;
+    GLenum                target = GL_TEXTURE_2D;
+    int                   texnameUser;
+    KTX_error_code        result = KTX_SUCCESS;
+    PFNKTXITERCB          iterCb = NULL;
+    ktx_cbdata            cbData;
+    int                   dimensions;
+
+    if (pGlerror)
+        *pGlerror = GL_NO_ERROR;
+
+    if (!This) {
+        return KTX_INVALID_VALUE;
+    }
+
+    if (!pTarget) {
+        return KTX_INVALID_VALUE;
+    }
+
+    if (contextProfile == 0)
+        discoverContextCapabilities();
+
+    /* KTX files require an unpack alignment of 4 */
+    glGetIntegerv(GL_UNPACK_ALIGNMENT, &previousUnpackAlignment);
+    if (previousUnpackAlignment != KTX_GL_UNPACK_ALIGNMENT) {
+        glPixelStorei(GL_UNPACK_ALIGNMENT, KTX_GL_UNPACK_ALIGNMENT);
+    }
+
+    cbData.glFormat = This->glFormat;
+    cbData.glInternalformat = This->glInternalformat;
+    cbData.glType = This->glType;
+
+    texnameUser = pTexture && *pTexture;
+    if (texnameUser) {
+        texname = *pTexture;
+    } else {
+        glGenTextures(1, &texname);
+    }
+
+    dimensions = This->numDimensions;
+    if (This->isArray) {
+        dimensions += 1;
+        if (This->numFaces == 6) {
+            /* _ktxCheckHeader should have caught this. */
+            assert(This->numDimensions == 2);
+            target = GL_TEXTURE_CUBE_MAP_ARRAY;
+        } else {
+            switch (This->numDimensions) {
+              case 1: target = GL_TEXTURE_1D_ARRAY_EXT; break;
+              case 2: target = GL_TEXTURE_2D_ARRAY_EXT; break;
+              /* _ktxCheckHeader should have caught this. */
+              default: assert(KTX_TRUE);
+            }
+        }
+        cbData.numLayers = This->numLayers;
+    } else {
+        if (This->numFaces == 6) {
+            /* _ktxCheckHeader should have caught this. */
+            assert(This->numDimensions == 2);
+            target = GL_TEXTURE_CUBE_MAP;
+        } else {
+            switch (This->numDimensions) {
+              case 1: target = GL_TEXTURE_1D; break;
+              case 2: target = GL_TEXTURE_2D; break;
+              case 3: target = GL_TEXTURE_3D; break;
+              /* _ktxCheckHeader shold have caught this. */
+              default: assert(KTX_TRUE);
+            }
+        }
+        cbData.numLayers = 0;
+    }
+    
+    if (target == GL_TEXTURE_1D &&
+        ((This->isCompressed && (pfGlCompressedTexImage1D == NULL)) ||
+         (!This->isCompressed && (pfGlTexImage1D == NULL))))
+    {
+        return KTX_UNSUPPORTED_TEXTURE_TYPE;
+    }
+    
+    /* Reject 3D texture if unsupported. */
+    if (target == GL_TEXTURE_3D &&
+        ((This->isCompressed && (pfGlCompressedTexImage3D == NULL)) ||
+         (!This->isCompressed && (pfGlTexImage3D == NULL))))
+    {
+        return KTX_UNSUPPORTED_TEXTURE_TYPE;
+    }
+    
+    /* Reject cube map arrays if not supported. */
+    if (target == GL_TEXTURE_CUBE_MAP_ARRAY && !supportsCubeMapArrays) {
+        return KTX_UNSUPPORTED_TEXTURE_TYPE;
+    }
+    
+    /* XXX Need to reject other array textures & cube maps if not supported. */
+    
+    switch (dimensions) {
+      case 1:
+        iterCb = This->isCompressed
+                  ? compressedTexImage1DCallback : texImage1DCallback;
+        break;
+      case 2:
+        iterCb = This->isCompressed
+                  ? compressedTexImage2DCallback : texImage2DCallback;
+            break;
+      case 3:
+        iterCb = This->isCompressed
+                  ? compressedTexImage3DCallback : texImage3DCallback;
+        break;
+      default:
+            assert(KTX_TRUE);
+    }
+   
+    glBindTexture(target, texname);
+    
+    // Prefer glGenerateMipmaps over GL_GENERATE_MIPMAP
+    if (This->generateMipmaps && (pfGlGenerateMipmap == NULL)) {
+        glTexParameteri(target, GL_GENERATE_MIPMAP, GL_TRUE);
+    }
+#ifdef GL_TEXTURE_MAX_LEVEL
+    if (!This->generateMipmaps)
+        glTexParameteri(target, GL_TEXTURE_MAX_LEVEL, This->numLevels - 1);
+#endif
+
+    if (target == GL_TEXTURE_CUBE_MAP) {
+        cbData.glTarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X;
+    } else {
+        cbData.glTarget = target;
+    }
+    
+    cbData.glInternalformat = This->glInternalformat;
+    cbData.glFormat = This->glFormat;
+    if (!This->isCompressed) {
+#if SUPPORT_LEGACY_FORMAT_CONVERSION
+        // If sized legacy formats are supported there is no need to convert.
+        // If only unsized formats are supported, there is no point in
+        // converting as the modern formats aren't supported either.
+        if (sizedFormats == _NON_LEGACY_FORMATS && supportsSwizzle) {
+            convertFormat(target, &cbData.glFormat, &cbData.glInternalformat);
+        } else if (sizedFormats == _NO_SIZED_FORMATS)
+            cbData.glInternalformat = This->glBaseInternalformat;
+#else
+        // When no sized formats are supported, or legacy sized formats are not
+        // supported, must change internal format.
+        if (sizedFormats == _NO_SIZED_FORMATS
+            || (!(sizedFormats & _LEGACY_FORMATS) &&
+                (This->glBaseInternalformat == GL_ALPHA
+                || This->glBaseInternalformat == GL_LUMINANCE
+                || This->glBaseInternalformat == GL_LUMINANCE_ALPHA
+                || This->glBaseInternalformat == GL_INTENSITY))) {
+            cbData.glInternalformat = This->glBaseInternalformat;
+        }
+#endif
+    }
+
+    if (ktxTexture_isActiveStream(This))
+        result = ktxTexture_IterateLoadLevelFaces(This, iterCb, &cbData);
+    else
+        result = ktxTexture_IterateLevelFaces(This, iterCb, &cbData);
+
+    /* GL errors are the only reason for failure. */
+    if (result != KTX_SUCCESS && cbData.glError != GL_NO_ERROR) {
+        if (pGlerror)
+            *pGlerror = cbData.glError;
+    }
+
+    /* restore previous GL state */
+    if (previousUnpackAlignment != KTX_GL_UNPACK_ALIGNMENT) {
+        glPixelStorei(GL_UNPACK_ALIGNMENT, previousUnpackAlignment);
+    }
+
+    if (result == KTX_SUCCESS)
+    {
+        // Prefer glGenerateMipmaps over GL_GENERATE_MIPMAP
+        if (This->generateMipmaps && pfGlGenerateMipmap) {
+            pfGlGenerateMipmap(target);
+        }
+        *pTarget = target;
+        if (pTexture) {
+            *pTexture = texname;
+        }
+    } else if (!texnameUser) {
+        glDeleteTextures(1, &texname);
+    }
+    return result;
+}
+/* [loadGLTexture] */
+
+/**
+ * @~English
+ * @deprecated Use ktxTexture_CreateFromStdioStream() and ktxTexture_GLUpload().
+ * @brief Create a GL texture object from KTX data in a stdio FILE stream.
+ *
+ * Sets the texture object's GL_TEXTURE_MAX_LEVEL parameter according to the
+ * number of levels in the ktxStream, provided the library has been compiled
+ * with a version of gl.h where GL_TEXTURE_MAX_LEVEL is defined.
+ *
+ * Unpacks compressed GL_ETC1_RGB8_OES and GL_ETC2_* format textures in
+ * software when the format is not supported by the GL context, provided the
+ * library has been compiled with SUPPORT_SOFTWARE_ETC_UNPACK defined as 1.
+ *
+ * Also converts texture with legacy formats to their modern equivalents
+ * when the format is not supported by the GL context, provided the library
+ * has been compiled with SUPPORT_LEGACY_FORMAT_CONVERSION defined as 1.
+ *
+ * @param[in] file         stdio stream FILE pointer
+ * @param[in,out] pTexture name of the GL texture object to load. If NULL or if
+ *                         <tt>*pTexture == 0</tt> the function will generate
+ *                         a texture name. The function binds either the
+ *                         generated name or the name given in @p *pTexture
+ *                         to the texture target returned in @p *pTarget,
+ *                         before loading the texture data. If @p pTexture
+ *                         is not NULL and a name was generated, the generated
+ *                         name will be returned in *pTexture.
+ * @param[out] pTarget     @p *pTarget is set to the texture target used. The
+ *                         target is chosen based on the file contents.
+ * @param[out] pDimensions If @p pDimensions is not NULL, the width, height and
+ *                         depth of the texture's base level are returned in
+ *                         the fields of the KTX_dimensions structure to which
+ *                         it points.
+ * @param[out] pIsMipmapped
+ *                         If @p pIsMipmapped is not NULL, @p *pIsMipmapped is
+ *                         set to GL_TRUE if the KTX texture is mipmapped,
+ *                         GL_FALSE otherwise.
+ * @param[out] pGlerror    @p *pGlerror is set to the value returned by
+ *                         glGetError when this function returns the error
+ *                         KTX_GL_ERROR. glerror can be NULL.
+ * @param[in,out] pKvdLen  If not NULL, @p *pKvdLen is set to the number of
+ *                         bytes of key-value data pointed at by @p *ppKvd.
+ *                         Must not be NULL, if @p ppKvd is not NULL.
+ * @param[in,out] ppKvd    If not NULL, @p *ppKvd is set to the point to a
+ *                         block of memory containing key-value data read from
+ *                         the file. The application is responsible for freeing
+ *                         the memory.
+ *
+ * @return  KTX_SUCCESS on success, other KTX_* enum values on error.
+ *
+ * @exception KTX_INVALID_VALUE @p target is @c NULL or the size of a mip
+ *                              level is greater than the size of the
+ *                              preceding level.
+ * @exception KTX_INVALID_OPERATION @p ppKvd is not NULL but pKvdLen is NULL.
+ * @exception KTX_UNEXPECTED_END_OF_FILE the file does not contain the
+ *                                       expected amount of data.
+ * @exception KTX_OUT_OF_MEMORY Sufficient memory could not be allocated for the
+ *                              underlying ktxTexture object or to store the
+ *                              requested key-value data.
+ * @exception KTX_GL_ERROR      A GL error was raised by glBindTexture,
+ *                              glGenTextures or gl*TexImage*. The GL error
+ *                              will be returned in @p *glerror, if glerror
+ *                              is not @c NULL.
+ * @exception KTX_UNSUPPORTED_TEXTURE_TYPE The type of texture is not supported
+ *                                         by the current OpenGL context.
+ */
+KTX_error_code
+ktxLoadTextureF(FILE* file, GLuint* pTexture, GLenum* pTarget,
+                KTX_dimensions* pDimensions, GLboolean* pIsMipmapped,
+                GLenum* pGlerror,
+                unsigned int* pKvdLen, unsigned char** ppKvd)
+{
+    ktxTexture* texture;
+    KTX_error_code result = KTX_SUCCESS;
+    
+    if (ppKvd != NULL && pKvdLen == NULL)
+        return KTX_INVALID_VALUE;
+
+    result = ktxTexture_CreateFromStdioStream(file,
+                                              KTX_TEXTURE_CREATE_RAW_KVDATA_BIT,
+                                              &texture);
+    if (result != KTX_SUCCESS)
+        return result;
+
+    result = ktxTexture_GLUpload(texture, pTexture, pTarget, pGlerror);
+    
+    if (result == KTX_SUCCESS) {
+        if (ppKvd != NULL) {
+            *ppKvd = texture->kvData;
+            *pKvdLen = texture->kvDataLen;
+            /* Remove to avoid it being freed when texture is destroyed. */
+            texture->kvData = NULL;
+            texture->kvDataLen = 0;
+        }
+        if (pDimensions) {
+            pDimensions->width = texture->baseWidth;
+            pDimensions->height = texture->baseHeight;
+            pDimensions->depth = texture->baseDepth;
+        }
+        if (pIsMipmapped) {
+            if (texture->generateMipmaps || texture->numLevels > 1)
+                *pIsMipmapped = GL_TRUE;
+            else
+                *pIsMipmapped = GL_FALSE;
+        }
+    }
+
+    ktxTexture_Destroy(texture);
+
+    return result;
+}
+
+/**
+ * @~English
+ * @deprecated Use ktxTexture_CreateFromNamedFile() and ktxTexture_GLUpload().
+ * @brief Create a GL texture object from KTX data in a named file on disk.
+ *
+ * @param[in] filename      pointer to a C string that contains the path of
+ *                          the file to load.
+ * @param[in,out] pTexture  name of the GL texture object to load. See
+ *                          ktxLoadTextureF() for details.
+ * @param[out] pTarget      @p *pTarget is set to the texture target used. See
+ *                          ktxLoadTextureF() for details.
+ * @param[out] pDimensions  @p the texture's base level width depth and height
+ *                          are returned in structure to which this points.
+ *                          See ktxLoadTextureF() for details.
+ * @param[out] pIsMipmapped @p pIsMipMapped is set to indicate if the loaded
+ *                          texture is mipmapped. See ktxLoadTextureF() for
+ *                          details.
+ * @param[out] pGlerror     @p *pGlerror is set to the value returned by
+ *                          glGetError when this function returns the error
+ *                          KTX_GL_ERROR. glerror can be NULL.
+ * @param[in,out] pKvdLen   If not NULL, @p *pKvdLen is set to the number of
+ *                          bytes of key-value data pointed at by @p *ppKvd.
+ *                          Must not be NULL, if @p ppKvd is not NULL.
+ * @param[in,out] ppKvd     If not NULL, @p *ppKvd is set to the point to a
+ *                          block of memory containing key-value data read from
+ *                          the file. The application is responsible for freeing
+ *                          the memory.
+ *
+ * @return  KTX_SUCCESS on success, other KTX_* enum values on error.
+ *
+ * @exception KTX_FILE_OPEN_FAILED  The specified file could not be opened.
+ * @exception KTX_INVALID_VALUE     See ktxLoadTextureF() for causes.
+ * @exception KTX_INVALID_OPERATION See ktxLoadTextureF() for causes.
+ * @exception KTX_UNEXPECTED_END_OF_FILE See ktxLoadTextureF() for causes.
+ * @exception KTX_GL_ERROR          See ktxLoadTextureF() for causes.
+ * @exception KTX_UNSUPPORTED_TEXTURE_TYPE See ktxLoadTextureF() for causes.
+ */
+KTX_error_code
+ktxLoadTextureN(const char* const filename, GLuint* pTexture, GLenum* pTarget,
+                KTX_dimensions* pDimensions, GLboolean* pIsMipmapped,
+                GLenum* pGlerror,
+                unsigned int* pKvdLen, unsigned char** ppKvd)
+{
+    KTX_error_code result;
+
+    FILE* file = fopen(filename, "rb");
+    if (file) {
+        result = ktxLoadTextureF(file, pTexture, pTarget, pDimensions,
+                                 pIsMipmapped, pGlerror, pKvdLen, ppKvd);
+        fclose(file);
+    } else
+        result = KTX_FILE_OPEN_FAILED;
+
+    return result;
+}
+
+/**
+ * @~English
+ * @deprecated Use ktxTexture_CreateFromMemory() and ktxTexture_GLUpload().
+ * @brief Create a GL texture object from KTX formatted data in memory.
+ *
+ * @param[in] bytes         pointer to the array of bytes containing
+ *                          the KTX data to load.
+ * @param[in] size          size of the memory array containing the
+ *                          KTX format data.
+ * @param[in,out] pTexture  name of the GL texture object to load. See
+ *                          ktxLoadTextureF() for details.
+ * @param[out] pTarget      @p *pTarget is set to the texture target used. See
+ *                          ktxLoadTextureF() for details.
+ * @param[out] pDimensions  @p the texture's base level width depth and height
+ *                          are returned in structure to which this points.
+ *                          See ktxLoadTextureF() for details.
+ * @param[out] pIsMipmapped @p *pIsMipMapped is set to indicate if the loaded
+ *                          texture is mipmapped. See ktxLoadTextureF() for
+ *                          details.
+ * @param[out] pGlerror     @p *pGlerror is set to the value returned by
+ *                          glGetError when this function returns the error
+ *                          KTX_GL_ERROR. glerror can be NULL.
+ * @param[in,out] pKvdLen   If not NULL, @p *pKvdLen is set to the number of
+ *                          bytes of key-value data pointed at by @p *ppKvd.
+ *                          Must not be NULL, if @p ppKvd is not NULL.
+ * @param[in,out] ppKvd     If not NULL, @p *ppKvd is set to the point to a
+ *                          block of memory containing key-value data read from
+ *                          the file. The application is responsible for freeing
+ *                          the memory.
+ *
+ * @return  KTX_SUCCESS on success, other KTX_* enum values on error.
+ *
+ * @exception KTX_FILE_OPEN_FAILED  The specified memory could not be opened as
+ *                                  a file.
+ * @exception KTX_INVALID_VALUE     See ktxLoadTextureF() for causes.
+ * @exception KTX_INVALID_OPERATION See ktxLoadTextureF() for causes.
+ * @exception KTX_UNEXPECTED_END_OF_FILE See ktxLoadTextureF() for causes.
+ *
+ * @exception KTX_GL_ERROR          See ktxLoadTextureF() for causes.
+ * @exception KTX_UNSUPPORTED_TEXTURE_TYPE See ktxLoadTextureF() for causes.
+ */
+KTX_error_code
+ktxLoadTextureM(const void* bytes, GLsizei size, GLuint* pTexture,
+                GLenum* pTarget, KTX_dimensions* pDimensions,
+                GLboolean* pIsMipmapped, GLenum* pGlerror,
+                unsigned int* pKvdLen, unsigned char** ppKvd)
+{
+    ktxTexture* texture;
+    KTX_error_code result = KTX_SUCCESS;
+
+    if (ppKvd != NULL && pKvdLen == NULL)
+        return KTX_INVALID_VALUE;
+    
+    result = ktxTexture_CreateFromMemory(bytes, size,
+                                         KTX_TEXTURE_CREATE_RAW_KVDATA_BIT,
+                                         &texture);
+
+    if (result != KTX_SUCCESS)
+        return result;
+
+    result = ktxTexture_GLUpload(texture, pTexture, pTarget, pGlerror);
+    if (result == KTX_SUCCESS) {
+        if (ppKvd != NULL) {
+            *ppKvd = texture->kvData;
+            *pKvdLen = texture->kvDataLen;
+            /* Remove to avoid it being freed when texture is destroyed. */
+            texture->kvData = NULL;
+            texture->kvDataLen = 0;
+        }
+        if (pDimensions) {
+            pDimensions->width = texture->baseWidth;
+            pDimensions->height = texture->baseHeight;
+            pDimensions->depth = texture->baseDepth;
+        }
+        if (pIsMipmapped) {
+            if (texture->generateMipmaps || texture->numLevels > 1)
+                *pIsMipmapped = GL_TRUE;
+            else
+                *pIsMipmapped = GL_FALSE;
+        }
+    }
+    
+    ktxTexture_Destroy(texture);
+
+    return result;
+}
+
+/** @} */
diff --git a/external/Vulkan/external/ktx/lib/hashlist.c b/external/Vulkan/external/ktx/lib/hashlist.c
new file mode 100644
index 0000000000000000000000000000000000000000..2afb20c67041494842b123aa5f0810f33104ea2e
--- /dev/null
+++ b/external/Vulkan/external/ktx/lib/hashlist.c
@@ -0,0 +1,344 @@
+/* -*- tab-width: 4; -*- */
+/* vi: set sw=2 ts=4 expandtab: */
+
+/*
+ * Copyright (c) 2010-2018 The Khronos Group Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @internal
+ * @file hashlist.c
+ * @~English
+ *
+ * @brief Functions for creating and using a hash list of key-value
+ *        pairs.
+ *
+ * @author Mark Callow, HI Corporation
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+// This is to avoid compile warnings. strlen is defined as returning
+// size_t and is used by the uthash macros. This avoids having to
+// make changes to uthash and a bunch of casts in this file. The
+// casts would be required because the key and value lengths in KTX
+// are specified as 4 byte quantities so we can't change _keyAndValue
+// below to use size_t.
+#define strlen(x) ((unsigned int)strlen(x))
+
+#include "uthash.h"
+
+#include "ktx.h"
+#include "ktxint.h"
+
+
+/**
+ * @internal
+ * @struct ktxKVListEntry
+ * @brief Hash list entry structure
+ */
+typedef struct ktxKVListEntry {
+    unsigned int keyLen;    /*!< Length of the key */
+    char* key;              /*!< Pointer to key string */
+    unsigned int valueLen;  /*!< Length of the value */
+    void* value;            /*!< Pointer to the value */
+    UT_hash_handle hh;      /*!< handle used by UT hash */
+} ktxKVListEntry;
+
+
+/**
+ * @memberof ktxHashList @public
+ * @~English
+ * @brief Construct an empty hash list for storing key-value pairs.
+ *
+ * @param [in] pHead pointer to the location to write the list head.
+ */
+void
+ktxHashList_Construct(ktxHashList* pHead)
+{
+    *pHead = NULL;
+}
+
+
+/**
+ * @memberof ktxHashList @public
+ * @~English
+ * @brief Destruct a hash list.
+ *
+ * All memory associated with the hash list's keys and values
+ * is freed.
+ *
+ * @param [in] pHead pointer to the hash list to be destroyed.
+ */
+void
+ktxHashList_Destruct(ktxHashList* pHead)
+{
+    ktxKVListEntry* kv;
+    ktxKVListEntry* head = *pHead;
+    
+    for(kv = head; kv != NULL;) {
+        ktxKVListEntry* tmp = (ktxKVListEntry*)kv->hh.next;
+        HASH_DELETE(hh, head, kv);
+        free(kv);
+        kv = tmp;
+    }
+}
+/**
+ * @memberof ktxHashList @public
+ * @~English
+ * @brief Create an empty hash list for storing key-value pairs.
+ *
+ * @param [in,out] ppHl address of a variable in which to set a pointer to
+ *                 the newly created hash list.
+ *
+ * @return KTX_SUCCESS or one of the following error codes.
+ * @exception KTX_OUT_OF_MEMORY if not enough memory.
+ */
+KTX_error_code
+ktxHashList_Create(ktxHashList** ppHl)
+{
+    ktxHashList* hl = (ktxHashList*)malloc(sizeof (ktxKVListEntry*));
+    if (hl == NULL)
+        return KTX_OUT_OF_MEMORY;
+    
+    ktxHashList_Construct(hl);
+    *ppHl = hl;
+    return KTX_SUCCESS;
+}
+
+
+/**
+ * @memberof ktxHashList @public
+ * @~English
+ * @brief Destroy a hash list.
+ *
+ * All memory associated with the hash list's keys and values
+ * is freed. The hash list is also freed.
+ *
+ * @param [in] pHead pointer to the hash list to be destroyed.
+ */
+void
+ktxHashList_Destroy(ktxHashList* pHead)
+{
+    ktxHashList_Destruct(pHead);
+    free(pHead);
+}
+
+
+/**
+ * @memberof ktxHashList @public
+ * @~English
+ * @brief Add a key value pair to a hash list.
+ *
+ * @param [in] pHead    pointer to the head of the target hash list.
+ * @param [in] key      pointer to the UTF8 NUL-terminated string to be used as the key.
+ * @param [in] valueLen the number of bytes of data in @p value.
+ * @param [in] value    pointer to the bytes of data constituting the value.
+ *
+ * @return KTX_SUCCESS or one of the following error codes.
+ * @exception KTX_INVALID_VALUE if @p This, @p key or @p value are NULL, @p key is an
+ *            empty string or @p valueLen == 0.
+ */
+KTX_error_code
+ktxHashList_AddKVPair(ktxHashList* pHead, const char* key, unsigned int valueLen, const void* value)
+{
+    if (pHead && key && value && valueLen != 0) {
+        unsigned int keyLen = (unsigned int)strlen(key) + 1;
+        /* ktxKVListEntry* head = *(ktxKVListEntry**)This; */
+        ktxKVListEntry* kv;
+
+        if (keyLen == 1)
+            return KTX_INVALID_VALUE;   /* Empty string */
+
+        /* Allocate all the memory as a block */
+        kv = (ktxKVListEntry*)malloc(sizeof(ktxKVListEntry) + keyLen + valueLen);
+        /* Put key first */
+        kv->key = (char *)kv + sizeof(ktxKVListEntry);
+        kv->keyLen = keyLen;
+        /* then value */
+        kv->value = kv->key + keyLen;
+        kv->valueLen = valueLen;
+        memcpy(kv->key, key, keyLen);
+        memcpy(kv->value, value, valueLen);
+
+        HASH_ADD_KEYPTR( hh, *pHead, kv->key, kv->keyLen-1, kv);
+        return KTX_SUCCESS;
+    } else
+        return KTX_INVALID_VALUE;
+}
+
+
+/**
+ * @memberof ktxHashList @public
+ * @~English
+ * @brief Looks up a key in a hash list and returns the value.
+ *
+ * @param [in]     pHead        pointer to the head of the target hash list.
+ * @param [in]     key          pointer to a UTF8 NUL-terminated string to find.
+ * @param [in,out] pValueLen    @p *pValueLen is set to the number of bytes of
+ *                              data in the returned value.
+ * @param [in,out] ppValue      @p *ppValue is set to the point to the value for
+ *                              @p key.
+ *
+ * @return KTX_SUCCESS or one of the following error codes.
+ *
+ * @exception KTX_INVALID_VALUE if @p This, @p key or @p pValueLen or @p ppValue
+ *                              is NULL.
+ * @exception KTX_NOT_FOUND     an entry matching @p key was not found.
+ */
+KTX_error_code
+ktxHashList_FindValue(ktxHashList *pHead, const char* key, unsigned int* pValueLen, void** ppValue)
+{
+    if (pHead && key && pValueLen && ppValue) {
+        ktxKVListEntry* kv;
+        /* ktxKVListEntry* head = *(ktxKVListEntry**)This; */
+
+        HASH_FIND_STR( *pHead, key, kv );  /* kv: output pointer */
+
+        if (kv) {
+            *pValueLen = kv->valueLen;
+            *ppValue = kv->value;
+            return KTX_SUCCESS;
+        } else
+            return KTX_NOT_FOUND;
+    } else
+        return KTX_INVALID_VALUE;
+}
+
+
+/**
+ * @memberof ktxHashList @public
+ * @~English
+ * @brief Serialize a hash list to a block of data suitable for writing
+ *        to a file.
+ *
+ * The caller is responsible for freeing the data block returned by this
+ * function.
+ *
+ * @param [in]     pHead        pointer to the head of the target hash list.
+ * @param [in,out] pKvdLen      @p *pKvdLen is set to the number of bytes of
+ *                              data in the returned data block.
+ * @param [in,out] ppKvd        @p *ppKvd is set to the point to the block of
+ *                              memory containing the serialized data.
+ *
+ * @return KTX_SUCCESS or one of the following error codes.
+ *
+ * @exception KTX_INVALID_VALUE if @p This, @p pKvdLen or @p ppKvd is NULL.
+ * @exception KTX_OUT_OF_MEMORY there was not enough memory to serialize the
+ *                              data.
+ */
+KTX_error_code
+ktxHashList_Serialize(ktxHashList* pHead,
+                      unsigned int* pKvdLen, unsigned char** ppKvd)
+{
+
+    if (pHead && pKvdLen && ppKvd) {
+        ktxKVListEntry* kv;
+        unsigned int bytesOfKeyValueData = 0;
+        unsigned int keyValueLen;
+        unsigned char* sd;
+        char padding[4] = {0, 0, 0, 0};
+
+        for (kv = *pHead; kv != NULL; kv = kv->hh.next) {
+            /* sizeof(sd) is to make space to write keyAndValueByteSize */
+            keyValueLen = kv->keyLen + kv->valueLen + sizeof(ktx_uint32_t);
+            /* Add valuePadding */
+            keyValueLen = _KTX_PAD4(keyValueLen);
+            bytesOfKeyValueData += keyValueLen;
+        }
+        sd = malloc(bytesOfKeyValueData);
+        if (!sd)
+            return KTX_OUT_OF_MEMORY;
+
+        *pKvdLen = bytesOfKeyValueData;
+        *ppKvd = sd;
+
+        for (kv = *pHead; kv != NULL; kv = kv->hh.next) {
+            int padLen;
+
+            keyValueLen = kv->keyLen + kv->valueLen;
+            *(ktx_uint32_t*)sd = keyValueLen;
+            sd += sizeof(ktx_uint32_t);
+            memcpy(sd, kv->key, kv->keyLen);
+            sd += kv->keyLen;
+            memcpy(sd, kv->value, kv->valueLen);
+            sd += kv->valueLen;
+            padLen = _KTX_PAD4_LEN(keyValueLen);
+            memcpy(sd, padding, padLen);
+            sd += padLen;
+        }
+        return KTX_SUCCESS;
+    } else
+        return KTX_INVALID_VALUE;
+}
+
+
+/**
+ * @memberof ktxHashList @public
+ * @~English
+ * @brief Construct a hash list from a block of serialized key-value
+ *        data read from a file.
+ * @note The bytes of the 32-bit key-value lengths within the serialized data
+ *       are expected to be in native endianness.
+ *
+ * @param [in]      pHead       pointer to the head of the target hash list.
+ * @param [in]      kvdLen      the length of the serialized key-value data.
+ * @param [in]      pKvd        pointer to the serialized key-value data.
+ *                              table.
+ *
+ * @return KTX_SUCCESS or one of the following error codes.
+ *
+ * @exception KTX_INVALID_OPERATION if @p pHead does not point to an empty list.
+ * @exception KTX_INVALID_VALUE if @p pKvd or @p pHt is NULL or kvdLen == 0.
+ * @exception KTX_OUT_OF_MEMORY there was not enough memory to create the hash
+ *                              table.
+ */
+KTX_error_code
+ktxHashList_Deserialize(ktxHashList* pHead, unsigned int kvdLen, void* pKvd)
+{
+    char* src = pKvd;
+    KTX_error_code result;
+
+    if (kvdLen == 0 || pKvd == NULL || pHead == NULL)
+        return KTX_INVALID_VALUE;
+    
+    if (*pHead != NULL)
+        return KTX_INVALID_OPERATION;
+
+    result = KTX_SUCCESS;
+    while (result == KTX_SUCCESS && src < (char *)pKvd + kvdLen) {
+        char* key;
+        unsigned int keyLen;
+        void* value;
+        ktx_uint32_t keyAndValueByteSize = *((ktx_uint32_t*)src);
+
+        src += sizeof(keyAndValueByteSize);
+        key = src;
+        keyLen = (unsigned int)strlen(key) + 1;
+        value = key + keyLen;
+
+        result = ktxHashList_AddKVPair(pHead, key, keyAndValueByteSize - keyLen,
+                                       value);
+        if (result == KTX_SUCCESS) {
+            src += _KTX_PAD4(keyAndValueByteSize);
+        }
+    }
+    return result;
+}
+
+
diff --git a/external/Vulkan/external/ktx/lib/hashtable.c b/external/Vulkan/external/ktx/lib/hashtable.c
new file mode 100644
index 0000000000000000000000000000000000000000..e9a42f1746756662066cab56fecedcf84316920c
--- /dev/null
+++ b/external/Vulkan/external/ktx/lib/hashtable.c
@@ -0,0 +1,97 @@
+/* -*- tab-width: 4; -*- */
+/* vi: set sw=2 ts=4 expandtab: */
+
+/**
+ * @internal
+ * @file hashtable.c
+ * @~English
+ *
+ * @brief Functions for backward compatibility with libktx v1
+ *        hashtable API.
+ *
+ * @author Mark Callow, www.edgewise-consulting.com
+ */
+
+/*
+ * ©2018 Mark Callow.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#include "ktx.h"
+
+/**
+ * @memberof KTX_hash_table
+ * @~English
+ * @deprecated Use ktxHashList_Create().
+ */
+KTX_hash_table
+ktxHashTable_Create(void) {
+    ktxHashList* hl;
+    (void)ktxHashList_Create(&hl);
+    return hl;
+}
+
+/**
+ * @memberof KTX_hash_table
+ * @~English
+ * @deprecated Use ktxHashList_Serialize().
+ * @brief Serializes the hash table to a block of memory suitable for
+ *        writing to a KTX file.
+ */
+KTX_error_code
+ktxHashTable_Serialize(KTX_hash_table This,
+                      unsigned int* kvdLen, unsigned char** kvd)
+{
+    return ktxHashList_Serialize(This, kvdLen, kvd);
+}
+
+/**
+ * @memberof KTX_hash_table
+ * @deprecated Use ktxHashList_Deserialize().
+ * @~English
+ * @brief Create a new hash table from a block of serialized key-value
+ *        data read from a file.
+ *
+ * The caller is responsible for freeing the returned hash table.
+ *
+ * @note The bytes of the 32-bit key-value lengths within the serialized data
+ *       are expected to be in native endianness.
+ *
+ * @param[in]        kvdLen      the length of the serialized key-value data.
+ * @param[in]        pKvd        pointer to the serialized key-value data.
+ * @param[in,out]    pHt         @p *pHt is set to point to the created hash
+ *                               table.
+ *
+ * @return KTX_SUCCESS or one of the following error codes.
+ *
+ * @exception KTX_INVALID_VALUE if @p pKvd or @p pHt is NULL or kvdLen == 0.
+ * @exception KTX_OUT_OF_MEMORY there was not enough memory to create the hash
+ *                              table.
+ */
+KTX_error_code
+ktxHashTable_Deserialize(unsigned int kvdLen, void* pKvd, KTX_hash_table* pHt)
+{
+    ktxHashList* pHl;
+    KTX_error_code result;
+    result = ktxHashList_Create(&pHl);
+    if (result != KTX_SUCCESS)
+        return result;
+
+    result = ktxHashList_Deserialize(pHl, kvdLen, pKvd);
+    if (result == KTX_SUCCESS)
+        *pHt = pHl;
+    return result;
+}  
+
diff --git a/external/Vulkan/external/ktx/lib/ktxgl.h b/external/Vulkan/external/ktx/lib/ktxgl.h
new file mode 100644
index 0000000000000000000000000000000000000000..c4674d4c1babd34bd844a5194172d25d66b301e5
--- /dev/null
+++ b/external/Vulkan/external/ktx/lib/ktxgl.h
@@ -0,0 +1,228 @@
+/* -*- tab-width: 4; -*- */
+/* vi: set sw=2 ts=4 expandtab: */
+
+/* $Id: 94277b50a14992fc9e5a6e011ef5f9cdf28f0d89 $ */
+
+/*
+ * Copyright (c) 2010-2018 The Khronos Group Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+/* 
+ * Author: Mark Callow from original code by Georg Kolling
+ */
+
+#ifndef KTXGL_H
+#define KTXGL_H
+
+#ifndef SUPPORT_LEGACY_FORMAT_CONVERSION
+  #if KTX_OPENGL
+    #define SUPPORT_LEGACY_FORMAT_CONVERSION 1
+  #elif KTX_OPENGL_ES1
+    /* ES1, ES2 & ES3 support the legacy formats */
+    #define SUPPORT_LEGACY_FORMAT_CONVERSION 0
+  #endif
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * These defines are needed to compile the KTX library. When
+ * these things are not available in the GL header in use at
+ * compile time, the library provides its own support, handles
+ * the expected run-time errors or just needs the token value.
+ */
+#ifndef GL_LUMINANCE
+#define GL_ALPHA                        0x1906
+#define GL_LUMINANCE                    0x1909
+#define GL_LUMINANCE_ALPHA              0x190A
+#endif
+#ifndef GL_INTENSITY
+#define GL_INTENSITY                    0x8049
+#endif
+#if SUPPORT_LEGACY_FORMAT_CONVERSION
+/* For loading legacy KTX files. */
+#ifndef GL_LUMINANCE4
+#define GL_ALPHA4                       0x803B
+#define GL_ALPHA8                       0x803C
+#define GL_ALPHA12                      0x803D
+#define GL_ALPHA16                      0x803E
+#define GL_LUMINANCE4                   0x803F
+#define GL_LUMINANCE8                   0x8040
+#define GL_LUMINANCE12                  0x8041
+#define GL_LUMINANCE16                  0x8042
+#define GL_LUMINANCE4_ALPHA4            0x8043
+#define GL_LUMINANCE6_ALPHA2            0x8044
+#define GL_LUMINANCE8_ALPHA8            0x8045
+#define GL_LUMINANCE12_ALPHA4           0x8046
+#define GL_LUMINANCE12_ALPHA12          0x8047
+#define GL_LUMINANCE16_ALPHA16          0x8048
+#endif
+#ifndef GL_INTENSITY4
+#define GL_INTENSITY4                   0x804A
+#define GL_INTENSITY8                   0x804B
+#define GL_INTENSITY12                  0x804C
+#define GL_INTENSITY16                  0x804D
+#endif
+#ifndef GL_SLUMINANCE
+#define GL_SLUMINANCE_ALPHA             0x8C44
+#define GL_SLUMINANCE8_ALPHA8           0x8C45
+#define GL_SLUMINANCE                   0x8C46
+#define GL_SLUMINANCE8                  0x8C47
+#endif
+#endif /* SUPPORT_LEGACY_FORMAT_CONVERSION */
+#ifndef GL_TEXTURE_1D
+#define GL_TEXTURE_1D                   0x0DE0
+#endif
+#ifndef GL_TEXTURE_3D
+#define GL_TEXTURE_3D                   0x806F
+#endif
+#ifndef GL_TEXTURE_CUBE_MAP
+#define GL_TEXTURE_CUBE_MAP             0x8513
+#define GL_TEXTURE_CUBE_MAP_POSITIVE_X  0x8515
+#endif
+#ifndef GL_TEXTURE_CUBE_MAP_ARRAY
+#define GL_TEXTURE_CUBE_MAP_ARRAY       0x9009
+#endif
+/* from GL_EXT_texture_array */
+#ifndef GL_TEXTURE_1D_ARRAY_EXT
+#define GL_TEXTURE_1D_ARRAY_EXT         0x8C18
+#define GL_TEXTURE_2D_ARRAY_EXT         0x8C1A
+#endif
+#ifndef GL_GENERATE_MIPMAP
+#define GL_GENERATE_MIPMAP              0x8191
+#endif
+
+/* For writer.c */
+#if !defined(GL_BGR)
+#define GL_BGR                          0x80E0
+#define GL_BGRA                         0x80E1
+#endif
+#if !defined(GL_RED_INTEGER)
+#define GL_RED_INTEGER                  0x8D94
+#define GL_RGB_INTEGER                  0x8D98
+#define GL_RGBA_INTEGER                 0x8D99
+#endif
+#if !defined(GL_GREEN_INTEGER)
+#define GL_GREEN_INTEGER                0x8D95
+#define GL_BLUE_INTEGER                 0x8D96
+#endif
+#if !defined(GL_ALPHA_INTEGER)
+#define GL_ALPHA_INTEGER                0x8D97
+#endif
+#if !defined (GL_BGR_INTEGER)
+#define GL_BGR_INTEGER                  0x8D9A
+#define GL_BGRA_INTEGER                 0x8D9B
+#endif
+#if !defined(GL_INT)
+#define GL_INT 0x1404
+#define GL_UNSIGNED_INT 0x1405
+#endif
+#if !defined(GL_HALF_FLOAT)
+typedef unsigned short GLhalf;
+#define GL_HALF_FLOAT                   0x140B
+#endif
+#if !defined(GL_UNSIGNED_BYTE_3_3_2)
+#define GL_UNSIGNED_BYTE_3_3_2          0x8032
+#define GL_UNSIGNED_INT_8_8_8_8         0x8035
+#define GL_UNSIGNED_INT_10_10_10_2      0x8036
+#endif
+#if !defined(GL_UNSIGNED_BYTE_2_3_3_REV)
+#define GL_UNSIGNED_BYTE_2_3_3_REV      0x8362
+#define GL_UNSIGNED_SHORT_5_6_5         0x8363
+#define GL_UNSIGNED_SHORT_5_6_5_REV     0x8364
+#define GL_UNSIGNED_SHORT_4_4_4_4_REV   0x8365
+#define GL_UNSIGNED_SHORT_1_5_5_5_REV   0x8366
+#define GL_UNSIGNED_INT_8_8_8_8_REV     0x8367
+#define GL_UNSIGNED_INT_2_10_10_10_REV  0x8368
+#endif
+#if !defined(GL_UNSIGNED_INT_24_8)
+#define GL_DEPTH_STENCIL                0x84F9
+#define GL_UNSIGNED_INT_24_8            0x84FA
+#endif
+#if !defined(GL_UNSIGNED_INT_5_9_9_9_REV)
+#define GL_UNSIGNED_INT_5_9_9_9_REV     0x8C3E
+#endif
+#if !defined(GL_UNSIGNED_INT_10F_11F_11F_REV)
+#define GL_UNSIGNED_INT_10F_11F_11F_REV 0x8C3B
+#endif
+#if !defined (GL_FLOAT_32_UNSIGNED_INT_24_8_REV)
+#define GL_FLOAT_32_UNSIGNED_INT_24_8_REV   0x8DAD
+#endif
+
+#ifndef GL_ETC1_RGB8_OES
+#define GL_ETC1_RGB8_OES                0x8D64
+#endif
+
+#if SUPPORT_SOFTWARE_ETC_UNPACK
+#ifndef GL_COMPRESSED_R11_EAC
+#define GL_COMPRESSED_R11_EAC                            0x9270
+#define GL_COMPRESSED_SIGNED_R11_EAC                     0x9271
+#define GL_COMPRESSED_RG11_EAC                           0x9272
+#define GL_COMPRESSED_SIGNED_RG11_EAC                    0x9273
+#define GL_COMPRESSED_RGB8_ETC2                          0x9274
+#define GL_COMPRESSED_SRGB8_ETC2                         0x9275
+#define GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2      0x9276
+#define GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2     0x9277
+#define GL_COMPRESSED_RGBA8_ETC2_EAC                     0x9278
+#define GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC              0x9279
+#endif
+#ifndef GL_R16_SNORM
+#define GL_R16_SNORM                    0x8F98
+#define GL_RG16_SNORM                   0x8F99
+#endif
+#ifndef GL_RED
+#define GL_RED                          0x1903
+#define GL_GREEN                        0x1904
+#define GL_BLUE                         0x1905
+#define GL_RG                           0x8227
+#define GL_RG_INTEGER                   0x8228
+#endif
+#ifndef GL_R16
+#define GL_R16                          0x822A
+#define GL_RG16                         0x822C
+#endif
+#ifndef GL_RGB8
+#define GL_RGB8                         0x8051
+#define GL_RGBA8                        0x8058
+#endif
+#ifndef GL_SRGB8
+#define GL_SRGB8                        0x8C41
+#define GL_SRGB8_ALPHA8                 0x8C43
+#endif
+#endif
+
+#ifndef GL_MAJOR_VERSION
+#define GL_MAJOR_VERSION                0x821B
+#define GL_MINOR_VERSION                0x821C
+#endif
+
+#ifndef GL_CONTEXT_PROFILE_MASK
+#define GL_CONTEXT_PROFILE_MASK              0x9126
+#define GL_CONTEXT_CORE_PROFILE_BIT          0x00000001
+#define GL_CONTEXT_COMPATIBILITY_PROFILE_BIT 0x00000002
+#endif
+    
+#ifndef GL_NUM_EXTENSIONS
+#define GL_NUM_EXTENSIONS              0x821D
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* KTXGL_H */
diff --git a/external/Vulkan/external/ktx/lib/ktxint.h b/external/Vulkan/external/ktx/lib/ktxint.h
new file mode 100644
index 0000000000000000000000000000000000000000..a7e27fb418ff416ae2244f98cfde2b11fc93729c
--- /dev/null
+++ b/external/Vulkan/external/ktx/lib/ktxint.h
@@ -0,0 +1,205 @@
+/* -*- tab-width: 4; -*- */
+/* vi: set sw=2 ts=4 expandtab: */
+
+/* $Id: 3dd768b381113d67ca7e4bde10e6252900bff845 $ */
+
+/*
+ * Copyright (c) 2010-2018 The Khronos Group Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+/* 
+ * Author: Mark Callow from original code by Georg Kolling
+ */
+
+#ifndef KTXINT_H
+#define KTXINT_H
+
+/* Define this to include the ETC unpack software in the library. */
+#ifndef SUPPORT_SOFTWARE_ETC_UNPACK
+  /* Include for all GL versions because have seen OpenGL ES 3
+   * implementaions that do not support ETC1 (ARM Mali emulator v1.0)!
+   */
+  #define SUPPORT_SOFTWARE_ETC_UNPACK 1
+#endif
+
+#ifndef MAX
+#define MAX(x, y) (((x) > (y)) ? (x) : (y))
+#endif
+
+#define KTX2_IDENTIFIER_REF  { 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x32, 0x30, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A }
+#define KTX2_HEADER_SIZE     (64)
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @internal
+ * @brief used to pass GL context capabilites to subroutines.
+ */
+#define _KTX_NO_R16_FORMATS     0x0
+#define _KTX_R16_FORMATS_NORM   0x1
+#define _KTX_R16_FORMATS_SNORM  0x2
+#define _KTX_ALL_R16_FORMATS (_KTX_R16_FORMATS_NORM | _KTX_R16_FORMATS_SNORM)
+extern GLint _ktxR16Formats;
+extern GLboolean _ktxSupportsSRGB;
+
+/**
+ * @internal
+ * @~English
+ * @brief KTX file header
+ *
+ * See the KTX specification for descriptions
+ */
+typedef struct KTX_header {
+    ktx_uint8_t  identifier[12];
+    ktx_uint32_t endianness;
+    ktx_uint32_t glType;
+    ktx_uint32_t glTypeSize;
+    ktx_uint32_t glFormat;
+    ktx_uint32_t glInternalformat;
+    ktx_uint32_t glBaseInternalformat;
+    ktx_uint32_t pixelWidth;
+    ktx_uint32_t pixelHeight;
+    ktx_uint32_t pixelDepth;
+    ktx_uint32_t numberOfArrayElements;
+    ktx_uint32_t numberOfFaces;
+    ktx_uint32_t numberOfMipmapLevels;
+    ktx_uint32_t bytesOfKeyValueData;
+} KTX_header;
+
+/* This will cause compilation to fail if the struct size doesn't match */
+typedef int KTX_header_SIZE_ASSERT [sizeof(KTX_header) == KTX_HEADER_SIZE];
+
+/**
+ * @internal
+ * @~English
+ * @brief Structure for supplemental information about the texture.
+ *
+ * _ktxCheckHeader returns supplemental information about the texture in this
+ * structure that is derived during checking of the file header.
+ */
+typedef struct KTX_supplemental_info
+{
+    ktx_uint8_t compressed;
+    ktx_uint8_t generateMipmaps;
+    ktx_uint16_t textureDimension;
+} KTX_supplemental_info;
+/**
+ * @internal
+ * @var ktx_uint8_t KTX_supplemental_info::compressed
+ * @~English
+ * @brief KTX_TRUE, if this a compressed texture, KTX_FALSE otherwise?
+ */
+/**
+ * @internal
+ * @var ktx_uint8_t KTX_supplemental_info::generateMipmaps
+ * @~English
+ * @brief KTX_TRUE, if mipmap generation is required, KTX_FALSE otherwise.
+ */
+/**
+ * @internal
+ * @var ktx_uint16_t KTX_supplemental_info::textureDimension
+ * @~English
+ * @brief The number of dimensions, 1, 2 or 3, of data in the texture image.
+ */
+
+/*
+ * @internal
+ * CheckHeader
+ * 
+ * Reads the KTX file header and performs some sanity checking on the values
+ */
+KTX_error_code _ktxCheckHeader(KTX_header* pHeader,
+                               KTX_supplemental_info* pSuppInfo);
+
+/*
+ * SwapEndian16: Swaps endianness in an array of 16-bit values
+ */
+void _ktxSwapEndian16(ktx_uint16_t* pData16, int count);
+
+/*
+ * SwapEndian32: Swaps endianness in an array of 32-bit values
+ */
+void _ktxSwapEndian32(ktx_uint32_t* pData32, int count);
+
+/*
+ * UnpackETC: uncompresses an ETC compressed texture image
+ */
+KTX_error_code _ktxUnpackETC(const GLubyte* srcETC, const GLenum srcFormat,
+                             ktx_uint32_t active_width, ktx_uint32_t active_height,
+                             GLubyte** dstImage,
+                             GLenum* format, GLenum* internalFormat, GLenum* type,
+                             GLint R16Formats, GLboolean supportsSRGB);
+
+/*
+ * Pad nbytes to next multiple of n
+ */
+/* Equivalent to n * ceil(nbytes / n) */
+#define _KTX_PADN(n, nbytes) (nbytes + (n-1) & ~(ktx_uint32_t)(n-1))
+/*
+ * Calculate bytes of of padding needed to reach next multiple of n.
+ */
+/* Equivalent to (n * ceil(nbytes / n)) - nbytes */
+#define _KTX_PADN_LEN(n, nbytes) ((n-1) - (nbytes + (n-1) & (n-1)))
+
+/*
+ * Pad nbytes to next multiple of 4
+ */
+#define _KTX_PAD4(nbytes) _KTX_PADN(4, nbytes)
+/*
+ * Calculate bytes of of padding needed to reach next multiple of 4.
+ */
+#define _KTX_PAD4_LEN(nbytes) _KTX_PADN_LEN(4, nbytes)
+
+/*
+ * Pad nbytes to KTX_GL_UNPACK_ALIGNMENT
+ */
+#define _KTX_PAD_UNPACK_ALIGN(nbytes)  \
+        _KTX_PADN(KTX_GL_UNPACK_ALIGNMENT, nbytes)
+/*
+ * Calculate bytes of of padding needed to reach KTX_GL_UNPACK_ALIGNMENT.
+ */
+#define _KTX_PAD_UNPACK_ALIGN_LEN(nbytes)  \
+        _KTX_PADN_LEN(KTX_GL_UNPACK_ALIGNMENT, nbytes)
+
+/*
+ ======================================
+     Internal ktxTexture functions
+ ======================================
+*/
+
+KTX_error_code
+ktxTexture_iterateLoadedImages(ktxTexture* This, PFNKTXITERCB iterCb,
+                               void* userdata);
+KTX_error_code
+ktxTexture_iterateSourceImages(ktxTexture* This, PFNKTXITERCB iterCb,
+                               void* userdata);
+    
+ktx_uint32_t ktxTexture_glTypeSize(ktxTexture* This);
+ktx_size_t ktxTexture_imageSize(ktxTexture* This, ktx_uint32_t level);
+ktx_bool_t ktxTexture_isActiveStream(ktxTexture* This);
+ktx_size_t ktxTexture_levelSize(ktxTexture* This, ktx_uint32_t level);
+ktx_size_t ktxTexture_faceLodSize(ktxTexture* This, ktx_uint32_t level);
+void ktxTexture_rowInfo(ktxTexture* This, ktx_uint32_t level,
+                        ktx_uint32_t* numRows, ktx_uint32_t* rowBytes,
+                        ktx_uint32_t* rowPadding);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* KTXINT_H */
diff --git a/external/Vulkan/external/ktx/lib/libktx.gypi b/external/Vulkan/external/ktx/lib/libktx.gypi
new file mode 100644
index 0000000000000000000000000000000000000000..06c019b62200a4ecf29a55d7e00ff5440ec3ea30
--- /dev/null
+++ b/external/Vulkan/external/ktx/lib/libktx.gypi
@@ -0,0 +1,226 @@
+##
+# @internal
+# @copyright © 2015, Mark Callow. For license see LICENSE.md.
+#
+# @brief Generate project to build KTX library for OpenGL.
+#
+{
+  'variables': {
+    'sources': [
+      # .h files are included so they will appear in IDEs' file lists.
+      '../include/ktx.h',
+      'checkheader.c',
+      'errstr.c',
+      'etcdec.cxx',
+      'etcunpack.cxx',
+      'filestream.c',
+      'filestream.h',
+      'gl_format.h',
+      'gl_funcptrs.h',
+      'gles1_funcptrs.h',
+      'gles2_funcptrs.h',
+      'gles3_funcptrs.h',
+      'glloader.c',
+      'hashlist.c',
+      'hashtable.c',
+      'ktxgl.h',
+      'ktxint.h',
+      'memstream.c',
+      'memstream.h',
+      'stream.h',
+      'swap.c',
+      'texture.c',
+      'uthash.h',
+      'writer.c',
+      'writer_v1.c'
+    ],
+    # Use _files to get the names relativized
+    'vksource_files': [
+      '../include/ktxvulkan.h',
+      'vk_format.h',
+      'vkloader.c',
+      'vk_funclist.inl',
+      'vk_funcs.c',
+      'vk_funcs.h'
+    ],
+    'include_dirs': [
+      '../include',
+      '../other_include',
+    ],
+  }, # variables
+
+  'includes': [
+      '../gyp_include/libgl.gypi',
+      '../gyp_include/libvulkan.gypi',
+  ],
+  'targets': [
+    {
+      'target_name': 'libktx.gl',
+      'type': '<(library)',
+      'cflags': [ '-std=c99' ],
+      'defines': [ 'KTX_OPENGL=1' ],
+      'direct_dependent_settings': {
+         'include_dirs': [ '<@(include_dirs)' ],
+      },
+      'include_dirs': [ '<@(include_dirs)' ],
+      'mac_bundle': 0,
+      'dependencies': [ 'vulkan_headers' ],
+      'sources': [
+        '<@(sources)',
+        '<@(vksource_files)',
+      ],
+      'conditions': [
+        ['_type == "shared_library"', {
+          'dependencies': [ 'libgl', 'libvulkan.lazy' ],
+          'conditions': [
+            ['OS == "mac" or OS == "ios"', {
+              'direct_dependent_settings': {
+                'target_conditions': [
+                  ['_mac_bundle == 1', {
+                    'copies': [{
+                      'xcode_code_sign': 1,
+                      'destination': '<(PRODUCT_DIR)/$(FRAMEWORKS_FOLDER_PATH)',
+                      'files': [ '<(PRODUCT_DIR)/<(_target_name)<(SHARED_LIB_SUFFIX)' ],
+                    }], # copies
+                    'xcode_settings': {
+                      # Tell DYLD where to search for this dylib.
+                      # "man dyld" for more information.
+                      'LD_RUNPATH_SEARCH_PATHS': [ '@executable_path/../Frameworks' ],
+                    },
+                  }, {
+                    'xcode_settings': {
+                      'LD_RUNPATH_SEARCH_PATHS': [ '@executable_path' ],
+                    },
+                  }], # _mac_bundle == 1
+                ], # target_conditions
+              }, # direct_dependent_settings
+              'sources!': [
+                'vk_funclist.inl',
+                'vk_funcs.c',
+                'vk_funcs.h',
+              ],
+              'xcode_settings': {
+                # This is so dyld can find the dylib when it is installed by
+                # the copy command above.
+                'INSTALL_PATH': '@rpath',
+              },
+            }, 'OS == "linux"', {
+              'defines': [ 'KTX_USE_FUNCPTRS_FOR_VULKAN' ],
+              'dependencies!': [ 'libvulkan.lazy' ],
+            }] # OS == "mac or OS == "ios"
+          ], # conditions
+        }] # _type == "shared_library"
+      ], # conditions
+    }, # libktx.gl target
+    {
+      'target_name': 'libktx.es1',
+      'type': 'static_library',
+      'cflags': [ '-std=c99' ],
+      'defines': [ 'KTX_OPENGL_ES1=1' ],
+      'direct_dependent_settings': {
+        'include_dirs': [ '<@(include_dirs)' ],
+      },
+      'sources': [ '<@(sources)' ],
+      'include_dirs': [ '<@(include_dirs)' ],
+    }, # libktx.es1
+    {
+      'target_name': 'libktx.es3',
+      'type': 'static_library',
+      'cflags': [ '-std=c99' ],
+      'defines': [ 'KTX_OPENGL_ES3=1' ],
+      'dependencies': [ 'vulkan_headers' ],
+      'direct_dependent_settings': {
+         'include_dirs': [ '<@(include_dirs)' ],
+      },
+      'sources': [
+        '<@(sources)',
+        '<@(vksource_files)',
+      ],
+      'include_dirs': [ '<@(include_dirs)' ],
+    }, # libktx.es3
+  ], # targets
+  'conditions': [
+    ['OS == "linux" or OS == "mac" or OS == "win"', {
+      # Can only build doc on desktops
+      'targets': [
+        {
+          'target_name': 'libktx.doc',
+          'type': 'none',
+          'variables': {
+            'variables': { # level 2
+              'output_dir': '../build/docs',
+            },
+            'output_dir': '<(output_dir)',
+            'doxyConfig': 'libktx.doxy',
+            'timestamp': '<(output_dir)/.libktx_gentimestamp',
+          },
+          'actions': [
+            {
+              'action_name': 'buildLibktxDoc',
+              'message': 'Generating libktx documentation with Doxygen',
+              'inputs': [
+                '../<(doxyConfig)',
+                '../runDoxygen',
+                '../lib/mainpage.md',
+                '../LICENSE.md',
+                '../TODO.md',
+                '<@(sources)',
+                '<@(vksource_files)',
+              ],
+              # If other partial Doxygen outputs are included, e.g.
+              # (<(output_dir)/html/libktx), CMake's make generator
+              # on Linux (at least), makes timestamp dependent on
+              # those other outputs. If those outputs exist, then
+              # neither timestamp nor the document is updated.
+              'outputs': [ '<(timestamp)' ],
+              # doxygen must be run in the top-level project directory
+              # so that ancestors of that directory will be removed
+              # from paths displayed in the documentation. That is
+              # the directory where the .doxy and .gyp files are stored.
+              #
+              # With Xcode, the current directory during project
+              # build is one we need so we're good to go. However
+              # we need to spawn another shell with -l so the
+              # startup (.bashrc, etc) files will be read.
+              #
+              # With MSVS the working directory will be the
+              # location of the vcxproj file. However when the
+              # action is using bash ('msvs_cygwin_shell': '1',
+              # the default, is set) no path relativization is
+              # performed on any command arguments. If forced, by
+              # using variable names such as '*_dir', paths will be
+              # made relative to the location of the .gyp file.
+              #
+              # A setup_env.bat file is run before the command.
+              # Apparently that .bat file is expected to be in the
+              # same location as the .gyp and to cd to
+              # its directory. That makes things work.
+              #
+              # Note that the same setup_env.bat is run by
+              # rules but rules relativize paths to the vcxproj
+              # location so cd to the .gyp home breaks rules.
+              # Therefore in rules set 'msvs_cygwin_shell': '0.
+              #
+              # If using cmd.exe ('msvs_cygwin_shell': '0')
+              # the MSVS generator will relativize to the vcxproj
+              # location *all* command arguments, that do not look
+              # like options.
+              #
+              # With `make`, cmake, etc, like Xcode,  the current
+              # directory during project build is the one we need.
+              'msvs_cygwin_shell': 1,
+              'action': [
+                './runDoxygen',
+                '-t', '<(timestamp)',
+                '-o', '<(output_dir)/html',
+                '<(doxyConfig)',
+              ],
+            }, # buildDoc action
+          ], # actions
+        }, # libktx.doc
+      ], # targets
+    }], # 'OS == "linux" or OS == "mac" or OS == "win"'
+  ], # conditions
+}
+
+# vim:ai:ts=4:sts=4:sw=2:expandtab:textwidth=70
diff --git a/external/Vulkan/external/ktx/lib/mainpage.md b/external/Vulkan/external/ktx/lib/mainpage.md
new file mode 100644
index 0000000000000000000000000000000000000000..b50ba9ec714d411ca07315065227788055334988
--- /dev/null
+++ b/external/Vulkan/external/ktx/lib/mainpage.md
@@ -0,0 +1,214 @@
+Introduction             {#mainpage}
+=========
+
+libktx is a small library of functions for creating and reading KTX (Khronos
+TeXture) files and instantiating OpenGL&reg; and OpenGL&reg; ES
+textures and Vulkan images from them.
+
+For information about the KTX format see the
+<a href="http://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/">
+formal specification.</a>
+
+The library is open source software. Source code is available at
+<a href="https://github.com/KhronosGroup/KTX">GitHub</a>. Most of the source
+code and the documentation is licensed under the Apache 2.0 license. See @ref license
+for details. When distributing the library, whether in source or binary form, this
+documentation must be included in the distribution or otherwise made available to
+recipients.
+
+See @ref libktx_history for the list of changes.
+
+See @ref todo for the current To Do list.
+
+@authors
+Mark Callow, <a href="http://www.edgewise-consulting.com">Edgewise Consulting</a>,
+             formerly at <a href="http://www.hicorp.co.jp">HI Corporation</a>\n
+Georg Kolling, <a href="http://www.imgtec.com">Imagination Technology</a>\n
+Jacob Str&ouml;m, <a href="http://www.ericsson.com">Ericsson AB</a>
+
+@version 3.0.0
+
+$Date$
+
+# Usage Overview                              {#overview}
+
+## Reading a KTX file for non-GL and non-Vulkan Use  {#readktx}
+
+~~~~~~~~~~~~~~~~{.c}
+#include <ktx.h>
+
+ktxTexture* texture;
+KTX_error_code result;
+ktx_size_t offset;
+ktx_uint8_t* image;
+ktx_uint32_t level, layer, faceSlice;
+
+result = ktxTexture_CreateFromNamedFile("mytex3d.ktx",
+                                        KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT,
+                                        &texture);
+
+// Retrieve information about the texture from fields in the ktxTexture
+// such as:
+ktx_uint32 numLevels = texture->numLevels;
+ktx_uint32 baseWidth = texture->baseWidth;
+ktx_bool_t isArray = texture->isArray;
+
+// Retrieve a pointer to the image for a specific mip level, array layer
+// & face or depth slice.
+level = 1; layer = 0; faceSlice = 3;
+result = ktxTexture_GetImageOffset(texture, level, layer, faceSlice, &offset);
+image = ktxTexture_GetData(texture) + offset;
+// ...
+// Do something with the texture image.
+// ...
+ktxTexture_Destroy(texture);
+~~~~~~~~~~~~~~~~
+
+## Creating a GL texture object from a KTX file.   {#createGL}
+
+~~~~~~~~~~~~~~~~{.c}
+#include <ktx.h>
+
+ktxTexture* kTexture;
+KTX_error_code result;
+ktx_size_t offset;
+ktx_uint8_t* image;
+ktx_uint32_t level, layer, faceSlice;
+GLuint texture = 0;
+GLenum target, glerror;
+
+result = ktxTexture_CreateFromNamedFile("mytex3d.ktx",
+                                        KTX_TEXTURE_CREATE_NO_FLAGS,
+                                        &kTexture);
+glGenTextures(1, &texture); // Optional. GLUpload can generate a texture.
+result = ktxtexture_GLUpload(kTexture, &texture, &target, &glerror);
+ktxTexture_Destroy(texture);
+// ...
+// GL rendering using the texture
+// ...
+~~~~~~~~~~~~~~~~
+
+## Creating a Vulkan image object from a KTX file.  {#createVulkan}
+
+~~~~~~~~~~~~~~~~{.c}
+#include <ktxvulkan.h>
+
+ktxTexture* kTexture;
+KTX_error_code result;
+ktx_size_t offset;
+ktx_uint8_t* image;
+ktx_uint32_t level, layer, faceSlice;
+ktxVulkanDeviceInfo vdi;
+ktxVulkanTexture texture;
+
+// Set up Vulkan physical device (gpu), logical device (device), queue
+// and command pool. Save the handles to these in a struct called vkctx.
+// ktx VulkanDeviceInfo is used to pass these with the expectation that
+// apps are likely to upload a large number of textures.
+ktxVulkanDeviceInfo_Construct(&vdi, vkctx.gpu, vkctx.device,
+                              vkctx.queue, vkctx.commandPool, nullptr);
+
+ktxresult = ktxTexture_CreateFromNamedFile("mytex3d.ktx",
+                                           KTX_TEXTURE_CREATE_NO_FLAGS,
+                                           &kTexture);
+
+ktxresult = ktxTexture_VkUploadEx(kTexture, &vdi, &texture,
+                                  VK_IMAGE_TILING_OPTIMAL,
+                                  VK_IMAGE_USAGE_SAMPLED_BIT,
+                                  VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+
+ktxTexture_Destroy(kTexture);
+ktxVulkanDeviceInfo_Destruct(&vdi);
+// ...
+// Vulkan rendering using the texture
+// ...
+// When done using the image in Vulkan...
+ktxVulkanTexture_Destruct(&texture, vkctx.device, nullptr);
+~~~~~~~~~~~~~~~~
+
+## Extracting Metadata        {#subsection}
+
+Once a ktxTexture object has been created, metadata can be easily found
+and extracted. The following can be added to any of the above.
+
+~~~~~~~~~~~~~~~~{.c}
+char* pValue;
+uint32_t valueLen;
+if (KTX_SUCCESS == ktxHashList_FindValue(&kTexture->kvDataHead,
+                                          KTX_ORIENTATION_KEY,
+                                          &valueLen, (void**)&pValue))
+ {
+      char s, t;
+
+      if (sscanf(pValue, KTX_ORIENTATION2_FMT, &s, &t) == 2) {
+         ...
+      }
+ }
+~~~~~~~~~~~~~~~~
+
+## Writing a KTX file         {#writektx}
+
+~~~~~~~~~~~~~~~~{.c}
+#include <ktx.h>
+
+ktxTexture* texture;
+ktxTextureCreateInfo createInfo;
+KTX_error_code result;
+ktx_uint32_t level, layer, faceSlice;
+FILE* src;
+ktx_size_t srcSize;
+
+createInfo.glInternalformat = GL_RGB8;
+createInfo.baseWidth = 2048;
+createInfo.baseHeight = 1024;
+createInfo.baseDepth = 16;
+createInfo.numDimensions = 3.
+// Note: it is not necessary to provide a full mipmap pyramid.
+createInfo.numLevels = log2(createInfo.baseWidth) + 1
+createInfo.numLayers = 1;
+createInfo.numFaces = 1;
+createInfo.isArray = KTX_FALSE;
+createInfo.generateMipmaps = KTX_FALSE;
+
+result = ktxTexture_Create(createInfo,
+                           KTX_TEXTURE_CREATE_ALLOC_STORAGE,
+                           &texture);
+
+src = // Open a stdio FILE* on the baseLevel image, slice 0.
+srcSize = // Query size of the file.
+level = 0;
+layer = 0;
+faceSlice = 0;                           
+result = ktxTexture_SetImageFromMemory(texture, level, layer, faceSlice,
+                                       src, srcSize);
+// Repeat for the other 15 slices of the base level and all other levels
+// up to createInfo.numLevels.
+
+ktxTexture_WriteToNamedFile(texture, "mytex3d.ktx");
+ktxTexture_Destroy(texture);
+~~~~~~~~~~~~~~~~
+
+## Modifying a KTX file         {#modifyktx}
+
+~~~~~~~~~~~~~~~~{.c}
+#include <ktx.h>
+
+ktxTexture* texture;
+KTX_error_code result;
+ktx_size_t offset;
+ktx_uint8_t* image;
+ktx_uint32_t level, layer, faceSlice;
+
+result = ktxTexture_CreateFromNamedFile("mytex3d.ktx",
+                                        KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT,
+                                        &texture);
+// The file is closed after all the data has been read.
+
+// It is the responsibilty of the application to make sure its
+// modifications are valid.
+texture->generateMipmaps = KTX_TRUE;
+
+ktxTexture_WriteToNamedFile(texture, "mytex3d.ktx");
+ktxTexture_Destroy(texture);
+~~~~~~~~~~~~~~~~
+
diff --git a/external/Vulkan/external/ktx/lib/memstream.c b/external/Vulkan/external/ktx/lib/memstream.c
new file mode 100644
index 0000000000000000000000000000000000000000..523b55191954405d8cb7e98f101f519dc671cbb9
--- /dev/null
+++ b/external/Vulkan/external/ktx/lib/memstream.c
@@ -0,0 +1,588 @@
+/* -*- tab-width: 4; -*- */
+/* vi: set sw=2 ts=4 expandtab: */
+
+/*
+ * Copyright (c) 2010-2018 The Khronos Group Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @internal
+ * @file
+ * @~English
+ *
+ * @brief Implementation of ktxStream for memory.
+ *
+ * @author Maksim Kolesin, Under Development
+ * @author Georg Kolling, Imagination Technology
+ * @author Mark Callow, HI Corporation
+ */
+
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "ktx.h"
+#include "ktxint.h"
+#include "memstream.h"
+
+/**
+* @brief Default allocation size for a ktxMemStream.
+*/
+#define KTX_MEM_DEFAULT_ALLOCATED_SIZE 256
+
+/**
+ * @internal
+ * @brief Structure to store information about data allocated for ktxMemStream.
+ */
+struct ktxMem
+{
+    const ktx_uint8_t* robytes;/*!< pointer to read-only data */
+    ktx_uint8_t* bytes;        /*!< pointer to rw data. */
+    ktx_size_t alloc_size;       /*!< allocated size of the memory block. */
+    ktx_size_t used_size;        /*!< bytes used. Effectively the write position. */
+    ktx_off_t pos;               /*!< read position. */
+};
+
+static KTX_error_code ktxMem_expand(ktxMem* pMem, const ktx_size_t size);
+
+/**
+ * @internal
+ * @brief Initialize a ktxMem struct for read-write.
+ *
+ * Memory for the stream data is allocated internally but the
+ * caller is responsible for freeing the memory. A pointer to
+ * the memory can be obtained with ktxMem_getdata().
+ *
+ * @sa ktxMem_getdata.
+ *
+ * @param [in] pMem pointer to the @c ktxMem to initialize.
+ */
+static KTX_error_code
+ktxMem_construct(ktxMem* pMem)
+{
+    pMem->pos = 0;
+    pMem->alloc_size = 0;
+    pMem->robytes = 0;
+    pMem->bytes = 0;
+    pMem->used_size = 0;
+    return ktxMem_expand(pMem, KTX_MEM_DEFAULT_ALLOCATED_SIZE);
+}
+
+/**
+ * @internal
+ * @brief Create & initialize a ktxMem struct for read-write.
+ *
+ * @sa ktxMem_construct.
+ *
+ * @param [in,out] ppMem pointer to the location in which to return
+ *                       a pointer to the newly created @c ktxMem.
+ *
+ * @return     KTX_SUCCESS on success, KTX_OUT_OF_MEMORY on error.
+ *
+ * @exception  KTX_OUT_OF_MEMORY    System failed to allocate sufficient pMemory.
+ */
+static KTX_error_code
+ktxMem_create(ktxMem** ppMem)
+{
+    ktxMem* pNewMem = (ktxMem*)malloc(sizeof(ktxMem));
+    if (pNewMem) {
+        KTX_error_code result = ktxMem_construct(pNewMem);
+        if (result == KTX_SUCCESS)
+            *ppMem = pNewMem;
+        return result;
+    }
+    else {
+        return KTX_OUT_OF_MEMORY;
+    }
+}
+
+/**
+ * @internal
+ * @brief Initialize a ktxMem struct for read-only.
+ *
+ * @param [in] pMem     pointer to the @c ktxMem to initialize.
+ * @param [in] bytes    pointer to the data to be read.
+ * @param [in] numBytes number of bytes of data.
+ */
+static void
+ktxMem_construct_ro(ktxMem* pMem, const void* bytes, ktx_size_t numBytes)
+{
+    pMem->pos = 0;
+    pMem->robytes = bytes;
+    pMem->bytes = 0;
+    pMem->used_size = numBytes;
+    pMem->alloc_size = numBytes;
+}
+
+/**
+ * @internal
+ * @brief Create & initialize a ktxMem struct for read-only.
+ *
+ * @sa ktxMem_construct.
+ *
+ * @param [in,out] ppMem    pointer to the location in which to return
+ *                          a pointer to the newly created @c ktxMem.
+ * @param [in]     bytes    pointer to the data to be read.
+ * @param [in]     numBytes number of bytes of data.
+ *
+ * @return     KTX_SUCCESS on success, KTX_OUT_OF_MEMORY on error.
+ *
+ * @exception  KTX_OUT_OF_MEMORY    System failed to allocate sufficient pMemory.
+ */
+static KTX_error_code
+ktxMem_create_ro(ktxMem** ppMem, const void* bytes, ktx_size_t numBytes)
+{
+    ktxMem* pNewMem = (ktxMem*)malloc(sizeof(ktxMem));
+    if (pNewMem) {
+        ktxMem_construct_ro(pNewMem, bytes, numBytes);
+        *ppMem = pNewMem;
+        return KTX_SUCCESS;
+    }
+    else {
+        return KTX_OUT_OF_MEMORY;
+    }
+}
+
+/*
+ * ktxMem_destruct not needed as ktxMem_construct caller is reponsible
+ * for freeing the data written.
+ */
+
+/**
+ * @internal
+ * @brief Free the memory of a struct ktxMem.
+ *
+ * @param pMem pointer to ktxMem to free.
+ */
+static void
+ktxMem_destroy(ktxMem* pMem, ktx_bool_t freeData)
+{
+    assert(pMem != NULL);
+    if (freeData) {
+        free(pMem->bytes);
+    }
+    free(pMem);
+}
+
+#ifdef KTXMEM_CLEAR_USED
+/**
+ * @internal
+ * @brief Clear the data of a memory stream.
+ *
+ * @param pMem pointer to ktxMem to clear.
+ */
+static void
+ktxMem_clear(ktxMem* pMem)
+{
+    assert(pMem != NULL);
+    memset(pMem, 0, sizeof(ktxMem));
+}
+#endif
+
+/**
+ * @internal
+ * @~English
+ * @brief Expand a ktxMem to fit to a new size.
+ *
+ * @param [in] pMem          pointer to ktxMem struct to expand.
+ * @param [in] newsize       minimum new size required.
+ *
+ * @return     KTX_SUCCESS on success, KTX_OUT_OF_MEMORY on error.
+ *
+ * @exception  KTX_OUT_OF_MEMORY    System failed to allocate sufficient pMemory.
+ */
+static KTX_error_code
+ktxMem_expand(ktxMem *pMem, const ktx_size_t newsize)
+{
+    ktx_size_t new_alloc_size;
+    
+    assert(pMem != NULL && newsize != 0);
+
+    new_alloc_size = pMem->alloc_size == 0 ?
+                     KTX_MEM_DEFAULT_ALLOCATED_SIZE : pMem->alloc_size;
+    while (new_alloc_size < newsize) {
+        ktx_size_t alloc_size = new_alloc_size;
+        new_alloc_size <<= 1;
+        if (new_alloc_size < alloc_size) {
+            /* Overflow. Set to maximum size. newsize can't be larger. */
+            new_alloc_size = (ktx_size_t)-1L;
+        }
+    }
+
+    if (new_alloc_size == pMem->alloc_size)
+        return KTX_SUCCESS;
+
+    if (!pMem->bytes)
+        pMem->bytes = (ktx_uint8_t*)malloc(new_alloc_size);
+    else
+        pMem->bytes = (ktx_uint8_t*)realloc(pMem->bytes, new_alloc_size);
+
+    if (!pMem->bytes)
+    {
+        pMem->alloc_size = 0;
+        pMem->used_size = 0;
+        return KTX_OUT_OF_MEMORY;
+    }
+
+    pMem->alloc_size = new_alloc_size;
+    return KTX_SUCCESS;
+}
+
+/**
+ * @internal
+ * @~English
+ * @brief Read bytes from a ktxMemStream.
+ *
+ * @param [in]     str      pointer to ktxMem struct, converted to a void*, that
+ *                          specifies an input stream.
+ * @param [in,out] dst      pointer to memory where to copy read bytes.
+ * @param [in,out] count    pointer to number of bytes to read.
+ *
+ * @return      KTX_SUCCESS on success, KTX_INVALID_VALUE on error.
+ *
+ * @exception KTX_INVALID_VALUE     @p str or @p dst is @c NULL or @p str->data is
+ *                                  @c NULL.
+ * @exception KTX_FILE_UNEXPECTED_EOF not enough data to satisfy the request.
+ */
+static
+KTX_error_code ktxMemStream_read(ktxStream* str, void* dst, const ktx_size_t count)
+{
+    ktxMem* mem;
+    ktx_off_t newpos;
+    const ktx_uint8_t* bytes;
+    
+
+    if (!str || !(mem = str->data.mem))
+        return KTX_INVALID_VALUE;
+
+    newpos = mem->pos + count;
+    /* The first clause checks for overflow. */
+    if (newpos < mem->pos || newpos > mem->used_size)
+        return KTX_FILE_UNEXPECTED_EOF;
+
+    bytes = mem->robytes ? mem->robytes : mem->bytes;
+    memcpy(dst, bytes + mem->pos, count);
+    mem->pos = newpos;
+
+    return KTX_SUCCESS;
+}
+
+/**
+ * @internal
+ * @~English
+ * @brief Skip bytes in a ktxMemStream.
+ *
+ * @param [in] str      pointer to the ktxStream on which to operate.
+ * @param [in] count    number of bytes to skip.
+ *
+ * @return      KTX_SUCCESS on success, KTX_INVALID_VALUE on error.
+ *
+ * @exception KTX_INVALID_VALUE     @p str or @p mem is @c NULL or sufficient
+ *                                  data is not available in ktxMem.
+ * @exception KTX_FILE_UNEXPECTED_EOF not enough data to satisfy the request.
+ */
+static
+KTX_error_code ktxMemStream_skip(ktxStream* str, const ktx_size_t count)
+{
+    ktxMem* mem;
+    ktx_off_t newpos;
+    
+    if (!str || !(mem = str->data.mem))
+        return KTX_INVALID_VALUE;
+    
+    newpos = mem->pos + count;
+    /* The first clause checks for overflow. */
+    if (newpos < mem->pos || newpos > mem->used_size)
+        return KTX_FILE_UNEXPECTED_EOF;
+
+    mem->pos = newpos;
+
+    return KTX_SUCCESS;
+}
+
+/**
+ * @internal
+ * @~English
+ * @brief Write bytes to a ktxMemStream.
+ *
+ * @param [out] str    pointer to the ktxStream that specifies the destination.
+ * @param [in] src     pointer to the array of elements to be written,
+ *                     converted to a const void*.
+ * @param [in] size    size in bytes of each element to be written.
+ * @param [in] count   number of elements, each one with a @p size of size
+ *                     bytes.
+ *
+ * @return      KTX_SUCCESS on success, other KTX_* enum values on error.
+ *
+ * @exception KTX_FILE_OVERFLOW        write would result in file exceeding the
+ *                                     maximum permissible size.
+ * @exception KTX_INVALID_OPERATION    @p str is a read-only stream.
+ * @exception KTX_INVALID_VALUE        @p dst is @c NULL or @p mem is @c NULL.
+ * @exception KTX_OUT_OF_MEMORY        See ktxMem_expand() for causes.
+ */
+static
+KTX_error_code ktxMemStream_write(ktxStream* str, const void* src,
+                                  const ktx_size_t size, const ktx_size_t count)
+{
+    ktxMem* mem;
+    KTX_error_code rc = KTX_SUCCESS;
+    ktx_size_t new_size;
+
+    if (!str || !(mem = str->data.mem))
+        return KTX_INVALID_VALUE;
+
+    if (mem->robytes)
+        return KTX_INVALID_OPERATION; /* read-only */
+
+    new_size = mem->used_size + size*count;
+    if (new_size < mem->used_size)
+        return KTX_FILE_OVERFLOW;
+
+    if (mem->alloc_size < new_size) {
+        rc = ktxMem_expand(mem, new_size);
+        if (rc != KTX_SUCCESS)
+            return rc;
+    }
+
+    memcpy(mem->bytes + mem->used_size, src, size*count);
+    mem->used_size += size*count;
+
+    return KTX_SUCCESS;
+}
+
+/**
+ * @internal
+ * @~English
+ * @brief Get the current read/write position in a ktxMemStream.
+ *
+ * @param [in] str      pointer to the ktxStream to query.
+ * @param [in,out] off  pointer to variable to receive the offset value.
+ *
+ * @return      KTX_SUCCESS on success, other KTX_* enum values on error.
+ *
+ * @exception KTX_INVALID_VALUE @p str or @p pos is @c NULL.
+ */
+static
+KTX_error_code ktxMemStream_getpos(ktxStream* str, ktx_off_t* const pos)
+{
+    if (!str || !pos)
+        return KTX_INVALID_VALUE;
+    
+    assert(str->type == eStreamTypeMemory);
+    
+    *pos = str->data.mem->pos;
+    return KTX_SUCCESS;
+}
+
+/**
+ * @internal
+ * @~English
+ * @brief Set the current read/write position in a ktxMemStream.
+ *
+ * Offset of 0 is the start of the file.
+ *
+ * @param [in] str    pointer to the ktxStream whose r/w position is to be set.
+ * @param [in] off    pointer to the offset value to set.
+ *
+ * @return      KTX_SUCCESS on success, other KTX_* enum values on error.
+ *
+ * @exception KTX_INVALID_VALUE @p str is @c NULL.
+ * @exception KTX_INVALID_OPERATION @p pos > size of the allocated memory.
+ */
+static
+KTX_error_code ktxMemStream_setpos(ktxStream* str, ktx_off_t pos)
+{
+    if (!str)
+        return KTX_INVALID_VALUE;
+    
+    assert(str->type == eStreamTypeMemory);
+    
+    if (pos > str->data.mem->alloc_size)
+        return KTX_INVALID_OPERATION;
+    
+    str->data.mem->pos = pos;
+    return KTX_SUCCESS;
+}
+
+/**
+ * @internal
+ * @~English
+ * @brief Get a pointer to a ktxMemStream's data.
+ *
+ * Gets a pointer to data that has been written to the stream. Returned
+ * pointer will be 0 if stream is read-only.
+ *
+ * @param [in] str       pointer to the ktxStream whose data pointer is to
+ *                       be queried.
+ * @param [in,out] size  pointer to a variable in which the data pointer
+ *                       will be written.
+ *
+ * @return      KTX_SUCCESS on success, other KTX_* enum values on error.
+ *
+ * @exception KTX_INVALID_VALUE @p str or @p ppBytes is @c NULL.
+ */
+KTX_error_code ktxMemStream_getdata(ktxStream* str, ktx_uint8_t** ppBytes)
+{
+    if (!str || !ppBytes)
+        return KTX_INVALID_VALUE;
+
+    assert(str->type == eStreamTypeMemory);
+
+    *ppBytes = str->data.mem->bytes;
+    return KTX_SUCCESS;
+}
+
+/**
+ * @internal
+ * @~English
+ * @brief Get the size of a ktxMemStream in bytes.
+ *
+ * @param [in] str       pointer to the ktxStream whose size is to be queried.
+ * @param [in,out] size  pointer to a variable in which size will be written.
+ *
+ * @return      KTX_SUCCESS on success, other KTX_* enum values on error.
+ *
+ * @exception KTX_INVALID_VALUE @p str or @p pSize is @c NULL.
+ */
+static
+KTX_error_code ktxMemStream_getsize(ktxStream* str, ktx_size_t* pSize)
+{
+    if (!str || !pSize)
+        return KTX_INVALID_VALUE;
+    
+    assert(str->type == eStreamTypeMemory);
+    
+    *pSize = str->data.mem->used_size;
+    return KTX_SUCCESS;
+}
+
+/**
+ * @internal
+ * @~English
+ * @brief Setup ktxMemStream function pointers.
+ */
+void
+ktxMemStream_setup(ktxStream* str)
+{
+    str->type = eStreamTypeMemory;
+    str->read = ktxMemStream_read;
+    str->skip = ktxMemStream_skip;
+    str->write = ktxMemStream_write;
+    str->getpos = ktxMemStream_getpos;
+    str->setpos = ktxMemStream_setpos;
+    str->getsize = ktxMemStream_getsize;
+    str->destruct = ktxMemStream_destruct;
+}
+
+/**
+ * @internal
+ * @~English
+ * @brief Initialize a read-write ktxMemStream.
+ *
+ * Memory is allocated as data is written. The caller of this is
+ * responsible for freeing this memory unless @a freeOnDestruct
+ * is not KTX_FALSE.
+ *
+ * @param [in] str             pointer to a ktxStream struct to initialize.
+ * @param [in] freeOnDestruct  If not KTX_FALSE memory holding the data will
+ *                             be freed by the destructor.
+ *
+ * @return      KTX_SUCCESS on success, other KTX_* enum values on error.
+ *
+ * @exception KTX_INVALID_VALUE     @p str is @c NULL.
+ * @exception KTX_OUT_OF_MEMORY     system failed to allocate sufficient memory.
+ */
+KTX_error_code ktxMemStream_construct(ktxStream* str,
+                                      ktx_bool_t freeOnDestruct)
+{
+    ktxMem* mem;
+    KTX_error_code result = KTX_SUCCESS;
+
+    if (!str)
+        return KTX_INVALID_VALUE;
+
+    result = ktxMem_create(&mem);
+
+    if (KTX_SUCCESS == result) {
+        str->data.mem = mem;
+        ktxMemStream_setup(str);
+        str->closeOnDestruct = freeOnDestruct;
+    }
+
+    return result;
+}
+
+/**
+ * @internal
+ * @~English
+ * @brief Initialize a read-only ktxMemStream.
+ *
+ * @param [in] str      pointer to a ktxStream struct to initialize.
+ * @param [in] bytes    pointer to an array of bytes containing the data.
+ * @param [in] size     size of array of data for ktxMemStream.
+ *
+ * @return      KTX_SUCCESS on success, other KTX_* enum values on error.
+ *
+ * @exception KTX_INVALID_VALUE     @p str or @p mem is @c NULL or @p numBytes
+ *                                  is 0.
+ *                                  or @p size is less than 0.
+ * @exception KTX_OUT_OF_MEMORY     system failed to allocate sufficient memory.
+ */
+KTX_error_code ktxMemStream_construct_ro(ktxStream* str,
+                                         const ktx_uint8_t* bytes,
+                                         const ktx_size_t numBytes)
+{
+    ktxMem* mem;
+    KTX_error_code result = KTX_SUCCESS;
+
+    if (!str || !bytes || numBytes == 0)
+        return KTX_INVALID_VALUE;
+
+    result = ktxMem_create_ro(&mem, bytes, numBytes);
+
+    if (KTX_SUCCESS == result) {
+        str->data.mem = mem;
+        ktxMemStream_setup(str);
+        str->closeOnDestruct = KTX_FALSE;
+    }
+
+    return result;
+}
+
+/**
+ * @internal
+ * @~English
+ * @brief Free the memory used by a ktxMemStream.
+ *
+ * This only frees the memory used to store the data written to the stream,
+ * if the @c freeOnDestruct parameter to ktxMemStream_construct() was not
+ * @c KTX_FALSE. Otherwise it is the responsibility of the caller of
+ * ktxMemStream_construct() and a pointer to this memory should be retrieved
+ * using ktxMemStream_getdata() before calling this function.
+ *
+ * @sa ktxMemStream_construct, ktxMemStream_getdata.
+ *
+ * @param [in] str pointer to the ktxStream whose memory is
+ *                 to be freed.
+ */
+void
+ktxMemStream_destruct(ktxStream* str)
+{
+    assert(str && str->type == eStreamTypeMemory);
+
+    ktxMem_destroy(str->data.mem, str->closeOnDestruct);
+    str->data.mem = NULL;
+}
+
diff --git a/external/Vulkan/external/ktx/lib/memstream.h b/external/Vulkan/external/ktx/lib/memstream.h
new file mode 100644
index 0000000000000000000000000000000000000000..3cd4e03f77a92342c42852f9688102bae79f61e9
--- /dev/null
+++ b/external/Vulkan/external/ktx/lib/memstream.h
@@ -0,0 +1,55 @@
+/* -*- tab-width: 4; -*- */
+/* vi: set sw=2 ts=4 expandtab: */
+
+/*
+ * Copyright (c) 2010-2018 The Khronos Group Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @internal
+ * @file
+ * @~English
+ *
+ * @brief Interface of ktxStream for memory.
+ *
+ * @author Maksim Kolesin
+ * @author Georg Kolling, Imagination Technology
+ * @author Mark Callow, HI Corporation
+ */
+
+#ifndef MEMSTREAM_H
+#define MEMSTREAM_H
+
+#include "ktx.h"
+#include "stream.h"
+
+/*
+ * Initialize a ktxStream to a ktxMemStream with internally
+ * allocated memory. Can be read or written.
+ */
+KTX_error_code ktxMemStream_construct(ktxStream* str,
+                                      ktx_bool_t freeOnDestruct);
+/*
+ * Initialize a ktxStream to a read-only ktxMemStream reading
+ * from an array of bytes.
+ */
+KTX_error_code ktxMemStream_construct_ro(ktxStream* str,
+                                         const ktx_uint8_t* pBytes,
+                                         const ktx_size_t size);
+void ktxMemStream_destruct(ktxStream* str);
+
+KTX_error_code ktxMemStream_getdata(ktxStream* str, ktx_uint8_t** ppBytes);
+
+#endif /* MEMSTREAM_H */
diff --git a/external/Vulkan/external/ktx/lib/stream.h b/external/Vulkan/external/ktx/lib/stream.h
new file mode 100644
index 0000000000000000000000000000000000000000..9b9a7272d31acd5510056a8203020c441f835a1f
--- /dev/null
+++ b/external/Vulkan/external/ktx/lib/stream.h
@@ -0,0 +1,137 @@
+/* -*- tab-width: 4; -*- */
+/* vi: set sw=2 ts=4 expandtab: */
+
+/*
+ * Copyright (c) 2010-2018 The Khronos Group Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @internal
+ * @file
+ * @~English
+ *
+ * @brief Interface of ktxStream.
+ *
+ * @author Maksim Kolesin
+ * @author Georg Kolling, Imagination Technology
+ * @author Mark Callow, HI Corporation
+ */
+
+#ifndef KTXSTREAM_H
+#define KTXSTREAM_H
+
+#include <sys/types.h>
+#include "ktx.h"
+
+/* 
+ * This is unsigned to allow ktxmemstreams to use the
+ * full amount of memory available. Platforms will
+ * limit the size of ktxfilestreams to, e.g, MAX_LONG
+ * on 32-bit and ktxfilestreams raises errors if
+ * offset values exceed the limits. This choice may
+ * need to be revisited if we ever start needing -ve
+ * offsets.
+ *
+ * Should the 2GB file size handling limit on 32-bit
+ * platforms become a problem, ktxfilestream will have
+ * to be changed to explicitly handle large files by
+ * using the 64-bit stream functions.
+ */
+#if defined(_MSC_VER) && defined(_WIN64)
+  typedef unsigned __int64 ktx_off_t;
+#else
+  typedef   size_t ktx_off_t;
+#endif
+typedef struct ktxMem ktxMem;
+typedef struct ktxStream ktxStream;
+
+enum streamType { eStreamTypeFile = 1, eStreamTypeMemory = 2 };
+
+/**
+ * @internal
+ * @~English
+ * @brief type for a pointer to a stream reading function
+ */
+typedef KTX_error_code (*ktxStream_read)(ktxStream* str, void* dst,
+                                         const ktx_size_t count);
+/**
+ * @internal
+ * @~English
+ * @brief type for a pointer to a stream skipping function
+ */
+typedef KTX_error_code (*ktxStream_skip)(ktxStream* str,
+                                         const ktx_size_t count);
+
+/**
+ * @internal
+ * @~English
+ * @brief type for a pointer to a stream reading function
+ */
+typedef KTX_error_code (*ktxStream_write)(ktxStream* str, const void *src,
+                                          const ktx_size_t size,
+                                          const ktx_size_t count);
+
+/**
+ * @internal
+ * @~English
+ * @brief type for a pointer to a stream position query function
+ */
+typedef KTX_error_code (*ktxStream_getpos)(ktxStream* str, ktx_off_t* const offset);
+
+/**
+ * @internal
+ * @~English
+ * @brief type for a pointer to a stream position query function
+ */
+typedef KTX_error_code (*ktxStream_setpos)(ktxStream* str, const ktx_off_t offset);
+
+/**
+ * @internal
+ * @~English
+ * @brief type for a pointer to a stream size query function
+ */
+typedef KTX_error_code (*ktxStream_getsize)(ktxStream* str, ktx_size_t* const size);
+
+/**
+ * @internal
+ * @~English
+ * @brief Destruct a stream
+ */
+typedef void (*ktxStream_destruct)(ktxStream* str);
+
+/**
+ * @internal
+ * @~English
+ * @brief KTX stream class
+ */
+struct ktxStream
+{
+    ktxStream_read read;   /*!< @internal pointer to function for reading bytes. */
+    ktxStream_skip skip;   /*!< @internal pointer to function for skipping bytes. */
+    ktxStream_write write; /*!< @internal pointer to function for writing bytes. */
+    ktxStream_getpos getpos; /*!< @internal pointer to function for getting current position in stream. */
+    ktxStream_setpos setpos; /*!< @internal pointer to function for setting current position in stream. */
+    ktxStream_getsize getsize; /*!< @internal pointer to function for querying size. */
+    ktxStream_destruct destruct; /*!< @internal destruct the stream. */
+
+    enum streamType type;
+    union {
+        FILE* file;
+        ktxMem* mem;
+    } data;                /**< @internal pointer to the stream data. */
+    ktx_bool_t closeOnDestruct; /**< @internal Close FILE* or dispose of memory on destruct. */
+};
+
+#endif /* KTXSTREAM_H */
diff --git a/external/Vulkan/external/ktx/lib/swap.c b/external/Vulkan/external/ktx/lib/swap.c
new file mode 100644
index 0000000000000000000000000000000000000000..bb2579e41b6f86fe576f1db7bd7394814fc0e25e
--- /dev/null
+++ b/external/Vulkan/external/ktx/lib/swap.c
@@ -0,0 +1,52 @@
+/* -*- tab-width: 4; -*- */
+/* vi: set sw=2 ts=4 expandtab: */
+
+/* $Id: d858975c68f39438233f78cbf217f70fc5ddd316 $ */
+
+/*
+ * Copyright (c) 2010 The Khronos Group Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "KHR/khrplatform.h"
+
+/*
+ * SwapEndian16: Swaps endianness in an array of 16-bit values
+ */
+void
+_ktxSwapEndian16(khronos_uint16_t* pData16, int count)
+{
+    int i;
+    for (i = 0; i < count; ++i)
+    {
+        khronos_uint16_t x = *pData16;
+        *pData16++ = (x << 8) | (x >> 8);
+    }
+}
+
+/*
+ * SwapEndian32: Swaps endianness in an array of 32-bit values
+ */
+void 
+_ktxSwapEndian32(khronos_uint32_t* pData32, int count)
+{
+    int i;
+    for (i = 0; i < count; ++i)
+    {
+        khronos_uint32_t x = *pData32;
+        *pData32++ = (x << 24) | ((x & 0xFF00) << 8) | ((x & 0xFF0000) >> 8) | (x >> 24);
+    }
+}
+
+
diff --git a/external/Vulkan/external/ktx/lib/texture.c b/external/Vulkan/external/ktx/lib/texture.c
new file mode 100644
index 0000000000000000000000000000000000000000..d0f1810e26f6786a83e8af663639be77173e0e85
--- /dev/null
+++ b/external/Vulkan/external/ktx/lib/texture.c
@@ -0,0 +1,1642 @@
+/* -*- tab-width: 4; -*- */
+/* vi: set sw=2 ts=4 expandtab: */
+
+/*
+ * ©2018 Mark Callow.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @internal
+ * @file writer.c
+ * @~English
+ *
+ * @brief ktxTexture implementation.
+ *
+ * @author Mark Callow, www.edgewise-consulting.com
+ */
+
+#ifdef _WIN32
+#define _CRT_SECURE_NO_WARNINGS
+#endif
+
+#include <stdlib.h>
+
+#include "ktx.h"
+#include "ktxint.h"
+#include "stream.h"
+#include "filestream.h"
+#include "memstream.h"
+#include "gl_format.h"
+#include "uthash.h"
+#include <math.h>
+
+/**
+ * @internal
+ * @~English
+ * @brief Internal ktxTexture structure.
+ *
+ * This is kept hidden to avoid burdening applications with the definitions
+ * of GlFormatSize and ktxStream.
+ */
+typedef struct _ktxTextureInt {
+    ktxTexture super;         /*!< Base ktxTexture class. */
+    GlFormatSize formatInfo;  /*!< Info about the image data format. */
+    // The following are needed because image data reading can be delayed.
+    ktx_uint32_t glTypeSize;  /*!< Size of the image data type in bytes. */
+    ktxStream stream;         /*!< Stream connected to KTX source. */
+    ktx_bool_t needSwap;   /*!< If KTX_TRUE, image data needs byte swapping. */
+} ktxTextureInt;
+
+ktx_size_t ktxTexture_GetSize(ktxTexture* This);
+KTX_error_code ktxTexture_LoadImageData(ktxTexture* This,
+                                        ktx_uint8_t* pBuffer,
+                                        ktx_size_t bufSize);
+
+static ktx_size_t ktxTexture_calcDataSize(ktxTexture* This);
+static ktx_uint32_t padRow(ktx_uint32_t* rowBytes);
+
+
+/**
+ * @memberof ktxTexture @private
+ * @brief Construct (initialize) a ktxTexture.
+ *
+ * @param[in] This pointer to a ktxTextureInt-sized block of memory to
+ *                 initialize.
+ * @param[in] createInfo pointer to a ktxTextureCreateInfo struct with
+ *                       information describing the texture.
+ * @param[in] storageAllocation
+ *                       enum indicating whether or not to allocation storage
+ *                       for the texture images.
+ *
+ * @return      KTX_SUCCESS on success, other KTX_* enum values on error.
+ *
+ * @exception KTX_INVALID_VALUE @c glInternalFormat in @p createInfo is not a
+ *                              valid OpenGL internal format value.
+ * @exception KTX_INVALID_VALUE @c numDimensions in @p createInfo is not 1, 2
+ *                              or 3.
+ * @exception KTX_INVALID_VALUE One of <tt>base{Width,Height,Depth}</tt> in
+ *                              @p createInfo is 0.
+ * @exception KTX_INVALID_VALUE @c numFaces in @p createInfo is not 1 or 6.
+ * @exception KTX_INVALID_VALUE @c numLevels in @p createInfo is 0.
+ * @exception KTX_INVALID_OPERATION
+ *                              The <tt>base{Width,Height,Depth}</tt> specified
+ *                              in @p createInfo are inconsistent with
+ *                              @c numDimensions.
+ * @exception KTX_INVALID_OPERATION
+ *                              @p createInfo is requesting a 3D array or
+ *                              3D cubemap texture.
+ * @exception KTX_INVALID_OPERATION
+ *                              @p createInfo is requesting a cubemap with
+ *                              non-square or non-2D images.
+ * @exception KTX_INVALID_OPERATION
+ *                              @p createInfo is requesting more mip levels
+ *                              than needed for the specified
+ *                              <tt>base{Width,Height,Depth}</tt>.
+ * @exception KTX_OUT_OF_MEMORY Not enough memory for the texture's images.
+ */
+static KTX_error_code
+ktxTextureInt_construct(ktxTextureInt* This, ktxTextureCreateInfo* createInfo,
+                        ktxTextureCreateStorageEnum storageAllocation)
+{
+    ktxTexture* super = (ktxTexture*)This;
+    GLuint typeSize;
+    GLenum glFormat;
+    
+    memset(This, 0, sizeof(*This));
+
+    super->glInternalformat = createInfo->glInternalformat;
+    glGetFormatSize(super->glInternalformat, &This->formatInfo);;
+    glFormat= glGetFormatFromInternalFormat(createInfo->glInternalformat);
+    if (glFormat == GL_INVALID_VALUE)
+        return KTX_INVALID_VALUE;
+    super->isCompressed
+                    = (This->formatInfo.flags & GL_FORMAT_SIZE_COMPRESSED_BIT);
+    if (super->isCompressed) {
+        super->glFormat = 0;
+        super->glBaseInternalformat = glFormat;
+        super->glType = 0;
+        This->glTypeSize = 0;
+    } else {
+        super->glBaseInternalformat = super->glFormat = glFormat;
+        super->glType
+                = glGetTypeFromInternalFormat(createInfo->glInternalformat);
+        if (super->glType == GL_INVALID_VALUE)
+            return KTX_INVALID_VALUE;
+        typeSize = glGetTypeSizeFromType(super->glType);
+        assert(typeSize != GL_INVALID_VALUE);
+    
+        /* Do some sanity checking */
+        if (typeSize != 1 &&
+            typeSize != 2 &&
+            typeSize != 4)
+        {
+            /* Only 8, 16, and 32-bit types are supported for byte-swapping.
+             * See UNPACK_SWAP_BYTES & table 8.4 in the OpenGL 4.4 spec.
+             */
+            return KTX_INVALID_VALUE;
+        }
+        This->glTypeSize = typeSize;
+    }
+    
+    /* Check texture dimensions. KTX files can store 8 types of textures:
+     * 1D, 2D, 3D, cube, and array variants of these.
+     */
+    if (createInfo->numDimensions < 1 || createInfo->numDimensions > 3)
+        return KTX_INVALID_VALUE;
+
+    if (createInfo->baseWidth == 0 || createInfo->baseHeight == 0
+        || createInfo->baseDepth == 0)
+        return KTX_INVALID_VALUE;
+
+    super->baseWidth = createInfo->baseWidth;
+    switch (createInfo->numDimensions) {
+      case 1:
+        if (createInfo->baseHeight > 1 || createInfo->baseDepth > 1)
+            return KTX_INVALID_OPERATION;
+        break;
+            
+      case 2:
+        if (createInfo->baseDepth > 1)
+            return KTX_INVALID_OPERATION;
+        super->baseHeight = createInfo->baseHeight;
+        break;
+            
+      case 3:
+        /* 3D array textures and 3D cubemaps are not supported by either
+         * OpenGL or Vulkan.
+         */
+        if (createInfo->isArray || createInfo->numFaces != 1
+            || createInfo->numLayers != 1)
+            return KTX_INVALID_OPERATION;
+        super->baseDepth = createInfo->baseDepth;
+        super->baseHeight = createInfo->baseHeight;
+        break;
+    }
+    super->numDimensions = createInfo->numDimensions;
+
+    if (createInfo->numLayers == 0)
+        return KTX_INVALID_VALUE;
+    super->numLayers = createInfo->numLayers;
+
+    if (createInfo->numFaces == 6) {
+        if (super->numDimensions != 2) {
+            /* cube map needs 2D faces */
+            return KTX_INVALID_OPERATION;
+        }
+        if (createInfo->baseWidth != createInfo->baseHeight) {
+            /* cube maps require square images */
+            return KTX_INVALID_OPERATION;
+        }
+        super->isCubemap = KTX_TRUE;
+    } else if (createInfo->numFaces != 1) {
+        /* numFaces must be either 1 or 6 */
+        return KTX_INVALID_VALUE;
+    }
+    super->numFaces = createInfo->numFaces;
+
+
+    /* Check number of mipmap levels */
+    if (createInfo->numLevels == 0)
+        return KTX_INVALID_VALUE;
+    super->numLevels = createInfo->numLevels;
+    super->generateMipmaps = createInfo->generateMipmaps;
+ 
+    if (createInfo->numLevels > 1) {
+        GLuint max_dim = MAX(MAX(createInfo->baseWidth, createInfo->baseHeight), createInfo->baseDepth);
+        if (max_dim < ((GLuint)1 << (super->numLevels - 1)))
+        {
+            /* Can't have more mip levels than 1 + log2(max(width, height, depth)) */
+            return KTX_INVALID_OPERATION;
+        }
+    }
+    
+    super->numLayers = createInfo->numLayers;
+    super->isArray = createInfo->isArray;
+    
+    ktxHashList_Construct(&super->kvDataHead);
+    if (storageAllocation == KTX_TEXTURE_CREATE_ALLOC_STORAGE) {
+        super->dataSize = ktxTexture_calcDataSize(super);
+        super->pData = malloc(super->dataSize);
+        if (super->pData == NULL)
+            return KTX_OUT_OF_MEMORY;
+    }
+    return KTX_SUCCESS;
+}
+
+/**
+ * @memberof ktxTexture @private
+ * @brief Construct a ktxTexture from a ktxStream reading from a KTX source.
+ *
+ * The caller constructs the stream inside the ktxTextureInt before calling
+ * this.
+ *
+ * The create flag KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT should not be set,
+ * if the ktxTexture is ultimately to be uploaded to OpenGL or Vulkan. This
+ * will minimize memory usage by allowing, for example, loading the images
+ * directly from the source into a Vulkan staging buffer.
+ *
+ * The create flag KTX_TEXTURE_CREATE_RAW_KVDATA_BIT should not be used. It is
+ * provided solely to enable implementation of the @e libktx v1 API on top of
+ * ktxTexture.
+ *
+ * @param[in] This pointer to a ktxTextureInt-sized block of memory to
+ *                 initialize.
+ * @param[in] createFlags bitmask requesting specific actions during creation.
+ *
+ * @return      KTX_SUCCESS on success, other KTX_* enum values on error.
+ *
+ * @exception KTX_FILE_DATA_ERROR
+ *                              Source data is inconsistent with the KTX
+ *                              specification.
+ * @exception KTX_FILE_READ_ERROR
+ *                              An error occurred while reading the source.
+ * @exception KTX_FILE_UNEXPECTED_EOF
+ *                              Not enough data in the source.
+ * @exception KTX_OUT_OF_MEMORY Not enough memory to load either the images or
+ *                              the key-value data.
+ * @exception KTX_UNKNOWN_FILE_FORMAT
+ *                              The source is not in KTX format.
+ * @exception KTX_UNSUPPORTED_TEXTURE_TYPE
+ *                              The source describes a texture type not
+ *                              supported by OpenGL or Vulkan, e.g, a 3D array.
+ */
+static KTX_error_code
+ktxTextureInt_constructFromStream(ktxTextureInt* This,
+                                  ktxTextureCreateFlags createFlags)
+{
+    ktxTexture* super = (ktxTexture*)This;
+    KTX_error_code result;
+    KTX_header header;
+    KTX_supplemental_info suppInfo;
+    ktxStream* stream;
+    ktx_off_t pos;
+    ktx_size_t size;
+    
+    assert(This != NULL);
+    assert(This->stream.data.mem != NULL);
+    assert(This->stream.type == eStreamTypeFile
+           || This->stream.type == eStreamTypeMemory);
+    stream = &This->stream;
+  
+    // Read header.
+    result = stream->read(stream, &header, KTX_HEADER_SIZE);
+    if (result != KTX_SUCCESS)
+        return result;
+    
+    result = _ktxCheckHeader(&header, &suppInfo);
+    if (result != KTX_SUCCESS)
+        return result;
+    
+    /*
+     * Initialize from header info.
+     */
+    super->glFormat = header.glFormat;
+    super->glInternalformat = header.glInternalformat;
+    super->glType = header.glType;
+    glGetFormatSize(super->glInternalformat, &This->formatInfo);
+    super->glBaseInternalformat = header.glBaseInternalformat;
+    super->numDimensions = suppInfo.textureDimension;
+    super->baseWidth = header.pixelWidth;
+    assert(suppInfo.textureDimension > 0 && suppInfo.textureDimension < 4);
+    switch (suppInfo.textureDimension) {
+      case 1:
+        super->baseHeight = super->baseDepth = 1;
+        break;
+      case 2:
+        super->baseHeight = header.pixelHeight;
+        super->baseDepth = 1;
+        break;
+      case 3:
+        super->baseHeight = header.pixelHeight;
+        super->baseDepth = header.pixelDepth;
+        break;
+    }
+    if (header.numberOfArrayElements > 0) {
+        super->numLayers = header.numberOfArrayElements;
+        super->isArray = KTX_TRUE;
+    } else {
+        super->numLayers = 1;
+        super->isArray = KTX_FALSE;
+    }
+    super->numFaces = header.numberOfFaces;
+    if (header.numberOfFaces == 6)
+        super->isCubemap = KTX_TRUE;
+    else
+        super->isCubemap = KTX_FALSE;
+    super->numLevels = header.numberOfMipmapLevels;
+    super->isCompressed = suppInfo.compressed;
+    super->generateMipmaps = suppInfo.generateMipmaps;
+    if (header.endianness == KTX_ENDIAN_REF_REV)
+        This->needSwap = KTX_TRUE;
+    This->glTypeSize = header.glTypeSize;
+
+    /*
+     * Make an empty hash list.
+     */
+    ktxHashList_Construct(&super->kvDataHead);
+    /*
+     * Load KVData.
+     */
+    if (header.bytesOfKeyValueData > 0) {
+        if (!(createFlags & KTX_TEXTURE_CREATE_SKIP_KVDATA_BIT)) {
+            ktx_uint32_t kvdLen = header.bytesOfKeyValueData;
+            ktx_uint8_t* pKvd;
+
+            pKvd = malloc(kvdLen);
+            if (pKvd == NULL)
+                return KTX_OUT_OF_MEMORY;
+            
+            result = stream->read(stream, pKvd, kvdLen);
+            if (result != KTX_SUCCESS)
+                return result;
+
+            if (This->needSwap) {
+                /* Swap the counts inside the key & value data. */
+                ktx_uint8_t* src = pKvd;
+                ktx_uint8_t* end = pKvd + kvdLen;
+                while (src < end) {
+                    ktx_uint32_t keyAndValueByteSize = *((ktx_uint32_t*)src);
+                    _ktxSwapEndian32(&keyAndValueByteSize, 1);
+                    src += _KTX_PAD4(keyAndValueByteSize);
+                }
+            }
+            
+            if (!(createFlags & KTX_TEXTURE_CREATE_RAW_KVDATA_BIT)) {
+                result = ktxHashList_Deserialize(&super->kvDataHead,
+                                                 kvdLen, pKvd);
+                if (result != KTX_SUCCESS) {
+                    free(pKvd);
+                    return result;
+                }
+            } else {
+                super->kvDataLen = kvdLen;
+                super->kvData = pKvd;
+            }
+        } else {
+            stream->skip(stream, header.bytesOfKeyValueData);
+        }
+    }
+    
+    /*
+     * Get the size of the image data.
+     */
+    result = stream->getsize(stream, &size);
+    if (result == KTX_SUCCESS) {
+        result = stream->getpos(stream, &pos);
+        if (result == KTX_SUCCESS)
+            super->dataSize = size - pos
+                                 /* Remove space for faceLodSize fields */
+                                 - super->numLevels * sizeof(ktx_uint32_t);
+    }
+
+    /*
+     * Load the images, if requested.
+     */
+    if (result == KTX_SUCCESS
+        && (createFlags & KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT)) {
+        result = ktxTexture_LoadImageData((ktxTexture*)super, NULL, 0);
+    }
+    return result;
+}
+
+/**
+ * @memberof ktxTexture @private
+ * @brief Construct a ktxTexture from a stdio stream reading from a KTX source.
+ *
+ * See ktxTextureInt_constructFromStream for details.
+ *
+ * @note Do not close the stdio stream until you are finished with the texture
+ *       object.
+ *
+ * @param[in] This pointer to a ktxTextureInt-sized block of memory to
+ *                 initialize.
+ * @param[in] stdioStream a stdio FILE pointer opened on the source.
+ * @param[in] createFlags bitmask requesting specific actions during creation.
+ *
+ * @return      KTX_SUCCESS on success, other KTX_* enum values on error.
+ *
+ * @exception KTX_INVALID_VALUE Either @p stdiostream or @p This is null.
+ *
+ * For other exceptions, see ktxTexture_constructFromStream().
+ */
+static KTX_error_code
+ktxTextureInt_constructFromStdioStream(ktxTextureInt* This, FILE* stdioStream,
+                                       ktxTextureCreateFlags createFlags)
+{
+    KTX_error_code result;
+
+    if (stdioStream == NULL || This == NULL)
+        return KTX_INVALID_VALUE;
+    
+    memset(This, 0, sizeof(*This));
+    
+    result = ktxFileStream_construct(&This->stream, stdioStream, KTX_FALSE);
+    if (result == KTX_SUCCESS)
+        result = ktxTextureInt_constructFromStream(This, createFlags);
+    return result;
+}
+
+/**
+ * @memberof ktxTexture @private
+ * @brief Construct a ktxTexture from a named KTX file.
+ *
+ * See ktxTextureInt_constructFromStream for details.
+ *
+ * @param[in] This pointer to a ktxTextureInt-sized block of memory to
+ *                 initialize.
+ * @param[in] filename    pointer to a char array containing the file name.
+ * @param[in] createFlags bitmask requesting specific actions during creation.
+ *
+ * @return      KTX_SUCCESS on success, other KTX_* enum values on error.
+ *
+ * @exception KTX_FILE_OPEN_FAILED The file could not be opened.
+ * @exception KTX_INVALID_VALUE @p filename is @c NULL.
+ *
+ * For other exceptions, see ktxTexture_constructFromStream().
+ */
+static KTX_error_code
+ktxTextureInt_constructFromNamedFile(ktxTextureInt* This,
+                                     const char* const filename,
+                                     ktxTextureCreateFlags createFlags)
+{
+    KTX_error_code result;
+    FILE* file;
+    
+    if (This == NULL || filename == NULL)
+        return KTX_INVALID_VALUE;
+    
+    memset(This, 0, sizeof(*This));
+
+    file = fopen(filename, "rb");
+    if (!file)
+       return KTX_FILE_OPEN_FAILED;
+    
+    result = ktxFileStream_construct(&This->stream, file, KTX_TRUE);
+    if (result == KTX_SUCCESS)
+        result = ktxTextureInt_constructFromStream(This, createFlags);
+    
+    return result;
+}
+
+/**
+ * @memberof ktxTexture @private
+ * @brief Construct a ktxTexture from KTX-formatted data in memory.
+ *
+ * See ktxTextureInt_constructFromStream for details.
+ *
+ * @param[in] This  pointer to a ktxTextureInt-sized block of memory to
+ *                  initialize.
+ * @param[in] bytes pointer to the memory containing the serialized KTX data.
+ * @param[in] size  length of the KTX data in bytes.
+ * @param[in] createFlags bitmask requesting specific actions during creation.
+ *
+ * @return      KTX_SUCCESS on success, other KTX_* enum values on error.
+ *
+ * @exception KTX_INVALID_VALUE Either @p bytes is NULL or @p size is 0.
+ *
+ * For other exceptions, see ktxTexture_constructFromStream().
+ */
+static KTX_error_code
+ktxTextureInt_constructFromMemory(ktxTextureInt* This,
+                                  const ktx_uint8_t* bytes, ktx_size_t size,
+                                  ktxTextureCreateFlags createFlags)
+{
+    KTX_error_code result;
+    
+    if (bytes == NULL || size == 0)
+        return KTX_INVALID_VALUE;
+    
+    memset(This, 0, sizeof(*This));
+    
+    result = ktxMemStream_construct_ro(&This->stream, bytes, size);
+    if (result == KTX_SUCCESS)
+        result = ktxTextureInt_constructFromStream(This, createFlags);
+    
+    return result;
+}
+
+/**
+ * @memberof ktxTexture @private
+ * @~English
+ * @brief Free the memory associated with the texture contents
+ *
+ * @param[in] This pointer to the ktxTextureInt whose texture contents are
+ *                 to be freed.
+ */
+void
+ktxTextureInt_destruct(ktxTextureInt* This)
+{
+    ktxTexture* super = (ktxTexture*)This;
+    if (This->stream.data.file != NULL)
+        This->stream.destruct(&This->stream);
+    if (super->kvDataHead != NULL)
+        ktxHashList_Destruct(&super->kvDataHead);
+    if (super->kvData != NULL)
+        free(super->kvData);
+    if (super->pData != NULL)
+        free(super->pData);
+}
+
+/**
+ * @memberof ktxTexture
+ * @ingroup writer
+ * @brief Create a new empty ktxTexture.
+ *
+ * The address of the newly created ktxTexture is written to the location
+ * pointed at by @p newTex.
+ *
+ * @param[in] createInfo pointer to a ktxTextureCreateInfo struct with
+ *                       information describing the texture.
+ * @param[in] storageAllocation
+ *                       enum indicating whether or not to allocate storage
+ *                       for the texture images.
+ * @param[in,out] newTex pointer to a location in which store the address of
+ *                       the newly created texture.
+ *
+ * @return      KTX_SUCCESS on success, other KTX_* enum values on error.
+ *
+ * @exception KTX_INVALID_VALUE @c glInternalFormat in @p createInfo is not a
+ *                              valid OpenGL internal format value.
+ * @exception KTX_INVALID_VALUE @c numDimensions in @p createInfo is not 1, 2
+ *                              or 3.
+ * @exception KTX_INVALID_VALUE One of <tt>base{Width,Height,Depth}</tt> in
+ *                              @p createInfo is 0.
+ * @exception KTX_INVALID_VALUE @c numFaces in @p createInfo is not 1 or 6.
+ * @exception KTX_INVALID_VALUE @c numLevels in @p createInfo is 0.
+ * @exception KTX_INVALID_OPERATION
+ *                              The <tt>base{Width,Height,Depth}</tt> specified
+ *                              in @p createInfo are inconsistent with
+ *                              @c numDimensions.
+ * @exception KTX_INVALID_OPERATION
+ *                              @p createInfo is requesting a 3D array or
+ *                              3D cubemap texture.
+ * @exception KTX_INVALID_OPERATION
+ *                              @p createInfo is requesting a cubemap with
+ *                              non-square or non-2D images.
+ * @exception KTX_INVALID_OPERATION
+ *                              @p createInfo is requesting more mip levels
+ *                              than needed for the specified
+ *                              <tt>base{Width,Height,Depth}</tt>.
+ * @exception KTX_OUT_OF_MEMORY Not enough memory for the texture's images.
+ */
+KTX_error_code
+ktxTexture_Create(ktxTextureCreateInfo* createInfo,
+                  ktxTextureCreateStorageEnum storageAllocation,
+                  ktxTexture** newTex)
+{
+    KTX_error_code result;
+    
+    if (newTex == NULL)
+        return KTX_INVALID_VALUE;
+    
+    ktxTextureInt* tex = (ktxTextureInt*)malloc(sizeof(ktxTextureInt));
+    if (tex == NULL)
+        return KTX_OUT_OF_MEMORY;
+    
+    result = ktxTextureInt_construct(tex, createInfo, storageAllocation);
+    if (result == KTX_SUCCESS)
+        *newTex = (ktxTexture*)tex;
+    else {
+        free(tex);
+        *newTex = NULL;
+    }
+    return result;
+}
+
+/**
+ * @defgroup reader Reader
+ * @brief Read KTX-formatted data.
+ * @{
+ */
+
+/**
+ * @memberof ktxTexture
+ * @~English
+ * @brief Create a ktxTexture from a stdio stream reading from a KTX source.
+ *
+ * The address of a newly created ktxTexture reflecting the contents of the
+ * stdio stream is written to the location pointed at by @p newTex.
+ *
+ * The create flag KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT should not be set,
+ * if the ktxTexture is ultimately to be uploaded to OpenGL or Vulkan. This
+ * will minimize memory usage by allowing, for example, loading the images
+ * directly from the source into a Vulkan staging buffer.
+ *
+ * The create flag KTX_TEXTURE_CREATE_RAW_KVDATA_BIT should not be used. It is
+ * provided solely to enable implementation of the @e libktx v1 API on top of
+ * ktxTexture.
+ *
+ * @param[in] stdioStream stdio FILE pointer created from the desired file.
+ * @param[in] createFlags bitmask requesting specific actions during creation.
+ * @param[in,out] newTex  pointer to a location in which store the address of
+ *                        the newly created texture.
+ *
+ * @return      KTX_SUCCESS on success, other KTX_* enum values on error.
+ *
+ * @exception KTX_INVALID_VALUE @p newTex is @c NULL.
+ * @exception KTX_FILE_DATA_ERROR
+ *                              Source data is inconsistent with the KTX
+ *                              specification.
+ * @exception KTX_FILE_READ_ERROR
+ *                              An error occurred while reading the source.
+ * @exception KTX_FILE_UNEXPECTED_EOF
+ *                              Not enough data in the source.
+ * @exception KTX_OUT_OF_MEMORY Not enough memory to create the texture object,
+ *                              load the images or load the key-value data.
+ * @exception KTX_UNKNOWN_FILE_FORMAT
+ *                              The source is not in KTX format.
+ * @exception KTX_UNSUPPORTED_TEXTURE_TYPE
+ *                              The source describes a texture type not
+ *                              supported by OpenGL or Vulkan, e.g, a 3D array.
+ */
+KTX_error_code
+ktxTexture_CreateFromStdioStream(FILE* stdioStream,
+                                 ktxTextureCreateFlags createFlags,
+                                 ktxTexture** newTex)
+{
+    KTX_error_code result;
+    if (newTex == NULL)
+        return KTX_INVALID_VALUE;
+    
+    ktxTextureInt* tex = (ktxTextureInt*)malloc(sizeof(ktxTextureInt));
+    if (tex == NULL)
+        return KTX_OUT_OF_MEMORY;
+    
+    result = ktxTextureInt_constructFromStdioStream(tex, stdioStream,
+                                                    createFlags);
+    if (result == KTX_SUCCESS)
+        *newTex = (ktxTexture*)tex;
+    else {
+        free(tex);
+        *newTex = NULL;
+    }
+    return result;
+}
+
+/**
+ * @memberof ktxTexture
+ * @~English
+ * @brief Create a ktxTexture from a named KTX file.
+ *
+ * The address of a newly created ktxTexture reflecting the contents of the
+ * file is written to the location pointed at by @p newTex.
+ *
+ * The create flag KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT should not be set,
+ * if the ktxTexture is ultimately to be uploaded to OpenGL or Vulkan. This
+ * will minimize memory usage by allowing, for example, loading the images
+ * directly from the source into a Vulkan staging buffer.
+ *
+ * The create flag KTX_TEXTURE_CREATE_RAW_KVDATA_BIT should not be used. It is
+ * provided solely to enable implementation of the @e libktx v1 API on top of
+ * ktxTexture.
+ *
+ * @param[in] filename    pointer to a char array containing the file name.
+ * @param[in] createFlags bitmask requesting specific actions during creation.
+ * @param[in,out] newTex  pointer to a location in which store the address of
+ *                        the newly created texture.
+ *
+ * @return      KTX_SUCCESS on success, other KTX_* enum values on error.
+
+ * @exception KTX_FILE_OPEN_FAILED The file could not be opened.
+ * @exception KTX_INVALID_VALUE @p filename is @c NULL.
+ *
+ * For other exceptions, see ktxTexture_CreateFromStdioStream().
+ */
+KTX_error_code
+ktxTexture_CreateFromNamedFile(const char* const filename,
+                               ktxTextureCreateFlags createFlags,
+                               ktxTexture** newTex)
+{
+    KTX_error_code result;
+
+    if (newTex == NULL)
+        return KTX_INVALID_VALUE;
+
+    ktxTextureInt* tex = (ktxTextureInt*)malloc(sizeof(ktxTextureInt));
+    if (tex == NULL)
+        return KTX_OUT_OF_MEMORY;
+    
+    result = ktxTextureInt_constructFromNamedFile(tex, filename, createFlags);
+    if (result == KTX_SUCCESS)
+        *newTex = (ktxTexture*)tex;
+    else {
+        free(tex);
+        *newTex = NULL;
+    }
+    return result;
+}
+
+/**
+ * @memberof ktxTexture
+ * @~English
+ * @brief Create a ktxTexture from KTX-formatted data in memory.
+ *
+ * The address of a newly created ktxTexture reflecting the contents of the
+ * serialized KTX data is written to the location pointed at by @p newTex.
+ *
+ * The create flag KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT should not be set,
+ * if the ktxTexture is ultimately to be uploaded to OpenGL or Vulkan. This
+ * will minimize memory usage by allowing, for example, loading the images
+ * directly from the source into a Vulkan staging buffer.
+ *
+ * The create flag KTX_TEXTURE_CREATE_RAW_KVDATA_BIT should not be used. It is
+ * provided solely to enable implementation of the @e libktx v1 API on top of
+ * ktxTexture.
+ *
+ * @param[in] bytes pointer to the memory containing the serialized KTX data.
+ * @param[in] size  length of the KTX data in bytes.
+ * @param[in] createFlags bitmask requesting specific actions during creation.
+ * @param[in,out] newTex  pointer to a location in which store the address of
+ *                        the newly created texture.
+ *
+ * @return      KTX_SUCCESS on success, other KTX_* enum values on error.
+ *
+ * @exception KTX_INVALID_VALUE Either @p bytes is NULL or @p size is 0.
+ *
+ * For other exceptions, see ktxTexture_CreateFromStdioStream().
+ */
+KTX_error_code
+ktxTexture_CreateFromMemory(const ktx_uint8_t* bytes, ktx_size_t size,
+                            ktxTextureCreateFlags createFlags,
+                            ktxTexture** newTex)
+{
+    KTX_error_code result;
+    if (newTex == NULL)
+        return KTX_INVALID_VALUE;
+    
+    ktxTextureInt* tex = (ktxTextureInt*)malloc(sizeof(ktxTextureInt));
+    if (tex == NULL)
+        return KTX_OUT_OF_MEMORY;
+    
+    result = ktxTextureInt_constructFromMemory(tex, bytes, size,
+                                               createFlags);
+    if (result == KTX_SUCCESS)
+        *newTex = (ktxTexture*)tex;
+    else {
+        free(tex);
+        *newTex = NULL;
+    }
+    return result;
+}
+
+/**
+ * @memberof ktxTexture
+ * @~English
+ * @brief Destroy a ktxTexture object.
+ *
+ * This frees the memory associated with the texture contents and the memory
+ * of the ktxTexture object. This does @e not delete any OpenGL or Vulkan
+ * texture objects created by ktxTexture_GLUpload or ktxTexture_VkUpload.
+ *
+ * @param[in] This pointer to the ktxTexture object to destroy
+ */
+void
+ktxTexture_Destroy(ktxTexture* This)
+{
+    ktxTextureInt_destruct((ktxTextureInt*)This);
+    free(This);
+}
+
+/**
+ * @memberof ktxTexture
+ * @~English
+ * @brief Return a pointer to the texture image data.
+ *
+ * @param[in] This pointer to the ktxTexture object of interest.
+ */
+ktx_uint8_t*
+ktxTexture_GetData(ktxTexture* This)
+{
+    return This->pData;
+}
+
+/**
+ * @memberof ktxTexture
+ * @~English
+ * @brief Return the total size of the texture image data in bytes.
+ *
+ * @param[in] This pointer to the ktxTexture object of interest.
+ */
+ktx_size_t
+ktxTexture_GetSize(ktxTexture* This)
+{
+    assert(This != NULL);
+    return This->dataSize;
+}
+
+/**
+ * @memberof ktxTexture
+ * @~English
+ * @brief Return the size in bytes of an elements of a texture's
+ *        images.
+ *
+ * For uncompressed textures an element is one texel. For compressed
+ * textures it is one block.
+ *
+ * @param[in]     This     pointer to the ktxTexture object of interest.
+ */
+ktx_uint32_t
+ktxTexture_GetElementSize(ktxTexture* This)
+{
+    GlFormatSize* formatInfo;
+
+    assert (This != NULL);
+
+    formatInfo = &((ktxTextureInt*)This)->formatInfo;
+    return (formatInfo->blockSizeInBits / 8);
+}
+
+/**
+ * @memberof ktxTexture
+ * @~English
+ * @brief Calculate & return the size in bytes of an image at the specified
+ *        mip level.
+ *
+ * For arrays, this is the size of layer, for cubemaps, the size of a face
+ * and for 3D textures, the size of a depth slice.
+ *
+ * The size reflects the padding of each row to KTX_GL_UNPACK_ALIGNMENT.
+ *
+ * @param[in]     This     pointer to the ktxTexture object of interest.
+ * @param[in]     level    level of interest. *
+ */
+ktx_size_t
+ktxTexture_GetImageSize(ktxTexture* This, ktx_uint32_t level)
+{
+    GlFormatSize* formatInfo;
+    struct blockCount {
+        ktx_uint32_t x, y, z;
+    } blockCount;
+    ktx_uint32_t blockSizeInBytes;
+    ktx_uint32_t rowBytes;
+
+    assert (This != NULL);
+
+    formatInfo = &((ktxTextureInt*)This)->formatInfo;
+
+    float levelWidth  = (float) (This->baseWidth >> level);
+    float levelHeight = (float) (This->baseHeight >> level);
+    blockCount.x      = (ktx_uint32_t) ceilf(levelWidth / formatInfo->blockWidth);
+    blockCount.y      = (ktx_uint32_t) ceilf(levelHeight / formatInfo->blockHeight);
+    blockCount.x      = MAX(1, blockCount.x);
+    blockCount.y      = MAX(1, blockCount.y);
+    blockSizeInBytes  = formatInfo->blockSizeInBits / 8;
+
+    if (formatInfo->flags & GL_FORMAT_SIZE_COMPRESSED_BIT) {
+        assert(This->isCompressed);
+        return blockCount.x * blockCount.y * blockSizeInBytes;
+    } else {
+        assert(formatInfo->blockWidth == formatInfo->blockHeight == formatInfo->blockDepth == 1);
+        rowBytes = blockCount.x * blockSizeInBytes;
+        (void)padRow(&rowBytes);
+        return rowBytes * blockCount.y;
+    }
+}
+
+/**
+ * @memberof ktxTexture
+ * @~English
+ * @brief Load all the image data from the ktxTexture's source.
+ *
+ * The data is loaded into the provided buffer or to an internally allocated
+ * buffer, if @p pBuffer is @c NULL.
+ *
+ * @param[in] This pointer to the ktxTexture object of interest.
+ * @param[in] pBuffer pointer to the buffer in which to load the image data.
+ * @param[in] bufSize size of the buffer pointed at by @p pBuffer.
+ *
+ * @return      KTX_SUCCESS on success, other KTX_* enum values on error.
+ *
+ * @exception KTX_INVALID_VALUE @p This is NULL.
+ * @exception KTX_INVALID_VALUE @p bufSize is less than the the image data size.
+ * @exception KTX_INVALID_OPERATION
+ *                              The data has already been loaded or the
+ *                              ktxTexture was not created from a KTX source.
+ * @exception KTX_OUT_OF_MEMORY Insufficient memory for the image data.
+ */
+KTX_error_code
+ktxTexture_LoadImageData(ktxTexture* This,
+                         ktx_uint8_t* pBuffer, ktx_size_t bufSize)
+{
+    ktxTextureInt*  subthis = (ktxTextureInt*)This;
+    ktx_uint32_t    miplevel;
+    ktx_uint8_t*    pDest;
+    KTX_error_code  result = KTX_SUCCESS;
+
+    if (This == NULL)
+        return KTX_INVALID_VALUE;
+    
+    if (subthis->stream.data.file == NULL)
+        // This Texture not created from a stream or images already loaded;
+        return KTX_INVALID_OPERATION;
+
+    if (pBuffer == NULL) {
+        This->pData = malloc(This->dataSize);
+        if (This->pData == NULL)
+            return KTX_OUT_OF_MEMORY;
+        pDest = This->pData;
+    } else if (bufSize < This->dataSize) {
+        return KTX_INVALID_VALUE;
+    } else {
+        pDest = pBuffer;
+    }
+
+    // Need to loop through for correct byte swapping
+    for (miplevel = 0; miplevel < This->numLevels; ++miplevel)
+    {
+        ktx_uint32_t faceLodSize;
+        ktx_uint32_t faceLodSizePadded;
+        ktx_uint32_t face;
+        ktx_uint32_t innerIterations;
+
+        result = subthis->stream.read(&subthis->stream, &faceLodSize,
+                                      sizeof(ktx_uint32_t));
+        if (result != KTX_SUCCESS) {
+            goto cleanup;
+        }
+        if (subthis->needSwap) {
+            _ktxSwapEndian32(&faceLodSize, 1);
+        }
+#if (KTX_GL_UNPACK_ALIGNMENT != 4)
+        faceLodSizePadded = _KTX_PAD4(faceLodSize);
+#else
+        faceLodSizePadded = faceLodSize;
+#endif
+        
+        if (This->isCubemap && !This->isArray)
+            innerIterations = This->numFaces;
+        else
+            innerIterations = 1;
+        for (face = 0; face < innerIterations; ++face)
+        {
+            result = subthis->stream.read(&subthis->stream, pDest,
+                                          faceLodSizePadded);
+            if (result != KTX_SUCCESS) {
+                goto cleanup;
+            }
+            
+            /* Perform endianness conversion on texture data */
+            if (subthis->needSwap) {
+                if (subthis->glTypeSize == 2)
+                    _ktxSwapEndian16((ktx_uint16_t*)pDest, faceLodSize / 2);
+                else if (subthis->glTypeSize == 4)
+                    _ktxSwapEndian32((ktx_uint32_t*)pDest, faceLodSize / 4);
+            }
+            
+            pDest += faceLodSizePadded;
+        }
+    }
+
+cleanup:
+    // No further need for This->
+    subthis->stream.destruct(&subthis->stream);
+    return result;
+}
+
+/**
+ * @memberof ktxTexture
+ * @~English
+ * @brief Iterate over the images in a ktxTexture object.
+ *
+ * Blocks of image data are passed to an application-supplied callback
+ * function. This is not a strict per-image iteration. Rather it reflects how
+ * OpenGL needs the images. For most textures the block of data includes all
+ * images of a mip level which implies all layers of an array. However, for
+ * non-array cube map textures the block is a single face of the mip level,
+ * i.e the callback is called once for each face.
+ *
+ * This function works even if @p This->pData == 0 so it can be used to
+ * obtain offsets and sizes for each level by callers who have loaded the data
+ * externally.
+ *
+ * @param[in]     This      pointer to the ktxTexture object of interest.
+ * @param[in,out] iterCb    the address of a callback function which is called
+ *                          with the data for each image block.
+ * @param[in,out] userdata  the address of application-specific data which is
+ *                          passed to the callback along with the image data.
+ *
+ * @return  KTX_SUCCESS on success, other KTX_* enum values on error. The
+ *          following are returned directly by this function. @p iterCb may
+ *          return these for other causes or may return additional errors.
+ *
+ * @exception KTX_FILE_DATA_ERROR   Mip level sizes are increasing not
+ *                                  decreasing
+ * @exception KTX_INVALID_VALUE     @p This is @c NULL or @p iterCb is @c NULL.
+ *
+ */
+KTX_error_code
+ktxTexture_IterateLevelFaces(ktxTexture* This, PFNKTXITERCB iterCb,
+                             void* userdata)
+{
+    ktx_uint32_t    miplevel;
+    KTX_error_code  result = KTX_SUCCESS;
+    
+    if (This == NULL)
+        return KTX_INVALID_VALUE;
+    
+    if (iterCb == NULL)
+        return KTX_INVALID_VALUE;
+    
+    for (miplevel = 0; miplevel < This->numLevels; ++miplevel)
+    {
+        ktx_uint32_t faceLodSize;
+        ktx_uint32_t face;
+        ktx_uint32_t innerIterations;
+        GLsizei      width, height, depth;
+        
+        /* Array textures have the same number of layers at each mip level. */
+        width = MAX(1, This->baseWidth  >> miplevel);
+        height = MAX(1, This->baseHeight >> miplevel);
+        depth = MAX(1, This->baseDepth  >> miplevel);
+
+        faceLodSize = (ktx_uint32_t)ktxTexture_faceLodSize(This, miplevel);
+
+        /* All array layers are passed in a group because that is how
+         * GL & Vulkan need them. Hence no
+         *    for (layer = 0; layer < This->numLayers)
+         */
+        if (This->isCubemap && !This->isArray)
+            innerIterations = This->numFaces;
+        else
+            innerIterations = 1;
+        for (face = 0; face < innerIterations; ++face)
+        {
+            /* And all z_slices are also passed as a group hence no
+             *    for (slice = 0; slice < This->depth)
+             */
+            ktx_size_t offset;
+
+            ktxTexture_GetImageOffset(This, miplevel, 0, face, &offset);
+            result = iterCb(miplevel, face,
+                             width, height, depth,
+                             faceLodSize, This->pData + offset, userdata);
+            
+            if (result != KTX_SUCCESS)
+                break;
+        }
+    }
+    
+    return result;
+}
+/**
+ * @memberof ktxTexture
+ * @~English
+ * @brief Iterate over the images in a ktxTexture object while loading the
+ *        image data.
+ *
+ * This operates similarly to ktxTexture_IterateLevelFaces() except that it
+ * loads the images from the ktxTexture's source to a temporary buffer
+ * while iterating. The callback function must copy the image data if it
+ * wishes to preserve it as the temporary buffer is reused for each level and
+ * is freed when this function exits.
+ *
+ * This function is helpful for reducing memory usage when uploading the data
+ * to a graphics API.
+ *
+ * @param[in]     This     pointer to the ktxTexture object of interest.
+ * @param[in,out] iterCb   the address of a callback function which is called
+ *                         with the data for each image.
+ * @param[in,out] userdata the address of application-specific data which is
+ *                         passed to the callback along with the image data.
+ *
+ * @return  KTX_SUCCESS on success, other KTX_* enum values on error. The
+ *          following are returned directly by this function. @p iterCb may
+ *          return these for other causes or may return additional errors.
+ *
+ * @exception KTX_FILE_DATA_ERROR   mip level sizes are increasing not
+ *                                  decreasing
+ * @exception KTX_INVALID_OPERATION the ktxTexture was not created from a
+ *                                  stream, i.e there is no data to load, or
+ *                                  this ktxTexture's images have already
+ *                                  been loaded.
+ * @exception KTX_INVALID_VALUE     @p This is @c NULL or @p iterCb is @c NULL.
+ * @exception KTX_OUT_OF_MEMORY     not enough memory to allocate a block to
+ *                                  hold the base level image.
+ */
+KTX_error_code
+ktxTexture_IterateLoadLevelFaces(ktxTexture* This, PFNKTXITERCB iterCb,
+                                 void* userdata)
+{
+    ktxTextureInt*  subthis = (ktxTextureInt*)This;
+    ktx_uint32_t    dataSize = 0;
+    ktx_uint32_t    miplevel;
+    KTX_error_code  result = KTX_SUCCESS;
+    void*           data = NULL;
+    
+    if (This == NULL)
+        return KTX_INVALID_VALUE;
+    
+    if (iterCb == NULL)
+        return KTX_INVALID_VALUE;
+    
+    if (subthis->stream.data.file == NULL)
+        // This Texture not created from a stream or images are already loaded.
+        return KTX_INVALID_OPERATION;
+    
+    for (miplevel = 0; miplevel < This->numLevels; ++miplevel)
+    {
+        ktx_uint32_t faceLodSize;
+        ktx_uint32_t faceLodSizePadded;
+        ktx_uint32_t face;
+        ktx_uint32_t innerIterations;
+        GLsizei      width, height, depth;
+        
+        /* Array textures have the same number of layers at each mip level. */
+        width = MAX(1, This->baseWidth  >> miplevel);
+        height = MAX(1, This->baseHeight >> miplevel);
+        depth = MAX(1, This->baseDepth  >> miplevel);
+        
+        result = subthis->stream.read(&subthis->stream, &faceLodSize,
+                                      sizeof(ktx_uint32_t));
+        if (result != KTX_SUCCESS) {
+            goto cleanup;
+        }
+        if (subthis->needSwap) {
+            _ktxSwapEndian32(&faceLodSize, 1);
+        }
+#if (KTX_GL_UNPACK_ALIGNMENT != 4)
+        faceLodSizePadded = _KTX_PAD4(faceLodSize);
+#else
+        faceLodSizePadded = faceLodSize;
+#endif
+        if (!data) {
+            /* allocate memory sufficient for the base miplevel */
+            data = malloc(faceLodSizePadded);
+            if (!data) {
+                result = KTX_OUT_OF_MEMORY;
+                goto cleanup;
+            }
+            dataSize = faceLodSizePadded;
+        }
+        else if (dataSize < faceLodSizePadded) {
+            /* subsequent miplevels cannot be larger than the base miplevel */
+            result = KTX_FILE_DATA_ERROR;
+            goto cleanup;
+        }
+        
+        /* All array layers are passed in a group because that is how
+         * GL & Vulkan need them. Hence no
+         *    for (layer = 0; layer < This->numLayers)
+         */
+        if (This->isCubemap && !This->isArray)
+            innerIterations = This->numFaces;
+        else
+            innerIterations = 1;
+        for (face = 0; face < innerIterations; ++face)
+        {
+            /* And all z_slices are also passed as a group hence no
+             *    for (z_slice = 0; z_slice < This->depth)
+             */
+            result = subthis->stream.read(&subthis->stream, data,
+                                          faceLodSizePadded);
+            if (result != KTX_SUCCESS) {
+                goto cleanup;
+            }
+            
+            /* Perform endianness conversion on texture data */
+            if (subthis->needSwap) {
+                if (subthis->glTypeSize == 2)
+                    _ktxSwapEndian16((ktx_uint16_t*)data, faceLodSize / 2);
+                else if (subthis->glTypeSize == 4)
+                    _ktxSwapEndian32((ktx_uint32_t*)data, faceLodSize / 4);
+            }
+            
+            result = iterCb(miplevel, face,
+                             width, height, depth,
+                             faceLodSize, data, userdata);
+            
+            if (result != KTX_SUCCESS)
+                goto cleanup;
+        }
+    }
+    
+cleanup:
+    free(data);
+    // No further need for this.
+    subthis->stream.destruct(&subthis->stream);
+
+    return result;
+}
+
+/**
+ * @memberof ktxTexture
+ * @~English
+ * @brief Iterate over the mip levels in a ktxTexture object.
+ *
+ * This is almost identical to ktxTexture_IterateLevelFaces(). The difference is
+ * that the blocks of image data for non-array cube maps include all faces of
+ * a mip level.
+ *
+ * This function works even if @p This->pData == 0 so it can be used to
+ * obtain offsets and sizes for each level by callers who have loaded the data
+ * externally.
+ *
+ * @param[in]     This     handle of the ktxTexture opened on the data.
+ * @param[in,out] iterCb   the address of a callback function which is called
+ *                         with the data for each image block.
+ * @param[in,out] userdata the address of application-specific data which is
+ *                         passed to the callback along with the image data.
+ *
+ * @return  KTX_SUCCESS on success, other KTX_* enum values on error. The
+ *          following are returned directly by this function. @p iterCb may
+ *          return these for other causes or may return additional errors.
+ *
+ * @exception KTX_FILE_DATA_ERROR   Mip level sizes are increasing not
+ *                                  decreasing
+ * @exception KTX_INVALID_VALUE     @p This is @c NULL or @p iterCb is @c NULL.
+ *
+ */
+KTX_error_code
+ktxTexture_IterateLevels(ktxTexture* This, PFNKTXITERCB iterCb, void* userdata)
+{
+    ktx_uint32_t    miplevel;
+    KTX_error_code  result = KTX_SUCCESS;
+    
+    if (This == NULL)
+        return KTX_INVALID_VALUE;
+    
+    if (iterCb == NULL)
+        return KTX_INVALID_VALUE;
+    
+    for (miplevel = 0; miplevel < This->numLevels; ++miplevel)
+    {
+        GLsizei width, height, depth;
+        ktx_uint32_t levelSize;
+        ktx_size_t offset;
+        
+        /* Array textures have the same number of layers at each mip level. */
+        width = MAX(1, This->baseWidth  >> miplevel);
+        height = MAX(1, This->baseHeight >> miplevel);
+        depth = MAX(1, This->baseDepth  >> miplevel);
+
+        levelSize = (ktx_uint32_t)ktxTexture_levelSize(This, miplevel);
+
+        /* All array layers are passed in a group because that is how
+         * GL & Vulkan need them. Hence no
+         *    for (layer = 0; layer < This->numLayers)
+         */
+        ktxTexture_GetImageOffset(This, miplevel, 0, 0, &offset);
+        result = iterCb(miplevel, 0, width, height, depth,
+                         levelSize, This->pData + offset, userdata);
+        if (result != KTX_SUCCESS)
+            break;
+    }
+    
+    return result;
+}
+
+/**
+ * @internal
+ * @brief  Calculate and apply the padding needed to comply with
+ *         KTX_GL_UNPACK_ALIGNMENT.
+ *
+ * For uncompressed textures, KTX format specifies KTX_GL_UNPACK_ALIGNMENT = 4.
+ *
+ * @param[in,out] rowBytes    pointer to variable containing the packed no. of
+ *                            bytes in a row. The no. of bytes after padding
+ *                            is written into this location.
+ * @return the no. of bytes of padding.
+ */
+static ktx_uint32_t
+padRow(ktx_uint32_t* rowBytes)
+{
+    ktx_uint32_t rowPadding;
+
+    assert (rowBytes != NULL);
+
+    rowPadding = _KTX_PAD_UNPACK_ALIGN_LEN(*rowBytes);
+    *rowBytes += rowPadding;
+    return rowPadding;
+}
+
+/**
+ * @memberof ktxTexture @private
+ * @~English
+ * @brief Calculate the size of an array layer at the specified mip level.
+ *
+ * The size of a layer is the size of an image * either the number of faces
+ * or the number of depth slices. This is the size of a layer as needed to
+ * find the offset within the array of images of a level and layer so the size
+ * reflects any @c cubePadding.
+ *
+ * @param[in]  This     pointer to the ktxTexture object of interest.
+ * @param[in] level     level whose layer size to return.
+ *
+ * @return the layer size in bytes.
+ */
+static inline ktx_size_t
+ktxTexture_layerSize(ktxTexture* This, ktx_uint32_t level)
+{
+    /*
+     * As there are no 3D cubemaps, the image's z block count will always be
+     * 1 for cubemaps and numFaces will always be 1 for 3D textures so the
+     * multiply is safe. 3D cubemaps, if they existed, would require
+     * imageSize * (blockCount.z + This->numFaces);
+     */
+    GlFormatSize* formatInfo;
+    ktx_uint32_t blockCountZ;
+    ktx_size_t imageSize, layerSize;
+
+    assert (This != NULL);
+    
+    formatInfo = &((ktxTextureInt*)This)->formatInfo;
+    blockCountZ = MAX(1, (This->baseDepth / formatInfo->blockDepth)  >> level);
+    imageSize = ktxTexture_GetImageSize(This, level);
+    layerSize = imageSize * blockCountZ;
+#if (KTX_GL_UNPACK_ALIGNMENT != 4)
+    if (This->isCubemap && !This->isArray) {
+        /* cubePadding. NOTE: this adds padding after the last face too. */
+        _KTX_PAD4(layerSize);
+    }
+#endif
+    return layerSize * This->numFaces;
+}
+
+/**
+ * @memberof ktxTexture @private
+ * @~English
+ * @brief Calculate the size of the specified mip level.
+ *
+ * The size of a level is the size of a layer * the number of layers.
+ *
+ * @param[in]  This     pointer to the ktxTexture object of interest.
+ * @param[in] level     level whose layer size to return.
+ *
+ * @return the level size in bytes.
+ */
+ktx_size_t
+ktxTexture_levelSize(ktxTexture* This, ktx_uint32_t level)
+{
+    assert (This != NULL);
+    return ktxTexture_layerSize(This, level) * This->numLayers;
+}
+
+/**
+ * @memberof ktxTexture @private
+ * @~English
+ * @brief Calculate the faceLodSize of the specified mip level.
+ *
+ * The faceLodSize of a level for most textures is the size of a level. For
+ * non-array cube map textures is the size of a face. This is the size that
+ * must be provided to OpenGL when uploading textures. Faces get uploaded 1
+ * at a time while all layers of an array or all slices of a 3D texture are
+ * uploaded together.
+ *
+ * @param[in]  This     pointer to the ktxTexture object of interest.
+ * @param[in] level     level whose layer size to return.
+ *
+ * @return the faceLodSize size in bytes.
+ */
+ktx_size_t
+ktxTexture_faceLodSize(ktxTexture* This, ktx_uint32_t level)
+{
+    /*
+     * For non-array cubemaps this is the size of a face. For everything
+     * else it is the size of the level.
+     */
+    if (This->isCubemap && !This->isArray)
+        return ktxTexture_GetImageSize(This, level);
+    else
+        return ktxTexture_levelSize(This, level);
+}
+
+/**
+ * @memberof ktxTexture @private
+ * @~English
+ * @brief Calculate the size of the image data for the specified number
+ *        of levels.
+ *
+ * The data size is the sum of the sizes of each level up to the number
+ * specified and includes any @c mipPadding.
+ *
+ * @param[in] This     pointer to the ktxTexture object of interest.
+ * @param[in] levels   number of levels whose data size to return.
+ *
+ * @return the data size in bytes.
+ */
+static inline ktx_size_t
+ktxTexture_dataSize(ktxTexture* This, ktx_uint32_t levels)
+{
+    ktx_uint32_t i;
+    ktx_size_t dataSize = 0;
+
+    assert (This != NULL);
+    for (i = 0; i < levels; i++) {
+        ktx_size_t levelSize = ktxTexture_levelSize(This, i);
+#if (KTX_GL_UNPACK_ALIGNMENT != 4)
+        /* mipPadding. NOTE: this adds padding after the last level too. */
+        dataSize += _KTX_PAD4(levelSize);
+#else
+        dataSize += levelSize;
+#endif
+    }
+    return dataSize;
+}
+
+/**
+ * @memberof ktxTexture @private
+ * @~English
+ * @brief Return the number of bytes needed to store all the image data for
+ *        a ktxTexture.
+ *
+ * The caclulated size does not include space for storing the @c imageSize
+ * fields of each mip level.
+ *
+ * @param[in]     This       pointer to the ktxTexture object of interest.
+ *
+ * @return the data size in bytes.
+ */
+static ktx_size_t
+ktxTexture_calcDataSize(ktxTexture* This)
+{
+    assert (This != NULL);
+    return ktxTexture_dataSize(This, This->numLevels);
+}
+
+/**
+ * @memberof ktxTexture @private
+ * @~English
+ * @brief Return the size of the primitive type of a single color component
+ *
+ * @param[in]     This       pointer to the ktxTexture object of interest.
+ *
+ * @return the type size in bytes.
+ */
+ktx_uint32_t
+ktxTexture_glTypeSize(ktxTexture* This)
+{
+    assert(This != NULL);
+    return ((ktxTextureInt*)This)->glTypeSize;
+}
+
+/**
+ * @memberof ktxTexture @private
+ * @~English
+ * @brief Get information about rows of an uncompresssed texture image at a
+ *        specified level.
+ *
+ * For an image at @p level of a ktxTexture provide the number of rows, the
+ * packed (unpadded) number of bytes in a row and the padding necessary to
+ * comply with KTX_GL_UNPACK_ALIGNMENT.
+ *
+ * @param[in]     This     pointer to the ktxTexture object of interest.
+ * @param[in]     level    level of interest.
+ * @param[in,out] numRows  pointer to location to store the number of rows.
+ * @param[in,out] pRowLengthBytes pointer to location to store number of bytes
+ *                                in a row.
+ * @param[in.out] pRowPadding pointer to location to store the number of bytes
+ *                            of padding.
+ */
+void
+ktxTexture_rowInfo(ktxTexture* This, ktx_uint32_t level,
+                   ktx_uint32_t* numRows, ktx_uint32_t* pRowLengthBytes,
+                   ktx_uint32_t* pRowPadding)
+{
+    GlFormatSize* formatInfo;
+    struct blockCount {
+        ktx_uint32_t x;
+    } blockCount;
+
+    assert (This != NULL);
+    
+    formatInfo = &((ktxTextureInt*)This)->formatInfo;
+    assert(!This->isCompressed);
+    assert(formatInfo->blockWidth == formatInfo->blockHeight == formatInfo->blockDepth == 1);
+
+    blockCount.x = MAX(1, (This->baseWidth / formatInfo->blockWidth)  >> level);
+    *numRows = MAX(1, (This->baseHeight / formatInfo->blockHeight)  >> level);
+
+    *pRowLengthBytes = blockCount.x * formatInfo->blockSizeInBits / 8;
+    *pRowPadding = padRow(pRowLengthBytes);
+}
+
+/**
+ * @memberof ktxTexture
+ * @~English
+ * @brief Return pitch betweeb rows of a texture image level in bytes.
+ *
+ * For uncompressed textures the pitch is the number of bytes between
+ * rows of texels. For compressed textures it is the number of bytes
+ * between rows of blocks. The value is padded to GL_UNPACK_ALIGNMENT,
+ * if necessary. For all currently known compressed formats padding
+ * will not be necessary.
+ *
+ * @param[in]     This     pointer to the ktxTexture object of interest.
+ * @param[in]     level    level of interest.
+ *
+ * @return  the row pitch in bytes.
+ */
+ ktx_uint32_t
+ ktxTexture_GetRowPitch(ktxTexture* This, ktx_uint32_t level)
+ {
+    GlFormatSize* formatInfo;
+    struct blockCount {
+        ktx_uint32_t x;
+    } blockCount;
+    ktx_uint32_t pitch;
+
+    formatInfo = &((ktxTextureInt*)This)->formatInfo;
+    blockCount.x = MAX(1, (This->baseWidth / formatInfo->blockWidth)  >> level);
+    pitch = blockCount.x * formatInfo->blockSizeInBits / 8;
+    (void)padRow(&pitch);
+
+    return pitch;
+ }
+
+/**
+ * @memberof ktxTexture @private
+ * @~English
+ * @brief Query if a ktxTexture has an active stream.
+ *
+ * Tests if a ktxTexture has unread image data. The internal stream is closed
+ * once all the images have been read.
+ *
+ * @param[in]     This     pointer to the ktxTexture object of interest.
+ *
+ * @return KTX_TRUE if there is an active stream, KTX_FALSE otherwise.
+ */
+ktx_bool_t
+ktxTexture_isActiveStream(ktxTexture* This)
+{
+    assert(This != NULL);
+    return ((ktxTextureInt*)This)->stream.data.file != NULL;
+}
+
+
+/**
+ * @memberof ktxTexture
+ * @~English
+ * @brief Find the offset of an image within a ktxTexture's image data.
+ *
+ * As there is no such thing as a 3D cubemap we make the 3rd location parameter
+ * do double duty.
+ *
+ * @param[in]     This      pointer to the ktxTexture object of interest.
+ * @param[in]     level     mip level of the image.
+ * @param[in]     layer     array layer of the image.
+ * @param[in]     faceSlice cube map face or depth slice of the image.
+ * @param[in,out] pOffset   pointer to location to store the offset.
+ *
+ * @return  KTX_SUCCESS on success, other KTX_* enum values on error.
+ *
+ * @exception KTX_INVALID_OPERATION
+ *                         @p level, @p layer or @p faceSlice exceed the
+ *                         dimensions of the texture.
+ * @exception KTX_INVALID_VALID @p This is NULL.
+ */
+KTX_error_code
+ktxTexture_GetImageOffset(ktxTexture* This, ktx_uint32_t level,
+                          ktx_uint32_t layer, ktx_uint32_t faceSlice,
+                          ktx_size_t* pOffset)
+{
+    if (This == NULL)
+        return KTX_INVALID_VALUE;
+
+    if (level >= This->numLevels || layer >= This->numLayers)
+        return KTX_INVALID_OPERATION;
+    
+    if (This->isCubemap) {
+        if (faceSlice >= This->numFaces)
+            return KTX_INVALID_OPERATION;
+    } else {
+        ktx_uint32_t maxSlice = MAX(1, This->baseDepth >> level);
+        if (faceSlice >= maxSlice)
+            return KTX_INVALID_OPERATION;
+    }
+    
+    // Get the size of the data up to the start of the indexed level.
+    *pOffset = ktxTexture_dataSize(This, level);
+    
+    // All layers, faces & slices within a level are the same size.
+    if (layer != 0) {
+        ktx_size_t layerSize;
+        layerSize = ktxTexture_layerSize(This, level);
+        *pOffset += layer * layerSize;
+    }
+    if (faceSlice != 0) {
+        ktx_size_t imageSize;
+        imageSize = ktxTexture_GetImageSize(This, level);
+#if (KTX_GL_UNPACK_ALIGNMENT != 4)
+        if (This->isCubemap)
+            _KTX_PAD4(imageSize); // Account for cubePadding.
+#endif
+        *pOffset += faceSlice * imageSize;
+    }
+
+    return KTX_SUCCESS;
+}
+
+/** @} */
+
diff --git a/external/Vulkan/external/ktx/lib/uthash.h b/external/Vulkan/external/ktx/lib/uthash.h
new file mode 100644
index 0000000000000000000000000000000000000000..ddd50a77bbe0f5a1f4781d380f9ba8e2ea217ad7
--- /dev/null
+++ b/external/Vulkan/external/ktx/lib/uthash.h
@@ -0,0 +1,960 @@
+/*
+Copyright (c) 2003-2010, Troy D. Hanson     http://uthash.sourceforge.net
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#ifndef UTHASH_H
+#define UTHASH_H 
+
+#include <string.h>   /* memcmp,strlen */
+#include <stddef.h>   /* ptrdiff_t */
+
+/* These macros use decltype or the earlier __typeof GNU extension.
+   As decltype is only available in newer compilers (VS2010 or gcc 4.3+
+   when compiling c++ source) this code uses whatever method is needed
+   or, for VS2008 where neither is available, uses casting workarounds. */
+#ifdef _MSC_VER         /* MS compiler */
+#if _MSC_VER >= 1600 && __cplusplus  /* VS2010 or newer in C++ mode */
+#define DECLTYPE(x) (decltype(x))
+#else                   /* VS2008 or older (or VS2010 in C mode) */
+#define NO_DECLTYPE
+#define DECLTYPE(x)
+#endif
+#else                   /* GNU, Sun and other compilers */
+#define DECLTYPE(x) (__typeof(x))
+#endif
+
+#ifdef NO_DECLTYPE
+#define DECLTYPE_ASSIGN(dst,src)                                                 \
+do {                                                                             \
+  char **_da_dst = (char**)(&(dst));                                             \
+  *_da_dst = (char*)(src);                                                       \
+} while(0)
+#else 
+#define DECLTYPE_ASSIGN(dst,src)                                                 \
+do {                                                                             \
+  (dst) = DECLTYPE(dst)(src);                                                    \
+} while(0)
+#endif
+
+/* a number of the hash function use uint32_t which isn't defined on win32 */
+#ifdef _MSC_VER
+typedef unsigned int uint32_t;
+#else
+#include <inttypes.h>   /* uint32_t */
+#endif
+
+#define UTHASH_VERSION 1.9.1
+
+#define uthash_fatal(msg) exit(-1)        /* fatal error (out of memory,etc) */
+#define uthash_malloc(sz) malloc(sz)      /* malloc fcn                      */
+#define uthash_free(ptr) free(ptr)        /* free fcn                        */
+
+#define uthash_noexpand_fyi(tbl)          /* can be defined to log noexpand  */
+#define uthash_expand_fyi(tbl)            /* can be defined to log expands   */
+
+/* initial number of buckets */
+#define HASH_INITIAL_NUM_BUCKETS 32      /* initial number of buckets        */
+#define HASH_INITIAL_NUM_BUCKETS_LOG2 5  /* lg2 of initial number of buckets */
+#define HASH_BKT_CAPACITY_THRESH 10      /* expand when bucket count reaches */
+
+/* calculate the element whose hash handle address is hhe */
+#define ELMT_FROM_HH(tbl,hhp) ((void*)(((char*)(hhp)) - ((tbl)->hho)))
+
+#define HASH_FIND(hh,head,keyptr,keylen,out)                                     \
+do {                                                                             \
+  unsigned _hf_bkt,_hf_hashv;                                                    \
+  out=NULL;                                                                      \
+  if (head) {                                                                    \
+     HASH_FCN(keyptr,keylen, (head)->hh.tbl->num_buckets, _hf_hashv, _hf_bkt);   \
+     if (HASH_BLOOM_TEST((head)->hh.tbl, _hf_hashv)) {                           \
+       HASH_FIND_IN_BKT((head)->hh.tbl, hh, (head)->hh.tbl->buckets[ _hf_bkt ],  \
+                        keyptr,keylen,out);                                      \
+     }                                                                           \
+  }                                                                              \
+} while (0)
+
+#ifdef HASH_BLOOM
+#define HASH_BLOOM_BITLEN (1ULL << HASH_BLOOM)
+#define HASH_BLOOM_BYTELEN (HASH_BLOOM_BITLEN/8) + ((HASH_BLOOM_BITLEN%8) ? 1:0)
+#define HASH_BLOOM_MAKE(tbl)                                                     \
+do {                                                                             \
+  (tbl)->bloom_nbits = HASH_BLOOM;                                               \
+  (tbl)->bloom_bv = (uint8_t*)uthash_malloc(HASH_BLOOM_BYTELEN);                 \
+  if (!((tbl)->bloom_bv))  { uthash_fatal( "out of memory"); }                   \
+  memset((tbl)->bloom_bv, 0, HASH_BLOOM_BYTELEN);                                \
+  (tbl)->bloom_sig = HASH_BLOOM_SIGNATURE;                                       \
+} while (0);
+
+#define HASH_BLOOM_FREE(tbl)                                                     \
+do {                                                                             \
+  uthash_free((tbl)->bloom_bv);                                                  \
+} while (0);
+
+#define HASH_BLOOM_BITSET(bv,idx) (bv[(idx)/8] |= (1U << ((idx)%8)))
+#define HASH_BLOOM_BITTEST(bv,idx) (bv[(idx)/8] & (1U << ((idx)%8)))
+
+#define HASH_BLOOM_ADD(tbl,hashv)                                                \
+  HASH_BLOOM_BITSET((tbl)->bloom_bv, (hashv & (uint32_t)((1ULL << (tbl)->bloom_nbits) - 1)))
+
+#define HASH_BLOOM_TEST(tbl,hashv)                                               \
+  HASH_BLOOM_BITTEST((tbl)->bloom_bv, (hashv & (uint32_t)((1ULL << (tbl)->bloom_nbits) - 1)))
+
+#else
+#define HASH_BLOOM_MAKE(tbl) 
+#define HASH_BLOOM_FREE(tbl) 
+#define HASH_BLOOM_ADD(tbl,hashv) 
+#define HASH_BLOOM_TEST(tbl,hashv) (1)
+#endif
+
+#define HASH_MAKE_TABLE(hh,head)                                                 \
+do {                                                                             \
+  (head)->hh.tbl = (UT_hash_table*)uthash_malloc(                                \
+                  sizeof(UT_hash_table));                                        \
+  if (!((head)->hh.tbl))  { uthash_fatal( "out of memory"); }                    \
+  memset((head)->hh.tbl, 0, sizeof(UT_hash_table));                              \
+  (head)->hh.tbl->tail = &((head)->hh);                                          \
+  (head)->hh.tbl->num_buckets = HASH_INITIAL_NUM_BUCKETS;                        \
+  (head)->hh.tbl->log2_num_buckets = HASH_INITIAL_NUM_BUCKETS_LOG2;              \
+  (head)->hh.tbl->hho = (char*)(&(head)->hh) - (char*)(head);                    \
+  (head)->hh.tbl->buckets = (UT_hash_bucket*)uthash_malloc(                      \
+          HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket));               \
+  if (! (head)->hh.tbl->buckets) { uthash_fatal( "out of memory"); }             \
+  memset((head)->hh.tbl->buckets, 0,                                             \
+          HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket));               \
+  HASH_BLOOM_MAKE((head)->hh.tbl);                                               \
+  (head)->hh.tbl->signature = HASH_SIGNATURE;                                    \
+} while(0)
+
+#define HASH_ADD(hh,head,fieldname,keylen_in,add)                                \
+        HASH_ADD_KEYPTR(hh,head,&add->fieldname,keylen_in,add)
+ 
+#define HASH_ADD_KEYPTR(hh,head,keyptr,keylen_in,add)                            \
+do {                                                                             \
+ unsigned _ha_bkt;                                                               \
+ (add)->hh.next = NULL;                                                          \
+ (add)->hh.key = (char*)keyptr;                                                  \
+ (add)->hh.keylen = keylen_in;                                                   \
+ if (!(head)) {                                                                  \
+    head = (add);                                                                \
+    (head)->hh.prev = NULL;                                                      \
+    HASH_MAKE_TABLE(hh,head);                                                    \
+ } else {                                                                        \
+    (head)->hh.tbl->tail->next = (add);                                          \
+    (add)->hh.prev = ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail);         \
+    (head)->hh.tbl->tail = &((add)->hh);                                         \
+ }                                                                               \
+ (head)->hh.tbl->num_items++;                                                    \
+ (add)->hh.tbl = (head)->hh.tbl;                                                 \
+ HASH_FCN(keyptr,keylen_in, (head)->hh.tbl->num_buckets,                         \
+         (add)->hh.hashv, _ha_bkt);                                              \
+ HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt],&(add)->hh);                   \
+ HASH_BLOOM_ADD((head)->hh.tbl,(add)->hh.hashv);                                 \
+ HASH_EMIT_KEY(hh,head,keyptr,keylen_in);                                        \
+ HASH_FSCK(hh,head);                                                             \
+} while(0)
+
+#define HASH_TO_BKT( hashv, num_bkts, bkt )                                      \
+do {                                                                             \
+  bkt = ((hashv) & ((num_bkts) - 1));                                            \
+} while(0)
+
+/* delete "delptr" from the hash table.
+ * "the usual" patch-up process for the app-order doubly-linked-list.
+ * The use of _hd_hh_del below deserves special explanation.
+ * These used to be expressed using (delptr) but that led to a bug
+ * if someone used the same symbol for the head and deletee, like
+ *  HASH_DELETE(hh,users,users);
+ * We want that to work, but by changing the head (users) below
+ * we were forfeiting our ability to further refer to the deletee (users)
+ * in the patch-up process. Solution: use scratch space to
+ * copy the deletee pointer, then the latter references are via that
+ * scratch pointer rather than through the repointed (users) symbol.
+ */
+#define HASH_DELETE(hh,head,delptr)                                              \
+do {                                                                             \
+    unsigned _hd_bkt;                                                            \
+    struct UT_hash_handle *_hd_hh_del;                                           \
+    if ( ((delptr)->hh.prev == NULL) && ((delptr)->hh.next == NULL) )  {         \
+        uthash_free((head)->hh.tbl->buckets );                                   \
+        HASH_BLOOM_FREE((head)->hh.tbl);                                         \
+        uthash_free((head)->hh.tbl);                                             \
+        head = NULL;                                                             \
+    } else {                                                                     \
+        _hd_hh_del = &((delptr)->hh);                                            \
+        if ((delptr) == ELMT_FROM_HH((head)->hh.tbl,(head)->hh.tbl->tail)) {     \
+            (head)->hh.tbl->tail =                                               \
+                (UT_hash_handle*)((char*)((delptr)->hh.prev) +                   \
+                (head)->hh.tbl->hho);                                            \
+        }                                                                        \
+        if ((delptr)->hh.prev) {                                                 \
+            ((UT_hash_handle*)((char*)((delptr)->hh.prev) +                      \
+                    (head)->hh.tbl->hho))->next = (delptr)->hh.next;             \
+        } else {                                                                 \
+            DECLTYPE_ASSIGN(head,(delptr)->hh.next);                             \
+        }                                                                        \
+        if (_hd_hh_del->next) {                                                  \
+            ((UT_hash_handle*)((char*)_hd_hh_del->next +                         \
+                    (head)->hh.tbl->hho))->prev =                                \
+                    _hd_hh_del->prev;                                            \
+        }                                                                        \
+        HASH_TO_BKT( _hd_hh_del->hashv, (head)->hh.tbl->num_buckets, _hd_bkt);   \
+        HASH_DEL_IN_BKT(hh,(head)->hh.tbl->buckets[_hd_bkt], _hd_hh_del);        \
+        (head)->hh.tbl->num_items--;                                             \
+    }                                                                            \
+    HASH_FSCK(hh,head);                                                          \
+} while (0)
+
+
+/* convenience forms of HASH_FIND/HASH_ADD/HASH_DEL */
+#define HASH_FIND_STR(head,findstr,out)                                          \
+    HASH_FIND(hh,head,findstr,strlen(findstr),out)
+#define HASH_ADD_STR(head,strfield,add)                                          \
+    HASH_ADD(hh,head,strfield,strlen(add->strfield),add)
+#define HASH_FIND_INT(head,findint,out)                                          \
+    HASH_FIND(hh,head,findint,sizeof(int),out)
+#define HASH_ADD_INT(head,intfield,add)                                          \
+    HASH_ADD(hh,head,intfield,sizeof(int),add)
+#define HASH_FIND_PTR(head,findptr,out)                                          \
+    HASH_FIND(hh,head,findptr,sizeof(void *),out)
+#define HASH_ADD_PTR(head,ptrfield,add)                                          \
+    HASH_ADD(hh,head,ptrfield,sizeof(void *),add)
+#define HASH_DEL(head,delptr)                                                    \
+    HASH_DELETE(hh,head,delptr)
+
+/* HASH_FSCK checks hash integrity on every add/delete when HASH_DEBUG is defined.
+ * This is for uthash developer only; it compiles away if HASH_DEBUG isn't defined.
+ */
+#ifdef HASH_DEBUG
+#define HASH_OOPS(...) do { fprintf(stderr,__VA_ARGS__); exit(-1); } while (0)
+#define HASH_FSCK(hh,head)                                                       \
+do {                                                                             \
+    unsigned _bkt_i;                                                             \
+    unsigned _count, _bkt_count;                                                 \
+    char *_prev;                                                                 \
+    struct UT_hash_handle *_thh;                                                 \
+    if (head) {                                                                  \
+        _count = 0;                                                              \
+        for( _bkt_i = 0; _bkt_i < (head)->hh.tbl->num_buckets; _bkt_i++) {       \
+            _bkt_count = 0;                                                      \
+            _thh = (head)->hh.tbl->buckets[_bkt_i].hh_head;                      \
+            _prev = NULL;                                                        \
+            while (_thh) {                                                       \
+               if (_prev != (char*)(_thh->hh_prev)) {                            \
+                   HASH_OOPS("invalid hh_prev %p, actual %p\n",                  \
+                    _thh->hh_prev, _prev );                                      \
+               }                                                                 \
+               _bkt_count++;                                                     \
+               _prev = (char*)(_thh);                                            \
+               _thh = _thh->hh_next;                                             \
+            }                                                                    \
+            _count += _bkt_count;                                                \
+            if ((head)->hh.tbl->buckets[_bkt_i].count !=  _bkt_count) {          \
+               HASH_OOPS("invalid bucket count %d, actual %d\n",                 \
+                (head)->hh.tbl->buckets[_bkt_i].count, _bkt_count);              \
+            }                                                                    \
+        }                                                                        \
+        if (_count != (head)->hh.tbl->num_items) {                               \
+            HASH_OOPS("invalid hh item count %d, actual %d\n",                   \
+                (head)->hh.tbl->num_items, _count );                             \
+        }                                                                        \
+        /* traverse hh in app order; check next/prev integrity, count */         \
+        _count = 0;                                                              \
+        _prev = NULL;                                                            \
+        _thh =  &(head)->hh;                                                     \
+        while (_thh) {                                                           \
+           _count++;                                                             \
+           if (_prev !=(char*)(_thh->prev)) {                                    \
+              HASH_OOPS("invalid prev %p, actual %p\n",                          \
+                    _thh->prev, _prev );                                         \
+           }                                                                     \
+           _prev = (char*)ELMT_FROM_HH((head)->hh.tbl, _thh);                    \
+           _thh = ( _thh->next ?  (UT_hash_handle*)((char*)(_thh->next) +        \
+                                  (head)->hh.tbl->hho) : NULL );                 \
+        }                                                                        \
+        if (_count != (head)->hh.tbl->num_items) {                               \
+            HASH_OOPS("invalid app item count %d, actual %d\n",                  \
+                (head)->hh.tbl->num_items, _count );                             \
+        }                                                                        \
+    }                                                                            \
+} while (0)
+#else
+#define HASH_FSCK(hh,head) 
+#endif
+
+/* When compiled with -DHASH_EMIT_KEYS, length-prefixed keys are emitted to 
+ * the descriptor to which this macro is defined for tuning the hash function.
+ * The app can #include <unistd.h> to get the prototype for write(2). */
+#ifdef HASH_EMIT_KEYS
+#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen)                                   \
+do {                                                                             \
+    unsigned _klen = fieldlen;                                                   \
+    write(HASH_EMIT_KEYS, &_klen, sizeof(_klen));                                \
+    write(HASH_EMIT_KEYS, keyptr, fieldlen);                                     \
+} while (0)
+#else 
+#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen)                    
+#endif
+
+/* default to Jenkin's hash unless overridden e.g. DHASH_FUNCTION=HASH_SAX */
+#ifdef HASH_FUNCTION 
+#define HASH_FCN HASH_FUNCTION
+#else
+#define HASH_FCN HASH_JEN
+#endif
+
+/* The Bernstein hash function, used in Perl prior to v5.6 */
+#define HASH_BER(key,keylen,num_bkts,hashv,bkt)                                  \
+do {                                                                             \
+  unsigned _hb_keylen=keylen;                                                    \
+  char *_hb_key=(char*)key;                                                      \
+  (hashv) = 0;                                                                   \
+  while (_hb_keylen--)  { (hashv) = ((hashv) * 33) + *_hb_key++; }               \
+  bkt = (hashv) & (num_bkts-1);                                                  \
+} while (0)
+
+
+/* SAX/FNV/OAT/JEN hash functions are macro variants of those listed at 
+ * http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx */
+#define HASH_SAX(key,keylen,num_bkts,hashv,bkt)                                  \
+do {                                                                             \
+  unsigned _sx_i;                                                                \
+  char *_hs_key=(char*)key;                                                      \
+  hashv = 0;                                                                     \
+  for(_sx_i=0; _sx_i < keylen; _sx_i++)                                          \
+      hashv ^= (hashv << 5) + (hashv >> 2) + _hs_key[_sx_i];                     \
+  bkt = hashv & (num_bkts-1);                                                    \
+} while (0)
+
+#define HASH_FNV(key,keylen,num_bkts,hashv,bkt)                                  \
+do {                                                                             \
+  unsigned _fn_i;                                                                \
+  char *_hf_key=(char*)key;                                                      \
+  hashv = 2166136261UL;                                                          \
+  for(_fn_i=0; _fn_i < keylen; _fn_i++)                                          \
+      hashv = (hashv * 16777619) ^ _hf_key[_fn_i];                               \
+  bkt = hashv & (num_bkts-1);                                                    \
+} while(0);
+ 
+#define HASH_OAT(key,keylen,num_bkts,hashv,bkt)                                  \
+do {                                                                             \
+  unsigned _ho_i;                                                                \
+  char *_ho_key=(char*)key;                                                      \
+  hashv = 0;                                                                     \
+  for(_ho_i=0; _ho_i < keylen; _ho_i++) {                                        \
+      hashv += _ho_key[_ho_i];                                                   \
+      hashv += (hashv << 10);                                                    \
+      hashv ^= (hashv >> 6);                                                     \
+  }                                                                              \
+  hashv += (hashv << 3);                                                         \
+  hashv ^= (hashv >> 11);                                                        \
+  hashv += (hashv << 15);                                                        \
+  bkt = hashv & (num_bkts-1);                                                    \
+} while(0)
+
+#define HASH_JEN_MIX(a,b,c)                                                      \
+do {                                                                             \
+  a -= b; a -= c; a ^= ( c >> 13 );                                              \
+  b -= c; b -= a; b ^= ( a << 8 );                                               \
+  c -= a; c -= b; c ^= ( b >> 13 );                                              \
+  a -= b; a -= c; a ^= ( c >> 12 );                                              \
+  b -= c; b -= a; b ^= ( a << 16 );                                              \
+  c -= a; c -= b; c ^= ( b >> 5 );                                               \
+  a -= b; a -= c; a ^= ( c >> 3 );                                               \
+  b -= c; b -= a; b ^= ( a << 10 );                                              \
+  c -= a; c -= b; c ^= ( b >> 15 );                                              \
+} while (0)
+
+#define HASH_JEN(key,keylen,num_bkts,hashv,bkt)                                  \
+do {                                                                             \
+  unsigned _hj_i,_hj_j,_hj_k;                                                    \
+  char *_hj_key=(char*)key;                                                      \
+  hashv = 0xfeedbeef;                                                            \
+  _hj_i = _hj_j = 0x9e3779b9;                                                    \
+  _hj_k = keylen;                                                                \
+  while (_hj_k >= 12) {                                                          \
+    _hj_i +=    (_hj_key[0] + ( (unsigned)_hj_key[1] << 8 )                      \
+        + ( (unsigned)_hj_key[2] << 16 )                                         \
+        + ( (unsigned)_hj_key[3] << 24 ) );                                      \
+    _hj_j +=    (_hj_key[4] + ( (unsigned)_hj_key[5] << 8 )                      \
+        + ( (unsigned)_hj_key[6] << 16 )                                         \
+        + ( (unsigned)_hj_key[7] << 24 ) );                                      \
+    hashv += (_hj_key[8] + ( (unsigned)_hj_key[9] << 8 )                         \
+        + ( (unsigned)_hj_key[10] << 16 )                                        \
+        + ( (unsigned)_hj_key[11] << 24 ) );                                     \
+                                                                                 \
+     HASH_JEN_MIX(_hj_i, _hj_j, hashv);                                          \
+                                                                                 \
+     _hj_key += 12;                                                              \
+     _hj_k -= 12;                                                                \
+  }                                                                              \
+  hashv += keylen;                                                               \
+  switch ( _hj_k ) {                                                             \
+     case 11: hashv += ( (unsigned)_hj_key[10] << 24 );                          \
+     case 10: hashv += ( (unsigned)_hj_key[9] << 16 );                           \
+     case 9:  hashv += ( (unsigned)_hj_key[8] << 8 );                            \
+     case 8:  _hj_j += ( (unsigned)_hj_key[7] << 24 );                           \
+     case 7:  _hj_j += ( (unsigned)_hj_key[6] << 16 );                           \
+     case 6:  _hj_j += ( (unsigned)_hj_key[5] << 8 );                            \
+     case 5:  _hj_j += _hj_key[4];                                               \
+     case 4:  _hj_i += ( (unsigned)_hj_key[3] << 24 );                           \
+     case 3:  _hj_i += ( (unsigned)_hj_key[2] << 16 );                           \
+     case 2:  _hj_i += ( (unsigned)_hj_key[1] << 8 );                            \
+     case 1:  _hj_i += _hj_key[0];                                               \
+  }                                                                              \
+  HASH_JEN_MIX(_hj_i, _hj_j, hashv);                                             \
+  bkt = hashv & (num_bkts-1);                                                    \
+} while(0)
+
+/* The Paul Hsieh hash function */
+#undef get16bits
+#if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__)             \
+  || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__)
+#define get16bits(d) (*((const uint16_t *) (d)))
+#endif
+
+#if !defined (get16bits)
+#define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8)             \
+                       +(uint32_t)(((const uint8_t *)(d))[0]) )
+#endif
+#define HASH_SFH(key,keylen,num_bkts,hashv,bkt)                                  \
+do {                                                                             \
+  char *_sfh_key=(char*)key;                                                     \
+  uint32_t _sfh_tmp, _sfh_len = keylen;                                          \
+                                                                                 \
+  int _sfh_rem = _sfh_len & 3;                                                   \
+  _sfh_len >>= 2;                                                                \
+  hashv = 0xcafebabe;                                                            \
+                                                                                 \
+  /* Main loop */                                                                \
+  for (;_sfh_len > 0; _sfh_len--) {                                              \
+    hashv    += get16bits (_sfh_key);                                            \
+    _sfh_tmp       = (get16bits (_sfh_key+2) << 11) ^ hashv;                     \
+    hashv     = (hashv << 16) ^ _sfh_tmp;                                        \
+    _sfh_key += 2*sizeof (uint16_t);                                             \
+    hashv    += hashv >> 11;                                                     \
+  }                                                                              \
+                                                                                 \
+  /* Handle end cases */                                                         \
+  switch (_sfh_rem) {                                                            \
+    case 3: hashv += get16bits (_sfh_key);                                       \
+            hashv ^= hashv << 16;                                                \
+            hashv ^= _sfh_key[sizeof (uint16_t)] << 18;                          \
+            hashv += hashv >> 11;                                                \
+            break;                                                               \
+    case 2: hashv += get16bits (_sfh_key);                                       \
+            hashv ^= hashv << 11;                                                \
+            hashv += hashv >> 17;                                                \
+            break;                                                               \
+    case 1: hashv += *_sfh_key;                                                  \
+            hashv ^= hashv << 10;                                                \
+            hashv += hashv >> 1;                                                 \
+  }                                                                              \
+                                                                                 \
+    /* Force "avalanching" of final 127 bits */                                  \
+    hashv ^= hashv << 3;                                                         \
+    hashv += hashv >> 5;                                                         \
+    hashv ^= hashv << 4;                                                         \
+    hashv += hashv >> 17;                                                        \
+    hashv ^= hashv << 25;                                                        \
+    hashv += hashv >> 6;                                                         \
+    bkt = hashv & (num_bkts-1);                                                  \
+} while(0);
+
+#ifdef HASH_USING_NO_STRICT_ALIASING
+/* The MurmurHash exploits some CPU's (e.g. x86) tolerance for unaligned reads.
+ * For other types of CPU's (e.g. Sparc) an unaligned read causes a bus error.
+ * So MurmurHash comes in two versions, the faster unaligned one and the slower
+ * aligned one. We only use the faster one on CPU's where we know it's safe. 
+ *
+ * Note the preprocessor built-in defines can be emitted using:
+ *
+ *   gcc -m64 -dM -E - < /dev/null                  (on gcc)
+ *   cc -## a.c (where a.c is a simple test file)   (Sun Studio)
+ */
+#if (defined(__i386__) || defined(__x86_64__)) 
+#define HASH_MUR HASH_MUR_UNALIGNED
+#else
+#define HASH_MUR HASH_MUR_ALIGNED
+#endif
+
+/* Appleby's MurmurHash fast version for unaligned-tolerant archs like i386 */
+#define HASH_MUR_UNALIGNED(key,keylen,num_bkts,hashv,bkt)                        \
+do {                                                                             \
+  const unsigned int _mur_m = 0x5bd1e995;                                        \
+  const int _mur_r = 24;                                                         \
+  hashv = 0xcafebabe ^ keylen;                                                   \
+  char *_mur_key = (char *)key;                                                  \
+  uint32_t _mur_tmp, _mur_len = keylen;                                          \
+                                                                                 \
+  for (;_mur_len >= 4; _mur_len-=4) {                                            \
+    _mur_tmp = *(uint32_t *)_mur_key;                                            \
+    _mur_tmp *= _mur_m;                                                          \
+    _mur_tmp ^= _mur_tmp >> _mur_r;                                              \
+    _mur_tmp *= _mur_m;                                                          \
+    hashv *= _mur_m;                                                             \
+    hashv ^= _mur_tmp;                                                           \
+    _mur_key += 4;                                                               \
+  }                                                                              \
+                                                                                 \
+  switch(_mur_len)                                                               \
+  {                                                                              \
+    case 3: hashv ^= _mur_key[2] << 16;                                          \
+    case 2: hashv ^= _mur_key[1] << 8;                                           \
+    case 1: hashv ^= _mur_key[0];                                                \
+            hashv *= _mur_m;                                                     \
+  };                                                                             \
+                                                                                 \
+  hashv ^= hashv >> 13;                                                          \
+  hashv *= _mur_m;                                                               \
+  hashv ^= hashv >> 15;                                                          \
+                                                                                 \
+  bkt = hashv & (num_bkts-1);                                                    \
+} while(0)
+
+/* Appleby's MurmurHash version for alignment-sensitive archs like Sparc */
+#define HASH_MUR_ALIGNED(key,keylen,num_bkts,hashv,bkt)                          \
+do {                                                                             \
+  const unsigned int _mur_m = 0x5bd1e995;                                        \
+  const int _mur_r = 24;                                                         \
+  hashv = 0xcafebabe ^ keylen;                                                   \
+  char *_mur_key = (char *)key;                                                  \
+  uint32_t _mur_len = keylen;                                                    \
+  int _mur_align = (int)_mur_key & 3;                                            \
+                                                                                 \
+  if (_mur_align && (_mur_len >= 4)) {                                           \
+    unsigned _mur_t = 0, _mur_d = 0;                                             \
+    switch(_mur_align) {                                                         \
+      case 1: _mur_t |= _mur_key[2] << 16;                                       \
+      case 2: _mur_t |= _mur_key[1] << 8;                                        \
+      case 3: _mur_t |= _mur_key[0];                                             \
+    }                                                                            \
+    _mur_t <<= (8 * _mur_align);                                                 \
+    _mur_key += 4-_mur_align;                                                    \
+    _mur_len -= 4-_mur_align;                                                    \
+    int _mur_sl = 8 * (4-_mur_align);                                            \
+    int _mur_sr = 8 * _mur_align;                                                \
+                                                                                 \
+    for (;_mur_len >= 4; _mur_len-=4) {                                          \
+      _mur_d = *(unsigned *)_mur_key;                                            \
+      _mur_t = (_mur_t >> _mur_sr) | (_mur_d << _mur_sl);                        \
+      unsigned _mur_k = _mur_t;                                                  \
+      _mur_k *= _mur_m;                                                          \
+      _mur_k ^= _mur_k >> _mur_r;                                                \
+      _mur_k *= _mur_m;                                                          \
+      hashv *= _mur_m;                                                           \
+      hashv ^= _mur_k;                                                           \
+      _mur_t = _mur_d;                                                           \
+      _mur_key += 4;                                                             \
+    }                                                                            \
+    _mur_d = 0;                                                                  \
+    if(_mur_len >= _mur_align) {                                                 \
+      switch(_mur_align) {                                                       \
+        case 3: _mur_d |= _mur_key[2] << 16;                                     \
+        case 2: _mur_d |= _mur_key[1] << 8;                                      \
+        case 1: _mur_d |= _mur_key[0];                                           \
+      }                                                                          \
+      unsigned _mur_k = (_mur_t >> _mur_sr) | (_mur_d << _mur_sl);               \
+      _mur_k *= _mur_m;                                                          \
+      _mur_k ^= _mur_k >> _mur_r;                                                \
+      _mur_k *= _mur_m;                                                          \
+      hashv *= _mur_m;                                                           \
+      hashv ^= _mur_k;                                                           \
+      _mur_k += _mur_align;                                                      \
+      _mur_len -= _mur_align;                                                    \
+                                                                                 \
+      switch(_mur_len)                                                           \
+      {                                                                          \
+        case 3: hashv ^= _mur_key[2] << 16;                                      \
+        case 2: hashv ^= _mur_key[1] << 8;                                       \
+        case 1: hashv ^= _mur_key[0];                                            \
+                hashv *= _mur_m;                                                 \
+      }                                                                          \
+    } else {                                                                     \
+      switch(_mur_len)                                                           \
+      {                                                                          \
+        case 3: _mur_d ^= _mur_key[2] << 16;                                     \
+        case 2: _mur_d ^= _mur_key[1] << 8;                                      \
+        case 1: _mur_d ^= _mur_key[0];                                           \
+        case 0: hashv ^= (_mur_t >> _mur_sr) | (_mur_d << _mur_sl);              \
+        hashv *= _mur_m;                                                         \
+      }                                                                          \
+    }                                                                            \
+                                                                                 \
+    hashv ^= hashv >> 13;                                                        \
+    hashv *= _mur_m;                                                             \
+    hashv ^= hashv >> 15;                                                        \
+  } else {                                                                       \
+    for (;_mur_len >= 4; _mur_len-=4) {                                          \
+      unsigned _mur_k = *(unsigned*)_mur_key;                                    \
+      _mur_k *= _mur_m;                                                          \
+      _mur_k ^= _mur_k >> _mur_r;                                                \
+      _mur_k *= _mur_m;                                                          \
+      hashv *= _mur_m;                                                           \
+      hashv ^= _mur_k;                                                           \
+      _mur_key += 4;                                                             \
+    }                                                                            \
+    switch(_mur_len)                                                             \
+    {                                                                            \
+      case 3: hashv ^= _mur_key[2] << 16;                                        \
+      case 2: hashv ^= _mur_key[1] << 8;                                         \
+      case 1: hashv ^= _mur_key[0];                                              \
+      hashv *= _mur_m;                                                           \
+    }                                                                            \
+                                                                                 \
+    hashv ^= hashv >> 13;                                                        \
+    hashv *= _mur_m;                                                             \
+    hashv ^= hashv >> 15;                                                        \
+  }                                                                              \
+  bkt = hashv & (num_bkts-1);                                                    \
+} while(0)
+#endif  /* HASH_USING_NO_STRICT_ALIASING */
+
+/* key comparison function; return 0 if keys equal */
+#define HASH_KEYCMP(a,b,len) memcmp(a,b,len) 
+
+/* iterate over items in a known bucket to find desired item */
+#define HASH_FIND_IN_BKT(tbl,hh,head,keyptr,keylen_in,out)                       \
+do {                                                                             \
+ if (head.hh_head) DECLTYPE_ASSIGN(out,ELMT_FROM_HH(tbl,head.hh_head));          \
+ else out=NULL;                                                                  \
+ while (out) {                                                                   \
+    if (out->hh.keylen == keylen_in) {                                           \
+        if ((HASH_KEYCMP(out->hh.key,keyptr,keylen_in)) == 0) break;             \
+    }                                                                            \
+    if (out->hh.hh_next) DECLTYPE_ASSIGN(out,ELMT_FROM_HH(tbl,out->hh.hh_next)); \
+    else out = NULL;                                                             \
+ }                                                                               \
+} while(0)
+
+/* add an item to a bucket  */
+#define HASH_ADD_TO_BKT(head,addhh)                                              \
+do {                                                                             \
+ head.count++;                                                                   \
+ (addhh)->hh_next = head.hh_head;                                                \
+ (addhh)->hh_prev = NULL;                                                        \
+ if (head.hh_head) { (head).hh_head->hh_prev = (addhh); }                        \
+ (head).hh_head=addhh;                                                           \
+ if (head.count >= ((head.expand_mult+1) * HASH_BKT_CAPACITY_THRESH)             \
+     && (addhh)->tbl->noexpand != 1) {                                           \
+       HASH_EXPAND_BUCKETS((addhh)->tbl);                                        \
+ }                                                                               \
+} while(0)
+
+/* remove an item from a given bucket */
+#define HASH_DEL_IN_BKT(hh,head,hh_del)                                          \
+    (head).count--;                                                              \
+    if ((head).hh_head == hh_del) {                                              \
+      (head).hh_head = hh_del->hh_next;                                          \
+    }                                                                            \
+    if (hh_del->hh_prev) {                                                       \
+        hh_del->hh_prev->hh_next = hh_del->hh_next;                              \
+    }                                                                            \
+    if (hh_del->hh_next) {                                                       \
+        hh_del->hh_next->hh_prev = hh_del->hh_prev;                              \
+    }                                                                
+
+/* Bucket expansion has the effect of doubling the number of buckets
+ * and redistributing the items into the new buckets. Ideally the
+ * items will distribute more or less evenly into the new buckets
+ * (the extent to which this is true is a measure of the quality of
+ * the hash function as it applies to the key domain). 
+ * 
+ * With the items distributed into more buckets, the chain length
+ * (item count) in each bucket is reduced. Thus by expanding buckets
+ * the hash keeps a bound on the chain length. This bounded chain 
+ * length is the essence of how a hash provides constant time lookup.
+ * 
+ * The calculation of tbl->ideal_chain_maxlen below deserves some
+ * explanation. First, keep in mind that we're calculating the ideal
+ * maximum chain length based on the *new* (doubled) bucket count.
+ * In fractions this is just n/b (n=number of items,b=new num buckets).
+ * Since the ideal chain length is an integer, we want to calculate 
+ * ceil(n/b). We don't depend on floating point arithmetic in this
+ * hash, so to calculate ceil(n/b) with integers we could write
+ * 
+ *      ceil(n/b) = (n/b) + ((n%b)?1:0)
+ * 
+ * and in fact a previous version of this hash did just that.
+ * But now we have improved things a bit by recognizing that b is
+ * always a power of two. We keep its base 2 log handy (call it lb),
+ * so now we can write this with a bit shift and logical AND:
+ * 
+ *      ceil(n/b) = (n>>lb) + ( (n & (b-1)) ? 1:0)
+ * 
+ */
+#define HASH_EXPAND_BUCKETS(tbl)                                                 \
+do {                                                                             \
+    unsigned _he_bkt;                                                            \
+    unsigned _he_bkt_i;                                                          \
+    struct UT_hash_handle *_he_thh, *_he_hh_nxt;                                 \
+    UT_hash_bucket *_he_new_buckets, *_he_newbkt;                                \
+    _he_new_buckets = (UT_hash_bucket*)uthash_malloc(                            \
+             2 * tbl->num_buckets * sizeof(struct UT_hash_bucket));              \
+    if (!_he_new_buckets) { uthash_fatal( "out of memory"); }                    \
+    memset(_he_new_buckets, 0,                                                   \
+            2 * tbl->num_buckets * sizeof(struct UT_hash_bucket));               \
+    tbl->ideal_chain_maxlen =                                                    \
+       (tbl->num_items >> (tbl->log2_num_buckets+1)) +                           \
+       ((tbl->num_items & ((tbl->num_buckets*2)-1)) ? 1 : 0);                    \
+    tbl->nonideal_items = 0;                                                     \
+    for(_he_bkt_i = 0; _he_bkt_i < tbl->num_buckets; _he_bkt_i++)                \
+    {                                                                            \
+        _he_thh = tbl->buckets[ _he_bkt_i ].hh_head;                             \
+        while (_he_thh) {                                                        \
+           _he_hh_nxt = _he_thh->hh_next;                                        \
+           HASH_TO_BKT( _he_thh->hashv, tbl->num_buckets*2, _he_bkt);            \
+           _he_newbkt = &(_he_new_buckets[ _he_bkt ]);                           \
+           if (++(_he_newbkt->count) > tbl->ideal_chain_maxlen) {                \
+             tbl->nonideal_items++;                                              \
+             _he_newbkt->expand_mult = _he_newbkt->count /                       \
+                                        tbl->ideal_chain_maxlen;                 \
+           }                                                                     \
+           _he_thh->hh_prev = NULL;                                              \
+           _he_thh->hh_next = _he_newbkt->hh_head;                               \
+           if (_he_newbkt->hh_head) _he_newbkt->hh_head->hh_prev =               \
+                _he_thh;                                                         \
+           _he_newbkt->hh_head = _he_thh;                                        \
+           _he_thh = _he_hh_nxt;                                                 \
+        }                                                                        \
+    }                                                                            \
+    tbl->num_buckets *= 2;                                                       \
+    tbl->log2_num_buckets++;                                                     \
+    uthash_free( tbl->buckets );                                                 \
+    tbl->buckets = _he_new_buckets;                                              \
+    tbl->ineff_expands = (tbl->nonideal_items > (tbl->num_items >> 1)) ?         \
+        (tbl->ineff_expands+1) : 0;                                              \
+    if (tbl->ineff_expands > 1) {                                                \
+        tbl->noexpand=1;                                                         \
+        uthash_noexpand_fyi(tbl);                                                \
+    }                                                                            \
+    uthash_expand_fyi(tbl);                                                      \
+} while(0)
+
+
+/* This is an adaptation of Simon Tatham's O(n log(n)) mergesort */
+/* Note that HASH_SORT assumes the hash handle name to be hh. 
+ * HASH_SRT was added to allow the hash handle name to be passed in. */
+#define HASH_SORT(head,cmpfcn) HASH_SRT(hh,head,cmpfcn)
+#define HASH_SRT(hh,head,cmpfcn)                                                 \
+do {                                                                             \
+  unsigned _hs_i;                                                                \
+  unsigned _hs_looping,_hs_nmerges,_hs_insize,_hs_psize,_hs_qsize;               \
+  struct UT_hash_handle *_hs_p, *_hs_q, *_hs_e, *_hs_list, *_hs_tail;            \
+  if (head) {                                                                    \
+      _hs_insize = 1;                                                            \
+      _hs_looping = 1;                                                           \
+      _hs_list = &((head)->hh);                                                  \
+      while (_hs_looping) {                                                      \
+          _hs_p = _hs_list;                                                      \
+          _hs_list = NULL;                                                       \
+          _hs_tail = NULL;                                                       \
+          _hs_nmerges = 0;                                                       \
+          while (_hs_p) {                                                        \
+              _hs_nmerges++;                                                     \
+              _hs_q = _hs_p;                                                     \
+              _hs_psize = 0;                                                     \
+              for ( _hs_i = 0; _hs_i  < _hs_insize; _hs_i++ ) {                  \
+                  _hs_psize++;                                                   \
+                  _hs_q = (UT_hash_handle*)((_hs_q->next) ?                      \
+                          ((void*)((char*)(_hs_q->next) +                        \
+                          (head)->hh.tbl->hho)) : NULL);                         \
+                  if (! (_hs_q) ) break;                                         \
+              }                                                                  \
+              _hs_qsize = _hs_insize;                                            \
+              while ((_hs_psize > 0) || ((_hs_qsize > 0) && _hs_q )) {           \
+                  if (_hs_psize == 0) {                                          \
+                      _hs_e = _hs_q;                                             \
+                      _hs_q = (UT_hash_handle*)((_hs_q->next) ?                  \
+                              ((void*)((char*)(_hs_q->next) +                    \
+                              (head)->hh.tbl->hho)) : NULL);                     \
+                      _hs_qsize--;                                               \
+                  } else if ( (_hs_qsize == 0) || !(_hs_q) ) {                   \
+                      _hs_e = _hs_p;                                             \
+                      _hs_p = (UT_hash_handle*)((_hs_p->next) ?                  \
+                              ((void*)((char*)(_hs_p->next) +                    \
+                              (head)->hh.tbl->hho)) : NULL);                     \
+                      _hs_psize--;                                               \
+                  } else if ((                                                   \
+                      cmpfcn(DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl,_hs_p)), \
+                             DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl,_hs_q))) \
+                             ) <= 0) {                                           \
+                      _hs_e = _hs_p;                                             \
+                      _hs_p = (UT_hash_handle*)((_hs_p->next) ?                  \
+                              ((void*)((char*)(_hs_p->next) +                    \
+                              (head)->hh.tbl->hho)) : NULL);                     \
+                      _hs_psize--;                                               \
+                  } else {                                                       \
+                      _hs_e = _hs_q;                                             \
+                      _hs_q = (UT_hash_handle*)((_hs_q->next) ?                  \
+                              ((void*)((char*)(_hs_q->next) +                    \
+                              (head)->hh.tbl->hho)) : NULL);                     \
+                      _hs_qsize--;                                               \
+                  }                                                              \
+                  if ( _hs_tail ) {                                              \
+                      _hs_tail->next = ((_hs_e) ?                                \
+                            ELMT_FROM_HH((head)->hh.tbl,_hs_e) : NULL);          \
+                  } else {                                                       \
+                      _hs_list = _hs_e;                                          \
+                  }                                                              \
+                  _hs_e->prev = ((_hs_tail) ?                                    \
+                     ELMT_FROM_HH((head)->hh.tbl,_hs_tail) : NULL);              \
+                  _hs_tail = _hs_e;                                              \
+              }                                                                  \
+              _hs_p = _hs_q;                                                     \
+          }                                                                      \
+          _hs_tail->next = NULL;                                                 \
+          if ( _hs_nmerges <= 1 ) {                                              \
+              _hs_looping=0;                                                     \
+              (head)->hh.tbl->tail = _hs_tail;                                   \
+              DECLTYPE_ASSIGN(head,ELMT_FROM_HH((head)->hh.tbl, _hs_list));      \
+          }                                                                      \
+          _hs_insize *= 2;                                                       \
+      }                                                                          \
+      HASH_FSCK(hh,head);                                                        \
+ }                                                                               \
+} while (0)
+
+/* This function selects items from one hash into another hash. 
+ * The end result is that the selected items have dual presence 
+ * in both hashes. There is no copy of the items made; rather 
+ * they are added into the new hash through a secondary hash 
+ * hash handle that must be present in the structure. */
+#define HASH_SELECT(hh_dst, dst, hh_src, src, cond)                              \
+do {                                                                             \
+  unsigned _src_bkt, _dst_bkt;                                                   \
+  void *_last_elt=NULL, *_elt;                                                   \
+  UT_hash_handle *_src_hh, *_dst_hh, *_last_elt_hh=NULL;                         \
+  ptrdiff_t _dst_hho = ((char*)(&(dst)->hh_dst) - (char*)(dst));                 \
+  if (src) {                                                                     \
+    for(_src_bkt=0; _src_bkt < (src)->hh_src.tbl->num_buckets; _src_bkt++) {     \
+      for(_src_hh = (src)->hh_src.tbl->buckets[_src_bkt].hh_head;                \
+          _src_hh;                                                               \
+          _src_hh = _src_hh->hh_next) {                                          \
+          _elt = ELMT_FROM_HH((src)->hh_src.tbl, _src_hh);                       \
+          if (cond(_elt)) {                                                      \
+            _dst_hh = (UT_hash_handle*)(((char*)_elt) + _dst_hho);               \
+            _dst_hh->key = _src_hh->key;                                         \
+            _dst_hh->keylen = _src_hh->keylen;                                   \
+            _dst_hh->hashv = _src_hh->hashv;                                     \
+            _dst_hh->prev = _last_elt;                                           \
+            _dst_hh->next = NULL;                                                \
+            if (_last_elt_hh) { _last_elt_hh->next = _elt; }                     \
+            if (!dst) {                                                          \
+              DECLTYPE_ASSIGN(dst,_elt);                                         \
+              HASH_MAKE_TABLE(hh_dst,dst);                                       \
+            } else {                                                             \
+              _dst_hh->tbl = (dst)->hh_dst.tbl;                                  \
+            }                                                                    \
+            HASH_TO_BKT(_dst_hh->hashv, _dst_hh->tbl->num_buckets, _dst_bkt);    \
+            HASH_ADD_TO_BKT(_dst_hh->tbl->buckets[_dst_bkt],_dst_hh);            \
+            (dst)->hh_dst.tbl->num_items++;                                      \
+            _last_elt = _elt;                                                    \
+            _last_elt_hh = _dst_hh;                                              \
+          }                                                                      \
+      }                                                                          \
+    }                                                                            \
+  }                                                                              \
+  HASH_FSCK(hh_dst,dst);                                                         \
+} while (0)
+
+#define HASH_CLEAR(hh,head)                                                      \
+do {                                                                             \
+  if (head) {                                                                    \
+    uthash_free((head)->hh.tbl->buckets );                                       \
+    uthash_free((head)->hh.tbl);                                                 \
+    (head)=NULL;                                                                 \
+  }                                                                              \
+} while(0)
+
+/* obtain a count of items in the hash */
+#define HASH_COUNT(head) HASH_CNT(hh,head) 
+#define HASH_CNT(hh,head) (head?(head->hh.tbl->num_items):0)
+
+typedef struct UT_hash_bucket {
+   struct UT_hash_handle *hh_head;
+   unsigned count;
+
+   /* expand_mult is normally set to 0. In this situation, the max chain length
+    * threshold is enforced at its default value, HASH_BKT_CAPACITY_THRESH. (If
+    * the bucket's chain exceeds this length, bucket expansion is triggered). 
+    * However, setting expand_mult to a non-zero value delays bucket expansion
+    * (that would be triggered by additions to this particular bucket)
+    * until its chain length reaches a *multiple* of HASH_BKT_CAPACITY_THRESH.
+    * (The multiplier is simply expand_mult+1). The whole idea of this
+    * multiplier is to reduce bucket expansions, since they are expensive, in
+    * situations where we know that a particular bucket tends to be overused.
+    * It is better to let its chain length grow to a longer yet-still-bounded
+    * value, than to do an O(n) bucket expansion too often. 
+    */
+   unsigned expand_mult;
+
+} UT_hash_bucket;
+
+/* random signature used only to find hash tables in external analysis */
+#define HASH_SIGNATURE 0xa0111fe1
+#define HASH_BLOOM_SIGNATURE 0xb12220f2
+
+typedef struct UT_hash_table {
+   UT_hash_bucket *buckets;
+   unsigned num_buckets, log2_num_buckets;
+   unsigned num_items;
+   struct UT_hash_handle *tail; /* tail hh in app order, for fast append    */
+   ptrdiff_t hho; /* hash handle offset (byte pos of hash handle in element */
+
+   /* in an ideal situation (all buckets used equally), no bucket would have
+    * more than ceil(#items/#buckets) items. that's the ideal chain length. */
+   unsigned ideal_chain_maxlen;
+
+   /* nonideal_items is the number of items in the hash whose chain position
+    * exceeds the ideal chain maxlen. these items pay the penalty for an uneven
+    * hash distribution; reaching them in a chain traversal takes >ideal steps */
+   unsigned nonideal_items;
+
+   /* ineffective expands occur when a bucket doubling was performed, but 
+    * afterward, more than half the items in the hash had nonideal chain
+    * positions. If this happens on two consecutive expansions we inhibit any
+    * further expansion, as it's not helping; this happens when the hash
+    * function isn't a good fit for the key domain. When expansion is inhibited
+    * the hash will still work, albeit no longer in constant time. */
+   unsigned ineff_expands, noexpand;
+
+   uint32_t signature; /* used only to find hash tables in external analysis */
+#ifdef HASH_BLOOM
+   uint32_t bloom_sig; /* used only to test bloom exists in external analysis */
+   uint8_t *bloom_bv;
+   char bloom_nbits;
+#endif
+
+} UT_hash_table;
+
+typedef struct UT_hash_handle {
+   struct UT_hash_table *tbl;
+   void *prev;                       /* prev element in app order      */
+   void *next;                       /* next element in app order      */
+   struct UT_hash_handle *hh_prev;   /* previous hh in bucket order    */
+   struct UT_hash_handle *hh_next;   /* next hh in bucket order        */
+   void *key;                        /* ptr to enclosing struct's key  */
+   unsigned keylen;                  /* enclosing struct's key len     */
+   unsigned hashv;                   /* result of hash-fcn(key)        */
+} UT_hash_handle;
+
+#endif /* UTHASH_H */
diff --git a/external/Vulkan/external/ktx/lib/vk_format.h b/external/Vulkan/external/ktx/lib/vk_format.h
new file mode 100644
index 0000000000000000000000000000000000000000..f8bd53b8c65813a8e13671b2d2ec50360e61d72a
--- /dev/null
+++ b/external/Vulkan/external/ktx/lib/vk_format.h
@@ -0,0 +1,1382 @@
+/*
+================================================================================================
+
+Description	:	Vulkan format properties and conversion from OpenGL.
+Author		:	J.M.P. van Waveren
+Date		:	07/17/2016
+Language	:	C99
+Format		:	Real tabs with the tab size equal to 4 spaces.
+Copyright	:	Copyright (c) 2016 Oculus VR, LLC. All Rights reserved.
+
+
+LICENSE
+=======
+
+Copyright (c) 2016 Oculus VR, LLC.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+DESCRIPTION
+===========
+
+This header implements several support routines to convert OpenGL formats/types
+to Vulkan formats. These routines are particularly useful for loading file
+formats that store OpenGL formats/types such as KTX and glTF.
+
+The functions in this header file convert the format, internalFormat and type
+that are used as parameters to the following OpenGL functions:
+
+void glTexImage2D( GLenum target, GLint level, GLint internalFormat,
+	GLsizei width, GLsizei height, GLint border,
+	GLenum format, GLenum type, const GLvoid * data );
+void glTexImage3D( GLenum target, GLint level, GLint internalFormat,
+	GLsizei width, GLsizei height, GLsizei depth, GLint border,
+	GLenum format, GLenum type, const GLvoid * data );
+void glCompressedTexImage2D( GLenum target, GLint level, GLenum internalformat,
+	GLsizei width, GLsizei height, GLint border,
+	GLsizei imageSize, const GLvoid * data );
+void glCompressedTexImage3D( GLenum target, GLint level, GLenum internalformat,
+	GLsizei width, GLsizei height, GLsizei depth, GLint border,
+	GLsizei imageSize, const GLvoid * data );
+void glTexStorage2D( GLenum target, GLsizei levels, GLenum internalformat,
+	GLsizei width, GLsizei height );
+void glTexStorage3D( GLenum target, GLsizei levels, GLenum internalformat,
+	GLsizei width, GLsizei height, GLsizei depth );
+void glVertexAttribPointer( GLuint index, GLint size, GLenum type, GLboolean normalized,
+	GLsizei stride, const GLvoid * pointer);
+
+
+IMPLEMENTATION
+==============
+
+This file does not include OpenGL / OpenGL ES headers because:
+
+  1. Including OpenGL / OpenGL ES headers is platform dependent and
+     may require a separate installation of an OpenGL SDK.
+  2. The OpenGL format/type constants are the same between extensions and core.
+  3. The OpenGL format/type constants are the same between OpenGL and OpenGL ES.
+  4. File formats like KTX and glTF may use OpenGL formats and types that
+     are not supported by the OpenGL implementation on the platform but are
+     supported by the Vulkan implementation.
+
+
+ENTRY POINTS
+============
+
+static inline VkFormat vkGetFormatFromOpenGLFormat( const GLenum format, const GLenum type );
+static inline VkFormat vkGetFormatFromOpenGLType( const GLenum type, const GLuint numComponents, const GLboolean normalized );
+static inline VkFormat vkGetFormatFromOpenGLInternalFormat( const GLenum internalFormat );
+static inline void vkGetFormatSize( const VkFormat format, VkFormatSize * pFormatSize );
+
+================================================================================================
+*/
+
+#if !defined( VK_FORMAT_H )
+#define VK_FORMAT_H
+
+#include "gl_format.h"
+
+static inline VkFormat vkGetFormatFromOpenGLFormat( const GLenum format, const GLenum type )
+{
+	switch ( type )
+	{
+		//
+		// 8 bits per component
+		//
+		case GL_UNSIGNED_BYTE:
+		{
+			switch ( format )
+			{
+				case GL_RED:					return VK_FORMAT_R8_UNORM;
+				case GL_RG:						return VK_FORMAT_R8G8_UNORM;
+				case GL_RGB:					return VK_FORMAT_R8G8B8_UNORM;
+				case GL_BGR:					return VK_FORMAT_B8G8R8_UNORM;
+				case GL_RGBA:					return VK_FORMAT_R8G8B8A8_UNORM;
+				case GL_BGRA:					return VK_FORMAT_B8G8R8A8_UNORM;
+				case GL_RED_INTEGER:			return VK_FORMAT_R8_UINT;
+				case GL_RG_INTEGER:				return VK_FORMAT_R8G8_UINT;
+				case GL_RGB_INTEGER:			return VK_FORMAT_R8G8B8_UINT;
+				case GL_BGR_INTEGER:			return VK_FORMAT_B8G8R8_UINT;
+				case GL_RGBA_INTEGER:			return VK_FORMAT_R8G8B8A8_UINT;
+				case GL_BGRA_INTEGER:			return VK_FORMAT_B8G8R8A8_UINT;
+				case GL_STENCIL_INDEX:			return VK_FORMAT_S8_UINT;
+				case GL_DEPTH_COMPONENT:		return VK_FORMAT_UNDEFINED;
+				case GL_DEPTH_STENCIL:			return VK_FORMAT_UNDEFINED;
+			}
+			break;
+		}
+		case GL_BYTE:
+		{
+			switch ( format )
+			{
+				case GL_RED:					return VK_FORMAT_R8_SNORM;
+				case GL_RG:						return VK_FORMAT_R8G8_SNORM;
+				case GL_RGB:					return VK_FORMAT_R8G8B8_SNORM;
+				case GL_BGR:					return VK_FORMAT_B8G8R8_SNORM;
+				case GL_RGBA:					return VK_FORMAT_R8G8B8A8_SNORM;
+				case GL_BGRA:					return VK_FORMAT_B8G8R8A8_SNORM;
+				case GL_RED_INTEGER:			return VK_FORMAT_R8_SINT;
+				case GL_RG_INTEGER:				return VK_FORMAT_R8G8_SINT;
+				case GL_RGB_INTEGER:			return VK_FORMAT_R8G8B8_SINT;
+				case GL_BGR_INTEGER:			return VK_FORMAT_B8G8R8_SINT;
+				case GL_RGBA_INTEGER:			return VK_FORMAT_R8G8B8A8_SINT;
+				case GL_BGRA_INTEGER:			return VK_FORMAT_B8G8R8A8_SINT;
+				case GL_STENCIL_INDEX:			return VK_FORMAT_UNDEFINED;
+				case GL_DEPTH_COMPONENT:		return VK_FORMAT_UNDEFINED;
+				case GL_DEPTH_STENCIL:			return VK_FORMAT_UNDEFINED;
+			}
+			break;
+		}
+
+		//
+		// 16 bits per component
+		//
+		case GL_UNSIGNED_SHORT:
+		{
+			switch ( format )
+			{
+				case GL_RED:					return VK_FORMAT_R16_UNORM;
+				case GL_RG:						return VK_FORMAT_R16G16_UNORM;
+				case GL_RGB:					return VK_FORMAT_R16G16B16_UNORM;
+				case GL_BGR:					return VK_FORMAT_UNDEFINED;
+				case GL_RGBA:					return VK_FORMAT_R16G16B16A16_UNORM;
+				case GL_BGRA:					return VK_FORMAT_UNDEFINED;
+				case GL_RED_INTEGER:			return VK_FORMAT_R16_UINT;
+				case GL_RG_INTEGER:				return VK_FORMAT_R16G16_UINT;
+				case GL_RGB_INTEGER:			return VK_FORMAT_R16G16B16_UINT;
+				case GL_BGR_INTEGER:			return VK_FORMAT_UNDEFINED;
+				case GL_RGBA_INTEGER:			return VK_FORMAT_R16G16B16A16_UINT;
+				case GL_BGRA_INTEGER:			return VK_FORMAT_UNDEFINED;
+				case GL_STENCIL_INDEX:			return VK_FORMAT_UNDEFINED;
+				case GL_DEPTH_COMPONENT:		return VK_FORMAT_D16_UNORM;
+				case GL_DEPTH_STENCIL:			return VK_FORMAT_D16_UNORM_S8_UINT;
+			}
+			break;
+		}
+		case GL_SHORT:
+		{
+			switch ( format )
+			{
+				case GL_RED:					return VK_FORMAT_R16_SNORM;
+				case GL_RG:						return VK_FORMAT_R16G16_SNORM;
+				case GL_RGB:					return VK_FORMAT_R16G16B16_SNORM;
+				case GL_BGR:					return VK_FORMAT_UNDEFINED;
+				case GL_RGBA:					return VK_FORMAT_R16G16B16A16_SNORM;
+				case GL_BGRA:					return VK_FORMAT_UNDEFINED;
+				case GL_RED_INTEGER:			return VK_FORMAT_R16_SINT;
+				case GL_RG_INTEGER:				return VK_FORMAT_R16G16_SINT;
+				case GL_RGB_INTEGER:			return VK_FORMAT_R16G16B16_SINT;
+				case GL_BGR_INTEGER:			return VK_FORMAT_UNDEFINED;
+				case GL_RGBA_INTEGER:			return VK_FORMAT_R16G16B16A16_SINT;
+				case GL_BGRA_INTEGER:			return VK_FORMAT_UNDEFINED;
+				case GL_STENCIL_INDEX:			return VK_FORMAT_UNDEFINED;
+				case GL_DEPTH_COMPONENT:		return VK_FORMAT_UNDEFINED;
+				case GL_DEPTH_STENCIL:			return VK_FORMAT_UNDEFINED;
+			}
+			break;
+		}
+		case GL_HALF_FLOAT:
+		case GL_HALF_FLOAT_OES:
+		{
+			switch ( format )
+			{
+				case GL_RED:					return VK_FORMAT_R16_SFLOAT;
+				case GL_RG:						return VK_FORMAT_R16G16_SFLOAT;
+				case GL_RGB:					return VK_FORMAT_R16G16B16_SFLOAT;
+				case GL_BGR:					return VK_FORMAT_UNDEFINED;
+				case GL_RGBA:					return VK_FORMAT_R16G16B16A16_SFLOAT;
+				case GL_BGRA:					return VK_FORMAT_UNDEFINED;
+				case GL_RED_INTEGER:			return VK_FORMAT_UNDEFINED;
+				case GL_RG_INTEGER:				return VK_FORMAT_UNDEFINED;
+				case GL_RGB_INTEGER:			return VK_FORMAT_UNDEFINED;
+				case GL_BGR_INTEGER:			return VK_FORMAT_UNDEFINED;
+				case GL_RGBA_INTEGER:			return VK_FORMAT_UNDEFINED;
+				case GL_BGRA_INTEGER:			return VK_FORMAT_UNDEFINED;
+				case GL_STENCIL_INDEX:			return VK_FORMAT_UNDEFINED;
+				case GL_DEPTH_COMPONENT:		return VK_FORMAT_UNDEFINED;
+				case GL_DEPTH_STENCIL:			return VK_FORMAT_UNDEFINED;
+			}
+			break;
+		}
+
+		//
+		// 32 bits per component
+		//
+		case GL_UNSIGNED_INT:
+		{
+			switch ( format )
+			{
+				case GL_RED:					return VK_FORMAT_R32_UINT;
+				case GL_RG:						return VK_FORMAT_R32G32_UINT;
+				case GL_RGB:					return VK_FORMAT_R32G32B32_UINT;
+				case GL_BGR:					return VK_FORMAT_UNDEFINED;
+				case GL_RGBA:					return VK_FORMAT_R32G32B32A32_UINT;
+				case GL_BGRA:					return VK_FORMAT_UNDEFINED;
+				case GL_RED_INTEGER:			return VK_FORMAT_R32_UINT;
+				case GL_RG_INTEGER:				return VK_FORMAT_R32G32_UINT;
+				case GL_RGB_INTEGER:			return VK_FORMAT_R32G32B32_UINT;
+				case GL_BGR_INTEGER:			return VK_FORMAT_UNDEFINED;
+				case GL_RGBA_INTEGER:			return VK_FORMAT_R32G32B32A32_UINT;
+				case GL_BGRA_INTEGER:			return VK_FORMAT_UNDEFINED;
+				case GL_STENCIL_INDEX:			return VK_FORMAT_UNDEFINED;
+				case GL_DEPTH_COMPONENT:		return VK_FORMAT_X8_D24_UNORM_PACK32;
+				case GL_DEPTH_STENCIL:			return VK_FORMAT_D24_UNORM_S8_UINT;
+			}
+			break;
+		}
+		case GL_INT:
+		{
+			switch ( format )
+			{
+				case GL_RED:					return VK_FORMAT_R32_SINT;
+				case GL_RG:						return VK_FORMAT_R32G32_SINT;
+				case GL_RGB:					return VK_FORMAT_R32G32B32_SINT;
+				case GL_BGR:					return VK_FORMAT_UNDEFINED;
+				case GL_RGBA:					return VK_FORMAT_R32G32B32A32_SINT;
+				case GL_BGRA:					return VK_FORMAT_UNDEFINED;
+				case GL_RED_INTEGER:			return VK_FORMAT_R32_SINT;
+				case GL_RG_INTEGER:				return VK_FORMAT_R32G32_SINT;
+				case GL_RGB_INTEGER:			return VK_FORMAT_R32G32B32_SINT;
+				case GL_BGR_INTEGER:			return VK_FORMAT_UNDEFINED;
+				case GL_RGBA_INTEGER:			return VK_FORMAT_R32G32B32A32_SINT;
+				case GL_BGRA_INTEGER:			return VK_FORMAT_UNDEFINED;
+				case GL_STENCIL_INDEX:			return VK_FORMAT_UNDEFINED;
+				case GL_DEPTH_COMPONENT:		return VK_FORMAT_UNDEFINED;
+				case GL_DEPTH_STENCIL:			return VK_FORMAT_UNDEFINED;
+			}
+			break;
+		}
+		case GL_FLOAT:
+		{
+			switch ( format )
+			{
+				case GL_RED:					return VK_FORMAT_R32_SFLOAT;
+				case GL_RG:						return VK_FORMAT_R32G32_SFLOAT;
+				case GL_RGB:					return VK_FORMAT_R32G32B32_SFLOAT;
+				case GL_BGR:					return VK_FORMAT_UNDEFINED;
+				case GL_RGBA:					return VK_FORMAT_R32G32B32A32_SFLOAT;
+				case GL_BGRA:					return VK_FORMAT_UNDEFINED;
+				case GL_RED_INTEGER:			return VK_FORMAT_UNDEFINED;
+				case GL_RG_INTEGER:				return VK_FORMAT_UNDEFINED;
+				case GL_RGB_INTEGER:			return VK_FORMAT_UNDEFINED;
+				case GL_BGR_INTEGER:			return VK_FORMAT_UNDEFINED;
+				case GL_RGBA_INTEGER:			return VK_FORMAT_UNDEFINED;
+				case GL_BGRA_INTEGER:			return VK_FORMAT_UNDEFINED;
+				case GL_STENCIL_INDEX:			return VK_FORMAT_UNDEFINED;
+				case GL_DEPTH_COMPONENT:		return VK_FORMAT_D32_SFLOAT;
+				case GL_DEPTH_STENCIL:			return VK_FORMAT_D32_SFLOAT_S8_UINT;
+			}
+			break;
+		}
+
+		//
+		// 64 bits per component
+		//
+		case GL_UNSIGNED_INT64:
+		{
+			switch ( format )
+			{
+				case GL_RED:					return VK_FORMAT_R64_UINT;
+				case GL_RG:						return VK_FORMAT_R64G64_UINT;
+				case GL_RGB:					return VK_FORMAT_R64G64B64_UINT;
+				case GL_BGR:					return VK_FORMAT_UNDEFINED;
+				case GL_RGBA:					return VK_FORMAT_R64G64B64A64_UINT;
+				case GL_BGRA:					return VK_FORMAT_UNDEFINED;
+				case GL_RED_INTEGER:			return VK_FORMAT_UNDEFINED;
+				case GL_RG_INTEGER:				return VK_FORMAT_UNDEFINED;
+				case GL_RGB_INTEGER:			return VK_FORMAT_UNDEFINED;
+				case GL_BGR_INTEGER:			return VK_FORMAT_UNDEFINED;
+				case GL_RGBA_INTEGER:			return VK_FORMAT_UNDEFINED;
+				case GL_BGRA_INTEGER:			return VK_FORMAT_UNDEFINED;
+				case GL_STENCIL_INDEX:			return VK_FORMAT_UNDEFINED;
+				case GL_DEPTH_COMPONENT:		return VK_FORMAT_UNDEFINED;
+				case GL_DEPTH_STENCIL:			return VK_FORMAT_UNDEFINED;
+			}
+			break;
+		}
+		case GL_INT64:
+		{
+			switch ( format )
+			{
+				case GL_RED:					return VK_FORMAT_R64_SINT;
+				case GL_RG:						return VK_FORMAT_R64G64_SINT;
+				case GL_RGB:					return VK_FORMAT_R64G64B64_SINT;
+				case GL_BGR:					return VK_FORMAT_UNDEFINED;
+				case GL_RGBA:					return VK_FORMAT_R64G64B64A64_SINT;
+				case GL_BGRA:					return VK_FORMAT_UNDEFINED;
+				case GL_RED_INTEGER:			return VK_FORMAT_R64_SINT;
+				case GL_RG_INTEGER:				return VK_FORMAT_R64G64_SINT;
+				case GL_RGB_INTEGER:			return VK_FORMAT_R64G64B64_SINT;
+				case GL_BGR_INTEGER:			return VK_FORMAT_UNDEFINED;
+				case GL_RGBA_INTEGER:			return VK_FORMAT_R64G64B64A64_SINT;
+				case GL_BGRA_INTEGER:			return VK_FORMAT_UNDEFINED;
+				case GL_STENCIL_INDEX:			return VK_FORMAT_UNDEFINED;
+				case GL_DEPTH_COMPONENT:		return VK_FORMAT_UNDEFINED;
+				case GL_DEPTH_STENCIL:			return VK_FORMAT_UNDEFINED;
+			}
+			break;
+		}
+		case GL_DOUBLE:
+		{
+			switch ( format )
+			{
+				case GL_RED:					return VK_FORMAT_R64_SFLOAT;
+				case GL_RG:						return VK_FORMAT_R64G64_SFLOAT;
+				case GL_RGB:					return VK_FORMAT_R64G64B64_SFLOAT;
+				case GL_BGR:					return VK_FORMAT_UNDEFINED;
+				case GL_RGBA:					return VK_FORMAT_R64G64B64A64_SFLOAT;
+				case GL_BGRA:					return VK_FORMAT_UNDEFINED;
+				case GL_RED_INTEGER:			return VK_FORMAT_R64_SFLOAT;
+				case GL_RG_INTEGER:				return VK_FORMAT_R64G64_SFLOAT;
+				case GL_RGB_INTEGER:			return VK_FORMAT_R64G64B64_SFLOAT;
+				case GL_BGR_INTEGER:			return VK_FORMAT_UNDEFINED;
+				case GL_RGBA_INTEGER:			return VK_FORMAT_R64G64B64A64_SFLOAT;
+				case GL_BGRA_INTEGER:			return VK_FORMAT_UNDEFINED;
+				case GL_STENCIL_INDEX:			return VK_FORMAT_UNDEFINED;
+				case GL_DEPTH_COMPONENT:		return VK_FORMAT_UNDEFINED;
+				case GL_DEPTH_STENCIL:			return VK_FORMAT_UNDEFINED;
+			}
+			break;
+		}
+
+		//
+		// Packed
+		//
+		case GL_UNSIGNED_BYTE_3_3_2:
+			assert( format == GL_RGB || format == GL_RGB_INTEGER );
+			return VK_FORMAT_UNDEFINED;
+		case GL_UNSIGNED_BYTE_2_3_3_REV:
+			assert( format == GL_BGR || format == GL_BGR_INTEGER );
+			return VK_FORMAT_UNDEFINED;
+		case GL_UNSIGNED_SHORT_5_6_5:
+			assert( format == GL_RGB || format == GL_RGB_INTEGER );
+			return VK_FORMAT_R5G6B5_UNORM_PACK16;
+		case GL_UNSIGNED_SHORT_5_6_5_REV:
+			assert( format == GL_BGR || format == GL_BGR_INTEGER );
+			return VK_FORMAT_B5G6R5_UNORM_PACK16;
+		case GL_UNSIGNED_SHORT_4_4_4_4:
+			assert( format == GL_RGB || format == GL_BGRA || format == GL_RGB_INTEGER || format == GL_BGRA_INTEGER );
+			return VK_FORMAT_R4G4B4A4_UNORM_PACK16;
+		case GL_UNSIGNED_SHORT_4_4_4_4_REV:
+			assert( format == GL_RGB || format == GL_BGRA || format == GL_RGB_INTEGER || format == GL_BGRA_INTEGER );
+			return VK_FORMAT_B4G4R4A4_UNORM_PACK16;
+		case GL_UNSIGNED_SHORT_5_5_5_1:
+			assert( format == GL_RGB || format == GL_BGRA || format == GL_RGB_INTEGER || format == GL_BGRA_INTEGER );
+			return VK_FORMAT_R5G5B5A1_UNORM_PACK16;
+		case GL_UNSIGNED_SHORT_1_5_5_5_REV:
+			assert( format == GL_RGB || format == GL_BGRA || format == GL_RGB_INTEGER || format == GL_BGRA_INTEGER );
+			return VK_FORMAT_A1R5G5B5_UNORM_PACK16;
+		case GL_UNSIGNED_INT_8_8_8_8:
+			assert( format == GL_RGB || format == GL_BGRA || format == GL_RGB_INTEGER || format == GL_BGRA_INTEGER );
+			return ( format == GL_RGB_INTEGER || format == GL_BGRA_INTEGER ) ? VK_FORMAT_R8G8B8A8_UINT : VK_FORMAT_R8G8B8A8_UNORM;
+		case GL_UNSIGNED_INT_8_8_8_8_REV:
+			assert( format == GL_RGB || format == GL_BGRA || format == GL_RGB_INTEGER || format == GL_BGRA_INTEGER );
+			return ( format == GL_RGB_INTEGER || format == GL_BGRA_INTEGER ) ? VK_FORMAT_A8B8G8R8_UINT_PACK32 : VK_FORMAT_A8B8G8R8_UNORM_PACK32;
+		case GL_UNSIGNED_INT_10_10_10_2:
+			assert( format == GL_RGB || format == GL_BGRA || format == GL_RGB_INTEGER || format == GL_BGRA_INTEGER );
+			return ( format == GL_RGB_INTEGER || format == GL_BGRA_INTEGER ) ? VK_FORMAT_A2R10G10B10_UINT_PACK32 : VK_FORMAT_A2R10G10B10_UNORM_PACK32;
+		case GL_UNSIGNED_INT_2_10_10_10_REV:
+			assert( format == GL_RGB || format == GL_BGRA || format == GL_RGB_INTEGER || format == GL_BGRA_INTEGER );
+			return ( format == GL_RGB_INTEGER || format == GL_BGRA_INTEGER ) ? VK_FORMAT_A2B10G10R10_UINT_PACK32 : VK_FORMAT_A2B10G10R10_UNORM_PACK32;
+		case GL_UNSIGNED_INT_10F_11F_11F_REV:
+			assert( format == GL_RGB || format == GL_BGR );
+			return VK_FORMAT_B10G11R11_UFLOAT_PACK32;
+		case GL_UNSIGNED_INT_5_9_9_9_REV:
+			assert( format == GL_RGB || format == GL_BGR );
+			return VK_FORMAT_E5B9G9R9_UFLOAT_PACK32;
+		case GL_UNSIGNED_INT_24_8:
+			assert( format == GL_DEPTH_STENCIL );
+			return VK_FORMAT_D24_UNORM_S8_UINT;
+		case GL_FLOAT_32_UNSIGNED_INT_24_8_REV:
+			assert( format == GL_DEPTH_STENCIL );
+			return VK_FORMAT_D32_SFLOAT_S8_UINT;
+	}
+
+	return VK_FORMAT_UNDEFINED;
+}
+
+static inline VkFormat vkGetFormatFromOpenGLType( const GLenum type, const GLuint numComponents, const GLboolean normalized )
+{
+	switch ( type )
+	{
+		//
+		// 8 bits per component
+		//
+		case GL_UNSIGNED_BYTE:
+		{
+			switch ( numComponents )
+			{
+				case 1:							return normalized ? VK_FORMAT_R8_UNORM : VK_FORMAT_R8_UINT;
+				case 2:							return normalized ? VK_FORMAT_R8G8_UNORM : VK_FORMAT_R8G8_UINT;
+				case 3:							return normalized ? VK_FORMAT_R8G8B8_UNORM : VK_FORMAT_R8G8B8_UINT;
+				case 4:							return normalized ? VK_FORMAT_R8G8B8A8_UNORM : VK_FORMAT_R8G8B8A8_UINT;
+			}
+			break;
+		}
+		case GL_BYTE:
+		{
+			switch ( numComponents )
+			{
+				case 1:							return normalized ? VK_FORMAT_R8_SNORM : VK_FORMAT_R8_SINT;
+				case 2:							return normalized ? VK_FORMAT_R8G8_SNORM : VK_FORMAT_R8G8_SINT;
+				case 3:							return normalized ? VK_FORMAT_R8G8B8_SNORM : VK_FORMAT_R8G8B8_SINT;
+				case 4:							return normalized ? VK_FORMAT_R8G8B8A8_SNORM : VK_FORMAT_R8G8B8A8_SINT;
+			}
+			break;
+		}
+
+		//
+		// 16 bits per component
+		//
+		case GL_UNSIGNED_SHORT:
+		{
+			switch ( numComponents )
+			{
+				case 1:							return normalized ? VK_FORMAT_R16_UNORM : VK_FORMAT_R16_UINT;
+				case 2:							return normalized ? VK_FORMAT_R16G16_UNORM : VK_FORMAT_R16G16_UINT;
+				case 3:							return normalized ? VK_FORMAT_R16G16B16_UNORM : VK_FORMAT_R16G16B16_UINT;
+				case 4:							return normalized ? VK_FORMAT_R16G16B16A16_UNORM : VK_FORMAT_R16G16B16A16_UINT;
+			}
+			break;
+		}
+		case GL_SHORT:
+		{
+			switch ( numComponents )
+			{
+				case 1:							return normalized ? VK_FORMAT_R16_SNORM : VK_FORMAT_R16_SINT;
+				case 2:							return normalized ? VK_FORMAT_R16G16_SNORM : VK_FORMAT_R16G16_SINT;
+				case 3:							return normalized ? VK_FORMAT_R16G16B16_SNORM : VK_FORMAT_R16G16B16_SINT;
+				case 4:							return normalized ? VK_FORMAT_R16G16B16A16_SNORM : VK_FORMAT_R16G16B16A16_SINT;
+			}
+			break;
+		}
+		case GL_HALF_FLOAT:
+		case GL_HALF_FLOAT_OES:
+		{
+			switch ( numComponents )
+			{
+				case 1:							return VK_FORMAT_R16_SFLOAT;
+				case 2:							return VK_FORMAT_R16G16_SFLOAT;
+				case 3:							return VK_FORMAT_R16G16B16_SFLOAT;
+				case 4:							return VK_FORMAT_R16G16B16A16_SFLOAT;
+			}
+			break;
+		}
+
+		//
+		// 32 bits per component
+		//
+		case GL_UNSIGNED_INT:
+		{
+			switch ( numComponents )
+			{
+				case 1:							return VK_FORMAT_R32_UINT;
+				case 2:							return VK_FORMAT_R32G32_UINT;
+				case 3:							return VK_FORMAT_R32G32B32_UINT;
+				case 4:							return VK_FORMAT_R32G32B32A32_UINT;
+			}
+			break;
+		}
+		case GL_INT:
+		{
+			switch ( numComponents )
+			{
+				case 1:							return VK_FORMAT_R32_SINT;
+				case 2:							return VK_FORMAT_R32G32_SINT;
+				case 3:							return VK_FORMAT_R32G32B32_SINT;
+				case 4:							return VK_FORMAT_R32G32B32A32_SINT;
+			}
+			break;
+		}
+		case GL_FLOAT:
+		{
+			switch ( numComponents )
+			{
+				case 1:							return VK_FORMAT_R32_SFLOAT;
+				case 2:							return VK_FORMAT_R32G32_SFLOAT;
+				case 3:							return VK_FORMAT_R32G32B32_SFLOAT;
+				case 4:							return VK_FORMAT_R32G32B32A32_SFLOAT;
+			}
+			break;
+		}
+
+		//
+		// 64 bits per component
+		//
+		case GL_UNSIGNED_INT64:
+		{
+			switch ( numComponents )
+			{
+				case 1:							return VK_FORMAT_R64_UINT;
+				case 2:							return VK_FORMAT_R64G64_UINT;
+				case 3:							return VK_FORMAT_R64G64B64_UINT;
+				case 4:							return VK_FORMAT_R64G64B64A64_UINT;
+			}
+			break;
+		}
+		case GL_INT64:
+		{
+			switch ( numComponents )
+			{
+				case 1:							return VK_FORMAT_R64_SINT;
+				case 2:							return VK_FORMAT_R64G64_SINT;
+				case 3:							return VK_FORMAT_R64G64B64_SINT;
+				case 4:							return VK_FORMAT_R64G64B64A64_SINT;
+			}
+			break;
+		}
+		case GL_DOUBLE:
+		{
+			switch ( numComponents )
+			{
+				case 1:							return VK_FORMAT_R64_SFLOAT;
+				case 2:							return VK_FORMAT_R64G64_SFLOAT;
+				case 3:							return VK_FORMAT_R64G64B64_SFLOAT;
+				case 4:							return VK_FORMAT_R64G64B64A64_SFLOAT;
+			}
+			break;
+		}
+
+		//
+		// Packed
+		//
+		case GL_UNSIGNED_BYTE_3_3_2:			return VK_FORMAT_UNDEFINED;
+		case GL_UNSIGNED_BYTE_2_3_3_REV:		return VK_FORMAT_UNDEFINED;
+		case GL_UNSIGNED_SHORT_5_6_5:			return VK_FORMAT_R5G6B5_UNORM_PACK16;
+		case GL_UNSIGNED_SHORT_5_6_5_REV:		return VK_FORMAT_B5G6R5_UNORM_PACK16;
+		case GL_UNSIGNED_SHORT_4_4_4_4:			return VK_FORMAT_R4G4B4A4_UNORM_PACK16;
+		case GL_UNSIGNED_SHORT_4_4_4_4_REV:		return VK_FORMAT_B4G4R4A4_UNORM_PACK16;
+		case GL_UNSIGNED_SHORT_5_5_5_1:			return VK_FORMAT_R5G5B5A1_UNORM_PACK16;
+		case GL_UNSIGNED_SHORT_1_5_5_5_REV:		return VK_FORMAT_A1R5G5B5_UNORM_PACK16;
+		case GL_UNSIGNED_INT_8_8_8_8:			return normalized ? VK_FORMAT_R8G8B8A8_UNORM : VK_FORMAT_R8G8B8A8_UINT;
+		case GL_UNSIGNED_INT_8_8_8_8_REV:		return normalized ? VK_FORMAT_A8B8G8R8_UNORM_PACK32 : VK_FORMAT_A8B8G8R8_UINT_PACK32;
+		case GL_UNSIGNED_INT_10_10_10_2:		return normalized ? VK_FORMAT_A2R10G10B10_UNORM_PACK32 : VK_FORMAT_A2R10G10B10_UINT_PACK32;
+		case GL_UNSIGNED_INT_2_10_10_10_REV:	return normalized ? VK_FORMAT_A2B10G10R10_UNORM_PACK32 : VK_FORMAT_A2B10G10R10_UINT_PACK32;
+		case GL_UNSIGNED_INT_10F_11F_11F_REV:	return VK_FORMAT_B10G11R11_UFLOAT_PACK32;
+		case GL_UNSIGNED_INT_5_9_9_9_REV:		return VK_FORMAT_E5B9G9R9_UFLOAT_PACK32;
+		case GL_UNSIGNED_INT_24_8:				return VK_FORMAT_D24_UNORM_S8_UINT;
+		case GL_FLOAT_32_UNSIGNED_INT_24_8_REV:	return VK_FORMAT_D32_SFLOAT_S8_UINT;
+	}
+
+	return VK_FORMAT_UNDEFINED;
+}
+
+static inline VkFormat vkGetFormatFromOpenGLInternalFormat( const GLenum internalFormat )
+{
+	switch ( internalFormat )
+	{
+		//
+		// 8 bits per component
+		//
+		case GL_R8:												return VK_FORMAT_R8_UNORM;					// 1-component, 8-bit unsigned normalized
+		case GL_RG8:											return VK_FORMAT_R8G8_UNORM;				// 2-component, 8-bit unsigned normalized
+		case GL_RGB8:											return VK_FORMAT_R8G8B8_UNORM;				// 3-component, 8-bit unsigned normalized
+		case GL_RGBA8:											return VK_FORMAT_R8G8B8A8_UNORM;			// 4-component, 8-bit unsigned normalized
+
+		case GL_R8_SNORM:										return VK_FORMAT_R8_SNORM;					// 1-component, 8-bit signed normalized
+		case GL_RG8_SNORM:										return VK_FORMAT_R8G8_SNORM;				// 2-component, 8-bit signed normalized
+		case GL_RGB8_SNORM:										return VK_FORMAT_R8G8B8_SNORM;				// 3-component, 8-bit signed normalized
+		case GL_RGBA8_SNORM:									return VK_FORMAT_R8G8B8A8_SNORM;			// 4-component, 8-bit signed normalized
+
+		case GL_R8UI:											return VK_FORMAT_R8_UINT;					// 1-component, 8-bit unsigned integer
+		case GL_RG8UI:											return VK_FORMAT_R8G8_UINT;					// 2-component, 8-bit unsigned integer
+		case GL_RGB8UI:											return VK_FORMAT_R8G8B8_UINT;				// 3-component, 8-bit unsigned integer
+		case GL_RGBA8UI:										return VK_FORMAT_R8G8B8A8_UINT;				// 4-component, 8-bit unsigned integer
+
+		case GL_R8I:											return VK_FORMAT_R8_SINT;					// 1-component, 8-bit signed integer
+		case GL_RG8I:											return VK_FORMAT_R8G8_SINT;					// 2-component, 8-bit signed integer
+		case GL_RGB8I:											return VK_FORMAT_R8G8B8_SINT;				// 3-component, 8-bit signed integer
+		case GL_RGBA8I:											return VK_FORMAT_R8G8B8A8_SINT;				// 4-component, 8-bit signed integer
+
+		case GL_SR8:											return VK_FORMAT_R8_SRGB;					// 1-component, 8-bit sRGB
+		case GL_SRG8:											return VK_FORMAT_R8G8_SRGB;					// 2-component, 8-bit sRGB
+		case GL_SRGB8:											return VK_FORMAT_R8G8B8_SRGB;				// 3-component, 8-bit sRGB
+		case GL_SRGB8_ALPHA8:									return VK_FORMAT_R8G8B8A8_SRGB;				// 4-component, 8-bit sRGB
+
+		//
+		// 16 bits per component
+		//
+		case GL_R16:											return VK_FORMAT_R16_UNORM;					// 1-component, 16-bit unsigned normalized
+		case GL_RG16:											return VK_FORMAT_R16G16_UNORM;				// 2-component, 16-bit unsigned normalized
+		case GL_RGB16:											return VK_FORMAT_R16G16B16_UNORM;			// 3-component, 16-bit unsigned normalized
+		case GL_RGBA16:											return VK_FORMAT_R16G16B16A16_UNORM;		// 4-component, 16-bit unsigned normalized
+
+		case GL_R16_SNORM:										return VK_FORMAT_R16_SNORM;					// 1-component, 16-bit signed normalized
+		case GL_RG16_SNORM:										return VK_FORMAT_R16G16_SNORM;				// 2-component, 16-bit signed normalized
+		case GL_RGB16_SNORM:									return VK_FORMAT_R16G16B16_SNORM;			// 3-component, 16-bit signed normalized
+		case GL_RGBA16_SNORM:									return VK_FORMAT_R16G16B16A16_SNORM;		// 4-component, 16-bit signed normalized
+
+		case GL_R16UI:											return VK_FORMAT_R16_UINT;					// 1-component, 16-bit unsigned integer
+		case GL_RG16UI:											return VK_FORMAT_R16G16_UINT;				// 2-component, 16-bit unsigned integer
+		case GL_RGB16UI:										return VK_FORMAT_R16G16B16_UINT;			// 3-component, 16-bit unsigned integer
+		case GL_RGBA16UI:										return VK_FORMAT_R16G16B16A16_UINT;			// 4-component, 16-bit unsigned integer
+
+		case GL_R16I:											return VK_FORMAT_R16_SINT;					// 1-component, 16-bit signed integer
+		case GL_RG16I:											return VK_FORMAT_R16G16_SINT;				// 2-component, 16-bit signed integer
+		case GL_RGB16I:											return VK_FORMAT_R16G16B16_SINT;			// 3-component, 16-bit signed integer
+		case GL_RGBA16I:										return VK_FORMAT_R16G16B16A16_SINT;			// 4-component, 16-bit signed integer
+
+		case GL_R16F:											return VK_FORMAT_R16_SFLOAT;				// 1-component, 16-bit floating-point
+		case GL_RG16F:											return VK_FORMAT_R16G16_SFLOAT;				// 2-component, 16-bit floating-point
+		case GL_RGB16F:											return VK_FORMAT_R16G16B16_SFLOAT;			// 3-component, 16-bit floating-point
+		case GL_RGBA16F:										return VK_FORMAT_R16G16B16A16_SFLOAT;		// 4-component, 16-bit floating-point
+
+		//
+		// 32 bits per component
+		//
+		case GL_R32UI:											return VK_FORMAT_R32_UINT;					// 1-component, 32-bit unsigned integer
+		case GL_RG32UI:											return VK_FORMAT_R32G32_UINT;				// 2-component, 32-bit unsigned integer
+		case GL_RGB32UI:										return VK_FORMAT_R32G32B32_UINT;			// 3-component, 32-bit unsigned integer
+		case GL_RGBA32UI:										return VK_FORMAT_R32G32B32A32_UINT;			// 4-component, 32-bit unsigned integer
+
+		case GL_R32I:											return VK_FORMAT_R32_SINT;					// 1-component, 32-bit signed integer
+		case GL_RG32I:											return VK_FORMAT_R32G32_SINT;				// 2-component, 32-bit signed integer
+		case GL_RGB32I:											return VK_FORMAT_R32G32B32_SINT;			// 3-component, 32-bit signed integer
+		case GL_RGBA32I:										return VK_FORMAT_R32G32B32A32_SINT;			// 4-component, 32-bit signed integer
+
+		case GL_R32F:											return VK_FORMAT_R32_SFLOAT;				// 1-component, 32-bit floating-point
+		case GL_RG32F:											return VK_FORMAT_R32G32_SFLOAT;				// 2-component, 32-bit floating-point
+		case GL_RGB32F:											return VK_FORMAT_R32G32B32_SFLOAT;			// 3-component, 32-bit floating-point
+		case GL_RGBA32F:										return VK_FORMAT_R32G32B32A32_SFLOAT;		// 4-component, 32-bit floating-point
+
+		//
+		// Packed
+		//
+		case GL_R3_G3_B2:										return VK_FORMAT_UNDEFINED;					// 3-component 3:3:2,       unsigned normalized
+		case GL_RGB4:											return VK_FORMAT_UNDEFINED;					// 3-component 4:4:4,       unsigned normalized
+		case GL_RGB5:											return VK_FORMAT_R5G5B5A1_UNORM_PACK16;		// 3-component 5:5:5,       unsigned normalized
+		case GL_RGB565:											return VK_FORMAT_R5G6B5_UNORM_PACK16;		// 3-component 5:6:5,       unsigned normalized
+		case GL_RGB10:											return VK_FORMAT_A2R10G10B10_UNORM_PACK32;	// 3-component 10:10:10,    unsigned normalized
+		case GL_RGB12:											return VK_FORMAT_UNDEFINED;					// 3-component 12:12:12,    unsigned normalized
+		case GL_RGBA2:											return VK_FORMAT_UNDEFINED;					// 4-component 2:2:2:2,     unsigned normalized
+		case GL_RGBA4:											return VK_FORMAT_R4G4B4A4_UNORM_PACK16;		// 4-component 4:4:4:4,     unsigned normalized
+		case GL_RGBA12:											return VK_FORMAT_UNDEFINED;					// 4-component 12:12:12:12, unsigned normalized
+		case GL_RGB5_A1:										return VK_FORMAT_A1R5G5B5_UNORM_PACK16;		// 4-component 5:5:5:1,     unsigned normalized
+		case GL_RGB10_A2:										return VK_FORMAT_A2R10G10B10_UNORM_PACK32;	// 4-component 10:10:10:2,  unsigned normalized
+		case GL_RGB10_A2UI:										return VK_FORMAT_A2R10G10B10_UINT_PACK32;	// 4-component 10:10:10:2,  unsigned integer
+		case GL_R11F_G11F_B10F:									return VK_FORMAT_B10G11R11_UFLOAT_PACK32;	// 3-component 11:11:10,    floating-point
+		case GL_RGB9_E5:										return VK_FORMAT_E5B9G9R9_UFLOAT_PACK32;	// 3-component/exp 9:9:9/5, floating-point
+
+		//
+		// S3TC/DXT/BC
+		//
+
+		case GL_COMPRESSED_RGB_S3TC_DXT1_EXT:					return VK_FORMAT_BC1_RGB_UNORM_BLOCK;		// line through 3D space, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:					return VK_FORMAT_BC1_RGBA_UNORM_BLOCK;		// line through 3D space plus 1-bit alpha, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:					return VK_FORMAT_BC2_UNORM_BLOCK;			// line through 3D space plus line through 1D space, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:					return VK_FORMAT_BC3_UNORM_BLOCK;			// line through 3D space plus 4-bit alpha, 4x4 blocks, unsigned normalized
+
+		case GL_COMPRESSED_SRGB_S3TC_DXT1_EXT:					return VK_FORMAT_BC1_RGB_SRGB_BLOCK;		// line through 3D space, 4x4 blocks, sRGB
+		case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT:			return VK_FORMAT_BC1_RGBA_SRGB_BLOCK;		// line through 3D space plus 1-bit alpha, 4x4 blocks, sRGB
+		case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT:			return VK_FORMAT_BC2_SRGB_BLOCK;			// line through 3D space plus line through 1D space, 4x4 blocks, sRGB
+		case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:			return VK_FORMAT_BC3_SRGB_BLOCK;			// line through 3D space plus 4-bit alpha, 4x4 blocks, sRGB
+
+		case GL_COMPRESSED_LUMINANCE_LATC1_EXT:					return VK_FORMAT_BC4_UNORM_BLOCK;			// line through 1D space, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT:			return VK_FORMAT_BC5_UNORM_BLOCK;			// two lines through 1D space, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_SIGNED_LUMINANCE_LATC1_EXT:			return VK_FORMAT_BC4_SNORM_BLOCK;			// line through 1D space, 4x4 blocks, signed normalized
+		case GL_COMPRESSED_SIGNED_LUMINANCE_ALPHA_LATC2_EXT:	return VK_FORMAT_BC5_SNORM_BLOCK;			// two lines through 1D space, 4x4 blocks, signed normalized
+
+		case GL_COMPRESSED_RED_RGTC1:							return VK_FORMAT_BC4_UNORM_BLOCK;			// line through 1D space, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_RG_RGTC2:							return VK_FORMAT_BC5_UNORM_BLOCK;			// two lines through 1D space, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_SIGNED_RED_RGTC1:					return VK_FORMAT_BC4_SNORM_BLOCK;			// line through 1D space, 4x4 blocks, signed normalized
+		case GL_COMPRESSED_SIGNED_RG_RGTC2:						return VK_FORMAT_BC5_SNORM_BLOCK;			// two lines through 1D space, 4x4 blocks, signed normalized
+
+		case GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT:				return VK_FORMAT_BC6H_UFLOAT_BLOCK;			// 3-component, 4x4 blocks, unsigned floating-point
+		case GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT:				return VK_FORMAT_BC6H_SFLOAT_BLOCK;			// 3-component, 4x4 blocks, signed floating-point
+		case GL_COMPRESSED_RGBA_BPTC_UNORM:						return VK_FORMAT_BC7_UNORM_BLOCK;			// 4-component, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM:				return VK_FORMAT_BC7_SRGB_BLOCK;			// 4-component, 4x4 blocks, sRGB
+
+		//
+		// ETC
+		//
+		case GL_ETC1_RGB8_OES:									return VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK;	// 3-component ETC1, 4x4 blocks, unsigned normalized
+
+		case GL_COMPRESSED_RGB8_ETC2:							return VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK;	// 3-component ETC2, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:		return VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK;	// 4-component ETC2 with 1-bit alpha, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA8_ETC2_EAC:						return VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK;	// 4-component ETC2, 4x4 blocks, unsigned normalized
+
+		case GL_COMPRESSED_SRGB8_ETC2:							return VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK;	// 3-component ETC2, 4x4 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2:		return VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK;	// 4-component ETC2 with 1-bit alpha, 4x4 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC:				return VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK;	// 4-component ETC2, 4x4 blocks, sRGB
+
+		case GL_COMPRESSED_R11_EAC:								return VK_FORMAT_EAC_R11_UNORM_BLOCK;		// 1-component ETC, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_RG11_EAC:							return VK_FORMAT_EAC_R11G11_UNORM_BLOCK;	// 2-component ETC, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_SIGNED_R11_EAC:						return VK_FORMAT_EAC_R11_SNORM_BLOCK;		// 1-component ETC, 4x4 blocks, signed normalized
+		case GL_COMPRESSED_SIGNED_RG11_EAC:						return VK_FORMAT_EAC_R11G11_SNORM_BLOCK;	// 2-component ETC, 4x4 blocks, signed normalized
+
+		//
+		// PVRTC
+		//
+		case GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG: return VK_FORMAT_PVRTC1_2BPP_UNORM_BLOCK_IMG;			// 3-component PVRTC, 16x8 blocks, unsigned normalized
+		case GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG: return VK_FORMAT_PVRTC1_4BPP_UNORM_BLOCK_IMG;			// 3-component PVRTC,  8x8 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG: return VK_FORMAT_PVRTC1_2BPP_UNORM_BLOCK_IMG;			// 4-component PVRTC, 16x8 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG: return VK_FORMAT_PVRTC1_4BPP_UNORM_BLOCK_IMG;			// 4-component PVRTC,  8x8 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_PVRTC_2BPPV2_IMG: return VK_FORMAT_PVRTC2_2BPP_UNORM_BLOCK_IMG;			// 4-component PVRTC,  8x4 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_PVRTC_4BPPV2_IMG: return VK_FORMAT_PVRTC2_4BPP_UNORM_BLOCK_IMG;			// 4-component PVRTC,  4x4 blocks, unsigned normalized
+
+		case GL_COMPRESSED_SRGB_PVRTC_2BPPV1_EXT: return VK_FORMAT_PVRTC1_2BPP_SRGB_BLOCK_IMG;			// 3-component PVRTC, 16x8 blocks, sRGB
+		case GL_COMPRESSED_SRGB_PVRTC_4BPPV1_EXT: return VK_FORMAT_PVRTC1_4BPP_SRGB_BLOCK_IMG;			// 3-component PVRTC,  8x8 blocks, sRGB
+		case GL_COMPRESSED_SRGB_ALPHA_PVRTC_2BPPV1_EXT: return VK_FORMAT_PVRTC1_2BPP_SRGB_BLOCK_IMG;	// 4-component PVRTC, 16x8 blocks, sRGB
+		case GL_COMPRESSED_SRGB_ALPHA_PVRTC_4BPPV1_EXT: return VK_FORMAT_PVRTC1_4BPP_SRGB_BLOCK_IMG;	// 4-component PVRTC,  8x8 blocks, sRGB
+		case GL_COMPRESSED_SRGB_ALPHA_PVRTC_2BPPV2_IMG: return VK_FORMAT_PVRTC2_2BPP_SRGB_BLOCK_IMG;	// 4-component PVRTC,  8x4 blocks, sRGB
+		case GL_COMPRESSED_SRGB_ALPHA_PVRTC_4BPPV2_IMG: return VK_FORMAT_PVRTC2_4BPP_SRGB_BLOCK_IMG;	// 4-component PVRTC,  4x4 blocks, sRGB
+
+		//
+		// ASTC
+		//
+		case GL_COMPRESSED_RGBA_ASTC_4x4_KHR:					return VK_FORMAT_ASTC_4x4_UNORM_BLOCK;		// 4-component ASTC, 4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_5x4_KHR:					return VK_FORMAT_ASTC_5x4_UNORM_BLOCK;		// 4-component ASTC, 5x4 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_5x5_KHR:					return VK_FORMAT_ASTC_5x5_UNORM_BLOCK;		// 4-component ASTC, 5x5 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_6x5_KHR:					return VK_FORMAT_ASTC_6x5_UNORM_BLOCK;		// 4-component ASTC, 6x5 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_6x6_KHR:					return VK_FORMAT_ASTC_6x6_UNORM_BLOCK;		// 4-component ASTC, 6x6 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_8x5_KHR:					return VK_FORMAT_ASTC_8x5_UNORM_BLOCK;		// 4-component ASTC, 8x5 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_8x6_KHR:					return VK_FORMAT_ASTC_8x6_UNORM_BLOCK;		// 4-component ASTC, 8x6 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_8x8_KHR:					return VK_FORMAT_ASTC_8x8_UNORM_BLOCK;		// 4-component ASTC, 8x8 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_10x5_KHR:					return VK_FORMAT_ASTC_10x5_UNORM_BLOCK;		// 4-component ASTC, 10x5 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_10x6_KHR:					return VK_FORMAT_ASTC_10x6_UNORM_BLOCK;		// 4-component ASTC, 10x6 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_10x8_KHR:					return VK_FORMAT_ASTC_10x8_UNORM_BLOCK;		// 4-component ASTC, 10x8 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_10x10_KHR:					return VK_FORMAT_ASTC_10x10_UNORM_BLOCK;	// 4-component ASTC, 10x10 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_12x10_KHR:					return VK_FORMAT_ASTC_12x10_UNORM_BLOCK;	// 4-component ASTC, 12x10 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_12x12_KHR:					return VK_FORMAT_ASTC_12x12_UNORM_BLOCK;	// 4-component ASTC, 12x12 blocks, unsigned normalized
+
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR:			return VK_FORMAT_ASTC_4x4_SRGB_BLOCK;		// 4-component ASTC, 4x4 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR:			return VK_FORMAT_ASTC_5x4_SRGB_BLOCK;		// 4-component ASTC, 5x4 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR:			return VK_FORMAT_ASTC_5x5_SRGB_BLOCK;		// 4-component ASTC, 5x5 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR:			return VK_FORMAT_ASTC_6x5_SRGB_BLOCK;		// 4-component ASTC, 6x5 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR:			return VK_FORMAT_ASTC_6x6_SRGB_BLOCK;		// 4-component ASTC, 6x6 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR:			return VK_FORMAT_ASTC_8x5_SRGB_BLOCK;		// 4-component ASTC, 8x5 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR:			return VK_FORMAT_ASTC_8x6_SRGB_BLOCK;		// 4-component ASTC, 8x6 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR:			return VK_FORMAT_ASTC_8x8_SRGB_BLOCK;		// 4-component ASTC, 8x8 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR:			return VK_FORMAT_ASTC_10x5_SRGB_BLOCK;		// 4-component ASTC, 10x5 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR:			return VK_FORMAT_ASTC_10x6_SRGB_BLOCK;		// 4-component ASTC, 10x6 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR:			return VK_FORMAT_ASTC_10x8_SRGB_BLOCK;		// 4-component ASTC, 10x8 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR:			return VK_FORMAT_ASTC_10x10_SRGB_BLOCK;		// 4-component ASTC, 10x10 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR:			return VK_FORMAT_ASTC_12x10_SRGB_BLOCK;		// 4-component ASTC, 12x10 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR:			return VK_FORMAT_ASTC_12x12_SRGB_BLOCK;		// 4-component ASTC, 12x12 blocks, sRGB
+
+		case GL_COMPRESSED_RGBA_ASTC_3x3x3_OES:					return VK_FORMAT_UNDEFINED;					// 4-component ASTC, 3x3x3 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_4x3x3_OES:					return VK_FORMAT_UNDEFINED;					// 4-component ASTC, 4x3x3 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_4x4x3_OES:					return VK_FORMAT_UNDEFINED;					// 4-component ASTC, 4x4x3 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_4x4x4_OES:					return VK_FORMAT_UNDEFINED;					// 4-component ASTC, 4x4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_5x4x4_OES:					return VK_FORMAT_UNDEFINED;					// 4-component ASTC, 5x4x4 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_5x5x4_OES:					return VK_FORMAT_UNDEFINED;					// 4-component ASTC, 5x5x4 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_5x5x5_OES:					return VK_FORMAT_UNDEFINED;					// 4-component ASTC, 5x5x5 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_6x5x5_OES:					return VK_FORMAT_UNDEFINED;					// 4-component ASTC, 6x5x5 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_6x6x5_OES:					return VK_FORMAT_UNDEFINED;					// 4-component ASTC, 6x6x5 blocks, unsigned normalized
+		case GL_COMPRESSED_RGBA_ASTC_6x6x6_OES:					return VK_FORMAT_UNDEFINED;					// 4-component ASTC, 6x6x6 blocks, unsigned normalized
+
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_3x3x3_OES:			return VK_FORMAT_UNDEFINED;					// 4-component ASTC, 3x3x3 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x3x3_OES:			return VK_FORMAT_UNDEFINED;					// 4-component ASTC, 4x3x3 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4x3_OES:			return VK_FORMAT_UNDEFINED;					// 4-component ASTC, 4x4x3 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4x4_OES:			return VK_FORMAT_UNDEFINED;					// 4-component ASTC, 4x4x4 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4x4_OES:			return VK_FORMAT_UNDEFINED;					// 4-component ASTC, 5x4x4 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5x4_OES:			return VK_FORMAT_UNDEFINED;					// 4-component ASTC, 5x5x4 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5x5_OES:			return VK_FORMAT_UNDEFINED;					// 4-component ASTC, 5x5x5 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5x5_OES:			return VK_FORMAT_UNDEFINED;					// 4-component ASTC, 6x5x5 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6x5_OES:			return VK_FORMAT_UNDEFINED;					// 4-component ASTC, 6x6x5 blocks, sRGB
+		case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6x6_OES:			return VK_FORMAT_UNDEFINED;					// 4-component ASTC, 6x6x6 blocks, sRGB
+
+		//
+		// ATC
+		//
+		case GL_ATC_RGB_AMD:									return VK_FORMAT_UNDEFINED;					// 3-component, 4x4 blocks, unsigned normalized
+		case GL_ATC_RGBA_EXPLICIT_ALPHA_AMD:					return VK_FORMAT_UNDEFINED;					// 4-component, 4x4 blocks, unsigned normalized
+		case GL_ATC_RGBA_INTERPOLATED_ALPHA_AMD:				return VK_FORMAT_UNDEFINED;					// 4-component, 4x4 blocks, unsigned normalized
+
+		//
+		// Palletized
+		//
+		case GL_PALETTE4_RGB8_OES:								return VK_FORMAT_UNDEFINED;					// 3-component 8:8:8,   4-bit palette, unsigned normalized
+		case GL_PALETTE4_RGBA8_OES:								return VK_FORMAT_UNDEFINED;					// 4-component 8:8:8:8, 4-bit palette, unsigned normalized
+		case GL_PALETTE4_R5_G6_B5_OES:							return VK_FORMAT_UNDEFINED;					// 3-component 5:6:5,   4-bit palette, unsigned normalized
+		case GL_PALETTE4_RGBA4_OES:								return VK_FORMAT_UNDEFINED;					// 4-component 4:4:4:4, 4-bit palette, unsigned normalized
+		case GL_PALETTE4_RGB5_A1_OES:							return VK_FORMAT_UNDEFINED;					// 4-component 5:5:5:1, 4-bit palette, unsigned normalized
+		case GL_PALETTE8_RGB8_OES:								return VK_FORMAT_UNDEFINED;					// 3-component 8:8:8,   8-bit palette, unsigned normalized
+		case GL_PALETTE8_RGBA8_OES:								return VK_FORMAT_UNDEFINED;					// 4-component 8:8:8:8, 8-bit palette, unsigned normalized
+		case GL_PALETTE8_R5_G6_B5_OES:							return VK_FORMAT_UNDEFINED;					// 3-component 5:6:5,   8-bit palette, unsigned normalized
+		case GL_PALETTE8_RGBA4_OES:								return VK_FORMAT_UNDEFINED;					// 4-component 4:4:4:4, 8-bit palette, unsigned normalized
+		case GL_PALETTE8_RGB5_A1_OES:							return VK_FORMAT_UNDEFINED;					// 4-component 5:5:5:1, 8-bit palette, unsigned normalized
+
+		//
+		// Depth/stencil
+		//
+		case GL_DEPTH_COMPONENT16:								return VK_FORMAT_D16_UNORM;
+		case GL_DEPTH_COMPONENT24:								return VK_FORMAT_X8_D24_UNORM_PACK32;
+		case GL_DEPTH_COMPONENT32:								return VK_FORMAT_UNDEFINED;
+		case GL_DEPTH_COMPONENT32F:								return VK_FORMAT_D32_SFLOAT;
+		case GL_DEPTH_COMPONENT32F_NV:							return VK_FORMAT_D32_SFLOAT;
+		case GL_STENCIL_INDEX1:									return VK_FORMAT_UNDEFINED;
+		case GL_STENCIL_INDEX4:									return VK_FORMAT_UNDEFINED;
+		case GL_STENCIL_INDEX8:									return VK_FORMAT_S8_UINT;
+		case GL_STENCIL_INDEX16:								return VK_FORMAT_UNDEFINED;
+		case GL_DEPTH24_STENCIL8:								return VK_FORMAT_D24_UNORM_S8_UINT;
+		case GL_DEPTH32F_STENCIL8:								return VK_FORMAT_D32_SFLOAT_S8_UINT;
+		case GL_DEPTH32F_STENCIL8_NV:							return VK_FORMAT_D32_SFLOAT_S8_UINT;
+
+		default:												return VK_FORMAT_UNDEFINED;
+	}
+}
+
+typedef enum VkFormatSizeFlagBits {
+	VK_FORMAT_SIZE_PACKED_BIT				= 0x00000001,
+	VK_FORMAT_SIZE_COMPRESSED_BIT			= 0x00000002,
+	VK_FORMAT_SIZE_PALETTIZED_BIT			= 0x00000004,
+	VK_FORMAT_SIZE_DEPTH_BIT				= 0x00000008,
+	VK_FORMAT_SIZE_STENCIL_BIT				= 0x00000010,
+} VkFormatSizeFlagBits;
+
+typedef VkFlags VkFormatSizeFlags;
+
+typedef struct VkFormatSize {
+	VkFormatSizeFlags	flags;
+	unsigned int		paletteSizeInBits;
+	unsigned int		blockSizeInBits;
+	unsigned int		blockWidth;			// in texels
+	unsigned int		blockHeight;		// in texels
+	unsigned int		blockDepth;			// in texels
+} VkFormatSize;
+
+static inline void vkGetFormatSize( const VkFormat format, VkFormatSize * pFormatSize )
+{
+	switch ( format )
+	{
+		case VK_FORMAT_R4G4_UNORM_PACK8:
+			pFormatSize->flags = VK_FORMAT_SIZE_PACKED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 1 * 8;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case VK_FORMAT_R4G4B4A4_UNORM_PACK16:
+		case VK_FORMAT_B4G4R4A4_UNORM_PACK16:
+		case VK_FORMAT_R5G6B5_UNORM_PACK16:
+		case VK_FORMAT_B5G6R5_UNORM_PACK16:
+		case VK_FORMAT_R5G5B5A1_UNORM_PACK16:
+		case VK_FORMAT_B5G5R5A1_UNORM_PACK16:
+		case VK_FORMAT_A1R5G5B5_UNORM_PACK16:
+			pFormatSize->flags = VK_FORMAT_SIZE_PACKED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 2 * 8;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case VK_FORMAT_R8_UNORM:
+		case VK_FORMAT_R8_SNORM:
+		case VK_FORMAT_R8_USCALED:
+		case VK_FORMAT_R8_SSCALED:
+		case VK_FORMAT_R8_UINT:
+		case VK_FORMAT_R8_SINT:
+		case VK_FORMAT_R8_SRGB:
+			pFormatSize->flags = 0;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 1 * 8;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case VK_FORMAT_R8G8_UNORM:
+		case VK_FORMAT_R8G8_SNORM:
+		case VK_FORMAT_R8G8_USCALED:
+		case VK_FORMAT_R8G8_SSCALED:
+		case VK_FORMAT_R8G8_UINT:
+		case VK_FORMAT_R8G8_SINT:
+		case VK_FORMAT_R8G8_SRGB:
+			pFormatSize->flags = 0;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 2 * 8;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case VK_FORMAT_R8G8B8_UNORM:
+		case VK_FORMAT_R8G8B8_SNORM:
+		case VK_FORMAT_R8G8B8_USCALED:
+		case VK_FORMAT_R8G8B8_SSCALED:
+		case VK_FORMAT_R8G8B8_UINT:
+		case VK_FORMAT_R8G8B8_SINT:
+		case VK_FORMAT_R8G8B8_SRGB:
+		case VK_FORMAT_B8G8R8_UNORM:
+		case VK_FORMAT_B8G8R8_SNORM:
+		case VK_FORMAT_B8G8R8_USCALED:
+		case VK_FORMAT_B8G8R8_SSCALED:
+		case VK_FORMAT_B8G8R8_UINT:
+		case VK_FORMAT_B8G8R8_SINT:
+		case VK_FORMAT_B8G8R8_SRGB:
+			pFormatSize->flags = 0;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 3 * 8;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case VK_FORMAT_R8G8B8A8_UNORM:
+		case VK_FORMAT_R8G8B8A8_SNORM:
+		case VK_FORMAT_R8G8B8A8_USCALED:
+		case VK_FORMAT_R8G8B8A8_SSCALED:
+		case VK_FORMAT_R8G8B8A8_UINT:
+		case VK_FORMAT_R8G8B8A8_SINT:
+		case VK_FORMAT_R8G8B8A8_SRGB:
+		case VK_FORMAT_B8G8R8A8_UNORM:
+		case VK_FORMAT_B8G8R8A8_SNORM:
+		case VK_FORMAT_B8G8R8A8_USCALED:
+		case VK_FORMAT_B8G8R8A8_SSCALED:
+		case VK_FORMAT_B8G8R8A8_UINT:
+		case VK_FORMAT_B8G8R8A8_SINT:
+		case VK_FORMAT_B8G8R8A8_SRGB:
+			pFormatSize->flags = 0;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 4 * 8;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case VK_FORMAT_A8B8G8R8_UNORM_PACK32:
+		case VK_FORMAT_A8B8G8R8_SNORM_PACK32:
+		case VK_FORMAT_A8B8G8R8_USCALED_PACK32:
+		case VK_FORMAT_A8B8G8R8_SSCALED_PACK32:
+		case VK_FORMAT_A8B8G8R8_UINT_PACK32:
+		case VK_FORMAT_A8B8G8R8_SINT_PACK32:
+		case VK_FORMAT_A8B8G8R8_SRGB_PACK32:
+			pFormatSize->flags = VK_FORMAT_SIZE_PACKED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 4 * 8;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case VK_FORMAT_A2R10G10B10_UNORM_PACK32:
+		case VK_FORMAT_A2R10G10B10_SNORM_PACK32:
+		case VK_FORMAT_A2R10G10B10_USCALED_PACK32:
+		case VK_FORMAT_A2R10G10B10_SSCALED_PACK32:
+		case VK_FORMAT_A2R10G10B10_UINT_PACK32:
+		case VK_FORMAT_A2R10G10B10_SINT_PACK32:
+		case VK_FORMAT_A2B10G10R10_UNORM_PACK32:
+		case VK_FORMAT_A2B10G10R10_SNORM_PACK32:
+		case VK_FORMAT_A2B10G10R10_USCALED_PACK32:
+		case VK_FORMAT_A2B10G10R10_SSCALED_PACK32:
+		case VK_FORMAT_A2B10G10R10_UINT_PACK32:
+		case VK_FORMAT_A2B10G10R10_SINT_PACK32:
+			pFormatSize->flags = VK_FORMAT_SIZE_PACKED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 4 * 8;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case VK_FORMAT_R16_UNORM:
+		case VK_FORMAT_R16_SNORM:
+		case VK_FORMAT_R16_USCALED:
+		case VK_FORMAT_R16_SSCALED:
+		case VK_FORMAT_R16_UINT:
+		case VK_FORMAT_R16_SINT:
+		case VK_FORMAT_R16_SFLOAT:
+			pFormatSize->flags = 0;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 2 * 8;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case VK_FORMAT_R16G16_UNORM:
+		case VK_FORMAT_R16G16_SNORM:
+		case VK_FORMAT_R16G16_USCALED:
+		case VK_FORMAT_R16G16_SSCALED:
+		case VK_FORMAT_R16G16_UINT:
+		case VK_FORMAT_R16G16_SINT:
+		case VK_FORMAT_R16G16_SFLOAT:
+			pFormatSize->flags = 0;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 4 * 8;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case VK_FORMAT_R16G16B16_UNORM:
+		case VK_FORMAT_R16G16B16_SNORM:
+		case VK_FORMAT_R16G16B16_USCALED:
+		case VK_FORMAT_R16G16B16_SSCALED:
+		case VK_FORMAT_R16G16B16_UINT:
+		case VK_FORMAT_R16G16B16_SINT:
+		case VK_FORMAT_R16G16B16_SFLOAT:
+			pFormatSize->flags = 0;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 6 * 8;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case VK_FORMAT_R16G16B16A16_UNORM:
+		case VK_FORMAT_R16G16B16A16_SNORM:
+		case VK_FORMAT_R16G16B16A16_USCALED:
+		case VK_FORMAT_R16G16B16A16_SSCALED:
+		case VK_FORMAT_R16G16B16A16_UINT:
+		case VK_FORMAT_R16G16B16A16_SINT:
+		case VK_FORMAT_R16G16B16A16_SFLOAT:
+			pFormatSize->flags = 0;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 8 * 8;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case VK_FORMAT_R32_UINT:
+		case VK_FORMAT_R32_SINT:
+		case VK_FORMAT_R32_SFLOAT:
+			pFormatSize->flags = 0;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 4 * 8;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case VK_FORMAT_R32G32_UINT:
+		case VK_FORMAT_R32G32_SINT:
+		case VK_FORMAT_R32G32_SFLOAT:
+			pFormatSize->flags = 0;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 8 * 8;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case VK_FORMAT_R32G32B32_UINT:
+		case VK_FORMAT_R32G32B32_SINT:
+		case VK_FORMAT_R32G32B32_SFLOAT:
+			pFormatSize->flags = 0;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 12 * 8;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case VK_FORMAT_R32G32B32A32_UINT:
+		case VK_FORMAT_R32G32B32A32_SINT:
+		case VK_FORMAT_R32G32B32A32_SFLOAT:
+			pFormatSize->flags = 0;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 16 * 8;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case VK_FORMAT_R64_UINT:
+		case VK_FORMAT_R64_SINT:
+		case VK_FORMAT_R64_SFLOAT:
+			pFormatSize->flags = 0;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 8 * 8;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case VK_FORMAT_R64G64_UINT:
+		case VK_FORMAT_R64G64_SINT:
+		case VK_FORMAT_R64G64_SFLOAT:
+			pFormatSize->flags = 0;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 16 * 8;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case VK_FORMAT_R64G64B64_UINT:
+		case VK_FORMAT_R64G64B64_SINT:
+		case VK_FORMAT_R64G64B64_SFLOAT:
+			pFormatSize->flags = 0;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 24 * 8;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case VK_FORMAT_R64G64B64A64_UINT:
+		case VK_FORMAT_R64G64B64A64_SINT:
+		case VK_FORMAT_R64G64B64A64_SFLOAT:
+			pFormatSize->flags = 0;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 32 * 8;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case VK_FORMAT_B10G11R11_UFLOAT_PACK32:
+		case VK_FORMAT_E5B9G9R9_UFLOAT_PACK32:
+			pFormatSize->flags = VK_FORMAT_SIZE_PACKED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 4 * 8;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case VK_FORMAT_D16_UNORM:
+			pFormatSize->flags = VK_FORMAT_SIZE_DEPTH_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 2 * 8;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case VK_FORMAT_X8_D24_UNORM_PACK32:
+			pFormatSize->flags = VK_FORMAT_SIZE_PACKED_BIT | VK_FORMAT_SIZE_DEPTH_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 4 * 8;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case VK_FORMAT_D32_SFLOAT:
+			pFormatSize->flags = VK_FORMAT_SIZE_DEPTH_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 4 * 8;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case VK_FORMAT_S8_UINT:
+			pFormatSize->flags = VK_FORMAT_SIZE_STENCIL_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 1 * 8;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case VK_FORMAT_D16_UNORM_S8_UINT:
+			pFormatSize->flags = VK_FORMAT_SIZE_DEPTH_BIT | VK_FORMAT_SIZE_STENCIL_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 3 * 8;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case VK_FORMAT_D24_UNORM_S8_UINT:
+			pFormatSize->flags = VK_FORMAT_SIZE_DEPTH_BIT | VK_FORMAT_SIZE_STENCIL_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 4 * 8;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case VK_FORMAT_D32_SFLOAT_S8_UINT:
+			pFormatSize->flags = VK_FORMAT_SIZE_DEPTH_BIT | VK_FORMAT_SIZE_STENCIL_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 8 * 8;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+		case VK_FORMAT_BC1_RGB_UNORM_BLOCK:
+		case VK_FORMAT_BC1_RGB_SRGB_BLOCK:
+		case VK_FORMAT_BC1_RGBA_UNORM_BLOCK:
+		case VK_FORMAT_BC1_RGBA_SRGB_BLOCK:
+		case VK_FORMAT_BC4_UNORM_BLOCK:
+		case VK_FORMAT_BC4_SNORM_BLOCK:
+			pFormatSize->flags = VK_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 8 * 8;
+			pFormatSize->blockWidth = 4;
+			pFormatSize->blockHeight = 4;
+			pFormatSize->blockDepth = 1;
+			break;
+		case VK_FORMAT_BC2_UNORM_BLOCK:
+		case VK_FORMAT_BC2_SRGB_BLOCK:
+		case VK_FORMAT_BC3_UNORM_BLOCK:
+		case VK_FORMAT_BC3_SRGB_BLOCK:
+		case VK_FORMAT_BC5_UNORM_BLOCK:
+		case VK_FORMAT_BC5_SNORM_BLOCK:
+		case VK_FORMAT_BC6H_UFLOAT_BLOCK:
+		case VK_FORMAT_BC6H_SFLOAT_BLOCK:
+		case VK_FORMAT_BC7_UNORM_BLOCK:
+		case VK_FORMAT_BC7_SRGB_BLOCK:
+			pFormatSize->flags = VK_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 16 * 8;
+			pFormatSize->blockWidth = 4;
+			pFormatSize->blockHeight = 4;
+			pFormatSize->blockDepth = 1;
+			break;
+		case VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK:
+		case VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK:
+		case VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK:
+		case VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK:
+			pFormatSize->flags = VK_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 8 * 8;
+			pFormatSize->blockWidth = 4;
+			pFormatSize->blockHeight = 4;
+			pFormatSize->blockDepth = 1;
+			break;
+		case VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK:
+		case VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK:
+		case VK_FORMAT_EAC_R11_UNORM_BLOCK:
+		case VK_FORMAT_EAC_R11_SNORM_BLOCK:
+		case VK_FORMAT_EAC_R11G11_UNORM_BLOCK:
+		case VK_FORMAT_EAC_R11G11_SNORM_BLOCK:
+			pFormatSize->flags = VK_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 16 * 8;
+			pFormatSize->blockWidth = 4;
+			pFormatSize->blockHeight = 4;
+			pFormatSize->blockDepth = 1;
+			break;
+		case VK_FORMAT_PVRTC1_4BPP_SRGB_BLOCK_IMG:
+		case VK_FORMAT_PVRTC1_4BPP_UNORM_BLOCK_IMG:
+		case VK_FORMAT_PVRTC2_4BPP_SRGB_BLOCK_IMG:
+		case VK_FORMAT_PVRTC2_4BPP_UNORM_BLOCK_IMG:
+			pFormatSize->flags = VK_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 8 * 8;
+			pFormatSize->blockWidth = 4;
+			pFormatSize->blockHeight = 4;
+			pFormatSize->blockDepth = 1;
+			break;
+		case VK_FORMAT_ASTC_4x4_UNORM_BLOCK:
+		case VK_FORMAT_ASTC_4x4_SRGB_BLOCK:
+			pFormatSize->flags = VK_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 16 * 8;
+			pFormatSize->blockWidth = 4;
+			pFormatSize->blockHeight = 4;
+			pFormatSize->blockDepth = 1;
+			break;
+		case VK_FORMAT_ASTC_5x4_UNORM_BLOCK:
+		case VK_FORMAT_ASTC_5x4_SRGB_BLOCK:
+			pFormatSize->flags = VK_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 16 * 8;
+			pFormatSize->blockWidth = 5;
+			pFormatSize->blockHeight = 4;
+			pFormatSize->blockDepth = 1;
+			break;
+		case VK_FORMAT_ASTC_5x5_UNORM_BLOCK:
+		case VK_FORMAT_ASTC_5x5_SRGB_BLOCK:
+			pFormatSize->flags = VK_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 16 * 8;
+			pFormatSize->blockWidth = 5;
+			pFormatSize->blockHeight = 5;
+			pFormatSize->blockDepth = 1;
+			break;
+		case VK_FORMAT_ASTC_6x5_UNORM_BLOCK:
+		case VK_FORMAT_ASTC_6x5_SRGB_BLOCK:
+			pFormatSize->flags = VK_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 16 * 8;
+			pFormatSize->blockWidth = 6;
+			pFormatSize->blockHeight = 5;
+			pFormatSize->blockDepth = 1;
+			break;
+		case VK_FORMAT_ASTC_6x6_UNORM_BLOCK:
+		case VK_FORMAT_ASTC_6x6_SRGB_BLOCK:
+			pFormatSize->flags = VK_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 16 * 8;
+			pFormatSize->blockWidth = 6;
+			pFormatSize->blockHeight = 6;
+			pFormatSize->blockDepth = 1;
+			break;
+		case VK_FORMAT_ASTC_8x5_UNORM_BLOCK:
+		case VK_FORMAT_ASTC_8x5_SRGB_BLOCK:
+			pFormatSize->flags = VK_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 16 * 8;
+			pFormatSize->blockWidth = 8;
+			pFormatSize->blockHeight = 5;
+			pFormatSize->blockDepth = 1;
+			break;
+		case VK_FORMAT_ASTC_8x6_UNORM_BLOCK:
+		case VK_FORMAT_ASTC_8x6_SRGB_BLOCK:
+			pFormatSize->flags = VK_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 16 * 8;
+			pFormatSize->blockWidth = 8;
+			pFormatSize->blockHeight = 6;
+			pFormatSize->blockDepth = 1;
+			break;
+		case VK_FORMAT_ASTC_8x8_UNORM_BLOCK:
+		case VK_FORMAT_ASTC_8x8_SRGB_BLOCK:
+			pFormatSize->flags = VK_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 16 * 8;
+			pFormatSize->blockWidth = 8;
+			pFormatSize->blockHeight = 8;
+			pFormatSize->blockDepth = 1;
+			break;
+		case VK_FORMAT_ASTC_10x5_UNORM_BLOCK:
+		case VK_FORMAT_ASTC_10x5_SRGB_BLOCK:
+			pFormatSize->flags = VK_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 16 * 8;
+			pFormatSize->blockWidth = 10;
+			pFormatSize->blockHeight = 5;
+			pFormatSize->blockDepth = 1;
+			break;
+		case VK_FORMAT_ASTC_10x6_UNORM_BLOCK:
+		case VK_FORMAT_ASTC_10x6_SRGB_BLOCK:
+			pFormatSize->flags = VK_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 16 * 8;
+			pFormatSize->blockWidth = 10;
+			pFormatSize->blockHeight = 6;
+			pFormatSize->blockDepth = 1;
+			break;
+		case VK_FORMAT_ASTC_10x8_UNORM_BLOCK:
+		case VK_FORMAT_ASTC_10x8_SRGB_BLOCK:
+			pFormatSize->flags = VK_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 16 * 8;
+			pFormatSize->blockWidth = 10;
+			pFormatSize->blockHeight = 8;
+			pFormatSize->blockDepth = 1;
+			break;
+		case VK_FORMAT_ASTC_10x10_UNORM_BLOCK:
+		case VK_FORMAT_ASTC_10x10_SRGB_BLOCK:
+			pFormatSize->flags = VK_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 16 * 8;
+			pFormatSize->blockWidth = 10;
+			pFormatSize->blockHeight = 10;
+			pFormatSize->blockDepth = 1;
+			break;
+		case VK_FORMAT_ASTC_12x10_UNORM_BLOCK:
+		case VK_FORMAT_ASTC_12x10_SRGB_BLOCK:
+			pFormatSize->flags = VK_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 16 * 8;
+			pFormatSize->blockWidth = 12;
+			pFormatSize->blockHeight = 10;
+			pFormatSize->blockDepth = 1;
+			break;
+		case VK_FORMAT_ASTC_12x12_UNORM_BLOCK:
+		case VK_FORMAT_ASTC_12x12_SRGB_BLOCK:
+			pFormatSize->flags = VK_FORMAT_SIZE_COMPRESSED_BIT;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 16 * 8;
+			pFormatSize->blockWidth = 12;
+			pFormatSize->blockHeight = 12;
+			pFormatSize->blockDepth = 1;
+			break;
+		default:
+			pFormatSize->flags = 0;
+			pFormatSize->paletteSizeInBits = 0;
+			pFormatSize->blockSizeInBits = 0 * 8;
+			pFormatSize->blockWidth = 1;
+			pFormatSize->blockHeight = 1;
+			pFormatSize->blockDepth = 1;
+			break;
+	}
+}
+
+#endif // !VK_FORMAT_H
diff --git a/external/Vulkan/external/ktx/lib/vk_funclist.inl b/external/Vulkan/external/ktx/lib/vk_funclist.inl
new file mode 100644
index 0000000000000000000000000000000000000000..80c23c97163489aed2bc0dd56d06cb6255110e79
--- /dev/null
+++ b/external/Vulkan/external/ktx/lib/vk_funclist.inl
@@ -0,0 +1,55 @@
+/* -*- tab-width: 4; -*- */
+/* vi: set sw=2 ts=4 expandtab textwidth=70: */
+
+/*
+ * ©2017 Mark Callow.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @internal
+ * @file vk_funclist.h
+ * @~English
+ *
+ * @brief List of Vulkan functions used by libktx.
+ */
+
+VK_FUNCTION(vkAllocateCommandBuffers)
+VK_FUNCTION(vkAllocateMemory)
+VK_FUNCTION(vkBeginCommandBuffer)
+VK_FUNCTION(vkBindBufferMemory)
+VK_FUNCTION(vkBindImageMemory)
+VK_FUNCTION(vkCmdBlitImage)
+VK_FUNCTION(vkCmdCopyBufferToImage)
+VK_FUNCTION(vkCmdPipelineBarrier)
+VK_FUNCTION(vkCreateBuffer)
+VK_FUNCTION(vkCreateFence)
+VK_FUNCTION(vkCreateImage)
+VK_FUNCTION(vkDestroyBuffer)
+VK_FUNCTION(vkDestroyFence)
+VK_FUNCTION(vkDestroyImage)
+VK_FUNCTION(vkEndCommandBuffer)
+VK_FUNCTION(vkFreeCommandBuffers)
+VK_FUNCTION(vkFreeMemory)
+VK_FUNCTION(vkGetBufferMemoryRequirements)
+VK_FUNCTION(vkGetImageMemoryRequirements)
+VK_FUNCTION(vkGetImageSubresourceLayout)
+VK_FUNCTION(vkGetPhysicalDeviceImageFormatProperties)
+VK_FUNCTION(vkGetPhysicalDeviceFormatProperties)
+VK_FUNCTION(vkGetPhysicalDeviceMemoryProperties)
+VK_FUNCTION(vkMapMemory)
+VK_FUNCTION(vkQueueSubmit)
+VK_FUNCTION(vkQueueWaitIdle)
+VK_FUNCTION(vkUnmapMemory)
+VK_FUNCTION(vkWaitForFences)
diff --git a/external/Vulkan/external/ktx/lib/vk_funcs.c b/external/Vulkan/external/ktx/lib/vk_funcs.c
new file mode 100644
index 0000000000000000000000000000000000000000..7d75053d741a70ab2d8e9b0a029c0018be55caff
--- /dev/null
+++ b/external/Vulkan/external/ktx/lib/vk_funcs.c
@@ -0,0 +1,164 @@
+/* -*- tab-width: 4; -*- */
+/* vi: set sw=2 ts=4 expandtab textwidth=70: */
+
+/*
+ * ©2017 Mark Callow.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @internal
+ * @file vk_funcs.c
+ * @~English
+ *
+ * @brief Retrieve Vulkan function pointers needed by libktx
+ */
+
+#if defined(KTX_USE_FUNCPTRS_FOR_VULKAN)
+
+#define UNIX 0
+#define MACOS 0
+#define WINDOWS 0
+
+#if defined(_WIN32)
+#undef WINDOWS
+#define WINDOWS 1
+#endif
+#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__bsdi__) || defined(__DragonFly__)
+#undef UNIX
+#define UNIX 1
+#endif
+#if defined(linux) || defined(__linux) || defined(__linux__)
+#undef UNIX
+#define UNIX 1
+#endif
+#if defined(__APPLE__) && defined(__x86_64__)
+#undef MACOS
+#define MACOS 1
+#endif
+
+#if (IOS + MACOS + UNIX + WINDOWS) > 1
+#error "Multiple OS\'s defined"
+#endif 
+
+#if WINDOWS
+#define WINDOWS_LEAN_AND_MEAN
+#include <windows.h>
+#else
+#include <dlfcn.h>
+#endif
+#include "ktx.h"
+#include "vk_funcs.h"
+
+#if WINDOWS
+HMODULE ktxVulkanLibary;
+#define LoadLibrary LoadLibrary
+#define LoadProcAddr GetProcAddress
+#elif MACOS || UNIX
+#define LoadLibrary dlopen
+#define LoadProcAddr dlsym
+void* ktxVulkanLibrary;
+#else
+#error "Don\'t know how to load symbols on this OS."
+#endif
+
+#if WINDOWS
+#define VULKANLIB "vulkan-1.dll"
+#elif MACOS
+#define VULKANLIB "vulkan.framework/vulkan"
+#elif UNIX
+#define VULKANLIB "libvulkan.so"
+#endif
+
+static PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr;
+
+/* Define pointers for functions libktx is using. */
+#define VK_FUNCTION(fun) PFN_##fun ktx_##fun;
+
+#include "vk_funclist.inl"
+
+#undef VK_FUNCTION
+
+#if 0
+// The Vulkan spec. recommends using vkGetInstanceProcAddr over dlsym
+// (or whatever). Doing so would require a backward incompatible
+// change to the libktx API to provide the VkInstance. We have no
+// choice but dlsym. We can't use vkGetDeviceProcAddr because libktx
+// also uses none-device-level functions.
+#define VK_FUNCTION(fun)                                                     \
+  if ( !(ktx_##fun = (PFN_##fun)vkGetInstanceProcAddr(instance, #fun )) ) {  \
+    fprintf(stderr, "Could not load Vulkan command: %s!\n", #fun);          \
+    return KTX_FALSE;                                             \
+  }
+#else
+#if defined(__GNUC__)
+// This strange casting is because dlsym returns a void* thus is not
+// compatible with ISO C which forbids conversion of object pointers
+// to function pointers. The cast masks the conversion from the
+// compiler thus no warning even though -pedantic is set. Since the
+// platform supports dlsym, conversion to function pointers must
+// work, despite the mandated ISO C warning.
+#define VK_FUNCTION(fun)                                                   \
+  if ( !(*(void **)(&ktx_##fun) = LoadProcAddr(ktxVulkanLibrary, #fun)) ) {\
+    fprintf(stderr, "Could not load Vulkan command: %s!\n", #fun);         \
+    return KTX_FALSE;                                                      \
+  }
+#else
+#define VK_FUNCTION(fun)                                                   \
+  if ( !(ktx_##fun = (PFN_##fun)LoadProcAddr(ktxVulkanLibrary, #fun)) ) {  \
+    fprintf(stderr, "Could not load Vulkan command: %s!\n", #fun);         \
+    return KTX_FALSE;                                                      \
+  }
+#endif
+#endif
+
+ktx_bool_t
+ktxVulkanLoadLibrary(void)
+{
+    if (ktxVulkanLibrary)
+        return KTX_TRUE;
+
+    ktxVulkanLibrary = LoadLibrary(VULKANLIB, RTLD_LAZY);
+    if (ktxVulkanLibrary == NULL) {
+        fprintf(stderr, "Could not load Vulkan library.\n");
+        return(KTX_FALSE);
+    }
+
+#if 0
+    vkGetInstanceProcAddr =
+            (PFN_vkGetInstanceProcAddr)LoadProcAddr(ktxVulkanLibrary,
+                                                  "vkGetInstanceProcAddr");
+    if (!vkGetInstanceProcAddr) {
+       fprintf(stderr, "Could not load Vulkan command: %s!\n",
+               "vkGetInstanceProcAddr");
+       return(KTX_FALSE);
+    }
+#endif
+
+#include "vk_funclist.inl"
+
+    return KTX_TRUE;
+}
+
+#undef VK_FUNCTION
+
+#else
+
+extern
+#if defined(__GNUC__)
+__attribute__((unused))
+#endif
+int keepISOCompilersHappy;
+
+#endif /* KTX_USE_FUNCPTRS_FOR_VULKAN */
diff --git a/external/Vulkan/external/ktx/lib/vk_funcs.h b/external/Vulkan/external/ktx/lib/vk_funcs.h
new file mode 100644
index 0000000000000000000000000000000000000000..07ea0c2b455e20958527fd3c03411bd15337cfa3
--- /dev/null
+++ b/external/Vulkan/external/ktx/lib/vk_funcs.h
@@ -0,0 +1,97 @@
+/* -*- tab-width: 4; -*- */
+/* vi: set sw=2 ts=4 expandtab textwidth=70: */
+
+/*
+ * ©2017 Mark Callow.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @internal
+ * @file vk_funcs.h
+ * @~English
+ *
+ * @brief Declare pointers for Vulkan functions.
+ *
+ * Dynamically retrieving pointers avoids apps having to make sure a
+ * Vulkan library is availablei when using a shared libktx, even if
+ * not using libktx's Vulkan loader.
+ */
+
+#ifndef _VK_FUNCS_H_
+#define _VK_FUNCS_H_
+
+#if defined(KTX_USE_FUNCPTRS_FOR_VULKAN)
+#define VK_NO_PROTOTYPES
+#endif
+
+#include <vulkan/vulkan.h>
+#include "ktx.h"
+
+#if defined(KTX_USE_FUNCPTRS_FOR_VULKAN)
+
+#if defined(_WIN32)
+extern HMODULE ktxVulkanLibary;
+#else
+extern void* ktxVulkanLibrary;
+#endif
+
+extern ktx_bool_t ktxVulkanLoadLibrary(void);
+
+/* Declare pointers for functions libktx is using. */
+#define VK_FUNCTION(fun) extern PFN_##fun ktx_##fun;
+
+#include "vk_funclist.inl"
+
+#undef VK_FUNCTION
+
+/*
+ * Define prefixed names to prevent collisions with other libraries or apps
+ * finding our pointers when searching the module for function addresses.
+ */
+#define vkAllocateCommandBuffers ktx_vkAllocateCommandBuffers
+#define vkAllocateMemory ktx_vkAllocateMemory
+#define vkBeginCommandBuffer ktx_vkBeginCommandBuffer
+#define vkBindBufferMemory ktx_vkBindBufferMemory
+#define vkBindImageMemory ktx_vkBindImageMemory
+#define vkCmdBlitImage ktx_vkCmdBlitImage
+#define vkCmdCopyBufferToImage ktx_vkCmdCopyBufferToImage
+#define vkCmdPipelineBarrier ktx_vkCmdPipelineBarrier
+#define vkCreateBuffer ktx_vkCreateBuffer
+#define vkCreateFence ktx_vkCreateFence
+#define vkCreateImage ktx_vkCreateImage
+#define vkDestroyBuffer ktx_vkDestroyBuffer
+#define vkDestroyFence ktx_vkDestroyFence
+#define vkDestroyImage ktx_vkDestroyImage
+#define vkEndCommandBuffer ktx_vkEndCommandBuffer
+#define vkFreeCommandBuffers ktx_vkFreeCommandBuffers
+#define vkFreeMemory ktx_vkFreeMemory
+#define vkGetBufferMemoryRequirements ktx_vkGetBufferMemoryRequirements
+#define vkGetImageMemoryRequirements ktx_vkGetImageMemoryRequirements
+#define vkGetImageSubresourceLayout ktx_vkGetImageSubresourceLayout
+#define vkGetPhysicalDeviceImageFormatProperties ktx_vkGetPhysicalDeviceImageFormatProperties
+#define vkGetPhysicalDeviceFormatProperties ktx_vkGetPhysicalDeviceFormatProperties
+#define vkGetPhysicalDeviceMemoryProperties ktx_vkGetPhysicalDeviceMemoryProperties
+#define vkMapMemory ktx_vkMapMemory
+#define vkQueueSubmit ktx_vkQueueSubmit
+#define vkQueueWaitIdle ktx_vkQueueWaitIdle
+#define vkUnmapMemory ktx_vkUnmapMemory
+#define vkWaitForFences ktx_vkWaitForFences
+
+#undef VK_FUNCTION
+
+#endif /* KTX_USE_FUNCPTRS_FOR_VULKAN */
+
+#endif /* _VK_FUNCS_H_ */
+
diff --git a/external/Vulkan/external/ktx/lib/vkloader.c b/external/Vulkan/external/ktx/lib/vkloader.c
new file mode 100644
index 0000000000000000000000000000000000000000..9916ce2b5c1ad6cbb64fc19242d84f65a83a0e93
--- /dev/null
+++ b/external/Vulkan/external/ktx/lib/vkloader.c
@@ -0,0 +1,1444 @@
+/* -*- tab-width: 4; -*- */
+/* vi: set sw=2 ts=4 expandtab: */
+
+/*
+ * ©2018 Mark Callow.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ 
+/**
+ * @internal
+ * @file
+ * @~English
+ *
+ * @brief Functions for instantiating Vulkan textures from KTX files.
+ *
+ * @author Mark Callow, Edgewise Consulting
+ */
+
+#ifdef _WIN32
+#define _CRT_SECURE_NO_WARNINGS
+#endif
+
+#include <assert.h>
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+
+#if defined(KTX_USE_FUNCPTRS_FOR_VULKAN)
+#include "vk_funcs.h"   // Must be included before ktxvulkan.h.
+#endif
+#include "ktxvulkan.h"
+#include "ktxint.h"
+#include "vk_format.h"
+
+// Macro to check and display Vulkan return results.
+// Use when the only possible errors are caused by invalid usage by this loader.
+#if defined(_DEBUG)
+#define VK_CHECK_RESULT(f)                                                  \
+{                                                                           \
+    VkResult res = (f);                                                     \
+    if (res != VK_SUCCESS)                                                  \
+    {                                                                       \
+        /* XXX Find an errorString function. */                             \
+        fprintf(stderr, "Fatal error in ktxLoadVkTexture*: "                \
+                "VkResult is \"%d\" in %s at line %d\n",                    \
+                res, __FILE__, __LINE__);                                   \
+        assert(res == VK_SUCCESS);                                          \
+    }                                                                       \
+}
+#else
+#define VK_CHECK_RESULT(f) ((void)f)
+#endif
+
+#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))
+
+#define DEFAULT_FENCE_TIMEOUT 100000000000
+#define VK_FLAGS_NONE 0
+
+static void
+setImageLayout(
+    VkCommandBuffer cmdBuffer,
+    VkImage image,
+    VkImageLayout oldLayout,
+    VkImageLayout newLayout,
+    VkImageSubresourceRange subresourceRange);
+
+static void
+generateMipmaps(ktxVulkanTexture* vkTexture, ktxVulkanDeviceInfo* vdi,
+                VkFilter filter, VkImageLayout initialLayout);
+
+/**
+ * @defgroup ktx_vkloader Vulkan Texture Image Loader
+ * @brief Create texture images on a Vulkan device.
+ * @{
+ */
+
+/**
+ * @example vkload.cpp
+ * This shows how to create and load a Vulkan image using the Vulkan texture
+ * image loading functions.
+ */
+
+/**
+ * @memberof ktxVulkanDeviceInfo
+ * @~English
+ * @brief Create a ktxVulkanDeviceInfo object.
+ * 
+ * Allocates CPU memory for a ktxVulkanDeviceInfo object then calls
+ * ktxVulkanDeviceInfo_construct(). See it for documentation of the
+ * parameters.
+ *
+ * @return a pointer to the constructed ktxVulkanDeviceInfo.
+ *
+ * @sa ktxVulkanDeviceInfo_construct(), ktxVulkanDeviceInfo_destroy()
+ */
+ktxVulkanDeviceInfo*
+ktxVulkanDeviceInfo_Create(VkPhysicalDevice physicalDevice, VkDevice device,
+                           VkQueue queue, VkCommandPool cmdPool,
+                           const VkAllocationCallbacks* pAllocator)
+{
+    ktxVulkanDeviceInfo* newvdi;
+    newvdi = (ktxVulkanDeviceInfo*)malloc(sizeof(ktxVulkanDeviceInfo));
+    if (newvdi != NULL) {
+        if (ktxVulkanDeviceInfo_Construct(newvdi, physicalDevice, device,
+                                    queue, cmdPool, pAllocator) != KTX_SUCCESS)
+        {
+            free(newvdi);
+            newvdi = 0;
+        }
+    }
+    return newvdi;
+}
+
+/**
+ * @memberof ktxVulkanDeviceInfo
+ * @~English
+ * @brief Construct a ktxVulkanDeviceInfo object.
+ *
+ * Records the device information, allocates a command buffer that will be
+ * used to transfer image data to the Vulkan device and retrieves the physical
+ * device memory properties for ease of use when allocating device memory for
+ * the images.
+ *
+ * Pass a valid ktxVulkanDeviceInfo* to any Vulkan KTX image loading
+ * function to provide it with the information.
+ *
+ * @param  This            pointer to the ktxVulkanDeviceInfo object to
+ *                        initialize.
+ * @param  physicalDevice handle of the Vulkan physical device.
+ * @param  device         handle of the Vulkan logical device.
+ * @param  queue          handle of the Vulkan queue.
+ * @param  cmdPool        handle of the Vulkan command pool.
+ * @param  pAllocator     pointer to the allocator to use for the image
+ *                        memory. If NULL, the default allocator will be used.
+ *
+ * @returns KTX_SUCCESS on success, KTX_OUT_OF_MEMORY if a command buffer could
+ *          not be allocated.
+ *
+ * @sa ktxVulkanDeviceInfo_destruct()
+ */
+KTX_error_code
+ktxVulkanDeviceInfo_Construct(ktxVulkanDeviceInfo* This,
+                              VkPhysicalDevice physicalDevice, VkDevice device,
+                              VkQueue queue, VkCommandPool cmdPool,
+                              const VkAllocationCallbacks* pAllocator)
+{
+    VkCommandBufferAllocateInfo cmdBufInfo = {
+        .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO
+    };
+    VkResult result;
+
+#if defined(KTX_USE_FUNCPTRS_FOR_VULKAN)
+    // Delay loading not supported so must do it ourselves.
+    if (!ktxVulkanLibrary) {
+        if (!ktxVulkanLoadLibrary())
+            // Normal use is for this constructor to be called by an application
+            // that has completed Vulkan initialization. In that case the only
+            // cause for failure would be an incompatible in the version of libvulkan
+            // that is loaded. The only other cause would be an application calling
+            // Vulkan functions without having initialized Vulkan.
+            //
+            // In these cases, an abort along with the messages sent to stderr by
+            // ktxVulkanLoadLibrary is sufficient as released applications should
+            // never suffer these.
+            abort();
+    }
+#endif
+
+    This->physicalDevice = physicalDevice;
+    This->device = device;
+    This->queue = queue;
+    This->cmdPool = cmdPool;
+    This->pAllocator = pAllocator;
+    vkGetPhysicalDeviceMemoryProperties(physicalDevice,
+                                        &This->deviceMemoryProperties);
+
+    // Use a separate command buffer for texture loading. Needed for
+    // submitting image barriers and converting tilings.
+    cmdBufInfo.commandPool = cmdPool;
+    cmdBufInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
+    cmdBufInfo.commandBufferCount = 1;
+    result = vkAllocateCommandBuffers(device, &cmdBufInfo, &This->cmdBuffer);
+    if (result != VK_SUCCESS) {
+        return KTX_OUT_OF_MEMORY; // XXX Consider an equivalent to pGlError
+    }
+    return KTX_SUCCESS;
+}
+
+/**
+ * @memberof ktxVulkanDeviceInfo
+ * @~English
+ * @brief Destruct a ktxVulkanDeviceInfo object.
+ *
+ * Frees the command buffer.
+ *
+ * @param This pointer to the ktxVulkanDeviceInfo to destruct.
+ */
+void
+ktxVulkanDeviceInfo_Destruct(ktxVulkanDeviceInfo* This)
+{
+    vkFreeCommandBuffers(This->device, This->cmdPool, 1,
+                         &This->cmdBuffer);
+}
+
+/**
+ * @memberof ktxVulkanDeviceInfo
+ * @~English
+ * @brief Destroy a ktxVulkanDeviceInfo object.
+ *
+ * Calls ktxVulkanDeviceInfo_destruct() then frees the ktxVulkanDeviceInfo.
+ *
+ * @param This pointer to the ktxVulkanDeviceInfo to destroy.
+ */
+void
+ktxVulkanDeviceInfo_Destroy(ktxVulkanDeviceInfo* This)
+{
+    assert(This != NULL);
+    ktxVulkanDeviceInfo_Destruct(This);
+    free(This);
+}
+
+/* Get appropriate memory type index for a memory allocation. */
+static uint32_t
+ktxVulkanDeviceInfo_getMemoryType(ktxVulkanDeviceInfo* This,
+                                  uint32_t typeBits, VkFlags properties)
+{
+    for (uint32_t i = 0; i < 32; i++)
+    {
+        if ((typeBits & 1) == 1)
+        {
+            if ((This->deviceMemoryProperties.memoryTypes[i].propertyFlags & properties) == properties)
+            {
+                return i;
+            }
+        }
+        typeBits >>= 1;
+    }
+
+    // XXX : throw error
+    return 0;
+}
+
+//======================================================================
+//  ReadImages callbacks
+//======================================================================
+
+typedef struct user_cbdata_optimal {
+    VkBufferImageCopy* region; // Specify destination region in final image.
+    VkDeviceSize offset;       // Offset of current level in staging buffer
+    ktx_uint32_t numFaces;
+    ktx_uint32_t numLayers;
+    // The following are used only by optimalTilingPadCallback
+    ktx_uint8_t* dest;         // Pointer to mapped staging buffer.
+    ktx_uint32_t elementSize;
+    ktx_uint32_t numDimensions;
+#if defined(_DEBUG)
+    VkBufferImageCopy* regionsArrayEnd;
+#endif
+} user_cbdata_optimal;
+
+/**
+ * @internal
+ * @~English
+ * @brief Callback for optimally tiled textures with no source row padding.
+ *
+ * Images must be preloaded into the staging buffer. Each iteration, i.e.
+ * the value of @p faceLodSize must be for a complete mip level, regardless of
+ * texture type. This should be used only with @c ktx_Texture_IterateLevels.
+ *
+ * Sets up a region to copy the data from the staging buffer to the final
+ * image.
+ *
+ * @note @p pixels is not used.
+ *
+ * @copydetails PFNKTXITERCB
+ */
+static KTX_error_code KTXAPIENTRY
+optimalTilingCallback(int miplevel, int face,
+                      int width, int height, int depth,
+                      ktx_uint32_t faceLodSize,
+                      void* pixels, void* userdata)
+{
+    user_cbdata_optimal* ud = (user_cbdata_optimal*)userdata;
+
+    // Set up copy to destination region in final image
+    assert(ud->region < ud->regionsArrayEnd);
+    ud->region->bufferOffset = ud->offset;
+    ud->offset += faceLodSize;
+    // These 2 are expressed in texels.
+    ud->region->bufferRowLength = 0;
+    ud->region->bufferImageHeight = 0;
+    ud->region->imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+    ud->region->imageSubresource.mipLevel = miplevel;
+    ud->region->imageSubresource.baseArrayLayer = face;
+    ud->region->imageSubresource.layerCount = ud->numLayers * ud->numFaces;
+    ud->region->imageOffset.x = 0;
+    ud->region->imageOffset.y = 0;
+    ud->region->imageOffset.z = 0;
+    ud->region->imageExtent.width = width;
+    ud->region->imageExtent.height = height;
+    ud->region->imageExtent.depth = depth;
+
+    ud->region += 1;
+
+    return KTX_SUCCESS;
+}
+
+/**
+ * @internal
+ * @~English
+ * @brief Callback for optimally tiled textures with possible source row
+ *        padding.
+ *
+ * Copies data to the staging buffer removing row padding, if necessary.
+ * Increments the offset for destination of the next copy increasing it to an
+ * appropriate common multiple of the element size and 4 to comply with Vulkan
+ * valid usage. Finally sets up a region to copy the face/lod from the staging
+ * buffer to the final image.
+ *
+ * This longer method is needed because row padding is different between
+ * KTX (pad to 4) and Vulkan (none). Also region->bufferOffset, i.e. the start
+ * of each image, has to be a multiple of 4 and also a multiple of the
+ * element size.
+ *
+ * This should be used with @c ktx_Texture_IterateFaceLevels or
+ * @c ktx_Texture_IterateLoadFaceLevels. Face-level iteration has been
+ * selected to minimize the buffering needed between reading the file and
+ * copying the data into the staging buffer. Obviously when
+ * @c ktx_Texture_IterateFaceLevels is being used, this is a moot point.
+*
+ * @copydetails PFNKTXITERCB
+ */
+KTX_error_code KTXAPIENTRY
+optimalTilingPadCallback(int miplevel, int face,
+                         int width, int height, int depth,
+                         ktx_uint32_t faceLodSize,
+                         void* pixels, void* userdata)
+{
+    user_cbdata_optimal* ud = (user_cbdata_optimal*)userdata;
+    ktx_uint32_t rowPitch = width * ud->elementSize;
+
+    // Set bufferOffset in destination region in final image
+    assert(ud->region < ud->regionsArrayEnd);
+    ud->region->bufferOffset = ud->offset;
+
+    // Copy data into staging buffer
+    if (_KTX_PAD_UNPACK_ALIGN_LEN(rowPitch) == 0) {
+        // No padding. Can copy in bulk.
+        memcpy(ud->dest + ud->offset, pixels, faceLodSize);
+        ud->offset += faceLodSize;
+    } else {
+        // Must remove padding. Copy a row at a time.
+		ktx_uint32_t image, imageIterations;
+		ktx_int32_t row;
+        ktx_uint32_t rowPitch, paddedRowPitch;
+
+        if (ud->numDimensions == 3)
+            imageIterations = depth;
+        else if (ud->numLayers > 1)
+            imageIterations = ud->numLayers * ud->numFaces;
+        else
+            imageIterations = 1;
+        rowPitch = paddedRowPitch = width * ud->elementSize;
+        paddedRowPitch = _KTX_PAD_UNPACK_ALIGN(paddedRowPitch);
+        for (image = 0; image < imageIterations; image++) {
+            for (row = 0; row < height; row++) {
+                memcpy(ud->dest + ud->offset, pixels, rowPitch);
+                ud->offset += rowPitch;
+                pixels = (ktx_uint8_t*)pixels + paddedRowPitch;
+            }
+        }
+    }
+
+    // Round to needed multiples for next region, if necessary.
+    if (ud->offset % ud->elementSize != 0 || ud->offset % 4 != 0) {
+        // Only elementSizes of 1,2 and 3 will bring us here.
+        assert(ud->elementSize < 4 && ud->elementSize > 0);
+        ktx_uint32_t lcm = ud->elementSize == 3 ? 12 : 4;
+        // Can't use _KTX_PADN shortcut because 12 is not power of 2.
+        ud->offset = (ktx_uint32_t)(lcm * ceil((float)ud->offset / lcm));
+    }
+    // These 2 are expressed in texels; not suitable for dealing with padding.
+    ud->region->bufferRowLength = 0;
+    ud->region->bufferImageHeight = 0;
+    ud->region->imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+    ud->region->imageSubresource.mipLevel = miplevel;
+    ud->region->imageSubresource.baseArrayLayer = face;
+    ud->region->imageSubresource.layerCount = ud->numLayers * ud->numFaces;
+    ud->region->imageOffset.x = 0;
+    ud->region->imageOffset.y = 0;
+    ud->region->imageOffset.z = 0;
+    ud->region->imageExtent.width = width;
+    ud->region->imageExtent.height = height;
+    ud->region->imageExtent.depth = depth;
+
+    ud->region += 1;
+
+    return KTX_SUCCESS;
+}
+
+typedef struct user_cbdata_linear {
+    VkImage destImage;
+    VkDevice device;
+    uint8_t* dest;   // Pointer to mapped Image memory
+    ktxTexture* texture;
+} user_cbdata_linear;
+
+/**
+ * @internal
+ * @~English
+ * @brief Callback for linear tiled textures with no source row padding.
+ *
+ * Copy the image data into the mapped Vulkan image.
+ */
+KTX_error_code KTXAPIENTRY
+linearTilingCallback(int miplevel, int face,
+                      int width, int height, int depth,
+                      ktx_uint32_t faceLodSize,
+                      void* pixels, void* userdata)
+{
+    user_cbdata_linear* ud = (user_cbdata_linear*)userdata;
+    VkSubresourceLayout subResLayout;
+    VkImageSubresource subRes = {
+      .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+      .mipLevel = miplevel,
+      .arrayLayer = face
+    };
+
+    // Get sub resources layout. Includes row pitch, size,
+    // offsets, etc.
+    vkGetImageSubresourceLayout(ud->device, ud->destImage, &subRes,
+                                &subResLayout);
+    // Copies all images of the miplevel (for array & 3d) or a single face.
+    memcpy(ud->dest + subResLayout.offset, pixels, faceLodSize);
+    return KTX_SUCCESS;
+}
+
+/**
+ * @internal
+ * @~English
+ * @brief Callback for linear tiled textures with possible source row
+ *        padding.
+ *
+ * Need to use this long method as row padding is different
+ * between KTX (pad to 4) and Vulkan (none).
+ *
+ * In theory this should work for the no-padding case too but it is much
+ * clearer and a bit faster to use the simple callback above. It also avoids
+ * potential Vulkan implementation bugs.
+ *
+ * I have seen weird subResLayout results with a BC2_UNORM texture in the only
+ * real Vulkan implementation I have available (Mesa). The reported row & image
+ * strides appears to be for an R8G8B8A8_UNORM of the same texel size.
+ */
+KTX_error_code KTXAPIENTRY
+linearTilingPadCallback(int miplevel, int face,
+                      int width, int height, int depth,
+                      ktx_uint32_t faceLodSize,
+                      void* pixels, void* userdata)
+{
+    user_cbdata_linear* ud = (user_cbdata_linear*)userdata;
+    VkSubresourceLayout subResLayout;
+    VkImageSubresource subRes = {
+      .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+      .mipLevel = miplevel,
+      .arrayLayer = face
+    };
+    VkDeviceSize offset;
+    ktx_size_t   imageSize = 0;
+    VkDeviceSize imagePitch = 0;
+    ktx_uint32_t srcRowPitch;
+    ktx_uint32_t rowIterations;
+    ktx_uint32_t imageIterations;
+    ktx_uint32_t row, image;
+    ktx_uint8_t* pSrc;
+    ktx_size_t   copySize;
+
+    // Get sub resources layout. Includes row pitch, size,
+    // offsets, etc.
+    vkGetImageSubresourceLayout(ud->device, ud->destImage, &subRes,
+                                &subResLayout);
+
+    srcRowPitch = ktxTexture_GetRowPitch(ud->texture, miplevel);
+
+    if (subResLayout.rowPitch != srcRowPitch)
+        rowIterations = height;
+    else
+        rowIterations = 1;
+
+    // Arrays, including cube map arrays, or 3D textures
+    // Note from the Vulkan spec:
+    //  *  arrayPitch is undefined for images that were not
+    //     created as arrays.
+    //  *  depthPitch is defined only for 3D images.
+    if (ud->texture->numLayers > 1 || ud->texture->numDimensions == 3) {
+        imageSize = ktxTexture_GetImageSize(ud->texture, miplevel);
+        if (ud->texture->numLayers > 1) {
+            imagePitch = subResLayout.arrayPitch;
+            if (imagePitch != imageSize)
+                imageIterations
+                        = ud->texture->numLayers * ud->texture->numFaces;
+        } else {
+            imagePitch = subResLayout.depthPitch;
+            if (imagePitch != imageSize)
+                imageIterations = depth;
+        }
+        assert(imageSize <= imagePitch);
+    } else
+        imageIterations = 1;
+
+    if (rowIterations > 1) {
+        // Copy the minimum of srcRowPitch, the GL_UNPACK_ALIGNMENT padded size,
+        // and subResLayout.rowPitch.
+        if (subResLayout.rowPitch < srcRowPitch)
+            copySize = subResLayout.rowPitch;
+        else
+            copySize = srcRowPitch;
+    } else if (imageIterations > 1)
+        copySize = faceLodSize / imageIterations;
+    else
+        copySize = faceLodSize;
+
+    offset = subResLayout.offset;
+    // Copy image data to destImage via its mapped memory.
+    for (image = 0; image < imageIterations; image++) {
+        pSrc = (ktx_uint8_t*)pixels + imageSize * image;
+        for (row = 0; row < rowIterations; row++) {
+            memcpy(ud->dest + offset, pSrc, copySize);
+            offset += subResLayout.rowPitch;
+            pSrc += srcRowPitch;
+          }
+        offset += imagePitch;
+    }
+    return KTX_SUCCESS;
+}
+
+/**
+ * @memberof ktxTexture
+ * @~English
+ * @brief Create a Vulkan image object from a ktxTexture object.
+ *
+ * Creates a VkImage with @c VkFormat etc. matching the KTX data and uploads
+ * the images. Also creates a VkImageView object for accessing the image.
+ * Mipmaps will be generated if the @c ktxTexture's @c generateMipmaps
+ * flag is set. Returns the handles of the created objects and information
+ * about the texture in the @c ktxVulkanTexture pointed at by @p vkTexture.
+ *
+ * @p usageFlags and thus acceptable usage of the created image may be
+ * augmented as follows:
+ * - with @c VK_IMAGE_USAGE_TRANSFER_DST_BIT if @p tiling is
+ *   @c VK_IMAGE_TILING_OPTIMAL
+ * - with <code>VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT</code>
+ *   if @c generateMipmaps is set in the @c ktxTexture.
+ *
+ * Most Vulkan implementations support VK_IMAGE_TILING_LINEAR only for a very
+ * limited number of formats and features. Generally VK_IMAGE_TILING_OPTIMAL is
+ * preferred. The latter requires a staging buffer so will use more memory
+ * during loading.
+ *
+ * @param[in] This          pointer to the ktxTexture from which to upload.
+ * @param [in] vdi          pointer to a ktxVulkanDeviceInfo structure providing
+ *                          information about the Vulkan device onto which to
+ *                          load the texture.
+ * @param [in,out] vkTexture pointer to a ktxVulkanTexture structure into which
+ *                           the function writes information about the created
+ *                           VkImage.
+ * @param [in] tiling       type of tiling to use in the destination image
+ *                          on the Vulkan device.
+ * @param [in] usageFlags   a set of VkImageUsageFlags bits indicating the
+ *                          intended usage of the destination image.
+ * @param [in] finalLayout  a VkImageLayout value indicating the desired
+ *                          final layout of the created image.
+ *
+ * @return  KTX_SUCCESS on success, other KTX_* enum values on error.
+ *
+ * @exception KTX_INVALID_VALUE @p This, @p vdi or @p vkTexture is @c NULL.
+ * @exception KTX_INVALID_OPERATION The ktxTexture contains neither images nor
+ *                                  an active stream from which to read them.
+ * @exception KTX_INVALID_OPERATION The combination of the ktxTexture's format,
+ *                                  @p tiling and @p usageFlags is not supported
+ *                                  by the physical device.
+ * @exception KTX_INVALID_OPERATION Requested mipmap generation is not supported
+ *                                  by the physical device for the combination
+ *                                  of the ktxTexture's format and @p tiling.
+ * @exception KTX_OUT_OF_MEMORY Sufficient memory could not be allocated
+ *                              on either the CPU or the Vulkan device.
+ */
+KTX_error_code
+ktxTexture_VkUploadEx(ktxTexture* This, ktxVulkanDeviceInfo* vdi,
+                      ktxVulkanTexture* vkTexture,
+                      VkImageTiling tiling,
+                      VkImageUsageFlags usageFlags,
+                      VkImageLayout finalLayout)
+{
+    KTX_error_code           kResult;
+    VkFilter                 blitFilter;
+    VkFormat                 vkFormat;
+    VkImageType              imageType;
+    VkImageViewType          viewType;
+    VkImageCreateFlags       createFlags = 0;
+    VkImageFormatProperties  imageFormatProperties;
+    VkResult                 vResult;
+    VkCommandBufferBeginInfo cmdBufBeginInfo = {
+        .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
+        .pNext = NULL
+    };
+    VkImageCreateInfo        imageCreateInfo = {
+         .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
+         .pNext = NULL
+    };
+    VkMemoryAllocateInfo     memAllocInfo = {
+        .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
+        .pNext = NULL,
+        .allocationSize = 0,
+        .memoryTypeIndex = 0
+    };
+    VkMemoryRequirements     memReqs;
+    ktx_uint32_t             numImageLayers, numImageLevels;
+    ktx_uint32_t elementSize = ktxTexture_GetElementSize(This);
+    ktx_bool_t               canUseFasterPath;
+
+    if (!vdi || !This || !vkTexture) {
+        return KTX_INVALID_VALUE;
+    }
+
+    if (!This->pData && !ktxTexture_isActiveStream(This)) {
+        /* Nothing to upload. */
+        return KTX_INVALID_OPERATION;
+    }
+
+    /* _ktxCheckHeader should have caught this. */
+    assert(This->numFaces == 6 ? This->numDimensions == 2 : VK_TRUE);
+
+    numImageLayers = This->numLayers;
+    if (This->isCubemap) {
+        numImageLayers *= 6;
+        createFlags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;
+    }
+
+    switch (This->numDimensions) {
+      case 1:
+        imageType = VK_IMAGE_TYPE_1D;
+        viewType = This->isArray ?
+                        VK_IMAGE_VIEW_TYPE_1D_ARRAY : VK_IMAGE_VIEW_TYPE_1D;
+        break;
+      case 2:
+        imageType = VK_IMAGE_TYPE_2D;
+        if (This->isCubemap)
+            viewType = This->isArray ?
+                        VK_IMAGE_VIEW_TYPE_CUBE_ARRAY : VK_IMAGE_VIEW_TYPE_CUBE;
+        else
+            viewType = This->isArray ?
+                        VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D;
+        break;
+      case 3:
+        imageType = VK_IMAGE_TYPE_3D;
+        /* 3D array textures not supported in Vulkan. Attempts to create or
+         * load them should have been trapped long before this.
+         */
+        assert(!This->isArray);
+        viewType = VK_IMAGE_VIEW_TYPE_3D;
+        break;
+    }
+
+    vkFormat = vkGetFormatFromOpenGLInternalFormat(This->glInternalformat);
+    if (vkFormat == VK_FORMAT_UNDEFINED)
+        vkFormat = vkGetFormatFromOpenGLFormat(This->glFormat, This->glType);
+    if (vkFormat == VK_FORMAT_UNDEFINED) {
+        return KTX_INVALID_OPERATION;
+    }
+
+    /* Get device properties for the requested image format */
+    if (tiling == VK_IMAGE_TILING_OPTIMAL) {
+        // Ensure we can copy from staging buffer to image.
+        usageFlags |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
+    }
+    if (This->generateMipmaps) {
+        // Ensure we can blit between levels.
+        usageFlags |= (VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT);
+    }
+    vResult = vkGetPhysicalDeviceImageFormatProperties(vdi->physicalDevice,
+                                                      vkFormat,
+                                                      imageType,
+                                                      tiling,
+                                                      usageFlags,
+                                                      createFlags,
+                                                      &imageFormatProperties);
+    if (vResult == VK_ERROR_FORMAT_NOT_SUPPORTED) {
+        return KTX_INVALID_OPERATION;
+    }
+
+    if (This->generateMipmaps) {
+        uint32_t max_dim;
+        VkFormatProperties    formatProperties;
+        VkFormatFeatureFlags  formatFeatureFlags;
+        VkFormatFeatureFlags  neededFeatures
+            = VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT;
+        vkGetPhysicalDeviceFormatProperties(vdi->physicalDevice,
+                                            vkFormat,
+                                            &formatProperties);
+        assert(vResult == VK_SUCCESS);
+        if (tiling == VK_IMAGE_TILING_OPTIMAL)
+            formatFeatureFlags = formatProperties.optimalTilingFeatures;
+        else
+            formatFeatureFlags = formatProperties.linearTilingFeatures;
+
+        if ((formatFeatureFlags & neededFeatures) != neededFeatures)
+            return KTX_INVALID_OPERATION;
+
+        if (formatFeatureFlags & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT)
+            blitFilter = VK_FILTER_LINEAR;
+        else
+            blitFilter = VK_FILTER_NEAREST; // XXX INVALID_OP?
+
+        max_dim = MAX(MAX(This->baseWidth, This->baseHeight), This->baseDepth);
+        numImageLevels = (uint32_t)floor(log2(max_dim)) + 1;
+    } else {
+        numImageLevels = This->numLevels;
+    }
+
+    {
+        ktx_uint32_t actualRowPitch = ktxTexture_GetRowPitch(This, 0);
+        ktx_uint32_t tightRowPitch = elementSize * This->baseWidth;
+        // If the texture's images do not have any row padding, we can use a
+        // faster path. Only uncompressed textures might have padding.
+        //
+        // The first test in the if will match compressed textures, because
+        // they all have a block size that is a multiple of 4, as well as
+        // a class of uncompressed textures that will never need padding.
+        //
+        // The second test matches textures whose level 0 has no padding. Any
+        // texture whose block size is not a multiple of 4 will need padding
+        // at some miplevel even if level 0 does not. So, if more than 1 level
+        // exists, we must use the slower path.
+        //
+        // Note all elementSizes > 4 Will be a multiple of 4, so only
+        // elementSizes of 1, 2 & 3 are a concern here.
+        if (elementSize % 4 == 0  /* There'll be no padding at any level. */
+               /* There is no padding at level 0 and no other levels. */
+            || (This->numLevels == 1 && actualRowPitch == tightRowPitch))
+            canUseFasterPath = KTX_TRUE;
+        else
+            canUseFasterPath = KTX_FALSE;
+    }
+
+    vkTexture->width = This->baseWidth;
+    vkTexture->height = This->baseHeight;
+    vkTexture->depth = This->baseDepth;
+    vkTexture->imageLayout = finalLayout;
+    vkTexture->imageFormat = vkFormat;
+    vkTexture->levelCount = numImageLevels;
+    vkTexture->layerCount = numImageLayers;
+    vkTexture->viewType = viewType;
+
+    VK_CHECK_RESULT(vkBeginCommandBuffer(vdi->cmdBuffer, &cmdBufBeginInfo));
+
+    if (tiling == VK_IMAGE_TILING_OPTIMAL)
+    {
+        // Create a host-visible staging buffer that contains the raw image data
+        VkBuffer stagingBuffer;
+        VkDeviceMemory stagingMemory;
+        VkBufferImageCopy* copyRegions;
+        VkDeviceSize textureSize;
+        VkBufferCreateInfo bufferCreateInfo = {
+          .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
+          .pNext = NULL
+        };
+        VkImageSubresourceRange subresourceRange;
+        VkFence copyFence;
+        VkFenceCreateInfo fenceCreateInfo = {
+            .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
+            .pNext = NULL,
+            .flags = VK_FLAGS_NONE
+        };
+        VkSubmitInfo submitInfo = {
+            .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
+            .pNext = NULL
+        };
+        ktx_uint8_t* pMappedStagingBuffer;
+        ktx_uint32_t numCopyRegions;
+        user_cbdata_optimal cbData;
+
+
+        textureSize = ktxTexture_GetSize(This);
+        bufferCreateInfo.size = textureSize;
+        if (canUseFasterPath) {
+            /*
+             * Because all array layers and faces are the same size they can
+             * be copied in a single operation so there'll be 1 copy per mip
+             * level.
+             */
+            numCopyRegions = This->numLevels;
+        } else {
+            /*
+             * Have to copy all images individually into the staging
+             * buffer so we can place them at correct multiples of
+             * elementSize and 4 and also need a copy region per image
+             * in case they end up with padding between them.
+             */
+            numCopyRegions = This->isArray ? This->numLevels
+                                  : This->numLevels * This->numFaces;
+            /* 
+             * Add extra space to allow for possible padding described
+             * above. A bit ad-hoc but it's only a small amount of
+             * memory.
+             */
+            bufferCreateInfo.size += numCopyRegions * elementSize * 4;
+        }
+        copyRegions = (VkBufferImageCopy*)malloc(sizeof(VkBufferImageCopy)
+                                                   * numCopyRegions);
+        if (copyRegions == NULL) {
+            return KTX_OUT_OF_MEMORY;
+        }
+
+        // This buffer is used as a transfer source for the buffer copy
+        bufferCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
+        bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+
+        VK_CHECK_RESULT(vkCreateBuffer(vdi->device, &bufferCreateInfo,
+                                       vdi->pAllocator, &stagingBuffer));
+
+        // Get memory requirements for the staging buffer (alignment,
+        // memory type bits)
+        vkGetBufferMemoryRequirements(vdi->device, stagingBuffer, &memReqs);
+
+        memAllocInfo.allocationSize = memReqs.size;
+        // Get memory type index for a host visible buffer
+        memAllocInfo.memoryTypeIndex = ktxVulkanDeviceInfo_getMemoryType(
+                vdi,
+                memReqs.memoryTypeBits,
+                VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
+              | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
+        );
+
+        vResult = vkAllocateMemory(vdi->device, &memAllocInfo,
+                                  vdi->pAllocator, &stagingMemory);
+        if (vResult != VK_SUCCESS) {
+            return KTX_OUT_OF_MEMORY;
+        }
+        VK_CHECK_RESULT(vkBindBufferMemory(vdi->device, stagingBuffer,
+                                           stagingMemory, 0));
+
+        VK_CHECK_RESULT(vkMapMemory(vdi->device, stagingMemory, 0,
+                                    memReqs.size, 0,
+                                    (void **)&pMappedStagingBuffer));
+
+        cbData.offset = 0;
+        cbData.region = copyRegions;
+        cbData.numFaces = This->numFaces;
+        cbData.numLayers = This->numLayers;
+        cbData.dest = pMappedStagingBuffer;
+        cbData.elementSize = elementSize;
+        cbData.numDimensions = This->numDimensions;
+#if defined(_DEBUG)
+        cbData.regionsArrayEnd = copyRegions + numCopyRegions;
+#endif
+        if (canUseFasterPath) {
+            // Bulk load the data to the staging buffer and iterate
+            // over levels.
+
+            if (This->pData) {
+                // Image data has already been loaded. Copy to staging
+                // buffer.
+                assert(This->dataSize <= memAllocInfo.allocationSize);
+                memcpy(pMappedStagingBuffer, This->pData, This->dataSize);
+            } else {
+                /* Load the image data directly into the staging buffer. */
+                /* The strange cast quiets an Xcode warning when building
+                 * for the Generic iOS Device where size_t is 32-bit even
+                 * when building for arm64. */
+                kResult = ktxTexture_LoadImageData(This,
+                                      pMappedStagingBuffer,
+                                      (ktx_size_t)memAllocInfo.allocationSize);
+                if (kResult != KTX_SUCCESS)
+                    return kResult;
+            }
+
+            // Iterate over mip levels to set up the copy regions.
+            kResult = ktxTexture_IterateLevels(This,
+                                               optimalTilingCallback,
+                                               &cbData);
+            // XXX Check for possible errors.
+        } else {
+            // Iterate over face-levels with callback that copies the
+            // face-levels to Vulkan-valid offsets in the staging buffer while
+            // removing padding. Using face-levels minimizes pre-staging-buffer
+            // buffering, in the event the data is not already loaded.
+            if (This->pData) {
+                kResult = ktxTexture_IterateLevelFaces(
+                                            This,
+                                            optimalTilingPadCallback,
+                                            &cbData);
+            } else {
+                kResult = ktxTexture_IterateLoadLevelFaces(
+                                            This,
+                                            optimalTilingPadCallback,
+                                            &cbData);
+                // XXX Check for possible errors.
+            }
+        }
+
+        vkUnmapMemory(vdi->device, stagingMemory);
+
+        // Create optimal tiled target image
+        imageCreateInfo.imageType = imageType;
+        imageCreateInfo.flags = createFlags;
+        imageCreateInfo.format = vkFormat;
+        // numImageLevels ensures enough levels for generateMipmaps.
+        imageCreateInfo.mipLevels = numImageLevels;
+        imageCreateInfo.arrayLayers = numImageLayers;
+        imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
+        imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
+        imageCreateInfo.usage = usageFlags;
+        imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+        imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+        imageCreateInfo.extent.width = vkTexture->width;
+        imageCreateInfo.extent.height = vkTexture->height;
+        imageCreateInfo.extent.depth = vkTexture->depth;
+
+        VK_CHECK_RESULT(vkCreateImage(vdi->device, &imageCreateInfo,
+                                      vdi->pAllocator, &vkTexture->image));
+
+        vkGetImageMemoryRequirements(vdi->device, vkTexture->image, &memReqs);
+
+        memAllocInfo.allocationSize = memReqs.size;
+
+        memAllocInfo.memoryTypeIndex = ktxVulkanDeviceInfo_getMemoryType(
+                                          vdi, memReqs.memoryTypeBits,
+                                          VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+        VK_CHECK_RESULT(vkAllocateMemory(vdi->device, &memAllocInfo,
+                                         vdi->pAllocator,
+                                         &vkTexture->deviceMemory));
+        VK_CHECK_RESULT(vkBindImageMemory(vdi->device, vkTexture->image,
+                                          vkTexture->deviceMemory, 0));
+
+        subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+        subresourceRange.baseMipLevel = 0;
+        subresourceRange.levelCount = This->numLevels;
+        subresourceRange.baseArrayLayer = 0;
+        subresourceRange.layerCount = numImageLayers;
+
+        // Image barrier to transition, possibly only the base level, image
+        // layout to TRANSFER_DST_OPTIMAL so it can be used as the copy
+        // destination.
+        setImageLayout(
+            vdi->cmdBuffer,
+            vkTexture->image,
+            VK_IMAGE_LAYOUT_UNDEFINED,
+            VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+            subresourceRange);
+
+        // Copy mip levels from staging buffer
+        vkCmdCopyBufferToImage(
+            vdi->cmdBuffer, stagingBuffer,
+            vkTexture->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+            numCopyRegions, copyRegions
+            );
+
+        if (This->generateMipmaps) {
+            generateMipmaps(vkTexture, vdi,
+                            blitFilter, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
+        } else {
+            // Transition image layout to finalLayout after all mip levels
+            // have been copied.
+            // In this case numImageLevels == This->numLevels
+            //subresourceRange.levelCount = numImageLevels;
+            setImageLayout(
+                vdi->cmdBuffer,
+                vkTexture->image,
+                VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+                finalLayout,
+                subresourceRange);
+        }
+
+        // Submit command buffer containing copy and image layout commands
+        VK_CHECK_RESULT(vkEndCommandBuffer(vdi->cmdBuffer));
+
+        // Create a fence to make sure that the copies have finished before
+        // continuing
+        VK_CHECK_RESULT(vkCreateFence(vdi->device, &fenceCreateInfo,
+                                      vdi->pAllocator, &copyFence));
+
+        submitInfo.commandBufferCount = 1;
+        submitInfo.pCommandBuffers = &vdi->cmdBuffer;
+
+        VK_CHECK_RESULT(vkQueueSubmit(vdi->queue, 1, &submitInfo, copyFence));
+
+        VK_CHECK_RESULT(vkWaitForFences(vdi->device, 1, &copyFence,
+                                        VK_TRUE, DEFAULT_FENCE_TIMEOUT));
+
+        vkDestroyFence(vdi->device, copyFence, vdi->pAllocator);
+
+        // Clean up staging resources
+        vkFreeMemory(vdi->device, stagingMemory, vdi->pAllocator);
+        vkDestroyBuffer(vdi->device, stagingBuffer, vdi->pAllocator);
+    }
+    else
+    {
+        VkImage mappableImage;
+        VkDeviceMemory mappableMemory;
+        VkFence nullFence = { VK_NULL_HANDLE };
+        VkSubmitInfo submitInfo = {
+            .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
+            .pNext = NULL
+        };
+        user_cbdata_linear cbData;
+        PFNKTXITERCB callback;
+
+        imageCreateInfo.imageType = imageType;
+        imageCreateInfo.flags = createFlags;
+        imageCreateInfo.format = vkFormat;
+        imageCreateInfo.extent.width = vkTexture->width;
+        imageCreateInfo.extent.height = vkTexture->height;
+        imageCreateInfo.extent.depth = vkTexture->depth;
+        // numImageLevels ensures enough levels for generateMipmaps.
+        imageCreateInfo.mipLevels = numImageLevels;
+        imageCreateInfo.arrayLayers = numImageLayers;
+        imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
+        imageCreateInfo.tiling = VK_IMAGE_TILING_LINEAR;
+        imageCreateInfo.usage = usageFlags;
+        imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+        imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
+
+        // Load mip map level 0 to linear tiling image
+        VK_CHECK_RESULT(vkCreateImage(vdi->device, &imageCreateInfo,
+                                      vdi->pAllocator, &mappableImage));
+
+        // Get memory requirements for this image
+        // like size and alignment
+        vkGetImageMemoryRequirements(vdi->device, mappableImage, &memReqs);
+        // Set memory allocation size to required memory size
+        memAllocInfo.allocationSize = memReqs.size;
+
+        // Get memory type that can be mapped to host memory
+        memAllocInfo.memoryTypeIndex = ktxVulkanDeviceInfo_getMemoryType(
+                vdi,
+                memReqs.memoryTypeBits,
+                VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
+
+        // Allocate host memory
+        vResult = vkAllocateMemory(vdi->device, &memAllocInfo, vdi->pAllocator,
+                                  &mappableMemory);
+        if (vResult != VK_SUCCESS) {
+            return KTX_OUT_OF_MEMORY;
+        }
+        VK_CHECK_RESULT(vkBindImageMemory(vdi->device, mappableImage,
+                                          mappableMemory, 0));
+
+        cbData.destImage = mappableImage;
+        cbData.device = vdi->device;
+        cbData.texture = This;
+        callback = canUseFasterPath ?
+                         linearTilingCallback : linearTilingPadCallback;
+
+        // Map image memory
+        VK_CHECK_RESULT(vkMapMemory(vdi->device, mappableMemory, 0,
+                        memReqs.size, 0, (void **)&cbData.dest));
+
+        // Iterate over images to copy texture data into mapped image memory.
+        if (ktxTexture_isActiveStream(This)) {
+            kResult = ktxTexture_IterateLoadLevelFaces(This,
+                                                       callback,
+                                                       &cbData);
+        } else {
+            kResult = ktxTexture_IterateLevelFaces(This,
+                                                   callback,
+                                                   &cbData);
+        }
+        // XXX Check for possible errors
+
+        vkUnmapMemory(vdi->device, mappableMemory);
+
+        // Linear tiled images can be directly used as textures.
+        vkTexture->image = mappableImage;
+        vkTexture->deviceMemory = mappableMemory;
+
+        if (This->generateMipmaps) {
+            generateMipmaps(vkTexture, vdi,
+                            blitFilter,
+                            VK_IMAGE_LAYOUT_PREINITIALIZED);
+        } else {
+            VkImageSubresourceRange subresourceRange;
+            subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+            subresourceRange.baseMipLevel = 0;
+            subresourceRange.levelCount = numImageLevels;
+            subresourceRange.baseArrayLayer = 0;
+            subresourceRange.layerCount = numImageLayers;
+
+           // Transition image layout to finalLayout.
+            setImageLayout(
+                vdi->cmdBuffer,
+                vkTexture->image,
+                VK_IMAGE_LAYOUT_PREINITIALIZED,
+                finalLayout,
+                subresourceRange);
+        }
+
+        // Submit command buffer containing image layout commands
+        VK_CHECK_RESULT(vkEndCommandBuffer(vdi->cmdBuffer));
+
+        submitInfo.waitSemaphoreCount = 0;
+        submitInfo.commandBufferCount = 1;
+        submitInfo.pCommandBuffers = &vdi->cmdBuffer;
+
+        VK_CHECK_RESULT(vkQueueSubmit(vdi->queue, 1, &submitInfo, nullFence));
+        VK_CHECK_RESULT(vkQueueWaitIdle(vdi->queue));
+    }
+    return KTX_SUCCESS;
+}
+
+/** @memberof ktxTexture
+ * @~English
+ * @brief Create a Vulkan image object from a ktxTexture object.
+ *
+ * Calls ktxTexture_VkUploadEx() with the most commonly used options:
+ * VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_SAMPLED_BIT and
+ * VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL.
+ * 
+ * @sa ktxTexture_VkUploadEx() for details and use that for complete
+ *     control.
+ */
+KTX_error_code
+ktxTexture_VkUpload(ktxTexture* texture, ktxVulkanDeviceInfo* vdi,
+                    ktxVulkanTexture *vkTexture)
+{
+    return ktxTexture_VkUploadEx(texture, vdi, vkTexture,
+                                 VK_IMAGE_TILING_OPTIMAL,
+                                 VK_IMAGE_USAGE_SAMPLED_BIT,
+                                 VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+}
+
+/** @memberof ktxTexture
+ * @~English
+ * @brief Return the VkFormat enum of a ktxTexture object.
+ *
+ * @return The VkFormat of the ktxTexture. May return VK_FORMAT_UNDEFINED if
+ *         there is no mapping from the GL internalformat and format.
+ */
+VkFormat
+ktxTexture_GetVkFormat(ktxTexture* This)
+{
+    VkFormat vkFormat;
+
+    vkFormat = vkGetFormatFromOpenGLInternalFormat(This->glInternalformat);
+    if (vkFormat == VK_FORMAT_UNDEFINED)
+        vkFormat = vkGetFormatFromOpenGLFormat(This->glFormat, This->glType);
+    return vkFormat;
+}
+
+//======================================================================
+//  Utilities
+//======================================================================
+
+/**
+ * @internal
+ * @~English
+ * @brief Create an image memory barrier for changing the layout of an image.
+ *
+ * The barrier is placed in the passed command buffer. See the Vulkan spec.
+ * chapter 11.4 "Image Layout" for details.
+ */
+static void
+setImageLayout(
+    VkCommandBuffer cmdBuffer,
+    VkImage image,
+    VkImageLayout oldLayout,
+    VkImageLayout newLayout,
+    VkImageSubresourceRange subresourceRange)
+{
+    // Create an image barrier object
+    VkImageMemoryBarrier imageMemoryBarrier = {
+        .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+        .pNext = NULL,
+         // Some default values
+        .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+        .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED
+    };
+
+    imageMemoryBarrier.oldLayout = oldLayout;
+    imageMemoryBarrier.newLayout = newLayout;
+    imageMemoryBarrier.image = image;
+    imageMemoryBarrier.subresourceRange = subresourceRange;
+
+    // Source layouts (old)
+    // The source access mask controls actions to be finished on the old
+    // layout before it will be transitioned to the new layout.
+    switch (oldLayout)
+    {
+    case VK_IMAGE_LAYOUT_UNDEFINED:
+        // Image layout is undefined (or does not matter).
+        // Only valid as initial layout. No flags required.
+        imageMemoryBarrier.srcAccessMask = 0;
+        break;
+
+    case VK_IMAGE_LAYOUT_PREINITIALIZED:
+        // Image is preinitialized.
+        // Only valid as initial layout for linear images; preserves memory
+        // contents. Make sure host writes have finished.
+        imageMemoryBarrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT;
+        break;
+
+    case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
+        // Image is a color attachment.
+        // Make sure writes to the color buffer have finished
+        imageMemoryBarrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+        break;
+
+    case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL:
+        // Image is a depth/stencil attachment.
+        // Make sure any writes to the depth/stencil buffer have finished.
+        imageMemoryBarrier.srcAccessMask
+                                = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
+        break;
+
+    case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL:
+        // Image is a transfer source.
+        // Make sure any reads from the image have finished
+        imageMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
+        break;
+
+    case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
+        // Image is a transfer destination.
+        // Make sure any writes to the image have finished.
+        imageMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+        break;
+
+    case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
+        // Image is read by a shader.
+        // Make sure any shader reads from the image have finished
+        imageMemoryBarrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
+        break;
+
+    default:
+        /* Value not used by callers, so not supported. */
+        assert(KTX_FALSE);
+    }
+
+    // Target layouts (new)
+    // The destination access mask controls the dependency for the new image
+    // layout.
+    switch (newLayout)
+    {
+    case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
+        // Image will be used as a transfer destination.
+        // Make sure any writes to the image have finished.
+        imageMemoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+        break;
+
+    case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL:
+        // Image will be used as a transfer source.
+        // Make sure any reads from and writes to the image have finished.
+        imageMemoryBarrier.srcAccessMask |= VK_ACCESS_TRANSFER_READ_BIT;
+        imageMemoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
+        break;
+
+    case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
+        // Image will be used as a color attachment.
+        // Make sure any writes to the color buffer have finished.
+        imageMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
+        imageMemoryBarrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+        break;
+
+    case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL:
+        // Image layout will be used as a depth/stencil attachment.
+        // Make sure any writes to depth/stencil buffer have finished.
+        imageMemoryBarrier.dstAccessMask
+                                = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
+        break;
+
+    case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
+        // Image will be read in a shader (sampler, input attachment).
+        // Make sure any writes to the image have finished.
+        if (imageMemoryBarrier.srcAccessMask == 0)
+        {
+            imageMemoryBarrier.srcAccessMask
+                    = VK_ACCESS_HOST_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT;
+        }
+        imageMemoryBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
+        break;
+    default:
+        /* Value not used by callers, so not supported. */
+        assert(KTX_FALSE);
+    }
+
+    // Put barrier on top of pipeline.
+    VkPipelineStageFlags srcStageFlags = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
+    VkPipelineStageFlags destStageFlags = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
+
+    // Add the barrier to the passed command buffer
+    vkCmdPipelineBarrier(
+        cmdBuffer,
+        srcStageFlags,
+        destStageFlags,
+        0,
+        0, NULL,
+        0, NULL,
+        1, &imageMemoryBarrier);
+}
+
+/** @internal
+ * @~English
+ * @brief Generate mipmaps from base using @c VkCmdBlitImage.
+ *
+ * Mipmaps are generated by blitting level n from level n-1 as it should
+ * be faster than the alternative of blitting all levels from the base level.
+ *
+ * After generation, the image is transitioned to the layout indicated by
+ * @c vkTexture->imageLayout.
+ *
+ * @param[in] vkTexture     pointer to an object with information about the
+ *                          image for which to generate mipmaps.
+ * @param[in] vdi           pointer to an object with information about the
+ *                          Vulkan device and command buffer to use.
+ * @param[in] blitFilter    the type of filter to use in the @c VkCmdBlitImage.
+ * @param[in] initialLayout the layout of the image on entry to the function.
+ */
+static void
+generateMipmaps(ktxVulkanTexture* vkTexture, ktxVulkanDeviceInfo* vdi,
+                VkFilter blitFilter, VkImageLayout initialLayout)
+{
+    VkImageSubresourceRange subresourceRange;
+    memset(&subresourceRange, 0, sizeof(subresourceRange));
+    subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+    subresourceRange.baseMipLevel = 0;
+    subresourceRange.levelCount = 1;
+    subresourceRange.baseArrayLayer = 0;
+    subresourceRange.layerCount = vkTexture->layerCount;
+
+    // Transition base level to SRC_OPTIMAL for blitting.
+    setImageLayout(
+        vdi->cmdBuffer,
+        vkTexture->image,
+        initialLayout,
+        VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+        subresourceRange);
+
+    // Generate the mip chain
+    // ----------------------
+    // Blit level n from level n-1.
+    for (uint32_t i = 1; i < vkTexture->levelCount; i++)
+    {
+        VkImageBlit imageBlit;
+        memset(&imageBlit, 0, sizeof(imageBlit));
+
+        // Source
+        imageBlit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+        imageBlit.srcSubresource.layerCount = vkTexture->layerCount;
+        imageBlit.srcSubresource.mipLevel = i-1;
+        imageBlit.srcOffsets[1].x = MAX(1, vkTexture->width >> (i - 1));
+        imageBlit.srcOffsets[1].y = MAX(1, vkTexture->height >> (i - 1));
+        imageBlit.srcOffsets[1].z = MAX(1, vkTexture->depth >> (i - 1));;
+
+        // Destination
+        imageBlit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+        imageBlit.dstSubresource.layerCount = 1;
+        imageBlit.dstSubresource.mipLevel = i;
+        imageBlit.dstOffsets[1].x = MAX(1, vkTexture->width >> i);
+        imageBlit.dstOffsets[1].y = MAX(1, vkTexture->height >> i);
+        imageBlit.dstOffsets[1].z = MAX(1, vkTexture->depth >> i);
+
+        VkImageSubresourceRange mipSubRange;
+        memset(&mipSubRange, 0, sizeof(mipSubRange));
+
+        mipSubRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+        mipSubRange.baseMipLevel = i;
+        mipSubRange.levelCount = 1;
+        mipSubRange.layerCount = vkTexture->layerCount;
+
+        // Transiton current mip level to transfer dest
+        setImageLayout(
+            vdi->cmdBuffer,
+            vkTexture->image,
+            VK_IMAGE_LAYOUT_UNDEFINED,
+            VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+            mipSubRange);
+
+        // Blit from previous level
+        vkCmdBlitImage(
+            vdi->cmdBuffer,
+            vkTexture->image,
+            VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+            vkTexture->image,
+            VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+            1,
+            &imageBlit,
+            blitFilter);
+
+        // Transiton current mip level to transfer source for read in
+        // next iteration.
+        setImageLayout(
+            vdi->cmdBuffer,
+            vkTexture->image,
+            VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+            VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+            mipSubRange);
+    }
+
+    // After the loop, all mip layers are in TRANSFER_SRC layout.
+    // Transition all to final layout.
+    subresourceRange.levelCount = vkTexture->levelCount;
+    setImageLayout(
+        vdi->cmdBuffer,
+        vkTexture->image,
+        VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+        vkTexture->imageLayout,
+        subresourceRange);
+}
+
+//======================================================================
+//  ktxVulkanTexture utilities
+//======================================================================
+
+/**
+ * @memberof ktxVulkanTexture
+ * @~English
+ * @brief Destructor for the object returned when loading a texture image.
+ *
+ * Frees the Vulkan resources created when the texture image was loaded.
+ *
+ * @param vkTexture  pointer to the ktxVulkanTexture to be destructed.
+ * @param device     handle to the Vulkan logical device to which the texture was
+ *                   loaded.
+ * @param pAllocator pointer to the allocator used during loading.
+ */
+void
+ktxVulkanTexture_Destruct(ktxVulkanTexture* vkTexture, VkDevice device,
+                          const VkAllocationCallbacks* pAllocator)
+{
+    vkDestroyImage(device, vkTexture->image, pAllocator);
+    vkFreeMemory(device, vkTexture->deviceMemory, pAllocator);
+}
+
+/** @} */
diff --git a/external/Vulkan/external/ktx/lib/writer.c b/external/Vulkan/external/ktx/lib/writer.c
new file mode 100644
index 0000000000000000000000000000000000000000..6c5166e7efaa86c8b4b060052d9870f9cc2f78ff
--- /dev/null
+++ b/external/Vulkan/external/ktx/lib/writer.c
@@ -0,0 +1,477 @@
+/* -*- tab-width: 4; -*- */
+/* vi: set sw=2 ts=4 expandtab: */
+
+/**
+ * @internal
+ * @file writer.c
+ * @~English
+ *
+ * @brief Functions for creating KTX-format files from a set of images.
+ *
+ * @author Mark Callow, HI Corporation
+ */
+
+/*
+ * ©2018 Mark Callow.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifdef _WIN32
+#define _CRT_SECURE_NO_WARNINGS
+#endif
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "ktx.h"
+#include "ktxint.h"
+#include "stream.h"
+#include "filestream.h"
+#include "memstream.h"
+
+/**
+ * @defgroup writer Writer
+ * @brief Write KTX-formatted data.
+ * @{
+ */
+
+/**
+ * @internal
+ * @memberof ktxTexture @private
+ * @~English
+ * @brief Set image for level, layer, faceSlice from a ktxStream source.
+ *
+ * @param[in] This      pointer to the target ktxTexture object.
+ * @param[in] level     mip level of the image to set.
+ * @param[in] layer     array layer of the image to set.
+ * @param[in] faceSlice cube map face or depth slice of the image to set.
+ * @param[in] src       ktxStream pointer to the source.
+ * @param[in] srcSize   size of the source image in bytes.
+ *
+ * @return      KTX_SUCCESS on success, other KTX_* enum values on error.
+ *
+ * @exception KTX_INVALID_VALUE @p This or @p src is NULL.
+ * @exception KTX_INVALID_VALUE @p srcSize != the expected image size for the
+ *                              specified level, layer & faceSlice.
+ * @exception KTX_INVALID_OPERATION
+ *                              No storage was allocated when the texture was
+ *                              created.
+ */
+KTX_error_code
+ktxTexture_setImageFromStream(ktxTexture* This, ktx_uint32_t level,
+                              ktx_uint32_t layer, ktx_uint32_t faceSlice,
+                              ktxStream* src, ktx_size_t srcSize)
+{
+    ktx_uint32_t packedRowBytes, rowBytes, rowPadding, numRows;
+    ktx_size_t packedBytes, unpackedBytes;
+    ktx_size_t imageOffset;
+#if (KTX_GL_UNPACK_ALIGNMENT != 4)
+    ktx_uint32_t faceLodPadding;
+#endif
+    
+    if (!This || !src)
+        return KTX_INVALID_VALUE;
+    
+    if (!This->pData)
+        return KTX_INVALID_OPERATION;
+    
+    ktxTexture_GetImageOffset(This, level, layer, faceSlice, &imageOffset);
+
+    if (This->isCompressed) {
+        packedBytes = ktxTexture_GetImageSize(This, level);
+        rowPadding = 0;
+        // These 2 are not used when rowPadding == 0. Quiets compiler warning.
+        packedRowBytes = 0;
+        rowBytes = 0;
+    } else {
+        ktxTexture_rowInfo(This, level, &numRows, &rowBytes, &rowPadding);
+        unpackedBytes = rowBytes * numRows;
+        if (rowPadding) {
+            packedRowBytes = rowBytes - rowPadding;
+            packedBytes = packedRowBytes * numRows;
+        } else {
+            packedRowBytes = rowBytes;
+            packedBytes = unpackedBytes;
+        }
+    }
+    
+    if (srcSize != packedBytes)
+        return KTX_INVALID_OPERATION;
+    // The above will catch a flagrantly invalid srcSize. This is an
+    // additional check of the internal calculations.
+    assert (imageOffset + srcSize <= This->dataSize);
+    
+#if (KTX_GL_UNPACK_ALIGNMENT != 4)
+    faceLodPadding = _KTX_PAD4_LEN(faceLodSize);
+#endif
+    
+    if (rowPadding == 0) {
+        /* Can copy whole image at once */
+        src->read(src, This->pData + imageOffset, srcSize);
+    } else {
+        /* Copy the rows individually, padding each one */
+        ktx_uint32_t row;
+        ktx_uint8_t* dst = This->pData + imageOffset;
+        ktx_uint8_t pad[4] = { 0, 0, 0, 0 };
+        for (row = 0; row < numRows; row++) {
+            ktx_uint32_t rowOffset = rowBytes * row;
+            src->read(src, dst + rowOffset, packedRowBytes);
+            memcpy(dst + rowOffset + packedRowBytes, pad, rowPadding);
+        }
+    }
+#if (KTX_GL_UNPACK_ALIGNMENT != 4)
+    /*
+     * When KTX_GL_UNPACK_ALIGNMENT == 4, rows, and therefore everything else,
+     * are always 4-byte aligned and faceLodPadding is always 0. It is always
+     * 0 for compressed formats too because they all have multiple-of-4 block
+     * sizes.
+     */
+    if (faceLodPadding)
+        memcpy(This->pData + faceLodSize, pad, faceLodPadding);
+#endif
+    return KTX_SUCCESS;
+}
+
+/**
+ * @memberof ktxTexture
+ * @~English
+ * @brief Set image for level, layer, faceSlice from a stdio stream source.
+ *
+ * Uncompressed images read from the stream are expected to have their rows
+ * tightly packed as is the norm for most image file formats. The copied image
+ * is padded as necessary to achieve the KTX-specified row alignment. No
+ * padding is done if the ktxTexture's @c isCompressed field is @c KTX_TRUE.
+ *
+ * Level, layer, faceSlice rather than offset are specified to enable some
+ * validation.
+ *
+ * @param[in] This      pointer to the target ktxTexture object.
+ * @param[in] level     mip level of the image to set.
+ * @param[in] layer     array layer of the image to set.
+ * @param[in] faceSlice cube map face or depth slice of the image to set.
+ * @param[in] src       stdio stream pointer to the source.
+ * @param[in] srcSize   size of the source image in bytes.
+ *
+ * @return      KTX_SUCCESS on success, other KTX_* enum values on error.
+ *
+ * @exception KTX_INVALID_VALUE @p This or @p src is NULL.
+ * @exception KTX_INVALID_VALUE @p srcSize != the expected image size for the
+ *                              specified level, layer & faceSlice.
+ * @exception KTX_INVALID_OPERATION
+ *                              No storage was allocated when the texture was
+ *                              created.
+ */
+KTX_error_code
+ktxTexture_SetImageFromStdioStream(ktxTexture* This, ktx_uint32_t level,
+                                   ktx_uint32_t layer, ktx_uint32_t faceSlice,
+                                   FILE* src, ktx_size_t srcSize)
+{
+    ktxStream srcstr;
+    KTX_error_code result;
+    
+    result = ktxFileStream_construct(&srcstr, src, KTX_FALSE);
+    if (result != KTX_SUCCESS)
+        return result;
+    result = ktxTexture_setImageFromStream(This, level, layer, faceSlice,
+                                           &srcstr, srcSize);
+    ktxFileStream_destruct(&srcstr);
+    return result;
+}
+
+/**
+ * @memberof ktxTexture
+ * @~English
+ * @brief Set image for level, layer, faceSlice from an image in memory.
+ *
+ * Uncompressed images in memory are expected to have their rows tightly packed
+ * as is the norm for most image file formats. The copied image is padded as
+ * necessary to achieve the KTX-specified row alignment. No padding is done if
+ * the ktxTexture's @c isCompressed field is @c KTX_TRUE.
+ *
+ * Level, layer, faceSlice rather than offset are specified to enable some
+ * validation.
+ *
+ * @warning Do not use @c memcpy for this as it will not pad when necessary.
+ *
+ * @param[in] This      pointer to the target ktxTexture object.
+ * @param[in] level     mip level of the image to set.
+ * @param[in] layer     array layer of the image to set.
+ * @param[in] faceSlice cube map face or depth slice of the image to set.
+ * @param[in] src       pointer to the image source in memory.
+ * @param[in] srcSize   size of the source image in bytes.
+ *
+ * @return      KTX_SUCCESS on success, other KTX_* enum values on error.
+ *
+ * @exception KTX_INVALID_VALUE @p This or @p src is NULL.
+ * @exception KTX_INVALID_VALUE @p srcSize != the expected image size for the
+ *                              specified level, layer & faceSlice.
+ * @exception KTX_INVALID_OPERATION
+ *                              No storage was allocated when the texture was
+ *                              created.
+ */
+KTX_error_code
+ktxTexture_SetImageFromMemory(ktxTexture* This, ktx_uint32_t level,
+                              ktx_uint32_t layer, ktx_uint32_t faceSlice,
+                              const ktx_uint8_t* src, ktx_size_t srcSize)
+{
+    ktxStream srcstr;
+    KTX_error_code result;
+    
+    result = ktxMemStream_construct_ro(&srcstr, src, srcSize);
+    if (result != KTX_SUCCESS)
+        return result;
+    result = ktxTexture_setImageFromStream(This, level, layer, faceSlice,
+                                           &srcstr, srcSize);
+    ktxMemStream_destruct(&srcstr);
+    return result;
+}
+
+/**
+ * @internal
+ * @memberof ktxTexture @private
+ * @~English
+ * @brief Write a ktxTexture object to a ktxStream in KTX format.
+ *
+ * @param[in] This      pointer to the target ktxTexture object.
+ * @param[in] dststr    destination ktxStream.
+ *
+ * @return      KTX_SUCCESS on success, other KTX_* enum values on error.
+ *
+ * @exception KTX_INVALID_VALUE @p This or @p dststr is NULL.
+ * @exception KTX_INVALID_OPERATION
+ *                              The ktxTexture does not contain any image data.
+ * @exception KTX_FILE_OVERFLOW The file exceeded the maximum size supported by
+ *                              the system.
+ * @exception KTX_FILE_WRITE_ERROR
+ *                              An error occurred while writing the file.
+ */
+static KTX_error_code
+ktxTexture_writeToStream(ktxTexture* This, ktxStream* dststr)
+{
+    KTX_header header = KTX_IDENTIFIER_REF;
+    KTX_error_code result = KTX_SUCCESS;
+    ktx_uint32_t kvdLen;
+    ktx_uint8_t* pKvd;
+    ktx_uint32_t level, levelOffset;
+    
+    if (!dststr) {
+        return KTX_INVALID_VALUE;
+    }
+    
+    if (This->pData == NULL)
+        return KTX_INVALID_OPERATION;
+
+    //endianess int.. if this comes out reversed, all of the other ints will too.
+    header.endianness = KTX_ENDIAN_REF;
+    header.glInternalformat = This->glInternalformat;
+    header.glFormat = This->glFormat;
+    header.glBaseInternalformat = This->glBaseInternalformat;
+    header.glType = This->glType;
+    header.glTypeSize = ktxTexture_glTypeSize(This);
+    header.pixelWidth = This->baseWidth;
+    header.pixelHeight = This->baseHeight;
+    header.pixelDepth = This->baseDepth;
+    header.numberOfArrayElements = This->isArray ? This->numLayers : 0;
+    assert (This->isCubemap ? This->numFaces == 6 : This->numFaces == 1);
+    header.numberOfFaces = This->numFaces;
+    assert (This->generateMipmaps ? This->numLevels == 1 : This->numLevels >= 1);
+    header.numberOfMipmapLevels = This->generateMipmaps ? 0 : This->numLevels;
+    
+    ktxHashList_Serialize(&This->kvDataHead, &kvdLen, &pKvd);
+    header.bytesOfKeyValueData = kvdLen;
+
+    //write header
+    result = dststr->write(dststr, &header, sizeof(KTX_header), 1);
+    if (result != KTX_SUCCESS)
+        return result;
+    
+    //write keyValueData
+    if (kvdLen != 0) {
+        assert(pKvd != NULL);
+
+        result = dststr->write(dststr, pKvd, 1, kvdLen);
+        free(pKvd);
+        if (result != KTX_SUCCESS)
+            return result;
+    }
+    
+    /* Write the image data */
+    for (level = 0, levelOffset=0; level < This->numLevels; ++level)
+    {
+        ktx_uint32_t faceLodSize, layer, levelDepth, numImages;
+        ktx_size_t imageSize;
+        
+        faceLodSize = (ktx_uint32_t)ktxTexture_faceLodSize(This, level);
+        imageSize = ktxTexture_GetImageSize(This, level);
+        levelDepth = MAX(1, This->baseDepth >> level);
+        if (This->isCubemap && !This->isArray)
+            numImages = This->numFaces;
+        else
+            numImages = This->isCubemap ? This->numFaces : levelDepth;
+        
+        result = dststr->write(dststr, &faceLodSize, sizeof(faceLodSize), 1);
+        if (result != KTX_SUCCESS)
+            goto cleanup;
+
+        for (layer = 0; layer < This->numLayers; layer++) {
+            ktx_uint32_t faceSlice;
+            
+            for (faceSlice = 0; faceSlice < numImages; faceSlice++) {
+                result = dststr->write(dststr, This->pData + levelOffset,
+                                       imageSize, 1);
+                levelOffset += (ktx_uint32_t)imageSize;
+            }
+        }
+    }
+    
+cleanup:
+    return result;
+}
+
+/**
+ * @memberof ktxTexture
+ * @~English
+ * @brief Write a ktxTexture object to a stdio stream in KTX format.
+ *
+ * @param[in] This      pointer to the target ktxTexture object.
+ * @param[in] dstsstr   destination stdio stream.
+ *
+ * @return      KTX_SUCCESS on success, other KTX_* enum values on error.
+ *
+ * @exception KTX_INVALID_VALUE @p This or @p dstsstr is NULL.
+ * @exception KTX_INVALID_OPERATION
+ *                              The ktxTexture does not contain any image data.
+ * @exception KTX_FILE_OVERFLOW The file exceeded the maximum size supported by
+ *                              the system.
+ * @exception KTX_FILE_WRITE_ERROR
+ *                              An error occurred while writing the file.
+ */
+KTX_error_code
+ktxTexture_WriteToStdioStream(ktxTexture* This, FILE* dstsstr)
+{
+    ktxStream stream;
+    KTX_error_code result = KTX_SUCCESS;
+    
+    if (!This)
+        return KTX_INVALID_VALUE;
+    
+    result = ktxFileStream_construct(&stream, dstsstr, KTX_FALSE);
+    if (result != KTX_SUCCESS)
+        return result;
+    
+    return ktxTexture_writeToStream(This, &stream);
+}
+
+/**
+ * @memberof ktxTexture
+ * @~English
+ * @brief Write a ktxTexture object to a named file in KTX format.
+ *
+ * @param[in] This      pointer to the target ktxTexture object.
+ * @param[in] dstname   destination file name.
+ *
+ * @return      KTX_SUCCESS on success, other KTX_* enum values on error.
+ *
+ * @exception KTX_INVALID_VALUE @p This or @p dstname is NULL.
+ * @exception KTX_INVALID_OPERATION
+ *                              The ktxTexture does not contain any image data.
+ * @exception KTX_FILE_OVERFLOW The file exceeded the maximum size supported by
+ *                              the system.
+ * @exception KTX_FILE_WRITE_ERROR
+ *                              An error occurred while writing the file.
+ */
+KTX_error_code
+ktxTexture_WriteToNamedFile(ktxTexture* This, const char* const dstname)
+{
+    KTX_error_code result;
+    FILE* dst;
+
+    if (!This)
+        return KTX_INVALID_VALUE;
+
+    dst = fopen(dstname, "wb");
+    if (dst) {
+        result = ktxTexture_WriteToStdioStream(This, dst);
+        fclose(dst);
+    } else
+        result = KTX_FILE_OPEN_FAILED;
+    
+    return result;
+}
+
+/**
+ * @memberof ktxTexture
+ * @~English
+ * @brief Write a ktxTexture object to block of memory in KTX format.
+ *
+ * Memory is allocated by the function and the caller is responsible for
+ * freeing it.
+ *
+ * @param[in]     This       pointer to the target ktxTexture object.
+ * @param[in,out] ppDstBytes pointer to location to write the address of
+ *                           the destination memory. The Application is
+ *                           responsible for freeing this memory.
+ * @param[in,out] pSize      pointer to location to write the size in bytes of
+ *                           the KTX data.
+ *
+ * @return      KTX_SUCCESS on success, other KTX_* enum values on error.
+ *
+ * @exception KTX_INVALID_VALUE @p This, @p ppDstBytes or @p pSize is NULL.
+ * @exception KTX_INVALID_OPERATION
+ *                              The ktxTexture does not contain any image data.
+ * @exception KTX_FILE_OVERFLOW The file exceeded the maximum size supported by
+ *                              the system.
+ * @exception KTX_FILE_WRITE_ERROR
+ *                              An error occurred while writing the file.
+ */
+KTX_error_code
+ktxTexture_WriteToMemory(ktxTexture* This,
+                         ktx_uint8_t** ppDstBytes, ktx_size_t* pSize)
+{
+    struct ktxStream dststr;
+    KTX_error_code result;
+    ktx_size_t strSize;
+
+    if (!This || !ppDstBytes || !pSize)
+        return KTX_INVALID_VALUE;
+
+    *ppDstBytes = NULL;
+    
+    result = ktxMemStream_construct(&dststr, KTX_FALSE);
+    if (result != KTX_SUCCESS)
+        return result;
+    
+    result = ktxTexture_writeToStream(This, &dststr);
+    if(result != KTX_SUCCESS)
+    {
+        ktxMemStream_destruct(&dststr);
+        return result;
+    }
+    
+    ktxMemStream_getdata(&dststr, ppDstBytes);
+    dststr.getsize(&dststr, &strSize);
+    *pSize = (GLsizei)strSize;
+    /* This function does not free the memory pointed at by the
+     * value obtained from ktxMemStream_getdata() thanks to the
+     * KTX_FALSE passed to the constructor above.
+     */
+    ktxMemStream_destruct(&dststr);
+    return KTX_SUCCESS;
+
+}
+
+/** @} */
+
diff --git a/external/Vulkan/external/ktx/lib/writer_v1.c b/external/Vulkan/external/ktx/lib/writer_v1.c
new file mode 100644
index 0000000000000000000000000000000000000000..50856587bd07952dd4a721ce6d1cf19a66ef2bbe
--- /dev/null
+++ b/external/Vulkan/external/ktx/lib/writer_v1.c
@@ -0,0 +1,708 @@
+/* -*- tab-width: 4; -*- */
+/* vi: set sw=2 ts=4 expandtab: */
+
+/* $Id: 687889ad2b1bee58a6d439ef4d6c10830a733418 $ */
+
+/*
+ * ©2010-2018 Mark Callow.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @internal
+ * @file writer.c
+ * @~English
+ *
+ * @brief V1 API functions for creating KTX-format files from a set of images.
+ *
+ * Keep the v1 API implementation as is because reimplementing it in terms
+ * of the v2 API would use too much memory. This is because this API expects
+ * all the images to already be loaded in memory and the v2 api would load
+ * them into another memory buffer prior to writing the file.
+ *
+ * @author Mark Callow, when at HI Corporation
+ */
+
+#ifdef _WIN32
+#define _CRT_SECURE_NO_WARNINGS
+#endif
+
+#include <assert.h>
+#include <limits.h>
+
+#include "GL/glcorearb.h"
+#include "ktx.h"
+#include "ktxint.h"
+#include "stream.h"
+#include "filestream.h"
+#include "memstream.h"
+#include "gl_format.h"
+
+static GLenum getFormatFromInternalFormatLegacy(GLenum internalFormat);
+static GLenum getTypeFromInternalFormatLegacy(GLenum internalFormat);
+static void getFormatSizeLegacy(GLenum internalFormat,
+                                GlFormatSize* formatSize);
+
+/**
+ * @internal
+ * @ingroup writer
+ * @~English
+ * @deprecated Use ktxTexture_writeToStream().
+ * @brief Write image(s) in a KTX-format to a ktxStream.
+ *
+ * @param [in] stream       pointer to the ktxStream from which to load.
+ * @param [in] textureInfo  pointer to a KTX_texture_info structure providing
+ *                          information about the images to be included in
+ *                          the KTX file.
+ * @param [in] bytesOfKeyValueData
+ *                          specifies the number of bytes of key-value data.
+ * @param [in] keyValueData a pointer to the keyValue data.
+ * @param [in] numImages    number of images in the following array
+ * @param [in] images       array of KTX_image_info providing image size and
+ *                          data.
+ *
+ * @return  KTX_SUCCESS on success, other KTX_* enum values on error.
+ *
+ * @exception KTX_INVALID_VALUE @c glTypeSize in @p textureInfo is not 1, 2, or
+ *                              4 or is different from the size of the type
+ *                              specified in @c glType.
+ * @exception KTX_INVALID_VALUE @c pixelWidth in @p textureInfo is 0 or
+ *                              pixelDepth != 0 && pixelHeight == 0.
+ * @exception KTX_INVALID_VALUE In @p textureInfo, @c numberOfFaces != 1 or
+ *                              numberOfFaces != 6 or numberOfArrayElements
+ *                              or numberOfMipmapLevels are < 0.
+ * @exception KTX_INVALID_VALUE @c glType in @p textureInfo is an unrecognized
+ *                              type.
+ * @exception KTX_INVALID_OPERATION
+ *                              In @p textureInfo, numberOfFaces == 6 and
+ *                              images are either not 2D or are not square.
+ * @exception KTX_INVALID_OPERATION
+ *                              @p numImages is insufficient for the specified
+ *                              number of mipmap levels and faces.
+ * @exception KTX_INVALID_OPERATION
+ *                              The size of a provided image is different than
+ *                              that required for the specified width, height
+ *                              or depth or for the mipmap level being
+ *                              processed.
+ * @exception KTX_INVALID_OPERATION
+ *                              @c glType and @c glFormat in @p textureInfo are
+ *                              mismatched. See OpenGL 4.4 specification
+ *                              section 8.4.4 and table 8.5.
+ * @exception KTX_FILE_WRITE_ERROR
+ *                              A system error occurred while writing the file.
+ * @exception KTX_OUT_OF_MEMORY System failed to allocate sufficient memory.
+ */
+static
+KTX_error_code
+ktxWriteKTXS(struct ktxStream *stream, const KTX_texture_info* textureInfo,
+             GLsizei bytesOfKeyValueData, const void* keyValueData,
+             GLuint numImages, KTX_image_info images[])
+{
+    KTX_header header = KTX_IDENTIFIER_REF;
+    GLuint i, level, dimension, cubemap = 0;
+    GLuint numMipmapLevels, numArrayElements;
+    GLbyte pad[4] = { 0, 0, 0, 0 };
+    KTX_error_code result = KTX_SUCCESS;
+    GLboolean compressed = GL_FALSE;
+    GLuint groupBytes;
+
+    if (!stream) {
+        return KTX_INVALID_VALUE;
+    }
+
+    /* endianess int.. if this comes out reversed, all of the other ints will
+     * too.
+     */
+    header.endianness = KTX_ENDIAN_REF;
+    header.glType = textureInfo->glType;
+    header.glTypeSize = textureInfo->glTypeSize;
+    header.glFormat = textureInfo->glFormat;
+    header.glInternalformat = textureInfo->glInternalFormat;
+    header.glBaseInternalformat = textureInfo->glBaseInternalFormat;
+    header.pixelWidth = textureInfo->pixelWidth;
+    header.pixelHeight = textureInfo->pixelHeight;
+    header.pixelDepth = textureInfo->pixelDepth;
+    header.numberOfArrayElements = textureInfo->numberOfArrayElements;
+    header.numberOfFaces = textureInfo->numberOfFaces;
+    header.numberOfMipmapLevels = textureInfo->numberOfMipmapLevels;
+    header.bytesOfKeyValueData = bytesOfKeyValueData;
+
+    /* Do some sanity checking */
+    if (header.glTypeSize != 1 &&
+        header.glTypeSize != 2 &&
+        header.glTypeSize != 4)
+    {
+        /* Only 8, 16, and 32-bit types are supported for byte-swapping.
+         * See UNPACK_SWAP_BYTES & table 8.4 in the OpenGL 4.4 spec.
+         */
+        return KTX_INVALID_VALUE;
+    }
+
+    if (header.glType == 0 || header.glFormat == 0)
+    {
+        if (header.glType + header.glFormat != 0) {
+            /* either both or neither of glType & glFormat must be zero */
+            return KTX_INVALID_VALUE;
+        } else
+            compressed = GL_TRUE;
+    }
+    else
+    {
+        GlFormatSize formatInfo;
+        GLenum expectedFormat, expectedType;
+        
+        expectedFormat = getFormatFromInternalFormatLegacy(header.glInternalformat);
+        if (expectedFormat == GL_INVALID_VALUE
+            || expectedFormat != header.glFormat)
+            return KTX_INVALID_OPERATION;
+        expectedType = getTypeFromInternalFormatLegacy(header.glInternalformat);
+        if (expectedType == GL_INVALID_VALUE
+            || expectedType != header.glType)
+            return KTX_INVALID_OPERATION;
+        getFormatSizeLegacy(header.glInternalformat, &formatInfo);
+        groupBytes = formatInfo.blockSizeInBits / CHAR_BIT;
+    }
+
+
+    /* Check texture dimensions. KTX files can store 8 types of textures:
+     * 1D, 2D, 3D, cube, and array variants of these. There is currently
+     * no GL extension that would accept 3D array array textures but we'll
+     * let such files be created.
+     */
+    if ((header.pixelWidth == 0) ||
+        (header.pixelDepth > 0 && header.pixelHeight == 0))
+    {
+        /* texture must have width */
+        /* texture must have height if it has depth */
+        return KTX_INVALID_VALUE;
+    }
+    if (header.pixelHeight > 0 && header.pixelDepth > 0)
+        dimension = 3;
+    else if (header.pixelHeight > 0)
+        dimension = 2;
+    else
+        dimension = 1;
+
+    if (header.numberOfFaces != 1 && header.pixelDepth != 0)
+    {
+        /* No 3D cubemaps so either faces or depth must be 1. */
+            return KTX_INVALID_OPERATION;
+    }
+
+    if (header.numberOfFaces == 6)
+    {
+        if (dimension != 2)
+        {
+            /* cube map needs 2D faces */
+            return KTX_INVALID_OPERATION;
+        }
+        if (header.pixelWidth != header.pixelHeight)
+        {
+            /* cube maps require square images */
+            return KTX_INVALID_OPERATION;
+        }
+    }
+    else if (header.numberOfFaces != 1)
+    {
+        /* numberOfFaces must be either 1 or 6 */
+        return KTX_INVALID_VALUE;
+    }
+ 
+    if (header.numberOfArrayElements == 0)
+        numArrayElements = 1;
+    else
+        numArrayElements = header.numberOfArrayElements;
+    
+    if (header.numberOfFaces == 6)
+        cubemap = 1;
+
+    /* Check number of mipmap levels */
+    if (header.numberOfMipmapLevels == 0)
+    {
+        numMipmapLevels = 1;
+    }
+    else
+        numMipmapLevels = header.numberOfMipmapLevels;
+    if (numMipmapLevels > 1) {
+        GLuint max_dim = MAX(MAX(header.pixelWidth, header.pixelHeight), header.pixelDepth);
+        if (max_dim < ((GLuint)1 << (header.numberOfMipmapLevels - 1)))
+        {
+            /* Can't have more mip levels than 1 + log2(max(width, height, depth)) */
+            return KTX_INVALID_VALUE;
+        }
+    }
+
+    if (numImages < numMipmapLevels * header.numberOfFaces)
+    {
+        /* Not enough images */
+        return KTX_INVALID_OPERATION;
+    }
+
+    //write header
+    result = stream->write(stream, &header, sizeof(KTX_header), 1);
+    if (result != KTX_SUCCESS)
+        return result;
+
+    //write keyValueData
+    if (bytesOfKeyValueData != 0) {
+        if (keyValueData == NULL)
+            return KTX_INVALID_OPERATION;
+
+        result = stream->write(stream, keyValueData, 1, bytesOfKeyValueData);
+        if (result != KTX_SUCCESS)
+            return result;
+    }
+
+    /* Write the image data */
+    for (level = 0, i = 0; level < numMipmapLevels; ++level)
+    {
+        GLuint faceSlice, faceLodSize;
+#if (KTX_GL_UNPACK_ALIGNMENT != 4)
+        GLuint faceLodPadding;
+#endif
+        GLuint pixelWidth, pixelHeight, pixelDepth;
+        GLsizei imageBytes, packedImageBytes;
+        GLsizei packedRowBytes, rowBytes, rowPadding;
+        GLuint numImages;
+
+        pixelWidth  = MAX(1, header.pixelWidth  >> level);
+        pixelHeight = MAX(1, header.pixelHeight >> level);
+        pixelDepth  = MAX(1, header.pixelDepth  >> level);
+
+        /* Calculate face sizes for this LoD based on glType, glFormat, width & height */
+        packedImageBytes = groupBytes
+                           * pixelWidth
+                           * pixelHeight;
+
+        rowPadding = 0;
+        packedRowBytes = groupBytes * pixelWidth;
+        /* KTX format specifies UNPACK_ALIGNMENT==4 */
+        /* GL spec: rows are not to be padded when elementBytes != 1, 2, 4 or 8.
+         * As GL currently has no such elements, no test is necessary.
+         */
+        if (!compressed) {
+            rowBytes = _KTX_PAD_UNPACK_ALIGN(packedRowBytes);
+            rowPadding = rowBytes - packedRowBytes;
+        }
+        if (rowPadding == 0) {
+            imageBytes = packedImageBytes;
+        } else {
+            /* Need to pad the rows to meet the required UNPACK_ALIGNMENT */
+            imageBytes = rowBytes * pixelHeight;
+        }
+
+        if (textureInfo->numberOfArrayElements == 0 && cubemap) {
+            /* Non-array cubemap. */
+            numImages = 6;
+            faceLodSize = imageBytes;
+        } else {
+            numImages = cubemap ? 6 : pixelDepth;
+            numImages *= numArrayElements;
+            faceLodSize = imageBytes * numImages;
+        }
+#if (KTX_GL_UNPACK_ALIGNMENT != 4)
+        faceLodPadding = _KTX_PAD4_LEN(faceLodSize);
+#endif
+ 
+        result = stream->write(stream, &faceLodSize, sizeof(faceLodSize), 1);
+        if (result != KTX_SUCCESS)
+            goto cleanup;
+
+        for (faceSlice = 0; faceSlice < numImages; ++faceSlice, ++i) {
+            if (!compressed) {
+                /* Sanity check. */
+                if (images[i].size != packedImageBytes) {
+                    result = KTX_INVALID_OPERATION;
+                    goto cleanup;
+                }
+            }
+            if (rowPadding == 0) {
+                /* Can write whole face at once */
+                result = stream->write(stream, images[i].data, images[i].size,
+                                       1);
+                if (result != KTX_SUCCESS)
+                    goto cleanup;
+            } else {
+                /* Write the rows individually, padding each one */
+                GLuint row;
+                GLuint numRows = pixelHeight;
+                for (row = 0; row < numRows; row++) {
+                    result = stream->write(stream,
+                                            &images[i].data[row*packedRowBytes],
+                                            packedRowBytes, 1);
+                    if (result != KTX_SUCCESS)
+                        goto cleanup;
+
+                    result = stream->write(stream, pad, sizeof(GLbyte),
+                                              rowPadding);
+                    if (result != KTX_SUCCESS)
+                        goto cleanup;
+                }
+            }
+#if (KTX_GL_UNPACK_ALIGNMENT != 4)
+            /*
+             * When KTX_GL_UNPACK_ALIGNMENT == 4, rows, and therefore everything
+             * else, are always 4-byte aligned and faceLodPadding is always 0.
+             * It is always 0 for compressed formats too because they all have
+             * multiple-of-4 block sizes.
+             */
+            if (faceLodPadding) {
+                result = stream->write(stream, pad, sizeof(GLbyte),
+                                       faceLodPadding);
+                if (result != KTX_SUCCESS)
+                    goto cleanup;
+            }
+#endif
+        }
+    }
+
+cleanup:
+    return result;
+}
+
+/**
+ * @~English
+ * @ingroup writer
+ * @deprecated Use ktxTexture_WriteToStdioStream().
+ * @brief Write image(s) in KTX format to a stdio FILE stream.
+ *
+ * @note textureInfo directly reflects what is written to the KTX file
+ *       header. That is @c numberOfArrayElements should be 0 for non arrays;
+ *       @c numMipmapLevels should be 0 to request generateMipmaps and @c type,
+ *       @c format & @c typesize should be 0 for compressed textures.
+ *
+ * @param[in] file         pointer to the FILE stream to write to.
+ * @param[in] textureInfo  pointer to a KTX_texture_info structure providing
+ *                         information about the images to be included in
+ *                         the KTX file.
+ * @param[in] bytesOfKeyValueData
+ *                         specifies the number of bytes of key-value data.
+ * @param[in] keyValueData a pointer to the keyValue data.
+ * @param[in] numImages    number of images in the following array
+ * @param[in] images       array of KTX_image_info providing image size and
+ *                         data.
+ *
+ * @return      KTX_SUCCESS on success, other KTX_* enum values on error.
+ *
+ * @exception KTX_INVALID_VALUE @c glTypeSize in @p textureInfo is not 1, 2, or
+ *                              4 or is different from the size of the type
+ *                              specified in @c glType.
+ * @exception KTX_INVALID_VALUE @c pixelWidth in @p textureInfo is 0 or
+ *                              pixelDepth != 0 && pixelHeight == 0.
+ * @exception KTX_INVALID_VALUE In @p textureInfo, @c numberOfFaces != 1 or
+ *                              numberOfFaces != 6 or numberOfArrayElements
+ *                              or numberOfMipmapLevels are < 0.
+ * @exception KTX_INVALID_VALUE @c glType in @p textureInfo is an unrecognized
+ *                              type.
+ * @exception KTX_INVALID_OPERATION
+ *                              In @p textureInfo, numberOfFaces == 6 and
+ *                              images are either not 2D or are not square.
+ * @exception KTX_INVALID_OPERATION
+ *                              @p numImages is insufficient for the specified
+ *                              number of mipmap levels and faces.
+ * @exception KTX_INVALID_OPERATION
+ *                              The size of a provided image is different than
+ *                              that required for the specified width, height
+ *                              or depth or for the mipmap level being
+ *                              processed.
+ * @exception KTX_INVALID_OPERATION
+ *                              @c glType and @c glFormat in @p textureInfo are
+ *                              mismatched. See OpenGL 4.4 specification
+ *                              section 8.4.4 and table 8.5.
+ * @exception KTX_FILE_WRITE_ERROR
+ *                              A system error occurred while writing the file.
+ * @exception KTX_OUT_OF_MEMORY System failed to allocate sufficient memory.
+ */
+KTX_error_code
+ktxWriteKTXF(FILE *file, const KTX_texture_info* textureInfo,
+                         GLsizei bytesOfKeyValueData, const void* keyValueData,
+                         GLuint numImages, KTX_image_info images[])
+{
+    struct ktxStream stream;
+    KTX_error_code result;
+    
+    result = ktxFileStream_construct(&stream, file, KTX_FALSE);
+    if (result != KTX_SUCCESS)
+        return result;
+    result = ktxWriteKTXS(&stream, textureInfo, bytesOfKeyValueData, keyValueData,
+                          numImages, images);
+    stream.destruct(&stream);
+    return result;
+}
+
+/**
+ * @~English
+ * @ingroup writer
+ * @deprecated Use ktxTexture_WriteToNamedFile().
+ * @brief Write image(s) in KTX format to a file on disk.
+ *
+ * @note textureInfo directly reflects what is written to the KTX file
+ *       header. That is @c numberOfArrayElements should be 0 for non arrays;
+ *       @c numMipmapLevels should be 0 to request generateMipmaps and @c type,
+ *       @c format & @c typesize should be 0 for compressed textures.
+ *
+ * @param[in] dstname      pointer to a C string that contains the path of
+ *                         the file to load.
+ * @param[in] textureInfo  pointer to a KTX_texture_info structure providing
+ *                         information about the images to be included in
+ *                         the KTX file.
+ * @param[in] bytesOfKeyValueData
+ *                         specifies the number of bytes of key-value data.
+ * @param[in] keyValueData a pointer to the keyValue data.
+ * @param[in] numImages    number of images in the following array.
+ * @param[in] images       array of KTX_image_info providing image size and
+ *                         data.
+ *
+ * @return  KTX_SUCCESS on success, other KTX_* enum values on error.
+ *
+ * @exception KTX_FILE_OPEN_FAILED Unable to open the specified file for
+ *                                 writing.
+ *
+ * For other exceptions, see ktxWriteKTXF().
+ */
+KTX_error_code
+ktxWriteKTXN(const char* dstname, const KTX_texture_info* textureInfo,
+             GLsizei bytesOfKeyValueData, const void* keyValueData,
+             GLuint numImages, KTX_image_info images[])
+{
+    struct ktxStream stream;
+    KTX_error_code result;
+    FILE* dst = fopen(dstname, "wb");
+
+    if (dst) {
+        result = ktxWriteKTXS(&stream, textureInfo, bytesOfKeyValueData,
+                              keyValueData, numImages, images);
+        fclose(dst);
+    } else
+        result = KTX_FILE_OPEN_FAILED;
+
+    return result;
+}
+
+/**
+ * @~English
+ * @ingroup writer
+ * @deprecated Use ktxTexture_WriteToMemory().
+ * @brief Write image(s) in KTX format to memory.
+ *
+ * Memory is allocated by the function and the caller is responsible for
+ * freeing it.
+ *
+ * @note textureInfo directly reflects what is written to the KTX file
+ *       header. That is @c numberOfArrayElements should be 0 for non arrays;
+ *       @c numMipmapLevels should be 0 to request generateMipmaps and @c type,
+ *       @c format & @c typesize should be 0 for compressed textures.
+ *
+ * @param[out] ppDstBytes  pointer to location to write the address of
+ *                         the destination memory. The Application is
+                           responsible for freeing this memory.
+ * @param[out] pSize       pointer to location to write the size in bytes of
+ *                         the KTX data.
+ * @param[in] textureInfo  pointer to a KTX_texture_info structure providing
+ *                         information about the images to be included in
+ *                         the KTX file.
+ * @param[in] bytesOfKeyValueData
+ *                         specifies the number of bytes of key-value data.
+ * @param[in] keyValueData a pointer to the keyValue data.
+ * @param[in] numImages    number of images in the following array.
+ * @param[in] images       array of KTX_image_info providing image size and
+ *                         data.
+ *
+ * @return      KTX_SUCCESS on success, other KTX_* enum values on error.
+ *
+ * @exception KTX_INVALID_VALUE @p dst or @p size is NULL.
+ * @exception KTX_INVALID_VALUE @c glTypeSize in @p textureInfo is not 1, 2, or
+ *                              4 or is different from the size of the type
+ *                              specified in @c glType.
+ * @exception KTX_INVALID_VALUE @c pixelWidth in @p textureInfo is 0 or
+ *                              pixelDepth != 0 && pixelHeight == 0.
+ * @exception KTX_INVALID_VALUE In @p textureInfo, @c numberOfFaces != 1 or
+ *                              numberOfFaces != 6 or numberOfArrayElements
+ *                              or numberOfMipmapLevels are < 0.
+ * @exception KTX_INVALID_VALUE @c glType in @p textureInfo is an unrecognized
+ *                              type.
+ * @exception KTX_INVALID_OPERATION
+ *                              In @p textureInfo, numberOfFaces == 6 and
+ *                              images are either not 2D or are not square.
+ * @exception KTX_INVALID_OPERATION
+ *                              @p numImages is insufficient for the specified
+ *                              number of mipmap levels and faces.
+ * @exception KTX_INVALID_OPERATION
+ *                              The size of a provided image is different than
+ *                              that required for the specified width, height
+ *                              or depth or for the mipmap level being
+ *                              processed.
+ * @exception KTX_INVALID_OPERATION
+ *                              @c glType and @c glFormat in @p textureInfo are
+ *                              mismatched. See OpenGL 4.4 specification
+ *                              section 8.4.4 and table 8.5.
+ * @exception KTX_FILE_WRITE_ERROR
+ *                              A system error occurred while writing the file.
+ * @exception KTX_OUT_OF_MEMORY System failed to allocate sufficient memory.
+ */
+KTX_error_code
+ktxWriteKTXM(unsigned char** ppDstBytes, GLsizei* pSize,
+             const KTX_texture_info* textureInfo,
+             GLsizei bytesOfKeyValueData, const void* keyValueData,
+             GLuint numImages, KTX_image_info images[])
+{
+    struct ktxStream stream;
+    KTX_error_code rc;
+    ktx_size_t strSize;
+
+    if (ppDstBytes == NULL || pSize == NULL)
+        return KTX_INVALID_VALUE;
+
+    *ppDstBytes = NULL;
+
+    rc = ktxMemStream_construct(&stream, KTX_FALSE);
+    if (rc != KTX_SUCCESS)
+        return rc;
+
+    rc = ktxWriteKTXS(&stream, textureInfo, bytesOfKeyValueData, keyValueData,
+                      numImages, images);
+    if(rc != KTX_SUCCESS)
+    {
+        ktxMemStream_destruct(&stream);
+        return rc;
+    }
+
+    ktxMemStream_getdata(&stream, ppDstBytes);
+    stream.getsize(&stream, &strSize);
+    *pSize = (GLsizei)strSize;
+    /* This function does not free the memory pointed at by the
+     * value obtained from ktxMemStream_getdata().
+     */
+    stream.destruct(&stream);
+    return KTX_SUCCESS;
+}
+
+/**
+ * @internal
+ * @~English
+ * @brief Get the matching format for an internalformat.
+ *
+ * Adds support for deprecated legacy formats needed to create textures for
+ * use with OpenGL ES 1 & 2 to glGetFormatFromInternalFormat.
+ *
+ * @param[in] internalFormat  the internal format of the image data
+ *
+ * @return    the matching glFormat enum or GL_INVALID_VALUE if format is
+ *            unrecognized.
+ */
+GLenum
+getFormatFromInternalFormatLegacy(GLenum internalFormat)
+{
+    switch (internalFormat) {
+      case GL_LUMINANCE8:
+      case GL_LUMINANCE16:
+        return GL_LUMINANCE;
+            
+      case GL_ALPHA8:
+      case GL_ALPHA16:
+        return GL_ALPHA;
+            
+      case GL_LUMINANCE8_ALPHA8:
+      case GL_LUMINANCE16_ALPHA16:
+        return GL_LUMINANCE_ALPHA;
+
+      default:
+        return glGetFormatFromInternalFormat(internalFormat);
+    }
+}
+
+/**
+ * @internal
+ * @~English
+ * @brief Get the GL data type for an internalformat.
+ *
+ * Adds support for deprecated legacy formats needed to create textures for
+ * use with OpenGL ES 1 & 2 to glGetTypeFromInternalFormat.
+ *
+ * @param[in] internalFormat  the internal format of the image data
+ *
+ * @return    the matching glFormat enum or GL_INVALID_VALUE if format is
+ *            unrecognized.
+ */
+GLenum
+getTypeFromInternalFormatLegacy(GLenum internalFormat)
+{
+    switch (internalFormat) {
+      case GL_LUMINANCE8:
+      case GL_ALPHA8:
+        return GL_UNSIGNED_BYTE;
+            
+      case GL_LUMINANCE16:
+      case GL_ALPHA16:
+      case GL_LUMINANCE8_ALPHA8:
+        return GL_UNSIGNED_SHORT;
+            
+      case GL_LUMINANCE16_ALPHA16:
+        return GL_UNSIGNED_INT;
+            
+      default:
+        return glGetTypeFromInternalFormat(internalFormat);
+    }
+}
+        
+/**
+ * @internal
+ * @~English
+ * @brief Get size information for an internalformat.
+ *
+ * Adds support for deprecated legacy formats needed to create textures for
+ * use with OpenGL ES 1 & 2 to glGetTypeFromInternalFormat.
+ *
+ * @param[in] internalFormat  the internal format of the image data
+ * @param[in,out] formatSize  pointer to a formatSize struct in which the
+ *                            information is returned.
+ */
+void
+getFormatSizeLegacy(GLenum internalFormat, GlFormatSize* pFormatSize)
+{
+    switch (internalFormat) {
+      case GL_LUMINANCE8:
+      case GL_ALPHA8:
+        pFormatSize->flags = 0;
+        pFormatSize->paletteSizeInBits = 0;
+        pFormatSize->blockSizeInBits = 1 * 8;
+        pFormatSize->blockWidth = 1;
+        pFormatSize->blockHeight = 1;
+        pFormatSize->blockDepth = 1;
+        break;
+
+      case GL_LUMINANCE16:
+      case GL_ALPHA16:
+      case GL_LUMINANCE8_ALPHA8:
+        pFormatSize->flags = 0;
+        pFormatSize->paletteSizeInBits = 0;
+        pFormatSize->blockSizeInBits = 2 * 8;
+        pFormatSize->blockWidth = 1;
+        pFormatSize->blockHeight = 1;
+        pFormatSize->blockDepth = 1;
+        break;
+
+      case GL_LUMINANCE16_ALPHA16:
+        pFormatSize->flags = 0;
+        pFormatSize->paletteSizeInBits = 0;
+        pFormatSize->blockSizeInBits = 4 * 8;
+        pFormatSize->blockWidth = 1;
+        pFormatSize->blockHeight = 1;
+        pFormatSize->blockDepth = 1;
+        break;
+
+      default:
+        glGetFormatSize(internalFormat, pFormatSize);
+    }
+}
diff --git a/external/Vulkan/external/ktx/other_include/KHR/khrplatform.h b/external/Vulkan/external/ktx/other_include/KHR/khrplatform.h
new file mode 100644
index 0000000000000000000000000000000000000000..c9e6f17d3434177459142f05e81285bd68103535
--- /dev/null
+++ b/external/Vulkan/external/ktx/other_include/KHR/khrplatform.h
@@ -0,0 +1,282 @@
+#ifndef __khrplatform_h_
+#define __khrplatform_h_
+
+/*
+** Copyright (c) 2008-2009 The Khronos Group Inc.
+**
+** Permission is hereby granted, free of charge, to any person obtaining a
+** copy of this software and/or associated documentation files (the
+** "Materials"), to deal in the Materials without restriction, including
+** without limitation the rights to use, copy, modify, merge, publish,
+** distribute, sublicense, and/or sell copies of the Materials, and to
+** permit persons to whom the Materials are furnished to do so, subject to
+** the following conditions:
+**
+** The above copyright notice and this permission notice shall be included
+** in all copies or substantial portions of the Materials.
+**
+** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+*/
+
+/* Khronos platform-specific types and definitions.
+ *
+ * $Revision: 23298 $ on $Date: 2013-09-30 17:07:13 -0700 (Mon, 30 Sep 2013) $
+ *
+ * Adopters may modify this file to suit their platform. Adopters are
+ * encouraged to submit platform specific modifications to the Khronos
+ * group so that they can be included in future versions of this file.
+ * Please submit changes by sending them to the public Khronos Bugzilla
+ * (http://khronos.org/bugzilla) by filing a bug against product
+ * "Khronos (general)" component "Registry".
+ *
+ * A predefined template which fills in some of the bug fields can be
+ * reached using http://tinyurl.com/khrplatform-h-bugreport, but you
+ * must create a Bugzilla login first.
+ *
+ *
+ * See the Implementer's Guidelines for information about where this file
+ * should be located on your system and for more details of its use:
+ *    http://www.khronos.org/registry/implementers_guide.pdf
+ *
+ * This file should be included as
+ *        #include <KHR/khrplatform.h>
+ * by Khronos client API header files that use its types and defines.
+ *
+ * The types in khrplatform.h should only be used to define API-specific types.
+ *
+ * Types defined in khrplatform.h:
+ *    khronos_int8_t              signed   8  bit
+ *    khronos_uint8_t             unsigned 8  bit
+ *    khronos_int16_t             signed   16 bit
+ *    khronos_uint16_t            unsigned 16 bit
+ *    khronos_int32_t             signed   32 bit
+ *    khronos_uint32_t            unsigned 32 bit
+ *    khronos_int64_t             signed   64 bit
+ *    khronos_uint64_t            unsigned 64 bit
+ *    khronos_intptr_t            signed   same number of bits as a pointer
+ *    khronos_uintptr_t           unsigned same number of bits as a pointer
+ *    khronos_ssize_t             signed   size
+ *    khronos_usize_t             unsigned size
+ *    khronos_float_t             signed   32 bit floating point
+ *    khronos_time_ns_t           unsigned 64 bit time in nanoseconds
+ *    khronos_utime_nanoseconds_t unsigned time interval or absolute time in
+ *                                         nanoseconds
+ *    khronos_stime_nanoseconds_t signed time interval in nanoseconds
+ *    khronos_boolean_enum_t      enumerated boolean type. This should
+ *      only be used as a base type when a client API's boolean type is
+ *      an enum. Client APIs which use an integer or other type for
+ *      booleans cannot use this as the base type for their boolean.
+ *
+ * Tokens defined in khrplatform.h:
+ *
+ *    KHRONOS_FALSE, KHRONOS_TRUE Enumerated boolean false/true values.
+ *
+ *    KHRONOS_SUPPORT_INT64 is 1 if 64 bit integers are supported; otherwise 0.
+ *    KHRONOS_SUPPORT_FLOAT is 1 if floats are supported; otherwise 0.
+ *
+ * Calling convention macros defined in this file:
+ *    KHRONOS_APICALL
+ *    KHRONOS_APIENTRY
+ *    KHRONOS_APIATTRIBUTES
+ *
+ * These may be used in function prototypes as:
+ *
+ *      KHRONOS_APICALL void KHRONOS_APIENTRY funcname(
+ *                                  int arg1,
+ *                                  int arg2) KHRONOS_APIATTRIBUTES;
+ */
+
+/*-------------------------------------------------------------------------
+ * Definition of KHRONOS_APICALL
+ *-------------------------------------------------------------------------
+ * This precedes the return type of the function in the function prototype.
+ */
+#if defined(_WIN32) && !defined(__SCITECH_SNAP__)
+#   define KHRONOS_APICALL __declspec(dllimport)
+#elif defined (__SYMBIAN32__)
+#   define KHRONOS_APICALL IMPORT_C
+#else
+#   define KHRONOS_APICALL
+#endif
+
+/*-------------------------------------------------------------------------
+ * Definition of KHRONOS_APIENTRY
+ *-------------------------------------------------------------------------
+ * This follows the return type of the function  and precedes the function
+ * name in the function prototype.
+ */
+#if defined(_WIN32) && !defined(_WIN32_WCE) && !defined(__SCITECH_SNAP__)
+    /* Win32 but not WinCE */
+#   define KHRONOS_APIENTRY __stdcall
+#else
+#   define KHRONOS_APIENTRY
+#endif
+
+/*-------------------------------------------------------------------------
+ * Definition of KHRONOS_APIATTRIBUTES
+ *-------------------------------------------------------------------------
+ * This follows the closing parenthesis of the function prototype arguments.
+ */
+#if defined (__ARMCC_2__)
+#define KHRONOS_APIATTRIBUTES __softfp
+#else
+#define KHRONOS_APIATTRIBUTES
+#endif
+
+/*-------------------------------------------------------------------------
+ * basic type definitions
+ *-----------------------------------------------------------------------*/
+#if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__GNUC__) || defined(__SCO__) || defined(__USLC__)
+
+
+/*
+ * Using <stdint.h>
+ */
+#include <stdint.h>
+typedef int32_t                 khronos_int32_t;
+typedef uint32_t                khronos_uint32_t;
+typedef int64_t                 khronos_int64_t;
+typedef uint64_t                khronos_uint64_t;
+#define KHRONOS_SUPPORT_INT64   1
+#define KHRONOS_SUPPORT_FLOAT   1
+
+#elif defined(__VMS ) || defined(__sgi)
+
+/*
+ * Using <inttypes.h>
+ */
+#include <inttypes.h>
+typedef int32_t                 khronos_int32_t;
+typedef uint32_t                khronos_uint32_t;
+typedef int64_t                 khronos_int64_t;
+typedef uint64_t                khronos_uint64_t;
+#define KHRONOS_SUPPORT_INT64   1
+#define KHRONOS_SUPPORT_FLOAT   1
+
+#elif defined(_WIN32) && !defined(__SCITECH_SNAP__)
+
+/*
+ * Win32
+ */
+typedef __int32                 khronos_int32_t;
+typedef unsigned __int32        khronos_uint32_t;
+typedef __int64                 khronos_int64_t;
+typedef unsigned __int64        khronos_uint64_t;
+#define KHRONOS_SUPPORT_INT64   1
+#define KHRONOS_SUPPORT_FLOAT   1
+
+#elif defined(__sun__) || defined(__digital__)
+
+/*
+ * Sun or Digital
+ */
+typedef int                     khronos_int32_t;
+typedef unsigned int            khronos_uint32_t;
+#if defined(__arch64__) || defined(_LP64)
+typedef long int                khronos_int64_t;
+typedef unsigned long int       khronos_uint64_t;
+#else
+typedef long long int           khronos_int64_t;
+typedef unsigned long long int  khronos_uint64_t;
+#endif /* __arch64__ */
+#define KHRONOS_SUPPORT_INT64   1
+#define KHRONOS_SUPPORT_FLOAT   1
+
+#elif 0
+
+/*
+ * Hypothetical platform with no float or int64 support
+ */
+typedef int                     khronos_int32_t;
+typedef unsigned int            khronos_uint32_t;
+#define KHRONOS_SUPPORT_INT64   0
+#define KHRONOS_SUPPORT_FLOAT   0
+
+#else
+
+/*
+ * Generic fallback
+ */
+#include <stdint.h>
+typedef int32_t                 khronos_int32_t;
+typedef uint32_t                khronos_uint32_t;
+typedef int64_t                 khronos_int64_t;
+typedef uint64_t                khronos_uint64_t;
+#define KHRONOS_SUPPORT_INT64   1
+#define KHRONOS_SUPPORT_FLOAT   1
+
+#endif
+
+
+/*
+ * Types that are (so far) the same on all platforms
+ */
+typedef signed   char          khronos_int8_t;
+typedef unsigned char          khronos_uint8_t;
+typedef signed   short int     khronos_int16_t;
+typedef unsigned short int     khronos_uint16_t;
+
+/*
+ * Types that differ between LLP64 and LP64 architectures - in LLP64, 
+ * pointers are 64 bits, but 'long' is still 32 bits. Win64 appears
+ * to be the only LLP64 architecture in current use.
+ */
+#ifdef _WIN64
+typedef signed   long long int khronos_intptr_t;
+typedef unsigned long long int khronos_uintptr_t;
+typedef signed   long long int khronos_ssize_t;
+typedef unsigned long long int khronos_usize_t;
+#else
+typedef signed   long  int     khronos_intptr_t;
+typedef unsigned long  int     khronos_uintptr_t;
+typedef signed   long  int     khronos_ssize_t;
+typedef unsigned long  int     khronos_usize_t;
+#endif
+
+#if KHRONOS_SUPPORT_FLOAT
+/*
+ * Float type
+ */
+typedef          float         khronos_float_t;
+#endif
+
+#if KHRONOS_SUPPORT_INT64
+/* Time types
+ *
+ * These types can be used to represent a time interval in nanoseconds or
+ * an absolute Unadjusted System Time.  Unadjusted System Time is the number
+ * of nanoseconds since some arbitrary system event (e.g. since the last
+ * time the system booted).  The Unadjusted System Time is an unsigned
+ * 64 bit value that wraps back to 0 every 584 years.  Time intervals
+ * may be either signed or unsigned.
+ */
+typedef khronos_uint64_t       khronos_utime_nanoseconds_t;
+typedef khronos_int64_t        khronos_stime_nanoseconds_t;
+#endif
+
+/*
+ * Dummy value used to pad enum types to 32 bits.
+ */
+#ifndef KHRONOS_MAX_ENUM
+#define KHRONOS_MAX_ENUM 0x7FFFFFFF
+#endif
+
+/*
+ * Enumerated boolean type
+ *
+ * Values other than zero should be considered to be true.  Therefore
+ * comparisons should not be made against KHRONOS_TRUE.
+ */
+typedef enum {
+    KHRONOS_FALSE = 0,
+    KHRONOS_TRUE  = 1,
+    KHRONOS_BOOLEAN_ENUM_FORCE_SIZE = KHRONOS_MAX_ENUM
+} khronos_boolean_enum_t;
+
+#endif /* __khrplatform_h_ */
diff --git a/external/Vulkan/external/stb/stb_font_consolas_24_latin1.inl b/external/Vulkan/external/stb/stb_font_consolas_24_latin1.inl
new file mode 100644
index 0000000000000000000000000000000000000000..12eedd2f43b49506f48ea3e709cd61bb9f9a766e
--- /dev/null
+++ b/external/Vulkan/external/stb/stb_font_consolas_24_latin1.inl
@@ -0,0 +1,734 @@
+// Font generated by stb_font_inl_generator.c (4/1 bpp)
+//
+// Following instructions show how to use the only included font, whatever it is, in
+// a generic way so you can replace it with any other font by changing the include.
+// To use multiple fonts, replace STB_SOMEFONT_* below with STB_FONT_consolas_24_latin1_*,
+// and separately install each font. Note that the CREATE function call has a
+// totally different name; it's just 'stb_font_consolas_24_latin1'.
+//
+/* // Example usage:
+
+static stb_fontchar fontdata[STB_SOMEFONT_NUM_CHARS];
+
+static void init(void)
+{
+    // optionally replace both STB_SOMEFONT_BITMAP_HEIGHT with STB_SOMEFONT_BITMAP_HEIGHT_POW2
+    static unsigned char fontpixels[STB_SOMEFONT_BITMAP_HEIGHT][STB_SOMEFONT_BITMAP_WIDTH];
+    STB_SOMEFONT_CREATE(fontdata, fontpixels, STB_SOMEFONT_BITMAP_HEIGHT);
+    ... create texture ...
+    // for best results rendering 1:1 pixels texels, use nearest-neighbor sampling
+    // if allowed to scale up, use bilerp
+}
+
+// This function positions characters on integer coordinates, and assumes 1:1 texels to pixels
+// Appropriate if nearest-neighbor sampling is used
+static void draw_string_integer(int x, int y, char *str) // draw with top-left point x,y
+{
+    ... use texture ...
+    ... turn on alpha blending and gamma-correct alpha blending ...
+    glBegin(GL_QUADS);
+    while (*str) {
+        int char_codepoint = *str++;
+        stb_fontchar *cd = &fontdata[char_codepoint - STB_SOMEFONT_FIRST_CHAR];
+        glTexCoord2f(cd->s0, cd->t0); glVertex2i(x + cd->x0, y + cd->y0);
+        glTexCoord2f(cd->s1, cd->t0); glVertex2i(x + cd->x1, y + cd->y0);
+        glTexCoord2f(cd->s1, cd->t1); glVertex2i(x + cd->x1, y + cd->y1);
+        glTexCoord2f(cd->s0, cd->t1); glVertex2i(x + cd->x0, y + cd->y1);
+        // if bilerping, in D3D9 you'll need a half-pixel offset here for 1:1 to behave correct
+        x += cd->advance_int;
+    }
+    glEnd();
+}
+
+// This function positions characters on float coordinates, and doesn't require 1:1 texels to pixels
+// Appropriate if bilinear filtering is used
+static void draw_string_float(float x, float y, char *str) // draw with top-left point x,y
+{
+    ... use texture ...
+    ... turn on alpha blending and gamma-correct alpha blending ...
+    glBegin(GL_QUADS);
+    while (*str) {
+        int char_codepoint = *str++;
+        stb_fontchar *cd = &fontdata[char_codepoint - STB_SOMEFONT_FIRST_CHAR];
+        glTexCoord2f(cd->s0f, cd->t0f); glVertex2f(x + cd->x0f, y + cd->y0f);
+        glTexCoord2f(cd->s1f, cd->t0f); glVertex2f(x + cd->x1f, y + cd->y0f);
+        glTexCoord2f(cd->s1f, cd->t1f); glVertex2f(x + cd->x1f, y + cd->y1f);
+        glTexCoord2f(cd->s0f, cd->t1f); glVertex2f(x + cd->x0f, y + cd->y1f);
+        // if bilerping, in D3D9 you'll need a half-pixel offset here for 1:1 to behave correct
+        x += cd->advance;
+    }
+    glEnd();
+}
+*/
+
+#pragma once
+
+#ifndef STB_FONTCHAR__TYPEDEF
+#define STB_FONTCHAR__TYPEDEF
+typedef struct
+{
+    // coordinates if using integer positioning
+    float s0,t0,s1,t1;
+    signed short x0,y0,x1,y1;
+    int   advance_int;
+    // coordinates if using floating positioning
+    float s0f,t0f,s1f,t1f;
+    float x0f,y0f,x1f,y1f;
+    float advance;
+} stb_fontchar;
+#endif
+
+#define STB_FONT_consolas_24_latin1_BITMAP_WIDTH         256
+#define STB_FONT_consolas_24_latin1_BITMAP_HEIGHT        170
+#define STB_FONT_consolas_24_latin1_BITMAP_HEIGHT_POW2   256
+
+#define STB_FONT_consolas_24_latin1_FIRST_CHAR            32
+#define STB_FONT_consolas_24_latin1_NUM_CHARS            224
+
+#define STB_FONT_consolas_24_latin1_LINE_SPACING          16
+
+static unsigned int stb__consolas_24_latin1_pixels[]={
+    0x08262131,0xff904400,0x3ffe1fff,0x3b2206ff,0x2007913f,0x0000defa,
+    0x64c00f32,0x0de5c402,0x00a614c0,0x4002ae62,0x98014c19,0x01aa881c,
+    0x00d54400,0xb880154c,0x8330020b,0x1e980029,0xaa7d5dd4,0x2001d94f,
+    0x3332a6e8,0x999ff0ff,0x37ffa207,0x2600df12,0x8000fffd,0x3fa005fd,
+    0xfdff700f,0x8ffc409f,0x3ea02ff8,0x200dffff,0x0bfe0ff8,0x7d407ee0,
+    0xfd10001f,0x9fff5007,0xcffff880,0x1ff104eb,0x320017fc,0x7d77e40f,
+    0x17ee9f54,0xfd027ec0,0x7dc01fe1,0x0037c40d,0xb0017f22,0xffe8007f,
+    0x7dc3fd80,0x741ff104,0x59ff701f,0x7401dfd7,0x2003f60e,0x3fa200fc,
+    0x0ff44001,0x7dc6fdc0,0x32236604,0x3ba00eff,0x31003f60,0xdf90dd57,
+    0xd93ea9f5,0x037e403f,0x803fc3fa,0x06f882fd,0x2006f980,0xa8800098,
+    0xf903fb81,0x88040401,0x1ff441ff,0x0fb00000,0x00000000,0x00020000,
+    0xffffa800,0xfadfb86f,0x7fc49f54,0x803ff301,0x200ff0fe,0x06f880fe,
+    0x0000ff00,0x05f88000,0x40000fe6,0x0ff504fc,0x81540153,0x100affb9,
+    0x22001573,0x31000ab9,0x26200157,0x731000ab,0xcffb8015,0x7dc4ffda,
+    0x89f54fad,0x3fe80ff9,0x0ff0fe80,0x3e203fa0,0x807ddb36,0x01dd107f,
+    0xdddb076c,0x1fb8bddd,0x1dd12fc0,0x3ff076c0,0x3f60ffc0,0xe98ff102,
+    0xa84fffff,0x00dfffff,0x37ffffea,0x3fffea00,0x3fea00df,0x2a00dfff,
+    0x80dfffff,0x3bb61ff8,0x3eb3ea3f,0x7f909f54,0xfd005fa8,0x7f401fe1,
+    0xffaef880,0x1fe05ffe,0xdf504fd8,0xfffffff8,0x9db33746,0x09fb1b6b,
+    0x80ff1bea,0x817ec2fd,0x33fe67f8,0x7dc3acfa,0x0efebacf,0xebacffb8,
+    0xcffb80ef,0xb80efeba,0xefebacff,0xbacffb80,0x27e40efe,0x20df59f1,
+    0x509f54fa,0x003fd8bf,0x401fe1fd,0x9fff107e,0x7407fe61,0x20ff500f,
+    0x6f9803fd,0x777ccfe2,0x7fa8db6f,0x37cc7fb0,0x5fb0ff60,0x3fe9fe20,
+    0x3ff105f3,0x7c43fe88,0x21ff441f,0x7f441ff8,0x220ffc43,0x0ffc43fe,
+    0x3ff0ffa2,0x267f97d4,0x9f54fadf,0x1ff8ff10,0x1fe1fd00,0x7c417e20,
+    0x704fb84f,0x217f405f,0xf9800ff8,0x47f47ea6,0xfd0f95f8,0x260ff885,
+    0x21fe406f,0x2ff102fd,0x207ea6f9,0x8ff504fc,0x8ff504fc,0x8ff504fc,
+    0x8ff504fc,0x8ff504fc,0x3fd3e47f,0x553eafea,0x261ff04f,0x1fd000ff,
+    0xefcc81fe,0x260ff101,0x9bfb105f,0x7d45fb81,0x64df3005,0x9f34f98f,
+    0x517ee1f2,0x407f98bf,0x817ec2fd,0x5cbf57f8,0x201ff80f,0x807fe1ff,
+    0x807fe1ff,0x807fe1ff,0x807fe1ff,0xf8df31ff,0x47e4bf65,0x203514fa,
+    0x00df52fd,0x107f87f4,0x7c403fff,0x440df306,0x7fc41ffe,0x4c00bf60,
+    0x97ddf66f,0xf10db2fa,0x2217ec1f,0x20ff407f,0x2ff102fd,0x7c1f64fb,
+    0xff17ec07,0x1fe2fd80,0x03fc5fb0,0x407f8bf6,0x4cdf32fd,0x9d4ff22f,
+    0x9f7004fa,0xfe8013ee,0x6cc40ff0,0x20df103f,0x3bf506f9,0x7c4ff601,
+    0xd9be6007,0x47f23f96,0x227fb06d,0x203ff07f,0x17ec0ff8,0x4df57f88,
+    0x406f986e,0x80df33fd,0x80df33fd,0x80df33fd,0x80df33fd,0x64ff13fd,
+    0x2a0bf60f,0x29f9004f,0x3fa005fa,0x1be00ff0,0x5fa837c4,0xfa801f90,
+    0x4c009f76,0x87edba6f,0x2a09f0fd,0x3209f76f,0xb0bf704f,0x4dfe205f,
+    0xf30bf0ff,0xf33fc80d,0xf33fc80d,0xf33fc80d,0xf33fc80d,0x3e3fc80d,
+    0x03fd1ba7,0x3f6009f5,0x7400ff13,0x3a00ff0f,0x906f880f,0x401fa07f,
+    0x001fe9ff,0xf96e9be6,0x017cdfe3,0x203fd3ff,0x7fd43ff9,0xf102fd81,
+    0x27d7fecf,0xfb01fe63,0xfb01fe65,0xfb01fe65,0xfb01fe65,0xfb01fe65,
+    0x1fc4ff45,0x9f501ff1,0xfe8bfe00,0x3e1fd001,0x101fd007,0x40df50df,
+    0xfbf9007e,0x26f9800d,0xfdb9f56d,0x7e401fb7,0x7fdc06fd,0xb02ffede,
+    0x89fe205f,0x4feefffc,0x1fe80ff1,0x1fe80ff1,0x1fe80ff1,0x1fe80ff1,
+    0x1fe80ff1,0xb87ef7f2,0x2a9f505f,0x647f984f,0x21fd002f,0x01fd007f,
+    0xfb99dff1,0x803f403f,0x4003fff8,0x6c5f26f9,0x01ffe8ef,0x401fffc4,
+    0x01dfffda,0x2fd40ff2,0x0e77ff4c,0x3fe203ff,0x7c407fe0,0x4407fe0f,
+    0x407fe0ff,0x07fe0ff8,0xff107fc4,0x807fd4fd,0x509f54fa,0x004fa8bf,
+    0x401fe1fd,0xfff880fe,0x7400deff,0x01ff6007,0x0fb9be60,0x7ec00302,
+    0x027dc007,0x4fc827dc,0x3f203f70,0xfc8bf704,0xfc8bf704,0xfc8bf704,
+    0xfc8bf704,0xf30bf704,0x07ffd9df,0x84faa7d4,0x07fc41fe,0x07f87f40,
+    0xdf101fd0,0x07f80022,0x2000ff60,0x00fe65fa,0x001fec00,0x98103fe6,
+    0x0ffcc1ff,0xff100fc8,0x440ffa87,0x07fd43ff,0xff50ffe2,0x543ff881,
+    0x1ffc40ff,0x7dc03fea,0x5401dfff,0xfc89f54f,0xd00df705,0x6c01fe1f,
+    0x006f883f,0xf5006f98,0x9fb0001f,0xa80006e8,0xfd8000ff,0xfc86fcdf,
+    0x04ffecdf,0xdff701f6,0x2e07ffd9,0x3ffeceff,0xfd9dff70,0x3bfee07f,
+    0xf703ffec,0x07ffd9df,0x5000cfec,0xfa93ea9f,0x013f600f,0x401fe1fd,
+    0x37c41efb,0x013f6600,0x33007fea,0x2e07fdc4,0xf500505f,0x7e40003f,
+    0xfd703fff,0x6e805dff,0xdfffea80,0x7fff5401,0x7ff5401d,0x7f5401df,
+    0x75401dff,0x7c01dfff,0x54fa8005,0x00bfe29f,0x775c3fd1,0xddff0ffe,
+    0x7fff409d,0x2600df12,0xf300effe,0xf7007fff,0x207fffdf,0x7fdcdffb,
+    0x07ffff30,0x80022000,0x0017e001,0x00060003,0x0018000c,0x07220030,
+    0x7d53ea00,0x26007fb4,0x3bbbae6f,0x9ddddb0e,0xd12ecb80,0x0f3a600b,
+    0x0019bd30,0x073bbb22,0x17bdb730,0x00337a60,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x4d3ea9f5,0x00154004,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000,0x20000000,0x009f54fa,
+    0x77777400,0xeeeeeeee,0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x4c000000,0x0007d33e,0x77777740,0x0eeeeeee,
+    0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x54400000,0x2a20001a,0x0154c01a,0x54400b20,
+    0xaaa98000,0x99953100,0x032e0059,0x10053066,0x22004177,0x04c801aa,
+    0x01aa8800,0x500d5440,0x51017997,0x51000035,0x0bb88035,0x00d54402,
+    0x0007fea0,0xf500ffa2,0x3e6009ff,0x3fef9803,0xfffff700,0xffffb89f,
+    0xf8804fff,0x3e0ff886,0x3ffe202f,0x5404ebcf,0x0fec01ff,0x07fd1000,
+    0x103fe880,0x05fffffd,0x80007fea,0x7c403fe8,0x04ebcfff,0x88003ff5,
+    0x3a2001fe,0x46fdc01f,0x7dc404fb,0x6baec00b,0xabcffd80,0x3fff24ec,
+    0x804fb9df,0x41dd03fb,0x236600fd,0x800effc8,0x3e601fe8,0x3fd10005,
+    0x01fe8800,0x008833f6,0x20007fa2,0xd9801fe8,0x00effc88,0x00007fa2,
+    0x00000000,0x9fffffd5,0x036cf640,0x98407bee,0xf54fffff,0x001fd009,
+    0x00020000,0x0007f400,0x44000000,0x0000007f,0x00200000,0x40153000,
+    0xa802a62a,0x2a802a62,0x33fb7ff2,0x3bfa204d,0x007fe201,0x53fffff2,
+    0x27d404fa,0x40015540,0x553002aa,0x50555555,0x01aa807f,0x554c1544,
+    0xf82aaaaa,0x2aa8002f,0x802aa800,0x50aa02a9,0x55555555,0xf102fd81,
+    0xf8817ecf,0x7c40bf67,0x1f61ff37,0x5c02aa80,0xfff9005f,0x013ea9ff,
+    0xff8803fb,0x3fe2002f,0xfffc802f,0xdf07ffff,0x6406fb80,0xffffc85f,
+    0xffb87fff,0x3ffe2003,0x3ffe2002,0x41ffe402,0x7fffc6f8,0x6c0fffff,
+    0x6cff102f,0x6cff102f,0x2eff102f,0x4401ba5f,0x3f202fff,0xffff7003,
+    0x8813ea9f,0xfffa805f,0x3ffea004,0xaacfc804,0x5f902aaa,0xf103ff80,
+    0x559f901f,0xff985555,0xfa802eff,0x3ea004ff,0x7fe404ff,0x5546f886,
+    0x0aaadfda,0x7f8817ec,0x3fc40bf6,0x5fe205fb,0x4017e7fa,0x3604fffa,
+    0xfff3002f,0x813ea9ff,0xafd802fc,0x2bf6007f,0x03fc807f,0x2a02fc40,
+    0x04fd80ff,0x3fa007f9,0x404ffd89,0x2007fafd,0x6407fafd,0x37c42fef,
+    0xfd809f70,0x7ecff102,0x7ecff102,0x3e2ff102,0xb004faef,0x7f40ff5f,
+    0x3fff2002,0x7c09f54f,0xfd7f8807,0x3aff1003,0x01fe401f,0x3a00fec0,
+    0x807fcc4f,0x5f9803fc,0xf8827fd4,0xf1003fd7,0xfc807faf,0x037c46fb,
+    0x2fd809f7,0x17ecff10,0x0bf67f88,0xffd33fc4,0x7f88019f,0x0bfa03fd,
+    0x29ffd500,0x07f504fa,0x13ee9f50,0x27dd3ea0,0x2000ff20,0x7fcc04fa,
+    0xfc80ff60,0x886f9803,0x74fa81ff,0x29f5009f,0x2bf204fb,0x81be22fd,
+    0x17ec04fb,0x0bf67f88,0x05fb3fc4,0x3fae1fe2,0x53ea03ff,0x07fb04fb,
+    0x4fa84c00,0xfb001fd0,0x3601fe65,0xc80ff32f,0x1fd0003f,0xdf34fd80,
+    0xf003fc80,0xb07f905f,0x201fe65f,0x40ff32fd,0x226faafc,0x013ee06f,
+    0x9fe205fb,0x4ff102fd,0x0ff102fd,0x417ff7ee,0x20ff32fd,0x500006fb,
+    0x00bf309f,0x01fe8ff1,0x03fd1fe2,0x777777e4,0x01fdc04e,0x0bf63fe2,
+    0xdddddf90,0x43ffa89d,0x23fc43fb,0x1fe201fe,0x4bf203fd,0x40df11fe,
+    0x17ec04fb,0x0bf67f88,0x05fb3fc4,0x9be41fe2,0x23fc43ff,0x2ff881fe,
+    0x84fa8000,0x5fa801fc,0xbf5027e4,0x3f204fc8,0x06ffffff,0x7e4037c4,
+    0xffc806fe,0xa86fffff,0x0ff89eff,0x13f22fd4,0x27e45fa8,0x2bf52fc8,
+    0x13ee06f8,0x3e205fb0,0x7c40bf67,0x7c40bf67,0x2fdcdb07,0x09f917ea,
+    0x0620bff2,0x3e227d40,0x985fb006,0x30bf607f,0x01fe40ff,0x8803f900,
+    0xfc801fff,0x7fec4003,0x42fd82ff,0x0bf607f9,0x8bf20ff3,0x206f89ff,
+    0x17ec04fb,0x0bf67f88,0x05fb3fc4,0xb2f41fe2,0x4c2fd89f,0x3fff607f,
+    0x5004fedd,0x802fb89f,0x99999ff8,0x9ff881ff,0x01ff9999,0x4c0007f9,
+    0x05fb805f,0x20007f90,0xff886fe9,0x1ff99999,0x9999ff88,0x2fc81ff9,
+    0x81be33ee,0x1fe404fb,0x0ff25fa8,0x07f92fd4,0x9f04d7ea,0x7c43ff71,
+    0xff99999f,0x3fffaa01,0x7f9002ce,0xff5007e8,0x9fffffff,0xffffff50,
+    0x3f209fff,0x07f40003,0x64013ee0,0x3a20003f,0x7fffd42f,0xa84fffff,
+    0xffffffff,0x222fc84f,0x2e06f9ff,0x827dc04f,0x413ee4fc,0x413ee4fc,
+    0xfdffb4fc,0xfa87ffff,0xffffffff,0x0077c404,0x9f517f40,0x3337f600,
+    0xd86fdccc,0xdcccccdf,0x007f906f,0x5c04fa80,0x07f9004f,0x6c2fe800,
+    0xdcccccdf,0xccdfd86f,0xc86fdccc,0x37e7e42f,0xf9809f70,0x30ffcc1f,
+    0x1ff983ff,0xff307fe6,0x3fffb6a3,0xcdfd80ce,0x06fdcccc,0x37601fd4,
+    0x6c6fd989,0x0ff8800f,0xff887fe0,0x3207fe00,0x7f80003f,0xc8027dc0,
+    0x4c15003f,0x3fe20ffb,0xf887fe00,0x907fe00f,0x6fff885f,0x32013ee0,
+    0x4ffecdff,0xfecdffc8,0xcdffc84f,0x3f704ffe,0xf007fc40,0x00fe403f,
+    0xdfffffb1,0xa802fcc3,0x527ec05f,0x84fd80bf,0xeeeeeefc,0x80bee006,
+    0xdf9004fb,0xf8dddddd,0x41ffffff,0x27ec05fa,0x4fd80bf5,0x7fe417e4,
+    0x7ff776c6,0xfd700eee,0x75c05dff,0x2e02efff,0x202efffe,0x17ea00fc,
+    0x14c09fb0,0x82cdba80,0x7fb001ca,0x3f66fa80,0x6437d403,0x7fffffff,
+    0xb80f2200,0xfff9004f,0xcb8fffff,0x7fb02cdd,0x3f66fa80,0x3237d403,
+    0x46ff882f,0xffffffff,0x8001800f,0x90018001,0x403fd80b,0x000006fa,
+    0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x0d544000,0x2600aa60,0x54c014c1,0x14c19802,0x400154c0,
+    0xcb9802c9,0x0332e02c,0x30a60f22,0x00332a05,0x4000b260,0x0cca83cc,
+    0x01e64000,0x2e200b26,0x6644020b,0x00cca801,0xe8803ca8,0x7ffd403f,
+    0xf83fe204,0x3ffea02f,0xf83fe204,0x3ffea02f,0x3f7ee004,0x3ffff604,
+    0x7f7ec0ef,0x220fe40f,0x05ff11ff,0xa8003bee,0x6c003fff,0x03bee05f,
+    0x202fec00,0x2203fffa,0x4eacffff,0x9d95ff70,0x9003bee0,0x1fe880bf,
+    0x7dc6fdc0,0xfd83ba04,0xf71bf700,0xfb077409,0x2e37ee01,0x2a7d004f,
+    0x261bf907,0x54fea4fd,0x2213ea3f,0x807fa0ff,0xfa800ef9,0xb003fc8d,
+    0x1df3007f,0x403fd800,0x03fc8dfa,0xdffb31d3,0xfffdb881,0x3be603ef,
+    0x0017f200,0x00000000,0x00000000,0x7b8dd800,0x6f981ff0,0x8a7c47ee,
+    0x020200ee,0x22002620,0x4c400cc1,0x00262000,0x18800988,0x011000cc,
+    0x0ffb7fe2,0xf9004c40,0x5555550b,0xaa981555,0x982aaaaa,0x2aaaaaaa,
+    0xaaaaaaa8,0x555540aa,0x200aaaaa,0x7cc002aa,0x86f882ff,0x44bee5fa,
+    0x0005f94f,0x00000000,0x00000000,0x00000000,0x001ff982,0xff0bf900,
+    0x1fffffff,0xffffffc8,0xffffc87f,0xfff87fff,0x40ffffff,0xffffffff,
+    0x5fff100f,0x26013000,0x30ffd45f,0xf33fb3bf,0x9dfb7009,0xcefdb801,
+    0x677edc00,0x677edc00,0xdfdb7100,0x3b6e203b,0xb7101def,0x2203bdfd,
+    0x01defedb,0x01bffb2a,0x7039dfb7,0xfb5550bf,0xfc81555b,0x82aaaaac,
+    0xaaaaacfc,0xdfdaaa82,0x55540aaa,0x00aaadfd,0x1009fff5,0x83bdfdb7,
+    0x07bee5f9,0x7f4fffee,0x76f7ec00,0xbdfb02ff,0x3f605ffd,0xb02ffede,
+    0x05ffdbdf,0xfffddff7,0xfddff705,0xdff705ff,0xf705fffd,0x05fffddf,
+    0x5ffddffb,0xffdffd30,0x404fb87f,0x1fe404fb,0x0007f900,0xfb8013ee,
+    0xff5fb004,0xfddff700,0x8afcc5ff,0x426200ff,0x27e402fc,0x9f903fea,
+    0x7e40ffa8,0x3207fd44,0x207fd44f,0x43fdc419,0x43fdc419,0x43fdc419,
+    0x23fdc419,0x1bea0dfc,0x3ff513fa,0x3ee027dc,0x001fe404,0x2e0007f9,
+    0x13ee004f,0x0ff5fe20,0xff710660,0x07f8afcc,0xf1017e60,0xf88ff20d,
+    0x7c47f906,0x7c47f906,0x4007f906,0x3fe000ff,0x003fe000,0x0df30ff8,
+    0x05fa87fa,0x027dcbf9,0x7f9013ee,0x001fe400,0x2e004fb8,0x74fa804f,
+    0x7fc0009f,0x27fcbf30,0x5400fe80,0x549f504f,0x549f504f,0x549f504f,
+    0x009f504f,0xfb000fec,0x00fec003,0x0fee3fb0,0x05f927e4,0x04fb9be2,
+    0x3f2027dc,0x00ff2003,0x70027dc0,0x25fb009f,0x360007f9,0x7ccbf31f,
+    0x4bee00df,0x77dc1dec,0x5feeeeee,0x3bbbbbee,0x3bee5fee,0x5feeeeee,
+    0x3bbbbbee,0xec985fee,0x981ffffe,0x1ffffeec,0xfffeec98,0xfeec981f,
+    0x05fb1fff,0x01fe97ea,0x403fa9fe,0x77e404fb,0xc84eeeee,0x4eeeeeef,
+    0x70027dc0,0x47f8809f,0x764c01fe,0xf31ffffe,0x80efe98b,0xffbfd5f8,
+    0x77777e43,0x3f24eeee,0xeeeeeeee,0x3bbbbf24,0x3f24eeee,0xeeeeeeee,
+    0x6677fdc4,0x7fdc1ffc,0x41ffccce,0xfccceffb,0x677fdc1f,0x3fb1ffcc,
+    0x1fe97ea0,0x03fa9fe0,0x3f2027dc,0x86ffffff,0xfffffffc,0x0027dc06,
+    0x5fa809f7,0xff7027e4,0x23ff999d,0x4fe885f9,0x33f98fe8,0x002fdc9f,
+    0x7dc00bf7,0x017ee005,0xfd81ff88,0x3607fe21,0x207fe21f,0x07fe21fd,
+    0x817e47f6,0x40bf64fb,0x007266f8,0x3fc809f7,0x000ff200,0xf70027dc,
+    0x4c2fd809,0x03ff107f,0x417e63fb,0xb9fdc6f9,0xffa97e1f,0x01ff5000,
+    0x4003fea0,0xf5000ffa,0xfa87fa0b,0x7d43fd05,0x7d43fd05,0x3ee3fd05,
+    0x7e45fb05,0x000bf704,0x3fc809f7,0x000ff200,0xf70027dc,0x4cffc409,
+    0xa81ff999,0x263fd05f,0x44df305f,0x7c4bea6f,0x80067f44,0xfd000cfe,
+    0x33fa0019,0x446fa800,0x1bea1ffd,0x7d43ffb1,0x50ffec46,0x1ffd88df,
+    0x7fa85ff1,0x3e60ffc4,0x700faa1f,0x03fc809f,0x4000ff20,0x3ee004fb,
+    0x3fffea04,0xa84fffff,0x8ffec46f,0xfb9935f9,0xf10fec5f,0xf983fb7d,
+    0x0eccbdff,0x65efffcc,0x7ffcc0ec,0x4c0eccbd,0xeccbdfff,0x373bfe20,
+    0x3e21feff,0xfeffdcef,0x373bfe21,0x3e21feff,0xfeffdcef,0x3737fee1,
+    0xdffb82ff,0x7fc3ffdc,0x2027dc07,0x3f2003fc,0x09f70003,0xfb027dc0,
+    0xfb99999b,0xb9dff10d,0x3e63fdff,0x45dfff35,0xfffa83fa,0xffffb102,
+    0xffb101df,0xb101dfff,0x01dfffff,0xdfffffb1,0xdfffe981,0x7f4c1fc8,
+    0x41fc8dff,0xc8dfffe9,0x7fff4c1f,0x7541fc8d,0x2a01efff,0xc81efffe,
+    0x027dc05f,0xfc800ff2,0x09f70003,0xf8827dc0,0x207fe00f,0xc8dfffe9,
+    0x0002201f,0x02620008,0x20009880,0x26200098,0x40008800,0x00440008,
+    0x06000220,0x40600600,0xeeffeeed,0x7777e40e,0xefc86eee,0xd86eeeee,
+    0xeeeffeee,0x7ff776c0,0x0bf50eee,0x02204fd8,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0xffffff80,0x7fe40fff,0xc87fffff,
+    0x7fffffff,0xfffffff8,0x7fffc0ff,0xfb0fffff,0x006fa807,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+    0x40f32000,0xca814c29,0x055cc00c,0x203cc800,0x98014c29,0x0bb8802c,
+    0x40044002,0x298003c8,0x0310014c,0x8000b260,0x157300cb,0x76c17a20,
+    0x00f32000,0x32a0aa62,0x027d400c,0xf882fec0,0x205ff11f,0x3ee00efb,
+    0x36001eff,0x47fe205f,0x7d402ff8,0x3fe203ff,0x204eacff,0x203ffffa,
+    0x7c4006f8,0x005ff11f,0x3fea01f6,0x3fb0003f,0x4fffff98,0x1fd06f88,
+    0x8817f600,0x706ffffb,0x7ff401df,0x80ff6000,0x07fa0ff8,0x5100ef98,
+    0xb005ffd7,0x0ff8807f,0xdfa807fa,0x1d303fc8,0x201dffb3,0x2ffdcffa,
+    0x8800df10,0x007fa0ff,0x37ea02fc,0xd8003fc8,0x26ffea1f,0x37c44feb,
+    0xfd800fe8,0x37ffe603,0x3be602ac,0x400bf700,0x10100098,0x20013100,
+    0x26200ffb,0x00202000,0x20019831,0x717ec008,0x01be20bf,0xb7004040,
+    0x18807fff,0x7ec000cc,0xff10bfa1,0xfe837c41,0x1004c400,0x310007ff,
+    0x00000001,0x20000000,0x000004fd,0x00000000,0x6f983fe0,0x0000df10,
+    0xfeffe980,0x000001ff,0x17ea3fb0,0x0df127dc,0x200003fa,0x000003fc,
+    0x05e88026,0x82f443d9,0x417a21ec,0x17ee01ec,0x00e77edc,0x80e77edc,
+    0x03d905e8,0x8073bf6e,0x413ee2fe,0x7ddb36f8,0x3bfb6e20,0xf13fe81d,
+    0x6dc03ffd,0x65401cef,0xf91ffdee,0x44dfd305,0x701fd06f,0x7c0bdddd,
+    0x7775c007,0x407f305e,0x45fb06f8,0x45fb06f8,0x05fb06f8,0x3fa61fdc,
+    0x303fffef,0x7fffdffd,0x3f60df10,0x7f7ff4c2,0x2df903ff,0xdf100ffa,
+    0x0bffdff5,0xfffddff7,0x5f52fdc5,0xffd309f7,0xb107fffd,0x3fffdfff,
+    0xfff707f6,0xfe837c4f,0x7ffffc80,0x2aa2bf30,0xffff9009,0x8813ea0f,
+    0x445fb06f,0x445fb06f,0x205fb06f,0x27f40ff9,0x3fa07fea,0x220ffd44,
+    0xe85fb06f,0x40ffd44f,0x01efeff8,0x4c33ffe2,0x220cc1ff,0xd8bf27fb,
+    0x9fd0bf17,0x7e41ffa8,0xfe8ff42d,0xff3dfb10,0x0fe837c4,0xfa83fc40,
+    0x2fffffee,0xfa83fc40,0x6c1be204,0x6c1be22f,0x6c1be22f,0x3fffe62f,
+    0xfc82fd44,0xfc82fd45,0xfd837c45,0x7e417ea2,0x00bffb05,0x9f709ff1,
+    0xfe87fc00,0x547f93e0,0x44bf905f,0x3e3fb07f,0xf0dff98f,0x303fe21f,
+    0x7f8803ff,0x537bff70,0x7c403ff9,0x4409f507,0x445fb06f,0x445fb06f,
+    0x4c5fb06f,0x05f902ef,0x05f91be2,0x0df11be2,0x02fc8bf6,0xefe88df1,
+    0x88ff11ff,0x00bf307f,0x50fe8fec,0x3f23fc5f,0x7dcdf102,0x3fa3fb04,
+    0x13fc3ffc,0x7ff445ff,0xb83fc401,0x40bf904f,0x09f507f8,0x2fd837c4,
+    0x17ec1be2,0x8bf60df1,0x07fa04f9,0x00ff47f8,0xb06f88ff,0xf00ff45f,
+    0xdfb4fd8f,0x6f88df31,0xd930df30,0x323ffffd,0x37c4fb2f,0x27f807fa,
+    0x23fb03fc,0x7c41effd,0x337ffe27,0x202efbef,0x09f707f8,0x7f881be6,
+    0x7c40bf50,0x7c45fb06,0x7c45fb06,0x7cc5fb06,0xf807fa04,0xff00ff47,
+    0x5fb06f88,0x4ff00ff4,0x56ff47f8,0x9837c45f,0x677fdc6f,0x9f51ffcc,
+    0x745fa97e,0xfd9fe01f,0x3f23fb02,0x225fa80d,0xb0dffeef,0x83fc409f,
+    0x0ff105fa,0x5fb83fc4,0x7ec1be20,0x7ec1be22,0x7ec1be22,0xfd80b222,
+    0xfd8df102,0xf88df102,0x7ec5fb06,0x7ccdf102,0x17fffc46,0x2fd41be2,
+    0x3fb03ff1,0x44bf3fe2,0x817ec1fe,0x413f26f8,0x20df31fe,0x45be22fd,
+    0x07f88000,0x17ea0ff1,0xbf707f88,0x7c40ff80,0x2207fc2f,0x207fc2ff,
+    0x64002ff8,0xc8bf704f,0xf0bf704f,0x22ff881f,0x4bf704fc,0xfffa87f9,
+    0xfc837c40,0x7f417ea3,0x373ffea1,0x04fc84ff,0x42fdcbf7,0x0ffa1ffb,
+    0x06f88ff5,0xb03fc400,0x02fe889f,0x2fdc1fe2,0xfe88bfe0,0xd117fc2f,
+    0x22ff85ff,0x3a62ffe8,0x307fe204,0x1ff883ff,0x7fc0ffcc,0x88bffa22,
+    0x0ffcc1ff,0xffd50ffe,0xfa86f883,0x36237d46,0x7ffd41ff,0x3ff102ef,
+    0x3e21ff98,0x87ffea1f,0xffdcdff9,0x00037c41,0xff981fe2,0x404fecbd,
+    0x0bf707f8,0xefcdffb8,0x6ffdc2fd,0x5c2fdefc,0xfdefcdff,0xb803ff62,
+    0x3ffdcdff,0xfb9bff70,0x37fee07f,0x5c2fdefc,0x3ffdcdff,0xffceffa8,
+    0x3e20efef,0x1ffdccef,0x7ee77fc4,0x5fd41fef,0x9bff7001,0xffb07ffb,
+    0x83f99fdb,0x81dfffd8,0xcc8006f8,0x1cccffcc,0x177ffec4,0xcffcccc8,
+    0x00df71cc,0xf71bffd5,0x1bffd505,0xffd505f7,0x7dc5f71b,0xfffea806,
+    0x7ff5401e,0x7f5401ef,0xa82fb8df,0x201efffe,0xd1dfffea,0xfffdb8bf,
+    0xffd300de,0xd83f91bf,0xffea8007,0x3ff201ef,0x0c03f93e,0x20017a20,
+    0xffffffff,0x3e00603f,0xffffffff,0x4400bd73,0x00044000,0x00020022,
+    0x000c0006,0x00c00044,0x01000040,0x3c800440,0x2000c000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x55530000,0x53035555,0x54c00557,
+    0xba9800aa,0x2aaaa60a,0x2a600aaa,0x0032e009,0x32205950,0x020bb883,
+    0x3c881654,0x6440736e,0x09999953,0x80357510,0x209aca98,0x20aa00a9,
+    0x2e014c29,0x0164c03f,0x1dd127dc,0x64c076c0,0xfffffc82,0x7ffe44ff,
+    0x3ee03fff,0x904fffff,0x2bffffff,0xfffffffc,0xfffffb81,0x000fe80d,
+    0x320bffd3,0x7fffc41f,0x3fa64eac,0x6c3f905f,0x1fc80ffd,0x40fffff9,
+    0xefffffc8,0xfffffc81,0x440bf65f,0x88ffc47f,0x1ffe02ff,0x203fffa8,
+    0x3f62fff8,0x3a0df504,0x567e40ff,0x5dc1aaaa,0x81ffdb9a,0xecabcffd,
+    0x5e7ff444,0x55535dca,0xfd83fd55,0x0efc989c,0xea8007f4,0x84fa85fa,
+    0xeffd98e9,0x217ebaa0,0x83fb04fa,0x266624fa,0x2fbf607f,0x360ffda9,
+    0x3baabcef,0x3fc40bf6,0x1fe83fe2,0x7d413f60,0x3e03fc8d,0x41fea1ff,
+    0x1ffd03fd,0x40001fc8,0x0f7dc4fe,0x8077ec08,0xf70ff400,0xfd17ea07,
+    0x45f88001,0x11000ee8,0x3a22fc40,0x22ffe40e,0xff100ee8,0x3e03fe20,
+    0x003ff32f,0x1fe205fb,0x20000404,0x9300cc18,0xf885fd05,0x9035100f,
+    0xfb80003f,0x4007fe25,0x20000ffa,0x4cdf11fe,0x743f91bb,0x2fc4000f,
+    0x80000bf2,0x417e45f8,0x3f23fda9,0x441fe202,0x2e5fb06f,0x05fb005f,
+    0x80001fe2,0x00000098,0x5fa8bf70,0x003f9000,0x3ee2fc80,0x00ff6005,
+    0x3ee3fd00,0x22ffff52,0x3603fa4f,0x265f884e,0x2a9d104f,0xbf101cfd,
+    0xc99827cc,0x8809f34f,0x10bfa07f,0x007fd4ff,0x7f8817ec,0x02f7775c,
+    0xeeb82fb8,0x440005ee,0x70bf60ff,0xf90bdddd,0xf3000335,0x001fe41d,
+    0xe80003fd,0xdf11f91f,0x3fa5f823,0x44077ec0,0x4403fa5f,0xffeefcdf,
+    0x3fa5f884,0x21dfff00,0x3fc400fe,0xf519ff50,0x177f447f,0x7c40bf60,
+    0x3ffffe47,0xfc82fb80,0x80007fff,0x90ff13fd,0xf90fffff,0x205bffff,
+    0xd85feee8,0x83fe002f,0x403cccc9,0x3eafb1fe,0x07f4fb03,0x90980bfb,
+    0x7ffc405f,0x2603fea3,0x4cc05f90,0x2200bf20,0x7ffcc07f,0xffe981ff,
+    0x02fd80be,0x3fc40ff1,0x2017e440,0xa80007f8,0x8809f76f,0xdccca87f,
+    0xff982fff,0x3fa0cfff,0xa8ff1002,0x7406ffff,0x0beadd1f,0x361fd3ec,
+    0x17e6005f,0xff07ff10,0x002fcc03,0x7c4017e6,0x7ffec407,0x3ffae03f,
+    0x8817ec3f,0x81fe207f,0x403fffd9,0x2da807f8,0x07fa7fe0,0x6400ff10,
+    0x6fd9807f,0x7c400bf6,0x37d4cc47,0x8bec7fa0,0x7f4dd04f,0x74005fd8,
+    0x83fc400f,0x03fa02fd,0x2001fd00,0x3fa607f8,0x405ffc8c,0x3f65ffda,
+    0x440ff102,0x273fa07f,0x03fc4009,0xfc80fffc,0x7f8806fd,0x007fe200,
+    0x03fc97f4,0x7cc03fe0,0xfc8ff406,0x7c737fd0,0x01bf7fa5,0x33265f70,
+    0xfd837c42,0xd915f702,0x997dc05b,0x0ff102cc,0x3fe617fc,0xb2ffa802,
+    0x81fe205f,0x0bf307f8,0xe807f880,0xfff103ff,0x00ff1007,0xf9002fe8,
+    0xe801bee7,0x80df303f,0x267f51fe,0x47f37ffd,0x002ffafe,0x27ff4bf1,
+    0x17ec1be2,0xecfaafc4,0x3a5f881f,0x0ff104ff,0x2fe417e6,0x7f94fc80,
+    0xf8817ea0,0x8009f707,0xff9807f8,0x401ff603,0x3e2007f8,0x23fd000f,
+    0xf5002ff8,0x40df301f,0x11fa0ff9,0x1fd07ec1,0xfd005ff3,0x113eff21,
+    0x20bf60df,0x17dc20fe,0xfbfc87f4,0x2e0ff104,0x00bf704f,0x827dcff2,
+    0x1fe204fc,0x220037dc,0x03ff007f,0xf8801fec,0x09fb1007,0xff91bee0,
+    0xefe83105,0x20bb7cc0,0x77d46fc8,0x7f43fc80,0x5c03ff50,0x9f39f33f,
+    0x5fb06f88,0xfe883fb8,0xcf99fdc0,0x0ff104f9,0x3fa03fea,0x8ffcc013,
+    0x7fcc1ff9,0x441fe201,0x3e2003ff,0x206fb807,0xf1000ffa,0xfb999b0f,
+    0xb999b0bf,0xfd881fff,0x44feddff,0xecdeffe8,0xffbdfd6f,0x59df703f,
+    0x1fd09fd7,0x7c40ffdc,0x3bfbbf66,0x7ec1be23,0x3e61be22,0xfb37c41e,
+    0xf107dfdd,0x667ff40f,0x3bf66ffc,0x44ffedcd,0xffecdffc,0x703fc404,
+    0x88013bff,0x3bfb207f,0x003ff500,0x7ff43fc4,0xfff02dff,0xea807dff,
+    0x702cefff,0x25bffffd,0x00dfffeb,0x0b7fff66,0x3ff207f4,0xdd90fec0,
+    0x37c47dfd,0x07f62fd8,0x6c357ff5,0x3fbbb21f,0xff99993e,0x7dc43999,
+    0x2e0bffff,0x2dffffff,0x5dfffd70,0x3ff33320,0x7fd41ccc,0x666644ff,
+    0x3e1cccff,0xffff983d,0xfcccc803,0x1331cccf,0x00026600,0x00cc0004,
+    0x00100011,0x1dfb01fd,0x4f980fea,0x2fd837c4,0xfff707f5,0x980fea9f,
+    0x3ffffe4f,0x3103ffff,0x00133001,0xffff8018,0x503fffff,0xffff87b9,
+    0x003fffff,0x20019bd3,0xffffffff,0x0000003f,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x02ae6200,
+    0xdddfd910,0xdd9501dd,0x0f223b9b,0x00014400,0x22179995,0x07ddb34e,
+    0x23deedb8,0x0d4c02a8,0x55440055,0x4c40009a,0x551001ab,0x2aa35555,
+    0xaaaaaaaa,0x5555530a,0x5554c555,0xaaaa8009,0x57510009,0x00aaa001,
+    0xff5013ee,0xf101bfff,0xddffd9bf,0xfeeffd81,0x00df11ff,0x22001fe0,
+    0x12fffffe,0xffdff5bf,0xddffd30b,0x103fe8ff,0x01fe21ff,0xffffffa8,
+    0x7ffd401d,0x7e401eff,0xdf4fffff,0xdddddddd,0x3ffff21f,0x3ff67fff,
+    0xf00dffff,0x07ffffff,0x1fffffe4,0x80bffe20,0xf702fff8,0x1dfd759f,
+    0x27e43fd8,0x3fa06fe4,0x2000df11,0xdfd8007f,0x3fe21312,0x83ff30cf,
+    0x26140dfe,0x23fd80ff,0x3ea007f8,0x3ffecbae,0x9339ff30,0xefef805f,
+    0x021f1aaa,0x559f90f8,0x67ec5555,0x82ffecba,0xffecabff,0xa9adfd83,
+    0x3fea03ff,0x0fffc04f,0x3a20ffc4,0x7c43fc3f,0x6c07fc47,0x000df11f,
+    0x3fe001fe,0x213fe201,0x017f24fb,0x37cc4fd8,0x3bffffe2,0xb85fa81d,
+    0x83fd80ff,0x2fdfd400,0x85dfd0f8,0xb007f90f,0x81ff705f,0x547fc87f,
+    0x206f986f,0x4c07fafd,0x209f902c,0x21fe27fa,0x837dc7f8,0x00df11fd,
+    0x3bffbba6,0xf301eeee,0x307f880d,0x000ff4bf,0x17f43ff1,0x3b733fe2,
+    0x82fd43ff,0x00fe85fc,0x05f9fe80,0xf27dc41f,0x3600ff21,0xf8bfb02f,
+    0x320ff887,0x105fb03f,0x0007faff,0x3fe01ff8,0xbf70bfa1,0x3fb04fc8,
+    0x67ed5be2,0x7ffffcc1,0x302fffff,0x06f880bf,0x007fcdf3,0x2fd57ee0,
+    0x7fd43fc4,0x7cc17ea1,0x98007f87,0x1f05f8cf,0x643e1f50,0x02fd803f,
+    0x887f8ff3,0xb817ec7f,0xf74fa83f,0x03fc0009,0xcffa8bf6,0x7ec0ffda,
+    0x3e23fb02,0x4ffeefce,0xf3001fe0,0x306f880b,0x001fe2df,0x407f57f4,
+    0x49f907f8,0x03fe05fa,0x3f9000ff,0x203e0bf1,0x3f21f1f9,0x202fd803,
+    0x321fe0ff,0xb81fec5f,0xf32fd84f,0x1be6000f,0x6ff47fb0,0xfc80dfff,
+    0x3e23fd03,0x03fea4ff,0x7ffc03fc,0x7fffffff,0x27d41be2,0xf50001fd,
+    0x1fe207ff,0xffeeb7d4,0xa8ffc1ee,0x2aaaaffa,0xeff8b7c0,0x4cc1f1ee,
+    0x3f21f0fd,0x24eeeeee,0x87fe02fd,0xefecbbff,0x64077d40,0x3a3fc45f,
+    0x6f98001f,0x4f99fe40,0x709f7002,0x0ffe23ff,0x03fc07fe,0x67fee664,
+    0x1be24ccc,0x07f71fe4,0x881bf600,0x3ebf707f,0x742fffff,0xffffff1f,
+    0x3ee01fff,0x3dddff13,0x06f7c43e,0x3ffff21f,0x0bf66fff,0x3ffe1fe8,
+    0xfd00bfff,0x9fffb99f,0x27e45fa8,0x0ff30150,0x3df52fd8,0xa83fe200,
+    0x0ff11fff,0x03fc0bf6,0xf1017e60,0x4c6fb88d,0x74040bff,0xeeeffeee,
+    0x7f41fe21,0xff817ea3,0x333ff331,0x887f4033,0x3e21f05f,0x07f90f80,
+    0x7fc05fb0,0x3f267fe1,0x3ffb200e,0x7ec3fccf,0xfe83fcc2,0x203fc40f,
+    0xfffd11fe,0xfb05bfff,0x3fb9fd9f,0x17ec1be2,0x7cc007f8,0x677fc405,
+    0xfc81fffc,0xe87ecdff,0xeeeffeee,0xf931fe21,0x882fd41f,0x003fc0ff,
+    0x82fc4bf3,0x43e03a0f,0x2fd803fc,0x3fc1ff10,0x320013f2,0x267fe22f,
+    0x441ff999,0x1ff82fff,0x7e41ff10,0x4fffeeed,0xfb3effc8,0x7ec1be23,
+    0x9800ff02,0x7ffc405f,0x2e00dfff,0x405ffffe,0x3fe204fb,0x41efffee,
+    0x0df705fa,0x7fe400ff,0x1f05ffff,0x7f90f804,0x2e05fb00,0x3e23fc6f,
+    0x07f4000f,0xfffffff5,0x1dfb09ff,0x3ee09f90,0x7dc17ee5,0x23fb0207,
+    0x05fb06f8,0xf98007fa,0x08b7c405,0x803be200,0x99dfc999,0x77fffc41,
+    0x10bf503d,0x00ff07ff,0x3bbbbfe2,0x3ea1f05f,0x07f90f82,0xff105fb0,
+    0x4fc87f85,0xfb0ff200,0xfb99999b,0xff88060d,0xfb07fd43,0x003fe205,
+    0x06f88fec,0x13f605fb,0x4405f980,0x7f50006f,0xffffff80,0x1fe21fff,
+    0x7545fa80,0x200ff06f,0x82fc43fb,0x1f03d30f,0x3f600ff2,0x7c2ffd42,
+    0x400ff987,0x7fc46fd9,0x0007fe00,0x3fb3bfee,0xc837ec3f,0x47f6005f,
+    0x05fb06f8,0x3733ffe6,0x880bf301,0x3f90006f,0xdfdaaa80,0x1fe20aaa,
+    0xfeeffa80,0x7f6c0dff,0x3eeeeeef,0x9ff103fa,0x2003e599,0xddddf90f,
+    0x777ecddd,0xff05fffe,0xddb13f60,0xfa819fff,0x0027ec05,0x1dfffea8,
+    0xeeefff98,0x36000eff,0x360df11f,0x3ffaa02f,0x0bf301ff,0x30006f88,
+    0x04fb8005,0xfa801fe2,0xf01cefff,0xffffffff,0x2217e69f,0xff5fffff,
+    0xffffffff,0x3ffff21f,0x3ff67fff,0x3e01ceef,0x741ff307,0xfb01cdef,
+    0x006fa807,0xeb880180,0x8002ceee,0x20df11ec,0x013002fd,0x74405f98,
+    0x00000005,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0xaaaa8000,0xaaa98099,0x31aaaaaa,0x55555555,0x260aaa00,
+    0x2055100a,0x2aa0d42a,0x0aaaaaaa,0x0aa60551,0x40335555,0x455302a9,
+    0xaaaaaaa9,0x803551aa,0x02aa22a8,0x03530aa8,0x01551a88,0x0154c550,
+    0x2aaaaa55,0x00aaaaaa,0x751002aa,0x80551015,0xfffffff8,0x3ffff20c,
+    0xf95fffff,0x0fffffff,0xfb0fff70,0x7c1be205,0xfff0fe65,0x21ffffff,
+    0x2ff886f8,0x3fffffe2,0x07fec0bf,0xfff737fc,0x2bffffff,0x2fe406fb,
+    0x7fd413fa,0xf9807f50,0xfa809fb4,0x220fff26,0xffffff6f,0x501fffff,
+    0x36203ffd,0x4c3fffff,0x57fc406f,0x2a5ffcba,0xdccccccc,0x555bf95f,
+    0xff880555,0x102fd87f,0xfa93e0df,0x6fed5542,0x0df10aaa,0xaff887fd,
+    0x6c5ffeba,0x3ffd43ff,0x99999993,0x81ffc9ff,0x7fcc0ff8,0xf517f441,
+    0xf53f9809,0x323fc80f,0x56f886ff,0x55bfb555,0x7ff4c155,0x7bfd01ff,
+    0x7cc3ff95,0x443fc406,0x3fd000ff,0x6c000ff2,0x2fd87fbf,0x9f10df10,
+    0x9f700fdc,0x9fb1be20,0xff10ff10,0x3637f747,0xdf7007ee,0xfd80ffa8,
+    0xfc8bf904,0x2a027cc5,0xf807fe3f,0x0bfbf21f,0x13ee0df1,0xff9dff98,
+    0xbf905501,0x3e2037cc,0x3003fd07,0x001fe4df,0x43fc67d4,0x4df102fd,
+    0xdaadfbaa,0x4fb81abf,0x2fdcdf10,0xbf907f88,0xf887e774,0xff8807dc,
+    0x7cc4fe81,0x25ff100f,0x2fcc0ff9,0x4fd8bea0,0xbfc8df30,0xb837c46f,
+    0xff0e404f,0x26f98003,0x3fc406f9,0x5fb007f8,0x22001fe4,0xd87f88ff,
+    0x74df102f,0xffffffff,0x04fb85ff,0x03be6df1,0x6fa83fc4,0x7dcfe7ba,
+    0x7ec00fd9,0x6c1ff304,0x47fd403f,0x45f883fe,0xfa8bee09,0xfc87f906,
+    0x1be22fda,0x3e0027dc,0x37cc001f,0x7f880df3,0xf9804fc8,0x2001fe46,
+    0xb0ff12fd,0x21be205f,0x701f61fb,0x23be209f,0x1fe201ff,0x3adf2fec,
+    0x803f6dd6,0xa7ec06fa,0xdfb006f9,0xa9be20df,0xff07ee3f,0xfc81fd03,
+    0x1be26faa,0x3e0027dc,0x2fdc001f,0xff880df3,0x002ffeee,0xcefc85fb,
+    0xfa82cccc,0x3f61fe25,0xfeeeeeee,0x1ba0fc86,0x3e209f70,0x7c402fef,
+    0x3e2ff887,0x367f5f95,0x03ff100f,0x2fd8ff88,0x03fff100,0x64df937c,
+    0x2627ec1f,0xfd2fc86f,0x7dc1be23,0x00ffc004,0x7cc5fd10,0x7fffc406,
+    0x4c02efff,0xffffc86f,0x0fe85fff,0x3ff61fe2,0x6fffffff,0x202fc7c8,
+    0xfff104fb,0x9ff8805f,0x7c5ffdb9,0x321fff35,0x009fb01f,0x001bfbf2,
+    0x37c01ffd,0x03f23fff,0x83fc8df5,0x22bf52fc,0x009f706f,0x36001ff8,
+    0x2037cc5f,0x7fe447f8,0x320bf601,0x099999cf,0x1fe217e4,0x37c40bf6,
+    0x7013e3ec,0x2bbe209f,0x3fe200ff,0x443fffff,0xfc97fa5f,0x2006fa81,
+    0x2001fff8,0x3a05fffb,0x329f9f57,0x3a1ff80f,0x3e2fc80f,0xf706f89f,
+    0x01ff8009,0xf981df90,0xc83fc406,0x20df305f,0xef9803fc,0x9ff99999,
+    0x2205fb09,0xdffdd56f,0x20ddffdd,0x2df104fb,0xff100efb,0xf1013599,
+    0x3f917dcb,0x4001ff88,0x3e6005fb,0xfd02ff9f,0x3edbe3f2,0x0df33fd8,
+    0x8cfb8bf2,0x009f706f,0xfc801ff8,0x406f980e,0x0df507f8,0x1fe40ff6,
+    0x7ffffdc0,0xb4ffffff,0x4dbe205f,0xfdccefcc,0x09f704cd,0x05fd9be2,
+    0x3e600ff1,0x3617e405,0x4fb8004f,0x7dcffa00,0x5f8fd80f,0x2a0fb3f9,
+    0x3207f76f,0x3e7fe22f,0x8009f706,0x77e401ff,0x880df300,0x309fb07f,
+    0x01fe40ff,0x6666664c,0xfb2ccffc,0xf11be205,0x2e017d49,0x44df104f,
+    0x07f883ff,0xfb809f30,0x0003bea2,0x7dc013ee,0x7e427f46,0xdd9f32fb,
+    0x07f47fc0,0x67e42fc8,0x009f706f,0x3f201ff8,0x01be600e,0xffa88ff1,
+    0x3203fd82,0x7c40003f,0xf102fd87,0x3ee4f88d,0x8827dc01,0x20ffcc6f,
+    0x9f3007f8,0xff13fb80,0x13ee0003,0xf30bfe20,0x47efc83f,0x3f606eff,
+    0x0bf206fc,0x2e0dfff1,0x0ffc004f,0x4c00efc8,0x77fc406f,0x983fffee,
+    0x0ff200ff,0xb0ff1000,0x31be205f,0x6c07e47f,0xeeeffeee,0xff70df10,
+    0xa803fc41,0xc9fdc04f,0xffffffff,0x013ee06f,0x37e417f6,0xff917fee,
+    0x1fffd40b,0xff905f90,0xb013ee0d,0xdddffddd,0x3ffffe67,0xff36ffff,
+    0x15dddddd,0x19dfffff,0xf900ff60,0x7f880007,0xdf102fd8,0x03f22fa8,
+    0x3ffffffe,0x20df10ff,0x01fe26fd,0x3ee027d4,0xffffffb3,0x7dc0dfff,
+    0x807fdc04,0x3fee4ff8,0x202ffcc2,0x3f200fff,0x706ff882,0xffff809f,
+    0xf34fffff,0xffffffff,0x3ffffe6d,0x00003fff,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x80322000,
+    0x8026200b,0x6c40001c,0xdb880000,0x2e01defe,0xe881cefd,0x6d43d905,
+    0xdfd98cef,0x00054400,0x207bddb7,0x700cefdb,0xc83ddfdb,0x21bd505e,
+    0xd52ed8bd,0xeeeed81b,0xaa986eee,0x2017540a,0x2e1d74e9,0x77441dfd,
+    0x6c03b600,0x40df504f,0x7ff304fa,0x0ffe6000,0x7dc04fa8,0x42fffeef,
+    0xfffeffe9,0xfd837c43,0x3bff7be2,0x7406fdef,0xffe9807f,0xefd87fee,
+    0x7f42ffed,0x442feeef,0x17fc43ff,0xf3ffdb9f,0xfffe8bfd,0x7dc7ffff,
+    0x3ea1ffff,0xfca7d404,0x1fffefc9,0x6fa81fec,0x32ebfe20,0x2a01ff9b,
+    0x13fe604f,0x805ff700,0x20cc04fa,0x27f47fb8,0x6f887fea,0x36145fb0,
+    0x405f90ff,0xdfe807fe,0x513f2140,0x417ee1ff,0x361ff980,0x5c77fc4f,
+    0x200fd4ef,0xf70cc3fe,0x2e02fccb,0x45fdf93f,0x837d45fc,0x7fd403fd,
+    0x403fffff,0x7f4404fa,0x1efc800d,0x0004fa80,0x82fd43fe,0x41be25fc,
+    0x97ee02fd,0x072204fa,0xf100bf90,0x7e4ff20d,0xab7e4003,0xf52ff86f,
+    0x32007ecf,0x37cc405f,0x9174cdf1,0x307ff23f,0x883ff0df,0x7fc400ff,
+    0x402ffc9c,0xdfb004fa,0x03bf6201,0x00027d40,0x40bf23fb,0x41be26f8,
+    0x8fea02fd,0xe80004f9,0x04fa801f,0x0bfee9f5,0x1ff9fd00,0xb27d4ff0,
+    0x077d401f,0x37fff644,0x365fc9fe,0xd107f90f,0xfb89f90b,0x645fb804,
+    0x7777645f,0x205eeeff,0x7f441ffb,0x5ccccc04,0x981999df,0x1ffffeec,
+    0x13fc03fd,0x88bf60df,0xeeefeedb,0x266665fe,0x41999999,0xefb800ff,
+    0x5feeeeee,0x0077fff6,0x7c0bffe2,0x0fd8fea7,0x3203ff30,0x746f99af,
+    0x3f43ffa7,0xf88005f9,0x6c00ff47,0x363fcc2f,0xffffffff,0x8ffdc07f,
+    0xff805ff8,0xffffffff,0x33bfee1f,0x3fd1ffcc,0x0df13fc0,0xcffe8bf6,
+    0x2ccccefd,0x3ffffffe,0xff11ffff,0x3bbbf200,0x4c4eeeee,0x200efffd,
+    0xff00ffe8,0x01fb1fd4,0x37c05fd1,0x98fd8df5,0x64df3fcf,0x2fd8002f,
+    0x3f600df3,0x2a03fc42,0x3fe6004f,0x201ffd43,0xcefdcccc,0x3ff10ccc,
+    0x0bf63fb0,0x0df137c4,0x52fd4bf6,0xcccc809f,0x0ccccccc,0x3ee003fe,
+    0xfd510005,0x77f7ec0d,0x8fea7f80,0x04fd80fd,0x37ff6fec,0x3e3f27ee,
+    0x017e4bf6,0x3fcafd40,0xf989fb00,0x8027d406,0xfd302ffb,0x027d4009,
+    0x47fa0bf5,0x8bf704fc,0x97fc40ff,0x01bea2fc,0x01ff4000,0x00007fd4,
+    0xdf701ff1,0xa9fe17f6,0xf703f63f,0x17b7100d,0x5ebfa877,0x7e49f5f9,
+    0xd1ff0002,0x3bea001f,0xf500ff60,0x03df9009,0x800dfe88,0x1bea04fa,
+    0x3e23ffb1,0x20ffcc1f,0x3ffa22ff,0x3fa27f72,0x0054001f,0x8102ffea,
+    0x30000cfe,0x23ff30ff,0x53fc3ff8,0xf307ec7f,0x4c00001f,0xfbf32fdf,
+    0x80017e47,0x2005fdfc,0xfffdfff8,0x1027d401,0x6c001dfb,0x27d400ef,
+    0xfb9dff10,0x7fdc3fdf,0xb83ffdcd,0xfdefcdff,0x7dd9df12,0x403b99ff,
+    0xffd806fd,0x7cc7ecdf,0x0eccbdff,0xffb999dd,0x4c3ff889,0xfa9fe1ff,
+    0xfff83f63,0x32eeeeee,0x7ddddddd,0xff07ffc4,0x0017e45f,0x002fff98,
+    0x37ffbbf6,0x8164c06f,0x70005fe8,0x27d403ff,0x37fffa60,0x7f541fc8,
+    0x3aa01eff,0x22fb8dff,0xff90efeb,0x3ff403ff,0xffffea80,0xffffd885,
+    0xffffb0ef,0x0bfb05df,0x53fc3ff2,0xff07ec7f,0x5fffffff,0x3bbbbba6,
+    0x322ffc3e,0x00bf20ff,0x2006fe80,0x09fb06fb,0x001f4400,0x98807ea0,
+    0x00011000,0x00088003,0x26002601,0x0088002c,0x4cc01310,0x00000009,
+    0x00000000,0x00000000,0x4407ee00,0x3333264f,0x002ccccc,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x40000000,
+    0x3fee0400,0x4fffffff,0x74400000,0x4039fb54,0x64400aa9,0xb9800001,
+    0x000001bc,0x03372a00,0x4017bb93,0x16dc04c9,0x200e65c4,0x333100a8,
+    0x81333333,0x4cc40bb9,0x09999999,0x26057700,0x257714c2,0x00000bb9,
+    0x40000000,0xfeefcdf8,0x7fff444f,0x80be202f,0xc81d705c,0x40dfdcdf,
+    0x3203645c,0xfd83320d,0x3f21ffff,0x6cc0ffff,0x3fe207ff,0x3fffea0f,
+    0x41bf604f,0xfffffffa,0x0efb84ff,0x3ffffff2,0xfd802fff,0x11ff880d,
+    0x54ffa3ff,0x000000ff,0x44000000,0x3fea3fff,0x3ee6ff60,0x8fc46a0f,
+    0x717fa238,0x557641df,0x7ec5d88a,0x7d40ff23,0x1731be65,0x32049fb1,
+    0x3f7fe23f,0x897ffc07,0x05fd10ff,0x5107fbf5,0x55555555,0x543be603,
+    0xebbbbbbb,0x01fec02f,0xd1fe83fa,0x001fe67f,0x00000000,0x3e0ffe20,
+    0xfc8bf31f,0x5cfd3fa3,0x57fa20ff,0x27e60efb,0x3f8afcfa,0x0fe83fa2,
+    0x07fa0fe8,0x7ec0bf70,0x03fc45c2,0x1fd4bfea,0x75ba13ea,0x8800000f,
+    0x05f90009,0x808004c4,0x3fccbf60,0x00000000,0x83fc4000,0xa87f52fd,
+    0x77f7544f,0xefe880bf,0x6ab5c0ef,0xbf317299,0x8ff22fcc,0x7f4403fc,
+    0x027ff542,0xff880ff1,0x4f987f70,0x44f98fdc,0x99999998,0x20000099,
+    0x000002fc,0x37c4bf60,0x00000000,0x837c4000,0xb8bf32fd,0x1ffe883f,
+    0x407ff440,0x21ddf54d,0x362fc86b,0x7ccdf12f,0x42ff4406,0x103ffdc9,
+    0x25fc80ff,0x117e45f9,0x2a1fd8bf,0xffffffff,0x3200004f,0x0000002f,
+    0x037c47f2,0x00000000,0xd837c400,0x223ff12f,0x37f220fe,0xf701dfdf,
+    0x2ab90bff,0x88b90fae,0xd8df10ff,0x5407f62f,0x7f9804ff,0xd910ff10,
+    0xbdfe81df,0x207e46fd,0x2eee66f8,0x02bbbbbb,0x00000000,0x00000000,
+    0x00000000,0x17ec1be2,0x87ffdff7,0xfd33f2fe,0xfe8efb81,0xdb547d45,
+    0x22fd89d4,0x137c42fd,0xbffb81df,0xff700999,0x3a61fe20,0xfffb102d,
+    0xb827cc19,0x6d40003f,0x2ca8103d,0xffb80dcc,0x55534fff,0x00000555,
+    0x00000000,0x7ec1be20,0x40e6e4c2,0x20c3f109,0xbfd10efb,0x99339dd8,
+    0xf527dc1f,0xfb93ee0b,0x3ffffe25,0xffddb2ff,0xffffe85f,0x010001ff,
+    0x00130062,0x2ffffdc0,0x7ffccbee,0x503fff11,0x3a799999,0x007fffff,
+    0x00000000,0x0df10000,0x10000bf6,0x2077405f,0x3ba20fe8,0x741fda9b,
+    0xfd007f46,0x999076c1,0x3ae39999,0xccb80bde,0x0000cccc,0x80000000,
+    0x8dfd89fe,0xfff50fd8,0x00fffe65,0x333332e0,0x00000004,0x10000000,
+    0x4cbf60df,0x3eeeeeee,0x04401510,0xdfb70088,0x00202019,0x00000101,
+    0x00000000,0x7c400000,0x2ffffec5,0x1ffd1bfa,0x00000000,0x00000000,
+    0x20df1000,0xdddd32fd,0x00007ddd,0x00000000,0x00000000,0x00000000,
+    0x06a00000,0x80413bae,0x00000009,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+};
+
+static signed short stb__consolas_24_latin1_x[224]={ 0,5,3,0,1,0,0,5,3,3,1,0,2,3,
+4,1,1,1,1,1,0,2,1,1,1,1,4,2,1,1,2,3,0,0,1,1,1,2,2,0,1,2,2,1,
+2,0,1,0,1,0,1,1,1,1,0,0,0,0,1,4,1,3,1,0,0,1,1,1,1,1,0,1,1,2,
+1,2,2,1,1,1,1,1,2,2,0,1,0,0,0,0,1,1,5,2,0,1,1,1,1,1,1,1,1,1,
+1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,5,1,1,1,0,
+5,1,0,0,2,1,1,3,1,0,2,1,2,3,0,1,1,4,5,2,2,1,0,0,0,2,0,0,0,0,
+0,0,-1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,1,0,0,
+0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,
+ };
+static signed short stb__consolas_24_latin1_y[224]={ 17,0,0,1,-1,0,0,0,-1,-1,0,4,13,9,
+13,0,1,1,1,1,1,1,1,1,1,1,5,5,4,7,4,0,0,1,1,1,1,1,1,1,1,1,1,1,
+1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,1,20,0,5,0,5,0,5,0,5,0,0,
+0,0,0,5,5,5,5,5,5,5,1,5,5,5,5,5,5,0,-3,0,8,1,1,1,1,1,1,1,1,1,
+1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,17,5,-1,1,2,1,
+-3,0,0,1,1,5,9,9,0,1,0,2,0,0,0,5,0,8,17,0,1,5,0,0,0,5,-3,-3,-3,-3,
+-3,-4,1,1,-3,-3,-3,-3,-3,-3,-3,-3,1,-3,-3,-3,-3,-3,-3,5,-1,-3,-3,-3,-3,-3,1,0,0,0,
+0,0,0,-1,5,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,2,0,0,0,0,0,0,0,
+ };
+static unsigned short stb__consolas_24_latin1_w[224]={ 0,4,8,13,11,13,14,3,8,7,11,13,7,8,
+5,11,12,11,11,11,13,10,11,11,11,11,5,7,10,11,10,8,14,14,11,11,12,10,10,12,11,10,9,12,
+10,13,11,13,11,14,12,11,12,11,14,13,13,14,11,6,11,7,11,14,8,11,11,11,11,11,13,12,11,10,
+10,11,10,12,11,12,11,11,11,10,12,11,13,13,13,13,11,10,3,10,13,12,12,12,12,12,12,12,12,12,
+12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,0,4,10,11,12,13,
+3,11,11,14,9,11,11,8,11,10,9,11,9,8,12,12,11,5,3,9,9,11,13,13,13,8,14,14,14,14,
+14,14,14,11,12,12,12,12,12,12,12,12,13,12,13,13,13,13,13,11,13,12,12,12,12,14,11,11,12,12,
+12,12,12,12,13,11,12,12,12,12,12,12,12,12,11,12,13,13,13,13,13,13,12,12,12,12,12,13,11,13,
+ };
+static unsigned short stb__consolas_24_latin1_h[224]={ 0,18,6,16,21,18,18,6,23,23,11,13,9,3,
+5,20,17,16,16,17,16,17,17,16,17,16,13,17,14,7,14,18,22,16,16,17,16,16,16,17,16,16,17,16,
+16,16,16,17,16,21,16,17,16,17,16,16,16,16,16,22,20,22,9,2,6,13,18,13,18,13,17,17,17,17,
+22,17,17,12,12,13,17,17,12,13,17,13,12,12,12,17,12,22,25,22,5,16,16,16,16,16,16,16,16,16,
+16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,0,17,21,16,15,16,
+25,20,6,17,12,11,6,3,11,5,9,15,10,10,6,17,20,5,4,10,12,11,17,17,17,17,20,20,20,20,
+20,21,16,20,20,20,20,20,20,20,20,20,16,20,21,21,21,21,21,11,21,21,21,21,21,20,16,18,18,18,
+18,18,18,19,13,16,18,18,18,18,17,17,17,17,18,17,18,18,18,18,18,13,18,18,18,18,18,22,22,22,
+ };
+static unsigned short stb__consolas_24_latin1_s[224]={ 252,250,247,62,40,106,104,252,17,9,70,
+48,159,238,215,91,183,221,233,12,36,1,222,13,152,220,247,223,37,189,26,
+40,100,232,1,24,194,183,25,36,50,76,49,87,245,112,196,1,100,129,207,
+164,208,176,181,167,153,138,126,34,146,26,177,26,201,62,119,127,171,139,65,
+15,40,245,89,74,141,176,48,74,79,28,225,151,52,87,237,211,162,231,189,
+41,1,64,201,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,
+170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,252,247,157,
+143,1,103,5,186,235,59,201,118,210,238,94,227,167,14,130,140,222,196,79,
+221,252,149,60,106,86,113,127,201,198,213,66,118,103,52,155,67,133,173,14,
+27,241,1,40,53,129,228,168,182,196,210,224,82,238,1,14,27,144,158,117,
+94,172,185,198,211,131,81,99,91,133,159,146,120,234,209,210,188,224,100,236,
+49,157,90,63,113,144,27,1,77,14,75,52,115, };
+static unsigned short stb__consolas_24_latin1_t[224]={ 13,49,156,125,27,49,70,1,1,1,156,
+142,156,163,163,27,70,125,125,89,125,89,70,125,89,107,107,89,142,156,142,
+70,1,107,125,89,107,107,125,89,125,125,89,125,125,125,125,107,125,1,107,
+89,125,89,125,125,125,125,125,1,27,1,156,24,156,142,70,142,70,142,107,
+107,107,89,1,89,89,142,156,142,107,107,142,142,107,142,142,142,142,89,142,
+1,1,1,163,107,107,107,107,107,107,107,107,107,107,107,107,107,107,107,107,
+107,107,107,107,107,107,107,107,107,107,107,107,107,107,107,107,107,13,70,1,
+107,142,107,1,27,156,89,142,156,156,163,156,163,156,142,156,156,156,70,27,
+163,8,156,156,156,89,89,89,89,27,27,49,27,27,27,107,27,27,27,49,
+49,27,49,49,49,107,27,1,1,1,1,1,156,1,27,27,27,1,27,107,
+49,49,49,49,49,70,49,142,107,49,49,49,49,70,70,89,89,49,89,49,
+70,70,70,70,142,70,70,70,70,70,1,1,1, };
+static unsigned short stb__consolas_24_latin1_a[224]={ 211,211,211,211,211,211,211,211,
+211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,
+211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,
+211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,
+211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,
+211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,
+211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,
+211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,
+211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,
+211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,
+211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,
+211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,
+211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,
+211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,
+211,211,211,211,211,211,211,211, };
+
+// Call this function with
+//    font: NULL or array length
+//    data: NULL or specified size
+//    height: STB_FONT_consolas_24_latin1_BITMAP_HEIGHT or STB_FONT_consolas_24_latin1_BITMAP_HEIGHT_POW2
+//    return value: spacing between lines
+static void stb_font_consolas_24_latin1(stb_fontchar font[STB_FONT_consolas_24_latin1_NUM_CHARS],
+                unsigned char data[STB_FONT_consolas_24_latin1_BITMAP_HEIGHT][STB_FONT_consolas_24_latin1_BITMAP_WIDTH],
+                int height)
+{
+    int i,j;
+    if (data != 0) {
+        unsigned int *bits = stb__consolas_24_latin1_pixels;
+        unsigned int bitpack = *bits++, numbits = 32;
+        for (i=0; i < STB_FONT_consolas_24_latin1_BITMAP_WIDTH*height; ++i)
+            data[0][i] = 0;  // zero entire bitmap
+        for (j=1; j < STB_FONT_consolas_24_latin1_BITMAP_HEIGHT-1; ++j) {
+            for (i=1; i < STB_FONT_consolas_24_latin1_BITMAP_WIDTH-1; ++i) {
+                unsigned int value;
+                if (numbits==0) bitpack = *bits++, numbits=32;
+                value = bitpack & 1;
+                bitpack >>= 1, --numbits;
+                if (value) {
+                    if (numbits < 3) bitpack = *bits++, numbits = 32;
+                    data[j][i] = (bitpack & 7) * 0x20 + 0x1f;
+                    bitpack >>= 3, numbits -= 3;
+                } else {
+                    data[j][i] = 0;
+                }
+            }
+        }
+    }
+
+    // build font description
+    if (font != 0) {
+        float recip_width = 1.0f / STB_FONT_consolas_24_latin1_BITMAP_WIDTH;
+        float recip_height = 1.0f / height;
+        for (i=0; i < STB_FONT_consolas_24_latin1_NUM_CHARS; ++i) {
+            // pad characters so they bilerp from empty space around each character
+            font[i].s0 = (stb__consolas_24_latin1_s[i]) * recip_width;
+            font[i].t0 = (stb__consolas_24_latin1_t[i]) * recip_height;
+            font[i].s1 = (stb__consolas_24_latin1_s[i] + stb__consolas_24_latin1_w[i]) * recip_width;
+            font[i].t1 = (stb__consolas_24_latin1_t[i] + stb__consolas_24_latin1_h[i]) * recip_height;
+            font[i].x0 = stb__consolas_24_latin1_x[i];
+            font[i].y0 = stb__consolas_24_latin1_y[i];
+            font[i].x1 = stb__consolas_24_latin1_x[i] + stb__consolas_24_latin1_w[i];
+            font[i].y1 = stb__consolas_24_latin1_y[i] + stb__consolas_24_latin1_h[i];
+            font[i].advance_int = (stb__consolas_24_latin1_a[i]+8)>>4;
+            font[i].s0f = (stb__consolas_24_latin1_s[i] - 0.5f) * recip_width;
+            font[i].t0f = (stb__consolas_24_latin1_t[i] - 0.5f) * recip_height;
+            font[i].s1f = (stb__consolas_24_latin1_s[i] + stb__consolas_24_latin1_w[i] + 0.5f) * recip_width;
+            font[i].t1f = (stb__consolas_24_latin1_t[i] + stb__consolas_24_latin1_h[i] + 0.5f) * recip_height;
+            font[i].x0f = stb__consolas_24_latin1_x[i] - 0.5f;
+            font[i].y0f = stb__consolas_24_latin1_y[i] - 0.5f;
+            font[i].x1f = stb__consolas_24_latin1_x[i] + stb__consolas_24_latin1_w[i] + 0.5f;
+            font[i].y1f = stb__consolas_24_latin1_y[i] + stb__consolas_24_latin1_h[i] + 0.5f;
+            font[i].advance = stb__consolas_24_latin1_a[i]/16.0f;
+        }
+    }
+}
+
+#ifndef STB_SOMEFONT_CREATE
+#define STB_SOMEFONT_CREATE              stb_font_consolas_24_latin1
+#define STB_SOMEFONT_BITMAP_WIDTH        STB_FONT_consolas_24_latin1_BITMAP_WIDTH
+#define STB_SOMEFONT_BITMAP_HEIGHT       STB_FONT_consolas_24_latin1_BITMAP_HEIGHT
+#define STB_SOMEFONT_BITMAP_HEIGHT_POW2  STB_FONT_consolas_24_latin1_BITMAP_HEIGHT_POW2
+#define STB_SOMEFONT_FIRST_CHAR          STB_FONT_consolas_24_latin1_FIRST_CHAR
+#define STB_SOMEFONT_NUM_CHARS           STB_FONT_consolas_24_latin1_NUM_CHARS
+#define STB_SOMEFONT_LINE_SPACING        STB_FONT_consolas_24_latin1_LINE_SPACING
+#endif
+
diff --git a/external/Vulkan/external/tinygltf/LICENSE b/external/Vulkan/external/tinygltf/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..34398adf07246224f14a9059a83bbbbbab008c9c
--- /dev/null
+++ b/external/Vulkan/external/tinygltf/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2017 Syoyo Fujita, Aurélien Chatelain and many contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/external/Vulkan/external/tinygltf/README.md b/external/Vulkan/external/tinygltf/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..2d9143ddd9c44e32d66e39bae1d1753ee551f2e5
--- /dev/null
+++ b/external/Vulkan/external/tinygltf/README.md
@@ -0,0 +1,218 @@
+# Header only C++ tiny glTF library(loader/saver).
+
+`TinyGLTF` is a header only C++11 glTF 2.0 https://github.com/KhronosGroup/glTF library.
+
+`TinyGLTF` uses Niels Lohmann's json library(https://github.com/nlohmann/json), so now it requires C++11 compiler.
+If you are looking for old, C++03 version, please use `devel-picojson` branch.
+
+## Status
+
+ - v2.4.0 Experimental RapidJSON support. Experimental C++14 support(C++14 may give better performance)
+ - v2.3.0 Modified Material representation according to glTF 2.0 schema(and introduced TextureInfo class)
+ - v2.2.0 release(Support loading 16bit PNG. Sparse accessor support)
+ - v2.1.0 release(Draco support)
+ - v2.0.0 release(22 Aug, 2018)!
+
+## Builds
+
+[![Build Status](https://travis-ci.org/syoyo/tinygltf.svg?branch=devel)](https://travis-ci.org/syoyo/tinygltf)
+
+[![Build status](https://ci.appveyor.com/api/projects/status/warngenu9wjjhlm8?svg=true)](https://ci.appveyor.com/project/syoyo/tinygltf)
+
+## Features
+
+* Written in portable C++. C++-11 with STL dependency only.
+  * [x] macOS + clang(LLVM)
+  * [x] iOS + clang
+  * [x] Linux + gcc/clang
+  * [x] Windows + MinGW
+  * [x] Windows + Visual Studio 2015 Update 3 or later.
+    * Visual Studio 2013 is not supported since they have limited C++11 support and failed to compile `json.hpp`.
+  * [x] Android NDK
+  * [x] Android + CrystaX(NDK drop-in replacement) GCC
+  * [x] Web using Emscripten(LLVM)
+* Moderate parsing time and memory consumption.
+* glTF specification v2.0.0
+  * [x] ASCII glTF
+    * [x] Load
+    * [x] Save
+  * [x] Binary glTF(GLB)
+    * [x] Load
+    * [x] Save(.bin embedded .glb)
+* Buffers
+  * [x] Parse BASE64 encoded embedded buffer data(DataURI).
+  * [x] Load `.bin` file.
+* Image(Using stb_image)
+  * [x] Parse BASE64 encoded embedded image data(DataURI).
+  * [x] Load external image file.
+  * [x] Load PNG(8bit and 16bit)
+  * [x] Load JPEG(8bit only)
+  * [x] Load BMP
+  * [x] Load GIF
+  * [x] Custom Image decoder callback(e.g. for decoding OpenEXR image)
+* Morph traget
+  * [x] Sparse accessor
+* Load glTF from memory
+* Custom callback handler
+  * [x] Image load
+  * [x] Image save
+* Extensions
+  * [x] Draco mesh decoding
+  * [ ] Draco mesh encoding
+
+## Note on extension property
+
+In extension(`ExtensionMap`), JSON number value is parsed as int or float(number) and stored as `tinygltf::Value` object. If you want a floating point value from `tinygltf::Value`, use `GetNumberAsDouble()` method.
+
+`IsNumber()` returns true if the underlying value is an int value or a floating point value.
+
+## Examples
+
+* [glview](examples/glview) : Simple glTF geometry viewer.
+* [validator](examples/validator) : Simple glTF validator with JSON schema.
+* [basic](examples/basic) : Basic glTF viewer with texturing support.
+
+## Projects using TinyGLTF
+
+* px_render Single header C++ Libraries for Thread Scheduling, Rendering, and so on... https://github.com/pplux/px
+* Physical based rendering with Vulkan using glTF 2.0 models https://github.com/SaschaWillems/Vulkan-glTF-PBR
+* GLTF loader plugin for OGRE 2.1. Support for PBR materials via HLMS/PBS https://github.com/Ybalrid/Ogre_glTF
+* [TinyGltfImporter](http://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1TinyGltfImporter.html) plugin for [Magnum](https://github.com/mosra/magnum), a lightweight and modular C++11/C++14 graphics middleware for games and data visualization.
+* [Diligent Engine](https://github.com/DiligentGraphics/DiligentEngine) - A modern cross-platform low-level graphics library and rendering framework
+* Lighthouse 2: a rendering framework for real-time ray tracing / path tracing experiments. https://github.com/jbikker/lighthouse2
+* [QuickLook GLTF](https://github.com/toshiks/glTF-quicklook) - quicklook plugin for macos. Also SceneKit wrapper for tinygltf.
+* [GlslViewer](https://github.com/patriciogonzalezvivo/glslViewer) - live GLSL coding for MacOS and Linux
+* [Vulkan-Samples](https://github.com/KhronosGroup/Vulkan-Samples) - The Vulkan Samples is collection of resources to help you develop optimized Vulkan applications.
+* Your projects here! (Please send PR)
+
+## TODOs
+
+* [ ] Write C++ code generator which emits C++ code from JSON schema for robust parsing.
+* [ ] Mesh Compression/decompression(Open3DGC, etc)
+  * [x] Load Draco compressed mesh
+  * [ ] Save Draco compressed mesh
+  * [ ] Open3DGC?
+* [x] Support `extensions` and `extras` property
+* [ ] HDR image?
+  * [ ] OpenEXR extension through TinyEXR.
+* [ ] 16bit PNG support in Serialization
+* [ ] Write example and tests for `animation` and `skin`
+
+## Licenses
+
+TinyGLTF is licensed under MIT license.
+
+TinyGLTF uses the following third party libraries.
+
+* json.hpp : Copyright (c) 2013-2017 Niels Lohmann. MIT license.
+* base64 : Copyright (C) 2004-2008 René Nyffenegger
+* stb_image.h : v2.08 - public domain image loader - [Github link](https://github.com/nothings/stb/blob/master/stb_image.h)
+* stb_image_write.h : v1.09 - public domain image writer - [Github link](https://github.com/nothings/stb/blob/master/stb_image_write.h)
+
+
+## Build and example
+
+Copy `stb_image.h`, `stb_image_write.h`, `json.hpp` and `tiny_gltf.h` to your project.
+
+### Loading glTF 2.0 model
+
+```c++
+// Define these only in *one* .cc file.
+#define TINYGLTF_IMPLEMENTATION
+#define STB_IMAGE_IMPLEMENTATION
+#define STB_IMAGE_WRITE_IMPLEMENTATION
+// #define TINYGLTF_NOEXCEPTION // optional. disable exception handling.
+#include "tiny_gltf.h"
+
+using namespace tinygltf;
+
+Model model;
+TinyGLTF loader;
+std::string err;
+std::string warn;
+
+bool ret = loader.LoadASCIIFromFile(&model, &err, &warn, argv[1]);
+//bool ret = loader.LoadBinaryFromFile(&model, &err, &warn, argv[1]); // for binary glTF(.glb)
+
+if (!warn.empty()) {
+  printf("Warn: %s\n", warn.c_str());
+}
+
+if (!err.empty()) {
+  printf("Err: %s\n", err.c_str());
+}
+
+if (!ret) {
+  printf("Failed to parse glTF\n");
+  return -1;
+}
+```
+
+## Compile options
+
+* `TINYGLTF_NOEXCEPTION` : Disable C++ exception in JSON parsing. You can use `-fno-exceptions` or by defining the symbol `JSON_NOEXCEPTION` and `TINYGLTF_NOEXCEPTION`  to fully remove C++ exception codes when compiling TinyGLTF.
+* `TINYGLTF_NO_STB_IMAGE` : Do not load images with stb_image. Instead use `TinyGLTF::SetImageLoader(LoadimageDataFunction LoadImageData, void *user_data)` to set a callback for loading images.
+* `TINYGLTF_NO_STB_IMAGE_WRITE` : Do not write images with stb_image_write. Instead use `TinyGLTF::SetImageWriter(WriteimageDataFunction WriteImageData, void *user_data)` to set a callback for writing images.
+* `TINYGLTF_NO_EXTERNAL_IMAGE` : Do not try to load external image file. This option would be helpful if you do not want to load image files during glTF parsing.
+* `TINYGLTF_ANDROID_LOAD_FROM_ASSETS`: Load all files from packaged app assets instead of the regular file system. **Note:** You must pass a valid asset manager from your android app to `tinygltf::asset_manager` beforehand.
+* `TINYGLTF_ENABLE_DRACO`: Enable Draco compression. User must provide include path and link correspnding libraries in your project file.
+* `TINYGLTF_NO_INCLUDE_JSON `: Disable including `json.hpp` from within `tiny_gltf.h` because it has been already included before or you want to include it using custom path before including `tiny_gltf.h`.
+* `TINYGLTF_NO_INCLUDE_STB_IMAGE `: Disable including `stb_image.h` from within `tiny_gltf.h` because it has been already included before or you want to include it using custom path before including `tiny_gltf.h`.
+* `TINYGLTF_NO_INCLUDE_STB_IMAGE_WRITE `: Disable including `stb_image_write.h` from within `tiny_gltf.h` because it has been already included before or you want to include it using custom path before including `tiny_gltf.h`.
+* `TINYGLTF_USE_RAPIDJSON` : Use RapidJSON as a JSON parser/serializer. RapidJSON files are not included in TinyGLTF repo. Please set an include path to RapidJSON if you enable this featrure.
+* `TINYGLTF_USE_CPP14` : Use C++14 feature(requires C++14 compiler). This may give better performance than C++11.
+
+
+### Saving gltTF 2.0 model
+
+* Buffers.
+  * [x] To file
+  * [x] Embedded
+  * [ ] Draco compressed?
+* [x] Images
+  * [x] To file
+  * [x] Embedded
+* Binary(.glb)
+  * [x] .bin embedded single .glb
+  * [ ] External .bin
+
+## Running tests.
+
+### glTF parsing test
+
+#### Setup
+
+Python 2.6 or 2.7 required.
+Git clone https://github.com/KhronosGroup/glTF-Sample-Models to your local dir.
+
+#### Run parsing test
+
+After building `loader_example`, edit `test_runner.py`, then,
+
+```bash
+$ python test_runner.py
+```
+
+### Unit tests
+
+```bash
+$ cd tests
+$ make
+$ ./tester
+$ ./tester_noexcept
+```
+
+### Fuzzing tests
+
+See `tests/fuzzer` for details.
+
+After running fuzzer on Ryzen9 3950X a week, at least `LoadASCIIFromString` looks safe except for out-of-memory error in Fuzzer.
+We may be better to introduce bounded memory size checking when parsing glTF data.
+
+## Third party licenses
+
+* json.hpp : Licensed under the MIT License <http://opensource.org/licenses/MIT>. Copyright (c) 2013-2017 Niels Lohmann <http://nlohmann.me>.
+* stb_image : Public domain.
+* catch : Copyright (c) 2012 Two Blue Cubes Ltd. All rights reserved. Distributed under the Boost Software License, Version 1.0.
+* RapidJSON : Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. http://rapidjson.org/
+* dlib(uridecode, uriencode) : Copyright (C) 2003  Davis E. King Boost Software License 1.0. http://dlib.net/dlib/server/server_http.cpp.html
diff --git a/external/Vulkan/external/tinygltf/json.hpp b/external/Vulkan/external/tinygltf/json.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..c9af0bed36d6852de735b8d0b5d034614aef0e54
--- /dev/null
+++ b/external/Vulkan/external/tinygltf/json.hpp
@@ -0,0 +1,20406 @@
+/*
+    __ _____ _____ _____
+ __|  |   __|     |   | |  JSON for Modern C++
+|  |  |__   |  |  | | | |  version 3.5.0
+|_____|_____|_____|_|___|  https://github.com/nlohmann/json
+
+Licensed under the MIT License <http://opensource.org/licenses/MIT>.
+SPDX-License-Identifier: MIT
+Copyright (c) 2013-2018 Niels Lohmann <http://nlohmann.me>.
+
+Permission is hereby  granted, free of charge, to any  person obtaining a copy
+of this software and associated  documentation files (the "Software"), to deal
+in the Software  without restriction, including without  limitation the rights
+to  use, copy,  modify, merge,  publish, distribute,  sublicense, and/or  sell
+copies  of  the Software,  and  to  permit persons  to  whom  the Software  is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE  IS PROVIDED "AS  IS", WITHOUT WARRANTY  OF ANY KIND,  EXPRESS OR
+IMPLIED,  INCLUDING BUT  NOT  LIMITED TO  THE  WARRANTIES OF  MERCHANTABILITY,
+FITNESS FOR  A PARTICULAR PURPOSE AND  NONINFRINGEMENT. IN NO EVENT  SHALL THE
+AUTHORS  OR COPYRIGHT  HOLDERS  BE  LIABLE FOR  ANY  CLAIM,  DAMAGES OR  OTHER
+LIABILITY, WHETHER IN AN ACTION OF  CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE  OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+#ifndef NLOHMANN_JSON_HPP
+#define NLOHMANN_JSON_HPP
+
+#define NLOHMANN_JSON_VERSION_MAJOR 3
+#define NLOHMANN_JSON_VERSION_MINOR 5
+#define NLOHMANN_JSON_VERSION_PATCH 0
+
+#include <algorithm> // all_of, find, for_each
+#include <cassert> // assert
+#include <ciso646> // and, not, or
+#include <cstddef> // nullptr_t, ptrdiff_t, size_t
+#include <functional> // hash, less
+#include <initializer_list> // initializer_list
+#include <iosfwd> // istream, ostream
+#include <iterator> // random_access_iterator_tag
+#include <numeric> // accumulate
+#include <string> // string, stoi, to_string
+#include <utility> // declval, forward, move, pair, swap
+
+// #include <nlohmann/json_fwd.hpp>
+#ifndef NLOHMANN_JSON_FWD_HPP
+#define NLOHMANN_JSON_FWD_HPP
+
+#include <cstdint> // int64_t, uint64_t
+#include <map> // map
+#include <memory> // allocator
+#include <string> // string
+#include <vector> // vector
+
+/*!
+@brief namespace for Niels Lohmann
+@see https://github.com/nlohmann
+@since version 1.0.0
+*/
+namespace nlohmann
+{
+/*!
+@brief default JSONSerializer template argument
+
+This serializer ignores the template arguments and uses ADL
+([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl))
+for serialization.
+*/
+template<typename T = void, typename SFINAE = void>
+struct adl_serializer;
+
+template<template<typename U, typename V, typename... Args> class ObjectType =
+         std::map,
+         template<typename U, typename... Args> class ArrayType = std::vector,
+         class StringType = std::string, class BooleanType = bool,
+         class NumberIntegerType = std::int64_t,
+         class NumberUnsignedType = std::uint64_t,
+         class NumberFloatType = double,
+         template<typename U> class AllocatorType = std::allocator,
+         template<typename T, typename SFINAE = void> class JSONSerializer =
+         adl_serializer>
+class basic_json;
+
+/*!
+@brief JSON Pointer
+
+A JSON pointer defines a string syntax for identifying a specific value
+within a JSON document. It can be used with functions `at` and
+`operator[]`. Furthermore, JSON pointers are the base for JSON patches.
+
+@sa [RFC 6901](https://tools.ietf.org/html/rfc6901)
+
+@since version 2.0.0
+*/
+template<typename BasicJsonType>
+class json_pointer;
+
+/*!
+@brief default JSON class
+
+This type is the default specialization of the @ref basic_json class which
+uses the standard template types.
+
+@since version 1.0.0
+*/
+using json = basic_json<>;
+}  // namespace nlohmann
+
+#endif
+
+// #include <nlohmann/detail/macro_scope.hpp>
+
+
+// This file contains all internal macro definitions
+// You MUST include macro_unscope.hpp at the end of json.hpp to undef all of them
+
+// exclude unsupported compilers
+#if !defined(JSON_SKIP_UNSUPPORTED_COMPILER_CHECK)
+    #if defined(__clang__)
+        #if (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) < 30400
+            #error "unsupported Clang version - see https://github.com/nlohmann/json#supported-compilers"
+        #endif
+    #elif defined(__GNUC__) && !(defined(__ICC) || defined(__INTEL_COMPILER))
+        #if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40800
+            #error "unsupported GCC version - see https://github.com/nlohmann/json#supported-compilers"
+        #endif
+    #endif
+#endif
+
+// disable float-equal warnings on GCC/clang
+#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__)
+    #pragma GCC diagnostic push
+    #pragma GCC diagnostic ignored "-Wfloat-equal"
+#endif
+
+// disable documentation warnings on clang
+#if defined(__clang__)
+    #pragma GCC diagnostic push
+    #pragma GCC diagnostic ignored "-Wdocumentation"
+#endif
+
+// allow for portable deprecation warnings
+#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__)
+    #define JSON_DEPRECATED __attribute__((deprecated))
+#elif defined(_MSC_VER)
+    #define JSON_DEPRECATED __declspec(deprecated)
+#else
+    #define JSON_DEPRECATED
+#endif
+
+// allow to disable exceptions
+#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION)
+    #define JSON_THROW(exception) throw exception
+    #define JSON_TRY try
+    #define JSON_CATCH(exception) catch(exception)
+    #define JSON_INTERNAL_CATCH(exception) catch(exception)
+#else
+    #define JSON_THROW(exception) std::abort()
+    #define JSON_TRY if(true)
+    #define JSON_CATCH(exception) if(false)
+    #define JSON_INTERNAL_CATCH(exception) if(false)
+#endif
+
+// override exception macros
+#if defined(JSON_THROW_USER)
+    #undef JSON_THROW
+    #define JSON_THROW JSON_THROW_USER
+#endif
+#if defined(JSON_TRY_USER)
+    #undef JSON_TRY
+    #define JSON_TRY JSON_TRY_USER
+#endif
+#if defined(JSON_CATCH_USER)
+    #undef JSON_CATCH
+    #define JSON_CATCH JSON_CATCH_USER
+    #undef JSON_INTERNAL_CATCH
+    #define JSON_INTERNAL_CATCH JSON_CATCH_USER
+#endif
+#if defined(JSON_INTERNAL_CATCH_USER)
+    #undef JSON_INTERNAL_CATCH
+    #define JSON_INTERNAL_CATCH JSON_INTERNAL_CATCH_USER
+#endif
+
+// manual branch prediction
+#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__)
+    #define JSON_LIKELY(x)      __builtin_expect(!!(x), 1)
+    #define JSON_UNLIKELY(x)    __builtin_expect(!!(x), 0)
+#else
+    #define JSON_LIKELY(x)      x
+    #define JSON_UNLIKELY(x)    x
+#endif
+
+// C++ language standard detection
+#if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464
+    #define JSON_HAS_CPP_17
+    #define JSON_HAS_CPP_14
+#elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1)
+    #define JSON_HAS_CPP_14
+#endif
+
+/*!
+@brief macro to briefly define a mapping between an enum and JSON
+@def NLOHMANN_JSON_SERIALIZE_ENUM
+@since version 3.4.0
+*/
+#define NLOHMANN_JSON_SERIALIZE_ENUM(ENUM_TYPE, ...)                                           \
+    template<typename BasicJsonType>                                                           \
+    inline void to_json(BasicJsonType& j, const ENUM_TYPE& e)                                  \
+    {                                                                                          \
+        static_assert(std::is_enum<ENUM_TYPE>::value, #ENUM_TYPE " must be an enum!");         \
+        static const std::pair<ENUM_TYPE, BasicJsonType> m[] = __VA_ARGS__;                    \
+        auto it = std::find_if(std::begin(m), std::end(m),                                     \
+                               [e](const std::pair<ENUM_TYPE, BasicJsonType>& ej_pair) -> bool \
+        {                                                                                      \
+            return ej_pair.first == e;                                                         \
+        });                                                                                    \
+        j = ((it != std::end(m)) ? it : std::begin(m))->second;                                \
+    }                                                                                          \
+    template<typename BasicJsonType>                                                           \
+    inline void from_json(const BasicJsonType& j, ENUM_TYPE& e)                                \
+    {                                                                                          \
+        static_assert(std::is_enum<ENUM_TYPE>::value, #ENUM_TYPE " must be an enum!");         \
+        static const std::pair<ENUM_TYPE, BasicJsonType> m[] = __VA_ARGS__;                    \
+        auto it = std::find_if(std::begin(m), std::end(m),                                     \
+                               [j](const std::pair<ENUM_TYPE, BasicJsonType>& ej_pair) -> bool \
+        {                                                                                      \
+            return ej_pair.second == j;                                                        \
+        });                                                                                    \
+        e = ((it != std::end(m)) ? it : std::begin(m))->first;                                 \
+    }
+
+// Ugly macros to avoid uglier copy-paste when specializing basic_json. They
+// may be removed in the future once the class is split.
+
+#define NLOHMANN_BASIC_JSON_TPL_DECLARATION                                \
+    template<template<typename, typename, typename...> class ObjectType,   \
+             template<typename, typename...> class ArrayType,              \
+             class StringType, class BooleanType, class NumberIntegerType, \
+             class NumberUnsignedType, class NumberFloatType,              \
+             template<typename> class AllocatorType,                       \
+             template<typename, typename = void> class JSONSerializer>
+
+#define NLOHMANN_BASIC_JSON_TPL                                            \
+    basic_json<ObjectType, ArrayType, StringType, BooleanType,             \
+    NumberIntegerType, NumberUnsignedType, NumberFloatType,                \
+    AllocatorType, JSONSerializer>
+
+// #include <nlohmann/detail/meta/cpp_future.hpp>
+
+
+#include <ciso646> // not
+#include <cstddef> // size_t
+#include <type_traits> // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type
+
+namespace nlohmann
+{
+namespace detail
+{
+// alias templates to reduce boilerplate
+template<bool B, typename T = void>
+using enable_if_t = typename std::enable_if<B, T>::type;
+
+template<typename T>
+using uncvref_t = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
+
+// implementation of C++14 index_sequence and affiliates
+// source: https://stackoverflow.com/a/32223343
+template<std::size_t... Ints>
+struct index_sequence
+{
+    using type = index_sequence;
+    using value_type = std::size_t;
+    static constexpr std::size_t size() noexcept
+    {
+        return sizeof...(Ints);
+    }
+};
+
+template<class Sequence1, class Sequence2>
+struct merge_and_renumber;
+
+template<std::size_t... I1, std::size_t... I2>
+struct merge_and_renumber<index_sequence<I1...>, index_sequence<I2...>>
+        : index_sequence < I1..., (sizeof...(I1) + I2)... > {};
+
+template<std::size_t N>
+struct make_index_sequence
+    : merge_and_renumber < typename make_index_sequence < N / 2 >::type,
+      typename make_index_sequence < N - N / 2 >::type > {};
+
+template<> struct make_index_sequence<0> : index_sequence<> {};
+template<> struct make_index_sequence<1> : index_sequence<0> {};
+
+template<typename... Ts>
+using index_sequence_for = make_index_sequence<sizeof...(Ts)>;
+
+// dispatch utility (taken from ranges-v3)
+template<unsigned N> struct priority_tag : priority_tag < N - 1 > {};
+template<> struct priority_tag<0> {};
+
+// taken from ranges-v3
+template<typename T>
+struct static_const
+{
+    static constexpr T value{};
+};
+
+template<typename T>
+constexpr T static_const<T>::value;
+}  // namespace detail
+}  // namespace nlohmann
+
+// #include <nlohmann/detail/meta/type_traits.hpp>
+
+
+#include <ciso646> // not
+#include <limits> // numeric_limits
+#include <type_traits> // false_type, is_constructible, is_integral, is_same, true_type
+#include <utility> // declval
+
+// #include <nlohmann/json_fwd.hpp>
+
+// #include <nlohmann/detail/iterators/iterator_traits.hpp>
+
+
+#include <iterator> // random_access_iterator_tag
+
+// #include <nlohmann/detail/meta/void_t.hpp>
+
+
+namespace nlohmann
+{
+namespace detail
+{
+template <typename ...Ts> struct make_void
+{
+    using type = void;
+};
+template <typename ...Ts> using void_t = typename make_void<Ts...>::type;
+} // namespace detail
+}  // namespace nlohmann
+
+// #include <nlohmann/detail/meta/cpp_future.hpp>
+
+
+namespace nlohmann
+{
+namespace detail
+{
+template <typename It, typename = void>
+struct iterator_types {};
+
+template <typename It>
+struct iterator_types <
+    It,
+    void_t<typename It::difference_type, typename It::value_type, typename It::pointer,
+    typename It::reference, typename It::iterator_category >>
+{
+    using difference_type = typename It::difference_type;
+    using value_type = typename It::value_type;
+    using pointer = typename It::pointer;
+    using reference = typename It::reference;
+    using iterator_category = typename It::iterator_category;
+};
+
+// This is required as some compilers implement std::iterator_traits in a way that
+// doesn't work with SFINAE. See https://github.com/nlohmann/json/issues/1341.
+template <typename T, typename = void>
+struct iterator_traits
+{
+};
+
+template <typename T>
+struct iterator_traits < T, enable_if_t < !std::is_pointer<T>::value >>
+            : iterator_types<T>
+{
+};
+
+template <typename T>
+struct iterator_traits<T*, enable_if_t<std::is_object<T>::value>>
+{
+    using iterator_category = std::random_access_iterator_tag;
+    using value_type = T;
+    using difference_type = ptrdiff_t;
+    using pointer = T*;
+    using reference = T&;
+};
+}
+}
+
+// #include <nlohmann/detail/meta/cpp_future.hpp>
+
+// #include <nlohmann/detail/meta/detected.hpp>
+
+
+#include <type_traits>
+
+// #include <nlohmann/detail/meta/void_t.hpp>
+
+
+// http://en.cppreference.com/w/cpp/experimental/is_detected
+namespace nlohmann
+{
+namespace detail
+{
+struct nonesuch
+{
+    nonesuch() = delete;
+    ~nonesuch() = delete;
+    nonesuch(nonesuch const&) = delete;
+    void operator=(nonesuch const&) = delete;
+};
+
+template <class Default,
+          class AlwaysVoid,
+          template <class...> class Op,
+          class... Args>
+struct detector
+{
+    using value_t = std::false_type;
+    using type = Default;
+};
+
+template <class Default, template <class...> class Op, class... Args>
+struct detector<Default, void_t<Op<Args...>>, Op, Args...>
+{
+    using value_t = std::true_type;
+    using type = Op<Args...>;
+};
+
+template <template <class...> class Op, class... Args>
+using is_detected = typename detector<nonesuch, void, Op, Args...>::value_t;
+
+template <template <class...> class Op, class... Args>
+using detected_t = typename detector<nonesuch, void, Op, Args...>::type;
+
+template <class Default, template <class...> class Op, class... Args>
+using detected_or = detector<Default, void, Op, Args...>;
+
+template <class Default, template <class...> class Op, class... Args>
+using detected_or_t = typename detected_or<Default, Op, Args...>::type;
+
+template <class Expected, template <class...> class Op, class... Args>
+using is_detected_exact = std::is_same<Expected, detected_t<Op, Args...>>;
+
+template <class To, template <class...> class Op, class... Args>
+using is_detected_convertible =
+    std::is_convertible<detected_t<Op, Args...>, To>;
+}  // namespace detail
+}  // namespace nlohmann
+
+// #include <nlohmann/detail/macro_scope.hpp>
+
+
+namespace nlohmann
+{
+/*!
+@brief detail namespace with internal helper functions
+
+This namespace collects functions that should not be exposed,
+implementations of some @ref basic_json methods, and meta-programming helpers.
+
+@since version 2.1.0
+*/
+namespace detail
+{
+/////////////
+// helpers //
+/////////////
+
+// Note to maintainers:
+//
+// Every trait in this file expects a non CV-qualified type.
+// The only exceptions are in the 'aliases for detected' section
+// (i.e. those of the form: decltype(T::member_function(std::declval<T>())))
+//
+// In this case, T has to be properly CV-qualified to constraint the function arguments
+// (e.g. to_json(BasicJsonType&, const T&))
+
+template<typename> struct is_basic_json : std::false_type {};
+
+NLOHMANN_BASIC_JSON_TPL_DECLARATION
+struct is_basic_json<NLOHMANN_BASIC_JSON_TPL> : std::true_type {};
+
+//////////////////////////
+// aliases for detected //
+//////////////////////////
+
+template <typename T>
+using mapped_type_t = typename T::mapped_type;
+
+template <typename T>
+using key_type_t = typename T::key_type;
+
+template <typename T>
+using value_type_t = typename T::value_type;
+
+template <typename T>
+using difference_type_t = typename T::difference_type;
+
+template <typename T>
+using pointer_t = typename T::pointer;
+
+template <typename T>
+using reference_t = typename T::reference;
+
+template <typename T>
+using iterator_category_t = typename T::iterator_category;
+
+template <typename T>
+using iterator_t = typename T::iterator;
+
+template <typename T, typename... Args>
+using to_json_function = decltype(T::to_json(std::declval<Args>()...));
+
+template <typename T, typename... Args>
+using from_json_function = decltype(T::from_json(std::declval<Args>()...));
+
+template <typename T, typename U>
+using get_template_function = decltype(std::declval<T>().template get<U>());
+
+// trait checking if JSONSerializer<T>::from_json(json const&, udt&) exists
+template <typename BasicJsonType, typename T, typename = void>
+struct has_from_json : std::false_type {};
+
+template <typename BasicJsonType, typename T>
+struct has_from_json<BasicJsonType, T,
+           enable_if_t<not is_basic_json<T>::value>>
+{
+    using serializer = typename BasicJsonType::template json_serializer<T, void>;
+
+    static constexpr bool value =
+        is_detected_exact<void, from_json_function, serializer,
+        const BasicJsonType&, T&>::value;
+};
+
+// This trait checks if JSONSerializer<T>::from_json(json const&) exists
+// this overload is used for non-default-constructible user-defined-types
+template <typename BasicJsonType, typename T, typename = void>
+struct has_non_default_from_json : std::false_type {};
+
+template<typename BasicJsonType, typename T>
+struct has_non_default_from_json<BasicJsonType, T, enable_if_t<not is_basic_json<T>::value>>
+{
+    using serializer = typename BasicJsonType::template json_serializer<T, void>;
+
+    static constexpr bool value =
+        is_detected_exact<T, from_json_function, serializer,
+        const BasicJsonType&>::value;
+};
+
+// This trait checks if BasicJsonType::json_serializer<T>::to_json exists
+// Do not evaluate the trait when T is a basic_json type, to avoid template instantiation infinite recursion.
+template <typename BasicJsonType, typename T, typename = void>
+struct has_to_json : std::false_type {};
+
+template <typename BasicJsonType, typename T>
+struct has_to_json<BasicJsonType, T, enable_if_t<not is_basic_json<T>::value>>
+{
+    using serializer = typename BasicJsonType::template json_serializer<T, void>;
+
+    static constexpr bool value =
+        is_detected_exact<void, to_json_function, serializer, BasicJsonType&,
+        T>::value;
+};
+
+
+///////////////////
+// is_ functions //
+///////////////////
+
+template <typename T, typename = void>
+struct is_iterator_traits : std::false_type {};
+
+template <typename T>
+struct is_iterator_traits<iterator_traits<T>>
+{
+  private:
+    using traits = iterator_traits<T>;
+
+  public:
+    static constexpr auto value =
+        is_detected<value_type_t, traits>::value &&
+        is_detected<difference_type_t, traits>::value &&
+        is_detected<pointer_t, traits>::value &&
+        is_detected<iterator_category_t, traits>::value &&
+        is_detected<reference_t, traits>::value;
+};
+
+// source: https://stackoverflow.com/a/37193089/4116453
+
+template <typename T, typename = void>
+struct is_complete_type : std::false_type {};
+
+template <typename T>
+struct is_complete_type<T, decltype(void(sizeof(T)))> : std::true_type {};
+
+template <typename BasicJsonType, typename CompatibleObjectType,
+          typename = void>
+struct is_compatible_object_type_impl : std::false_type {};
+
+template <typename BasicJsonType, typename CompatibleObjectType>
+struct is_compatible_object_type_impl <
+    BasicJsonType, CompatibleObjectType,
+    enable_if_t<is_detected<mapped_type_t, CompatibleObjectType>::value and
+    is_detected<key_type_t, CompatibleObjectType>::value >>
+{
+
+    using object_t = typename BasicJsonType::object_t;
+
+    // macOS's is_constructible does not play well with nonesuch...
+    static constexpr bool value =
+        std::is_constructible<typename object_t::key_type,
+        typename CompatibleObjectType::key_type>::value and
+        std::is_constructible<typename object_t::mapped_type,
+        typename CompatibleObjectType::mapped_type>::value;
+};
+
+template <typename BasicJsonType, typename CompatibleObjectType>
+struct is_compatible_object_type
+    : is_compatible_object_type_impl<BasicJsonType, CompatibleObjectType> {};
+
+template <typename BasicJsonType, typename ConstructibleObjectType,
+          typename = void>
+struct is_constructible_object_type_impl : std::false_type {};
+
+template <typename BasicJsonType, typename ConstructibleObjectType>
+struct is_constructible_object_type_impl <
+    BasicJsonType, ConstructibleObjectType,
+    enable_if_t<is_detected<mapped_type_t, ConstructibleObjectType>::value and
+    is_detected<key_type_t, ConstructibleObjectType>::value >>
+{
+    using object_t = typename BasicJsonType::object_t;
+
+    static constexpr bool value =
+        (std::is_constructible<typename ConstructibleObjectType::key_type, typename object_t::key_type>::value and
+         std::is_same<typename object_t::mapped_type, typename ConstructibleObjectType::mapped_type>::value) or
+        (has_from_json<BasicJsonType, typename ConstructibleObjectType::mapped_type>::value or
+         has_non_default_from_json<BasicJsonType, typename ConstructibleObjectType::mapped_type >::value);
+};
+
+template <typename BasicJsonType, typename ConstructibleObjectType>
+struct is_constructible_object_type
+    : is_constructible_object_type_impl<BasicJsonType,
+      ConstructibleObjectType> {};
+
+template <typename BasicJsonType, typename CompatibleStringType,
+          typename = void>
+struct is_compatible_string_type_impl : std::false_type {};
+
+template <typename BasicJsonType, typename CompatibleStringType>
+struct is_compatible_string_type_impl <
+    BasicJsonType, CompatibleStringType,
+    enable_if_t<is_detected_exact<typename BasicJsonType::string_t::value_type,
+    value_type_t, CompatibleStringType>::value >>
+{
+    static constexpr auto value =
+        std::is_constructible<typename BasicJsonType::string_t, CompatibleStringType>::value;
+};
+
+template <typename BasicJsonType, typename ConstructibleStringType>
+struct is_compatible_string_type
+    : is_compatible_string_type_impl<BasicJsonType, ConstructibleStringType> {};
+
+template <typename BasicJsonType, typename ConstructibleStringType,
+          typename = void>
+struct is_constructible_string_type_impl : std::false_type {};
+
+template <typename BasicJsonType, typename ConstructibleStringType>
+struct is_constructible_string_type_impl <
+    BasicJsonType, ConstructibleStringType,
+    enable_if_t<is_detected_exact<typename BasicJsonType::string_t::value_type,
+    value_type_t, ConstructibleStringType>::value >>
+{
+    static constexpr auto value =
+        std::is_constructible<ConstructibleStringType,
+        typename BasicJsonType::string_t>::value;
+};
+
+template <typename BasicJsonType, typename ConstructibleStringType>
+struct is_constructible_string_type
+    : is_constructible_string_type_impl<BasicJsonType, ConstructibleStringType> {};
+
+template <typename BasicJsonType, typename CompatibleArrayType, typename = void>
+struct is_compatible_array_type_impl : std::false_type {};
+
+template <typename BasicJsonType, typename CompatibleArrayType>
+struct is_compatible_array_type_impl <
+    BasicJsonType, CompatibleArrayType,
+    enable_if_t<is_detected<value_type_t, CompatibleArrayType>::value and
+    is_detected<iterator_t, CompatibleArrayType>::value and
+// This is needed because json_reverse_iterator has a ::iterator type...
+// Therefore it is detected as a CompatibleArrayType.
+// The real fix would be to have an Iterable concept.
+    not is_iterator_traits<
+    iterator_traits<CompatibleArrayType>>::value >>
+{
+    static constexpr bool value =
+        std::is_constructible<BasicJsonType,
+        typename CompatibleArrayType::value_type>::value;
+};
+
+template <typename BasicJsonType, typename CompatibleArrayType>
+struct is_compatible_array_type
+    : is_compatible_array_type_impl<BasicJsonType, CompatibleArrayType> {};
+
+template <typename BasicJsonType, typename ConstructibleArrayType, typename = void>
+struct is_constructible_array_type_impl : std::false_type {};
+
+template <typename BasicJsonType, typename ConstructibleArrayType>
+struct is_constructible_array_type_impl <
+    BasicJsonType, ConstructibleArrayType,
+    enable_if_t<std::is_same<ConstructibleArrayType,
+    typename BasicJsonType::value_type>::value >>
+            : std::true_type {};
+
+template <typename BasicJsonType, typename ConstructibleArrayType>
+struct is_constructible_array_type_impl <
+    BasicJsonType, ConstructibleArrayType,
+    enable_if_t<not std::is_same<ConstructibleArrayType,
+    typename BasicJsonType::value_type>::value and
+    is_detected<value_type_t, ConstructibleArrayType>::value and
+    is_detected<iterator_t, ConstructibleArrayType>::value and
+    is_complete_type<
+    detected_t<value_type_t, ConstructibleArrayType>>::value >>
+{
+    static constexpr bool value =
+        // This is needed because json_reverse_iterator has a ::iterator type,
+        // furthermore, std::back_insert_iterator (and other iterators) have a base class `iterator`...
+        // Therefore it is detected as a ConstructibleArrayType.
+        // The real fix would be to have an Iterable concept.
+        not is_iterator_traits <
+        iterator_traits<ConstructibleArrayType >>::value and
+
+        (std::is_same<typename ConstructibleArrayType::value_type, typename BasicJsonType::array_t::value_type>::value or
+         has_from_json<BasicJsonType,
+         typename ConstructibleArrayType::value_type>::value or
+         has_non_default_from_json <
+         BasicJsonType, typename ConstructibleArrayType::value_type >::value);
+};
+
+template <typename BasicJsonType, typename ConstructibleArrayType>
+struct is_constructible_array_type
+    : is_constructible_array_type_impl<BasicJsonType, ConstructibleArrayType> {};
+
+template <typename RealIntegerType, typename CompatibleNumberIntegerType,
+          typename = void>
+struct is_compatible_integer_type_impl : std::false_type {};
+
+template <typename RealIntegerType, typename CompatibleNumberIntegerType>
+struct is_compatible_integer_type_impl <
+    RealIntegerType, CompatibleNumberIntegerType,
+    enable_if_t<std::is_integral<RealIntegerType>::value and
+    std::is_integral<CompatibleNumberIntegerType>::value and
+    not std::is_same<bool, CompatibleNumberIntegerType>::value >>
+{
+    // is there an assert somewhere on overflows?
+    using RealLimits = std::numeric_limits<RealIntegerType>;
+    using CompatibleLimits = std::numeric_limits<CompatibleNumberIntegerType>;
+
+    static constexpr auto value =
+        std::is_constructible<RealIntegerType,
+        CompatibleNumberIntegerType>::value and
+        CompatibleLimits::is_integer and
+        RealLimits::is_signed == CompatibleLimits::is_signed;
+};
+
+template <typename RealIntegerType, typename CompatibleNumberIntegerType>
+struct is_compatible_integer_type
+    : is_compatible_integer_type_impl<RealIntegerType,
+      CompatibleNumberIntegerType> {};
+
+template <typename BasicJsonType, typename CompatibleType, typename = void>
+struct is_compatible_type_impl: std::false_type {};
+
+template <typename BasicJsonType, typename CompatibleType>
+struct is_compatible_type_impl <
+    BasicJsonType, CompatibleType,
+    enable_if_t<is_complete_type<CompatibleType>::value >>
+{
+    static constexpr bool value =
+        has_to_json<BasicJsonType, CompatibleType>::value;
+};
+
+template <typename BasicJsonType, typename CompatibleType>
+struct is_compatible_type
+    : is_compatible_type_impl<BasicJsonType, CompatibleType> {};
+}  // namespace detail
+}  // namespace nlohmann
+
+// #include <nlohmann/detail/exceptions.hpp>
+
+
+#include <exception> // exception
+#include <stdexcept> // runtime_error
+#include <string> // to_string
+
+// #include <nlohmann/detail/input/position_t.hpp>
+
+
+#include <cstddef> // size_t
+
+namespace nlohmann
+{
+namespace detail
+{
+/// struct to capture the start position of the current token
+struct position_t
+{
+    /// the total number of characters read
+    std::size_t chars_read_total = 0;
+    /// the number of characters read in the current line
+    std::size_t chars_read_current_line = 0;
+    /// the number of lines read
+    std::size_t lines_read = 0;
+
+    /// conversion to size_t to preserve SAX interface
+    constexpr operator size_t() const
+    {
+        return chars_read_total;
+    }
+};
+
+}
+}
+
+
+namespace nlohmann
+{
+namespace detail
+{
+////////////////
+// exceptions //
+////////////////
+
+/*!
+@brief general exception of the @ref basic_json class
+
+This class is an extension of `std::exception` objects with a member @a id for
+exception ids. It is used as the base class for all exceptions thrown by the
+@ref basic_json class. This class can hence be used as "wildcard" to catch
+exceptions.
+
+Subclasses:
+- @ref parse_error for exceptions indicating a parse error
+- @ref invalid_iterator for exceptions indicating errors with iterators
+- @ref type_error for exceptions indicating executing a member function with
+                  a wrong type
+- @ref out_of_range for exceptions indicating access out of the defined range
+- @ref other_error for exceptions indicating other library errors
+
+@internal
+@note To have nothrow-copy-constructible exceptions, we internally use
+      `std::runtime_error` which can cope with arbitrary-length error messages.
+      Intermediate strings are built with static functions and then passed to
+      the actual constructor.
+@endinternal
+
+@liveexample{The following code shows how arbitrary library exceptions can be
+caught.,exception}
+
+@since version 3.0.0
+*/
+class exception : public std::exception
+{
+  public:
+    /// returns the explanatory string
+    const char* what() const noexcept override
+    {
+        return m.what();
+    }
+
+    /// the id of the exception
+    const int id;
+
+  protected:
+    exception(int id_, const char* what_arg) : id(id_), m(what_arg) {}
+
+    static std::string name(const std::string& ename, int id_)
+    {
+        return "[json.exception." + ename + "." + std::to_string(id_) + "] ";
+    }
+
+  private:
+    /// an exception object as storage for error messages
+    std::runtime_error m;
+};
+
+/*!
+@brief exception indicating a parse error
+
+This exception is thrown by the library when a parse error occurs. Parse errors
+can occur during the deserialization of JSON text, CBOR, MessagePack, as well
+as when using JSON Patch.
+
+Member @a byte holds the byte index of the last read character in the input
+file.
+
+Exceptions have ids 1xx.
+
+name / id                      | example message | description
+------------------------------ | --------------- | -------------------------
+json.exception.parse_error.101 | parse error at 2: unexpected end of input; expected string literal | This error indicates a syntax error while deserializing a JSON text. The error message describes that an unexpected token (character) was encountered, and the member @a byte indicates the error position.
+json.exception.parse_error.102 | parse error at 14: missing or wrong low surrogate | JSON uses the `\uxxxx` format to describe Unicode characters. Code points above above 0xFFFF are split into two `\uxxxx` entries ("surrogate pairs"). This error indicates that the surrogate pair is incomplete or contains an invalid code point.
+json.exception.parse_error.103 | parse error: code points above 0x10FFFF are invalid | Unicode supports code points up to 0x10FFFF. Code points above 0x10FFFF are invalid.
+json.exception.parse_error.104 | parse error: JSON patch must be an array of objects | [RFC 6902](https://tools.ietf.org/html/rfc6902) requires a JSON Patch document to be a JSON document that represents an array of objects.
+json.exception.parse_error.105 | parse error: operation must have string member 'op' | An operation of a JSON Patch document must contain exactly one "op" member, whose value indicates the operation to perform. Its value must be one of "add", "remove", "replace", "move", "copy", or "test"; other values are errors.
+json.exception.parse_error.106 | parse error: array index '01' must not begin with '0' | An array index in a JSON Pointer ([RFC 6901](https://tools.ietf.org/html/rfc6901)) may be `0` or any number without a leading `0`.
+json.exception.parse_error.107 | parse error: JSON pointer must be empty or begin with '/' - was: 'foo' | A JSON Pointer must be a Unicode string containing a sequence of zero or more reference tokens, each prefixed by a `/` character.
+json.exception.parse_error.108 | parse error: escape character '~' must be followed with '0' or '1' | In a JSON Pointer, only `~0` and `~1` are valid escape sequences.
+json.exception.parse_error.109 | parse error: array index 'one' is not a number | A JSON Pointer array index must be a number.
+json.exception.parse_error.110 | parse error at 1: cannot read 2 bytes from vector | When parsing CBOR or MessagePack, the byte vector ends before the complete value has been read.
+json.exception.parse_error.112 | parse error at 1: error reading CBOR; last byte: 0xF8 | Not all types of CBOR or MessagePack are supported. This exception occurs if an unsupported byte was read.
+json.exception.parse_error.113 | parse error at 2: expected a CBOR string; last byte: 0x98 | While parsing a map key, a value that is not a string has been read.
+json.exception.parse_error.114 | parse error: Unsupported BSON record type 0x0F | The parsing of the corresponding BSON record type is not implemented (yet).
+
+@note For an input with n bytes, 1 is the index of the first character and n+1
+      is the index of the terminating null byte or the end of file. This also
+      holds true when reading a byte vector (CBOR or MessagePack).
+
+@liveexample{The following code shows how a `parse_error` exception can be
+caught.,parse_error}
+
+@sa @ref exception for the base class of the library exceptions
+@sa @ref invalid_iterator for exceptions indicating errors with iterators
+@sa @ref type_error for exceptions indicating executing a member function with
+                    a wrong type
+@sa @ref out_of_range for exceptions indicating access out of the defined range
+@sa @ref other_error for exceptions indicating other library errors
+
+@since version 3.0.0
+*/
+class parse_error : public exception
+{
+  public:
+    /*!
+    @brief create a parse error exception
+    @param[in] id_       the id of the exception
+    @param[in] position  the position where the error occurred (or with
+                         chars_read_total=0 if the position cannot be
+                         determined)
+    @param[in] what_arg  the explanatory string
+    @return parse_error object
+    */
+    static parse_error create(int id_, const position_t& pos, const std::string& what_arg)
+    {
+        std::string w = exception::name("parse_error", id_) + "parse error" +
+                        position_string(pos) + ": " + what_arg;
+        return parse_error(id_, pos.chars_read_total, w.c_str());
+    }
+
+    static parse_error create(int id_, std::size_t byte_, const std::string& what_arg)
+    {
+        std::string w = exception::name("parse_error", id_) + "parse error" +
+                        (byte_ != 0 ? (" at byte " + std::to_string(byte_)) : "") +
+                        ": " + what_arg;
+        return parse_error(id_, byte_, w.c_str());
+    }
+
+    /*!
+    @brief byte index of the parse error
+
+    The byte index of the last read character in the input file.
+
+    @note For an input with n bytes, 1 is the index of the first character and
+          n+1 is the index of the terminating null byte or the end of file.
+          This also holds true when reading a byte vector (CBOR or MessagePack).
+    */
+    const std::size_t byte;
+
+  private:
+    parse_error(int id_, std::size_t byte_, const char* what_arg)
+        : exception(id_, what_arg), byte(byte_) {}
+
+    static std::string position_string(const position_t& pos)
+    {
+        return " at line " + std::to_string(pos.lines_read + 1) +
+               ", column " + std::to_string(pos.chars_read_current_line);
+    }
+};
+
+/*!
+@brief exception indicating errors with iterators
+
+This exception is thrown if iterators passed to a library function do not match
+the expected semantics.
+
+Exceptions have ids 2xx.
+
+name / id                           | example message | description
+----------------------------------- | --------------- | -------------------------
+json.exception.invalid_iterator.201 | iterators are not compatible | The iterators passed to constructor @ref basic_json(InputIT first, InputIT last) are not compatible, meaning they do not belong to the same container. Therefore, the range (@a first, @a last) is invalid.
+json.exception.invalid_iterator.202 | iterator does not fit current value | In an erase or insert function, the passed iterator @a pos does not belong to the JSON value for which the function was called. It hence does not define a valid position for the deletion/insertion.
+json.exception.invalid_iterator.203 | iterators do not fit current value | Either iterator passed to function @ref erase(IteratorType first, IteratorType last) does not belong to the JSON value from which values shall be erased. It hence does not define a valid range to delete values from.
+json.exception.invalid_iterator.204 | iterators out of range | When an iterator range for a primitive type (number, boolean, or string) is passed to a constructor or an erase function, this range has to be exactly (@ref begin(), @ref end()), because this is the only way the single stored value is expressed. All other ranges are invalid.
+json.exception.invalid_iterator.205 | iterator out of range | When an iterator for a primitive type (number, boolean, or string) is passed to an erase function, the iterator has to be the @ref begin() iterator, because it is the only way to address the stored value. All other iterators are invalid.
+json.exception.invalid_iterator.206 | cannot construct with iterators from null | The iterators passed to constructor @ref basic_json(InputIT first, InputIT last) belong to a JSON null value and hence to not define a valid range.
+json.exception.invalid_iterator.207 | cannot use key() for non-object iterators | The key() member function can only be used on iterators belonging to a JSON object, because other types do not have a concept of a key.
+json.exception.invalid_iterator.208 | cannot use operator[] for object iterators | The operator[] to specify a concrete offset cannot be used on iterators belonging to a JSON object, because JSON objects are unordered.
+json.exception.invalid_iterator.209 | cannot use offsets with object iterators | The offset operators (+, -, +=, -=) cannot be used on iterators belonging to a JSON object, because JSON objects are unordered.
+json.exception.invalid_iterator.210 | iterators do not fit | The iterator range passed to the insert function are not compatible, meaning they do not belong to the same container. Therefore, the range (@a first, @a last) is invalid.
+json.exception.invalid_iterator.211 | passed iterators may not belong to container | The iterator range passed to the insert function must not be a subrange of the container to insert to.
+json.exception.invalid_iterator.212 | cannot compare iterators of different containers | When two iterators are compared, they must belong to the same container.
+json.exception.invalid_iterator.213 | cannot compare order of object iterators | The order of object iterators cannot be compared, because JSON objects are unordered.
+json.exception.invalid_iterator.214 | cannot get value | Cannot get value for iterator: Either the iterator belongs to a null value or it is an iterator to a primitive type (number, boolean, or string), but the iterator is different to @ref begin().
+
+@liveexample{The following code shows how an `invalid_iterator` exception can be
+caught.,invalid_iterator}
+
+@sa @ref exception for the base class of the library exceptions
+@sa @ref parse_error for exceptions indicating a parse error
+@sa @ref type_error for exceptions indicating executing a member function with
+                    a wrong type
+@sa @ref out_of_range for exceptions indicating access out of the defined range
+@sa @ref other_error for exceptions indicating other library errors
+
+@since version 3.0.0
+*/
+class invalid_iterator : public exception
+{
+  public:
+    static invalid_iterator create(int id_, const std::string& what_arg)
+    {
+        std::string w = exception::name("invalid_iterator", id_) + what_arg;
+        return invalid_iterator(id_, w.c_str());
+    }
+
+  private:
+    invalid_iterator(int id_, const char* what_arg)
+        : exception(id_, what_arg) {}
+};
+
+/*!
+@brief exception indicating executing a member function with a wrong type
+
+This exception is thrown in case of a type error; that is, a library function is
+executed on a JSON value whose type does not match the expected semantics.
+
+Exceptions have ids 3xx.
+
+name / id                     | example message | description
+----------------------------- | --------------- | -------------------------
+json.exception.type_error.301 | cannot create object from initializer list | To create an object from an initializer list, the initializer list must consist only of a list of pairs whose first element is a string. When this constraint is violated, an array is created instead.
+json.exception.type_error.302 | type must be object, but is array | During implicit or explicit value conversion, the JSON type must be compatible to the target type. For instance, a JSON string can only be converted into string types, but not into numbers or boolean types.
+json.exception.type_error.303 | incompatible ReferenceType for get_ref, actual type is object | To retrieve a reference to a value stored in a @ref basic_json object with @ref get_ref, the type of the reference must match the value type. For instance, for a JSON array, the @a ReferenceType must be @ref array_t&.
+json.exception.type_error.304 | cannot use at() with string | The @ref at() member functions can only be executed for certain JSON types.
+json.exception.type_error.305 | cannot use operator[] with string | The @ref operator[] member functions can only be executed for certain JSON types.
+json.exception.type_error.306 | cannot use value() with string | The @ref value() member functions can only be executed for certain JSON types.
+json.exception.type_error.307 | cannot use erase() with string | The @ref erase() member functions can only be executed for certain JSON types.
+json.exception.type_error.308 | cannot use push_back() with string | The @ref push_back() and @ref operator+= member functions can only be executed for certain JSON types.
+json.exception.type_error.309 | cannot use insert() with | The @ref insert() member functions can only be executed for certain JSON types.
+json.exception.type_error.310 | cannot use swap() with number | The @ref swap() member functions can only be executed for certain JSON types.
+json.exception.type_error.311 | cannot use emplace_back() with string | The @ref emplace_back() member function can only be executed for certain JSON types.
+json.exception.type_error.312 | cannot use update() with string | The @ref update() member functions can only be executed for certain JSON types.
+json.exception.type_error.313 | invalid value to unflatten | The @ref unflatten function converts an object whose keys are JSON Pointers back into an arbitrary nested JSON value. The JSON Pointers must not overlap, because then the resulting value would not be well defined.
+json.exception.type_error.314 | only objects can be unflattened | The @ref unflatten function only works for an object whose keys are JSON Pointers.
+json.exception.type_error.315 | values in object must be primitive | The @ref unflatten function only works for an object whose keys are JSON Pointers and whose values are primitive.
+json.exception.type_error.316 | invalid UTF-8 byte at index 10: 0x7E | The @ref dump function only works with UTF-8 encoded strings; that is, if you assign a `std::string` to a JSON value, make sure it is UTF-8 encoded. |
+json.exception.type_error.317 | JSON value cannot be serialized to requested format | The dynamic type of the object cannot be represented in the requested serialization format (e.g. a raw `true` or `null` JSON object cannot be serialized to BSON) |
+
+@liveexample{The following code shows how a `type_error` exception can be
+caught.,type_error}
+
+@sa @ref exception for the base class of the library exceptions
+@sa @ref parse_error for exceptions indicating a parse error
+@sa @ref invalid_iterator for exceptions indicating errors with iterators
+@sa @ref out_of_range for exceptions indicating access out of the defined range
+@sa @ref other_error for exceptions indicating other library errors
+
+@since version 3.0.0
+*/
+class type_error : public exception
+{
+  public:
+    static type_error create(int id_, const std::string& what_arg)
+    {
+        std::string w = exception::name("type_error", id_) + what_arg;
+        return type_error(id_, w.c_str());
+    }
+
+  private:
+    type_error(int id_, const char* what_arg) : exception(id_, what_arg) {}
+};
+
+/*!
+@brief exception indicating access out of the defined range
+
+This exception is thrown in case a library function is called on an input
+parameter that exceeds the expected range, for instance in case of array
+indices or nonexisting object keys.
+
+Exceptions have ids 4xx.
+
+name / id                       | example message | description
+------------------------------- | --------------- | -------------------------
+json.exception.out_of_range.401 | array index 3 is out of range | The provided array index @a i is larger than @a size-1.
+json.exception.out_of_range.402 | array index '-' (3) is out of range | The special array index `-` in a JSON Pointer never describes a valid element of the array, but the index past the end. That is, it can only be used to add elements at this position, but not to read it.
+json.exception.out_of_range.403 | key 'foo' not found | The provided key was not found in the JSON object.
+json.exception.out_of_range.404 | unresolved reference token 'foo' | A reference token in a JSON Pointer could not be resolved.
+json.exception.out_of_range.405 | JSON pointer has no parent | The JSON Patch operations 'remove' and 'add' can not be applied to the root element of the JSON value.
+json.exception.out_of_range.406 | number overflow parsing '10E1000' | A parsed number could not be stored as without changing it to NaN or INF.
+json.exception.out_of_range.407 | number overflow serializing '9223372036854775808' | UBJSON and BSON only support integer numbers up to 9223372036854775807. |
+json.exception.out_of_range.408 | excessive array size: 8658170730974374167 | The size (following `#`) of an UBJSON array or object exceeds the maximal capacity. |
+json.exception.out_of_range.409 | BSON key cannot contain code point U+0000 (at byte 2) | Key identifiers to be serialized to BSON cannot contain code point U+0000, since the key is stored as zero-terminated c-string |
+
+@liveexample{The following code shows how an `out_of_range` exception can be
+caught.,out_of_range}
+
+@sa @ref exception for the base class of the library exceptions
+@sa @ref parse_error for exceptions indicating a parse error
+@sa @ref invalid_iterator for exceptions indicating errors with iterators
+@sa @ref type_error for exceptions indicating executing a member function with
+                    a wrong type
+@sa @ref other_error for exceptions indicating other library errors
+
+@since version 3.0.0
+*/
+class out_of_range : public exception
+{
+  public:
+    static out_of_range create(int id_, const std::string& what_arg)
+    {
+        std::string w = exception::name("out_of_range", id_) + what_arg;
+        return out_of_range(id_, w.c_str());
+    }
+
+  private:
+    out_of_range(int id_, const char* what_arg) : exception(id_, what_arg) {}
+};
+
+/*!
+@brief exception indicating other library errors
+
+This exception is thrown in case of errors that cannot be classified with the
+other exception types.
+
+Exceptions have ids 5xx.
+
+name / id                      | example message | description
+------------------------------ | --------------- | -------------------------
+json.exception.other_error.501 | unsuccessful: {"op":"test","path":"/baz", "value":"bar"} | A JSON Patch operation 'test' failed. The unsuccessful operation is also printed.
+
+@sa @ref exception for the base class of the library exceptions
+@sa @ref parse_error for exceptions indicating a parse error
+@sa @ref invalid_iterator for exceptions indicating errors with iterators
+@sa @ref type_error for exceptions indicating executing a member function with
+                    a wrong type
+@sa @ref out_of_range for exceptions indicating access out of the defined range
+
+@liveexample{The following code shows how an `other_error` exception can be
+caught.,other_error}
+
+@since version 3.0.0
+*/
+class other_error : public exception
+{
+  public:
+    static other_error create(int id_, const std::string& what_arg)
+    {
+        std::string w = exception::name("other_error", id_) + what_arg;
+        return other_error(id_, w.c_str());
+    }
+
+  private:
+    other_error(int id_, const char* what_arg) : exception(id_, what_arg) {}
+};
+}  // namespace detail
+}  // namespace nlohmann
+
+// #include <nlohmann/detail/value_t.hpp>
+
+
+#include <array> // array
+#include <ciso646> // and
+#include <cstddef> // size_t
+#include <cstdint> // uint8_t
+
+namespace nlohmann
+{
+namespace detail
+{
+///////////////////////////
+// JSON type enumeration //
+///////////////////////////
+
+/*!
+@brief the JSON type enumeration
+
+This enumeration collects the different JSON types. It is internally used to
+distinguish the stored values, and the functions @ref basic_json::is_null(),
+@ref basic_json::is_object(), @ref basic_json::is_array(),
+@ref basic_json::is_string(), @ref basic_json::is_boolean(),
+@ref basic_json::is_number() (with @ref basic_json::is_number_integer(),
+@ref basic_json::is_number_unsigned(), and @ref basic_json::is_number_float()),
+@ref basic_json::is_discarded(), @ref basic_json::is_primitive(), and
+@ref basic_json::is_structured() rely on it.
+
+@note There are three enumeration entries (number_integer, number_unsigned, and
+number_float), because the library distinguishes these three types for numbers:
+@ref basic_json::number_unsigned_t is used for unsigned integers,
+@ref basic_json::number_integer_t is used for signed integers, and
+@ref basic_json::number_float_t is used for floating-point numbers or to
+approximate integers which do not fit in the limits of their respective type.
+
+@sa @ref basic_json::basic_json(const value_t value_type) -- create a JSON
+value with the default value for a given type
+
+@since version 1.0.0
+*/
+enum class value_t : std::uint8_t
+{
+    null,             ///< null value
+    object,           ///< object (unordered set of name/value pairs)
+    array,            ///< array (ordered collection of values)
+    string,           ///< string value
+    boolean,          ///< boolean value
+    number_integer,   ///< number value (signed integer)
+    number_unsigned,  ///< number value (unsigned integer)
+    number_float,     ///< number value (floating-point)
+    discarded         ///< discarded by the the parser callback function
+};
+
+/*!
+@brief comparison operator for JSON types
+
+Returns an ordering that is similar to Python:
+- order: null < boolean < number < object < array < string
+- furthermore, each type is not smaller than itself
+- discarded values are not comparable
+
+@since version 1.0.0
+*/
+inline bool operator<(const value_t lhs, const value_t rhs) noexcept
+{
+    static constexpr std::array<std::uint8_t, 8> order = {{
+            0 /* null */, 3 /* object */, 4 /* array */, 5 /* string */,
+            1 /* boolean */, 2 /* integer */, 2 /* unsigned */, 2 /* float */
+        }
+    };
+
+    const auto l_index = static_cast<std::size_t>(lhs);
+    const auto r_index = static_cast<std::size_t>(rhs);
+    return l_index < order.size() and r_index < order.size() and order[l_index] < order[r_index];
+}
+}  // namespace detail
+}  // namespace nlohmann
+
+// #include <nlohmann/detail/conversions/from_json.hpp>
+
+
+#include <algorithm> // transform
+#include <array> // array
+#include <ciso646> // and, not
+#include <forward_list> // forward_list
+#include <iterator> // inserter, front_inserter, end
+#include <map> // map
+#include <string> // string
+#include <tuple> // tuple, make_tuple
+#include <type_traits> // is_arithmetic, is_same, is_enum, underlying_type, is_convertible
+#include <unordered_map> // unordered_map
+#include <utility> // pair, declval
+#include <valarray> // valarray
+
+// #include <nlohmann/detail/exceptions.hpp>
+
+// #include <nlohmann/detail/macro_scope.hpp>
+
+// #include <nlohmann/detail/meta/cpp_future.hpp>
+
+// #include <nlohmann/detail/meta/type_traits.hpp>
+
+// #include <nlohmann/detail/value_t.hpp>
+
+
+namespace nlohmann
+{
+namespace detail
+{
+template<typename BasicJsonType>
+void from_json(const BasicJsonType& j, typename std::nullptr_t& n)
+{
+    if (JSON_UNLIKELY(not j.is_null()))
+    {
+        JSON_THROW(type_error::create(302, "type must be null, but is " + std::string(j.type_name())));
+    }
+    n = nullptr;
+}
+
+// overloads for basic_json template parameters
+template<typename BasicJsonType, typename ArithmeticType,
+         enable_if_t<std::is_arithmetic<ArithmeticType>::value and
+                     not std::is_same<ArithmeticType, typename BasicJsonType::boolean_t>::value,
+                     int> = 0>
+void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val)
+{
+    switch (static_cast<value_t>(j))
+    {
+        case value_t::number_unsigned:
+        {
+            val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>());
+            break;
+        }
+        case value_t::number_integer:
+        {
+            val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_integer_t*>());
+            break;
+        }
+        case value_t::number_float:
+        {
+            val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_float_t*>());
+            break;
+        }
+
+        default:
+            JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name())));
+    }
+}
+
+template<typename BasicJsonType>
+void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& b)
+{
+    if (JSON_UNLIKELY(not j.is_boolean()))
+    {
+        JSON_THROW(type_error::create(302, "type must be boolean, but is " + std::string(j.type_name())));
+    }
+    b = *j.template get_ptr<const typename BasicJsonType::boolean_t*>();
+}
+
+template<typename BasicJsonType>
+void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& s)
+{
+    if (JSON_UNLIKELY(not j.is_string()))
+    {
+        JSON_THROW(type_error::create(302, "type must be string, but is " + std::string(j.type_name())));
+    }
+    s = *j.template get_ptr<const typename BasicJsonType::string_t*>();
+}
+
+template <
+    typename BasicJsonType, typename ConstructibleStringType,
+    enable_if_t <
+        is_constructible_string_type<BasicJsonType, ConstructibleStringType>::value and
+        not std::is_same<typename BasicJsonType::string_t,
+                         ConstructibleStringType>::value,
+        int > = 0 >
+void from_json(const BasicJsonType& j, ConstructibleStringType& s)
+{
+    if (JSON_UNLIKELY(not j.is_string()))
+    {
+        JSON_THROW(type_error::create(302, "type must be string, but is " + std::string(j.type_name())));
+    }
+
+    s = *j.template get_ptr<const typename BasicJsonType::string_t*>();
+}
+
+template<typename BasicJsonType>
+void from_json(const BasicJsonType& j, typename BasicJsonType::number_float_t& val)
+{
+    get_arithmetic_value(j, val);
+}
+
+template<typename BasicJsonType>
+void from_json(const BasicJsonType& j, typename BasicJsonType::number_unsigned_t& val)
+{
+    get_arithmetic_value(j, val);
+}
+
+template<typename BasicJsonType>
+void from_json(const BasicJsonType& j, typename BasicJsonType::number_integer_t& val)
+{
+    get_arithmetic_value(j, val);
+}
+
+template<typename BasicJsonType, typename EnumType,
+         enable_if_t<std::is_enum<EnumType>::value, int> = 0>
+void from_json(const BasicJsonType& j, EnumType& e)
+{
+    typename std::underlying_type<EnumType>::type val;
+    get_arithmetic_value(j, val);
+    e = static_cast<EnumType>(val);
+}
+
+// forward_list doesn't have an insert method
+template<typename BasicJsonType, typename T, typename Allocator,
+         enable_if_t<std::is_convertible<BasicJsonType, T>::value, int> = 0>
+void from_json(const BasicJsonType& j, std::forward_list<T, Allocator>& l)
+{
+    if (JSON_UNLIKELY(not j.is_array()))
+    {
+        JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name())));
+    }
+    std::transform(j.rbegin(), j.rend(),
+                   std::front_inserter(l), [](const BasicJsonType & i)
+    {
+        return i.template get<T>();
+    });
+}
+
+// valarray doesn't have an insert method
+template<typename BasicJsonType, typename T,
+         enable_if_t<std::is_convertible<BasicJsonType, T>::value, int> = 0>
+void from_json(const BasicJsonType& j, std::valarray<T>& l)
+{
+    if (JSON_UNLIKELY(not j.is_array()))
+    {
+        JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name())));
+    }
+    l.resize(j.size());
+    std::copy(j.m_value.array->begin(), j.m_value.array->end(), std::begin(l));
+}
+
+template<typename BasicJsonType>
+void from_json_array_impl(const BasicJsonType& j, typename BasicJsonType::array_t& arr, priority_tag<3> /*unused*/)
+{
+    arr = *j.template get_ptr<const typename BasicJsonType::array_t*>();
+}
+
+template <typename BasicJsonType, typename T, std::size_t N>
+auto from_json_array_impl(const BasicJsonType& j, std::array<T, N>& arr,
+                          priority_tag<2> /*unused*/)
+-> decltype(j.template get<T>(), void())
+{
+    for (std::size_t i = 0; i < N; ++i)
+    {
+        arr[i] = j.at(i).template get<T>();
+    }
+}
+
+template<typename BasicJsonType, typename ConstructibleArrayType>
+auto from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, priority_tag<1> /*unused*/)
+-> decltype(
+    arr.reserve(std::declval<typename ConstructibleArrayType::size_type>()),
+    j.template get<typename ConstructibleArrayType::value_type>(),
+    void())
+{
+    using std::end;
+
+    arr.reserve(j.size());
+    std::transform(j.begin(), j.end(),
+                   std::inserter(arr, end(arr)), [](const BasicJsonType & i)
+    {
+        // get<BasicJsonType>() returns *this, this won't call a from_json
+        // method when value_type is BasicJsonType
+        return i.template get<typename ConstructibleArrayType::value_type>();
+    });
+}
+
+template <typename BasicJsonType, typename ConstructibleArrayType>
+void from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr,
+                          priority_tag<0> /*unused*/)
+{
+    using std::end;
+
+    std::transform(
+        j.begin(), j.end(), std::inserter(arr, end(arr)),
+        [](const BasicJsonType & i)
+    {
+        // get<BasicJsonType>() returns *this, this won't call a from_json
+        // method when value_type is BasicJsonType
+        return i.template get<typename ConstructibleArrayType::value_type>();
+    });
+}
+
+template <typename BasicJsonType, typename ConstructibleArrayType,
+          enable_if_t <
+              is_constructible_array_type<BasicJsonType, ConstructibleArrayType>::value and
+              not is_constructible_object_type<BasicJsonType, ConstructibleArrayType>::value and
+              not is_constructible_string_type<BasicJsonType, ConstructibleArrayType>::value and
+              not is_basic_json<ConstructibleArrayType>::value,
+              int > = 0 >
+
+auto from_json(const BasicJsonType& j, ConstructibleArrayType& arr)
+-> decltype(from_json_array_impl(j, arr, priority_tag<3> {}),
+j.template get<typename ConstructibleArrayType::value_type>(),
+void())
+{
+    if (JSON_UNLIKELY(not j.is_array()))
+    {
+        JSON_THROW(type_error::create(302, "type must be array, but is " +
+                                      std::string(j.type_name())));
+    }
+
+    from_json_array_impl(j, arr, priority_tag<3> {});
+}
+
+template<typename BasicJsonType, typename ConstructibleObjectType,
+         enable_if_t<is_constructible_object_type<BasicJsonType, ConstructibleObjectType>::value, int> = 0>
+void from_json(const BasicJsonType& j, ConstructibleObjectType& obj)
+{
+    if (JSON_UNLIKELY(not j.is_object()))
+    {
+        JSON_THROW(type_error::create(302, "type must be object, but is " + std::string(j.type_name())));
+    }
+
+    auto inner_object = j.template get_ptr<const typename BasicJsonType::object_t*>();
+    using value_type = typename ConstructibleObjectType::value_type;
+    std::transform(
+        inner_object->begin(), inner_object->end(),
+        std::inserter(obj, obj.begin()),
+        [](typename BasicJsonType::object_t::value_type const & p)
+    {
+        return value_type(p.first, p.second.template get<typename ConstructibleObjectType::mapped_type>());
+    });
+}
+
+// overload for arithmetic types, not chosen for basic_json template arguments
+// (BooleanType, etc..); note: Is it really necessary to provide explicit
+// overloads for boolean_t etc. in case of a custom BooleanType which is not
+// an arithmetic type?
+template<typename BasicJsonType, typename ArithmeticType,
+         enable_if_t <
+             std::is_arithmetic<ArithmeticType>::value and
+             not std::is_same<ArithmeticType, typename BasicJsonType::number_unsigned_t>::value and
+             not std::is_same<ArithmeticType, typename BasicJsonType::number_integer_t>::value and
+             not std::is_same<ArithmeticType, typename BasicJsonType::number_float_t>::value and
+             not std::is_same<ArithmeticType, typename BasicJsonType::boolean_t>::value,
+             int> = 0>
+void from_json(const BasicJsonType& j, ArithmeticType& val)
+{
+    switch (static_cast<value_t>(j))
+    {
+        case value_t::number_unsigned:
+        {
+            val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>());
+            break;
+        }
+        case value_t::number_integer:
+        {
+            val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_integer_t*>());
+            break;
+        }
+        case value_t::number_float:
+        {
+            val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_float_t*>());
+            break;
+        }
+        case value_t::boolean:
+        {
+            val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::boolean_t*>());
+            break;
+        }
+
+        default:
+            JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name())));
+    }
+}
+
+template<typename BasicJsonType, typename A1, typename A2>
+void from_json(const BasicJsonType& j, std::pair<A1, A2>& p)
+{
+    p = {j.at(0).template get<A1>(), j.at(1).template get<A2>()};
+}
+
+template<typename BasicJsonType, typename Tuple, std::size_t... Idx>
+void from_json_tuple_impl(const BasicJsonType& j, Tuple& t, index_sequence<Idx...> /*unused*/)
+{
+    t = std::make_tuple(j.at(Idx).template get<typename std::tuple_element<Idx, Tuple>::type>()...);
+}
+
+template<typename BasicJsonType, typename... Args>
+void from_json(const BasicJsonType& j, std::tuple<Args...>& t)
+{
+    from_json_tuple_impl(j, t, index_sequence_for<Args...> {});
+}
+
+template <typename BasicJsonType, typename Key, typename Value, typename Compare, typename Allocator,
+          typename = enable_if_t<not std::is_constructible<
+                                     typename BasicJsonType::string_t, Key>::value>>
+void from_json(const BasicJsonType& j, std::map<Key, Value, Compare, Allocator>& m)
+{
+    if (JSON_UNLIKELY(not j.is_array()))
+    {
+        JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name())));
+    }
+    for (const auto& p : j)
+    {
+        if (JSON_UNLIKELY(not p.is_array()))
+        {
+            JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(p.type_name())));
+        }
+        m.emplace(p.at(0).template get<Key>(), p.at(1).template get<Value>());
+    }
+}
+
+template <typename BasicJsonType, typename Key, typename Value, typename Hash, typename KeyEqual, typename Allocator,
+          typename = enable_if_t<not std::is_constructible<
+                                     typename BasicJsonType::string_t, Key>::value>>
+void from_json(const BasicJsonType& j, std::unordered_map<Key, Value, Hash, KeyEqual, Allocator>& m)
+{
+    if (JSON_UNLIKELY(not j.is_array()))
+    {
+        JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name())));
+    }
+    for (const auto& p : j)
+    {
+        if (JSON_UNLIKELY(not p.is_array()))
+        {
+            JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(p.type_name())));
+        }
+        m.emplace(p.at(0).template get<Key>(), p.at(1).template get<Value>());
+    }
+}
+
+struct from_json_fn
+{
+    template<typename BasicJsonType, typename T>
+    auto operator()(const BasicJsonType& j, T& val) const
+    noexcept(noexcept(from_json(j, val)))
+    -> decltype(from_json(j, val), void())
+    {
+        return from_json(j, val);
+    }
+};
+}  // namespace detail
+
+/// namespace to hold default `from_json` function
+/// to see why this is required:
+/// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html
+namespace
+{
+constexpr const auto& from_json = detail::static_const<detail::from_json_fn>::value;
+} // namespace
+}  // namespace nlohmann
+
+// #include <nlohmann/detail/conversions/to_json.hpp>
+
+
+#include <ciso646> // or, and, not
+#include <iterator> // begin, end
+#include <tuple> // tuple, get
+#include <type_traits> // is_same, is_constructible, is_floating_point, is_enum, underlying_type
+#include <utility> // move, forward, declval, pair
+#include <valarray> // valarray
+#include <vector> // vector
+
+// #include <nlohmann/detail/meta/cpp_future.hpp>
+
+// #include <nlohmann/detail/meta/type_traits.hpp>
+
+// #include <nlohmann/detail/value_t.hpp>
+
+// #include <nlohmann/detail/iterators/iteration_proxy.hpp>
+
+
+#include <cstddef> // size_t
+#include <string> // string, to_string
+#include <iterator> // input_iterator_tag
+#include <tuple> // tuple_size, get, tuple_element
+
+// #include <nlohmann/detail/value_t.hpp>
+
+// #include <nlohmann/detail/meta/type_traits.hpp>
+
+
+namespace nlohmann
+{
+namespace detail
+{
+template <typename IteratorType> class iteration_proxy_value
+{
+  public:
+    using difference_type = std::ptrdiff_t;
+    using value_type = iteration_proxy_value;
+    using pointer = value_type * ;
+    using reference = value_type & ;
+    using iterator_category = std::input_iterator_tag;
+
+  private:
+    /// the iterator
+    IteratorType anchor;
+    /// an index for arrays (used to create key names)
+    std::size_t array_index = 0;
+    /// last stringified array index
+    mutable std::size_t array_index_last = 0;
+    /// a string representation of the array index
+    mutable std::string array_index_str = "0";
+    /// an empty string (to return a reference for primitive values)
+    const std::string empty_str = "";
+
+  public:
+    explicit iteration_proxy_value(IteratorType it) noexcept : anchor(it) {}
+
+    /// dereference operator (needed for range-based for)
+    iteration_proxy_value& operator*()
+    {
+        return *this;
+    }
+
+    /// increment operator (needed for range-based for)
+    iteration_proxy_value& operator++()
+    {
+        ++anchor;
+        ++array_index;
+
+        return *this;
+    }
+
+    /// equality operator (needed for InputIterator)
+    bool operator==(const iteration_proxy_value& o) const noexcept
+    {
+        return anchor == o.anchor;
+    }
+
+    /// inequality operator (needed for range-based for)
+    bool operator!=(const iteration_proxy_value& o) const noexcept
+    {
+        return anchor != o.anchor;
+    }
+
+    /// return key of the iterator
+    const std::string& key() const
+    {
+        assert(anchor.m_object != nullptr);
+
+        switch (anchor.m_object->type())
+        {
+            // use integer array index as key
+            case value_t::array:
+            {
+                if (array_index != array_index_last)
+                {
+                    array_index_str = std::to_string(array_index);
+                    array_index_last = array_index;
+                }
+                return array_index_str;
+            }
+
+            // use key from the object
+            case value_t::object:
+                return anchor.key();
+
+            // use an empty key for all primitive types
+            default:
+                return empty_str;
+        }
+    }
+
+    /// return value of the iterator
+    typename IteratorType::reference value() const
+    {
+        return anchor.value();
+    }
+};
+
+/// proxy class for the items() function
+template<typename IteratorType> class iteration_proxy
+{
+  private:
+    /// the container to iterate
+    typename IteratorType::reference container;
+
+  public:
+    /// construct iteration proxy from a container
+    explicit iteration_proxy(typename IteratorType::reference cont) noexcept
+        : container(cont) {}
+
+    /// return iterator begin (needed for range-based for)
+    iteration_proxy_value<IteratorType> begin() noexcept
+    {
+        return iteration_proxy_value<IteratorType>(container.begin());
+    }
+
+    /// return iterator end (needed for range-based for)
+    iteration_proxy_value<IteratorType> end() noexcept
+    {
+        return iteration_proxy_value<IteratorType>(container.end());
+    }
+};
+// Structured Bindings Support
+// For further reference see https://blog.tartanllama.xyz/structured-bindings/
+// And see https://github.com/nlohmann/json/pull/1391
+template <std::size_t N, typename IteratorType, enable_if_t<N == 0, int> = 0>
+auto get(const nlohmann::detail::iteration_proxy_value<IteratorType>& i) -> decltype(i.key())
+{
+    return i.key();
+}
+// Structured Bindings Support
+// For further reference see https://blog.tartanllama.xyz/structured-bindings/
+// And see https://github.com/nlohmann/json/pull/1391
+template <std::size_t N, typename IteratorType, enable_if_t<N == 1, int> = 0>
+auto get(const nlohmann::detail::iteration_proxy_value<IteratorType>& i) -> decltype(i.value())
+{
+    return i.value();
+}
+}  // namespace detail
+}  // namespace nlohmann
+
+// The Addition to the STD Namespace is required to add
+// Structured Bindings Support to the iteration_proxy_value class
+// For further reference see https://blog.tartanllama.xyz/structured-bindings/
+// And see https://github.com/nlohmann/json/pull/1391
+namespace std
+{
+template <typename IteratorType>
+class tuple_size<::nlohmann::detail::iteration_proxy_value<IteratorType>>
+            : public std::integral_constant<std::size_t, 2> {};
+
+template <std::size_t N, typename IteratorType>
+class tuple_element<N, ::nlohmann::detail::iteration_proxy_value<IteratorType >>
+{
+  public:
+    using type = decltype(
+                     get<N>(std::declval <
+                            ::nlohmann::detail::iteration_proxy_value<IteratorType >> ()));
+};
+}
+
+namespace nlohmann
+{
+namespace detail
+{
+//////////////////
+// constructors //
+//////////////////
+
+template<value_t> struct external_constructor;
+
+template<>
+struct external_constructor<value_t::boolean>
+{
+    template<typename BasicJsonType>
+    static void construct(BasicJsonType& j, typename BasicJsonType::boolean_t b) noexcept
+    {
+        j.m_type = value_t::boolean;
+        j.m_value = b;
+        j.assert_invariant();
+    }
+};
+
+template<>
+struct external_constructor<value_t::string>
+{
+    template<typename BasicJsonType>
+    static void construct(BasicJsonType& j, const typename BasicJsonType::string_t& s)
+    {
+        j.m_type = value_t::string;
+        j.m_value = s;
+        j.assert_invariant();
+    }
+
+    template<typename BasicJsonType>
+    static void construct(BasicJsonType& j, typename BasicJsonType::string_t&& s)
+    {
+        j.m_type = value_t::string;
+        j.m_value = std::move(s);
+        j.assert_invariant();
+    }
+
+    template<typename BasicJsonType, typename CompatibleStringType,
+             enable_if_t<not std::is_same<CompatibleStringType, typename BasicJsonType::string_t>::value,
+                         int> = 0>
+    static void construct(BasicJsonType& j, const CompatibleStringType& str)
+    {
+        j.m_type = value_t::string;
+        j.m_value.string = j.template create<typename BasicJsonType::string_t>(str);
+        j.assert_invariant();
+    }
+};
+
+template<>
+struct external_constructor<value_t::number_float>
+{
+    template<typename BasicJsonType>
+    static void construct(BasicJsonType& j, typename BasicJsonType::number_float_t val) noexcept
+    {
+        j.m_type = value_t::number_float;
+        j.m_value = val;
+        j.assert_invariant();
+    }
+};
+
+template<>
+struct external_constructor<value_t::number_unsigned>
+{
+    template<typename BasicJsonType>
+    static void construct(BasicJsonType& j, typename BasicJsonType::number_unsigned_t val) noexcept
+    {
+        j.m_type = value_t::number_unsigned;
+        j.m_value = val;
+        j.assert_invariant();
+    }
+};
+
+template<>
+struct external_constructor<value_t::number_integer>
+{
+    template<typename BasicJsonType>
+    static void construct(BasicJsonType& j, typename BasicJsonType::number_integer_t val) noexcept
+    {
+        j.m_type = value_t::number_integer;
+        j.m_value = val;
+        j.assert_invariant();
+    }
+};
+
+template<>
+struct external_constructor<value_t::array>
+{
+    template<typename BasicJsonType>
+    static void construct(BasicJsonType& j, const typename BasicJsonType::array_t& arr)
+    {
+        j.m_type = value_t::array;
+        j.m_value = arr;
+        j.assert_invariant();
+    }
+
+    template<typename BasicJsonType>
+    static void construct(BasicJsonType& j, typename BasicJsonType::array_t&& arr)
+    {
+        j.m_type = value_t::array;
+        j.m_value = std::move(arr);
+        j.assert_invariant();
+    }
+
+    template<typename BasicJsonType, typename CompatibleArrayType,
+             enable_if_t<not std::is_same<CompatibleArrayType, typename BasicJsonType::array_t>::value,
+                         int> = 0>
+    static void construct(BasicJsonType& j, const CompatibleArrayType& arr)
+    {
+        using std::begin;
+        using std::end;
+        j.m_type = value_t::array;
+        j.m_value.array = j.template create<typename BasicJsonType::array_t>(begin(arr), end(arr));
+        j.assert_invariant();
+    }
+
+    template<typename BasicJsonType>
+    static void construct(BasicJsonType& j, const std::vector<bool>& arr)
+    {
+        j.m_type = value_t::array;
+        j.m_value = value_t::array;
+        j.m_value.array->reserve(arr.size());
+        for (const bool x : arr)
+        {
+            j.m_value.array->push_back(x);
+        }
+        j.assert_invariant();
+    }
+
+    template<typename BasicJsonType, typename T,
+             enable_if_t<std::is_convertible<T, BasicJsonType>::value, int> = 0>
+    static void construct(BasicJsonType& j, const std::valarray<T>& arr)
+    {
+        j.m_type = value_t::array;
+        j.m_value = value_t::array;
+        j.m_value.array->resize(arr.size());
+        std::copy(std::begin(arr), std::end(arr), j.m_value.array->begin());
+        j.assert_invariant();
+    }
+};
+
+template<>
+struct external_constructor<value_t::object>
+{
+    template<typename BasicJsonType>
+    static void construct(BasicJsonType& j, const typename BasicJsonType::object_t& obj)
+    {
+        j.m_type = value_t::object;
+        j.m_value = obj;
+        j.assert_invariant();
+    }
+
+    template<typename BasicJsonType>
+    static void construct(BasicJsonType& j, typename BasicJsonType::object_t&& obj)
+    {
+        j.m_type = value_t::object;
+        j.m_value = std::move(obj);
+        j.assert_invariant();
+    }
+
+    template<typename BasicJsonType, typename CompatibleObjectType,
+             enable_if_t<not std::is_same<CompatibleObjectType, typename BasicJsonType::object_t>::value, int> = 0>
+    static void construct(BasicJsonType& j, const CompatibleObjectType& obj)
+    {
+        using std::begin;
+        using std::end;
+
+        j.m_type = value_t::object;
+        j.m_value.object = j.template create<typename BasicJsonType::object_t>(begin(obj), end(obj));
+        j.assert_invariant();
+    }
+};
+
+/////////////
+// to_json //
+/////////////
+
+template<typename BasicJsonType, typename T,
+         enable_if_t<std::is_same<T, typename BasicJsonType::boolean_t>::value, int> = 0>
+void to_json(BasicJsonType& j, T b) noexcept
+{
+    external_constructor<value_t::boolean>::construct(j, b);
+}
+
+template<typename BasicJsonType, typename CompatibleString,
+         enable_if_t<std::is_constructible<typename BasicJsonType::string_t, CompatibleString>::value, int> = 0>
+void to_json(BasicJsonType& j, const CompatibleString& s)
+{
+    external_constructor<value_t::string>::construct(j, s);
+}
+
+template<typename BasicJsonType>
+void to_json(BasicJsonType& j, typename BasicJsonType::string_t&& s)
+{
+    external_constructor<value_t::string>::construct(j, std::move(s));
+}
+
+template<typename BasicJsonType, typename FloatType,
+         enable_if_t<std::is_floating_point<FloatType>::value, int> = 0>
+void to_json(BasicJsonType& j, FloatType val) noexcept
+{
+    external_constructor<value_t::number_float>::construct(j, static_cast<typename BasicJsonType::number_float_t>(val));
+}
+
+template<typename BasicJsonType, typename CompatibleNumberUnsignedType,
+         enable_if_t<is_compatible_integer_type<typename BasicJsonType::number_unsigned_t, CompatibleNumberUnsignedType>::value, int> = 0>
+void to_json(BasicJsonType& j, CompatibleNumberUnsignedType val) noexcept
+{
+    external_constructor<value_t::number_unsigned>::construct(j, static_cast<typename BasicJsonType::number_unsigned_t>(val));
+}
+
+template<typename BasicJsonType, typename CompatibleNumberIntegerType,
+         enable_if_t<is_compatible_integer_type<typename BasicJsonType::number_integer_t, CompatibleNumberIntegerType>::value, int> = 0>
+void to_json(BasicJsonType& j, CompatibleNumberIntegerType val) noexcept
+{
+    external_constructor<value_t::number_integer>::construct(j, static_cast<typename BasicJsonType::number_integer_t>(val));
+}
+
+template<typename BasicJsonType, typename EnumType,
+         enable_if_t<std::is_enum<EnumType>::value, int> = 0>
+void to_json(BasicJsonType& j, EnumType e) noexcept
+{
+    using underlying_type = typename std::underlying_type<EnumType>::type;
+    external_constructor<value_t::number_integer>::construct(j, static_cast<underlying_type>(e));
+}
+
+template<typename BasicJsonType>
+void to_json(BasicJsonType& j, const std::vector<bool>& e)
+{
+    external_constructor<value_t::array>::construct(j, e);
+}
+
+template <typename BasicJsonType, typename CompatibleArrayType,
+          enable_if_t<is_compatible_array_type<BasicJsonType,
+                      CompatibleArrayType>::value and
+                      not is_compatible_object_type<
+                          BasicJsonType, CompatibleArrayType>::value and
+                      not is_compatible_string_type<BasicJsonType, CompatibleArrayType>::value and
+                      not is_basic_json<CompatibleArrayType>::value,
+                      int> = 0>
+void to_json(BasicJsonType& j, const CompatibleArrayType& arr)
+{
+    external_constructor<value_t::array>::construct(j, arr);
+}
+
+template<typename BasicJsonType, typename T,
+         enable_if_t<std::is_convertible<T, BasicJsonType>::value, int> = 0>
+void to_json(BasicJsonType& j, const std::valarray<T>& arr)
+{
+    external_constructor<value_t::array>::construct(j, std::move(arr));
+}
+
+template<typename BasicJsonType>
+void to_json(BasicJsonType& j, typename BasicJsonType::array_t&& arr)
+{
+    external_constructor<value_t::array>::construct(j, std::move(arr));
+}
+
+template<typename BasicJsonType, typename CompatibleObjectType,
+         enable_if_t<is_compatible_object_type<BasicJsonType, CompatibleObjectType>::value and not is_basic_json<CompatibleObjectType>::value, int> = 0>
+void to_json(BasicJsonType& j, const CompatibleObjectType& obj)
+{
+    external_constructor<value_t::object>::construct(j, obj);
+}
+
+template<typename BasicJsonType>
+void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj)
+{
+    external_constructor<value_t::object>::construct(j, std::move(obj));
+}
+
+template <
+    typename BasicJsonType, typename T, std::size_t N,
+    enable_if_t<not std::is_constructible<typename BasicJsonType::string_t,
+                const T(&)[N]>::value,
+                int> = 0 >
+void to_json(BasicJsonType& j, const T(&arr)[N])
+{
+    external_constructor<value_t::array>::construct(j, arr);
+}
+
+template<typename BasicJsonType, typename... Args>
+void to_json(BasicJsonType& j, const std::pair<Args...>& p)
+{
+    j = { p.first, p.second };
+}
+
+// for https://github.com/nlohmann/json/pull/1134
+template < typename BasicJsonType, typename T,
+           enable_if_t<std::is_same<T, iteration_proxy_value<typename BasicJsonType::iterator>>::value, int> = 0>
+void to_json(BasicJsonType& j, const T& b)
+{
+    j = { {b.key(), b.value()} };
+}
+
+template<typename BasicJsonType, typename Tuple, std::size_t... Idx>
+void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence<Idx...> /*unused*/)
+{
+    j = { std::get<Idx>(t)... };
+}
+
+template<typename BasicJsonType, typename... Args>
+void to_json(BasicJsonType& j, const std::tuple<Args...>& t)
+{
+    to_json_tuple_impl(j, t, index_sequence_for<Args...> {});
+}
+
+struct to_json_fn
+{
+    template<typename BasicJsonType, typename T>
+    auto operator()(BasicJsonType& j, T&& val) const noexcept(noexcept(to_json(j, std::forward<T>(val))))
+    -> decltype(to_json(j, std::forward<T>(val)), void())
+    {
+        return to_json(j, std::forward<T>(val));
+    }
+};
+}  // namespace detail
+
+/// namespace to hold default `to_json` function
+namespace
+{
+constexpr const auto& to_json = detail::static_const<detail::to_json_fn>::value;
+} // namespace
+}  // namespace nlohmann
+
+// #include <nlohmann/detail/input/input_adapters.hpp>
+
+
+#include <cassert> // assert
+#include <cstddef> // size_t
+#include <cstring> // strlen
+#include <istream> // istream
+#include <iterator> // begin, end, iterator_traits, random_access_iterator_tag, distance, next
+#include <memory> // shared_ptr, make_shared, addressof
+#include <numeric> // accumulate
+#include <string> // string, char_traits
+#include <type_traits> // enable_if, is_base_of, is_pointer, is_integral, remove_pointer
+#include <utility> // pair, declval
+#include <cstdio> //FILE *
+
+// #include <nlohmann/detail/macro_scope.hpp>
+
+
+namespace nlohmann
+{
+namespace detail
+{
+/// the supported input formats
+enum class input_format_t { json, cbor, msgpack, ubjson, bson };
+
+////////////////////
+// input adapters //
+////////////////////
+
+/*!
+@brief abstract input adapter interface
+
+Produces a stream of std::char_traits<char>::int_type characters from a
+std::istream, a buffer, or some other input type. Accepts the return of
+exactly one non-EOF character for future input. The int_type characters
+returned consist of all valid char values as positive values (typically
+unsigned char), plus an EOF value outside that range, specified by the value
+of the function std::char_traits<char>::eof(). This value is typically -1, but
+could be any arbitrary value which is not a valid char value.
+*/
+struct input_adapter_protocol
+{
+    /// get a character [0,255] or std::char_traits<char>::eof().
+    virtual std::char_traits<char>::int_type get_character() = 0;
+    virtual ~input_adapter_protocol() = default;
+};
+
+/// a type to simplify interfaces
+using input_adapter_t = std::shared_ptr<input_adapter_protocol>;
+
+/*!
+Input adapter for stdio file access. This adapter read only 1 byte and do not use any
+ buffer. This adapter is a very low level adapter.
+*/
+class file_input_adapter : public input_adapter_protocol
+{
+  public:
+    explicit file_input_adapter(std::FILE* f)  noexcept
+        : m_file(f)
+    {}
+
+    std::char_traits<char>::int_type get_character() noexcept override
+    {
+        return std::fgetc(m_file);
+    }
+  private:
+    /// the file pointer to read from
+    std::FILE* m_file;
+};
+
+
+/*!
+Input adapter for a (caching) istream. Ignores a UFT Byte Order Mark at
+beginning of input. Does not support changing the underlying std::streambuf
+in mid-input. Maintains underlying std::istream and std::streambuf to support
+subsequent use of standard std::istream operations to process any input
+characters following those used in parsing the JSON input.  Clears the
+std::istream flags; any input errors (e.g., EOF) will be detected by the first
+subsequent call for input from the std::istream.
+*/
+class input_stream_adapter : public input_adapter_protocol
+{
+  public:
+    ~input_stream_adapter() override
+    {
+        // clear stream flags; we use underlying streambuf I/O, do not
+        // maintain ifstream flags, except eof
+        is.clear(is.rdstate() & std::ios::eofbit);
+    }
+
+    explicit input_stream_adapter(std::istream& i)
+        : is(i), sb(*i.rdbuf())
+    {}
+
+    // delete because of pointer members
+    input_stream_adapter(const input_stream_adapter&) = delete;
+    input_stream_adapter& operator=(input_stream_adapter&) = delete;
+    input_stream_adapter(input_stream_adapter&&) = delete;
+    input_stream_adapter& operator=(input_stream_adapter&&) = delete;
+
+    // std::istream/std::streambuf use std::char_traits<char>::to_int_type, to
+    // ensure that std::char_traits<char>::eof() and the character 0xFF do not
+    // end up as the same value, eg. 0xFFFFFFFF.
+    std::char_traits<char>::int_type get_character() override
+    {
+        auto res = sb.sbumpc();
+        // set eof manually, as we don't use the istream interface.
+        if (res == EOF)
+        {
+            is.clear(is.rdstate() | std::ios::eofbit);
+        }
+        return res;
+    }
+
+  private:
+    /// the associated input stream
+    std::istream& is;
+    std::streambuf& sb;
+};
+
+/// input adapter for buffer input
+class input_buffer_adapter : public input_adapter_protocol
+{
+  public:
+    input_buffer_adapter(const char* b, const std::size_t l) noexcept
+        : cursor(b), limit(b + l)
+    {}
+
+    // delete because of pointer members
+    input_buffer_adapter(const input_buffer_adapter&) = delete;
+    input_buffer_adapter& operator=(input_buffer_adapter&) = delete;
+    input_buffer_adapter(input_buffer_adapter&&) = delete;
+    input_buffer_adapter& operator=(input_buffer_adapter&&) = delete;
+    ~input_buffer_adapter() override = default;
+
+    std::char_traits<char>::int_type get_character() noexcept override
+    {
+        if (JSON_LIKELY(cursor < limit))
+        {
+            return std::char_traits<char>::to_int_type(*(cursor++));
+        }
+
+        return std::char_traits<char>::eof();
+    }
+
+  private:
+    /// pointer to the current character
+    const char* cursor;
+    /// pointer past the last character
+    const char* const limit;
+};
+
+template<typename WideStringType, size_t T>
+struct wide_string_input_helper
+{
+    // UTF-32
+    static void fill_buffer(const WideStringType& str, size_t& current_wchar, std::array<std::char_traits<char>::int_type, 4>& utf8_bytes, size_t& utf8_bytes_index, size_t& utf8_bytes_filled)
+    {
+        utf8_bytes_index = 0;
+
+        if (current_wchar == str.size())
+        {
+            utf8_bytes[0] = std::char_traits<char>::eof();
+            utf8_bytes_filled = 1;
+        }
+        else
+        {
+            // get the current character
+            const auto wc = static_cast<int>(str[current_wchar++]);
+
+            // UTF-32 to UTF-8 encoding
+            if (wc < 0x80)
+            {
+                utf8_bytes[0] = wc;
+                utf8_bytes_filled = 1;
+            }
+            else if (wc <= 0x7FF)
+            {
+                utf8_bytes[0] = 0xC0 | ((wc >> 6) & 0x1F);
+                utf8_bytes[1] = 0x80 | (wc & 0x3F);
+                utf8_bytes_filled = 2;
+            }
+            else if (wc <= 0xFFFF)
+            {
+                utf8_bytes[0] = 0xE0 | ((wc >> 12) & 0x0F);
+                utf8_bytes[1] = 0x80 | ((wc >> 6) & 0x3F);
+                utf8_bytes[2] = 0x80 | (wc & 0x3F);
+                utf8_bytes_filled = 3;
+            }
+            else if (wc <= 0x10FFFF)
+            {
+                utf8_bytes[0] = 0xF0 | ((wc >> 18) & 0x07);
+                utf8_bytes[1] = 0x80 | ((wc >> 12) & 0x3F);
+                utf8_bytes[2] = 0x80 | ((wc >> 6) & 0x3F);
+                utf8_bytes[3] = 0x80 | (wc & 0x3F);
+                utf8_bytes_filled = 4;
+            }
+            else
+            {
+                // unknown character
+                utf8_bytes[0] = wc;
+                utf8_bytes_filled = 1;
+            }
+        }
+    }
+};
+
+template<typename WideStringType>
+struct wide_string_input_helper<WideStringType, 2>
+{
+    // UTF-16
+    static void fill_buffer(const WideStringType& str, size_t& current_wchar, std::array<std::char_traits<char>::int_type, 4>& utf8_bytes, size_t& utf8_bytes_index, size_t& utf8_bytes_filled)
+    {
+        utf8_bytes_index = 0;
+
+        if (current_wchar == str.size())
+        {
+            utf8_bytes[0] = std::char_traits<char>::eof();
+            utf8_bytes_filled = 1;
+        }
+        else
+        {
+            // get the current character
+            const auto wc = static_cast<int>(str[current_wchar++]);
+
+            // UTF-16 to UTF-8 encoding
+            if (wc < 0x80)
+            {
+                utf8_bytes[0] = wc;
+                utf8_bytes_filled = 1;
+            }
+            else if (wc <= 0x7FF)
+            {
+                utf8_bytes[0] = 0xC0 | ((wc >> 6));
+                utf8_bytes[1] = 0x80 | (wc & 0x3F);
+                utf8_bytes_filled = 2;
+            }
+            else if (0xD800 > wc or wc >= 0xE000)
+            {
+                utf8_bytes[0] = 0xE0 | ((wc >> 12));
+                utf8_bytes[1] = 0x80 | ((wc >> 6) & 0x3F);
+                utf8_bytes[2] = 0x80 | (wc & 0x3F);
+                utf8_bytes_filled = 3;
+            }
+            else
+            {
+                if (current_wchar < str.size())
+                {
+                    const auto wc2 = static_cast<int>(str[current_wchar++]);
+                    const int charcode = 0x10000 + (((wc & 0x3FF) << 10) | (wc2 & 0x3FF));
+                    utf8_bytes[0] = 0xf0 | (charcode >> 18);
+                    utf8_bytes[1] = 0x80 | ((charcode >> 12) & 0x3F);
+                    utf8_bytes[2] = 0x80 | ((charcode >> 6) & 0x3F);
+                    utf8_bytes[3] = 0x80 | (charcode & 0x3F);
+                    utf8_bytes_filled = 4;
+                }
+                else
+                {
+                    // unknown character
+                    ++current_wchar;
+                    utf8_bytes[0] = wc;
+                    utf8_bytes_filled = 1;
+                }
+            }
+        }
+    }
+};
+
+template<typename WideStringType>
+class wide_string_input_adapter : public input_adapter_protocol
+{
+  public:
+    explicit wide_string_input_adapter(const WideStringType& w)  noexcept
+        : str(w)
+    {}
+
+    std::char_traits<char>::int_type get_character() noexcept override
+    {
+        // check if buffer needs to be filled
+        if (utf8_bytes_index == utf8_bytes_filled)
+        {
+            fill_buffer<sizeof(typename WideStringType::value_type)>();
+
+            assert(utf8_bytes_filled > 0);
+            assert(utf8_bytes_index == 0);
+        }
+
+        // use buffer
+        assert(utf8_bytes_filled > 0);
+        assert(utf8_bytes_index < utf8_bytes_filled);
+        return utf8_bytes[utf8_bytes_index++];
+    }
+
+  private:
+    template<size_t T>
+    void fill_buffer()
+    {
+        wide_string_input_helper<WideStringType, T>::fill_buffer(str, current_wchar, utf8_bytes, utf8_bytes_index, utf8_bytes_filled);
+    }
+
+    /// the wstring to process
+    const WideStringType& str;
+
+    /// index of the current wchar in str
+    std::size_t current_wchar = 0;
+
+    /// a buffer for UTF-8 bytes
+    std::array<std::char_traits<char>::int_type, 4> utf8_bytes = {{0, 0, 0, 0}};
+
+    /// index to the utf8_codes array for the next valid byte
+    std::size_t utf8_bytes_index = 0;
+    /// number of valid bytes in the utf8_codes array
+    std::size_t utf8_bytes_filled = 0;
+};
+
+class input_adapter
+{
+  public:
+    // native support
+    input_adapter(std::FILE* file)
+        : ia(std::make_shared<file_input_adapter>(file)) {}
+    /// input adapter for input stream
+    input_adapter(std::istream& i)
+        : ia(std::make_shared<input_stream_adapter>(i)) {}
+
+    /// input adapter for input stream
+    input_adapter(std::istream&& i)
+        : ia(std::make_shared<input_stream_adapter>(i)) {}
+
+    input_adapter(const std::wstring& ws)
+        : ia(std::make_shared<wide_string_input_adapter<std::wstring>>(ws)) {}
+
+    input_adapter(const std::u16string& ws)
+        : ia(std::make_shared<wide_string_input_adapter<std::u16string>>(ws)) {}
+
+    input_adapter(const std::u32string& ws)
+        : ia(std::make_shared<wide_string_input_adapter<std::u32string>>(ws)) {}
+
+    /// input adapter for buffer
+    template<typename CharT,
+             typename std::enable_if<
+                 std::is_pointer<CharT>::value and
+                 std::is_integral<typename std::remove_pointer<CharT>::type>::value and
+                 sizeof(typename std::remove_pointer<CharT>::type) == 1,
+                 int>::type = 0>
+    input_adapter(CharT b, std::size_t l)
+        : ia(std::make_shared<input_buffer_adapter>(reinterpret_cast<const char*>(b), l)) {}
+
+    // derived support
+
+    /// input adapter for string literal
+    template<typename CharT,
+             typename std::enable_if<
+                 std::is_pointer<CharT>::value and
+                 std::is_integral<typename std::remove_pointer<CharT>::type>::value and
+                 sizeof(typename std::remove_pointer<CharT>::type) == 1,
+                 int>::type = 0>
+    input_adapter(CharT b)
+        : input_adapter(reinterpret_cast<const char*>(b),
+                        std::strlen(reinterpret_cast<const char*>(b))) {}
+
+    /// input adapter for iterator range with contiguous storage
+    template<class IteratorType,
+             typename std::enable_if<
+                 std::is_same<typename iterator_traits<IteratorType>::iterator_category, std::random_access_iterator_tag>::value,
+                 int>::type = 0>
+    input_adapter(IteratorType first, IteratorType last)
+    {
+#ifndef NDEBUG
+        // assertion to check that the iterator range is indeed contiguous,
+        // see http://stackoverflow.com/a/35008842/266378 for more discussion
+        const auto is_contiguous = std::accumulate(
+                                       first, last, std::pair<bool, int>(true, 0),
+                                       [&first](std::pair<bool, int> res, decltype(*first) val)
+        {
+            res.first &= (val == *(std::next(std::addressof(*first), res.second++)));
+            return res;
+        }).first;
+        assert(is_contiguous);
+#endif
+
+        // assertion to check that each element is 1 byte long
+        static_assert(
+            sizeof(typename iterator_traits<IteratorType>::value_type) == 1,
+            "each element in the iterator range must have the size of 1 byte");
+
+        const auto len = static_cast<size_t>(std::distance(first, last));
+        if (JSON_LIKELY(len > 0))
+        {
+            // there is at least one element: use the address of first
+            ia = std::make_shared<input_buffer_adapter>(reinterpret_cast<const char*>(&(*first)), len);
+        }
+        else
+        {
+            // the address of first cannot be used: use nullptr
+            ia = std::make_shared<input_buffer_adapter>(nullptr, len);
+        }
+    }
+
+    /// input adapter for array
+    template<class T, std::size_t N>
+    input_adapter(T (&array)[N])
+        : input_adapter(std::begin(array), std::end(array)) {}
+
+    /// input adapter for contiguous container
+    template<class ContiguousContainer, typename
+             std::enable_if<not std::is_pointer<ContiguousContainer>::value and
+                            std::is_base_of<std::random_access_iterator_tag, typename iterator_traits<decltype(std::begin(std::declval<ContiguousContainer const>()))>::iterator_category>::value,
+                            int>::type = 0>
+    input_adapter(const ContiguousContainer& c)
+        : input_adapter(std::begin(c), std::end(c)) {}
+
+    operator input_adapter_t()
+    {
+        return ia;
+    }
+
+  private:
+    /// the actual adapter
+    input_adapter_t ia = nullptr;
+};
+}  // namespace detail
+}  // namespace nlohmann
+
+// #include <nlohmann/detail/input/lexer.hpp>
+
+
+#include <clocale> // localeconv
+#include <cstddef> // size_t
+#include <cstdlib> // strtof, strtod, strtold, strtoll, strtoull
+#include <cstdio> // snprintf
+#include <initializer_list> // initializer_list
+#include <string> // char_traits, string
+#include <vector> // vector
+
+// #include <nlohmann/detail/macro_scope.hpp>
+
+// #include <nlohmann/detail/input/input_adapters.hpp>
+
+// #include <nlohmann/detail/input/position_t.hpp>
+
+
+namespace nlohmann
+{
+namespace detail
+{
+///////////
+// lexer //
+///////////
+
+/*!
+@brief lexical analysis
+
+This class organizes the lexical analysis during JSON deserialization.
+*/
+template<typename BasicJsonType>
+class lexer
+{
+    using number_integer_t = typename BasicJsonType::number_integer_t;
+    using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
+    using number_float_t = typename BasicJsonType::number_float_t;
+    using string_t = typename BasicJsonType::string_t;
+
+  public:
+    /// token types for the parser
+    enum class token_type
+    {
+        uninitialized,    ///< indicating the scanner is uninitialized
+        literal_true,     ///< the `true` literal
+        literal_false,    ///< the `false` literal
+        literal_null,     ///< the `null` literal
+        value_string,     ///< a string -- use get_string() for actual value
+        value_unsigned,   ///< an unsigned integer -- use get_number_unsigned() for actual value
+        value_integer,    ///< a signed integer -- use get_number_integer() for actual value
+        value_float,      ///< an floating point number -- use get_number_float() for actual value
+        begin_array,      ///< the character for array begin `[`
+        begin_object,     ///< the character for object begin `{`
+        end_array,        ///< the character for array end `]`
+        end_object,       ///< the character for object end `}`
+        name_separator,   ///< the name separator `:`
+        value_separator,  ///< the value separator `,`
+        parse_error,      ///< indicating a parse error
+        end_of_input,     ///< indicating the end of the input buffer
+        literal_or_value  ///< a literal or the begin of a value (only for diagnostics)
+    };
+
+    /// return name of values of type token_type (only used for errors)
+    static const char* token_type_name(const token_type t) noexcept
+    {
+        switch (t)
+        {
+            case token_type::uninitialized:
+                return "<uninitialized>";
+            case token_type::literal_true:
+                return "true literal";
+            case token_type::literal_false:
+                return "false literal";
+            case token_type::literal_null:
+                return "null literal";
+            case token_type::value_string:
+                return "string literal";
+            case lexer::token_type::value_unsigned:
+            case lexer::token_type::value_integer:
+            case lexer::token_type::value_float:
+                return "number literal";
+            case token_type::begin_array:
+                return "'['";
+            case token_type::begin_object:
+                return "'{'";
+            case token_type::end_array:
+                return "']'";
+            case token_type::end_object:
+                return "'}'";
+            case token_type::name_separator:
+                return "':'";
+            case token_type::value_separator:
+                return "','";
+            case token_type::parse_error:
+                return "<parse error>";
+            case token_type::end_of_input:
+                return "end of input";
+            case token_type::literal_or_value:
+                return "'[', '{', or a literal";
+            // LCOV_EXCL_START
+            default: // catch non-enum values
+                return "unknown token";
+                // LCOV_EXCL_STOP
+        }
+    }
+
+    explicit lexer(detail::input_adapter_t&& adapter)
+        : ia(std::move(adapter)), decimal_point_char(get_decimal_point()) {}
+
+    // delete because of pointer members
+    lexer(const lexer&) = delete;
+    lexer(lexer&&) = delete;
+    lexer& operator=(lexer&) = delete;
+    lexer& operator=(lexer&&) = delete;
+    ~lexer() = default;
+
+  private:
+    /////////////////////
+    // locales
+    /////////////////////
+
+    /// return the locale-dependent decimal point
+    static char get_decimal_point() noexcept
+    {
+        const auto loc = localeconv();
+        assert(loc != nullptr);
+        return (loc->decimal_point == nullptr) ? '.' : *(loc->decimal_point);
+    }
+
+    /////////////////////
+    // scan functions
+    /////////////////////
+
+    /*!
+    @brief get codepoint from 4 hex characters following `\u`
+
+    For input "\u c1 c2 c3 c4" the codepoint is:
+      (c1 * 0x1000) + (c2 * 0x0100) + (c3 * 0x0010) + c4
+    = (c1 << 12) + (c2 << 8) + (c3 << 4) + (c4 << 0)
+
+    Furthermore, the possible characters '0'..'9', 'A'..'F', and 'a'..'f'
+    must be converted to the integers 0x0..0x9, 0xA..0xF, 0xA..0xF, resp. The
+    conversion is done by subtracting the offset (0x30, 0x37, and 0x57)
+    between the ASCII value of the character and the desired integer value.
+
+    @return codepoint (0x0000..0xFFFF) or -1 in case of an error (e.g. EOF or
+            non-hex character)
+    */
+    int get_codepoint()
+    {
+        // this function only makes sense after reading `\u`
+        assert(current == 'u');
+        int codepoint = 0;
+
+        const auto factors = { 12, 8, 4, 0 };
+        for (const auto factor : factors)
+        {
+            get();
+
+            if (current >= '0' and current <= '9')
+            {
+                codepoint += ((current - 0x30) << factor);
+            }
+            else if (current >= 'A' and current <= 'F')
+            {
+                codepoint += ((current - 0x37) << factor);
+            }
+            else if (current >= 'a' and current <= 'f')
+            {
+                codepoint += ((current - 0x57) << factor);
+            }
+            else
+            {
+                return -1;
+            }
+        }
+
+        assert(0x0000 <= codepoint and codepoint <= 0xFFFF);
+        return codepoint;
+    }
+
+    /*!
+    @brief check if the next byte(s) are inside a given range
+
+    Adds the current byte and, for each passed range, reads a new byte and
+    checks if it is inside the range. If a violation was detected, set up an
+    error message and return false. Otherwise, return true.
+
+    @param[in] ranges  list of integers; interpreted as list of pairs of
+                       inclusive lower and upper bound, respectively
+
+    @pre The passed list @a ranges must have 2, 4, or 6 elements; that is,
+         1, 2, or 3 pairs. This precondition is enforced by an assertion.
+
+    @return true if and only if no range violation was detected
+    */
+    bool next_byte_in_range(std::initializer_list<int> ranges)
+    {
+        assert(ranges.size() == 2 or ranges.size() == 4 or ranges.size() == 6);
+        add(current);
+
+        for (auto range = ranges.begin(); range != ranges.end(); ++range)
+        {
+            get();
+            if (JSON_LIKELY(*range <= current and current <= *(++range)))
+            {
+                add(current);
+            }
+            else
+            {
+                error_message = "invalid string: ill-formed UTF-8 byte";
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /*!
+    @brief scan a string literal
+
+    This function scans a string according to Sect. 7 of RFC 7159. While
+    scanning, bytes are escaped and copied into buffer token_buffer. Then the
+    function returns successfully, token_buffer is *not* null-terminated (as it
+    may contain \0 bytes), and token_buffer.size() is the number of bytes in the
+    string.
+
+    @return token_type::value_string if string could be successfully scanned,
+            token_type::parse_error otherwise
+
+    @note In case of errors, variable error_message contains a textual
+          description.
+    */
+    token_type scan_string()
+    {
+        // reset token_buffer (ignore opening quote)
+        reset();
+
+        // we entered the function by reading an open quote
+        assert(current == '\"');
+
+        while (true)
+        {
+            // get next character
+            switch (get())
+            {
+                // end of file while parsing string
+                case std::char_traits<char>::eof():
+                {
+                    error_message = "invalid string: missing closing quote";
+                    return token_type::parse_error;
+                }
+
+                // closing quote
+                case '\"':
+                {
+                    return token_type::value_string;
+                }
+
+                // escapes
+                case '\\':
+                {
+                    switch (get())
+                    {
+                        // quotation mark
+                        case '\"':
+                            add('\"');
+                            break;
+                        // reverse solidus
+                        case '\\':
+                            add('\\');
+                            break;
+                        // solidus
+                        case '/':
+                            add('/');
+                            break;
+                        // backspace
+                        case 'b':
+                            add('\b');
+                            break;
+                        // form feed
+                        case 'f':
+                            add('\f');
+                            break;
+                        // line feed
+                        case 'n':
+                            add('\n');
+                            break;
+                        // carriage return
+                        case 'r':
+                            add('\r');
+                            break;
+                        // tab
+                        case 't':
+                            add('\t');
+                            break;
+
+                        // unicode escapes
+                        case 'u':
+                        {
+                            const int codepoint1 = get_codepoint();
+                            int codepoint = codepoint1; // start with codepoint1
+
+                            if (JSON_UNLIKELY(codepoint1 == -1))
+                            {
+                                error_message = "invalid string: '\\u' must be followed by 4 hex digits";
+                                return token_type::parse_error;
+                            }
+
+                            // check if code point is a high surrogate
+                            if (0xD800 <= codepoint1 and codepoint1 <= 0xDBFF)
+                            {
+                                // expect next \uxxxx entry
+                                if (JSON_LIKELY(get() == '\\' and get() == 'u'))
+                                {
+                                    const int codepoint2 = get_codepoint();
+
+                                    if (JSON_UNLIKELY(codepoint2 == -1))
+                                    {
+                                        error_message = "invalid string: '\\u' must be followed by 4 hex digits";
+                                        return token_type::parse_error;
+                                    }
+
+                                    // check if codepoint2 is a low surrogate
+                                    if (JSON_LIKELY(0xDC00 <= codepoint2 and codepoint2 <= 0xDFFF))
+                                    {
+                                        // overwrite codepoint
+                                        codepoint =
+                                            // high surrogate occupies the most significant 22 bits
+                                            (codepoint1 << 10)
+                                            // low surrogate occupies the least significant 15 bits
+                                            + codepoint2
+                                            // there is still the 0xD800, 0xDC00 and 0x10000 noise
+                                            // in the result so we have to subtract with:
+                                            // (0xD800 << 10) + DC00 - 0x10000 = 0x35FDC00
+                                            - 0x35FDC00;
+                                    }
+                                    else
+                                    {
+                                        error_message = "invalid string: surrogate U+DC00..U+DFFF must be followed by U+DC00..U+DFFF";
+                                        return token_type::parse_error;
+                                    }
+                                }
+                                else
+                                {
+                                    error_message = "invalid string: surrogate U+DC00..U+DFFF must be followed by U+DC00..U+DFFF";
+                                    return token_type::parse_error;
+                                }
+                            }
+                            else
+                            {
+                                if (JSON_UNLIKELY(0xDC00 <= codepoint1 and codepoint1 <= 0xDFFF))
+                                {
+                                    error_message = "invalid string: surrogate U+DC00..U+DFFF must follow U+D800..U+DBFF";
+                                    return token_type::parse_error;
+                                }
+                            }
+
+                            // result of the above calculation yields a proper codepoint
+                            assert(0x00 <= codepoint and codepoint <= 0x10FFFF);
+
+                            // translate codepoint into bytes
+                            if (codepoint < 0x80)
+                            {
+                                // 1-byte characters: 0xxxxxxx (ASCII)
+                                add(codepoint);
+                            }
+                            else if (codepoint <= 0x7FF)
+                            {
+                                // 2-byte characters: 110xxxxx 10xxxxxx
+                                add(0xC0 | (codepoint >> 6));
+                                add(0x80 | (codepoint & 0x3F));
+                            }
+                            else if (codepoint <= 0xFFFF)
+                            {
+                                // 3-byte characters: 1110xxxx 10xxxxxx 10xxxxxx
+                                add(0xE0 | (codepoint >> 12));
+                                add(0x80 | ((codepoint >> 6) & 0x3F));
+                                add(0x80 | (codepoint & 0x3F));
+                            }
+                            else
+                            {
+                                // 4-byte characters: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
+                                add(0xF0 | (codepoint >> 18));
+                                add(0x80 | ((codepoint >> 12) & 0x3F));
+                                add(0x80 | ((codepoint >> 6) & 0x3F));
+                                add(0x80 | (codepoint & 0x3F));
+                            }
+
+                            break;
+                        }
+
+                        // other characters after escape
+                        default:
+                            error_message = "invalid string: forbidden character after backslash";
+                            return token_type::parse_error;
+                    }
+
+                    break;
+                }
+
+                // invalid control characters
+                case 0x00:
+                {
+                    error_message = "invalid string: control character U+0000 (NUL) must be escaped to \\u0000";
+                    return token_type::parse_error;
+                }
+
+                case 0x01:
+                {
+                    error_message = "invalid string: control character U+0001 (SOH) must be escaped to \\u0001";
+                    return token_type::parse_error;
+                }
+
+                case 0x02:
+                {
+                    error_message = "invalid string: control character U+0002 (STX) must be escaped to \\u0002";
+                    return token_type::parse_error;
+                }
+
+                case 0x03:
+                {
+                    error_message = "invalid string: control character U+0003 (ETX) must be escaped to \\u0003";
+                    return token_type::parse_error;
+                }
+
+                case 0x04:
+                {
+                    error_message = "invalid string: control character U+0004 (EOT) must be escaped to \\u0004";
+                    return token_type::parse_error;
+                }
+
+                case 0x05:
+                {
+                    error_message = "invalid string: control character U+0005 (ENQ) must be escaped to \\u0005";
+                    return token_type::parse_error;
+                }
+
+                case 0x06:
+                {
+                    error_message = "invalid string: control character U+0006 (ACK) must be escaped to \\u0006";
+                    return token_type::parse_error;
+                }
+
+                case 0x07:
+                {
+                    error_message = "invalid string: control character U+0007 (BEL) must be escaped to \\u0007";
+                    return token_type::parse_error;
+                }
+
+                case 0x08:
+                {
+                    error_message = "invalid string: control character U+0008 (BS) must be escaped to \\u0008 or \\b";
+                    return token_type::parse_error;
+                }
+
+                case 0x09:
+                {
+                    error_message = "invalid string: control character U+0009 (HT) must be escaped to \\u0009 or \\t";
+                    return token_type::parse_error;
+                }
+
+                case 0x0A:
+                {
+                    error_message = "invalid string: control character U+000A (LF) must be escaped to \\u000A or \\n";
+                    return token_type::parse_error;
+                }
+
+                case 0x0B:
+                {
+                    error_message = "invalid string: control character U+000B (VT) must be escaped to \\u000B";
+                    return token_type::parse_error;
+                }
+
+                case 0x0C:
+                {
+                    error_message = "invalid string: control character U+000C (FF) must be escaped to \\u000C or \\f";
+                    return token_type::parse_error;
+                }
+
+                case 0x0D:
+                {
+                    error_message = "invalid string: control character U+000D (CR) must be escaped to \\u000D or \\r";
+                    return token_type::parse_error;
+                }
+
+                case 0x0E:
+                {
+                    error_message = "invalid string: control character U+000E (SO) must be escaped to \\u000E";
+                    return token_type::parse_error;
+                }
+
+                case 0x0F:
+                {
+                    error_message = "invalid string: control character U+000F (SI) must be escaped to \\u000F";
+                    return token_type::parse_error;
+                }
+
+                case 0x10:
+                {
+                    error_message = "invalid string: control character U+0010 (DLE) must be escaped to \\u0010";
+                    return token_type::parse_error;
+                }
+
+                case 0x11:
+                {
+                    error_message = "invalid string: control character U+0011 (DC1) must be escaped to \\u0011";
+                    return token_type::parse_error;
+                }
+
+                case 0x12:
+                {
+                    error_message = "invalid string: control character U+0012 (DC2) must be escaped to \\u0012";
+                    return token_type::parse_error;
+                }
+
+                case 0x13:
+                {
+                    error_message = "invalid string: control character U+0013 (DC3) must be escaped to \\u0013";
+                    return token_type::parse_error;
+                }
+
+                case 0x14:
+                {
+                    error_message = "invalid string: control character U+0014 (DC4) must be escaped to \\u0014";
+                    return token_type::parse_error;
+                }
+
+                case 0x15:
+                {
+                    error_message = "invalid string: control character U+0015 (NAK) must be escaped to \\u0015";
+                    return token_type::parse_error;
+                }
+
+                case 0x16:
+                {
+                    error_message = "invalid string: control character U+0016 (SYN) must be escaped to \\u0016";
+                    return token_type::parse_error;
+                }
+
+                case 0x17:
+                {
+                    error_message = "invalid string: control character U+0017 (ETB) must be escaped to \\u0017";
+                    return token_type::parse_error;
+                }
+
+                case 0x18:
+                {
+                    error_message = "invalid string: control character U+0018 (CAN) must be escaped to \\u0018";
+                    return token_type::parse_error;
+                }
+
+                case 0x19:
+                {
+                    error_message = "invalid string: control character U+0019 (EM) must be escaped to \\u0019";
+                    return token_type::parse_error;
+                }
+
+                case 0x1A:
+                {
+                    error_message = "invalid string: control character U+001A (SUB) must be escaped to \\u001A";
+                    return token_type::parse_error;
+                }
+
+                case 0x1B:
+                {
+                    error_message = "invalid string: control character U+001B (ESC) must be escaped to \\u001B";
+                    return token_type::parse_error;
+                }
+
+                case 0x1C:
+                {
+                    error_message = "invalid string: control character U+001C (FS) must be escaped to \\u001C";
+                    return token_type::parse_error;
+                }
+
+                case 0x1D:
+                {
+                    error_message = "invalid string: control character U+001D (GS) must be escaped to \\u001D";
+                    return token_type::parse_error;
+                }
+
+                case 0x1E:
+                {
+                    error_message = "invalid string: control character U+001E (RS) must be escaped to \\u001E";
+                    return token_type::parse_error;
+                }
+
+                case 0x1F:
+                {
+                    error_message = "invalid string: control character U+001F (US) must be escaped to \\u001F";
+                    return token_type::parse_error;
+                }
+
+                // U+0020..U+007F (except U+0022 (quote) and U+005C (backspace))
+                case 0x20:
+                case 0x21:
+                case 0x23:
+                case 0x24:
+                case 0x25:
+                case 0x26:
+                case 0x27:
+                case 0x28:
+                case 0x29:
+                case 0x2A:
+                case 0x2B:
+                case 0x2C:
+                case 0x2D:
+                case 0x2E:
+                case 0x2F:
+                case 0x30:
+                case 0x31:
+                case 0x32:
+                case 0x33:
+                case 0x34:
+                case 0x35:
+                case 0x36:
+                case 0x37:
+                case 0x38:
+                case 0x39:
+                case 0x3A:
+                case 0x3B:
+                case 0x3C:
+                case 0x3D:
+                case 0x3E:
+                case 0x3F:
+                case 0x40:
+                case 0x41:
+                case 0x42:
+                case 0x43:
+                case 0x44:
+                case 0x45:
+                case 0x46:
+                case 0x47:
+                case 0x48:
+                case 0x49:
+                case 0x4A:
+                case 0x4B:
+                case 0x4C:
+                case 0x4D:
+                case 0x4E:
+                case 0x4F:
+                case 0x50:
+                case 0x51:
+                case 0x52:
+                case 0x53:
+                case 0x54:
+                case 0x55:
+                case 0x56:
+                case 0x57:
+                case 0x58:
+                case 0x59:
+                case 0x5A:
+                case 0x5B:
+                case 0x5D:
+                case 0x5E:
+                case 0x5F:
+                case 0x60:
+                case 0x61:
+                case 0x62:
+                case 0x63:
+                case 0x64:
+                case 0x65:
+                case 0x66:
+                case 0x67:
+                case 0x68:
+                case 0x69:
+                case 0x6A:
+                case 0x6B:
+                case 0x6C:
+                case 0x6D:
+                case 0x6E:
+                case 0x6F:
+                case 0x70:
+                case 0x71:
+                case 0x72:
+                case 0x73:
+                case 0x74:
+                case 0x75:
+                case 0x76:
+                case 0x77:
+                case 0x78:
+                case 0x79:
+                case 0x7A:
+                case 0x7B:
+                case 0x7C:
+                case 0x7D:
+                case 0x7E:
+                case 0x7F:
+                {
+                    add(current);
+                    break;
+                }
+
+                // U+0080..U+07FF: bytes C2..DF 80..BF
+                case 0xC2:
+                case 0xC3:
+                case 0xC4:
+                case 0xC5:
+                case 0xC6:
+                case 0xC7:
+                case 0xC8:
+                case 0xC9:
+                case 0xCA:
+                case 0xCB:
+                case 0xCC:
+                case 0xCD:
+                case 0xCE:
+                case 0xCF:
+                case 0xD0:
+                case 0xD1:
+                case 0xD2:
+                case 0xD3:
+                case 0xD4:
+                case 0xD5:
+                case 0xD6:
+                case 0xD7:
+                case 0xD8:
+                case 0xD9:
+                case 0xDA:
+                case 0xDB:
+                case 0xDC:
+                case 0xDD:
+                case 0xDE:
+                case 0xDF:
+                {
+                    if (JSON_UNLIKELY(not next_byte_in_range({0x80, 0xBF})))
+                    {
+                        return token_type::parse_error;
+                    }
+                    break;
+                }
+
+                // U+0800..U+0FFF: bytes E0 A0..BF 80..BF
+                case 0xE0:
+                {
+                    if (JSON_UNLIKELY(not (next_byte_in_range({0xA0, 0xBF, 0x80, 0xBF}))))
+                    {
+                        return token_type::parse_error;
+                    }
+                    break;
+                }
+
+                // U+1000..U+CFFF: bytes E1..EC 80..BF 80..BF
+                // U+E000..U+FFFF: bytes EE..EF 80..BF 80..BF
+                case 0xE1:
+                case 0xE2:
+                case 0xE3:
+                case 0xE4:
+                case 0xE5:
+                case 0xE6:
+                case 0xE7:
+                case 0xE8:
+                case 0xE9:
+                case 0xEA:
+                case 0xEB:
+                case 0xEC:
+                case 0xEE:
+                case 0xEF:
+                {
+                    if (JSON_UNLIKELY(not (next_byte_in_range({0x80, 0xBF, 0x80, 0xBF}))))
+                    {
+                        return token_type::parse_error;
+                    }
+                    break;
+                }
+
+                // U+D000..U+D7FF: bytes ED 80..9F 80..BF
+                case 0xED:
+                {
+                    if (JSON_UNLIKELY(not (next_byte_in_range({0x80, 0x9F, 0x80, 0xBF}))))
+                    {
+                        return token_type::parse_error;
+                    }
+                    break;
+                }
+
+                // U+10000..U+3FFFF F0 90..BF 80..BF 80..BF
+                case 0xF0:
+                {
+                    if (JSON_UNLIKELY(not (next_byte_in_range({0x90, 0xBF, 0x80, 0xBF, 0x80, 0xBF}))))
+                    {
+                        return token_type::parse_error;
+                    }
+                    break;
+                }
+
+                // U+40000..U+FFFFF F1..F3 80..BF 80..BF 80..BF
+                case 0xF1:
+                case 0xF2:
+                case 0xF3:
+                {
+                    if (JSON_UNLIKELY(not (next_byte_in_range({0x80, 0xBF, 0x80, 0xBF, 0x80, 0xBF}))))
+                    {
+                        return token_type::parse_error;
+                    }
+                    break;
+                }
+
+                // U+100000..U+10FFFF F4 80..8F 80..BF 80..BF
+                case 0xF4:
+                {
+                    if (JSON_UNLIKELY(not (next_byte_in_range({0x80, 0x8F, 0x80, 0xBF, 0x80, 0xBF}))))
+                    {
+                        return token_type::parse_error;
+                    }
+                    break;
+                }
+
+                // remaining bytes (80..C1 and F5..FF) are ill-formed
+                default:
+                {
+                    error_message = "invalid string: ill-formed UTF-8 byte";
+                    return token_type::parse_error;
+                }
+            }
+        }
+    }
+
+    static void strtof(float& f, const char* str, char** endptr) noexcept
+    {
+        f = std::strtof(str, endptr);
+    }
+
+    static void strtof(double& f, const char* str, char** endptr) noexcept
+    {
+        f = std::strtod(str, endptr);
+    }
+
+    static void strtof(long double& f, const char* str, char** endptr) noexcept
+    {
+        f = std::strtold(str, endptr);
+    }
+
+    /*!
+    @brief scan a number literal
+
+    This function scans a string according to Sect. 6 of RFC 7159.
+
+    The function is realized with a deterministic finite state machine derived
+    from the grammar described in RFC 7159. Starting in state "init", the
+    input is read and used to determined the next state. Only state "done"
+    accepts the number. State "error" is a trap state to model errors. In the
+    table below, "anything" means any character but the ones listed before.
+
+    state    | 0        | 1-9      | e E      | +       | -       | .        | anything
+    ---------|----------|----------|----------|---------|---------|----------|-----------
+    init     | zero     | any1     | [error]  | [error] | minus   | [error]  | [error]
+    minus    | zero     | any1     | [error]  | [error] | [error] | [error]  | [error]
+    zero     | done     | done     | exponent | done    | done    | decimal1 | done
+    any1     | any1     | any1     | exponent | done    | done    | decimal1 | done
+    decimal1 | decimal2 | [error]  | [error]  | [error] | [error] | [error]  | [error]
+    decimal2 | decimal2 | decimal2 | exponent | done    | done    | done     | done
+    exponent | any2     | any2     | [error]  | sign    | sign    | [error]  | [error]
+    sign     | any2     | any2     | [error]  | [error] | [error] | [error]  | [error]
+    any2     | any2     | any2     | done     | done    | done    | done     | done
+
+    The state machine is realized with one label per state (prefixed with
+    "scan_number_") and `goto` statements between them. The state machine
+    contains cycles, but any cycle can be left when EOF is read. Therefore,
+    the function is guaranteed to terminate.
+
+    During scanning, the read bytes are stored in token_buffer. This string is
+    then converted to a signed integer, an unsigned integer, or a
+    floating-point number.
+
+    @return token_type::value_unsigned, token_type::value_integer, or
+            token_type::value_float if number could be successfully scanned,
+            token_type::parse_error otherwise
+
+    @note The scanner is independent of the current locale. Internally, the
+          locale's decimal point is used instead of `.` to work with the
+          locale-dependent converters.
+    */
+    token_type scan_number()  // lgtm [cpp/use-of-goto]
+    {
+        // reset token_buffer to store the number's bytes
+        reset();
+
+        // the type of the parsed number; initially set to unsigned; will be
+        // changed if minus sign, decimal point or exponent is read
+        token_type number_type = token_type::value_unsigned;
+
+        // state (init): we just found out we need to scan a number
+        switch (current)
+        {
+            case '-':
+            {
+                add(current);
+                goto scan_number_minus;
+            }
+
+            case '0':
+            {
+                add(current);
+                goto scan_number_zero;
+            }
+
+            case '1':
+            case '2':
+            case '3':
+            case '4':
+            case '5':
+            case '6':
+            case '7':
+            case '8':
+            case '9':
+            {
+                add(current);
+                goto scan_number_any1;
+            }
+
+            // LCOV_EXCL_START
+            default:
+            {
+                // all other characters are rejected outside scan_number()
+                assert(false);
+            }
+                // LCOV_EXCL_STOP
+        }
+
+scan_number_minus:
+        // state: we just parsed a leading minus sign
+        number_type = token_type::value_integer;
+        switch (get())
+        {
+            case '0':
+            {
+                add(current);
+                goto scan_number_zero;
+            }
+
+            case '1':
+            case '2':
+            case '3':
+            case '4':
+            case '5':
+            case '6':
+            case '7':
+            case '8':
+            case '9':
+            {
+                add(current);
+                goto scan_number_any1;
+            }
+
+            default:
+            {
+                error_message = "invalid number; expected digit after '-'";
+                return token_type::parse_error;
+            }
+        }
+
+scan_number_zero:
+        // state: we just parse a zero (maybe with a leading minus sign)
+        switch (get())
+        {
+            case '.':
+            {
+                add(decimal_point_char);
+                goto scan_number_decimal1;
+            }
+
+            case 'e':
+            case 'E':
+            {
+                add(current);
+                goto scan_number_exponent;
+            }
+
+            default:
+                goto scan_number_done;
+        }
+
+scan_number_any1:
+        // state: we just parsed a number 0-9 (maybe with a leading minus sign)
+        switch (get())
+        {
+            case '0':
+            case '1':
+            case '2':
+            case '3':
+            case '4':
+            case '5':
+            case '6':
+            case '7':
+            case '8':
+            case '9':
+            {
+                add(current);
+                goto scan_number_any1;
+            }
+
+            case '.':
+            {
+                add(decimal_point_char);
+                goto scan_number_decimal1;
+            }
+
+            case 'e':
+            case 'E':
+            {
+                add(current);
+                goto scan_number_exponent;
+            }
+
+            default:
+                goto scan_number_done;
+        }
+
+scan_number_decimal1:
+        // state: we just parsed a decimal point
+        number_type = token_type::value_float;
+        switch (get())
+        {
+            case '0':
+            case '1':
+            case '2':
+            case '3':
+            case '4':
+            case '5':
+            case '6':
+            case '7':
+            case '8':
+            case '9':
+            {
+                add(current);
+                goto scan_number_decimal2;
+            }
+
+            default:
+            {
+                error_message = "invalid number; expected digit after '.'";
+                return token_type::parse_error;
+            }
+        }
+
+scan_number_decimal2:
+        // we just parsed at least one number after a decimal point
+        switch (get())
+        {
+            case '0':
+            case '1':
+            case '2':
+            case '3':
+            case '4':
+            case '5':
+            case '6':
+            case '7':
+            case '8':
+            case '9':
+            {
+                add(current);
+                goto scan_number_decimal2;
+            }
+
+            case 'e':
+            case 'E':
+            {
+                add(current);
+                goto scan_number_exponent;
+            }
+
+            default:
+                goto scan_number_done;
+        }
+
+scan_number_exponent:
+        // we just parsed an exponent
+        number_type = token_type::value_float;
+        switch (get())
+        {
+            case '+':
+            case '-':
+            {
+                add(current);
+                goto scan_number_sign;
+            }
+
+            case '0':
+            case '1':
+            case '2':
+            case '3':
+            case '4':
+            case '5':
+            case '6':
+            case '7':
+            case '8':
+            case '9':
+            {
+                add(current);
+                goto scan_number_any2;
+            }
+
+            default:
+            {
+                error_message =
+                    "invalid number; expected '+', '-', or digit after exponent";
+                return token_type::parse_error;
+            }
+        }
+
+scan_number_sign:
+        // we just parsed an exponent sign
+        switch (get())
+        {
+            case '0':
+            case '1':
+            case '2':
+            case '3':
+            case '4':
+            case '5':
+            case '6':
+            case '7':
+            case '8':
+            case '9':
+            {
+                add(current);
+                goto scan_number_any2;
+            }
+
+            default:
+            {
+                error_message = "invalid number; expected digit after exponent sign";
+                return token_type::parse_error;
+            }
+        }
+
+scan_number_any2:
+        // we just parsed a number after the exponent or exponent sign
+        switch (get())
+        {
+            case '0':
+            case '1':
+            case '2':
+            case '3':
+            case '4':
+            case '5':
+            case '6':
+            case '7':
+            case '8':
+            case '9':
+            {
+                add(current);
+                goto scan_number_any2;
+            }
+
+            default:
+                goto scan_number_done;
+        }
+
+scan_number_done:
+        // unget the character after the number (we only read it to know that
+        // we are done scanning a number)
+        unget();
+
+        char* endptr = nullptr;
+        errno = 0;
+
+        // try to parse integers first and fall back to floats
+        if (number_type == token_type::value_unsigned)
+        {
+            const auto x = std::strtoull(token_buffer.data(), &endptr, 10);
+
+            // we checked the number format before
+            assert(endptr == token_buffer.data() + token_buffer.size());
+
+            if (errno == 0)
+            {
+                value_unsigned = static_cast<number_unsigned_t>(x);
+                if (value_unsigned == x)
+                {
+                    return token_type::value_unsigned;
+                }
+            }
+        }
+        else if (number_type == token_type::value_integer)
+        {
+            const auto x = std::strtoll(token_buffer.data(), &endptr, 10);
+
+            // we checked the number format before
+            assert(endptr == token_buffer.data() + token_buffer.size());
+
+            if (errno == 0)
+            {
+                value_integer = static_cast<number_integer_t>(x);
+                if (value_integer == x)
+                {
+                    return token_type::value_integer;
+                }
+            }
+        }
+
+        // this code is reached if we parse a floating-point number or if an
+        // integer conversion above failed
+        strtof(value_float, token_buffer.data(), &endptr);
+
+        // we checked the number format before
+        assert(endptr == token_buffer.data() + token_buffer.size());
+
+        return token_type::value_float;
+    }
+
+    /*!
+    @param[in] literal_text  the literal text to expect
+    @param[in] length        the length of the passed literal text
+    @param[in] return_type   the token type to return on success
+    */
+    token_type scan_literal(const char* literal_text, const std::size_t length,
+                            token_type return_type)
+    {
+        assert(current == literal_text[0]);
+        for (std::size_t i = 1; i < length; ++i)
+        {
+            if (JSON_UNLIKELY(get() != literal_text[i]))
+            {
+                error_message = "invalid literal";
+                return token_type::parse_error;
+            }
+        }
+        return return_type;
+    }
+
+    /////////////////////
+    // input management
+    /////////////////////
+
+    /// reset token_buffer; current character is beginning of token
+    void reset() noexcept
+    {
+        token_buffer.clear();
+        token_string.clear();
+        token_string.push_back(std::char_traits<char>::to_char_type(current));
+    }
+
+    /*
+    @brief get next character from the input
+
+    This function provides the interface to the used input adapter. It does
+    not throw in case the input reached EOF, but returns a
+    `std::char_traits<char>::eof()` in that case.  Stores the scanned characters
+    for use in error messages.
+
+    @return character read from the input
+    */
+    std::char_traits<char>::int_type get()
+    {
+        ++position.chars_read_total;
+        ++position.chars_read_current_line;
+
+        if (next_unget)
+        {
+            // just reset the next_unget variable and work with current
+            next_unget = false;
+        }
+        else
+        {
+            current = ia->get_character();
+        }
+
+        if (JSON_LIKELY(current != std::char_traits<char>::eof()))
+        {
+            token_string.push_back(std::char_traits<char>::to_char_type(current));
+        }
+
+        if (current == '\n')
+        {
+            ++position.lines_read;
+            ++position.chars_read_current_line = 0;
+        }
+
+        return current;
+    }
+
+    /*!
+    @brief unget current character (read it again on next get)
+
+    We implement unget by setting variable next_unget to true. The input is not
+    changed - we just simulate ungetting by modifying chars_read_total,
+    chars_read_current_line, and token_string. The next call to get() will
+    behave as if the unget character is read again.
+    */
+    void unget()
+    {
+        next_unget = true;
+
+        --position.chars_read_total;
+
+        // in case we "unget" a newline, we have to also decrement the lines_read
+        if (position.chars_read_current_line == 0)
+        {
+            if (position.lines_read > 0)
+            {
+                --position.lines_read;
+            }
+        }
+        else
+        {
+            --position.chars_read_current_line;
+        }
+
+        if (JSON_LIKELY(current != std::char_traits<char>::eof()))
+        {
+            assert(token_string.size() != 0);
+            token_string.pop_back();
+        }
+    }
+
+    /// add a character to token_buffer
+    void add(int c)
+    {
+        token_buffer.push_back(std::char_traits<char>::to_char_type(c));
+    }
+
+  public:
+    /////////////////////
+    // value getters
+    /////////////////////
+
+    /// return integer value
+    constexpr number_integer_t get_number_integer() const noexcept
+    {
+        return value_integer;
+    }
+
+    /// return unsigned integer value
+    constexpr number_unsigned_t get_number_unsigned() const noexcept
+    {
+        return value_unsigned;
+    }
+
+    /// return floating-point value
+    constexpr number_float_t get_number_float() const noexcept
+    {
+        return value_float;
+    }
+
+    /// return current string value (implicitly resets the token; useful only once)
+    string_t& get_string()
+    {
+        return token_buffer;
+    }
+
+    /////////////////////
+    // diagnostics
+    /////////////////////
+
+    /// return position of last read token
+    constexpr position_t get_position() const noexcept
+    {
+        return position;
+    }
+
+    /// return the last read token (for errors only).  Will never contain EOF
+    /// (an arbitrary value that is not a valid char value, often -1), because
+    /// 255 may legitimately occur.  May contain NUL, which should be escaped.
+    std::string get_token_string() const
+    {
+        // escape control characters
+        std::string result;
+        for (const auto c : token_string)
+        {
+            if ('\x00' <= c and c <= '\x1F')
+            {
+                // escape control characters
+                char cs[9];
+                (std::snprintf)(cs, 9, "<U+%.4X>", static_cast<unsigned char>(c));
+                result += cs;
+            }
+            else
+            {
+                // add character as is
+                result.push_back(c);
+            }
+        }
+
+        return result;
+    }
+
+    /// return syntax error message
+    constexpr const char* get_error_message() const noexcept
+    {
+        return error_message;
+    }
+
+    /////////////////////
+    // actual scanner
+    /////////////////////
+
+    /*!
+    @brief skip the UTF-8 byte order mark
+    @return true iff there is no BOM or the correct BOM has been skipped
+    */
+    bool skip_bom()
+    {
+        if (get() == 0xEF)
+        {
+            // check if we completely parse the BOM
+            return get() == 0xBB and get() == 0xBF;
+        }
+
+        // the first character is not the beginning of the BOM; unget it to
+        // process is later
+        unget();
+        return true;
+    }
+
+    token_type scan()
+    {
+        // initially, skip the BOM
+        if (position.chars_read_total == 0 and not skip_bom())
+        {
+            error_message = "invalid BOM; must be 0xEF 0xBB 0xBF if given";
+            return token_type::parse_error;
+        }
+
+        // read next character and ignore whitespace
+        do
+        {
+            get();
+        }
+        while (current == ' ' or current == '\t' or current == '\n' or current == '\r');
+
+        switch (current)
+        {
+            // structural characters
+            case '[':
+                return token_type::begin_array;
+            case ']':
+                return token_type::end_array;
+            case '{':
+                return token_type::begin_object;
+            case '}':
+                return token_type::end_object;
+            case ':':
+                return token_type::name_separator;
+            case ',':
+                return token_type::value_separator;
+
+            // literals
+            case 't':
+                return scan_literal("true", 4, token_type::literal_true);
+            case 'f':
+                return scan_literal("false", 5, token_type::literal_false);
+            case 'n':
+                return scan_literal("null", 4, token_type::literal_null);
+
+            // string
+            case '\"':
+                return scan_string();
+
+            // number
+            case '-':
+            case '0':
+            case '1':
+            case '2':
+            case '3':
+            case '4':
+            case '5':
+            case '6':
+            case '7':
+            case '8':
+            case '9':
+                return scan_number();
+
+            // end of input (the null byte is needed when parsing from
+            // string literals)
+            case '\0':
+            case std::char_traits<char>::eof():
+                return token_type::end_of_input;
+
+            // error
+            default:
+                error_message = "invalid literal";
+                return token_type::parse_error;
+        }
+    }
+
+  private:
+    /// input adapter
+    detail::input_adapter_t ia = nullptr;
+
+    /// the current character
+    std::char_traits<char>::int_type current = std::char_traits<char>::eof();
+
+    /// whether the next get() call should just return current
+    bool next_unget = false;
+
+    /// the start position of the current token
+    position_t position;
+
+    /// raw input token string (for error messages)
+    std::vector<char> token_string {};
+
+    /// buffer for variable-length tokens (numbers, strings)
+    string_t token_buffer {};
+
+    /// a description of occurred lexer errors
+    const char* error_message = "";
+
+    // number values
+    number_integer_t value_integer = 0;
+    number_unsigned_t value_unsigned = 0;
+    number_float_t value_float = 0;
+
+    /// the decimal point
+    const char decimal_point_char = '.';
+};
+}  // namespace detail
+}  // namespace nlohmann
+
+// #include <nlohmann/detail/input/parser.hpp>
+
+
+#include <cassert> // assert
+#include <cmath> // isfinite
+#include <cstdint> // uint8_t
+#include <functional> // function
+#include <string> // string
+#include <utility> // move
+
+// #include <nlohmann/detail/exceptions.hpp>
+
+// #include <nlohmann/detail/macro_scope.hpp>
+
+// #include <nlohmann/detail/meta/is_sax.hpp>
+
+
+#include <cstdint> // size_t
+#include <utility> // declval
+
+// #include <nlohmann/detail/meta/detected.hpp>
+
+// #include <nlohmann/detail/meta/type_traits.hpp>
+
+
+namespace nlohmann
+{
+namespace detail
+{
+template <typename T>
+using null_function_t = decltype(std::declval<T&>().null());
+
+template <typename T>
+using boolean_function_t =
+    decltype(std::declval<T&>().boolean(std::declval<bool>()));
+
+template <typename T, typename Integer>
+using number_integer_function_t =
+    decltype(std::declval<T&>().number_integer(std::declval<Integer>()));
+
+template <typename T, typename Unsigned>
+using number_unsigned_function_t =
+    decltype(std::declval<T&>().number_unsigned(std::declval<Unsigned>()));
+
+template <typename T, typename Float, typename String>
+using number_float_function_t = decltype(std::declval<T&>().number_float(
+                                    std::declval<Float>(), std::declval<const String&>()));
+
+template <typename T, typename String>
+using string_function_t =
+    decltype(std::declval<T&>().string(std::declval<String&>()));
+
+template <typename T>
+using start_object_function_t =
+    decltype(std::declval<T&>().start_object(std::declval<std::size_t>()));
+
+template <typename T, typename String>
+using key_function_t =
+    decltype(std::declval<T&>().key(std::declval<String&>()));
+
+template <typename T>
+using end_object_function_t = decltype(std::declval<T&>().end_object());
+
+template <typename T>
+using start_array_function_t =
+    decltype(std::declval<T&>().start_array(std::declval<std::size_t>()));
+
+template <typename T>
+using end_array_function_t = decltype(std::declval<T&>().end_array());
+
+template <typename T, typename Exception>
+using parse_error_function_t = decltype(std::declval<T&>().parse_error(
+        std::declval<std::size_t>(), std::declval<const std::string&>(),
+        std::declval<const Exception&>()));
+
+template <typename SAX, typename BasicJsonType>
+struct is_sax
+{
+  private:
+    static_assert(is_basic_json<BasicJsonType>::value,
+                  "BasicJsonType must be of type basic_json<...>");
+
+    using number_integer_t = typename BasicJsonType::number_integer_t;
+    using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
+    using number_float_t = typename BasicJsonType::number_float_t;
+    using string_t = typename BasicJsonType::string_t;
+    using exception_t = typename BasicJsonType::exception;
+
+  public:
+    static constexpr bool value =
+        is_detected_exact<bool, null_function_t, SAX>::value &&
+        is_detected_exact<bool, boolean_function_t, SAX>::value &&
+        is_detected_exact<bool, number_integer_function_t, SAX,
+        number_integer_t>::value &&
+        is_detected_exact<bool, number_unsigned_function_t, SAX,
+        number_unsigned_t>::value &&
+        is_detected_exact<bool, number_float_function_t, SAX, number_float_t,
+        string_t>::value &&
+        is_detected_exact<bool, string_function_t, SAX, string_t>::value &&
+        is_detected_exact<bool, start_object_function_t, SAX>::value &&
+        is_detected_exact<bool, key_function_t, SAX, string_t>::value &&
+        is_detected_exact<bool, end_object_function_t, SAX>::value &&
+        is_detected_exact<bool, start_array_function_t, SAX>::value &&
+        is_detected_exact<bool, end_array_function_t, SAX>::value &&
+        is_detected_exact<bool, parse_error_function_t, SAX, exception_t>::value;
+};
+
+template <typename SAX, typename BasicJsonType>
+struct is_sax_static_asserts
+{
+  private:
+    static_assert(is_basic_json<BasicJsonType>::value,
+                  "BasicJsonType must be of type basic_json<...>");
+
+    using number_integer_t = typename BasicJsonType::number_integer_t;
+    using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
+    using number_float_t = typename BasicJsonType::number_float_t;
+    using string_t = typename BasicJsonType::string_t;
+    using exception_t = typename BasicJsonType::exception;
+
+  public:
+    static_assert(is_detected_exact<bool, null_function_t, SAX>::value,
+                  "Missing/invalid function: bool null()");
+    static_assert(is_detected_exact<bool, boolean_function_t, SAX>::value,
+                  "Missing/invalid function: bool boolean(bool)");
+    static_assert(is_detected_exact<bool, boolean_function_t, SAX>::value,
+                  "Missing/invalid function: bool boolean(bool)");
+    static_assert(
+        is_detected_exact<bool, number_integer_function_t, SAX,
+        number_integer_t>::value,
+        "Missing/invalid function: bool number_integer(number_integer_t)");
+    static_assert(
+        is_detected_exact<bool, number_unsigned_function_t, SAX,
+        number_unsigned_t>::value,
+        "Missing/invalid function: bool number_unsigned(number_unsigned_t)");
+    static_assert(is_detected_exact<bool, number_float_function_t, SAX,
+                  number_float_t, string_t>::value,
+                  "Missing/invalid function: bool number_float(number_float_t, const string_t&)");
+    static_assert(
+        is_detected_exact<bool, string_function_t, SAX, string_t>::value,
+        "Missing/invalid function: bool string(string_t&)");
+    static_assert(is_detected_exact<bool, start_object_function_t, SAX>::value,
+                  "Missing/invalid function: bool start_object(std::size_t)");
+    static_assert(is_detected_exact<bool, key_function_t, SAX, string_t>::value,
+                  "Missing/invalid function: bool key(string_t&)");
+    static_assert(is_detected_exact<bool, end_object_function_t, SAX>::value,
+                  "Missing/invalid function: bool end_object()");
+    static_assert(is_detected_exact<bool, start_array_function_t, SAX>::value,
+                  "Missing/invalid function: bool start_array(std::size_t)");
+    static_assert(is_detected_exact<bool, end_array_function_t, SAX>::value,
+                  "Missing/invalid function: bool end_array()");
+    static_assert(
+        is_detected_exact<bool, parse_error_function_t, SAX, exception_t>::value,
+        "Missing/invalid function: bool parse_error(std::size_t, const "
+        "std::string&, const exception&)");
+};
+}  // namespace detail
+}  // namespace nlohmann
+
+// #include <nlohmann/detail/input/input_adapters.hpp>
+
+// #include <nlohmann/detail/input/json_sax.hpp>
+
+
+#include <cstddef>
+#include <string>
+#include <vector>
+
+// #include <nlohmann/detail/input/parser.hpp>
+
+// #include <nlohmann/detail/exceptions.hpp>
+
+
+namespace nlohmann
+{
+
+/*!
+@brief SAX interface
+
+This class describes the SAX interface used by @ref nlohmann::json::sax_parse.
+Each function is called in different situations while the input is parsed. The
+boolean return value informs the parser whether to continue processing the
+input.
+*/
+template<typename BasicJsonType>
+struct json_sax
+{
+    /// type for (signed) integers
+    using number_integer_t = typename BasicJsonType::number_integer_t;
+    /// type for unsigned integers
+    using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
+    /// type for floating-point numbers
+    using number_float_t = typename BasicJsonType::number_float_t;
+    /// type for strings
+    using string_t = typename BasicJsonType::string_t;
+
+    /*!
+    @brief a null value was read
+    @return whether parsing should proceed
+    */
+    virtual bool null() = 0;
+
+    /*!
+    @brief a boolean value was read
+    @param[in] val  boolean value
+    @return whether parsing should proceed
+    */
+    virtual bool boolean(bool val) = 0;
+
+    /*!
+    @brief an integer number was read
+    @param[in] val  integer value
+    @return whether parsing should proceed
+    */
+    virtual bool number_integer(number_integer_t val) = 0;
+
+    /*!
+    @brief an unsigned integer number was read
+    @param[in] val  unsigned integer value
+    @return whether parsing should proceed
+    */
+    virtual bool number_unsigned(number_unsigned_t val) = 0;
+
+    /*!
+    @brief an floating-point number was read
+    @param[in] val  floating-point value
+    @param[in] s    raw token value
+    @return whether parsing should proceed
+    */
+    virtual bool number_float(number_float_t val, const string_t& s) = 0;
+
+    /*!
+    @brief a string was read
+    @param[in] val  string value
+    @return whether parsing should proceed
+    @note It is safe to move the passed string.
+    */
+    virtual bool string(string_t& val) = 0;
+
+    /*!
+    @brief the beginning of an object was read
+    @param[in] elements  number of object elements or -1 if unknown
+    @return whether parsing should proceed
+    @note binary formats may report the number of elements
+    */
+    virtual bool start_object(std::size_t elements) = 0;
+
+    /*!
+    @brief an object key was read
+    @param[in] val  object key
+    @return whether parsing should proceed
+    @note It is safe to move the passed string.
+    */
+    virtual bool key(string_t& val) = 0;
+
+    /*!
+    @brief the end of an object was read
+    @return whether parsing should proceed
+    */
+    virtual bool end_object() = 0;
+
+    /*!
+    @brief the beginning of an array was read
+    @param[in] elements  number of array elements or -1 if unknown
+    @return whether parsing should proceed
+    @note binary formats may report the number of elements
+    */
+    virtual bool start_array(std::size_t elements) = 0;
+
+    /*!
+    @brief the end of an array was read
+    @return whether parsing should proceed
+    */
+    virtual bool end_array() = 0;
+
+    /*!
+    @brief a parse error occurred
+    @param[in] position    the position in the input where the error occurs
+    @param[in] last_token  the last read token
+    @param[in] ex          an exception object describing the error
+    @return whether parsing should proceed (must return false)
+    */
+    virtual bool parse_error(std::size_t position,
+                             const std::string& last_token,
+                             const detail::exception& ex) = 0;
+
+    virtual ~json_sax() = default;
+};
+
+
+namespace detail
+{
+/*!
+@brief SAX implementation to create a JSON value from SAX events
+
+This class implements the @ref json_sax interface and processes the SAX events
+to create a JSON value which makes it basically a DOM parser. The structure or
+hierarchy of the JSON value is managed by the stack `ref_stack` which contains
+a pointer to the respective array or object for each recursion depth.
+
+After successful parsing, the value that is passed by reference to the
+constructor contains the parsed value.
+
+@tparam BasicJsonType  the JSON type
+*/
+template<typename BasicJsonType>
+class json_sax_dom_parser
+{
+  public:
+    using number_integer_t = typename BasicJsonType::number_integer_t;
+    using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
+    using number_float_t = typename BasicJsonType::number_float_t;
+    using string_t = typename BasicJsonType::string_t;
+
+    /*!
+    @param[in, out] r  reference to a JSON value that is manipulated while
+                       parsing
+    @param[in] allow_exceptions_  whether parse errors yield exceptions
+    */
+    explicit json_sax_dom_parser(BasicJsonType& r, const bool allow_exceptions_ = true)
+        : root(r), allow_exceptions(allow_exceptions_)
+    {}
+
+    bool null()
+    {
+        handle_value(nullptr);
+        return true;
+    }
+
+    bool boolean(bool val)
+    {
+        handle_value(val);
+        return true;
+    }
+
+    bool number_integer(number_integer_t val)
+    {
+        handle_value(val);
+        return true;
+    }
+
+    bool number_unsigned(number_unsigned_t val)
+    {
+        handle_value(val);
+        return true;
+    }
+
+    bool number_float(number_float_t val, const string_t& /*unused*/)
+    {
+        handle_value(val);
+        return true;
+    }
+
+    bool string(string_t& val)
+    {
+        handle_value(val);
+        return true;
+    }
+
+    bool start_object(std::size_t len)
+    {
+        ref_stack.push_back(handle_value(BasicJsonType::value_t::object));
+
+        if (JSON_UNLIKELY(len != std::size_t(-1) and len > ref_stack.back()->max_size()))
+        {
+            JSON_THROW(out_of_range::create(408,
+                                            "excessive object size: " + std::to_string(len)));
+        }
+
+        return true;
+    }
+
+    bool key(string_t& val)
+    {
+        // add null at given key and store the reference for later
+        object_element = &(ref_stack.back()->m_value.object->operator[](val));
+        return true;
+    }
+
+    bool end_object()
+    {
+        ref_stack.pop_back();
+        return true;
+    }
+
+    bool start_array(std::size_t len)
+    {
+        ref_stack.push_back(handle_value(BasicJsonType::value_t::array));
+
+        if (JSON_UNLIKELY(len != std::size_t(-1) and len > ref_stack.back()->max_size()))
+        {
+            JSON_THROW(out_of_range::create(408,
+                                            "excessive array size: " + std::to_string(len)));
+        }
+
+        return true;
+    }
+
+    bool end_array()
+    {
+        ref_stack.pop_back();
+        return true;
+    }
+
+    bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/,
+                     const detail::exception& ex)
+    {
+        errored = true;
+        if (allow_exceptions)
+        {
+            // determine the proper exception type from the id
+            switch ((ex.id / 100) % 100)
+            {
+                case 1:
+                    JSON_THROW(*reinterpret_cast<const detail::parse_error*>(&ex));
+                case 4:
+                    JSON_THROW(*reinterpret_cast<const detail::out_of_range*>(&ex));
+                // LCOV_EXCL_START
+                case 2:
+                    JSON_THROW(*reinterpret_cast<const detail::invalid_iterator*>(&ex));
+                case 3:
+                    JSON_THROW(*reinterpret_cast<const detail::type_error*>(&ex));
+                case 5:
+                    JSON_THROW(*reinterpret_cast<const detail::other_error*>(&ex));
+                default:
+                    assert(false);
+                    // LCOV_EXCL_STOP
+            }
+        }
+        return false;
+    }
+
+    constexpr bool is_errored() const
+    {
+        return errored;
+    }
+
+  private:
+    /*!
+    @invariant If the ref stack is empty, then the passed value will be the new
+               root.
+    @invariant If the ref stack contains a value, then it is an array or an
+               object to which we can add elements
+    */
+    template<typename Value>
+    BasicJsonType* handle_value(Value&& v)
+    {
+        if (ref_stack.empty())
+        {
+            root = BasicJsonType(std::forward<Value>(v));
+            return &root;
+        }
+
+        assert(ref_stack.back()->is_array() or ref_stack.back()->is_object());
+
+        if (ref_stack.back()->is_array())
+        {
+            ref_stack.back()->m_value.array->emplace_back(std::forward<Value>(v));
+            return &(ref_stack.back()->m_value.array->back());
+        }
+        else
+        {
+            assert(object_element);
+            *object_element = BasicJsonType(std::forward<Value>(v));
+            return object_element;
+        }
+    }
+
+    /// the parsed JSON value
+    BasicJsonType& root;
+    /// stack to model hierarchy of values
+    std::vector<BasicJsonType*> ref_stack;
+    /// helper to hold the reference for the next object element
+    BasicJsonType* object_element = nullptr;
+    /// whether a syntax error occurred
+    bool errored = false;
+    /// whether to throw exceptions in case of errors
+    const bool allow_exceptions = true;
+};
+
+template<typename BasicJsonType>
+class json_sax_dom_callback_parser
+{
+  public:
+    using number_integer_t = typename BasicJsonType::number_integer_t;
+    using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
+    using number_float_t = typename BasicJsonType::number_float_t;
+    using string_t = typename BasicJsonType::string_t;
+    using parser_callback_t = typename BasicJsonType::parser_callback_t;
+    using parse_event_t = typename BasicJsonType::parse_event_t;
+
+    json_sax_dom_callback_parser(BasicJsonType& r,
+                                 const parser_callback_t cb,
+                                 const bool allow_exceptions_ = true)
+        : root(r), callback(cb), allow_exceptions(allow_exceptions_)
+    {
+        keep_stack.push_back(true);
+    }
+
+    bool null()
+    {
+        handle_value(nullptr);
+        return true;
+    }
+
+    bool boolean(bool val)
+    {
+        handle_value(val);
+        return true;
+    }
+
+    bool number_integer(number_integer_t val)
+    {
+        handle_value(val);
+        return true;
+    }
+
+    bool number_unsigned(number_unsigned_t val)
+    {
+        handle_value(val);
+        return true;
+    }
+
+    bool number_float(number_float_t val, const string_t& /*unused*/)
+    {
+        handle_value(val);
+        return true;
+    }
+
+    bool string(string_t& val)
+    {
+        handle_value(val);
+        return true;
+    }
+
+    bool start_object(std::size_t len)
+    {
+        // check callback for object start
+        const bool keep = callback(static_cast<int>(ref_stack.size()), parse_event_t::object_start, discarded);
+        keep_stack.push_back(keep);
+
+        auto val = handle_value(BasicJsonType::value_t::object, true);
+        ref_stack.push_back(val.second);
+
+        // check object limit
+        if (ref_stack.back())
+        {
+            if (JSON_UNLIKELY(len != std::size_t(-1) and len > ref_stack.back()->max_size()))
+            {
+                JSON_THROW(out_of_range::create(408,
+                                                "excessive object size: " + std::to_string(len)));
+            }
+        }
+
+        return true;
+    }
+
+    bool key(string_t& val)
+    {
+        BasicJsonType k = BasicJsonType(val);
+
+        // check callback for key
+        const bool keep = callback(static_cast<int>(ref_stack.size()), parse_event_t::key, k);
+        key_keep_stack.push_back(keep);
+
+        // add discarded value at given key and store the reference for later
+        if (keep and ref_stack.back())
+        {
+            object_element = &(ref_stack.back()->m_value.object->operator[](val) = discarded);
+        }
+
+        return true;
+    }
+
+    bool end_object()
+    {
+        if (ref_stack.back())
+        {
+            if (not callback(static_cast<int>(ref_stack.size()) - 1, parse_event_t::object_end, *ref_stack.back()))
+            {
+                // discard object
+                *ref_stack.back() = discarded;
+            }
+        }
+
+        assert(not ref_stack.empty());
+        assert(not keep_stack.empty());
+        ref_stack.pop_back();
+        keep_stack.pop_back();
+
+        if (not ref_stack.empty() and ref_stack.back())
+        {
+            // remove discarded value
+            if (ref_stack.back()->is_object())
+            {
+                for (auto it = ref_stack.back()->begin(); it != ref_stack.back()->end(); ++it)
+                {
+                    if (it->is_discarded())
+                    {
+                        ref_stack.back()->erase(it);
+                        break;
+                    }
+                }
+            }
+        }
+
+        return true;
+    }
+
+    bool start_array(std::size_t len)
+    {
+        const bool keep = callback(static_cast<int>(ref_stack.size()), parse_event_t::array_start, discarded);
+        keep_stack.push_back(keep);
+
+        auto val = handle_value(BasicJsonType::value_t::array, true);
+        ref_stack.push_back(val.second);
+
+        // check array limit
+        if (ref_stack.back())
+        {
+            if (JSON_UNLIKELY(len != std::size_t(-1) and len > ref_stack.back()->max_size()))
+            {
+                JSON_THROW(out_of_range::create(408,
+                                                "excessive array size: " + std::to_string(len)));
+            }
+        }
+
+        return true;
+    }
+
+    bool end_array()
+    {
+        bool keep = true;
+
+        if (ref_stack.back())
+        {
+            keep = callback(static_cast<int>(ref_stack.size()) - 1, parse_event_t::array_end, *ref_stack.back());
+            if (not keep)
+            {
+                // discard array
+                *ref_stack.back() = discarded;
+            }
+        }
+
+        assert(not ref_stack.empty());
+        assert(not keep_stack.empty());
+        ref_stack.pop_back();
+        keep_stack.pop_back();
+
+        // remove discarded value
+        if (not keep and not ref_stack.empty())
+        {
+            if (ref_stack.back()->is_array())
+            {
+                ref_stack.back()->m_value.array->pop_back();
+            }
+        }
+
+        return true;
+    }
+
+    bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/,
+                     const detail::exception& ex)
+    {
+        errored = true;
+        if (allow_exceptions)
+        {
+            // determine the proper exception type from the id
+            switch ((ex.id / 100) % 100)
+            {
+                case 1:
+                    JSON_THROW(*reinterpret_cast<const detail::parse_error*>(&ex));
+                case 4:
+                    JSON_THROW(*reinterpret_cast<const detail::out_of_range*>(&ex));
+                // LCOV_EXCL_START
+                case 2:
+                    JSON_THROW(*reinterpret_cast<const detail::invalid_iterator*>(&ex));
+                case 3:
+                    JSON_THROW(*reinterpret_cast<const detail::type_error*>(&ex));
+                case 5:
+                    JSON_THROW(*reinterpret_cast<const detail::other_error*>(&ex));
+                default:
+                    assert(false);
+                    // LCOV_EXCL_STOP
+            }
+        }
+        return false;
+    }
+
+    constexpr bool is_errored() const
+    {
+        return errored;
+    }
+
+  private:
+    /*!
+    @param[in] v  value to add to the JSON value we build during parsing
+    @param[in] skip_callback  whether we should skip calling the callback
+               function; this is required after start_array() and
+               start_object() SAX events, because otherwise we would call the
+               callback function with an empty array or object, respectively.
+
+    @invariant If the ref stack is empty, then the passed value will be the new
+               root.
+    @invariant If the ref stack contains a value, then it is an array or an
+               object to which we can add elements
+
+    @return pair of boolean (whether value should be kept) and pointer (to the
+            passed value in the ref_stack hierarchy; nullptr if not kept)
+    */
+    template<typename Value>
+    std::pair<bool, BasicJsonType*> handle_value(Value&& v, const bool skip_callback = false)
+    {
+        assert(not keep_stack.empty());
+
+        // do not handle this value if we know it would be added to a discarded
+        // container
+        if (not keep_stack.back())
+        {
+            return {false, nullptr};
+        }
+
+        // create value
+        auto value = BasicJsonType(std::forward<Value>(v));
+
+        // check callback
+        const bool keep = skip_callback or callback(static_cast<int>(ref_stack.size()), parse_event_t::value, value);
+
+        // do not handle this value if we just learnt it shall be discarded
+        if (not keep)
+        {
+            return {false, nullptr};
+        }
+
+        if (ref_stack.empty())
+        {
+            root = std::move(value);
+            return {true, &root};
+        }
+
+        // skip this value if we already decided to skip the parent
+        // (https://github.com/nlohmann/json/issues/971#issuecomment-413678360)
+        if (not ref_stack.back())
+        {
+            return {false, nullptr};
+        }
+
+        // we now only expect arrays and objects
+        assert(ref_stack.back()->is_array() or ref_stack.back()->is_object());
+
+        if (ref_stack.back()->is_array())
+        {
+            ref_stack.back()->m_value.array->push_back(std::move(value));
+            return {true, &(ref_stack.back()->m_value.array->back())};
+        }
+        else
+        {
+            // check if we should store an element for the current key
+            assert(not key_keep_stack.empty());
+            const bool store_element = key_keep_stack.back();
+            key_keep_stack.pop_back();
+
+            if (not store_element)
+            {
+                return {false, nullptr};
+            }
+
+            assert(object_element);
+            *object_element = std::move(value);
+            return {true, object_element};
+        }
+    }
+
+    /// the parsed JSON value
+    BasicJsonType& root;
+    /// stack to model hierarchy of values
+    std::vector<BasicJsonType*> ref_stack;
+    /// stack to manage which values to keep
+    std::vector<bool> keep_stack;
+    /// stack to manage which object keys to keep
+    std::vector<bool> key_keep_stack;
+    /// helper to hold the reference for the next object element
+    BasicJsonType* object_element = nullptr;
+    /// whether a syntax error occurred
+    bool errored = false;
+    /// callback function
+    const parser_callback_t callback = nullptr;
+    /// whether to throw exceptions in case of errors
+    const bool allow_exceptions = true;
+    /// a discarded value for the callback
+    BasicJsonType discarded = BasicJsonType::value_t::discarded;
+};
+
+template<typename BasicJsonType>
+class json_sax_acceptor
+{
+  public:
+    using number_integer_t = typename BasicJsonType::number_integer_t;
+    using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
+    using number_float_t = typename BasicJsonType::number_float_t;
+    using string_t = typename BasicJsonType::string_t;
+
+    bool null()
+    {
+        return true;
+    }
+
+    bool boolean(bool /*unused*/)
+    {
+        return true;
+    }
+
+    bool number_integer(number_integer_t /*unused*/)
+    {
+        return true;
+    }
+
+    bool number_unsigned(number_unsigned_t /*unused*/)
+    {
+        return true;
+    }
+
+    bool number_float(number_float_t /*unused*/, const string_t& /*unused*/)
+    {
+        return true;
+    }
+
+    bool string(string_t& /*unused*/)
+    {
+        return true;
+    }
+
+    bool start_object(std::size_t  /*unused*/ = std::size_t(-1))
+    {
+        return true;
+    }
+
+    bool key(string_t& /*unused*/)
+    {
+        return true;
+    }
+
+    bool end_object()
+    {
+        return true;
+    }
+
+    bool start_array(std::size_t  /*unused*/ = std::size_t(-1))
+    {
+        return true;
+    }
+
+    bool end_array()
+    {
+        return true;
+    }
+
+    bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const detail::exception& /*unused*/)
+    {
+        return false;
+    }
+};
+}  // namespace detail
+
+}  // namespace nlohmann
+
+// #include <nlohmann/detail/input/lexer.hpp>
+
+// #include <nlohmann/detail/value_t.hpp>
+
+
+namespace nlohmann
+{
+namespace detail
+{
+////////////
+// parser //
+////////////
+
+/*!
+@brief syntax analysis
+
+This class implements a recursive decent parser.
+*/
+template<typename BasicJsonType>
+class parser
+{
+    using number_integer_t = typename BasicJsonType::number_integer_t;
+    using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
+    using number_float_t = typename BasicJsonType::number_float_t;
+    using string_t = typename BasicJsonType::string_t;
+    using lexer_t = lexer<BasicJsonType>;
+    using token_type = typename lexer_t::token_type;
+
+  public:
+    enum class parse_event_t : uint8_t
+    {
+        /// the parser read `{` and started to process a JSON object
+        object_start,
+        /// the parser read `}` and finished processing a JSON object
+        object_end,
+        /// the parser read `[` and started to process a JSON array
+        array_start,
+        /// the parser read `]` and finished processing a JSON array
+        array_end,
+        /// the parser read a key of a value in an object
+        key,
+        /// the parser finished reading a JSON value
+        value
+    };
+
+    using parser_callback_t =
+        std::function<bool(int depth, parse_event_t event, BasicJsonType& parsed)>;
+
+    /// a parser reading from an input adapter
+    explicit parser(detail::input_adapter_t&& adapter,
+                    const parser_callback_t cb = nullptr,
+                    const bool allow_exceptions_ = true)
+        : callback(cb), m_lexer(std::move(adapter)), allow_exceptions(allow_exceptions_)
+    {
+        // read first token
+        get_token();
+    }
+
+    /*!
+    @brief public parser interface
+
+    @param[in] strict      whether to expect the last token to be EOF
+    @param[in,out] result  parsed JSON value
+
+    @throw parse_error.101 in case of an unexpected token
+    @throw parse_error.102 if to_unicode fails or surrogate error
+    @throw parse_error.103 if to_unicode fails
+    */
+    void parse(const bool strict, BasicJsonType& result)
+    {
+        if (callback)
+        {
+            json_sax_dom_callback_parser<BasicJsonType> sdp(result, callback, allow_exceptions);
+            sax_parse_internal(&sdp);
+            result.assert_invariant();
+
+            // in strict mode, input must be completely read
+            if (strict and (get_token() != token_type::end_of_input))
+            {
+                sdp.parse_error(m_lexer.get_position(),
+                                m_lexer.get_token_string(),
+                                parse_error::create(101, m_lexer.get_position(),
+                                                    exception_message(token_type::end_of_input, "value")));
+            }
+
+            // in case of an error, return discarded value
+            if (sdp.is_errored())
+            {
+                result = value_t::discarded;
+                return;
+            }
+
+            // set top-level value to null if it was discarded by the callback
+            // function
+            if (result.is_discarded())
+            {
+                result = nullptr;
+            }
+        }
+        else
+        {
+            json_sax_dom_parser<BasicJsonType> sdp(result, allow_exceptions);
+            sax_parse_internal(&sdp);
+            result.assert_invariant();
+
+            // in strict mode, input must be completely read
+            if (strict and (get_token() != token_type::end_of_input))
+            {
+                sdp.parse_error(m_lexer.get_position(),
+                                m_lexer.get_token_string(),
+                                parse_error::create(101, m_lexer.get_position(),
+                                                    exception_message(token_type::end_of_input, "value")));
+            }
+
+            // in case of an error, return discarded value
+            if (sdp.is_errored())
+            {
+                result = value_t::discarded;
+                return;
+            }
+        }
+    }
+
+    /*!
+    @brief public accept interface
+
+    @param[in] strict  whether to expect the last token to be EOF
+    @return whether the input is a proper JSON text
+    */
+    bool accept(const bool strict = true)
+    {
+        json_sax_acceptor<BasicJsonType> sax_acceptor;
+        return sax_parse(&sax_acceptor, strict);
+    }
+
+    template <typename SAX>
+    bool sax_parse(SAX* sax, const bool strict = true)
+    {
+        (void)detail::is_sax_static_asserts<SAX, BasicJsonType> {};
+        const bool result = sax_parse_internal(sax);
+
+        // strict mode: next byte must be EOF
+        if (result and strict and (get_token() != token_type::end_of_input))
+        {
+            return sax->parse_error(m_lexer.get_position(),
+                                    m_lexer.get_token_string(),
+                                    parse_error::create(101, m_lexer.get_position(),
+                                            exception_message(token_type::end_of_input, "value")));
+        }
+
+        return result;
+    }
+
+  private:
+    template <typename SAX>
+    bool sax_parse_internal(SAX* sax)
+    {
+        // stack to remember the hierarchy of structured values we are parsing
+        // true = array; false = object
+        std::vector<bool> states;
+        // value to avoid a goto (see comment where set to true)
+        bool skip_to_state_evaluation = false;
+
+        while (true)
+        {
+            if (not skip_to_state_evaluation)
+            {
+                // invariant: get_token() was called before each iteration
+                switch (last_token)
+                {
+                    case token_type::begin_object:
+                    {
+                        if (JSON_UNLIKELY(not sax->start_object(std::size_t(-1))))
+                        {
+                            return false;
+                        }
+
+                        // closing } -> we are done
+                        if (get_token() == token_type::end_object)
+                        {
+                            if (JSON_UNLIKELY(not sax->end_object()))
+                            {
+                                return false;
+                            }
+                            break;
+                        }
+
+                        // parse key
+                        if (JSON_UNLIKELY(last_token != token_type::value_string))
+                        {
+                            return sax->parse_error(m_lexer.get_position(),
+                                                    m_lexer.get_token_string(),
+                                                    parse_error::create(101, m_lexer.get_position(),
+                                                            exception_message(token_type::value_string, "object key")));
+                        }
+                        if (JSON_UNLIKELY(not sax->key(m_lexer.get_string())))
+                        {
+                            return false;
+                        }
+
+                        // parse separator (:)
+                        if (JSON_UNLIKELY(get_token() != token_type::name_separator))
+                        {
+                            return sax->parse_error(m_lexer.get_position(),
+                                                    m_lexer.get_token_string(),
+                                                    parse_error::create(101, m_lexer.get_position(),
+                                                            exception_message(token_type::name_separator, "object separator")));
+                        }
+
+                        // remember we are now inside an object
+                        states.push_back(false);
+
+                        // parse values
+                        get_token();
+                        continue;
+                    }
+
+                    case token_type::begin_array:
+                    {
+                        if (JSON_UNLIKELY(not sax->start_array(std::size_t(-1))))
+                        {
+                            return false;
+                        }
+
+                        // closing ] -> we are done
+                        if (get_token() == token_type::end_array)
+                        {
+                            if (JSON_UNLIKELY(not sax->end_array()))
+                            {
+                                return false;
+                            }
+                            break;
+                        }
+
+                        // remember we are now inside an array
+                        states.push_back(true);
+
+                        // parse values (no need to call get_token)
+                        continue;
+                    }
+
+                    case token_type::value_float:
+                    {
+                        const auto res = m_lexer.get_number_float();
+
+                        if (JSON_UNLIKELY(not std::isfinite(res)))
+                        {
+                            return sax->parse_error(m_lexer.get_position(),
+                                                    m_lexer.get_token_string(),
+                                                    out_of_range::create(406, "number overflow parsing '" + m_lexer.get_token_string() + "'"));
+                        }
+                        else
+                        {
+                            if (JSON_UNLIKELY(not sax->number_float(res, m_lexer.get_string())))
+                            {
+                                return false;
+                            }
+                            break;
+                        }
+                    }
+
+                    case token_type::literal_false:
+                    {
+                        if (JSON_UNLIKELY(not sax->boolean(false)))
+                        {
+                            return false;
+                        }
+                        break;
+                    }
+
+                    case token_type::literal_null:
+                    {
+                        if (JSON_UNLIKELY(not sax->null()))
+                        {
+                            return false;
+                        }
+                        break;
+                    }
+
+                    case token_type::literal_true:
+                    {
+                        if (JSON_UNLIKELY(not sax->boolean(true)))
+                        {
+                            return false;
+                        }
+                        break;
+                    }
+
+                    case token_type::value_integer:
+                    {
+                        if (JSON_UNLIKELY(not sax->number_integer(m_lexer.get_number_integer())))
+                        {
+                            return false;
+                        }
+                        break;
+                    }
+
+                    case token_type::value_string:
+                    {
+                        if (JSON_UNLIKELY(not sax->string(m_lexer.get_string())))
+                        {
+                            return false;
+                        }
+                        break;
+                    }
+
+                    case token_type::value_unsigned:
+                    {
+                        if (JSON_UNLIKELY(not sax->number_unsigned(m_lexer.get_number_unsigned())))
+                        {
+                            return false;
+                        }
+                        break;
+                    }
+
+                    case token_type::parse_error:
+                    {
+                        // using "uninitialized" to avoid "expected" message
+                        return sax->parse_error(m_lexer.get_position(),
+                                                m_lexer.get_token_string(),
+                                                parse_error::create(101, m_lexer.get_position(),
+                                                        exception_message(token_type::uninitialized, "value")));
+                    }
+
+                    default: // the last token was unexpected
+                    {
+                        return sax->parse_error(m_lexer.get_position(),
+                                                m_lexer.get_token_string(),
+                                                parse_error::create(101, m_lexer.get_position(),
+                                                        exception_message(token_type::literal_or_value, "value")));
+                    }
+                }
+            }
+            else
+            {
+                skip_to_state_evaluation = false;
+            }
+
+            // we reached this line after we successfully parsed a value
+            if (states.empty())
+            {
+                // empty stack: we reached the end of the hierarchy: done
+                return true;
+            }
+            else
+            {
+                if (states.back())  // array
+                {
+                    // comma -> next value
+                    if (get_token() == token_type::value_separator)
+                    {
+                        // parse a new value
+                        get_token();
+                        continue;
+                    }
+
+                    // closing ]
+                    if (JSON_LIKELY(last_token == token_type::end_array))
+                    {
+                        if (JSON_UNLIKELY(not sax->end_array()))
+                        {
+                            return false;
+                        }
+
+                        // We are done with this array. Before we can parse a
+                        // new value, we need to evaluate the new state first.
+                        // By setting skip_to_state_evaluation to false, we
+                        // are effectively jumping to the beginning of this if.
+                        assert(not states.empty());
+                        states.pop_back();
+                        skip_to_state_evaluation = true;
+                        continue;
+                    }
+                    else
+                    {
+                        return sax->parse_error(m_lexer.get_position(),
+                                                m_lexer.get_token_string(),
+                                                parse_error::create(101, m_lexer.get_position(),
+                                                        exception_message(token_type::end_array, "array")));
+                    }
+                }
+                else  // object
+                {
+                    // comma -> next value
+                    if (get_token() == token_type::value_separator)
+                    {
+                        // parse key
+                        if (JSON_UNLIKELY(get_token() != token_type::value_string))
+                        {
+                            return sax->parse_error(m_lexer.get_position(),
+                                                    m_lexer.get_token_string(),
+                                                    parse_error::create(101, m_lexer.get_position(),
+                                                            exception_message(token_type::value_string, "object key")));
+                        }
+                        else
+                        {
+                            if (JSON_UNLIKELY(not sax->key(m_lexer.get_string())))
+                            {
+                                return false;
+                            }
+                        }
+
+                        // parse separator (:)
+                        if (JSON_UNLIKELY(get_token() != token_type::name_separator))
+                        {
+                            return sax->parse_error(m_lexer.get_position(),
+                                                    m_lexer.get_token_string(),
+                                                    parse_error::create(101, m_lexer.get_position(),
+                                                            exception_message(token_type::name_separator, "object separator")));
+                        }
+
+                        // parse values
+                        get_token();
+                        continue;
+                    }
+
+                    // closing }
+                    if (JSON_LIKELY(last_token == token_type::end_object))
+                    {
+                        if (JSON_UNLIKELY(not sax->end_object()))
+                        {
+                            return false;
+                        }
+
+                        // We are done with this object. Before we can parse a
+                        // new value, we need to evaluate the new state first.
+                        // By setting skip_to_state_evaluation to false, we
+                        // are effectively jumping to the beginning of this if.
+                        assert(not states.empty());
+                        states.pop_back();
+                        skip_to_state_evaluation = true;
+                        continue;
+                    }
+                    else
+                    {
+                        return sax->parse_error(m_lexer.get_position(),
+                                                m_lexer.get_token_string(),
+                                                parse_error::create(101, m_lexer.get_position(),
+                                                        exception_message(token_type::end_object, "object")));
+                    }
+                }
+            }
+        }
+    }
+
+    /// get next token from lexer
+    token_type get_token()
+    {
+        return (last_token = m_lexer.scan());
+    }
+
+    std::string exception_message(const token_type expected, const std::string& context)
+    {
+        std::string error_msg = "syntax error ";
+
+        if (not context.empty())
+        {
+            error_msg += "while parsing " + context + " ";
+        }
+
+        error_msg += "- ";
+
+        if (last_token == token_type::parse_error)
+        {
+            error_msg += std::string(m_lexer.get_error_message()) + "; last read: '" +
+                         m_lexer.get_token_string() + "'";
+        }
+        else
+        {
+            error_msg += "unexpected " + std::string(lexer_t::token_type_name(last_token));
+        }
+
+        if (expected != token_type::uninitialized)
+        {
+            error_msg += "; expected " + std::string(lexer_t::token_type_name(expected));
+        }
+
+        return error_msg;
+    }
+
+  private:
+    /// callback function
+    const parser_callback_t callback = nullptr;
+    /// the type of the last read token
+    token_type last_token = token_type::uninitialized;
+    /// the lexer
+    lexer_t m_lexer;
+    /// whether to throw exceptions in case of errors
+    const bool allow_exceptions = true;
+};
+}  // namespace detail
+}  // namespace nlohmann
+
+// #include <nlohmann/detail/iterators/primitive_iterator.hpp>
+
+
+#include <cstddef> // ptrdiff_t
+#include <limits>  // numeric_limits
+
+namespace nlohmann
+{
+namespace detail
+{
+/*
+@brief an iterator for primitive JSON types
+
+This class models an iterator for primitive JSON types (boolean, number,
+string). It's only purpose is to allow the iterator/const_iterator classes
+to "iterate" over primitive values. Internally, the iterator is modeled by
+a `difference_type` variable. Value begin_value (`0`) models the begin,
+end_value (`1`) models past the end.
+*/
+class primitive_iterator_t
+{
+  private:
+    using difference_type = std::ptrdiff_t;
+    static constexpr difference_type begin_value = 0;
+    static constexpr difference_type end_value = begin_value + 1;
+
+    /// iterator as signed integer type
+    difference_type m_it = (std::numeric_limits<std::ptrdiff_t>::min)();
+
+  public:
+    constexpr difference_type get_value() const noexcept
+    {
+        return m_it;
+    }
+
+    /// set iterator to a defined beginning
+    void set_begin() noexcept
+    {
+        m_it = begin_value;
+    }
+
+    /// set iterator to a defined past the end
+    void set_end() noexcept
+    {
+        m_it = end_value;
+    }
+
+    /// return whether the iterator can be dereferenced
+    constexpr bool is_begin() const noexcept
+    {
+        return m_it == begin_value;
+    }
+
+    /// return whether the iterator is at end
+    constexpr bool is_end() const noexcept
+    {
+        return m_it == end_value;
+    }
+
+    friend constexpr bool operator==(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept
+    {
+        return lhs.m_it == rhs.m_it;
+    }
+
+    friend constexpr bool operator<(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept
+    {
+        return lhs.m_it < rhs.m_it;
+    }
+
+    primitive_iterator_t operator+(difference_type n) noexcept
+    {
+        auto result = *this;
+        result += n;
+        return result;
+    }
+
+    friend constexpr difference_type operator-(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept
+    {
+        return lhs.m_it - rhs.m_it;
+    }
+
+    primitive_iterator_t& operator++() noexcept
+    {
+        ++m_it;
+        return *this;
+    }
+
+    primitive_iterator_t const operator++(int) noexcept
+    {
+        auto result = *this;
+        ++m_it;
+        return result;
+    }
+
+    primitive_iterator_t& operator--() noexcept
+    {
+        --m_it;
+        return *this;
+    }
+
+    primitive_iterator_t const operator--(int) noexcept
+    {
+        auto result = *this;
+        --m_it;
+        return result;
+    }
+
+    primitive_iterator_t& operator+=(difference_type n) noexcept
+    {
+        m_it += n;
+        return *this;
+    }
+
+    primitive_iterator_t& operator-=(difference_type n) noexcept
+    {
+        m_it -= n;
+        return *this;
+    }
+};
+}  // namespace detail
+}  // namespace nlohmann
+
+// #include <nlohmann/detail/iterators/internal_iterator.hpp>
+
+
+// #include <nlohmann/detail/iterators/primitive_iterator.hpp>
+
+
+namespace nlohmann
+{
+namespace detail
+{
+/*!
+@brief an iterator value
+
+@note This structure could easily be a union, but MSVC currently does not allow
+unions members with complex constructors, see https://github.com/nlohmann/json/pull/105.
+*/
+template<typename BasicJsonType> struct internal_iterator
+{
+    /// iterator for JSON objects
+    typename BasicJsonType::object_t::iterator object_iterator {};
+    /// iterator for JSON arrays
+    typename BasicJsonType::array_t::iterator array_iterator {};
+    /// generic iterator for all other types
+    primitive_iterator_t primitive_iterator {};
+};
+}  // namespace detail
+}  // namespace nlohmann
+
+// #include <nlohmann/detail/iterators/iter_impl.hpp>
+
+
+#include <ciso646> // not
+#include <iterator> // iterator, random_access_iterator_tag, bidirectional_iterator_tag, advance, next
+#include <type_traits> // conditional, is_const, remove_const
+
+// #include <nlohmann/detail/exceptions.hpp>
+
+// #include <nlohmann/detail/iterators/internal_iterator.hpp>
+
+// #include <nlohmann/detail/iterators/primitive_iterator.hpp>
+
+// #include <nlohmann/detail/macro_scope.hpp>
+
+// #include <nlohmann/detail/meta/cpp_future.hpp>
+
+// #include <nlohmann/detail/value_t.hpp>
+
+
+namespace nlohmann
+{
+namespace detail
+{
+// forward declare, to be able to friend it later on
+template<typename IteratorType> class iteration_proxy;
+template<typename IteratorType> class iteration_proxy_value;
+
+/*!
+@brief a template for a bidirectional iterator for the @ref basic_json class
+This class implements a both iterators (iterator and const_iterator) for the
+@ref basic_json class.
+@note An iterator is called *initialized* when a pointer to a JSON value has
+      been set (e.g., by a constructor or a copy assignment). If the iterator is
+      default-constructed, it is *uninitialized* and most methods are undefined.
+      **The library uses assertions to detect calls on uninitialized iterators.**
+@requirement The class satisfies the following concept requirements:
+-
+[BidirectionalIterator](https://en.cppreference.com/w/cpp/named_req/BidirectionalIterator):
+  The iterator that can be moved can be moved in both directions (i.e.
+  incremented and decremented).
+@since version 1.0.0, simplified in version 2.0.9, change to bidirectional
+       iterators in version 3.0.0 (see https://github.com/nlohmann/json/issues/593)
+*/
+template<typename BasicJsonType>
+class iter_impl
+{
+    /// allow basic_json to access private members
+    friend iter_impl<typename std::conditional<std::is_const<BasicJsonType>::value, typename std::remove_const<BasicJsonType>::type, const BasicJsonType>::type>;
+    friend BasicJsonType;
+    friend iteration_proxy<iter_impl>;
+    friend iteration_proxy_value<iter_impl>;
+
+    using object_t = typename BasicJsonType::object_t;
+    using array_t = typename BasicJsonType::array_t;
+    // make sure BasicJsonType is basic_json or const basic_json
+    static_assert(is_basic_json<typename std::remove_const<BasicJsonType>::type>::value,
+                  "iter_impl only accepts (const) basic_json");
+
+  public:
+
+    /// The std::iterator class template (used as a base class to provide typedefs) is deprecated in C++17.
+    /// The C++ Standard has never required user-defined iterators to derive from std::iterator.
+    /// A user-defined iterator should provide publicly accessible typedefs named
+    /// iterator_category, value_type, difference_type, pointer, and reference.
+    /// Note that value_type is required to be non-const, even for constant iterators.
+    using iterator_category = std::bidirectional_iterator_tag;
+
+    /// the type of the values when the iterator is dereferenced
+    using value_type = typename BasicJsonType::value_type;
+    /// a type to represent differences between iterators
+    using difference_type = typename BasicJsonType::difference_type;
+    /// defines a pointer to the type iterated over (value_type)
+    using pointer = typename std::conditional<std::is_const<BasicJsonType>::value,
+          typename BasicJsonType::const_pointer,
+          typename BasicJsonType::pointer>::type;
+    /// defines a reference to the type iterated over (value_type)
+    using reference =
+        typename std::conditional<std::is_const<BasicJsonType>::value,
+        typename BasicJsonType::const_reference,
+        typename BasicJsonType::reference>::type;
+
+    /// default constructor
+    iter_impl() = default;
+
+    /*!
+    @brief constructor for a given JSON instance
+    @param[in] object  pointer to a JSON object for this iterator
+    @pre object != nullptr
+    @post The iterator is initialized; i.e. `m_object != nullptr`.
+    */
+    explicit iter_impl(pointer object) noexcept : m_object(object)
+    {
+        assert(m_object != nullptr);
+
+        switch (m_object->m_type)
+        {
+            case value_t::object:
+            {
+                m_it.object_iterator = typename object_t::iterator();
+                break;
+            }
+
+            case value_t::array:
+            {
+                m_it.array_iterator = typename array_t::iterator();
+                break;
+            }
+
+            default:
+            {
+                m_it.primitive_iterator = primitive_iterator_t();
+                break;
+            }
+        }
+    }
+
+    /*!
+    @note The conventional copy constructor and copy assignment are implicitly
+          defined. Combined with the following converting constructor and
+          assignment, they support: (1) copy from iterator to iterator, (2)
+          copy from const iterator to const iterator, and (3) conversion from
+          iterator to const iterator. However conversion from const iterator
+          to iterator is not defined.
+    */
+
+    /*!
+    @brief converting constructor
+    @param[in] other  non-const iterator to copy from
+    @note It is not checked whether @a other is initialized.
+    */
+    iter_impl(const iter_impl<typename std::remove_const<BasicJsonType>::type>& other) noexcept
+        : m_object(other.m_object), m_it(other.m_it) {}
+
+    /*!
+    @brief converting assignment
+    @param[in,out] other  non-const iterator to copy from
+    @return const/non-const iterator
+    @note It is not checked whether @a other is initialized.
+    */
+    iter_impl& operator=(const iter_impl<typename std::remove_const<BasicJsonType>::type>& other) noexcept
+    {
+        m_object = other.m_object;
+        m_it = other.m_it;
+        return *this;
+    }
+
+  private:
+    /*!
+    @brief set the iterator to the first value
+    @pre The iterator is initialized; i.e. `m_object != nullptr`.
+    */
+    void set_begin() noexcept
+    {
+        assert(m_object != nullptr);
+
+        switch (m_object->m_type)
+        {
+            case value_t::object:
+            {
+                m_it.object_iterator = m_object->m_value.object->begin();
+                break;
+            }
+
+            case value_t::array:
+            {
+                m_it.array_iterator = m_object->m_value.array->begin();
+                break;
+            }
+
+            case value_t::null:
+            {
+                // set to end so begin()==end() is true: null is empty
+                m_it.primitive_iterator.set_end();
+                break;
+            }
+
+            default:
+            {
+                m_it.primitive_iterator.set_begin();
+                break;
+            }
+        }
+    }
+
+    /*!
+    @brief set the iterator past the last value
+    @pre The iterator is initialized; i.e. `m_object != nullptr`.
+    */
+    void set_end() noexcept
+    {
+        assert(m_object != nullptr);
+
+        switch (m_object->m_type)
+        {
+            case value_t::object:
+            {
+                m_it.object_iterator = m_object->m_value.object->end();
+                break;
+            }
+
+            case value_t::array:
+            {
+                m_it.array_iterator = m_object->m_value.array->end();
+                break;
+            }
+
+            default:
+            {
+                m_it.primitive_iterator.set_end();
+                break;
+            }
+        }
+    }
+
+  public:
+    /*!
+    @brief return a reference to the value pointed to by the iterator
+    @pre The iterator is initialized; i.e. `m_object != nullptr`.
+    */
+    reference operator*() const
+    {
+        assert(m_object != nullptr);
+
+        switch (m_object->m_type)
+        {
+            case value_t::object:
+            {
+                assert(m_it.object_iterator != m_object->m_value.object->end());
+                return m_it.object_iterator->second;
+            }
+
+            case value_t::array:
+            {
+                assert(m_it.array_iterator != m_object->m_value.array->end());
+                return *m_it.array_iterator;
+            }
+
+            case value_t::null:
+                JSON_THROW(invalid_iterator::create(214, "cannot get value"));
+
+            default:
+            {
+                if (JSON_LIKELY(m_it.primitive_iterator.is_begin()))
+                {
+                    return *m_object;
+                }
+
+                JSON_THROW(invalid_iterator::create(214, "cannot get value"));
+            }
+        }
+    }
+
+    /*!
+    @brief dereference the iterator
+    @pre The iterator is initialized; i.e. `m_object != nullptr`.
+    */
+    pointer operator->() const
+    {
+        assert(m_object != nullptr);
+
+        switch (m_object->m_type)
+        {
+            case value_t::object:
+            {
+                assert(m_it.object_iterator != m_object->m_value.object->end());
+                return &(m_it.object_iterator->second);
+            }
+
+            case value_t::array:
+            {
+                assert(m_it.array_iterator != m_object->m_value.array->end());
+                return &*m_it.array_iterator;
+            }
+
+            default:
+            {
+                if (JSON_LIKELY(m_it.primitive_iterator.is_begin()))
+                {
+                    return m_object;
+                }
+
+                JSON_THROW(invalid_iterator::create(214, "cannot get value"));
+            }
+        }
+    }
+
+    /*!
+    @brief post-increment (it++)
+    @pre The iterator is initialized; i.e. `m_object != nullptr`.
+    */
+    iter_impl const operator++(int)
+    {
+        auto result = *this;
+        ++(*this);
+        return result;
+    }
+
+    /*!
+    @brief pre-increment (++it)
+    @pre The iterator is initialized; i.e. `m_object != nullptr`.
+    */
+    iter_impl& operator++()
+    {
+        assert(m_object != nullptr);
+
+        switch (m_object->m_type)
+        {
+            case value_t::object:
+            {
+                std::advance(m_it.object_iterator, 1);
+                break;
+            }
+
+            case value_t::array:
+            {
+                std::advance(m_it.array_iterator, 1);
+                break;
+            }
+
+            default:
+            {
+                ++m_it.primitive_iterator;
+                break;
+            }
+        }
+
+        return *this;
+    }
+
+    /*!
+    @brief post-decrement (it--)
+    @pre The iterator is initialized; i.e. `m_object != nullptr`.
+    */
+    iter_impl const operator--(int)
+    {
+        auto result = *this;
+        --(*this);
+        return result;
+    }
+
+    /*!
+    @brief pre-decrement (--it)
+    @pre The iterator is initialized; i.e. `m_object != nullptr`.
+    */
+    iter_impl& operator--()
+    {
+        assert(m_object != nullptr);
+
+        switch (m_object->m_type)
+        {
+            case value_t::object:
+            {
+                std::advance(m_it.object_iterator, -1);
+                break;
+            }
+
+            case value_t::array:
+            {
+                std::advance(m_it.array_iterator, -1);
+                break;
+            }
+
+            default:
+            {
+                --m_it.primitive_iterator;
+                break;
+            }
+        }
+
+        return *this;
+    }
+
+    /*!
+    @brief  comparison: equal
+    @pre The iterator is initialized; i.e. `m_object != nullptr`.
+    */
+    bool operator==(const iter_impl& other) const
+    {
+        // if objects are not the same, the comparison is undefined
+        if (JSON_UNLIKELY(m_object != other.m_object))
+        {
+            JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers"));
+        }
+
+        assert(m_object != nullptr);
+
+        switch (m_object->m_type)
+        {
+            case value_t::object:
+                return (m_it.object_iterator == other.m_it.object_iterator);
+
+            case value_t::array:
+                return (m_it.array_iterator == other.m_it.array_iterator);
+
+            default:
+                return (m_it.primitive_iterator == other.m_it.primitive_iterator);
+        }
+    }
+
+    /*!
+    @brief  comparison: not equal
+    @pre The iterator is initialized; i.e. `m_object != nullptr`.
+    */
+    bool operator!=(const iter_impl& other) const
+    {
+        return not operator==(other);
+    }
+
+    /*!
+    @brief  comparison: smaller
+    @pre The iterator is initialized; i.e. `m_object != nullptr`.
+    */
+    bool operator<(const iter_impl& other) const
+    {
+        // if objects are not the same, the comparison is undefined
+        if (JSON_UNLIKELY(m_object != other.m_object))
+        {
+            JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers"));
+        }
+
+        assert(m_object != nullptr);
+
+        switch (m_object->m_type)
+        {
+            case value_t::object:
+                JSON_THROW(invalid_iterator::create(213, "cannot compare order of object iterators"));
+
+            case value_t::array:
+                return (m_it.array_iterator < other.m_it.array_iterator);
+
+            default:
+                return (m_it.primitive_iterator < other.m_it.primitive_iterator);
+        }
+    }
+
+    /*!
+    @brief  comparison: less than or equal
+    @pre The iterator is initialized; i.e. `m_object != nullptr`.
+    */
+    bool operator<=(const iter_impl& other) const
+    {
+        return not other.operator < (*this);
+    }
+
+    /*!
+    @brief  comparison: greater than
+    @pre The iterator is initialized; i.e. `m_object != nullptr`.
+    */
+    bool operator>(const iter_impl& other) const
+    {
+        return not operator<=(other);
+    }
+
+    /*!
+    @brief  comparison: greater than or equal
+    @pre The iterator is initialized; i.e. `m_object != nullptr`.
+    */
+    bool operator>=(const iter_impl& other) const
+    {
+        return not operator<(other);
+    }
+
+    /*!
+    @brief  add to iterator
+    @pre The iterator is initialized; i.e. `m_object != nullptr`.
+    */
+    iter_impl& operator+=(difference_type i)
+    {
+        assert(m_object != nullptr);
+
+        switch (m_object->m_type)
+        {
+            case value_t::object:
+                JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators"));
+
+            case value_t::array:
+            {
+                std::advance(m_it.array_iterator, i);
+                break;
+            }
+
+            default:
+            {
+                m_it.primitive_iterator += i;
+                break;
+            }
+        }
+
+        return *this;
+    }
+
+    /*!
+    @brief  subtract from iterator
+    @pre The iterator is initialized; i.e. `m_object != nullptr`.
+    */
+    iter_impl& operator-=(difference_type i)
+    {
+        return operator+=(-i);
+    }
+
+    /*!
+    @brief  add to iterator
+    @pre The iterator is initialized; i.e. `m_object != nullptr`.
+    */
+    iter_impl operator+(difference_type i) const
+    {
+        auto result = *this;
+        result += i;
+        return result;
+    }
+
+    /*!
+    @brief  addition of distance and iterator
+    @pre The iterator is initialized; i.e. `m_object != nullptr`.
+    */
+    friend iter_impl operator+(difference_type i, const iter_impl& it)
+    {
+        auto result = it;
+        result += i;
+        return result;
+    }
+
+    /*!
+    @brief  subtract from iterator
+    @pre The iterator is initialized; i.e. `m_object != nullptr`.
+    */
+    iter_impl operator-(difference_type i) const
+    {
+        auto result = *this;
+        result -= i;
+        return result;
+    }
+
+    /*!
+    @brief  return difference
+    @pre The iterator is initialized; i.e. `m_object != nullptr`.
+    */
+    difference_type operator-(const iter_impl& other) const
+    {
+        assert(m_object != nullptr);
+
+        switch (m_object->m_type)
+        {
+            case value_t::object:
+                JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators"));
+
+            case value_t::array:
+                return m_it.array_iterator - other.m_it.array_iterator;
+
+            default:
+                return m_it.primitive_iterator - other.m_it.primitive_iterator;
+        }
+    }
+
+    /*!
+    @brief  access to successor
+    @pre The iterator is initialized; i.e. `m_object != nullptr`.
+    */
+    reference operator[](difference_type n) const
+    {
+        assert(m_object != nullptr);
+
+        switch (m_object->m_type)
+        {
+            case value_t::object:
+                JSON_THROW(invalid_iterator::create(208, "cannot use operator[] for object iterators"));
+
+            case value_t::array:
+                return *std::next(m_it.array_iterator, n);
+
+            case value_t::null:
+                JSON_THROW(invalid_iterator::create(214, "cannot get value"));
+
+            default:
+            {
+                if (JSON_LIKELY(m_it.primitive_iterator.get_value() == -n))
+                {
+                    return *m_object;
+                }
+
+                JSON_THROW(invalid_iterator::create(214, "cannot get value"));
+            }
+        }
+    }
+
+    /*!
+    @brief  return the key of an object iterator
+    @pre The iterator is initialized; i.e. `m_object != nullptr`.
+    */
+    const typename object_t::key_type& key() const
+    {
+        assert(m_object != nullptr);
+
+        if (JSON_LIKELY(m_object->is_object()))
+        {
+            return m_it.object_iterator->first;
+        }
+
+        JSON_THROW(invalid_iterator::create(207, "cannot use key() for non-object iterators"));
+    }
+
+    /*!
+    @brief  return the value of an iterator
+    @pre The iterator is initialized; i.e. `m_object != nullptr`.
+    */
+    reference value() const
+    {
+        return operator*();
+    }
+
+  private:
+    /// associated JSON instance
+    pointer m_object = nullptr;
+    /// the actual iterator of the associated instance
+    internal_iterator<typename std::remove_const<BasicJsonType>::type> m_it;
+};
+}  // namespace detail
+} // namespace nlohmann
+// #include <nlohmann/detail/iterators/iteration_proxy.hpp>
+
+// #include <nlohmann/detail/iterators/json_reverse_iterator.hpp>
+
+
+#include <cstddef> // ptrdiff_t
+#include <iterator> // reverse_iterator
+#include <utility> // declval
+
+namespace nlohmann
+{
+namespace detail
+{
+//////////////////////
+// reverse_iterator //
+//////////////////////
+
+/*!
+@brief a template for a reverse iterator class
+
+@tparam Base the base iterator type to reverse. Valid types are @ref
+iterator (to create @ref reverse_iterator) and @ref const_iterator (to
+create @ref const_reverse_iterator).
+
+@requirement The class satisfies the following concept requirements:
+-
+[BidirectionalIterator](https://en.cppreference.com/w/cpp/named_req/BidirectionalIterator):
+  The iterator that can be moved can be moved in both directions (i.e.
+  incremented and decremented).
+- [OutputIterator](https://en.cppreference.com/w/cpp/named_req/OutputIterator):
+  It is possible to write to the pointed-to element (only if @a Base is
+  @ref iterator).
+
+@since version 1.0.0
+*/
+template<typename Base>
+class json_reverse_iterator : public std::reverse_iterator<Base>
+{
+  public:
+    using difference_type = std::ptrdiff_t;
+    /// shortcut to the reverse iterator adapter
+    using base_iterator = std::reverse_iterator<Base>;
+    /// the reference type for the pointed-to element
+    using reference = typename Base::reference;
+
+    /// create reverse iterator from iterator
+    explicit json_reverse_iterator(const typename base_iterator::iterator_type& it) noexcept
+        : base_iterator(it) {}
+
+    /// create reverse iterator from base class
+    explicit json_reverse_iterator(const base_iterator& it) noexcept : base_iterator(it) {}
+
+    /// post-increment (it++)
+    json_reverse_iterator const operator++(int)
+    {
+        return static_cast<json_reverse_iterator>(base_iterator::operator++(1));
+    }
+
+    /// pre-increment (++it)
+    json_reverse_iterator& operator++()
+    {
+        return static_cast<json_reverse_iterator&>(base_iterator::operator++());
+    }
+
+    /// post-decrement (it--)
+    json_reverse_iterator const operator--(int)
+    {
+        return static_cast<json_reverse_iterator>(base_iterator::operator--(1));
+    }
+
+    /// pre-decrement (--it)
+    json_reverse_iterator& operator--()
+    {
+        return static_cast<json_reverse_iterator&>(base_iterator::operator--());
+    }
+
+    /// add to iterator
+    json_reverse_iterator& operator+=(difference_type i)
+    {
+        return static_cast<json_reverse_iterator&>(base_iterator::operator+=(i));
+    }
+
+    /// add to iterator
+    json_reverse_iterator operator+(difference_type i) const
+    {
+        return static_cast<json_reverse_iterator>(base_iterator::operator+(i));
+    }
+
+    /// subtract from iterator
+    json_reverse_iterator operator-(difference_type i) const
+    {
+        return static_cast<json_reverse_iterator>(base_iterator::operator-(i));
+    }
+
+    /// return difference
+    difference_type operator-(const json_reverse_iterator& other) const
+    {
+        return base_iterator(*this) - base_iterator(other);
+    }
+
+    /// access to successor
+    reference operator[](difference_type n) const
+    {
+        return *(this->operator+(n));
+    }
+
+    /// return the key of an object iterator
+    auto key() const -> decltype(std::declval<Base>().key())
+    {
+        auto it = --this->base();
+        return it.key();
+    }
+
+    /// return the value of an iterator
+    reference value() const
+    {
+        auto it = --this->base();
+        return it.operator * ();
+    }
+};
+}  // namespace detail
+}  // namespace nlohmann
+
+// #include <nlohmann/detail/output/output_adapters.hpp>
+
+
+#include <algorithm> // copy
+#include <cstddef> // size_t
+#include <ios> // streamsize
+#include <iterator> // back_inserter
+#include <memory> // shared_ptr, make_shared
+#include <ostream> // basic_ostream
+#include <string> // basic_string
+#include <vector> // vector
+
+namespace nlohmann
+{
+namespace detail
+{
+/// abstract output adapter interface
+template<typename CharType> struct output_adapter_protocol
+{
+    virtual void write_character(CharType c) = 0;
+    virtual void write_characters(const CharType* s, std::size_t length) = 0;
+    virtual ~output_adapter_protocol() = default;
+};
+
+/// a type to simplify interfaces
+template<typename CharType>
+using output_adapter_t = std::shared_ptr<output_adapter_protocol<CharType>>;
+
+/// output adapter for byte vectors
+template<typename CharType>
+class output_vector_adapter : public output_adapter_protocol<CharType>
+{
+  public:
+    explicit output_vector_adapter(std::vector<CharType>& vec) noexcept
+        : v(vec)
+    {}
+
+    void write_character(CharType c) override
+    {
+        v.push_back(c);
+    }
+
+    void write_characters(const CharType* s, std::size_t length) override
+    {
+        std::copy(s, s + length, std::back_inserter(v));
+    }
+
+  private:
+    std::vector<CharType>& v;
+};
+
+/// output adapter for output streams
+template<typename CharType>
+class output_stream_adapter : public output_adapter_protocol<CharType>
+{
+  public:
+    explicit output_stream_adapter(std::basic_ostream<CharType>& s) noexcept
+        : stream(s)
+    {}
+
+    void write_character(CharType c) override
+    {
+        stream.put(c);
+    }
+
+    void write_characters(const CharType* s, std::size_t length) override
+    {
+        stream.write(s, static_cast<std::streamsize>(length));
+    }
+
+  private:
+    std::basic_ostream<CharType>& stream;
+};
+
+/// output adapter for basic_string
+template<typename CharType, typename StringType = std::basic_string<CharType>>
+class output_string_adapter : public output_adapter_protocol<CharType>
+{
+  public:
+    explicit output_string_adapter(StringType& s) noexcept
+        : str(s)
+    {}
+
+    void write_character(CharType c) override
+    {
+        str.push_back(c);
+    }
+
+    void write_characters(const CharType* s, std::size_t length) override
+    {
+        str.append(s, length);
+    }
+
+  private:
+    StringType& str;
+};
+
+template<typename CharType, typename StringType = std::basic_string<CharType>>
+class output_adapter
+{
+  public:
+    output_adapter(std::vector<CharType>& vec)
+        : oa(std::make_shared<output_vector_adapter<CharType>>(vec)) {}
+
+    output_adapter(std::basic_ostream<CharType>& s)
+        : oa(std::make_shared<output_stream_adapter<CharType>>(s)) {}
+
+    output_adapter(StringType& s)
+        : oa(std::make_shared<output_string_adapter<CharType, StringType>>(s)) {}
+
+    operator output_adapter_t<CharType>()
+    {
+        return oa;
+    }
+
+  private:
+    output_adapter_t<CharType> oa = nullptr;
+};
+}  // namespace detail
+}  // namespace nlohmann
+
+// #include <nlohmann/detail/input/binary_reader.hpp>
+
+
+#include <algorithm> // generate_n
+#include <array> // array
+#include <cassert> // assert
+#include <cmath> // ldexp
+#include <cstddef> // size_t
+#include <cstdint> // uint8_t, uint16_t, uint32_t, uint64_t
+#include <cstdio> // snprintf
+#include <cstring> // memcpy
+#include <iterator> // back_inserter
+#include <limits> // numeric_limits
+#include <string> // char_traits, string
+#include <utility> // make_pair, move
+
+// #include <nlohmann/detail/input/input_adapters.hpp>
+
+// #include <nlohmann/detail/input/json_sax.hpp>
+
+// #include <nlohmann/detail/exceptions.hpp>
+
+// #include <nlohmann/detail/macro_scope.hpp>
+
+// #include <nlohmann/detail/meta/is_sax.hpp>
+
+// #include <nlohmann/detail/value_t.hpp>
+
+
+namespace nlohmann
+{
+namespace detail
+{
+///////////////////
+// binary reader //
+///////////////////
+
+/*!
+@brief deserialization of CBOR, MessagePack, and UBJSON values
+*/
+template<typename BasicJsonType, typename SAX = json_sax_dom_parser<BasicJsonType>>
+class binary_reader
+{
+    using number_integer_t = typename BasicJsonType::number_integer_t;
+    using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
+    using number_float_t = typename BasicJsonType::number_float_t;
+    using string_t = typename BasicJsonType::string_t;
+    using json_sax_t = SAX;
+
+  public:
+    /*!
+    @brief create a binary reader
+
+    @param[in] adapter  input adapter to read from
+    */
+    explicit binary_reader(input_adapter_t adapter) : ia(std::move(adapter))
+    {
+        (void)detail::is_sax_static_asserts<SAX, BasicJsonType> {};
+        assert(ia);
+    }
+
+    /*!
+    @param[in] format  the binary format to parse
+    @param[in] sax_    a SAX event processor
+    @param[in] strict  whether to expect the input to be consumed completed
+
+    @return
+    */
+    bool sax_parse(const input_format_t format,
+                   json_sax_t* sax_,
+                   const bool strict = true)
+    {
+        sax = sax_;
+        bool result = false;
+
+        switch (format)
+        {
+            case input_format_t::bson:
+                result = parse_bson_internal();
+                break;
+
+            case input_format_t::cbor:
+                result = parse_cbor_internal();
+                break;
+
+            case input_format_t::msgpack:
+                result = parse_msgpack_internal();
+                break;
+
+            case input_format_t::ubjson:
+                result = parse_ubjson_internal();
+                break;
+
+            // LCOV_EXCL_START
+            default:
+                assert(false);
+                // LCOV_EXCL_STOP
+        }
+
+        // strict mode: next byte must be EOF
+        if (result and strict)
+        {
+            if (format == input_format_t::ubjson)
+            {
+                get_ignore_noop();
+            }
+            else
+            {
+                get();
+            }
+
+            if (JSON_UNLIKELY(current != std::char_traits<char>::eof()))
+            {
+                return sax->parse_error(chars_read, get_token_string(),
+                                        parse_error::create(110, chars_read, exception_message(format, "expected end of input; last byte: 0x" + get_token_string(), "value")));
+            }
+        }
+
+        return result;
+    }
+
+    /*!
+    @brief determine system byte order
+
+    @return true if and only if system's byte order is little endian
+
+    @note from http://stackoverflow.com/a/1001328/266378
+    */
+    static constexpr bool little_endianess(int num = 1) noexcept
+    {
+        return (*reinterpret_cast<char*>(&num) == 1);
+    }
+
+  private:
+    //////////
+    // BSON //
+    //////////
+
+    /*!
+    @brief Reads in a BSON-object and passes it to the SAX-parser.
+    @return whether a valid BSON-value was passed to the SAX parser
+    */
+    bool parse_bson_internal()
+    {
+        std::int32_t document_size;
+        get_number<std::int32_t, true>(input_format_t::bson, document_size);
+
+        if (JSON_UNLIKELY(not sax->start_object(std::size_t(-1))))
+        {
+            return false;
+        }
+
+        if (JSON_UNLIKELY(not parse_bson_element_list(/*is_array*/false)))
+        {
+            return false;
+        }
+
+        return sax->end_object();
+    }
+
+    /*!
+    @brief Parses a C-style string from the BSON input.
+    @param[in, out] result  A reference to the string variable where the read
+                            string is to be stored.
+    @return `true` if the \x00-byte indicating the end of the string was
+             encountered before the EOF; false` indicates an unexpected EOF.
+    */
+    bool get_bson_cstr(string_t& result)
+    {
+        auto out = std::back_inserter(result);
+        while (true)
+        {
+            get();
+            if (JSON_UNLIKELY(not unexpect_eof(input_format_t::bson, "cstring")))
+            {
+                return false;
+            }
+            if (current == 0x00)
+            {
+                return true;
+            }
+            *out++ = static_cast<char>(current);
+        }
+
+        return true;
+    }
+
+    /*!
+    @brief Parses a zero-terminated string of length @a len from the BSON
+           input.
+    @param[in] len  The length (including the zero-byte at the end) of the
+                    string to be read.
+    @param[in, out] result  A reference to the string variable where the read
+                            string is to be stored.
+    @tparam NumberType The type of the length @a len
+    @pre len >= 1
+    @return `true` if the string was successfully parsed
+    */
+    template<typename NumberType>
+    bool get_bson_string(const NumberType len, string_t& result)
+    {
+        if (JSON_UNLIKELY(len < 1))
+        {
+            auto last_token = get_token_string();
+            return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::bson, "string length must be at least 1, is " + std::to_string(len), "string")));
+        }
+
+        return get_string(input_format_t::bson, len - static_cast<NumberType>(1), result) and get() != std::char_traits<char>::eof();
+    }
+
+    /*!
+    @brief Read a BSON document element of the given @a element_type.
+    @param[in] element_type The BSON element type, c.f. http://bsonspec.org/spec.html
+    @param[in] element_type_parse_position The position in the input stream,
+               where the `element_type` was read.
+    @warning Not all BSON element types are supported yet. An unsupported
+             @a element_type will give rise to a parse_error.114:
+             Unsupported BSON record type 0x...
+    @return whether a valid BSON-object/array was passed to the SAX parser
+    */
+    bool parse_bson_element_internal(const int element_type,
+                                     const std::size_t element_type_parse_position)
+    {
+        switch (element_type)
+        {
+            case 0x01: // double
+            {
+                double number;
+                return get_number<double, true>(input_format_t::bson, number) and sax->number_float(static_cast<number_float_t>(number), "");
+            }
+
+            case 0x02: // string
+            {
+                std::int32_t len;
+                string_t value;
+                return get_number<std::int32_t, true>(input_format_t::bson, len) and get_bson_string(len, value) and sax->string(value);
+            }
+
+            case 0x03: // object
+            {
+                return parse_bson_internal();
+            }
+
+            case 0x04: // array
+            {
+                return parse_bson_array();
+            }
+
+            case 0x08: // boolean
+            {
+                return sax->boolean(get() != 0);
+            }
+
+            case 0x0A: // null
+            {
+                return sax->null();
+            }
+
+            case 0x10: // int32
+            {
+                std::int32_t value;
+                return get_number<std::int32_t, true>(input_format_t::bson, value) and sax->number_integer(value);
+            }
+
+            case 0x12: // int64
+            {
+                std::int64_t value;
+                return get_number<std::int64_t, true>(input_format_t::bson, value) and sax->number_integer(value);
+            }
+
+            default: // anything else not supported (yet)
+            {
+                char cr[3];
+                (std::snprintf)(cr, sizeof(cr), "%.2hhX", static_cast<unsigned char>(element_type));
+                return sax->parse_error(element_type_parse_position, std::string(cr), parse_error::create(114, element_type_parse_position, "Unsupported BSON record type 0x" + std::string(cr)));
+            }
+        }
+    }
+
+    /*!
+    @brief Read a BSON element list (as specified in the BSON-spec)
+
+    The same binary layout is used for objects and arrays, hence it must be
+    indicated with the argument @a is_array which one is expected
+    (true --> array, false --> object).
+
+    @param[in] is_array Determines if the element list being read is to be
+                        treated as an object (@a is_array == false), or as an
+                        array (@a is_array == true).
+    @return whether a valid BSON-object/array was passed to the SAX parser
+    */
+    bool parse_bson_element_list(const bool is_array)
+    {
+        string_t key;
+        while (int element_type = get())
+        {
+            if (JSON_UNLIKELY(not unexpect_eof(input_format_t::bson, "element list")))
+            {
+                return false;
+            }
+
+            const std::size_t element_type_parse_position = chars_read;
+            if (JSON_UNLIKELY(not get_bson_cstr(key)))
+            {
+                return false;
+            }
+
+            if (not is_array)
+            {
+                if (not sax->key(key))
+                {
+                    return false;
+                }
+            }
+
+            if (JSON_UNLIKELY(not parse_bson_element_internal(element_type, element_type_parse_position)))
+            {
+                return false;
+            }
+
+            // get_bson_cstr only appends
+            key.clear();
+        }
+
+        return true;
+    }
+
+    /*!
+    @brief Reads an array from the BSON input and passes it to the SAX-parser.
+    @return whether a valid BSON-array was passed to the SAX parser
+    */
+    bool parse_bson_array()
+    {
+        std::int32_t document_size;
+        get_number<std::int32_t, true>(input_format_t::bson, document_size);
+
+        if (JSON_UNLIKELY(not sax->start_array(std::size_t(-1))))
+        {
+            return false;
+        }
+
+        if (JSON_UNLIKELY(not parse_bson_element_list(/*is_array*/true)))
+        {
+            return false;
+        }
+
+        return sax->end_array();
+    }
+
+    //////////
+    // CBOR //
+    //////////
+
+    /*!
+    @param[in] get_char  whether a new character should be retrieved from the
+                         input (true, default) or whether the last read
+                         character should be considered instead
+
+    @return whether a valid CBOR value was passed to the SAX parser
+    */
+    bool parse_cbor_internal(const bool get_char = true)
+    {
+        switch (get_char ? get() : current)
+        {
+            // EOF
+            case std::char_traits<char>::eof():
+                return unexpect_eof(input_format_t::cbor, "value");
+
+            // Integer 0x00..0x17 (0..23)
+            case 0x00:
+            case 0x01:
+            case 0x02:
+            case 0x03:
+            case 0x04:
+            case 0x05:
+            case 0x06:
+            case 0x07:
+            case 0x08:
+            case 0x09:
+            case 0x0A:
+            case 0x0B:
+            case 0x0C:
+            case 0x0D:
+            case 0x0E:
+            case 0x0F:
+            case 0x10:
+            case 0x11:
+            case 0x12:
+            case 0x13:
+            case 0x14:
+            case 0x15:
+            case 0x16:
+            case 0x17:
+                return sax->number_unsigned(static_cast<number_unsigned_t>(current));
+
+            case 0x18: // Unsigned integer (one-byte uint8_t follows)
+            {
+                uint8_t number;
+                return get_number(input_format_t::cbor, number) and sax->number_unsigned(number);
+            }
+
+            case 0x19: // Unsigned integer (two-byte uint16_t follows)
+            {
+                uint16_t number;
+                return get_number(input_format_t::cbor, number) and sax->number_unsigned(number);
+            }
+
+            case 0x1A: // Unsigned integer (four-byte uint32_t follows)
+            {
+                uint32_t number;
+                return get_number(input_format_t::cbor, number) and sax->number_unsigned(number);
+            }
+
+            case 0x1B: // Unsigned integer (eight-byte uint64_t follows)
+            {
+                uint64_t number;
+                return get_number(input_format_t::cbor, number) and sax->number_unsigned(number);
+            }
+
+            // Negative integer -1-0x00..-1-0x17 (-1..-24)
+            case 0x20:
+            case 0x21:
+            case 0x22:
+            case 0x23:
+            case 0x24:
+            case 0x25:
+            case 0x26:
+            case 0x27:
+            case 0x28:
+            case 0x29:
+            case 0x2A:
+            case 0x2B:
+            case 0x2C:
+            case 0x2D:
+            case 0x2E:
+            case 0x2F:
+            case 0x30:
+            case 0x31:
+            case 0x32:
+            case 0x33:
+            case 0x34:
+            case 0x35:
+            case 0x36:
+            case 0x37:
+                return sax->number_integer(static_cast<int8_t>(0x20 - 1 - current));
+
+            case 0x38: // Negative integer (one-byte uint8_t follows)
+            {
+                uint8_t number;
+                return get_number(input_format_t::cbor, number) and sax->number_integer(static_cast<number_integer_t>(-1) - number);
+            }
+
+            case 0x39: // Negative integer -1-n (two-byte uint16_t follows)
+            {
+                uint16_t number;
+                return get_number(input_format_t::cbor, number) and sax->number_integer(static_cast<number_integer_t>(-1) - number);
+            }
+
+            case 0x3A: // Negative integer -1-n (four-byte uint32_t follows)
+            {
+                uint32_t number;
+                return get_number(input_format_t::cbor, number) and sax->number_integer(static_cast<number_integer_t>(-1) - number);
+            }
+
+            case 0x3B: // Negative integer -1-n (eight-byte uint64_t follows)
+            {
+                uint64_t number;
+                return get_number(input_format_t::cbor, number) and sax->number_integer(static_cast<number_integer_t>(-1)
+                        - static_cast<number_integer_t>(number));
+            }
+
+            // UTF-8 string (0x00..0x17 bytes follow)
+            case 0x60:
+            case 0x61:
+            case 0x62:
+            case 0x63:
+            case 0x64:
+            case 0x65:
+            case 0x66:
+            case 0x67:
+            case 0x68:
+            case 0x69:
+            case 0x6A:
+            case 0x6B:
+            case 0x6C:
+            case 0x6D:
+            case 0x6E:
+            case 0x6F:
+            case 0x70:
+            case 0x71:
+            case 0x72:
+            case 0x73:
+            case 0x74:
+            case 0x75:
+            case 0x76:
+            case 0x77:
+            case 0x78: // UTF-8 string (one-byte uint8_t for n follows)
+            case 0x79: // UTF-8 string (two-byte uint16_t for n follow)
+            case 0x7A: // UTF-8 string (four-byte uint32_t for n follow)
+            case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow)
+            case 0x7F: // UTF-8 string (indefinite length)
+            {
+                string_t s;
+                return get_cbor_string(s) and sax->string(s);
+            }
+
+            // array (0x00..0x17 data items follow)
+            case 0x80:
+            case 0x81:
+            case 0x82:
+            case 0x83:
+            case 0x84:
+            case 0x85:
+            case 0x86:
+            case 0x87:
+            case 0x88:
+            case 0x89:
+            case 0x8A:
+            case 0x8B:
+            case 0x8C:
+            case 0x8D:
+            case 0x8E:
+            case 0x8F:
+            case 0x90:
+            case 0x91:
+            case 0x92:
+            case 0x93:
+            case 0x94:
+            case 0x95:
+            case 0x96:
+            case 0x97:
+                return get_cbor_array(static_cast<std::size_t>(current & 0x1F));
+
+            case 0x98: // array (one-byte uint8_t for n follows)
+            {
+                uint8_t len;
+                return get_number(input_format_t::cbor, len) and get_cbor_array(static_cast<std::size_t>(len));
+            }
+
+            case 0x99: // array (two-byte uint16_t for n follow)
+            {
+                uint16_t len;
+                return get_number(input_format_t::cbor, len) and get_cbor_array(static_cast<std::size_t>(len));
+            }
+
+            case 0x9A: // array (four-byte uint32_t for n follow)
+            {
+                uint32_t len;
+                return get_number(input_format_t::cbor, len) and get_cbor_array(static_cast<std::size_t>(len));
+            }
+
+            case 0x9B: // array (eight-byte uint64_t for n follow)
+            {
+                uint64_t len;
+                return get_number(input_format_t::cbor, len) and get_cbor_array(static_cast<std::size_t>(len));
+            }
+
+            case 0x9F: // array (indefinite length)
+                return get_cbor_array(std::size_t(-1));
+
+            // map (0x00..0x17 pairs of data items follow)
+            case 0xA0:
+            case 0xA1:
+            case 0xA2:
+            case 0xA3:
+            case 0xA4:
+            case 0xA5:
+            case 0xA6:
+            case 0xA7:
+            case 0xA8:
+            case 0xA9:
+            case 0xAA:
+            case 0xAB:
+            case 0xAC:
+            case 0xAD:
+            case 0xAE:
+            case 0xAF:
+            case 0xB0:
+            case 0xB1:
+            case 0xB2:
+            case 0xB3:
+            case 0xB4:
+            case 0xB5:
+            case 0xB6:
+            case 0xB7:
+                return get_cbor_object(static_cast<std::size_t>(current & 0x1F));
+
+            case 0xB8: // map (one-byte uint8_t for n follows)
+            {
+                uint8_t len;
+                return get_number(input_format_t::cbor, len) and get_cbor_object(static_cast<std::size_t>(len));
+            }
+
+            case 0xB9: // map (two-byte uint16_t for n follow)
+            {
+                uint16_t len;
+                return get_number(input_format_t::cbor, len) and get_cbor_object(static_cast<std::size_t>(len));
+            }
+
+            case 0xBA: // map (four-byte uint32_t for n follow)
+            {
+                uint32_t len;
+                return get_number(input_format_t::cbor, len) and get_cbor_object(static_cast<std::size_t>(len));
+            }
+
+            case 0xBB: // map (eight-byte uint64_t for n follow)
+            {
+                uint64_t len;
+                return get_number(input_format_t::cbor, len) and get_cbor_object(static_cast<std::size_t>(len));
+            }
+
+            case 0xBF: // map (indefinite length)
+                return get_cbor_object(std::size_t(-1));
+
+            case 0xF4: // false
+                return sax->boolean(false);
+
+            case 0xF5: // true
+                return sax->boolean(true);
+
+            case 0xF6: // null
+                return sax->null();
+
+            case 0xF9: // Half-Precision Float (two-byte IEEE 754)
+            {
+                const int byte1_raw = get();
+                if (JSON_UNLIKELY(not unexpect_eof(input_format_t::cbor, "number")))
+                {
+                    return false;
+                }
+                const int byte2_raw = get();
+                if (JSON_UNLIKELY(not unexpect_eof(input_format_t::cbor, "number")))
+                {
+                    return false;
+                }
+
+                const auto byte1 = static_cast<unsigned char>(byte1_raw);
+                const auto byte2 = static_cast<unsigned char>(byte2_raw);
+
+                // code from RFC 7049, Appendix D, Figure 3:
+                // As half-precision floating-point numbers were only added
+                // to IEEE 754 in 2008, today's programming platforms often
+                // still only have limited support for them. It is very
+                // easy to include at least decoding support for them even
+                // without such support. An example of a small decoder for
+                // half-precision floating-point numbers in the C language
+                // is shown in Fig. 3.
+                const int half = (byte1 << 8) + byte2;
+                const double val = [&half]
+                {
+                    const int exp = (half >> 10) & 0x1F;
+                    const int mant = half & 0x3FF;
+                    assert(0 <= exp and exp <= 32);
+                    assert(0 <= mant and mant <= 1024);
+                    switch (exp)
+                    {
+                        case 0:
+                            return std::ldexp(mant, -24);
+                        case 31:
+                            return (mant == 0)
+                            ? std::numeric_limits<double>::infinity()
+                            : std::numeric_limits<double>::quiet_NaN();
+                        default:
+                            return std::ldexp(mant + 1024, exp - 25);
+                    }
+                }();
+                return sax->number_float((half & 0x8000) != 0
+                                         ? static_cast<number_float_t>(-val)
+                                         : static_cast<number_float_t>(val), "");
+            }
+
+            case 0xFA: // Single-Precision Float (four-byte IEEE 754)
+            {
+                float number;
+                return get_number(input_format_t::cbor, number) and sax->number_float(static_cast<number_float_t>(number), "");
+            }
+
+            case 0xFB: // Double-Precision Float (eight-byte IEEE 754)
+            {
+                double number;
+                return get_number(input_format_t::cbor, number) and sax->number_float(static_cast<number_float_t>(number), "");
+            }
+
+            default: // anything else (0xFF is handled inside the other types)
+            {
+                auto last_token = get_token_string();
+                return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::cbor, "invalid byte: 0x" + last_token, "value")));
+            }
+        }
+    }
+
+    /*!
+    @brief reads a CBOR string
+
+    This function first reads starting bytes to determine the expected
+    string length and then copies this number of bytes into a string.
+    Additionally, CBOR's strings with indefinite lengths are supported.
+
+    @param[out] result  created string
+
+    @return whether string creation completed
+    */
+    bool get_cbor_string(string_t& result)
+    {
+        if (JSON_UNLIKELY(not unexpect_eof(input_format_t::cbor, "string")))
+        {
+            return false;
+        }
+
+        switch (current)
+        {
+            // UTF-8 string (0x00..0x17 bytes follow)
+            case 0x60:
+            case 0x61:
+            case 0x62:
+            case 0x63:
+            case 0x64:
+            case 0x65:
+            case 0x66:
+            case 0x67:
+            case 0x68:
+            case 0x69:
+            case 0x6A:
+            case 0x6B:
+            case 0x6C:
+            case 0x6D:
+            case 0x6E:
+            case 0x6F:
+            case 0x70:
+            case 0x71:
+            case 0x72:
+            case 0x73:
+            case 0x74:
+            case 0x75:
+            case 0x76:
+            case 0x77:
+            {
+                return get_string(input_format_t::cbor, current & 0x1F, result);
+            }
+
+            case 0x78: // UTF-8 string (one-byte uint8_t for n follows)
+            {
+                uint8_t len;
+                return get_number(input_format_t::cbor, len) and get_string(input_format_t::cbor, len, result);
+            }
+
+            case 0x79: // UTF-8 string (two-byte uint16_t for n follow)
+            {
+                uint16_t len;
+                return get_number(input_format_t::cbor, len) and get_string(input_format_t::cbor, len, result);
+            }
+
+            case 0x7A: // UTF-8 string (four-byte uint32_t for n follow)
+            {
+                uint32_t len;
+                return get_number(input_format_t::cbor, len) and get_string(input_format_t::cbor, len, result);
+            }
+
+            case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow)
+            {
+                uint64_t len;
+                return get_number(input_format_t::cbor, len) and get_string(input_format_t::cbor, len, result);
+            }
+
+            case 0x7F: // UTF-8 string (indefinite length)
+            {
+                while (get() != 0xFF)
+                {
+                    string_t chunk;
+                    if (not get_cbor_string(chunk))
+                    {
+                        return false;
+                    }
+                    result.append(chunk);
+                }
+                return true;
+            }
+
+            default:
+            {
+                auto last_token = get_token_string();
+                return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::cbor, "expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0x" + last_token, "string")));
+            }
+        }
+    }
+
+    /*!
+    @param[in] len  the length of the array or std::size_t(-1) for an
+                    array of indefinite size
+    @return whether array creation completed
+    */
+    bool get_cbor_array(const std::size_t len)
+    {
+        if (JSON_UNLIKELY(not sax->start_array(len)))
+        {
+            return false;
+        }
+
+        if (len != std::size_t(-1))
+        {
+            for (std::size_t i = 0; i < len; ++i)
+            {
+                if (JSON_UNLIKELY(not parse_cbor_internal()))
+                {
+                    return false;
+                }
+            }
+        }
+        else
+        {
+            while (get() != 0xFF)
+            {
+                if (JSON_UNLIKELY(not parse_cbor_internal(false)))
+                {
+                    return false;
+                }
+            }
+        }
+
+        return sax->end_array();
+    }
+
+    /*!
+    @param[in] len  the length of the object or std::size_t(-1) for an
+                    object of indefinite size
+    @return whether object creation completed
+    */
+    bool get_cbor_object(const std::size_t len)
+    {
+        if (not JSON_UNLIKELY(sax->start_object(len)))
+        {
+            return false;
+        }
+
+        string_t key;
+        if (len != std::size_t(-1))
+        {
+            for (std::size_t i = 0; i < len; ++i)
+            {
+                get();
+                if (JSON_UNLIKELY(not get_cbor_string(key) or not sax->key(key)))
+                {
+                    return false;
+                }
+
+                if (JSON_UNLIKELY(not parse_cbor_internal()))
+                {
+                    return false;
+                }
+                key.clear();
+            }
+        }
+        else
+        {
+            while (get() != 0xFF)
+            {
+                if (JSON_UNLIKELY(not get_cbor_string(key) or not sax->key(key)))
+                {
+                    return false;
+                }
+
+                if (JSON_UNLIKELY(not parse_cbor_internal()))
+                {
+                    return false;
+                }
+                key.clear();
+            }
+        }
+
+        return sax->end_object();
+    }
+
+    /////////////
+    // MsgPack //
+    /////////////
+
+    /*!
+    @return whether a valid MessagePack value was passed to the SAX parser
+    */
+    bool parse_msgpack_internal()
+    {
+        switch (get())
+        {
+            // EOF
+            case std::char_traits<char>::eof():
+                return unexpect_eof(input_format_t::msgpack, "value");
+
+            // positive fixint
+            case 0x00:
+            case 0x01:
+            case 0x02:
+            case 0x03:
+            case 0x04:
+            case 0x05:
+            case 0x06:
+            case 0x07:
+            case 0x08:
+            case 0x09:
+            case 0x0A:
+            case 0x0B:
+            case 0x0C:
+            case 0x0D:
+            case 0x0E:
+            case 0x0F:
+            case 0x10:
+            case 0x11:
+            case 0x12:
+            case 0x13:
+            case 0x14:
+            case 0x15:
+            case 0x16:
+            case 0x17:
+            case 0x18:
+            case 0x19:
+            case 0x1A:
+            case 0x1B:
+            case 0x1C:
+            case 0x1D:
+            case 0x1E:
+            case 0x1F:
+            case 0x20:
+            case 0x21:
+            case 0x22:
+            case 0x23:
+            case 0x24:
+            case 0x25:
+            case 0x26:
+            case 0x27:
+            case 0x28:
+            case 0x29:
+            case 0x2A:
+            case 0x2B:
+            case 0x2C:
+            case 0x2D:
+            case 0x2E:
+            case 0x2F:
+            case 0x30:
+            case 0x31:
+            case 0x32:
+            case 0x33:
+            case 0x34:
+            case 0x35:
+            case 0x36:
+            case 0x37:
+            case 0x38:
+            case 0x39:
+            case 0x3A:
+            case 0x3B:
+            case 0x3C:
+            case 0x3D:
+            case 0x3E:
+            case 0x3F:
+            case 0x40:
+            case 0x41:
+            case 0x42:
+            case 0x43:
+            case 0x44:
+            case 0x45:
+            case 0x46:
+            case 0x47:
+            case 0x48:
+            case 0x49:
+            case 0x4A:
+            case 0x4B:
+            case 0x4C:
+            case 0x4D:
+            case 0x4E:
+            case 0x4F:
+            case 0x50:
+            case 0x51:
+            case 0x52:
+            case 0x53:
+            case 0x54:
+            case 0x55:
+            case 0x56:
+            case 0x57:
+            case 0x58:
+            case 0x59:
+            case 0x5A:
+            case 0x5B:
+            case 0x5C:
+            case 0x5D:
+            case 0x5E:
+            case 0x5F:
+            case 0x60:
+            case 0x61:
+            case 0x62:
+            case 0x63:
+            case 0x64:
+            case 0x65:
+            case 0x66:
+            case 0x67:
+            case 0x68:
+            case 0x69:
+            case 0x6A:
+            case 0x6B:
+            case 0x6C:
+            case 0x6D:
+            case 0x6E:
+            case 0x6F:
+            case 0x70:
+            case 0x71:
+            case 0x72:
+            case 0x73:
+            case 0x74:
+            case 0x75:
+            case 0x76:
+            case 0x77:
+            case 0x78:
+            case 0x79:
+            case 0x7A:
+            case 0x7B:
+            case 0x7C:
+            case 0x7D:
+            case 0x7E:
+            case 0x7F:
+                return sax->number_unsigned(static_cast<number_unsigned_t>(current));
+
+            // fixmap
+            case 0x80:
+            case 0x81:
+            case 0x82:
+            case 0x83:
+            case 0x84:
+            case 0x85:
+            case 0x86:
+            case 0x87:
+            case 0x88:
+            case 0x89:
+            case 0x8A:
+            case 0x8B:
+            case 0x8C:
+            case 0x8D:
+            case 0x8E:
+            case 0x8F:
+                return get_msgpack_object(static_cast<std::size_t>(current & 0x0F));
+
+            // fixarray
+            case 0x90:
+            case 0x91:
+            case 0x92:
+            case 0x93:
+            case 0x94:
+            case 0x95:
+            case 0x96:
+            case 0x97:
+            case 0x98:
+            case 0x99:
+            case 0x9A:
+            case 0x9B:
+            case 0x9C:
+            case 0x9D:
+            case 0x9E:
+            case 0x9F:
+                return get_msgpack_array(static_cast<std::size_t>(current & 0x0F));
+
+            // fixstr
+            case 0xA0:
+            case 0xA1:
+            case 0xA2:
+            case 0xA3:
+            case 0xA4:
+            case 0xA5:
+            case 0xA6:
+            case 0xA7:
+            case 0xA8:
+            case 0xA9:
+            case 0xAA:
+            case 0xAB:
+            case 0xAC:
+            case 0xAD:
+            case 0xAE:
+            case 0xAF:
+            case 0xB0:
+            case 0xB1:
+            case 0xB2:
+            case 0xB3:
+            case 0xB4:
+            case 0xB5:
+            case 0xB6:
+            case 0xB7:
+            case 0xB8:
+            case 0xB9:
+            case 0xBA:
+            case 0xBB:
+            case 0xBC:
+            case 0xBD:
+            case 0xBE:
+            case 0xBF:
+            {
+                string_t s;
+                return get_msgpack_string(s) and sax->string(s);
+            }
+
+            case 0xC0: // nil
+                return sax->null();
+
+            case 0xC2: // false
+                return sax->boolean(false);
+
+            case 0xC3: // true
+                return sax->boolean(true);
+
+            case 0xCA: // float 32
+            {
+                float number;
+                return get_number(input_format_t::msgpack, number) and sax->number_float(static_cast<number_float_t>(number), "");
+            }
+
+            case 0xCB: // float 64
+            {
+                double number;
+                return get_number(input_format_t::msgpack, number) and sax->number_float(static_cast<number_float_t>(number), "");
+            }
+
+            case 0xCC: // uint 8
+            {
+                uint8_t number;
+                return get_number(input_format_t::msgpack, number) and sax->number_unsigned(number);
+            }
+
+            case 0xCD: // uint 16
+            {
+                uint16_t number;
+                return get_number(input_format_t::msgpack, number) and sax->number_unsigned(number);
+            }
+
+            case 0xCE: // uint 32
+            {
+                uint32_t number;
+                return get_number(input_format_t::msgpack, number) and sax->number_unsigned(number);
+            }
+
+            case 0xCF: // uint 64
+            {
+                uint64_t number;
+                return get_number(input_format_t::msgpack, number) and sax->number_unsigned(number);
+            }
+
+            case 0xD0: // int 8
+            {
+                int8_t number;
+                return get_number(input_format_t::msgpack, number) and sax->number_integer(number);
+            }
+
+            case 0xD1: // int 16
+            {
+                int16_t number;
+                return get_number(input_format_t::msgpack, number) and sax->number_integer(number);
+            }
+
+            case 0xD2: // int 32
+            {
+                int32_t number;
+                return get_number(input_format_t::msgpack, number) and sax->number_integer(number);
+            }
+
+            case 0xD3: // int 64
+            {
+                int64_t number;
+                return get_number(input_format_t::msgpack, number) and sax->number_integer(number);
+            }
+
+            case 0xD9: // str 8
+            case 0xDA: // str 16
+            case 0xDB: // str 32
+            {
+                string_t s;
+                return get_msgpack_string(s) and sax->string(s);
+            }
+
+            case 0xDC: // array 16
+            {
+                uint16_t len;
+                return get_number(input_format_t::msgpack, len) and get_msgpack_array(static_cast<std::size_t>(len));
+            }
+
+            case 0xDD: // array 32
+            {
+                uint32_t len;
+                return get_number(input_format_t::msgpack, len) and get_msgpack_array(static_cast<std::size_t>(len));
+            }
+
+            case 0xDE: // map 16
+            {
+                uint16_t len;
+                return get_number(input_format_t::msgpack, len) and get_msgpack_object(static_cast<std::size_t>(len));
+            }
+
+            case 0xDF: // map 32
+            {
+                uint32_t len;
+                return get_number(input_format_t::msgpack, len) and get_msgpack_object(static_cast<std::size_t>(len));
+            }
+
+            // negative fixint
+            case 0xE0:
+            case 0xE1:
+            case 0xE2:
+            case 0xE3:
+            case 0xE4:
+            case 0xE5:
+            case 0xE6:
+            case 0xE7:
+            case 0xE8:
+            case 0xE9:
+            case 0xEA:
+            case 0xEB:
+            case 0xEC:
+            case 0xED:
+            case 0xEE:
+            case 0xEF:
+            case 0xF0:
+            case 0xF1:
+            case 0xF2:
+            case 0xF3:
+            case 0xF4:
+            case 0xF5:
+            case 0xF6:
+            case 0xF7:
+            case 0xF8:
+            case 0xF9:
+            case 0xFA:
+            case 0xFB:
+            case 0xFC:
+            case 0xFD:
+            case 0xFE:
+            case 0xFF:
+                return sax->number_integer(static_cast<int8_t>(current));
+
+            default: // anything else
+            {
+                auto last_token = get_token_string();
+                return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::msgpack, "invalid byte: 0x" + last_token, "value")));
+            }
+        }
+    }
+
+    /*!
+    @brief reads a MessagePack string
+
+    This function first reads starting bytes to determine the expected
+    string length and then copies this number of bytes into a string.
+
+    @param[out] result  created string
+
+    @return whether string creation completed
+    */
+    bool get_msgpack_string(string_t& result)
+    {
+        if (JSON_UNLIKELY(not unexpect_eof(input_format_t::msgpack, "string")))
+        {
+            return false;
+        }
+
+        switch (current)
+        {
+            // fixstr
+            case 0xA0:
+            case 0xA1:
+            case 0xA2:
+            case 0xA3:
+            case 0xA4:
+            case 0xA5:
+            case 0xA6:
+            case 0xA7:
+            case 0xA8:
+            case 0xA9:
+            case 0xAA:
+            case 0xAB:
+            case 0xAC:
+            case 0xAD:
+            case 0xAE:
+            case 0xAF:
+            case 0xB0:
+            case 0xB1:
+            case 0xB2:
+            case 0xB3:
+            case 0xB4:
+            case 0xB5:
+            case 0xB6:
+            case 0xB7:
+            case 0xB8:
+            case 0xB9:
+            case 0xBA:
+            case 0xBB:
+            case 0xBC:
+            case 0xBD:
+            case 0xBE:
+            case 0xBF:
+            {
+                return get_string(input_format_t::msgpack, current & 0x1F, result);
+            }
+
+            case 0xD9: // str 8
+            {
+                uint8_t len;
+                return get_number(input_format_t::msgpack, len) and get_string(input_format_t::msgpack, len, result);
+            }
+
+            case 0xDA: // str 16
+            {
+                uint16_t len;
+                return get_number(input_format_t::msgpack, len) and get_string(input_format_t::msgpack, len, result);
+            }
+
+            case 0xDB: // str 32
+            {
+                uint32_t len;
+                return get_number(input_format_t::msgpack, len) and get_string(input_format_t::msgpack, len, result);
+            }
+
+            default:
+            {
+                auto last_token = get_token_string();
+                return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::msgpack, "expected length specification (0xA0-0xBF, 0xD9-0xDB); last byte: 0x" + last_token, "string")));
+            }
+        }
+    }
+
+    /*!
+    @param[in] len  the length of the array
+    @return whether array creation completed
+    */
+    bool get_msgpack_array(const std::size_t len)
+    {
+        if (JSON_UNLIKELY(not sax->start_array(len)))
+        {
+            return false;
+        }
+
+        for (std::size_t i = 0; i < len; ++i)
+        {
+            if (JSON_UNLIKELY(not parse_msgpack_internal()))
+            {
+                return false;
+            }
+        }
+
+        return sax->end_array();
+    }
+
+    /*!
+    @param[in] len  the length of the object
+    @return whether object creation completed
+    */
+    bool get_msgpack_object(const std::size_t len)
+    {
+        if (JSON_UNLIKELY(not sax->start_object(len)))
+        {
+            return false;
+        }
+
+        string_t key;
+        for (std::size_t i = 0; i < len; ++i)
+        {
+            get();
+            if (JSON_UNLIKELY(not get_msgpack_string(key) or not sax->key(key)))
+            {
+                return false;
+            }
+
+            if (JSON_UNLIKELY(not parse_msgpack_internal()))
+            {
+                return false;
+            }
+            key.clear();
+        }
+
+        return sax->end_object();
+    }
+
+    ////////////
+    // UBJSON //
+    ////////////
+
+    /*!
+    @param[in] get_char  whether a new character should be retrieved from the
+                         input (true, default) or whether the last read
+                         character should be considered instead
+
+    @return whether a valid UBJSON value was passed to the SAX parser
+    */
+    bool parse_ubjson_internal(const bool get_char = true)
+    {
+        return get_ubjson_value(get_char ? get_ignore_noop() : current);
+    }
+
+    /*!
+    @brief reads a UBJSON string
+
+    This function is either called after reading the 'S' byte explicitly
+    indicating a string, or in case of an object key where the 'S' byte can be
+    left out.
+
+    @param[out] result   created string
+    @param[in] get_char  whether a new character should be retrieved from the
+                         input (true, default) or whether the last read
+                         character should be considered instead
+
+    @return whether string creation completed
+    */
+    bool get_ubjson_string(string_t& result, const bool get_char = true)
+    {
+        if (get_char)
+        {
+            get();  // TODO: may we ignore N here?
+        }
+
+        if (JSON_UNLIKELY(not unexpect_eof(input_format_t::ubjson, "value")))
+        {
+            return false;
+        }
+
+        switch (current)
+        {
+            case 'U':
+            {
+                uint8_t len;
+                return get_number(input_format_t::ubjson, len) and get_string(input_format_t::ubjson, len, result);
+            }
+
+            case 'i':
+            {
+                int8_t len;
+                return get_number(input_format_t::ubjson, len) and get_string(input_format_t::ubjson, len, result);
+            }
+
+            case 'I':
+            {
+                int16_t len;
+                return get_number(input_format_t::ubjson, len) and get_string(input_format_t::ubjson, len, result);
+            }
+
+            case 'l':
+            {
+                int32_t len;
+                return get_number(input_format_t::ubjson, len) and get_string(input_format_t::ubjson, len, result);
+            }
+
+            case 'L':
+            {
+                int64_t len;
+                return get_number(input_format_t::ubjson, len) and get_string(input_format_t::ubjson, len, result);
+            }
+
+            default:
+                auto last_token = get_token_string();
+                return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::ubjson, "expected length type specification (U, i, I, l, L); last byte: 0x" + last_token, "string")));
+        }
+    }
+
+    /*!
+    @param[out] result  determined size
+    @return whether size determination completed
+    */
+    bool get_ubjson_size_value(std::size_t& result)
+    {
+        switch (get_ignore_noop())
+        {
+            case 'U':
+            {
+                uint8_t number;
+                if (JSON_UNLIKELY(not get_number(input_format_t::ubjson, number)))
+                {
+                    return false;
+                }
+                result = static_cast<std::size_t>(number);
+                return true;
+            }
+
+            case 'i':
+            {
+                int8_t number;
+                if (JSON_UNLIKELY(not get_number(input_format_t::ubjson, number)))
+                {
+                    return false;
+                }
+                result = static_cast<std::size_t>(number);
+                return true;
+            }
+
+            case 'I':
+            {
+                int16_t number;
+                if (JSON_UNLIKELY(not get_number(input_format_t::ubjson, number)))
+                {
+                    return false;
+                }
+                result = static_cast<std::size_t>(number);
+                return true;
+            }
+
+            case 'l':
+            {
+                int32_t number;
+                if (JSON_UNLIKELY(not get_number(input_format_t::ubjson, number)))
+                {
+                    return false;
+                }
+                result = static_cast<std::size_t>(number);
+                return true;
+            }
+
+            case 'L':
+            {
+                int64_t number;
+                if (JSON_UNLIKELY(not get_number(input_format_t::ubjson, number)))
+                {
+                    return false;
+                }
+                result = static_cast<std::size_t>(number);
+                return true;
+            }
+
+            default:
+            {
+                auto last_token = get_token_string();
+                return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::ubjson, "expected length type specification (U, i, I, l, L) after '#'; last byte: 0x" + last_token, "size")));
+            }
+        }
+    }
+
+    /*!
+    @brief determine the type and size for a container
+
+    In the optimized UBJSON format, a type and a size can be provided to allow
+    for a more compact representation.
+
+    @param[out] result  pair of the size and the type
+
+    @return whether pair creation completed
+    */
+    bool get_ubjson_size_type(std::pair<std::size_t, int>& result)
+    {
+        result.first = string_t::npos; // size
+        result.second = 0; // type
+
+        get_ignore_noop();
+
+        if (current == '$')
+        {
+            result.second = get();  // must not ignore 'N', because 'N' maybe the type
+            if (JSON_UNLIKELY(not unexpect_eof(input_format_t::ubjson, "type")))
+            {
+                return false;
+            }
+
+            get_ignore_noop();
+            if (JSON_UNLIKELY(current != '#'))
+            {
+                if (JSON_UNLIKELY(not unexpect_eof(input_format_t::ubjson, "value")))
+                {
+                    return false;
+                }
+                auto last_token = get_token_string();
+                return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::ubjson, "expected '#' after type information; last byte: 0x" + last_token, "size")));
+            }
+
+            return get_ubjson_size_value(result.first);
+        }
+        else if (current == '#')
+        {
+            return get_ubjson_size_value(result.first);
+        }
+        return true;
+    }
+
+    /*!
+    @param prefix  the previously read or set type prefix
+    @return whether value creation completed
+    */
+    bool get_ubjson_value(const int prefix)
+    {
+        switch (prefix)
+        {
+            case std::char_traits<char>::eof():  // EOF
+                return unexpect_eof(input_format_t::ubjson, "value");
+
+            case 'T':  // true
+                return sax->boolean(true);
+            case 'F':  // false
+                return sax->boolean(false);
+
+            case 'Z':  // null
+                return sax->null();
+
+            case 'U':
+            {
+                uint8_t number;
+                return get_number(input_format_t::ubjson, number) and sax->number_unsigned(number);
+            }
+
+            case 'i':
+            {
+                int8_t number;
+                return get_number(input_format_t::ubjson, number) and sax->number_integer(number);
+            }
+
+            case 'I':
+            {
+                int16_t number;
+                return get_number(input_format_t::ubjson, number) and sax->number_integer(number);
+            }
+
+            case 'l':
+            {
+                int32_t number;
+                return get_number(input_format_t::ubjson, number) and sax->number_integer(number);
+            }
+
+            case 'L':
+            {
+                int64_t number;
+                return get_number(input_format_t::ubjson, number) and sax->number_integer(number);
+            }
+
+            case 'd':
+            {
+                float number;
+                return get_number(input_format_t::ubjson, number) and sax->number_float(static_cast<number_float_t>(number), "");
+            }
+
+            case 'D':
+            {
+                double number;
+                return get_number(input_format_t::ubjson, number) and sax->number_float(static_cast<number_float_t>(number), "");
+            }
+
+            case 'C':  // char
+            {
+                get();
+                if (JSON_UNLIKELY(not unexpect_eof(input_format_t::ubjson, "char")))
+                {
+                    return false;
+                }
+                if (JSON_UNLIKELY(current > 127))
+                {
+                    auto last_token = get_token_string();
+                    return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::ubjson, "byte after 'C' must be in range 0x00..0x7F; last byte: 0x" + last_token, "char")));
+                }
+                string_t s(1, static_cast<char>(current));
+                return sax->string(s);
+            }
+
+            case 'S':  // string
+            {
+                string_t s;
+                return get_ubjson_string(s) and sax->string(s);
+            }
+
+            case '[':  // array
+                return get_ubjson_array();
+
+            case '{':  // object
+                return get_ubjson_object();
+
+            default: // anything else
+            {
+                auto last_token = get_token_string();
+                return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::ubjson, "invalid byte: 0x" + last_token, "value")));
+            }
+        }
+    }
+
+    /*!
+    @return whether array creation completed
+    */
+    bool get_ubjson_array()
+    {
+        std::pair<std::size_t, int> size_and_type;
+        if (JSON_UNLIKELY(not get_ubjson_size_type(size_and_type)))
+        {
+            return false;
+        }
+
+        if (size_and_type.first != string_t::npos)
+        {
+            if (JSON_UNLIKELY(not sax->start_array(size_and_type.first)))
+            {
+                return false;
+            }
+
+            if (size_and_type.second != 0)
+            {
+                if (size_and_type.second != 'N')
+                {
+                    for (std::size_t i = 0; i < size_and_type.first; ++i)
+                    {
+                        if (JSON_UNLIKELY(not get_ubjson_value(size_and_type.second)))
+                        {
+                            return false;
+                        }
+                    }
+                }
+            }
+            else
+            {
+                for (std::size_t i = 0; i < size_and_type.first; ++i)
+                {
+                    if (JSON_UNLIKELY(not parse_ubjson_internal()))
+                    {
+                        return false;
+                    }
+                }
+            }
+        }
+        else
+        {
+            if (JSON_UNLIKELY(not sax->start_array(std::size_t(-1))))
+            {
+                return false;
+            }
+
+            while (current != ']')
+            {
+                if (JSON_UNLIKELY(not parse_ubjson_internal(false)))
+                {
+                    return false;
+                }
+                get_ignore_noop();
+            }
+        }
+
+        return sax->end_array();
+    }
+
+    /*!
+    @return whether object creation completed
+    */
+    bool get_ubjson_object()
+    {
+        std::pair<std::size_t, int> size_and_type;
+        if (JSON_UNLIKELY(not get_ubjson_size_type(size_and_type)))
+        {
+            return false;
+        }
+
+        string_t key;
+        if (size_and_type.first != string_t::npos)
+        {
+            if (JSON_UNLIKELY(not sax->start_object(size_and_type.first)))
+            {
+                return false;
+            }
+
+            if (size_and_type.second != 0)
+            {
+                for (std::size_t i = 0; i < size_and_type.first; ++i)
+                {
+                    if (JSON_UNLIKELY(not get_ubjson_string(key) or not sax->key(key)))
+                    {
+                        return false;
+                    }
+                    if (JSON_UNLIKELY(not get_ubjson_value(size_and_type.second)))
+                    {
+                        return false;
+                    }
+                    key.clear();
+                }
+            }
+            else
+            {
+                for (std::size_t i = 0; i < size_and_type.first; ++i)
+                {
+                    if (JSON_UNLIKELY(not get_ubjson_string(key) or not sax->key(key)))
+                    {
+                        return false;
+                    }
+                    if (JSON_UNLIKELY(not parse_ubjson_internal()))
+                    {
+                        return false;
+                    }
+                    key.clear();
+                }
+            }
+        }
+        else
+        {
+            if (JSON_UNLIKELY(not sax->start_object(std::size_t(-1))))
+            {
+                return false;
+            }
+
+            while (current != '}')
+            {
+                if (JSON_UNLIKELY(not get_ubjson_string(key, false) or not sax->key(key)))
+                {
+                    return false;
+                }
+                if (JSON_UNLIKELY(not parse_ubjson_internal()))
+                {
+                    return false;
+                }
+                get_ignore_noop();
+                key.clear();
+            }
+        }
+
+        return sax->end_object();
+    }
+
+    ///////////////////////
+    // Utility functions //
+    ///////////////////////
+
+    /*!
+    @brief get next character from the input
+
+    This function provides the interface to the used input adapter. It does
+    not throw in case the input reached EOF, but returns a -'ve valued
+    `std::char_traits<char>::eof()` in that case.
+
+    @return character read from the input
+    */
+    int get()
+    {
+        ++chars_read;
+        return (current = ia->get_character());
+    }
+
+    /*!
+    @return character read from the input after ignoring all 'N' entries
+    */
+    int get_ignore_noop()
+    {
+        do
+        {
+            get();
+        }
+        while (current == 'N');
+
+        return current;
+    }
+
+    /*
+    @brief read a number from the input
+
+    @tparam NumberType the type of the number
+    @param[in] format   the current format (for diagnostics)
+    @param[out] result  number of type @a NumberType
+
+    @return whether conversion completed
+
+    @note This function needs to respect the system's endianess, because
+          bytes in CBOR, MessagePack, and UBJSON are stored in network order
+          (big endian) and therefore need reordering on little endian systems.
+    */
+    template<typename NumberType, bool InputIsLittleEndian = false>
+    bool get_number(const input_format_t format, NumberType& result)
+    {
+        // step 1: read input into array with system's byte order
+        std::array<uint8_t, sizeof(NumberType)> vec;
+        for (std::size_t i = 0; i < sizeof(NumberType); ++i)
+        {
+            get();
+            if (JSON_UNLIKELY(not unexpect_eof(format, "number")))
+            {
+                return false;
+            }
+
+            // reverse byte order prior to conversion if necessary
+            if (is_little_endian && !InputIsLittleEndian)
+            {
+                vec[sizeof(NumberType) - i - 1] = static_cast<uint8_t>(current);
+            }
+            else
+            {
+                vec[i] = static_cast<uint8_t>(current); // LCOV_EXCL_LINE
+            }
+        }
+
+        // step 2: convert array into number of type T and return
+        std::memcpy(&result, vec.data(), sizeof(NumberType));
+        return true;
+    }
+
+    /*!
+    @brief create a string by reading characters from the input
+
+    @tparam NumberType the type of the number
+    @param[in] format the current format (for diagnostics)
+    @param[in] len number of characters to read
+    @param[out] result string created by reading @a len bytes
+
+    @return whether string creation completed
+
+    @note We can not reserve @a len bytes for the result, because @a len
+          may be too large. Usually, @ref unexpect_eof() detects the end of
+          the input before we run out of string memory.
+    */
+    template<typename NumberType>
+    bool get_string(const input_format_t format,
+                    const NumberType len,
+                    string_t& result)
+    {
+        bool success = true;
+        std::generate_n(std::back_inserter(result), len, [this, &success, &format]()
+        {
+            get();
+            if (JSON_UNLIKELY(not unexpect_eof(format, "string")))
+            {
+                success = false;
+            }
+            return static_cast<char>(current);
+        });
+        return success;
+    }
+
+    /*!
+    @param[in] format   the current format (for diagnostics)
+    @param[in] context  further context information (for diagnostics)
+    @return whether the last read character is not EOF
+    */
+    bool unexpect_eof(const input_format_t format, const char* context) const
+    {
+        if (JSON_UNLIKELY(current == std::char_traits<char>::eof()))
+        {
+            return sax->parse_error(chars_read, "<end of file>",
+                                    parse_error::create(110, chars_read, exception_message(format, "unexpected end of input", context)));
+        }
+        return true;
+    }
+
+    /*!
+    @return a string representation of the last read byte
+    */
+    std::string get_token_string() const
+    {
+        char cr[3];
+        (std::snprintf)(cr, 3, "%.2hhX", static_cast<unsigned char>(current));
+        return std::string{cr};
+    }
+
+    /*!
+    @param[in] format   the current format
+    @param[in] detail   a detailed error message
+    @param[in] context  further contect information
+    @return a message string to use in the parse_error exceptions
+    */
+    std::string exception_message(const input_format_t format,
+                                  const std::string& detail,
+                                  const std::string& context) const
+    {
+        std::string error_msg = "syntax error while parsing ";
+
+        switch (format)
+        {
+            case input_format_t::cbor:
+                error_msg += "CBOR";
+                break;
+
+            case input_format_t::msgpack:
+                error_msg += "MessagePack";
+                break;
+
+            case input_format_t::ubjson:
+                error_msg += "UBJSON";
+                break;
+
+            case input_format_t::bson:
+                error_msg += "BSON";
+                break;
+
+            // LCOV_EXCL_START
+            default:
+                assert(false);
+                // LCOV_EXCL_STOP
+        }
+
+        return error_msg + " " + context + ": " + detail;
+    }
+
+  private:
+    /// input adapter
+    input_adapter_t ia = nullptr;
+
+    /// the current character
+    int current = std::char_traits<char>::eof();
+
+    /// the number of characters read
+    std::size_t chars_read = 0;
+
+    /// whether we can assume little endianess
+    const bool is_little_endian = little_endianess();
+
+    /// the SAX parser
+    json_sax_t* sax = nullptr;
+};
+}  // namespace detail
+}  // namespace nlohmann
+
+// #include <nlohmann/detail/output/binary_writer.hpp>
+
+
+#include <algorithm> // reverse
+#include <array> // array
+#include <cstdint> // uint8_t, uint16_t, uint32_t, uint64_t
+#include <cstring> // memcpy
+#include <limits> // numeric_limits
+
+// #include <nlohmann/detail/input/binary_reader.hpp>
+
+// #include <nlohmann/detail/output/output_adapters.hpp>
+
+
+namespace nlohmann
+{
+namespace detail
+{
+///////////////////
+// binary writer //
+///////////////////
+
+/*!
+@brief serialization to CBOR and MessagePack values
+*/
+template<typename BasicJsonType, typename CharType>
+class binary_writer
+{
+    using string_t = typename BasicJsonType::string_t;
+
+  public:
+    /*!
+    @brief create a binary writer
+
+    @param[in] adapter  output adapter to write to
+    */
+    explicit binary_writer(output_adapter_t<CharType> adapter) : oa(adapter)
+    {
+        assert(oa);
+    }
+
+    /*!
+    @param[in] j  JSON value to serialize
+    @pre       j.type() == value_t::object
+    */
+    void write_bson(const BasicJsonType& j)
+    {
+        switch (j.type())
+        {
+            case value_t::object:
+            {
+                write_bson_object(*j.m_value.object);
+                break;
+            }
+
+            default:
+            {
+                JSON_THROW(type_error::create(317, "to serialize to BSON, top-level type must be object, but is " + std::string(j.type_name())));
+            }
+        }
+    }
+
+    /*!
+    @param[in] j  JSON value to serialize
+    */
+    void write_cbor(const BasicJsonType& j)
+    {
+        switch (j.type())
+        {
+            case value_t::null:
+            {
+                oa->write_character(to_char_type(0xF6));
+                break;
+            }
+
+            case value_t::boolean:
+            {
+                oa->write_character(j.m_value.boolean
+                                    ? to_char_type(0xF5)
+                                    : to_char_type(0xF4));
+                break;
+            }
+
+            case value_t::number_integer:
+            {
+                if (j.m_value.number_integer >= 0)
+                {
+                    // CBOR does not differentiate between positive signed
+                    // integers and unsigned integers. Therefore, we used the
+                    // code from the value_t::number_unsigned case here.
+                    if (j.m_value.number_integer <= 0x17)
+                    {
+                        write_number(static_cast<uint8_t>(j.m_value.number_integer));
+                    }
+                    else if (j.m_value.number_integer <= (std::numeric_limits<uint8_t>::max)())
+                    {
+                        oa->write_character(to_char_type(0x18));
+                        write_number(static_cast<uint8_t>(j.m_value.number_integer));
+                    }
+                    else if (j.m_value.number_integer <= (std::numeric_limits<uint16_t>::max)())
+                    {
+                        oa->write_character(to_char_type(0x19));
+                        write_number(static_cast<uint16_t>(j.m_value.number_integer));
+                    }
+                    else if (j.m_value.number_integer <= (std::numeric_limits<uint32_t>::max)())
+                    {
+                        oa->write_character(to_char_type(0x1A));
+                        write_number(static_cast<uint32_t>(j.m_value.number_integer));
+                    }
+                    else
+                    {
+                        oa->write_character(to_char_type(0x1B));
+                        write_number(static_cast<uint64_t>(j.m_value.number_integer));
+                    }
+                }
+                else
+                {
+                    // The conversions below encode the sign in the first
+                    // byte, and the value is converted to a positive number.
+                    const auto positive_number = -1 - j.m_value.number_integer;
+                    if (j.m_value.number_integer >= -24)
+                    {
+                        write_number(static_cast<uint8_t>(0x20 + positive_number));
+                    }
+                    else if (positive_number <= (std::numeric_limits<uint8_t>::max)())
+                    {
+                        oa->write_character(to_char_type(0x38));
+                        write_number(static_cast<uint8_t>(positive_number));
+                    }
+                    else if (positive_number <= (std::numeric_limits<uint16_t>::max)())
+                    {
+                        oa->write_character(to_char_type(0x39));
+                        write_number(static_cast<uint16_t>(positive_number));
+                    }
+                    else if (positive_number <= (std::numeric_limits<uint32_t>::max)())
+                    {
+                        oa->write_character(to_char_type(0x3A));
+                        write_number(static_cast<uint32_t>(positive_number));
+                    }
+                    else
+                    {
+                        oa->write_character(to_char_type(0x3B));
+                        write_number(static_cast<uint64_t>(positive_number));
+                    }
+                }
+                break;
+            }
+
+            case value_t::number_unsigned:
+            {
+                if (j.m_value.number_unsigned <= 0x17)
+                {
+                    write_number(static_cast<uint8_t>(j.m_value.number_unsigned));
+                }
+                else if (j.m_value.number_unsigned <= (std::numeric_limits<uint8_t>::max)())
+                {
+                    oa->write_character(to_char_type(0x18));
+                    write_number(static_cast<uint8_t>(j.m_value.number_unsigned));
+                }
+                else if (j.m_value.number_unsigned <= (std::numeric_limits<uint16_t>::max)())
+                {
+                    oa->write_character(to_char_type(0x19));
+                    write_number(static_cast<uint16_t>(j.m_value.number_unsigned));
+                }
+                else if (j.m_value.number_unsigned <= (std::numeric_limits<uint32_t>::max)())
+                {
+                    oa->write_character(to_char_type(0x1A));
+                    write_number(static_cast<uint32_t>(j.m_value.number_unsigned));
+                }
+                else
+                {
+                    oa->write_character(to_char_type(0x1B));
+                    write_number(static_cast<uint64_t>(j.m_value.number_unsigned));
+                }
+                break;
+            }
+
+            case value_t::number_float:
+            {
+                oa->write_character(get_cbor_float_prefix(j.m_value.number_float));
+                write_number(j.m_value.number_float);
+                break;
+            }
+
+            case value_t::string:
+            {
+                // step 1: write control byte and the string length
+                const auto N = j.m_value.string->size();
+                if (N <= 0x17)
+                {
+                    write_number(static_cast<uint8_t>(0x60 + N));
+                }
+                else if (N <= (std::numeric_limits<uint8_t>::max)())
+                {
+                    oa->write_character(to_char_type(0x78));
+                    write_number(static_cast<uint8_t>(N));
+                }
+                else if (N <= (std::numeric_limits<uint16_t>::max)())
+                {
+                    oa->write_character(to_char_type(0x79));
+                    write_number(static_cast<uint16_t>(N));
+                }
+                else if (N <= (std::numeric_limits<uint32_t>::max)())
+                {
+                    oa->write_character(to_char_type(0x7A));
+                    write_number(static_cast<uint32_t>(N));
+                }
+                // LCOV_EXCL_START
+                else if (N <= (std::numeric_limits<uint64_t>::max)())
+                {
+                    oa->write_character(to_char_type(0x7B));
+                    write_number(static_cast<uint64_t>(N));
+                }
+                // LCOV_EXCL_STOP
+
+                // step 2: write the string
+                oa->write_characters(
+                    reinterpret_cast<const CharType*>(j.m_value.string->c_str()),
+                    j.m_value.string->size());
+                break;
+            }
+
+            case value_t::array:
+            {
+                // step 1: write control byte and the array size
+                const auto N = j.m_value.array->size();
+                if (N <= 0x17)
+                {
+                    write_number(static_cast<uint8_t>(0x80 + N));
+                }
+                else if (N <= (std::numeric_limits<uint8_t>::max)())
+                {
+                    oa->write_character(to_char_type(0x98));
+                    write_number(static_cast<uint8_t>(N));
+                }
+                else if (N <= (std::numeric_limits<uint16_t>::max)())
+                {
+                    oa->write_character(to_char_type(0x99));
+                    write_number(static_cast<uint16_t>(N));
+                }
+                else if (N <= (std::numeric_limits<uint32_t>::max)())
+                {
+                    oa->write_character(to_char_type(0x9A));
+                    write_number(static_cast<uint32_t>(N));
+                }
+                // LCOV_EXCL_START
+                else if (N <= (std::numeric_limits<uint64_t>::max)())
+                {
+                    oa->write_character(to_char_type(0x9B));
+                    write_number(static_cast<uint64_t>(N));
+                }
+                // LCOV_EXCL_STOP
+
+                // step 2: write each element
+                for (const auto& el : *j.m_value.array)
+                {
+                    write_cbor(el);
+                }
+                break;
+            }
+
+            case value_t::object:
+            {
+                // step 1: write control byte and the object size
+                const auto N = j.m_value.object->size();
+                if (N <= 0x17)
+                {
+                    write_number(static_cast<uint8_t>(0xA0 + N));
+                }
+                else if (N <= (std::numeric_limits<uint8_t>::max)())
+                {
+                    oa->write_character(to_char_type(0xB8));
+                    write_number(static_cast<uint8_t>(N));
+                }
+                else if (N <= (std::numeric_limits<uint16_t>::max)())
+                {
+                    oa->write_character(to_char_type(0xB9));
+                    write_number(static_cast<uint16_t>(N));
+                }
+                else if (N <= (std::numeric_limits<uint32_t>::max)())
+                {
+                    oa->write_character(to_char_type(0xBA));
+                    write_number(static_cast<uint32_t>(N));
+                }
+                // LCOV_EXCL_START
+                else if (N <= (std::numeric_limits<uint64_t>::max)())
+                {
+                    oa->write_character(to_char_type(0xBB));
+                    write_number(static_cast<uint64_t>(N));
+                }
+                // LCOV_EXCL_STOP
+
+                // step 2: write each element
+                for (const auto& el : *j.m_value.object)
+                {
+                    write_cbor(el.first);
+                    write_cbor(el.second);
+                }
+                break;
+            }
+
+            default:
+                break;
+        }
+    }
+
+    /*!
+    @param[in] j  JSON value to serialize
+    */
+    void write_msgpack(const BasicJsonType& j)
+    {
+        switch (j.type())
+        {
+            case value_t::null: // nil
+            {
+                oa->write_character(to_char_type(0xC0));
+                break;
+            }
+
+            case value_t::boolean: // true and false
+            {
+                oa->write_character(j.m_value.boolean
+                                    ? to_char_type(0xC3)
+                                    : to_char_type(0xC2));
+                break;
+            }
+
+            case value_t::number_integer:
+            {
+                if (j.m_value.number_integer >= 0)
+                {
+                    // MessagePack does not differentiate between positive
+                    // signed integers and unsigned integers. Therefore, we used
+                    // the code from the value_t::number_unsigned case here.
+                    if (j.m_value.number_unsigned < 128)
+                    {
+                        // positive fixnum
+                        write_number(static_cast<uint8_t>(j.m_value.number_integer));
+                    }
+                    else if (j.m_value.number_unsigned <= (std::numeric_limits<uint8_t>::max)())
+                    {
+                        // uint 8
+                        oa->write_character(to_char_type(0xCC));
+                        write_number(static_cast<uint8_t>(j.m_value.number_integer));
+                    }
+                    else if (j.m_value.number_unsigned <= (std::numeric_limits<uint16_t>::max)())
+                    {
+                        // uint 16
+                        oa->write_character(to_char_type(0xCD));
+                        write_number(static_cast<uint16_t>(j.m_value.number_integer));
+                    }
+                    else if (j.m_value.number_unsigned <= (std::numeric_limits<uint32_t>::max)())
+                    {
+                        // uint 32
+                        oa->write_character(to_char_type(0xCE));
+                        write_number(static_cast<uint32_t>(j.m_value.number_integer));
+                    }
+                    else if (j.m_value.number_unsigned <= (std::numeric_limits<uint64_t>::max)())
+                    {
+                        // uint 64
+                        oa->write_character(to_char_type(0xCF));
+                        write_number(static_cast<uint64_t>(j.m_value.number_integer));
+                    }
+                }
+                else
+                {
+                    if (j.m_value.number_integer >= -32)
+                    {
+                        // negative fixnum
+                        write_number(static_cast<int8_t>(j.m_value.number_integer));
+                    }
+                    else if (j.m_value.number_integer >= (std::numeric_limits<int8_t>::min)() and
+                             j.m_value.number_integer <= (std::numeric_limits<int8_t>::max)())
+                    {
+                        // int 8
+                        oa->write_character(to_char_type(0xD0));
+                        write_number(static_cast<int8_t>(j.m_value.number_integer));
+                    }
+                    else if (j.m_value.number_integer >= (std::numeric_limits<int16_t>::min)() and
+                             j.m_value.number_integer <= (std::numeric_limits<int16_t>::max)())
+                    {
+                        // int 16
+                        oa->write_character(to_char_type(0xD1));
+                        write_number(static_cast<int16_t>(j.m_value.number_integer));
+                    }
+                    else if (j.m_value.number_integer >= (std::numeric_limits<int32_t>::min)() and
+                             j.m_value.number_integer <= (std::numeric_limits<int32_t>::max)())
+                    {
+                        // int 32
+                        oa->write_character(to_char_type(0xD2));
+                        write_number(static_cast<int32_t>(j.m_value.number_integer));
+                    }
+                    else if (j.m_value.number_integer >= (std::numeric_limits<int64_t>::min)() and
+                             j.m_value.number_integer <= (std::numeric_limits<int64_t>::max)())
+                    {
+                        // int 64
+                        oa->write_character(to_char_type(0xD3));
+                        write_number(static_cast<int64_t>(j.m_value.number_integer));
+                    }
+                }
+                break;
+            }
+
+            case value_t::number_unsigned:
+            {
+                if (j.m_value.number_unsigned < 128)
+                {
+                    // positive fixnum
+                    write_number(static_cast<uint8_t>(j.m_value.number_integer));
+                }
+                else if (j.m_value.number_unsigned <= (std::numeric_limits<uint8_t>::max)())
+                {
+                    // uint 8
+                    oa->write_character(to_char_type(0xCC));
+                    write_number(static_cast<uint8_t>(j.m_value.number_integer));
+                }
+                else if (j.m_value.number_unsigned <= (std::numeric_limits<uint16_t>::max)())
+                {
+                    // uint 16
+                    oa->write_character(to_char_type(0xCD));
+                    write_number(static_cast<uint16_t>(j.m_value.number_integer));
+                }
+                else if (j.m_value.number_unsigned <= (std::numeric_limits<uint32_t>::max)())
+                {
+                    // uint 32
+                    oa->write_character(to_char_type(0xCE));
+                    write_number(static_cast<uint32_t>(j.m_value.number_integer));
+                }
+                else if (j.m_value.number_unsigned <= (std::numeric_limits<uint64_t>::max)())
+                {
+                    // uint 64
+                    oa->write_character(to_char_type(0xCF));
+                    write_number(static_cast<uint64_t>(j.m_value.number_integer));
+                }
+                break;
+            }
+
+            case value_t::number_float:
+            {
+                oa->write_character(get_msgpack_float_prefix(j.m_value.number_float));
+                write_number(j.m_value.number_float);
+                break;
+            }
+
+            case value_t::string:
+            {
+                // step 1: write control byte and the string length
+                const auto N = j.m_value.string->size();
+                if (N <= 31)
+                {
+                    // fixstr
+                    write_number(static_cast<uint8_t>(0xA0 | N));
+                }
+                else if (N <= (std::numeric_limits<uint8_t>::max)())
+                {
+                    // str 8
+                    oa->write_character(to_char_type(0xD9));
+                    write_number(static_cast<uint8_t>(N));
+                }
+                else if (N <= (std::numeric_limits<uint16_t>::max)())
+                {
+                    // str 16
+                    oa->write_character(to_char_type(0xDA));
+                    write_number(static_cast<uint16_t>(N));
+                }
+                else if (N <= (std::numeric_limits<uint32_t>::max)())
+                {
+                    // str 32
+                    oa->write_character(to_char_type(0xDB));
+                    write_number(static_cast<uint32_t>(N));
+                }
+
+                // step 2: write the string
+                oa->write_characters(
+                    reinterpret_cast<const CharType*>(j.m_value.string->c_str()),
+                    j.m_value.string->size());
+                break;
+            }
+
+            case value_t::array:
+            {
+                // step 1: write control byte and the array size
+                const auto N = j.m_value.array->size();
+                if (N <= 15)
+                {
+                    // fixarray
+                    write_number(static_cast<uint8_t>(0x90 | N));
+                }
+                else if (N <= (std::numeric_limits<uint16_t>::max)())
+                {
+                    // array 16
+                    oa->write_character(to_char_type(0xDC));
+                    write_number(static_cast<uint16_t>(N));
+                }
+                else if (N <= (std::numeric_limits<uint32_t>::max)())
+                {
+                    // array 32
+                    oa->write_character(to_char_type(0xDD));
+                    write_number(static_cast<uint32_t>(N));
+                }
+
+                // step 2: write each element
+                for (const auto& el : *j.m_value.array)
+                {
+                    write_msgpack(el);
+                }
+                break;
+            }
+
+            case value_t::object:
+            {
+                // step 1: write control byte and the object size
+                const auto N = j.m_value.object->size();
+                if (N <= 15)
+                {
+                    // fixmap
+                    write_number(static_cast<uint8_t>(0x80 | (N & 0xF)));
+                }
+                else if (N <= (std::numeric_limits<uint16_t>::max)())
+                {
+                    // map 16
+                    oa->write_character(to_char_type(0xDE));
+                    write_number(static_cast<uint16_t>(N));
+                }
+                else if (N <= (std::numeric_limits<uint32_t>::max)())
+                {
+                    // map 32
+                    oa->write_character(to_char_type(0xDF));
+                    write_number(static_cast<uint32_t>(N));
+                }
+
+                // step 2: write each element
+                for (const auto& el : *j.m_value.object)
+                {
+                    write_msgpack(el.first);
+                    write_msgpack(el.second);
+                }
+                break;
+            }
+
+            default:
+                break;
+        }
+    }
+
+    /*!
+    @param[in] j  JSON value to serialize
+    @param[in] use_count   whether to use '#' prefixes (optimized format)
+    @param[in] use_type    whether to use '$' prefixes (optimized format)
+    @param[in] add_prefix  whether prefixes need to be used for this value
+    */
+    void write_ubjson(const BasicJsonType& j, const bool use_count,
+                      const bool use_type, const bool add_prefix = true)
+    {
+        switch (j.type())
+        {
+            case value_t::null:
+            {
+                if (add_prefix)
+                {
+                    oa->write_character(to_char_type('Z'));
+                }
+                break;
+            }
+
+            case value_t::boolean:
+            {
+                if (add_prefix)
+                {
+                    oa->write_character(j.m_value.boolean
+                                        ? to_char_type('T')
+                                        : to_char_type('F'));
+                }
+                break;
+            }
+
+            case value_t::number_integer:
+            {
+                write_number_with_ubjson_prefix(j.m_value.number_integer, add_prefix);
+                break;
+            }
+
+            case value_t::number_unsigned:
+            {
+                write_number_with_ubjson_prefix(j.m_value.number_unsigned, add_prefix);
+                break;
+            }
+
+            case value_t::number_float:
+            {
+                write_number_with_ubjson_prefix(j.m_value.number_float, add_prefix);
+                break;
+            }
+
+            case value_t::string:
+            {
+                if (add_prefix)
+                {
+                    oa->write_character(to_char_type('S'));
+                }
+                write_number_with_ubjson_prefix(j.m_value.string->size(), true);
+                oa->write_characters(
+                    reinterpret_cast<const CharType*>(j.m_value.string->c_str()),
+                    j.m_value.string->size());
+                break;
+            }
+
+            case value_t::array:
+            {
+                if (add_prefix)
+                {
+                    oa->write_character(to_char_type('['));
+                }
+
+                bool prefix_required = true;
+                if (use_type and not j.m_value.array->empty())
+                {
+                    assert(use_count);
+                    const CharType first_prefix = ubjson_prefix(j.front());
+                    const bool same_prefix = std::all_of(j.begin() + 1, j.end(),
+                                                         [this, first_prefix](const BasicJsonType & v)
+                    {
+                        return ubjson_prefix(v) == first_prefix;
+                    });
+
+                    if (same_prefix)
+                    {
+                        prefix_required = false;
+                        oa->write_character(to_char_type('$'));
+                        oa->write_character(first_prefix);
+                    }
+                }
+
+                if (use_count)
+                {
+                    oa->write_character(to_char_type('#'));
+                    write_number_with_ubjson_prefix(j.m_value.array->size(), true);
+                }
+
+                for (const auto& el : *j.m_value.array)
+                {
+                    write_ubjson(el, use_count, use_type, prefix_required);
+                }
+
+                if (not use_count)
+                {
+                    oa->write_character(to_char_type(']'));
+                }
+
+                break;
+            }
+
+            case value_t::object:
+            {
+                if (add_prefix)
+                {
+                    oa->write_character(to_char_type('{'));
+                }
+
+                bool prefix_required = true;
+                if (use_type and not j.m_value.object->empty())
+                {
+                    assert(use_count);
+                    const CharType first_prefix = ubjson_prefix(j.front());
+                    const bool same_prefix = std::all_of(j.begin(), j.end(),
+                                                         [this, first_prefix](const BasicJsonType & v)
+                    {
+                        return ubjson_prefix(v) == first_prefix;
+                    });
+
+                    if (same_prefix)
+                    {
+                        prefix_required = false;
+                        oa->write_character(to_char_type('$'));
+                        oa->write_character(first_prefix);
+                    }
+                }
+
+                if (use_count)
+                {
+                    oa->write_character(to_char_type('#'));
+                    write_number_with_ubjson_prefix(j.m_value.object->size(), true);
+                }
+
+                for (const auto& el : *j.m_value.object)
+                {
+                    write_number_with_ubjson_prefix(el.first.size(), true);
+                    oa->write_characters(
+                        reinterpret_cast<const CharType*>(el.first.c_str()),
+                        el.first.size());
+                    write_ubjson(el.second, use_count, use_type, prefix_required);
+                }
+
+                if (not use_count)
+                {
+                    oa->write_character(to_char_type('}'));
+                }
+
+                break;
+            }
+
+            default:
+                break;
+        }
+    }
+
+  private:
+    //////////
+    // BSON //
+    //////////
+
+    /*!
+    @return The size of a BSON document entry header, including the id marker
+            and the entry name size (and its null-terminator).
+    */
+    static std::size_t calc_bson_entry_header_size(const string_t& name)
+    {
+        const auto it = name.find(static_cast<typename string_t::value_type>(0));
+        if (JSON_UNLIKELY(it != BasicJsonType::string_t::npos))
+        {
+            JSON_THROW(out_of_range::create(409,
+                                            "BSON key cannot contain code point U+0000 (at byte " + std::to_string(it) + ")"));
+        }
+
+        return /*id*/ 1ul + name.size() + /*zero-terminator*/1u;
+    }
+
+    /*!
+    @brief Writes the given @a element_type and @a name to the output adapter
+    */
+    void write_bson_entry_header(const string_t& name,
+                                 const std::uint8_t element_type)
+    {
+        oa->write_character(to_char_type(element_type)); // boolean
+        oa->write_characters(
+            reinterpret_cast<const CharType*>(name.c_str()),
+            name.size() + 1u);
+    }
+
+    /*!
+    @brief Writes a BSON element with key @a name and boolean value @a value
+    */
+    void write_bson_boolean(const string_t& name,
+                            const bool value)
+    {
+        write_bson_entry_header(name, 0x08);
+        oa->write_character(value ? to_char_type(0x01) : to_char_type(0x00));
+    }
+
+    /*!
+    @brief Writes a BSON element with key @a name and double value @a value
+    */
+    void write_bson_double(const string_t& name,
+                           const double value)
+    {
+        write_bson_entry_header(name, 0x01);
+        write_number<double, true>(value);
+    }
+
+    /*!
+    @return The size of the BSON-encoded string in @a value
+    */
+    static std::size_t calc_bson_string_size(const string_t& value)
+    {
+        return sizeof(std::int32_t) + value.size() + 1ul;
+    }
+
+    /*!
+    @brief Writes a BSON element with key @a name and string value @a value
+    */
+    void write_bson_string(const string_t& name,
+                           const string_t& value)
+    {
+        write_bson_entry_header(name, 0x02);
+
+        write_number<std::int32_t, true>(static_cast<std::int32_t>(value.size() + 1ul));
+        oa->write_characters(
+            reinterpret_cast<const CharType*>(value.c_str()),
+            value.size() + 1);
+    }
+
+    /*!
+    @brief Writes a BSON element with key @a name and null value
+    */
+    void write_bson_null(const string_t& name)
+    {
+        write_bson_entry_header(name, 0x0A);
+    }
+
+    /*!
+    @return The size of the BSON-encoded integer @a value
+    */
+    static std::size_t calc_bson_integer_size(const std::int64_t value)
+    {
+        if ((std::numeric_limits<std::int32_t>::min)() <= value and value <= (std::numeric_limits<std::int32_t>::max)())
+        {
+            return sizeof(std::int32_t);
+        }
+        else
+        {
+            return sizeof(std::int64_t);
+        }
+    }
+
+    /*!
+    @brief Writes a BSON element with key @a name and integer @a value
+    */
+    void write_bson_integer(const string_t& name,
+                            const std::int64_t value)
+    {
+        if ((std::numeric_limits<std::int32_t>::min)() <= value and value <= (std::numeric_limits<std::int32_t>::max)())
+        {
+            write_bson_entry_header(name, 0x10); // int32
+            write_number<std::int32_t, true>(static_cast<std::int32_t>(value));
+        }
+        else
+        {
+            write_bson_entry_header(name, 0x12); // int64
+            write_number<std::int64_t, true>(static_cast<std::int64_t>(value));
+        }
+    }
+
+    /*!
+    @return The size of the BSON-encoded unsigned integer in @a j
+    */
+    static constexpr std::size_t calc_bson_unsigned_size(const std::uint64_t value) noexcept
+    {
+        return (value <= static_cast<std::uint64_t>((std::numeric_limits<std::int32_t>::max)()))
+               ? sizeof(std::int32_t)
+               : sizeof(std::int64_t);
+    }
+
+    /*!
+    @brief Writes a BSON element with key @a name and unsigned @a value
+    */
+    void write_bson_unsigned(const string_t& name,
+                             const std::uint64_t value)
+    {
+        if (value <= static_cast<std::uint64_t>((std::numeric_limits<std::int32_t>::max)()))
+        {
+            write_bson_entry_header(name, 0x10 /* int32 */);
+            write_number<std::int32_t, true>(static_cast<std::int32_t>(value));
+        }
+        else if (value <= static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()))
+        {
+            write_bson_entry_header(name, 0x12 /* int64 */);
+            write_number<std::int64_t, true>(static_cast<std::int64_t>(value));
+        }
+        else
+        {
+            JSON_THROW(out_of_range::create(407, "integer number " + std::to_string(value) + " cannot be represented by BSON as it does not fit int64"));
+        }
+    }
+
+    /*!
+    @brief Writes a BSON element with key @a name and object @a value
+    */
+    void write_bson_object_entry(const string_t& name,
+                                 const typename BasicJsonType::object_t& value)
+    {
+        write_bson_entry_header(name, 0x03); // object
+        write_bson_object(value);
+    }
+
+    /*!
+    @return The size of the BSON-encoded array @a value
+    */
+    static std::size_t calc_bson_array_size(const typename BasicJsonType::array_t& value)
+    {
+        std::size_t embedded_document_size = 0ul;
+        std::size_t array_index = 0ul;
+
+        for (const auto& el : value)
+        {
+            embedded_document_size += calc_bson_element_size(std::to_string(array_index++), el);
+        }
+
+        return sizeof(std::int32_t) + embedded_document_size + 1ul;
+    }
+
+    /*!
+    @brief Writes a BSON element with key @a name and array @a value
+    */
+    void write_bson_array(const string_t& name,
+                          const typename BasicJsonType::array_t& value)
+    {
+        write_bson_entry_header(name, 0x04); // array
+        write_number<std::int32_t, true>(static_cast<std::int32_t>(calc_bson_array_size(value)));
+
+        std::size_t array_index = 0ul;
+
+        for (const auto& el : value)
+        {
+            write_bson_element(std::to_string(array_index++), el);
+        }
+
+        oa->write_character(to_char_type(0x00));
+    }
+
+    /*!
+    @brief Calculates the size necessary to serialize the JSON value @a j with its @a name
+    @return The calculated size for the BSON document entry for @a j with the given @a name.
+    */
+    static std::size_t calc_bson_element_size(const string_t& name,
+            const BasicJsonType& j)
+    {
+        const auto header_size = calc_bson_entry_header_size(name);
+        switch (j.type())
+        {
+            case value_t::object:
+                return header_size + calc_bson_object_size(*j.m_value.object);
+
+            case value_t::array:
+                return header_size + calc_bson_array_size(*j.m_value.array);
+
+            case value_t::boolean:
+                return header_size + 1ul;
+
+            case value_t::number_float:
+                return header_size + 8ul;
+
+            case value_t::number_integer:
+                return header_size + calc_bson_integer_size(j.m_value.number_integer);
+
+            case value_t::number_unsigned:
+                return header_size + calc_bson_unsigned_size(j.m_value.number_unsigned);
+
+            case value_t::string:
+                return header_size + calc_bson_string_size(*j.m_value.string);
+
+            case value_t::null:
+                return header_size + 0ul;
+
+            // LCOV_EXCL_START
+            default:
+                assert(false);
+                return 0ul;
+                // LCOV_EXCL_STOP
+        };
+    }
+
+    /*!
+    @brief Serializes the JSON value @a j to BSON and associates it with the
+           key @a name.
+    @param name The name to associate with the JSON entity @a j within the
+                current BSON document
+    @return The size of the BSON entry
+    */
+    void write_bson_element(const string_t& name,
+                            const BasicJsonType& j)
+    {
+        switch (j.type())
+        {
+            case value_t::object:
+                return write_bson_object_entry(name, *j.m_value.object);
+
+            case value_t::array:
+                return write_bson_array(name, *j.m_value.array);
+
+            case value_t::boolean:
+                return write_bson_boolean(name, j.m_value.boolean);
+
+            case value_t::number_float:
+                return write_bson_double(name, j.m_value.number_float);
+
+            case value_t::number_integer:
+                return write_bson_integer(name, j.m_value.number_integer);
+
+            case value_t::number_unsigned:
+                return write_bson_unsigned(name, j.m_value.number_unsigned);
+
+            case value_t::string:
+                return write_bson_string(name, *j.m_value.string);
+
+            case value_t::null:
+                return write_bson_null(name);
+
+            // LCOV_EXCL_START
+            default:
+                assert(false);
+                return;
+                // LCOV_EXCL_STOP
+        };
+    }
+
+    /*!
+    @brief Calculates the size of the BSON serialization of the given
+           JSON-object @a j.
+    @param[in] j  JSON value to serialize
+    @pre       j.type() == value_t::object
+    */
+    static std::size_t calc_bson_object_size(const typename BasicJsonType::object_t& value)
+    {
+        std::size_t document_size = std::accumulate(value.begin(), value.end(), 0ul,
+                                    [](size_t result, const typename BasicJsonType::object_t::value_type & el)
+        {
+            return result += calc_bson_element_size(el.first, el.second);
+        });
+
+        return sizeof(std::int32_t) + document_size + 1ul;
+    }
+
+    /*!
+    @param[in] j  JSON value to serialize
+    @pre       j.type() == value_t::object
+    */
+    void write_bson_object(const typename BasicJsonType::object_t& value)
+    {
+        write_number<std::int32_t, true>(static_cast<std::int32_t>(calc_bson_object_size(value)));
+
+        for (const auto& el : value)
+        {
+            write_bson_element(el.first, el.second);
+        }
+
+        oa->write_character(to_char_type(0x00));
+    }
+
+    //////////
+    // CBOR //
+    //////////
+
+    static constexpr CharType get_cbor_float_prefix(float /*unused*/)
+    {
+        return to_char_type(0xFA);  // Single-Precision Float
+    }
+
+    static constexpr CharType get_cbor_float_prefix(double /*unused*/)
+    {
+        return to_char_type(0xFB);  // Double-Precision Float
+    }
+
+    /////////////
+    // MsgPack //
+    /////////////
+
+    static constexpr CharType get_msgpack_float_prefix(float /*unused*/)
+    {
+        return to_char_type(0xCA);  // float 32
+    }
+
+    static constexpr CharType get_msgpack_float_prefix(double /*unused*/)
+    {
+        return to_char_type(0xCB);  // float 64
+    }
+
+    ////////////
+    // UBJSON //
+    ////////////
+
+    // UBJSON: write number (floating point)
+    template<typename NumberType, typename std::enable_if<
+                 std::is_floating_point<NumberType>::value, int>::type = 0>
+    void write_number_with_ubjson_prefix(const NumberType n,
+                                         const bool add_prefix)
+    {
+        if (add_prefix)
+        {
+            oa->write_character(get_ubjson_float_prefix(n));
+        }
+        write_number(n);
+    }
+
+    // UBJSON: write number (unsigned integer)
+    template<typename NumberType, typename std::enable_if<
+                 std::is_unsigned<NumberType>::value, int>::type = 0>
+    void write_number_with_ubjson_prefix(const NumberType n,
+                                         const bool add_prefix)
+    {
+        if (n <= static_cast<uint64_t>((std::numeric_limits<int8_t>::max)()))
+        {
+            if (add_prefix)
+            {
+                oa->write_character(to_char_type('i'));  // int8
+            }
+            write_number(static_cast<uint8_t>(n));
+        }
+        else if (n <= (std::numeric_limits<uint8_t>::max)())
+        {
+            if (add_prefix)
+            {
+                oa->write_character(to_char_type('U'));  // uint8
+            }
+            write_number(static_cast<uint8_t>(n));
+        }
+        else if (n <= static_cast<uint64_t>((std::numeric_limits<int16_t>::max)()))
+        {
+            if (add_prefix)
+            {
+                oa->write_character(to_char_type('I'));  // int16
+            }
+            write_number(static_cast<int16_t>(n));
+        }
+        else if (n <= static_cast<uint64_t>((std::numeric_limits<int32_t>::max)()))
+        {
+            if (add_prefix)
+            {
+                oa->write_character(to_char_type('l'));  // int32
+            }
+            write_number(static_cast<int32_t>(n));
+        }
+        else if (n <= static_cast<uint64_t>((std::numeric_limits<int64_t>::max)()))
+        {
+            if (add_prefix)
+            {
+                oa->write_character(to_char_type('L'));  // int64
+            }
+            write_number(static_cast<int64_t>(n));
+        }
+        else
+        {
+            JSON_THROW(out_of_range::create(407, "integer number " + std::to_string(n) + " cannot be represented by UBJSON as it does not fit int64"));
+        }
+    }
+
+    // UBJSON: write number (signed integer)
+    template<typename NumberType, typename std::enable_if<
+                 std::is_signed<NumberType>::value and
+                 not std::is_floating_point<NumberType>::value, int>::type = 0>
+    void write_number_with_ubjson_prefix(const NumberType n,
+                                         const bool add_prefix)
+    {
+        if ((std::numeric_limits<int8_t>::min)() <= n and n <= (std::numeric_limits<int8_t>::max)())
+        {
+            if (add_prefix)
+            {
+                oa->write_character(to_char_type('i'));  // int8
+            }
+            write_number(static_cast<int8_t>(n));
+        }
+        else if (static_cast<int64_t>((std::numeric_limits<uint8_t>::min)()) <= n and n <= static_cast<int64_t>((std::numeric_limits<uint8_t>::max)()))
+        {
+            if (add_prefix)
+            {
+                oa->write_character(to_char_type('U'));  // uint8
+            }
+            write_number(static_cast<uint8_t>(n));
+        }
+        else if ((std::numeric_limits<int16_t>::min)() <= n and n <= (std::numeric_limits<int16_t>::max)())
+        {
+            if (add_prefix)
+            {
+                oa->write_character(to_char_type('I'));  // int16
+            }
+            write_number(static_cast<int16_t>(n));
+        }
+        else if ((std::numeric_limits<int32_t>::min)() <= n and n <= (std::numeric_limits<int32_t>::max)())
+        {
+            if (add_prefix)
+            {
+                oa->write_character(to_char_type('l'));  // int32
+            }
+            write_number(static_cast<int32_t>(n));
+        }
+        else if ((std::numeric_limits<int64_t>::min)() <= n and n <= (std::numeric_limits<int64_t>::max)())
+        {
+            if (add_prefix)
+            {
+                oa->write_character(to_char_type('L'));  // int64
+            }
+            write_number(static_cast<int64_t>(n));
+        }
+        // LCOV_EXCL_START
+        else
+        {
+            JSON_THROW(out_of_range::create(407, "integer number " + std::to_string(n) + " cannot be represented by UBJSON as it does not fit int64"));
+        }
+        // LCOV_EXCL_STOP
+    }
+
+    /*!
+    @brief determine the type prefix of container values
+
+    @note This function does not need to be 100% accurate when it comes to
+          integer limits. In case a number exceeds the limits of int64_t,
+          this will be detected by a later call to function
+          write_number_with_ubjson_prefix. Therefore, we return 'L' for any
+          value that does not fit the previous limits.
+    */
+    CharType ubjson_prefix(const BasicJsonType& j) const noexcept
+    {
+        switch (j.type())
+        {
+            case value_t::null:
+                return 'Z';
+
+            case value_t::boolean:
+                return j.m_value.boolean ? 'T' : 'F';
+
+            case value_t::number_integer:
+            {
+                if ((std::numeric_limits<int8_t>::min)() <= j.m_value.number_integer and j.m_value.number_integer <= (std::numeric_limits<int8_t>::max)())
+                {
+                    return 'i';
+                }
+                if ((std::numeric_limits<uint8_t>::min)() <= j.m_value.number_integer and j.m_value.number_integer <= (std::numeric_limits<uint8_t>::max)())
+                {
+                    return 'U';
+                }
+                if ((std::numeric_limits<int16_t>::min)() <= j.m_value.number_integer and j.m_value.number_integer <= (std::numeric_limits<int16_t>::max)())
+                {
+                    return 'I';
+                }
+                if ((std::numeric_limits<int32_t>::min)() <= j.m_value.number_integer and j.m_value.number_integer <= (std::numeric_limits<int32_t>::max)())
+                {
+                    return 'l';
+                }
+                // no check and assume int64_t (see note above)
+                return 'L';
+            }
+
+            case value_t::number_unsigned:
+            {
+                if (j.m_value.number_unsigned <= (std::numeric_limits<int8_t>::max)())
+                {
+                    return 'i';
+                }
+                if (j.m_value.number_unsigned <= (std::numeric_limits<uint8_t>::max)())
+                {
+                    return 'U';
+                }
+                if (j.m_value.number_unsigned <= (std::numeric_limits<int16_t>::max)())
+                {
+                    return 'I';
+                }
+                if (j.m_value.number_unsigned <= (std::numeric_limits<int32_t>::max)())
+                {
+                    return 'l';
+                }
+                // no check and assume int64_t (see note above)
+                return 'L';
+            }
+
+            case value_t::number_float:
+                return get_ubjson_float_prefix(j.m_value.number_float);
+
+            case value_t::string:
+                return 'S';
+
+            case value_t::array:
+                return '[';
+
+            case value_t::object:
+                return '{';
+
+            default:  // discarded values
+                return 'N';
+        }
+    }
+
+    static constexpr CharType get_ubjson_float_prefix(float /*unused*/)
+    {
+        return 'd';  // float 32
+    }
+
+    static constexpr CharType get_ubjson_float_prefix(double /*unused*/)
+    {
+        return 'D';  // float 64
+    }
+
+    ///////////////////////
+    // Utility functions //
+    ///////////////////////
+
+    /*
+    @brief write a number to output input
+    @param[in] n number of type @a NumberType
+    @tparam NumberType the type of the number
+    @tparam OutputIsLittleEndian Set to true if output data is
+                                 required to be little endian
+
+    @note This function needs to respect the system's endianess, because bytes
+          in CBOR, MessagePack, and UBJSON are stored in network order (big
+          endian) and therefore need reordering on little endian systems.
+    */
+    template<typename NumberType, bool OutputIsLittleEndian = false>
+    void write_number(const NumberType n)
+    {
+        // step 1: write number to array of length NumberType
+        std::array<CharType, sizeof(NumberType)> vec;
+        std::memcpy(vec.data(), &n, sizeof(NumberType));
+
+        // step 2: write array to output (with possible reordering)
+        if (is_little_endian and not OutputIsLittleEndian)
+        {
+            // reverse byte order prior to conversion if necessary
+            std::reverse(vec.begin(), vec.end());
+        }
+
+        oa->write_characters(vec.data(), sizeof(NumberType));
+    }
+
+  public:
+    // The following to_char_type functions are implement the conversion
+    // between uint8_t and CharType. In case CharType is not unsigned,
+    // such a conversion is required to allow values greater than 128.
+    // See <https://github.com/nlohmann/json/issues/1286> for a discussion.
+    template < typename C = CharType,
+               enable_if_t < std::is_signed<C>::value and std::is_signed<char>::value > * = nullptr >
+    static constexpr CharType to_char_type(std::uint8_t x) noexcept
+    {
+        return *reinterpret_cast<char*>(&x);
+    }
+
+    template < typename C = CharType,
+               enable_if_t < std::is_signed<C>::value and std::is_unsigned<char>::value > * = nullptr >
+    static CharType to_char_type(std::uint8_t x) noexcept
+    {
+        static_assert(sizeof(std::uint8_t) == sizeof(CharType), "size of CharType must be equal to std::uint8_t");
+        static_assert(std::is_pod<CharType>::value, "CharType must be POD");
+        CharType result;
+        std::memcpy(&result, &x, sizeof(x));
+        return result;
+    }
+
+    template<typename C = CharType,
+             enable_if_t<std::is_unsigned<C>::value>* = nullptr>
+    static constexpr CharType to_char_type(std::uint8_t x) noexcept
+    {
+        return x;
+    }
+
+    template < typename InputCharType, typename C = CharType,
+               enable_if_t <
+                   std::is_signed<C>::value and
+                   std::is_signed<char>::value and
+                   std::is_same<char, typename std::remove_cv<InputCharType>::type>::value
+                   > * = nullptr >
+    static constexpr CharType to_char_type(InputCharType x) noexcept
+    {
+        return x;
+    }
+
+  private:
+    /// whether we can assume little endianess
+    const bool is_little_endian = binary_reader<BasicJsonType>::little_endianess();
+
+    /// the output
+    output_adapter_t<CharType> oa = nullptr;
+};
+}  // namespace detail
+}  // namespace nlohmann
+
+// #include <nlohmann/detail/output/serializer.hpp>
+
+
+#include <algorithm> // reverse, remove, fill, find, none_of
+#include <array> // array
+#include <cassert> // assert
+#include <ciso646> // and, or
+#include <clocale> // localeconv, lconv
+#include <cmath> // labs, isfinite, isnan, signbit
+#include <cstddef> // size_t, ptrdiff_t
+#include <cstdint> // uint8_t
+#include <cstdio> // snprintf
+#include <limits> // numeric_limits
+#include <string> // string
+#include <type_traits> // is_same
+
+// #include <nlohmann/detail/exceptions.hpp>
+
+// #include <nlohmann/detail/conversions/to_chars.hpp>
+
+
+#include <cassert> // assert
+#include <ciso646> // or, and, not
+#include <cmath>   // signbit, isfinite
+#include <cstdint> // intN_t, uintN_t
+#include <cstring> // memcpy, memmove
+
+namespace nlohmann
+{
+namespace detail
+{
+
+/*!
+@brief implements the Grisu2 algorithm for binary to decimal floating-point
+conversion.
+
+This implementation is a slightly modified version of the reference
+implementation which may be obtained from
+http://florian.loitsch.com/publications (bench.tar.gz).
+
+The code is distributed under the MIT license, Copyright (c) 2009 Florian Loitsch.
+
+For a detailed description of the algorithm see:
+
+[1] Loitsch, "Printing Floating-Point Numbers Quickly and Accurately with
+    Integers", Proceedings of the ACM SIGPLAN 2010 Conference on Programming
+    Language Design and Implementation, PLDI 2010
+[2] Burger, Dybvig, "Printing Floating-Point Numbers Quickly and Accurately",
+    Proceedings of the ACM SIGPLAN 1996 Conference on Programming Language
+    Design and Implementation, PLDI 1996
+*/
+namespace dtoa_impl
+{
+
+template <typename Target, typename Source>
+Target reinterpret_bits(const Source source)
+{
+    static_assert(sizeof(Target) == sizeof(Source), "size mismatch");
+
+    Target target;
+    std::memcpy(&target, &source, sizeof(Source));
+    return target;
+}
+
+struct diyfp // f * 2^e
+{
+    static constexpr int kPrecision = 64; // = q
+
+    uint64_t f = 0;
+    int e = 0;
+
+    constexpr diyfp(uint64_t f_, int e_) noexcept : f(f_), e(e_) {}
+
+    /*!
+    @brief returns x - y
+    @pre x.e == y.e and x.f >= y.f
+    */
+    static diyfp sub(const diyfp& x, const diyfp& y) noexcept
+    {
+        assert(x.e == y.e);
+        assert(x.f >= y.f);
+
+        return {x.f - y.f, x.e};
+    }
+
+    /*!
+    @brief returns x * y
+    @note The result is rounded. (Only the upper q bits are returned.)
+    */
+    static diyfp mul(const diyfp& x, const diyfp& y) noexcept
+    {
+        static_assert(kPrecision == 64, "internal error");
+
+        // Computes:
+        //  f = round((x.f * y.f) / 2^q)
+        //  e = x.e + y.e + q
+
+        // Emulate the 64-bit * 64-bit multiplication:
+        //
+        // p = u * v
+        //   = (u_lo + 2^32 u_hi) (v_lo + 2^32 v_hi)
+        //   = (u_lo v_lo         ) + 2^32 ((u_lo v_hi         ) + (u_hi v_lo         )) + 2^64 (u_hi v_hi         )
+        //   = (p0                ) + 2^32 ((p1                ) + (p2                )) + 2^64 (p3                )
+        //   = (p0_lo + 2^32 p0_hi) + 2^32 ((p1_lo + 2^32 p1_hi) + (p2_lo + 2^32 p2_hi)) + 2^64 (p3                )
+        //   = (p0_lo             ) + 2^32 (p0_hi + p1_lo + p2_lo                      ) + 2^64 (p1_hi + p2_hi + p3)
+        //   = (p0_lo             ) + 2^32 (Q                                          ) + 2^64 (H                 )
+        //   = (p0_lo             ) + 2^32 (Q_lo + 2^32 Q_hi                           ) + 2^64 (H                 )
+        //
+        // (Since Q might be larger than 2^32 - 1)
+        //
+        //   = (p0_lo + 2^32 Q_lo) + 2^64 (Q_hi + H)
+        //
+        // (Q_hi + H does not overflow a 64-bit int)
+        //
+        //   = p_lo + 2^64 p_hi
+
+        const uint64_t u_lo = x.f & 0xFFFFFFFF;
+        const uint64_t u_hi = x.f >> 32;
+        const uint64_t v_lo = y.f & 0xFFFFFFFF;
+        const uint64_t v_hi = y.f >> 32;
+
+        const uint64_t p0 = u_lo * v_lo;
+        const uint64_t p1 = u_lo * v_hi;
+        const uint64_t p2 = u_hi * v_lo;
+        const uint64_t p3 = u_hi * v_hi;
+
+        const uint64_t p0_hi = p0 >> 32;
+        const uint64_t p1_lo = p1 & 0xFFFFFFFF;
+        const uint64_t p1_hi = p1 >> 32;
+        const uint64_t p2_lo = p2 & 0xFFFFFFFF;
+        const uint64_t p2_hi = p2 >> 32;
+
+        uint64_t Q = p0_hi + p1_lo + p2_lo;
+
+        // The full product might now be computed as
+        //
+        // p_hi = p3 + p2_hi + p1_hi + (Q >> 32)
+        // p_lo = p0_lo + (Q << 32)
+        //
+        // But in this particular case here, the full p_lo is not required.
+        // Effectively we only need to add the highest bit in p_lo to p_hi (and
+        // Q_hi + 1 does not overflow).
+
+        Q += uint64_t{1} << (64 - 32 - 1); // round, ties up
+
+        const uint64_t h = p3 + p2_hi + p1_hi + (Q >> 32);
+
+        return {h, x.e + y.e + 64};
+    }
+
+    /*!
+    @brief normalize x such that the significand is >= 2^(q-1)
+    @pre x.f != 0
+    */
+    static diyfp normalize(diyfp x) noexcept
+    {
+        assert(x.f != 0);
+
+        while ((x.f >> 63) == 0)
+        {
+            x.f <<= 1;
+            x.e--;
+        }
+
+        return x;
+    }
+
+    /*!
+    @brief normalize x such that the result has the exponent E
+    @pre e >= x.e and the upper e - x.e bits of x.f must be zero.
+    */
+    static diyfp normalize_to(const diyfp& x, const int target_exponent) noexcept
+    {
+        const int delta = x.e - target_exponent;
+
+        assert(delta >= 0);
+        assert(((x.f << delta) >> delta) == x.f);
+
+        return {x.f << delta, target_exponent};
+    }
+};
+
+struct boundaries
+{
+    diyfp w;
+    diyfp minus;
+    diyfp plus;
+};
+
+/*!
+Compute the (normalized) diyfp representing the input number 'value' and its
+boundaries.
+
+@pre value must be finite and positive
+*/
+template <typename FloatType>
+boundaries compute_boundaries(FloatType value)
+{
+    assert(std::isfinite(value));
+    assert(value > 0);
+
+    // Convert the IEEE representation into a diyfp.
+    //
+    // If v is denormal:
+    //      value = 0.F * 2^(1 - bias) = (          F) * 2^(1 - bias - (p-1))
+    // If v is normalized:
+    //      value = 1.F * 2^(E - bias) = (2^(p-1) + F) * 2^(E - bias - (p-1))
+
+    static_assert(std::numeric_limits<FloatType>::is_iec559,
+                  "internal error: dtoa_short requires an IEEE-754 floating-point implementation");
+
+    constexpr int      kPrecision = std::numeric_limits<FloatType>::digits; // = p (includes the hidden bit)
+    constexpr int      kBias      = std::numeric_limits<FloatType>::max_exponent - 1 + (kPrecision - 1);
+    constexpr int      kMinExp    = 1 - kBias;
+    constexpr uint64_t kHiddenBit = uint64_t{1} << (kPrecision - 1); // = 2^(p-1)
+
+    using bits_type = typename std::conditional< kPrecision == 24, uint32_t, uint64_t >::type;
+
+    const uint64_t bits = reinterpret_bits<bits_type>(value);
+    const uint64_t E = bits >> (kPrecision - 1);
+    const uint64_t F = bits & (kHiddenBit - 1);
+
+    const bool is_denormal = (E == 0);
+    const diyfp v = is_denormal
+                    ? diyfp(F, kMinExp)
+                    : diyfp(F + kHiddenBit, static_cast<int>(E) - kBias);
+
+    // Compute the boundaries m- and m+ of the floating-point value
+    // v = f * 2^e.
+    //
+    // Determine v- and v+, the floating-point predecessor and successor if v,
+    // respectively.
+    //
+    //      v- = v - 2^e        if f != 2^(p-1) or e == e_min                (A)
+    //         = v - 2^(e-1)    if f == 2^(p-1) and e > e_min                (B)
+    //
+    //      v+ = v + 2^e
+    //
+    // Let m- = (v- + v) / 2 and m+ = (v + v+) / 2. All real numbers _strictly_
+    // between m- and m+ round to v, regardless of how the input rounding
+    // algorithm breaks ties.
+    //
+    //      ---+-------------+-------------+-------------+-------------+---  (A)
+    //         v-            m-            v             m+            v+
+    //
+    //      -----------------+------+------+-------------+-------------+---  (B)
+    //                       v-     m-     v             m+            v+
+
+    const bool lower_boundary_is_closer = (F == 0 and E > 1);
+    const diyfp m_plus = diyfp(2 * v.f + 1, v.e - 1);
+    const diyfp m_minus = lower_boundary_is_closer
+                          ? diyfp(4 * v.f - 1, v.e - 2)  // (B)
+                          : diyfp(2 * v.f - 1, v.e - 1); // (A)
+
+    // Determine the normalized w+ = m+.
+    const diyfp w_plus = diyfp::normalize(m_plus);
+
+    // Determine w- = m- such that e_(w-) = e_(w+).
+    const diyfp w_minus = diyfp::normalize_to(m_minus, w_plus.e);
+
+    return {diyfp::normalize(v), w_minus, w_plus};
+}
+
+// Given normalized diyfp w, Grisu needs to find a (normalized) cached
+// power-of-ten c, such that the exponent of the product c * w = f * 2^e lies
+// within a certain range [alpha, gamma] (Definition 3.2 from [1])
+//
+//      alpha <= e = e_c + e_w + q <= gamma
+//
+// or
+//
+//      f_c * f_w * 2^alpha <= f_c 2^(e_c) * f_w 2^(e_w) * 2^q
+//                          <= f_c * f_w * 2^gamma
+//
+// Since c and w are normalized, i.e. 2^(q-1) <= f < 2^q, this implies
+//
+//      2^(q-1) * 2^(q-1) * 2^alpha <= c * w * 2^q < 2^q * 2^q * 2^gamma
+//
+// or
+//
+//      2^(q - 2 + alpha) <= c * w < 2^(q + gamma)
+//
+// The choice of (alpha,gamma) determines the size of the table and the form of
+// the digit generation procedure. Using (alpha,gamma)=(-60,-32) works out well
+// in practice:
+//
+// The idea is to cut the number c * w = f * 2^e into two parts, which can be
+// processed independently: An integral part p1, and a fractional part p2:
+//
+//      f * 2^e = ( (f div 2^-e) * 2^-e + (f mod 2^-e) ) * 2^e
+//              = (f div 2^-e) + (f mod 2^-e) * 2^e
+//              = p1 + p2 * 2^e
+//
+// The conversion of p1 into decimal form requires a series of divisions and
+// modulos by (a power of) 10. These operations are faster for 32-bit than for
+// 64-bit integers, so p1 should ideally fit into a 32-bit integer. This can be
+// achieved by choosing
+//
+//      -e >= 32   or   e <= -32 := gamma
+//
+// In order to convert the fractional part
+//
+//      p2 * 2^e = p2 / 2^-e = d[-1] / 10^1 + d[-2] / 10^2 + ...
+//
+// into decimal form, the fraction is repeatedly multiplied by 10 and the digits
+// d[-i] are extracted in order:
+//
+//      (10 * p2) div 2^-e = d[-1]
+//      (10 * p2) mod 2^-e = d[-2] / 10^1 + ...
+//
+// The multiplication by 10 must not overflow. It is sufficient to choose
+//
+//      10 * p2 < 16 * p2 = 2^4 * p2 <= 2^64.
+//
+// Since p2 = f mod 2^-e < 2^-e,
+//
+//      -e <= 60   or   e >= -60 := alpha
+
+constexpr int kAlpha = -60;
+constexpr int kGamma = -32;
+
+struct cached_power // c = f * 2^e ~= 10^k
+{
+    uint64_t f;
+    int e;
+    int k;
+};
+
+/*!
+For a normalized diyfp w = f * 2^e, this function returns a (normalized) cached
+power-of-ten c = f_c * 2^e_c, such that the exponent of the product w * c
+satisfies (Definition 3.2 from [1])
+
+     alpha <= e_c + e + q <= gamma.
+*/
+inline cached_power get_cached_power_for_binary_exponent(int e)
+{
+    // Now
+    //
+    //      alpha <= e_c + e + q <= gamma                                    (1)
+    //      ==> f_c * 2^alpha <= c * 2^e * 2^q
+    //
+    // and since the c's are normalized, 2^(q-1) <= f_c,
+    //
+    //      ==> 2^(q - 1 + alpha) <= c * 2^(e + q)
+    //      ==> 2^(alpha - e - 1) <= c
+    //
+    // If c were an exakt power of ten, i.e. c = 10^k, one may determine k as
+    //
+    //      k = ceil( log_10( 2^(alpha - e - 1) ) )
+    //        = ceil( (alpha - e - 1) * log_10(2) )
+    //
+    // From the paper:
+    // "In theory the result of the procedure could be wrong since c is rounded,
+    //  and the computation itself is approximated [...]. In practice, however,
+    //  this simple function is sufficient."
+    //
+    // For IEEE double precision floating-point numbers converted into
+    // normalized diyfp's w = f * 2^e, with q = 64,
+    //
+    //      e >= -1022      (min IEEE exponent)
+    //           -52        (p - 1)
+    //           -52        (p - 1, possibly normalize denormal IEEE numbers)
+    //           -11        (normalize the diyfp)
+    //         = -1137
+    //
+    // and
+    //
+    //      e <= +1023      (max IEEE exponent)
+    //           -52        (p - 1)
+    //           -11        (normalize the diyfp)
+    //         = 960
+    //
+    // This binary exponent range [-1137,960] results in a decimal exponent
+    // range [-307,324]. One does not need to store a cached power for each
+    // k in this range. For each such k it suffices to find a cached power
+    // such that the exponent of the product lies in [alpha,gamma].
+    // This implies that the difference of the decimal exponents of adjacent
+    // table entries must be less than or equal to
+    //
+    //      floor( (gamma - alpha) * log_10(2) ) = 8.
+    //
+    // (A smaller distance gamma-alpha would require a larger table.)
+
+    // NB:
+    // Actually this function returns c, such that -60 <= e_c + e + 64 <= -34.
+
+    constexpr int kCachedPowersSize = 79;
+    constexpr int kCachedPowersMinDecExp = -300;
+    constexpr int kCachedPowersDecStep = 8;
+
+    static constexpr cached_power kCachedPowers[] =
+    {
+        { 0xAB70FE17C79AC6CA, -1060, -300 },
+        { 0xFF77B1FCBEBCDC4F, -1034, -292 },
+        { 0xBE5691EF416BD60C, -1007, -284 },
+        { 0x8DD01FAD907FFC3C,  -980, -276 },
+        { 0xD3515C2831559A83,  -954, -268 },
+        { 0x9D71AC8FADA6C9B5,  -927, -260 },
+        { 0xEA9C227723EE8BCB,  -901, -252 },
+        { 0xAECC49914078536D,  -874, -244 },
+        { 0x823C12795DB6CE57,  -847, -236 },
+        { 0xC21094364DFB5637,  -821, -228 },
+        { 0x9096EA6F3848984F,  -794, -220 },
+        { 0xD77485CB25823AC7,  -768, -212 },
+        { 0xA086CFCD97BF97F4,  -741, -204 },
+        { 0xEF340A98172AACE5,  -715, -196 },
+        { 0xB23867FB2A35B28E,  -688, -188 },
+        { 0x84C8D4DFD2C63F3B,  -661, -180 },
+        { 0xC5DD44271AD3CDBA,  -635, -172 },
+        { 0x936B9FCEBB25C996,  -608, -164 },
+        { 0xDBAC6C247D62A584,  -582, -156 },
+        { 0xA3AB66580D5FDAF6,  -555, -148 },
+        { 0xF3E2F893DEC3F126,  -529, -140 },
+        { 0xB5B5ADA8AAFF80B8,  -502, -132 },
+        { 0x87625F056C7C4A8B,  -475, -124 },
+        { 0xC9BCFF6034C13053,  -449, -116 },
+        { 0x964E858C91BA2655,  -422, -108 },
+        { 0xDFF9772470297EBD,  -396, -100 },
+        { 0xA6DFBD9FB8E5B88F,  -369,  -92 },
+        { 0xF8A95FCF88747D94,  -343,  -84 },
+        { 0xB94470938FA89BCF,  -316,  -76 },
+        { 0x8A08F0F8BF0F156B,  -289,  -68 },
+        { 0xCDB02555653131B6,  -263,  -60 },
+        { 0x993FE2C6D07B7FAC,  -236,  -52 },
+        { 0xE45C10C42A2B3B06,  -210,  -44 },
+        { 0xAA242499697392D3,  -183,  -36 },
+        { 0xFD87B5F28300CA0E,  -157,  -28 },
+        { 0xBCE5086492111AEB,  -130,  -20 },
+        { 0x8CBCCC096F5088CC,  -103,  -12 },
+        { 0xD1B71758E219652C,   -77,   -4 },
+        { 0x9C40000000000000,   -50,    4 },
+        { 0xE8D4A51000000000,   -24,   12 },
+        { 0xAD78EBC5AC620000,     3,   20 },
+        { 0x813F3978F8940984,    30,   28 },
+        { 0xC097CE7BC90715B3,    56,   36 },
+        { 0x8F7E32CE7BEA5C70,    83,   44 },
+        { 0xD5D238A4ABE98068,   109,   52 },
+        { 0x9F4F2726179A2245,   136,   60 },
+        { 0xED63A231D4C4FB27,   162,   68 },
+        { 0xB0DE65388CC8ADA8,   189,   76 },
+        { 0x83C7088E1AAB65DB,   216,   84 },
+        { 0xC45D1DF942711D9A,   242,   92 },
+        { 0x924D692CA61BE758,   269,  100 },
+        { 0xDA01EE641A708DEA,   295,  108 },
+        { 0xA26DA3999AEF774A,   322,  116 },
+        { 0xF209787BB47D6B85,   348,  124 },
+        { 0xB454E4A179DD1877,   375,  132 },
+        { 0x865B86925B9BC5C2,   402,  140 },
+        { 0xC83553C5C8965D3D,   428,  148 },
+        { 0x952AB45CFA97A0B3,   455,  156 },
+        { 0xDE469FBD99A05FE3,   481,  164 },
+        { 0xA59BC234DB398C25,   508,  172 },
+        { 0xF6C69A72A3989F5C,   534,  180 },
+        { 0xB7DCBF5354E9BECE,   561,  188 },
+        { 0x88FCF317F22241E2,   588,  196 },
+        { 0xCC20CE9BD35C78A5,   614,  204 },
+        { 0x98165AF37B2153DF,   641,  212 },
+        { 0xE2A0B5DC971F303A,   667,  220 },
+        { 0xA8D9D1535CE3B396,   694,  228 },
+        { 0xFB9B7CD9A4A7443C,   720,  236 },
+        { 0xBB764C4CA7A44410,   747,  244 },
+        { 0x8BAB8EEFB6409C1A,   774,  252 },
+        { 0xD01FEF10A657842C,   800,  260 },
+        { 0x9B10A4E5E9913129,   827,  268 },
+        { 0xE7109BFBA19C0C9D,   853,  276 },
+        { 0xAC2820D9623BF429,   880,  284 },
+        { 0x80444B5E7AA7CF85,   907,  292 },
+        { 0xBF21E44003ACDD2D,   933,  300 },
+        { 0x8E679C2F5E44FF8F,   960,  308 },
+        { 0xD433179D9C8CB841,   986,  316 },
+        { 0x9E19DB92B4E31BA9,  1013,  324 },
+    };
+
+    // This computation gives exactly the same results for k as
+    //      k = ceil((kAlpha - e - 1) * 0.30102999566398114)
+    // for |e| <= 1500, but doesn't require floating-point operations.
+    // NB: log_10(2) ~= 78913 / 2^18
+    assert(e >= -1500);
+    assert(e <=  1500);
+    const int f = kAlpha - e - 1;
+    const int k = (f * 78913) / (1 << 18) + static_cast<int>(f > 0);
+
+    const int index = (-kCachedPowersMinDecExp + k + (kCachedPowersDecStep - 1)) / kCachedPowersDecStep;
+    assert(index >= 0);
+    assert(index < kCachedPowersSize);
+    static_cast<void>(kCachedPowersSize); // Fix warning.
+
+    const cached_power cached = kCachedPowers[index];
+    assert(kAlpha <= cached.e + e + 64);
+    assert(kGamma >= cached.e + e + 64);
+
+    return cached;
+}
+
+/*!
+For n != 0, returns k, such that pow10 := 10^(k-1) <= n < 10^k.
+For n == 0, returns 1 and sets pow10 := 1.
+*/
+inline int find_largest_pow10(const uint32_t n, uint32_t& pow10)
+{
+    // LCOV_EXCL_START
+    if (n >= 1000000000)
+    {
+        pow10 = 1000000000;
+        return 10;
+    }
+    // LCOV_EXCL_STOP
+    else if (n >= 100000000)
+    {
+        pow10 = 100000000;
+        return  9;
+    }
+    else if (n >= 10000000)
+    {
+        pow10 = 10000000;
+        return  8;
+    }
+    else if (n >= 1000000)
+    {
+        pow10 = 1000000;
+        return  7;
+    }
+    else if (n >= 100000)
+    {
+        pow10 = 100000;
+        return  6;
+    }
+    else if (n >= 10000)
+    {
+        pow10 = 10000;
+        return  5;
+    }
+    else if (n >= 1000)
+    {
+        pow10 = 1000;
+        return  4;
+    }
+    else if (n >= 100)
+    {
+        pow10 = 100;
+        return  3;
+    }
+    else if (n >= 10)
+    {
+        pow10 = 10;
+        return  2;
+    }
+    else
+    {
+        pow10 = 1;
+        return 1;
+    }
+}
+
+inline void grisu2_round(char* buf, int len, uint64_t dist, uint64_t delta,
+                         uint64_t rest, uint64_t ten_k)
+{
+    assert(len >= 1);
+    assert(dist <= delta);
+    assert(rest <= delta);
+    assert(ten_k > 0);
+
+    //               <--------------------------- delta ---->
+    //                                  <---- dist --------->
+    // --------------[------------------+-------------------]--------------
+    //               M-                 w                   M+
+    //
+    //                                  ten_k
+    //                                <------>
+    //                                       <---- rest ---->
+    // --------------[------------------+----+--------------]--------------
+    //                                  w    V
+    //                                       = buf * 10^k
+    //
+    // ten_k represents a unit-in-the-last-place in the decimal representation
+    // stored in buf.
+    // Decrement buf by ten_k while this takes buf closer to w.
+
+    // The tests are written in this order to avoid overflow in unsigned
+    // integer arithmetic.
+
+    while (rest < dist
+            and delta - rest >= ten_k
+            and (rest + ten_k < dist or dist - rest > rest + ten_k - dist))
+    {
+        assert(buf[len - 1] != '0');
+        buf[len - 1]--;
+        rest += ten_k;
+    }
+}
+
+/*!
+Generates V = buffer * 10^decimal_exponent, such that M- <= V <= M+.
+M- and M+ must be normalized and share the same exponent -60 <= e <= -32.
+*/
+inline void grisu2_digit_gen(char* buffer, int& length, int& decimal_exponent,
+                             diyfp M_minus, diyfp w, diyfp M_plus)
+{
+    static_assert(kAlpha >= -60, "internal error");
+    static_assert(kGamma <= -32, "internal error");
+
+    // Generates the digits (and the exponent) of a decimal floating-point
+    // number V = buffer * 10^decimal_exponent in the range [M-, M+]. The diyfp's
+    // w, M- and M+ share the same exponent e, which satisfies alpha <= e <= gamma.
+    //
+    //               <--------------------------- delta ---->
+    //                                  <---- dist --------->
+    // --------------[------------------+-------------------]--------------
+    //               M-                 w                   M+
+    //
+    // Grisu2 generates the digits of M+ from left to right and stops as soon as
+    // V is in [M-,M+].
+
+    assert(M_plus.e >= kAlpha);
+    assert(M_plus.e <= kGamma);
+
+    uint64_t delta = diyfp::sub(M_plus, M_minus).f; // (significand of (M+ - M-), implicit exponent is e)
+    uint64_t dist  = diyfp::sub(M_plus, w      ).f; // (significand of (M+ - w ), implicit exponent is e)
+
+    // Split M+ = f * 2^e into two parts p1 and p2 (note: e < 0):
+    //
+    //      M+ = f * 2^e
+    //         = ((f div 2^-e) * 2^-e + (f mod 2^-e)) * 2^e
+    //         = ((p1        ) * 2^-e + (p2        )) * 2^e
+    //         = p1 + p2 * 2^e
+
+    const diyfp one(uint64_t{1} << -M_plus.e, M_plus.e);
+
+    auto p1 = static_cast<uint32_t>(M_plus.f >> -one.e); // p1 = f div 2^-e (Since -e >= 32, p1 fits into a 32-bit int.)
+    uint64_t p2 = M_plus.f & (one.f - 1);                    // p2 = f mod 2^-e
+
+    // 1)
+    //
+    // Generate the digits of the integral part p1 = d[n-1]...d[1]d[0]
+
+    assert(p1 > 0);
+
+    uint32_t pow10;
+    const int k = find_largest_pow10(p1, pow10);
+
+    //      10^(k-1) <= p1 < 10^k, pow10 = 10^(k-1)
+    //
+    //      p1 = (p1 div 10^(k-1)) * 10^(k-1) + (p1 mod 10^(k-1))
+    //         = (d[k-1]         ) * 10^(k-1) + (p1 mod 10^(k-1))
+    //
+    //      M+ = p1                                             + p2 * 2^e
+    //         = d[k-1] * 10^(k-1) + (p1 mod 10^(k-1))          + p2 * 2^e
+    //         = d[k-1] * 10^(k-1) + ((p1 mod 10^(k-1)) * 2^-e + p2) * 2^e
+    //         = d[k-1] * 10^(k-1) + (                         rest) * 2^e
+    //
+    // Now generate the digits d[n] of p1 from left to right (n = k-1,...,0)
+    //
+    //      p1 = d[k-1]...d[n] * 10^n + d[n-1]...d[0]
+    //
+    // but stop as soon as
+    //
+    //      rest * 2^e = (d[n-1]...d[0] * 2^-e + p2) * 2^e <= delta * 2^e
+
+    int n = k;
+    while (n > 0)
+    {
+        // Invariants:
+        //      M+ = buffer * 10^n + (p1 + p2 * 2^e)    (buffer = 0 for n = k)
+        //      pow10 = 10^(n-1) <= p1 < 10^n
+        //
+        const uint32_t d = p1 / pow10;  // d = p1 div 10^(n-1)
+        const uint32_t r = p1 % pow10;  // r = p1 mod 10^(n-1)
+        //
+        //      M+ = buffer * 10^n + (d * 10^(n-1) + r) + p2 * 2^e
+        //         = (buffer * 10 + d) * 10^(n-1) + (r + p2 * 2^e)
+        //
+        assert(d <= 9);
+        buffer[length++] = static_cast<char>('0' + d); // buffer := buffer * 10 + d
+        //
+        //      M+ = buffer * 10^(n-1) + (r + p2 * 2^e)
+        //
+        p1 = r;
+        n--;
+        //
+        //      M+ = buffer * 10^n + (p1 + p2 * 2^e)
+        //      pow10 = 10^n
+        //
+
+        // Now check if enough digits have been generated.
+        // Compute
+        //
+        //      p1 + p2 * 2^e = (p1 * 2^-e + p2) * 2^e = rest * 2^e
+        //
+        // Note:
+        // Since rest and delta share the same exponent e, it suffices to
+        // compare the significands.
+        const uint64_t rest = (uint64_t{p1} << -one.e) + p2;
+        if (rest <= delta)
+        {
+            // V = buffer * 10^n, with M- <= V <= M+.
+
+            decimal_exponent += n;
+
+            // We may now just stop. But instead look if the buffer could be
+            // decremented to bring V closer to w.
+            //
+            // pow10 = 10^n is now 1 ulp in the decimal representation V.
+            // The rounding procedure works with diyfp's with an implicit
+            // exponent of e.
+            //
+            //      10^n = (10^n * 2^-e) * 2^e = ulp * 2^e
+            //
+            const uint64_t ten_n = uint64_t{pow10} << -one.e;
+            grisu2_round(buffer, length, dist, delta, rest, ten_n);
+
+            return;
+        }
+
+        pow10 /= 10;
+        //
+        //      pow10 = 10^(n-1) <= p1 < 10^n
+        // Invariants restored.
+    }
+
+    // 2)
+    //
+    // The digits of the integral part have been generated:
+    //
+    //      M+ = d[k-1]...d[1]d[0] + p2 * 2^e
+    //         = buffer            + p2 * 2^e
+    //
+    // Now generate the digits of the fractional part p2 * 2^e.
+    //
+    // Note:
+    // No decimal point is generated: the exponent is adjusted instead.
+    //
+    // p2 actually represents the fraction
+    //
+    //      p2 * 2^e
+    //          = p2 / 2^-e
+    //          = d[-1] / 10^1 + d[-2] / 10^2 + ...
+    //
+    // Now generate the digits d[-m] of p1 from left to right (m = 1,2,...)
+    //
+    //      p2 * 2^e = d[-1]d[-2]...d[-m] * 10^-m
+    //                      + 10^-m * (d[-m-1] / 10^1 + d[-m-2] / 10^2 + ...)
+    //
+    // using
+    //
+    //      10^m * p2 = ((10^m * p2) div 2^-e) * 2^-e + ((10^m * p2) mod 2^-e)
+    //                = (                   d) * 2^-e + (                   r)
+    //
+    // or
+    //      10^m * p2 * 2^e = d + r * 2^e
+    //
+    // i.e.
+    //
+    //      M+ = buffer + p2 * 2^e
+    //         = buffer + 10^-m * (d + r * 2^e)
+    //         = (buffer * 10^m + d) * 10^-m + 10^-m * r * 2^e
+    //
+    // and stop as soon as 10^-m * r * 2^e <= delta * 2^e
+
+    assert(p2 > delta);
+
+    int m = 0;
+    for (;;)
+    {
+        // Invariant:
+        //      M+ = buffer * 10^-m + 10^-m * (d[-m-1] / 10 + d[-m-2] / 10^2 + ...) * 2^e
+        //         = buffer * 10^-m + 10^-m * (p2                                 ) * 2^e
+        //         = buffer * 10^-m + 10^-m * (1/10 * (10 * p2)                   ) * 2^e
+        //         = buffer * 10^-m + 10^-m * (1/10 * ((10*p2 div 2^-e) * 2^-e + (10*p2 mod 2^-e)) * 2^e
+        //
+        assert(p2 <= UINT64_MAX / 10);
+        p2 *= 10;
+        const uint64_t d = p2 >> -one.e;     // d = (10 * p2) div 2^-e
+        const uint64_t r = p2 & (one.f - 1); // r = (10 * p2) mod 2^-e
+        //
+        //      M+ = buffer * 10^-m + 10^-m * (1/10 * (d * 2^-e + r) * 2^e
+        //         = buffer * 10^-m + 10^-m * (1/10 * (d + r * 2^e))
+        //         = (buffer * 10 + d) * 10^(-m-1) + 10^(-m-1) * r * 2^e
+        //
+        assert(d <= 9);
+        buffer[length++] = static_cast<char>('0' + d); // buffer := buffer * 10 + d
+        //
+        //      M+ = buffer * 10^(-m-1) + 10^(-m-1) * r * 2^e
+        //
+        p2 = r;
+        m++;
+        //
+        //      M+ = buffer * 10^-m + 10^-m * p2 * 2^e
+        // Invariant restored.
+
+        // Check if enough digits have been generated.
+        //
+        //      10^-m * p2 * 2^e <= delta * 2^e
+        //              p2 * 2^e <= 10^m * delta * 2^e
+        //                    p2 <= 10^m * delta
+        delta *= 10;
+        dist  *= 10;
+        if (p2 <= delta)
+        {
+            break;
+        }
+    }
+
+    // V = buffer * 10^-m, with M- <= V <= M+.
+
+    decimal_exponent -= m;
+
+    // 1 ulp in the decimal representation is now 10^-m.
+    // Since delta and dist are now scaled by 10^m, we need to do the
+    // same with ulp in order to keep the units in sync.
+    //
+    //      10^m * 10^-m = 1 = 2^-e * 2^e = ten_m * 2^e
+    //
+    const uint64_t ten_m = one.f;
+    grisu2_round(buffer, length, dist, delta, p2, ten_m);
+
+    // By construction this algorithm generates the shortest possible decimal
+    // number (Loitsch, Theorem 6.2) which rounds back to w.
+    // For an input number of precision p, at least
+    //
+    //      N = 1 + ceil(p * log_10(2))
+    //
+    // decimal digits are sufficient to identify all binary floating-point
+    // numbers (Matula, "In-and-Out conversions").
+    // This implies that the algorithm does not produce more than N decimal
+    // digits.
+    //
+    //      N = 17 for p = 53 (IEEE double precision)
+    //      N = 9  for p = 24 (IEEE single precision)
+}
+
+/*!
+v = buf * 10^decimal_exponent
+len is the length of the buffer (number of decimal digits)
+The buffer must be large enough, i.e. >= max_digits10.
+*/
+inline void grisu2(char* buf, int& len, int& decimal_exponent,
+                   diyfp m_minus, diyfp v, diyfp m_plus)
+{
+    assert(m_plus.e == m_minus.e);
+    assert(m_plus.e == v.e);
+
+    //  --------(-----------------------+-----------------------)--------    (A)
+    //          m-                      v                       m+
+    //
+    //  --------------------(-----------+-----------------------)--------    (B)
+    //                      m-          v                       m+
+    //
+    // First scale v (and m- and m+) such that the exponent is in the range
+    // [alpha, gamma].
+
+    const cached_power cached = get_cached_power_for_binary_exponent(m_plus.e);
+
+    const diyfp c_minus_k(cached.f, cached.e); // = c ~= 10^-k
+
+    // The exponent of the products is = v.e + c_minus_k.e + q and is in the range [alpha,gamma]
+    const diyfp w       = diyfp::mul(v,       c_minus_k);
+    const diyfp w_minus = diyfp::mul(m_minus, c_minus_k);
+    const diyfp w_plus  = diyfp::mul(m_plus,  c_minus_k);
+
+    //  ----(---+---)---------------(---+---)---------------(---+---)----
+    //          w-                      w                       w+
+    //          = c*m-                  = c*v                   = c*m+
+    //
+    // diyfp::mul rounds its result and c_minus_k is approximated too. w, w- and
+    // w+ are now off by a small amount.
+    // In fact:
+    //
+    //      w - v * 10^k < 1 ulp
+    //
+    // To account for this inaccuracy, add resp. subtract 1 ulp.
+    //
+    //  --------+---[---------------(---+---)---------------]---+--------
+    //          w-  M-                  w                   M+  w+
+    //
+    // Now any number in [M-, M+] (bounds included) will round to w when input,
+    // regardless of how the input rounding algorithm breaks ties.
+    //
+    // And digit_gen generates the shortest possible such number in [M-, M+].
+    // Note that this does not mean that Grisu2 always generates the shortest
+    // possible number in the interval (m-, m+).
+    const diyfp M_minus(w_minus.f + 1, w_minus.e);
+    const diyfp M_plus (w_plus.f  - 1, w_plus.e );
+
+    decimal_exponent = -cached.k; // = -(-k) = k
+
+    grisu2_digit_gen(buf, len, decimal_exponent, M_minus, w, M_plus);
+}
+
+/*!
+v = buf * 10^decimal_exponent
+len is the length of the buffer (number of decimal digits)
+The buffer must be large enough, i.e. >= max_digits10.
+*/
+template <typename FloatType>
+void grisu2(char* buf, int& len, int& decimal_exponent, FloatType value)
+{
+    static_assert(diyfp::kPrecision >= std::numeric_limits<FloatType>::digits + 3,
+                  "internal error: not enough precision");
+
+    assert(std::isfinite(value));
+    assert(value > 0);
+
+    // If the neighbors (and boundaries) of 'value' are always computed for double-precision
+    // numbers, all float's can be recovered using strtod (and strtof). However, the resulting
+    // decimal representations are not exactly "short".
+    //
+    // The documentation for 'std::to_chars' (https://en.cppreference.com/w/cpp/utility/to_chars)
+    // says "value is converted to a string as if by std::sprintf in the default ("C") locale"
+    // and since sprintf promotes float's to double's, I think this is exactly what 'std::to_chars'
+    // does.
+    // On the other hand, the documentation for 'std::to_chars' requires that "parsing the
+    // representation using the corresponding std::from_chars function recovers value exactly". That
+    // indicates that single precision floating-point numbers should be recovered using
+    // 'std::strtof'.
+    //
+    // NB: If the neighbors are computed for single-precision numbers, there is a single float
+    //     (7.0385307e-26f) which can't be recovered using strtod. The resulting double precision
+    //     value is off by 1 ulp.
+#if 0
+    const boundaries w = compute_boundaries(static_cast<double>(value));
+#else
+    const boundaries w = compute_boundaries(value);
+#endif
+
+    grisu2(buf, len, decimal_exponent, w.minus, w.w, w.plus);
+}
+
+/*!
+@brief appends a decimal representation of e to buf
+@return a pointer to the element following the exponent.
+@pre -1000 < e < 1000
+*/
+inline char* append_exponent(char* buf, int e)
+{
+    assert(e > -1000);
+    assert(e <  1000);
+
+    if (e < 0)
+    {
+        e = -e;
+        *buf++ = '-';
+    }
+    else
+    {
+        *buf++ = '+';
+    }
+
+    auto k = static_cast<uint32_t>(e);
+    if (k < 10)
+    {
+        // Always print at least two digits in the exponent.
+        // This is for compatibility with printf("%g").
+        *buf++ = '0';
+        *buf++ = static_cast<char>('0' + k);
+    }
+    else if (k < 100)
+    {
+        *buf++ = static_cast<char>('0' + k / 10);
+        k %= 10;
+        *buf++ = static_cast<char>('0' + k);
+    }
+    else
+    {
+        *buf++ = static_cast<char>('0' + k / 100);
+        k %= 100;
+        *buf++ = static_cast<char>('0' + k / 10);
+        k %= 10;
+        *buf++ = static_cast<char>('0' + k);
+    }
+
+    return buf;
+}
+
+/*!
+@brief prettify v = buf * 10^decimal_exponent
+
+If v is in the range [10^min_exp, 10^max_exp) it will be printed in fixed-point
+notation. Otherwise it will be printed in exponential notation.
+
+@pre min_exp < 0
+@pre max_exp > 0
+*/
+inline char* format_buffer(char* buf, int len, int decimal_exponent,
+                           int min_exp, int max_exp)
+{
+    assert(min_exp < 0);
+    assert(max_exp > 0);
+
+    const int k = len;
+    const int n = len + decimal_exponent;
+
+    // v = buf * 10^(n-k)
+    // k is the length of the buffer (number of decimal digits)
+    // n is the position of the decimal point relative to the start of the buffer.
+
+    if (k <= n and n <= max_exp)
+    {
+        // digits[000]
+        // len <= max_exp + 2
+
+        std::memset(buf + k, '0', static_cast<size_t>(n - k));
+        // Make it look like a floating-point number (#362, #378)
+        buf[n + 0] = '.';
+        buf[n + 1] = '0';
+        return buf + (n + 2);
+    }
+
+    if (0 < n and n <= max_exp)
+    {
+        // dig.its
+        // len <= max_digits10 + 1
+
+        assert(k > n);
+
+        std::memmove(buf + (n + 1), buf + n, static_cast<size_t>(k - n));
+        buf[n] = '.';
+        return buf + (k + 1);
+    }
+
+    if (min_exp < n and n <= 0)
+    {
+        // 0.[000]digits
+        // len <= 2 + (-min_exp - 1) + max_digits10
+
+        std::memmove(buf + (2 + -n), buf, static_cast<size_t>(k));
+        buf[0] = '0';
+        buf[1] = '.';
+        std::memset(buf + 2, '0', static_cast<size_t>(-n));
+        return buf + (2 + (-n) + k);
+    }
+
+    if (k == 1)
+    {
+        // dE+123
+        // len <= 1 + 5
+
+        buf += 1;
+    }
+    else
+    {
+        // d.igitsE+123
+        // len <= max_digits10 + 1 + 5
+
+        std::memmove(buf + 2, buf + 1, static_cast<size_t>(k - 1));
+        buf[1] = '.';
+        buf += 1 + k;
+    }
+
+    *buf++ = 'e';
+    return append_exponent(buf, n - 1);
+}
+
+} // namespace dtoa_impl
+
+/*!
+@brief generates a decimal representation of the floating-point number value in [first, last).
+
+The format of the resulting decimal representation is similar to printf's %g
+format. Returns an iterator pointing past-the-end of the decimal representation.
+
+@note The input number must be finite, i.e. NaN's and Inf's are not supported.
+@note The buffer must be large enough.
+@note The result is NOT null-terminated.
+*/
+template <typename FloatType>
+char* to_chars(char* first, const char* last, FloatType value)
+{
+    static_cast<void>(last); // maybe unused - fix warning
+    assert(std::isfinite(value));
+
+    // Use signbit(value) instead of (value < 0) since signbit works for -0.
+    if (std::signbit(value))
+    {
+        value = -value;
+        *first++ = '-';
+    }
+
+    if (value == 0) // +-0
+    {
+        *first++ = '0';
+        // Make it look like a floating-point number (#362, #378)
+        *first++ = '.';
+        *first++ = '0';
+        return first;
+    }
+
+    assert(last - first >= std::numeric_limits<FloatType>::max_digits10);
+
+    // Compute v = buffer * 10^decimal_exponent.
+    // The decimal digits are stored in the buffer, which needs to be interpreted
+    // as an unsigned decimal integer.
+    // len is the length of the buffer, i.e. the number of decimal digits.
+    int len = 0;
+    int decimal_exponent = 0;
+    dtoa_impl::grisu2(first, len, decimal_exponent, value);
+
+    assert(len <= std::numeric_limits<FloatType>::max_digits10);
+
+    // Format the buffer like printf("%.*g", prec, value)
+    constexpr int kMinExp = -4;
+    // Use digits10 here to increase compatibility with version 2.
+    constexpr int kMaxExp = std::numeric_limits<FloatType>::digits10;
+
+    assert(last - first >= kMaxExp + 2);
+    assert(last - first >= 2 + (-kMinExp - 1) + std::numeric_limits<FloatType>::max_digits10);
+    assert(last - first >= std::numeric_limits<FloatType>::max_digits10 + 6);
+
+    return dtoa_impl::format_buffer(first, len, decimal_exponent, kMinExp, kMaxExp);
+}
+
+} // namespace detail
+} // namespace nlohmann
+
+// #include <nlohmann/detail/macro_scope.hpp>
+
+// #include <nlohmann/detail/meta/cpp_future.hpp>
+
+// #include <nlohmann/detail/output/binary_writer.hpp>
+
+// #include <nlohmann/detail/output/output_adapters.hpp>
+
+// #include <nlohmann/detail/value_t.hpp>
+
+
+namespace nlohmann
+{
+namespace detail
+{
+///////////////////
+// serialization //
+///////////////////
+
+/// how to treat decoding errors
+enum class error_handler_t
+{
+    strict,  ///< throw a type_error exception in case of invalid UTF-8
+    replace, ///< replace invalid UTF-8 sequences with U+FFFD
+    ignore   ///< ignore invalid UTF-8 sequences
+};
+
+template<typename BasicJsonType>
+class serializer
+{
+    using string_t = typename BasicJsonType::string_t;
+    using number_float_t = typename BasicJsonType::number_float_t;
+    using number_integer_t = typename BasicJsonType::number_integer_t;
+    using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
+    static constexpr uint8_t UTF8_ACCEPT = 0;
+    static constexpr uint8_t UTF8_REJECT = 1;
+
+  public:
+    /*!
+    @param[in] s  output stream to serialize to
+    @param[in] ichar  indentation character to use
+    @param[in] error_handler_  how to react on decoding errors
+    */
+    serializer(output_adapter_t<char> s, const char ichar,
+               error_handler_t error_handler_ = error_handler_t::strict)
+        : o(std::move(s))
+        , loc(std::localeconv())
+        , thousands_sep(loc->thousands_sep == nullptr ? '\0' : * (loc->thousands_sep))
+        , decimal_point(loc->decimal_point == nullptr ? '\0' : * (loc->decimal_point))
+        , indent_char(ichar)
+        , indent_string(512, indent_char)
+        , error_handler(error_handler_)
+    {}
+
+    // delete because of pointer members
+    serializer(const serializer&) = delete;
+    serializer& operator=(const serializer&) = delete;
+    serializer(serializer&&) = delete;
+    serializer& operator=(serializer&&) = delete;
+    ~serializer() = default;
+
+    /*!
+    @brief internal implementation of the serialization function
+
+    This function is called by the public member function dump and organizes
+    the serialization internally. The indentation level is propagated as
+    additional parameter. In case of arrays and objects, the function is
+    called recursively.
+
+    - strings and object keys are escaped using `escape_string()`
+    - integer numbers are converted implicitly via `operator<<`
+    - floating-point numbers are converted to a string using `"%g"` format
+
+    @param[in] val             value to serialize
+    @param[in] pretty_print    whether the output shall be pretty-printed
+    @param[in] indent_step     the indent level
+    @param[in] current_indent  the current indent level (only used internally)
+    */
+    void dump(const BasicJsonType& val, const bool pretty_print,
+              const bool ensure_ascii,
+              const unsigned int indent_step,
+              const unsigned int current_indent = 0)
+    {
+        switch (val.m_type)
+        {
+            case value_t::object:
+            {
+                if (val.m_value.object->empty())
+                {
+                    o->write_characters("{}", 2);
+                    return;
+                }
+
+                if (pretty_print)
+                {
+                    o->write_characters("{\n", 2);
+
+                    // variable to hold indentation for recursive calls
+                    const auto new_indent = current_indent + indent_step;
+                    if (JSON_UNLIKELY(indent_string.size() < new_indent))
+                    {
+                        indent_string.resize(indent_string.size() * 2, ' ');
+                    }
+
+                    // first n-1 elements
+                    auto i = val.m_value.object->cbegin();
+                    for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i)
+                    {
+                        o->write_characters(indent_string.c_str(), new_indent);
+                        o->write_character('\"');
+                        dump_escaped(i->first, ensure_ascii);
+                        o->write_characters("\": ", 3);
+                        dump(i->second, true, ensure_ascii, indent_step, new_indent);
+                        o->write_characters(",\n", 2);
+                    }
+
+                    // last element
+                    assert(i != val.m_value.object->cend());
+                    assert(std::next(i) == val.m_value.object->cend());
+                    o->write_characters(indent_string.c_str(), new_indent);
+                    o->write_character('\"');
+                    dump_escaped(i->first, ensure_ascii);
+                    o->write_characters("\": ", 3);
+                    dump(i->second, true, ensure_ascii, indent_step, new_indent);
+
+                    o->write_character('\n');
+                    o->write_characters(indent_string.c_str(), current_indent);
+                    o->write_character('}');
+                }
+                else
+                {
+                    o->write_character('{');
+
+                    // first n-1 elements
+                    auto i = val.m_value.object->cbegin();
+                    for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i)
+                    {
+                        o->write_character('\"');
+                        dump_escaped(i->first, ensure_ascii);
+                        o->write_characters("\":", 2);
+                        dump(i->second, false, ensure_ascii, indent_step, current_indent);
+                        o->write_character(',');
+                    }
+
+                    // last element
+                    assert(i != val.m_value.object->cend());
+                    assert(std::next(i) == val.m_value.object->cend());
+                    o->write_character('\"');
+                    dump_escaped(i->first, ensure_ascii);
+                    o->write_characters("\":", 2);
+                    dump(i->second, false, ensure_ascii, indent_step, current_indent);
+
+                    o->write_character('}');
+                }
+
+                return;
+            }
+
+            case value_t::array:
+            {
+                if (val.m_value.array->empty())
+                {
+                    o->write_characters("[]", 2);
+                    return;
+                }
+
+                if (pretty_print)
+                {
+                    o->write_characters("[\n", 2);
+
+                    // variable to hold indentation for recursive calls
+                    const auto new_indent = current_indent + indent_step;
+                    if (JSON_UNLIKELY(indent_string.size() < new_indent))
+                    {
+                        indent_string.resize(indent_string.size() * 2, ' ');
+                    }
+
+                    // first n-1 elements
+                    for (auto i = val.m_value.array->cbegin();
+                            i != val.m_value.array->cend() - 1; ++i)
+                    {
+                        o->write_characters(indent_string.c_str(), new_indent);
+                        dump(*i, true, ensure_ascii, indent_step, new_indent);
+                        o->write_characters(",\n", 2);
+                    }
+
+                    // last element
+                    assert(not val.m_value.array->empty());
+                    o->write_characters(indent_string.c_str(), new_indent);
+                    dump(val.m_value.array->back(), true, ensure_ascii, indent_step, new_indent);
+
+                    o->write_character('\n');
+                    o->write_characters(indent_string.c_str(), current_indent);
+                    o->write_character(']');
+                }
+                else
+                {
+                    o->write_character('[');
+
+                    // first n-1 elements
+                    for (auto i = val.m_value.array->cbegin();
+                            i != val.m_value.array->cend() - 1; ++i)
+                    {
+                        dump(*i, false, ensure_ascii, indent_step, current_indent);
+                        o->write_character(',');
+                    }
+
+                    // last element
+                    assert(not val.m_value.array->empty());
+                    dump(val.m_value.array->back(), false, ensure_ascii, indent_step, current_indent);
+
+                    o->write_character(']');
+                }
+
+                return;
+            }
+
+            case value_t::string:
+            {
+                o->write_character('\"');
+                dump_escaped(*val.m_value.string, ensure_ascii);
+                o->write_character('\"');
+                return;
+            }
+
+            case value_t::boolean:
+            {
+                if (val.m_value.boolean)
+                {
+                    o->write_characters("true", 4);
+                }
+                else
+                {
+                    o->write_characters("false", 5);
+                }
+                return;
+            }
+
+            case value_t::number_integer:
+            {
+                dump_integer(val.m_value.number_integer);
+                return;
+            }
+
+            case value_t::number_unsigned:
+            {
+                dump_integer(val.m_value.number_unsigned);
+                return;
+            }
+
+            case value_t::number_float:
+            {
+                dump_float(val.m_value.number_float);
+                return;
+            }
+
+            case value_t::discarded:
+            {
+                o->write_characters("<discarded>", 11);
+                return;
+            }
+
+            case value_t::null:
+            {
+                o->write_characters("null", 4);
+                return;
+            }
+        }
+    }
+
+  private:
+    /*!
+    @brief dump escaped string
+
+    Escape a string by replacing certain special characters by a sequence of an
+    escape character (backslash) and another character and other control
+    characters by a sequence of "\u" followed by a four-digit hex
+    representation. The escaped string is written to output stream @a o.
+
+    @param[in] s  the string to escape
+    @param[in] ensure_ascii  whether to escape non-ASCII characters with
+                             \uXXXX sequences
+
+    @complexity Linear in the length of string @a s.
+    */
+    void dump_escaped(const string_t& s, const bool ensure_ascii)
+    {
+        uint32_t codepoint;
+        uint8_t state = UTF8_ACCEPT;
+        std::size_t bytes = 0;  // number of bytes written to string_buffer
+
+        // number of bytes written at the point of the last valid byte
+        std::size_t bytes_after_last_accept = 0;
+        std::size_t undumped_chars = 0;
+
+        for (std::size_t i = 0; i < s.size(); ++i)
+        {
+            const auto byte = static_cast<uint8_t>(s[i]);
+
+            switch (decode(state, codepoint, byte))
+            {
+                case UTF8_ACCEPT:  // decode found a new code point
+                {
+                    switch (codepoint)
+                    {
+                        case 0x08: // backspace
+                        {
+                            string_buffer[bytes++] = '\\';
+                            string_buffer[bytes++] = 'b';
+                            break;
+                        }
+
+                        case 0x09: // horizontal tab
+                        {
+                            string_buffer[bytes++] = '\\';
+                            string_buffer[bytes++] = 't';
+                            break;
+                        }
+
+                        case 0x0A: // newline
+                        {
+                            string_buffer[bytes++] = '\\';
+                            string_buffer[bytes++] = 'n';
+                            break;
+                        }
+
+                        case 0x0C: // formfeed
+                        {
+                            string_buffer[bytes++] = '\\';
+                            string_buffer[bytes++] = 'f';
+                            break;
+                        }
+
+                        case 0x0D: // carriage return
+                        {
+                            string_buffer[bytes++] = '\\';
+                            string_buffer[bytes++] = 'r';
+                            break;
+                        }
+
+                        case 0x22: // quotation mark
+                        {
+                            string_buffer[bytes++] = '\\';
+                            string_buffer[bytes++] = '\"';
+                            break;
+                        }
+
+                        case 0x5C: // reverse solidus
+                        {
+                            string_buffer[bytes++] = '\\';
+                            string_buffer[bytes++] = '\\';
+                            break;
+                        }
+
+                        default:
+                        {
+                            // escape control characters (0x00..0x1F) or, if
+                            // ensure_ascii parameter is used, non-ASCII characters
+                            if ((codepoint <= 0x1F) or (ensure_ascii and (codepoint >= 0x7F)))
+                            {
+                                if (codepoint <= 0xFFFF)
+                                {
+                                    (std::snprintf)(string_buffer.data() + bytes, 7, "\\u%04x",
+                                                    static_cast<uint16_t>(codepoint));
+                                    bytes += 6;
+                                }
+                                else
+                                {
+                                    (std::snprintf)(string_buffer.data() + bytes, 13, "\\u%04x\\u%04x",
+                                                    static_cast<uint16_t>(0xD7C0 + (codepoint >> 10)),
+                                                    static_cast<uint16_t>(0xDC00 + (codepoint & 0x3FF)));
+                                    bytes += 12;
+                                }
+                            }
+                            else
+                            {
+                                // copy byte to buffer (all previous bytes
+                                // been copied have in default case above)
+                                string_buffer[bytes++] = s[i];
+                            }
+                            break;
+                        }
+                    }
+
+                    // write buffer and reset index; there must be 13 bytes
+                    // left, as this is the maximal number of bytes to be
+                    // written ("\uxxxx\uxxxx\0") for one code point
+                    if (string_buffer.size() - bytes < 13)
+                    {
+                        o->write_characters(string_buffer.data(), bytes);
+                        bytes = 0;
+                    }
+
+                    // remember the byte position of this accept
+                    bytes_after_last_accept = bytes;
+                    undumped_chars = 0;
+                    break;
+                }
+
+                case UTF8_REJECT:  // decode found invalid UTF-8 byte
+                {
+                    switch (error_handler)
+                    {
+                        case error_handler_t::strict:
+                        {
+                            std::string sn(3, '\0');
+                            (std::snprintf)(&sn[0], sn.size(), "%.2X", byte);
+                            JSON_THROW(type_error::create(316, "invalid UTF-8 byte at index " + std::to_string(i) + ": 0x" + sn));
+                        }
+
+                        case error_handler_t::ignore:
+                        case error_handler_t::replace:
+                        {
+                            // in case we saw this character the first time, we
+                            // would like to read it again, because the byte
+                            // may be OK for itself, but just not OK for the
+                            // previous sequence
+                            if (undumped_chars > 0)
+                            {
+                                --i;
+                            }
+
+                            // reset length buffer to the last accepted index;
+                            // thus removing/ignoring the invalid characters
+                            bytes = bytes_after_last_accept;
+
+                            if (error_handler == error_handler_t::replace)
+                            {
+                                // add a replacement character
+                                if (ensure_ascii)
+                                {
+                                    string_buffer[bytes++] = '\\';
+                                    string_buffer[bytes++] = 'u';
+                                    string_buffer[bytes++] = 'f';
+                                    string_buffer[bytes++] = 'f';
+                                    string_buffer[bytes++] = 'f';
+                                    string_buffer[bytes++] = 'd';
+                                }
+                                else
+                                {
+                                    string_buffer[bytes++] = detail::binary_writer<BasicJsonType, char>::to_char_type('\xEF');
+                                    string_buffer[bytes++] = detail::binary_writer<BasicJsonType, char>::to_char_type('\xBF');
+                                    string_buffer[bytes++] = detail::binary_writer<BasicJsonType, char>::to_char_type('\xBD');
+                                }
+                                bytes_after_last_accept = bytes;
+                            }
+
+                            undumped_chars = 0;
+
+                            // continue processing the string
+                            state = UTF8_ACCEPT;
+                            break;
+                        }
+                    }
+                    break;
+                }
+
+                default:  // decode found yet incomplete multi-byte code point
+                {
+                    if (not ensure_ascii)
+                    {
+                        // code point will not be escaped - copy byte to buffer
+                        string_buffer[bytes++] = s[i];
+                    }
+                    ++undumped_chars;
+                    break;
+                }
+            }
+        }
+
+        // we finished processing the string
+        if (JSON_LIKELY(state == UTF8_ACCEPT))
+        {
+            // write buffer
+            if (bytes > 0)
+            {
+                o->write_characters(string_buffer.data(), bytes);
+            }
+        }
+        else
+        {
+            // we finish reading, but do not accept: string was incomplete
+            switch (error_handler)
+            {
+                case error_handler_t::strict:
+                {
+                    std::string sn(3, '\0');
+                    (std::snprintf)(&sn[0], sn.size(), "%.2X", static_cast<uint8_t>(s.back()));
+                    JSON_THROW(type_error::create(316, "incomplete UTF-8 string; last byte: 0x" + sn));
+                }
+
+                case error_handler_t::ignore:
+                {
+                    // write all accepted bytes
+                    o->write_characters(string_buffer.data(), bytes_after_last_accept);
+                    break;
+                }
+
+                case error_handler_t::replace:
+                {
+                    // write all accepted bytes
+                    o->write_characters(string_buffer.data(), bytes_after_last_accept);
+                    // add a replacement character
+                    if (ensure_ascii)
+                    {
+                        o->write_characters("\\ufffd", 6);
+                    }
+                    else
+                    {
+                        o->write_characters("\xEF\xBF\xBD", 3);
+                    }
+                    break;
+                }
+            }
+        }
+    }
+
+    /*!
+    @brief dump an integer
+
+    Dump a given integer to output stream @a o. Works internally with
+    @a number_buffer.
+
+    @param[in] x  integer number (signed or unsigned) to dump
+    @tparam NumberType either @a number_integer_t or @a number_unsigned_t
+    */
+    template<typename NumberType, detail::enable_if_t<
+                 std::is_same<NumberType, number_unsigned_t>::value or
+                 std::is_same<NumberType, number_integer_t>::value,
+                 int> = 0>
+    void dump_integer(NumberType x)
+    {
+        // special case for "0"
+        if (x == 0)
+        {
+            o->write_character('0');
+            return;
+        }
+
+        const bool is_negative = std::is_same<NumberType, number_integer_t>::value and not (x >= 0);  // see issue #755
+        std::size_t i = 0;
+
+        while (x != 0)
+        {
+            // spare 1 byte for '\0'
+            assert(i < number_buffer.size() - 1);
+
+            const auto digit = std::labs(static_cast<long>(x % 10));
+            number_buffer[i++] = static_cast<char>('0' + digit);
+            x /= 10;
+        }
+
+        if (is_negative)
+        {
+            // make sure there is capacity for the '-'
+            assert(i < number_buffer.size() - 2);
+            number_buffer[i++] = '-';
+        }
+
+        std::reverse(number_buffer.begin(), number_buffer.begin() + i);
+        o->write_characters(number_buffer.data(), i);
+    }
+
+    /*!
+    @brief dump a floating-point number
+
+    Dump a given floating-point number to output stream @a o. Works internally
+    with @a number_buffer.
+
+    @param[in] x  floating-point number to dump
+    */
+    void dump_float(number_float_t x)
+    {
+        // NaN / inf
+        if (not std::isfinite(x))
+        {
+            o->write_characters("null", 4);
+            return;
+        }
+
+        // If number_float_t is an IEEE-754 single or double precision number,
+        // use the Grisu2 algorithm to produce short numbers which are
+        // guaranteed to round-trip, using strtof and strtod, resp.
+        //
+        // NB: The test below works if <long double> == <double>.
+        static constexpr bool is_ieee_single_or_double
+            = (std::numeric_limits<number_float_t>::is_iec559 and std::numeric_limits<number_float_t>::digits == 24 and std::numeric_limits<number_float_t>::max_exponent == 128) or
+              (std::numeric_limits<number_float_t>::is_iec559 and std::numeric_limits<number_float_t>::digits == 53 and std::numeric_limits<number_float_t>::max_exponent == 1024);
+
+        dump_float(x, std::integral_constant<bool, is_ieee_single_or_double>());
+    }
+
+    void dump_float(number_float_t x, std::true_type /*is_ieee_single_or_double*/)
+    {
+        char* begin = number_buffer.data();
+        char* end = ::nlohmann::detail::to_chars(begin, begin + number_buffer.size(), x);
+
+        o->write_characters(begin, static_cast<size_t>(end - begin));
+    }
+
+    void dump_float(number_float_t x, std::false_type /*is_ieee_single_or_double*/)
+    {
+        // get number of digits for a float -> text -> float round-trip
+        static constexpr auto d = std::numeric_limits<number_float_t>::max_digits10;
+
+        // the actual conversion
+        std::ptrdiff_t len = (std::snprintf)(number_buffer.data(), number_buffer.size(), "%.*g", d, x);
+
+        // negative value indicates an error
+        assert(len > 0);
+        // check if buffer was large enough
+        assert(static_cast<std::size_t>(len) < number_buffer.size());
+
+        // erase thousands separator
+        if (thousands_sep != '\0')
+        {
+            const auto end = std::remove(number_buffer.begin(),
+                                         number_buffer.begin() + len, thousands_sep);
+            std::fill(end, number_buffer.end(), '\0');
+            assert((end - number_buffer.begin()) <= len);
+            len = (end - number_buffer.begin());
+        }
+
+        // convert decimal point to '.'
+        if (decimal_point != '\0' and decimal_point != '.')
+        {
+            const auto dec_pos = std::find(number_buffer.begin(), number_buffer.end(), decimal_point);
+            if (dec_pos != number_buffer.end())
+            {
+                *dec_pos = '.';
+            }
+        }
+
+        o->write_characters(number_buffer.data(), static_cast<std::size_t>(len));
+
+        // determine if need to append ".0"
+        const bool value_is_int_like =
+            std::none_of(number_buffer.begin(), number_buffer.begin() + len + 1,
+                         [](char c)
+        {
+            return (c == '.' or c == 'e');
+        });
+
+        if (value_is_int_like)
+        {
+            o->write_characters(".0", 2);
+        }
+    }
+
+    /*!
+    @brief check whether a string is UTF-8 encoded
+
+    The function checks each byte of a string whether it is UTF-8 encoded. The
+    result of the check is stored in the @a state parameter. The function must
+    be called initially with state 0 (accept). State 1 means the string must
+    be rejected, because the current byte is not allowed. If the string is
+    completely processed, but the state is non-zero, the string ended
+    prematurely; that is, the last byte indicated more bytes should have
+    followed.
+
+    @param[in,out] state  the state of the decoding
+    @param[in,out] codep  codepoint (valid only if resulting state is UTF8_ACCEPT)
+    @param[in] byte       next byte to decode
+    @return               new state
+
+    @note The function has been edited: a std::array is used.
+
+    @copyright Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>
+    @sa http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
+    */
+    static uint8_t decode(uint8_t& state, uint32_t& codep, const uint8_t byte) noexcept
+    {
+        static const std::array<uint8_t, 400> utf8d =
+        {
+            {
+                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1F
+                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3F
+                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5F
+                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7F
+                1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9F
+                7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // A0..BF
+                8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C0..DF
+                0xA, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // E0..EF
+                0xB, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // F0..FF
+                0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0
+                1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2
+                1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4
+                1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6
+                1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // s7..s8
+            }
+        };
+
+        const uint8_t type = utf8d[byte];
+
+        codep = (state != UTF8_ACCEPT)
+                ? (byte & 0x3fu) | (codep << 6)
+                : static_cast<uint32_t>(0xff >> type) & (byte);
+
+        state = utf8d[256u + state * 16u + type];
+        return state;
+    }
+
+  private:
+    /// the output of the serializer
+    output_adapter_t<char> o = nullptr;
+
+    /// a (hopefully) large enough character buffer
+    std::array<char, 64> number_buffer{{}};
+
+    /// the locale
+    const std::lconv* loc = nullptr;
+    /// the locale's thousand separator character
+    const char thousands_sep = '\0';
+    /// the locale's decimal point character
+    const char decimal_point = '\0';
+
+    /// string buffer
+    std::array<char, 512> string_buffer{{}};
+
+    /// the indentation character
+    const char indent_char;
+    /// the indentation string
+    string_t indent_string;
+
+    /// error_handler how to react on decoding errors
+    const error_handler_t error_handler;
+};
+}  // namespace detail
+}  // namespace nlohmann
+
+// #include <nlohmann/detail/json_ref.hpp>
+
+
+#include <initializer_list>
+#include <utility>
+
+// #include <nlohmann/detail/meta/type_traits.hpp>
+
+
+namespace nlohmann
+{
+namespace detail
+{
+template<typename BasicJsonType>
+class json_ref
+{
+  public:
+    using value_type = BasicJsonType;
+
+    json_ref(value_type&& value)
+        : owned_value(std::move(value)), value_ref(&owned_value), is_rvalue(true)
+    {}
+
+    json_ref(const value_type& value)
+        : value_ref(const_cast<value_type*>(&value)), is_rvalue(false)
+    {}
+
+    json_ref(std::initializer_list<json_ref> init)
+        : owned_value(init), value_ref(&owned_value), is_rvalue(true)
+    {}
+
+    template <
+        class... Args,
+        enable_if_t<std::is_constructible<value_type, Args...>::value, int> = 0 >
+    json_ref(Args && ... args)
+        : owned_value(std::forward<Args>(args)...), value_ref(&owned_value),
+          is_rvalue(true) {}
+
+    // class should be movable only
+    json_ref(json_ref&&) = default;
+    json_ref(const json_ref&) = delete;
+    json_ref& operator=(const json_ref&) = delete;
+    json_ref& operator=(json_ref&&) = delete;
+    ~json_ref() = default;
+
+    value_type moved_or_copied() const
+    {
+        if (is_rvalue)
+        {
+            return std::move(*value_ref);
+        }
+        return *value_ref;
+    }
+
+    value_type const& operator*() const
+    {
+        return *static_cast<value_type const*>(value_ref);
+    }
+
+    value_type const* operator->() const
+    {
+        return static_cast<value_type const*>(value_ref);
+    }
+
+  private:
+    mutable value_type owned_value = nullptr;
+    value_type* value_ref = nullptr;
+    const bool is_rvalue;
+};
+}  // namespace detail
+}  // namespace nlohmann
+
+// #include <nlohmann/detail/json_pointer.hpp>
+
+
+#include <cassert> // assert
+#include <numeric> // accumulate
+#include <string> // string
+#include <vector> // vector
+
+// #include <nlohmann/detail/macro_scope.hpp>
+
+// #include <nlohmann/detail/exceptions.hpp>
+
+// #include <nlohmann/detail/value_t.hpp>
+
+
+namespace nlohmann
+{
+template<typename BasicJsonType>
+class json_pointer
+{
+    // allow basic_json to access private members
+    NLOHMANN_BASIC_JSON_TPL_DECLARATION
+    friend class basic_json;
+
+  public:
+    /*!
+    @brief create JSON pointer
+
+    Create a JSON pointer according to the syntax described in
+    [Section 3 of RFC6901](https://tools.ietf.org/html/rfc6901#section-3).
+
+    @param[in] s  string representing the JSON pointer; if omitted, the empty
+                  string is assumed which references the whole JSON value
+
+    @throw parse_error.107 if the given JSON pointer @a s is nonempty and does
+                           not begin with a slash (`/`); see example below
+
+    @throw parse_error.108 if a tilde (`~`) in the given JSON pointer @a s is
+    not followed by `0` (representing `~`) or `1` (representing `/`); see
+    example below
+
+    @liveexample{The example shows the construction several valid JSON pointers
+    as well as the exceptional behavior.,json_pointer}
+
+    @since version 2.0.0
+    */
+    explicit json_pointer(const std::string& s = "")
+        : reference_tokens(split(s))
+    {}
+
+    /*!
+    @brief return a string representation of the JSON pointer
+
+    @invariant For each JSON pointer `ptr`, it holds:
+    @code {.cpp}
+    ptr == json_pointer(ptr.to_string());
+    @endcode
+
+    @return a string representation of the JSON pointer
+
+    @liveexample{The example shows the result of `to_string`.,
+    json_pointer__to_string}
+
+    @since version 2.0.0
+    */
+    std::string to_string() const
+    {
+        return std::accumulate(reference_tokens.begin(), reference_tokens.end(),
+                               std::string{},
+                               [](const std::string & a, const std::string & b)
+        {
+            return a + "/" + escape(b);
+        });
+    }
+
+    /// @copydoc to_string()
+    operator std::string() const
+    {
+        return to_string();
+    }
+
+    /*!
+    @param[in] s  reference token to be converted into an array index
+
+    @return integer representation of @a s
+
+    @throw out_of_range.404 if string @a s could not be converted to an integer
+    */
+    static int array_index(const std::string& s)
+    {
+        std::size_t processed_chars = 0;
+        const int res = std::stoi(s, &processed_chars);
+
+        // check if the string was completely read
+        if (JSON_UNLIKELY(processed_chars != s.size()))
+        {
+            JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + s + "'"));
+        }
+
+        return res;
+    }
+
+  private:
+    /*!
+    @brief remove and return last reference pointer
+    @throw out_of_range.405 if JSON pointer has no parent
+    */
+    std::string pop_back()
+    {
+        if (JSON_UNLIKELY(is_root()))
+        {
+            JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent"));
+        }
+
+        auto last = reference_tokens.back();
+        reference_tokens.pop_back();
+        return last;
+    }
+
+    /// return whether pointer points to the root document
+    bool is_root() const noexcept
+    {
+        return reference_tokens.empty();
+    }
+
+    json_pointer top() const
+    {
+        if (JSON_UNLIKELY(is_root()))
+        {
+            JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent"));
+        }
+
+        json_pointer result = *this;
+        result.reference_tokens = {reference_tokens[0]};
+        return result;
+    }
+
+    /*!
+    @brief create and return a reference to the pointed to value
+
+    @complexity Linear in the number of reference tokens.
+
+    @throw parse_error.109 if array index is not a number
+    @throw type_error.313 if value cannot be unflattened
+    */
+    BasicJsonType& get_and_create(BasicJsonType& j) const
+    {
+        using size_type = typename BasicJsonType::size_type;
+        auto result = &j;
+
+        // in case no reference tokens exist, return a reference to the JSON value
+        // j which will be overwritten by a primitive value
+        for (const auto& reference_token : reference_tokens)
+        {
+            switch (result->m_type)
+            {
+                case detail::value_t::null:
+                {
+                    if (reference_token == "0")
+                    {
+                        // start a new array if reference token is 0
+                        result = &result->operator[](0);
+                    }
+                    else
+                    {
+                        // start a new object otherwise
+                        result = &result->operator[](reference_token);
+                    }
+                    break;
+                }
+
+                case detail::value_t::object:
+                {
+                    // create an entry in the object
+                    result = &result->operator[](reference_token);
+                    break;
+                }
+
+                case detail::value_t::array:
+                {
+                    // create an entry in the array
+                    JSON_TRY
+                    {
+                        result = &result->operator[](static_cast<size_type>(array_index(reference_token)));
+                    }
+                    JSON_CATCH(std::invalid_argument&)
+                    {
+                        JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number"));
+                    }
+                    break;
+                }
+
+                /*
+                The following code is only reached if there exists a reference
+                token _and_ the current value is primitive. In this case, we have
+                an error situation, because primitive values may only occur as
+                single value; that is, with an empty list of reference tokens.
+                */
+                default:
+                    JSON_THROW(detail::type_error::create(313, "invalid value to unflatten"));
+            }
+        }
+
+        return *result;
+    }
+
+    /*!
+    @brief return a reference to the pointed to value
+
+    @note This version does not throw if a value is not present, but tries to
+          create nested values instead. For instance, calling this function
+          with pointer `"/this/that"` on a null value is equivalent to calling
+          `operator[]("this").operator[]("that")` on that value, effectively
+          changing the null value to an object.
+
+    @param[in] ptr  a JSON value
+
+    @return reference to the JSON value pointed to by the JSON pointer
+
+    @complexity Linear in the length of the JSON pointer.
+
+    @throw parse_error.106   if an array index begins with '0'
+    @throw parse_error.109   if an array index was not a number
+    @throw out_of_range.404  if the JSON pointer can not be resolved
+    */
+    BasicJsonType& get_unchecked(BasicJsonType* ptr) const
+    {
+        using size_type = typename BasicJsonType::size_type;
+        for (const auto& reference_token : reference_tokens)
+        {
+            // convert null values to arrays or objects before continuing
+            if (ptr->m_type == detail::value_t::null)
+            {
+                // check if reference token is a number
+                const bool nums =
+                    std::all_of(reference_token.begin(), reference_token.end(),
+                                [](const char x)
+                {
+                    return (x >= '0' and x <= '9');
+                });
+
+                // change value to array for numbers or "-" or to object otherwise
+                *ptr = (nums or reference_token == "-")
+                       ? detail::value_t::array
+                       : detail::value_t::object;
+            }
+
+            switch (ptr->m_type)
+            {
+                case detail::value_t::object:
+                {
+                    // use unchecked object access
+                    ptr = &ptr->operator[](reference_token);
+                    break;
+                }
+
+                case detail::value_t::array:
+                {
+                    // error condition (cf. RFC 6901, Sect. 4)
+                    if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0'))
+                    {
+                        JSON_THROW(detail::parse_error::create(106, 0,
+                                                               "array index '" + reference_token +
+                                                               "' must not begin with '0'"));
+                    }
+
+                    if (reference_token == "-")
+                    {
+                        // explicitly treat "-" as index beyond the end
+                        ptr = &ptr->operator[](ptr->m_value.array->size());
+                    }
+                    else
+                    {
+                        // convert array index to number; unchecked access
+                        JSON_TRY
+                        {
+                            ptr = &ptr->operator[](
+                                static_cast<size_type>(array_index(reference_token)));
+                        }
+                        JSON_CATCH(std::invalid_argument&)
+                        {
+                            JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number"));
+                        }
+                    }
+                    break;
+                }
+
+                default:
+                    JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'"));
+            }
+        }
+
+        return *ptr;
+    }
+
+    /*!
+    @throw parse_error.106   if an array index begins with '0'
+    @throw parse_error.109   if an array index was not a number
+    @throw out_of_range.402  if the array index '-' is used
+    @throw out_of_range.404  if the JSON pointer can not be resolved
+    */
+    BasicJsonType& get_checked(BasicJsonType* ptr) const
+    {
+        using size_type = typename BasicJsonType::size_type;
+        for (const auto& reference_token : reference_tokens)
+        {
+            switch (ptr->m_type)
+            {
+                case detail::value_t::object:
+                {
+                    // note: at performs range check
+                    ptr = &ptr->at(reference_token);
+                    break;
+                }
+
+                case detail::value_t::array:
+                {
+                    if (JSON_UNLIKELY(reference_token == "-"))
+                    {
+                        // "-" always fails the range check
+                        JSON_THROW(detail::out_of_range::create(402,
+                                                                "array index '-' (" + std::to_string(ptr->m_value.array->size()) +
+                                                                ") is out of range"));
+                    }
+
+                    // error condition (cf. RFC 6901, Sect. 4)
+                    if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0'))
+                    {
+                        JSON_THROW(detail::parse_error::create(106, 0,
+                                                               "array index '" + reference_token +
+                                                               "' must not begin with '0'"));
+                    }
+
+                    // note: at performs range check
+                    JSON_TRY
+                    {
+                        ptr = &ptr->at(static_cast<size_type>(array_index(reference_token)));
+                    }
+                    JSON_CATCH(std::invalid_argument&)
+                    {
+                        JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number"));
+                    }
+                    break;
+                }
+
+                default:
+                    JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'"));
+            }
+        }
+
+        return *ptr;
+    }
+
+    /*!
+    @brief return a const reference to the pointed to value
+
+    @param[in] ptr  a JSON value
+
+    @return const reference to the JSON value pointed to by the JSON
+    pointer
+
+    @throw parse_error.106   if an array index begins with '0'
+    @throw parse_error.109   if an array index was not a number
+    @throw out_of_range.402  if the array index '-' is used
+    @throw out_of_range.404  if the JSON pointer can not be resolved
+    */
+    const BasicJsonType& get_unchecked(const BasicJsonType* ptr) const
+    {
+        using size_type = typename BasicJsonType::size_type;
+        for (const auto& reference_token : reference_tokens)
+        {
+            switch (ptr->m_type)
+            {
+                case detail::value_t::object:
+                {
+                    // use unchecked object access
+                    ptr = &ptr->operator[](reference_token);
+                    break;
+                }
+
+                case detail::value_t::array:
+                {
+                    if (JSON_UNLIKELY(reference_token == "-"))
+                    {
+                        // "-" cannot be used for const access
+                        JSON_THROW(detail::out_of_range::create(402,
+                                                                "array index '-' (" + std::to_string(ptr->m_value.array->size()) +
+                                                                ") is out of range"));
+                    }
+
+                    // error condition (cf. RFC 6901, Sect. 4)
+                    if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0'))
+                    {
+                        JSON_THROW(detail::parse_error::create(106, 0,
+                                                               "array index '" + reference_token +
+                                                               "' must not begin with '0'"));
+                    }
+
+                    // use unchecked array access
+                    JSON_TRY
+                    {
+                        ptr = &ptr->operator[](
+                            static_cast<size_type>(array_index(reference_token)));
+                    }
+                    JSON_CATCH(std::invalid_argument&)
+                    {
+                        JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number"));
+                    }
+                    break;
+                }
+
+                default:
+                    JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'"));
+            }
+        }
+
+        return *ptr;
+    }
+
+    /*!
+    @throw parse_error.106   if an array index begins with '0'
+    @throw parse_error.109   if an array index was not a number
+    @throw out_of_range.402  if the array index '-' is used
+    @throw out_of_range.404  if the JSON pointer can not be resolved
+    */
+    const BasicJsonType& get_checked(const BasicJsonType* ptr) const
+    {
+        using size_type = typename BasicJsonType::size_type;
+        for (const auto& reference_token : reference_tokens)
+        {
+            switch (ptr->m_type)
+            {
+                case detail::value_t::object:
+                {
+                    // note: at performs range check
+                    ptr = &ptr->at(reference_token);
+                    break;
+                }
+
+                case detail::value_t::array:
+                {
+                    if (JSON_UNLIKELY(reference_token == "-"))
+                    {
+                        // "-" always fails the range check
+                        JSON_THROW(detail::out_of_range::create(402,
+                                                                "array index '-' (" + std::to_string(ptr->m_value.array->size()) +
+                                                                ") is out of range"));
+                    }
+
+                    // error condition (cf. RFC 6901, Sect. 4)
+                    if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0'))
+                    {
+                        JSON_THROW(detail::parse_error::create(106, 0,
+                                                               "array index '" + reference_token +
+                                                               "' must not begin with '0'"));
+                    }
+
+                    // note: at performs range check
+                    JSON_TRY
+                    {
+                        ptr = &ptr->at(static_cast<size_type>(array_index(reference_token)));
+                    }
+                    JSON_CATCH(std::invalid_argument&)
+                    {
+                        JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number"));
+                    }
+                    break;
+                }
+
+                default:
+                    JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'"));
+            }
+        }
+
+        return *ptr;
+    }
+
+    /*!
+    @brief split the string input to reference tokens
+
+    @note This function is only called by the json_pointer constructor.
+          All exceptions below are documented there.
+
+    @throw parse_error.107  if the pointer is not empty or begins with '/'
+    @throw parse_error.108  if character '~' is not followed by '0' or '1'
+    */
+    static std::vector<std::string> split(const std::string& reference_string)
+    {
+        std::vector<std::string> result;
+
+        // special case: empty reference string -> no reference tokens
+        if (reference_string.empty())
+        {
+            return result;
+        }
+
+        // check if nonempty reference string begins with slash
+        if (JSON_UNLIKELY(reference_string[0] != '/'))
+        {
+            JSON_THROW(detail::parse_error::create(107, 1,
+                                                   "JSON pointer must be empty or begin with '/' - was: '" +
+                                                   reference_string + "'"));
+        }
+
+        // extract the reference tokens:
+        // - slash: position of the last read slash (or end of string)
+        // - start: position after the previous slash
+        for (
+            // search for the first slash after the first character
+            std::size_t slash = reference_string.find_first_of('/', 1),
+            // set the beginning of the first reference token
+            start = 1;
+            // we can stop if start == 0 (if slash == std::string::npos)
+            start != 0;
+            // set the beginning of the next reference token
+            // (will eventually be 0 if slash == std::string::npos)
+            start = (slash == std::string::npos) ? 0 : slash + 1,
+            // find next slash
+            slash = reference_string.find_first_of('/', start))
+        {
+            // use the text between the beginning of the reference token
+            // (start) and the last slash (slash).
+            auto reference_token = reference_string.substr(start, slash - start);
+
+            // check reference tokens are properly escaped
+            for (std::size_t pos = reference_token.find_first_of('~');
+                    pos != std::string::npos;
+                    pos = reference_token.find_first_of('~', pos + 1))
+            {
+                assert(reference_token[pos] == '~');
+
+                // ~ must be followed by 0 or 1
+                if (JSON_UNLIKELY(pos == reference_token.size() - 1 or
+                                  (reference_token[pos + 1] != '0' and
+                                   reference_token[pos + 1] != '1')))
+                {
+                    JSON_THROW(detail::parse_error::create(108, 0, "escape character '~' must be followed with '0' or '1'"));
+                }
+            }
+
+            // finally, store the reference token
+            unescape(reference_token);
+            result.push_back(reference_token);
+        }
+
+        return result;
+    }
+
+    /*!
+    @brief replace all occurrences of a substring by another string
+
+    @param[in,out] s  the string to manipulate; changed so that all
+                   occurrences of @a f are replaced with @a t
+    @param[in]     f  the substring to replace with @a t
+    @param[in]     t  the string to replace @a f
+
+    @pre The search string @a f must not be empty. **This precondition is
+    enforced with an assertion.**
+
+    @since version 2.0.0
+    */
+    static void replace_substring(std::string& s, const std::string& f,
+                                  const std::string& t)
+    {
+        assert(not f.empty());
+        for (auto pos = s.find(f);                // find first occurrence of f
+                pos != std::string::npos;         // make sure f was found
+                s.replace(pos, f.size(), t),      // replace with t, and
+                pos = s.find(f, pos + t.size()))  // find next occurrence of f
+        {}
+    }
+
+    /// escape "~" to "~0" and "/" to "~1"
+    static std::string escape(std::string s)
+    {
+        replace_substring(s, "~", "~0");
+        replace_substring(s, "/", "~1");
+        return s;
+    }
+
+    /// unescape "~1" to tilde and "~0" to slash (order is important!)
+    static void unescape(std::string& s)
+    {
+        replace_substring(s, "~1", "/");
+        replace_substring(s, "~0", "~");
+    }
+
+    /*!
+    @param[in] reference_string  the reference string to the current value
+    @param[in] value             the value to consider
+    @param[in,out] result        the result object to insert values to
+
+    @note Empty objects or arrays are flattened to `null`.
+    */
+    static void flatten(const std::string& reference_string,
+                        const BasicJsonType& value,
+                        BasicJsonType& result)
+    {
+        switch (value.m_type)
+        {
+            case detail::value_t::array:
+            {
+                if (value.m_value.array->empty())
+                {
+                    // flatten empty array as null
+                    result[reference_string] = nullptr;
+                }
+                else
+                {
+                    // iterate array and use index as reference string
+                    for (std::size_t i = 0; i < value.m_value.array->size(); ++i)
+                    {
+                        flatten(reference_string + "/" + std::to_string(i),
+                                value.m_value.array->operator[](i), result);
+                    }
+                }
+                break;
+            }
+
+            case detail::value_t::object:
+            {
+                if (value.m_value.object->empty())
+                {
+                    // flatten empty object as null
+                    result[reference_string] = nullptr;
+                }
+                else
+                {
+                    // iterate object and use keys as reference string
+                    for (const auto& element : *value.m_value.object)
+                    {
+                        flatten(reference_string + "/" + escape(element.first), element.second, result);
+                    }
+                }
+                break;
+            }
+
+            default:
+            {
+                // add primitive value with its reference string
+                result[reference_string] = value;
+                break;
+            }
+        }
+    }
+
+    /*!
+    @param[in] value  flattened JSON
+
+    @return unflattened JSON
+
+    @throw parse_error.109 if array index is not a number
+    @throw type_error.314  if value is not an object
+    @throw type_error.315  if object values are not primitive
+    @throw type_error.313  if value cannot be unflattened
+    */
+    static BasicJsonType
+    unflatten(const BasicJsonType& value)
+    {
+        if (JSON_UNLIKELY(not value.is_object()))
+        {
+            JSON_THROW(detail::type_error::create(314, "only objects can be unflattened"));
+        }
+
+        BasicJsonType result;
+
+        // iterate the JSON object values
+        for (const auto& element : *value.m_value.object)
+        {
+            if (JSON_UNLIKELY(not element.second.is_primitive()))
+            {
+                JSON_THROW(detail::type_error::create(315, "values in object must be primitive"));
+            }
+
+            // assign value to reference pointed to by JSON pointer; Note that if
+            // the JSON pointer is "" (i.e., points to the whole value), function
+            // get_and_create returns a reference to result itself. An assignment
+            // will then create a primitive value.
+            json_pointer(element.first).get_and_create(result) = element.second;
+        }
+
+        return result;
+    }
+
+    friend bool operator==(json_pointer const& lhs,
+                           json_pointer const& rhs) noexcept
+    {
+        return (lhs.reference_tokens == rhs.reference_tokens);
+    }
+
+    friend bool operator!=(json_pointer const& lhs,
+                           json_pointer const& rhs) noexcept
+    {
+        return not (lhs == rhs);
+    }
+
+    /// the reference tokens
+    std::vector<std::string> reference_tokens;
+};
+}  // namespace nlohmann
+
+// #include <nlohmann/adl_serializer.hpp>
+
+
+#include <utility>
+
+// #include <nlohmann/detail/conversions/from_json.hpp>
+
+// #include <nlohmann/detail/conversions/to_json.hpp>
+
+
+namespace nlohmann
+{
+
+template<typename, typename>
+struct adl_serializer
+{
+    /*!
+    @brief convert a JSON value to any value type
+
+    This function is usually called by the `get()` function of the
+    @ref basic_json class (either explicit or via conversion operators).
+
+    @param[in] j        JSON value to read from
+    @param[in,out] val  value to write to
+    */
+    template<typename BasicJsonType, typename ValueType>
+    static auto from_json(BasicJsonType&& j, ValueType& val) noexcept(
+        noexcept(::nlohmann::from_json(std::forward<BasicJsonType>(j), val)))
+    -> decltype(::nlohmann::from_json(std::forward<BasicJsonType>(j), val), void())
+    {
+        ::nlohmann::from_json(std::forward<BasicJsonType>(j), val);
+    }
+
+    /*!
+    @brief convert any value type to a JSON value
+
+    This function is usually called by the constructors of the @ref basic_json
+    class.
+
+    @param[in,out] j  JSON value to write to
+    @param[in] val    value to read from
+    */
+    template <typename BasicJsonType, typename ValueType>
+    static auto to_json(BasicJsonType& j, ValueType&& val) noexcept(
+        noexcept(::nlohmann::to_json(j, std::forward<ValueType>(val))))
+    -> decltype(::nlohmann::to_json(j, std::forward<ValueType>(val)), void())
+    {
+        ::nlohmann::to_json(j, std::forward<ValueType>(val));
+    }
+};
+
+}  // namespace nlohmann
+
+
+/*!
+@brief namespace for Niels Lohmann
+@see https://github.com/nlohmann
+@since version 1.0.0
+*/
+namespace nlohmann
+{
+
+/*!
+@brief a class to store JSON values
+
+@tparam ObjectType type for JSON objects (`std::map` by default; will be used
+in @ref object_t)
+@tparam ArrayType type for JSON arrays (`std::vector` by default; will be used
+in @ref array_t)
+@tparam StringType type for JSON strings and object keys (`std::string` by
+default; will be used in @ref string_t)
+@tparam BooleanType type for JSON booleans (`bool` by default; will be used
+in @ref boolean_t)
+@tparam NumberIntegerType type for JSON integer numbers (`int64_t` by
+default; will be used in @ref number_integer_t)
+@tparam NumberUnsignedType type for JSON unsigned integer numbers (@c
+`uint64_t` by default; will be used in @ref number_unsigned_t)
+@tparam NumberFloatType type for JSON floating-point numbers (`double` by
+default; will be used in @ref number_float_t)
+@tparam AllocatorType type of the allocator to use (`std::allocator` by
+default)
+@tparam JSONSerializer the serializer to resolve internal calls to `to_json()`
+and `from_json()` (@ref adl_serializer by default)
+
+@requirement The class satisfies the following concept requirements:
+- Basic
+ - [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible):
+   JSON values can be default constructed. The result will be a JSON null
+   value.
+ - [MoveConstructible](https://en.cppreference.com/w/cpp/named_req/MoveConstructible):
+   A JSON value can be constructed from an rvalue argument.
+ - [CopyConstructible](https://en.cppreference.com/w/cpp/named_req/CopyConstructible):
+   A JSON value can be copy-constructed from an lvalue expression.
+ - [MoveAssignable](https://en.cppreference.com/w/cpp/named_req/MoveAssignable):
+   A JSON value van be assigned from an rvalue argument.
+ - [CopyAssignable](https://en.cppreference.com/w/cpp/named_req/CopyAssignable):
+   A JSON value can be copy-assigned from an lvalue expression.
+ - [Destructible](https://en.cppreference.com/w/cpp/named_req/Destructible):
+   JSON values can be destructed.
+- Layout
+ - [StandardLayoutType](https://en.cppreference.com/w/cpp/named_req/StandardLayoutType):
+   JSON values have
+   [standard layout](https://en.cppreference.com/w/cpp/language/data_members#Standard_layout):
+   All non-static data members are private and standard layout types, the
+   class has no virtual functions or (virtual) base classes.
+- Library-wide
+ - [EqualityComparable](https://en.cppreference.com/w/cpp/named_req/EqualityComparable):
+   JSON values can be compared with `==`, see @ref
+   operator==(const_reference,const_reference).
+ - [LessThanComparable](https://en.cppreference.com/w/cpp/named_req/LessThanComparable):
+   JSON values can be compared with `<`, see @ref
+   operator<(const_reference,const_reference).
+ - [Swappable](https://en.cppreference.com/w/cpp/named_req/Swappable):
+   Any JSON lvalue or rvalue of can be swapped with any lvalue or rvalue of
+   other compatible types, using unqualified function call @ref swap().
+ - [NullablePointer](https://en.cppreference.com/w/cpp/named_req/NullablePointer):
+   JSON values can be compared against `std::nullptr_t` objects which are used
+   to model the `null` value.
+- Container
+ - [Container](https://en.cppreference.com/w/cpp/named_req/Container):
+   JSON values can be used like STL containers and provide iterator access.
+ - [ReversibleContainer](https://en.cppreference.com/w/cpp/named_req/ReversibleContainer);
+   JSON values can be used like STL containers and provide reverse iterator
+   access.
+
+@invariant The member variables @a m_value and @a m_type have the following
+relationship:
+- If `m_type == value_t::object`, then `m_value.object != nullptr`.
+- If `m_type == value_t::array`, then `m_value.array != nullptr`.
+- If `m_type == value_t::string`, then `m_value.string != nullptr`.
+The invariants are checked by member function assert_invariant().
+
+@internal
+@note ObjectType trick from http://stackoverflow.com/a/9860911
+@endinternal
+
+@see [RFC 7159: The JavaScript Object Notation (JSON) Data Interchange
+Format](http://rfc7159.net/rfc7159)
+
+@since version 1.0.0
+
+@nosubgrouping
+*/
+NLOHMANN_BASIC_JSON_TPL_DECLARATION
+class basic_json
+{
+  private:
+    template<detail::value_t> friend struct detail::external_constructor;
+    friend ::nlohmann::json_pointer<basic_json>;
+    friend ::nlohmann::detail::parser<basic_json>;
+    friend ::nlohmann::detail::serializer<basic_json>;
+    template<typename BasicJsonType>
+    friend class ::nlohmann::detail::iter_impl;
+    template<typename BasicJsonType, typename CharType>
+    friend class ::nlohmann::detail::binary_writer;
+    template<typename BasicJsonType, typename SAX>
+    friend class ::nlohmann::detail::binary_reader;
+    template<typename BasicJsonType>
+    friend class ::nlohmann::detail::json_sax_dom_parser;
+    template<typename BasicJsonType>
+    friend class ::nlohmann::detail::json_sax_dom_callback_parser;
+
+    /// workaround type for MSVC
+    using basic_json_t = NLOHMANN_BASIC_JSON_TPL;
+
+    // convenience aliases for types residing in namespace detail;
+    using lexer = ::nlohmann::detail::lexer<basic_json>;
+    using parser = ::nlohmann::detail::parser<basic_json>;
+
+    using primitive_iterator_t = ::nlohmann::detail::primitive_iterator_t;
+    template<typename BasicJsonType>
+    using internal_iterator = ::nlohmann::detail::internal_iterator<BasicJsonType>;
+    template<typename BasicJsonType>
+    using iter_impl = ::nlohmann::detail::iter_impl<BasicJsonType>;
+    template<typename Iterator>
+    using iteration_proxy = ::nlohmann::detail::iteration_proxy<Iterator>;
+    template<typename Base> using json_reverse_iterator = ::nlohmann::detail::json_reverse_iterator<Base>;
+
+    template<typename CharType>
+    using output_adapter_t = ::nlohmann::detail::output_adapter_t<CharType>;
+
+    using binary_reader = ::nlohmann::detail::binary_reader<basic_json>;
+    template<typename CharType> using binary_writer = ::nlohmann::detail::binary_writer<basic_json, CharType>;
+
+    using serializer = ::nlohmann::detail::serializer<basic_json>;
+
+  public:
+    using value_t = detail::value_t;
+    /// JSON Pointer, see @ref nlohmann::json_pointer
+    using json_pointer = ::nlohmann::json_pointer<basic_json>;
+    template<typename T, typename SFINAE>
+    using json_serializer = JSONSerializer<T, SFINAE>;
+    /// how to treat decoding errors
+    using error_handler_t = detail::error_handler_t;
+    /// helper type for initializer lists of basic_json values
+    using initializer_list_t = std::initializer_list<detail::json_ref<basic_json>>;
+
+    using input_format_t = detail::input_format_t;
+    /// SAX interface type, see @ref nlohmann::json_sax
+    using json_sax_t = json_sax<basic_json>;
+
+    ////////////////
+    // exceptions //
+    ////////////////
+
+    /// @name exceptions
+    /// Classes to implement user-defined exceptions.
+    /// @{
+
+    /// @copydoc detail::exception
+    using exception = detail::exception;
+    /// @copydoc detail::parse_error
+    using parse_error = detail::parse_error;
+    /// @copydoc detail::invalid_iterator
+    using invalid_iterator = detail::invalid_iterator;
+    /// @copydoc detail::type_error
+    using type_error = detail::type_error;
+    /// @copydoc detail::out_of_range
+    using out_of_range = detail::out_of_range;
+    /// @copydoc detail::other_error
+    using other_error = detail::other_error;
+
+    /// @}
+
+
+    /////////////////////
+    // container types //
+    /////////////////////
+
+    /// @name container types
+    /// The canonic container types to use @ref basic_json like any other STL
+    /// container.
+    /// @{
+
+    /// the type of elements in a basic_json container
+    using value_type = basic_json;
+
+    /// the type of an element reference
+    using reference = value_type&;
+    /// the type of an element const reference
+    using const_reference = const value_type&;
+
+    /// a type to represent differences between iterators
+    using difference_type = std::ptrdiff_t;
+    /// a type to represent container sizes
+    using size_type = std::size_t;
+
+    /// the allocator type
+    using allocator_type = AllocatorType<basic_json>;
+
+    /// the type of an element pointer
+    using pointer = typename std::allocator_traits<allocator_type>::pointer;
+    /// the type of an element const pointer
+    using const_pointer = typename std::allocator_traits<allocator_type>::const_pointer;
+
+    /// an iterator for a basic_json container
+    using iterator = iter_impl<basic_json>;
+    /// a const iterator for a basic_json container
+    using const_iterator = iter_impl<const basic_json>;
+    /// a reverse iterator for a basic_json container
+    using reverse_iterator = json_reverse_iterator<typename basic_json::iterator>;
+    /// a const reverse iterator for a basic_json container
+    using const_reverse_iterator = json_reverse_iterator<typename basic_json::const_iterator>;
+
+    /// @}
+
+
+    /*!
+    @brief returns the allocator associated with the container
+    */
+    static allocator_type get_allocator()
+    {
+        return allocator_type();
+    }
+
+    /*!
+    @brief returns version information on the library
+
+    This function returns a JSON object with information about the library,
+    including the version number and information on the platform and compiler.
+
+    @return JSON object holding version information
+    key         | description
+    ----------- | ---------------
+    `compiler`  | Information on the used compiler. It is an object with the following keys: `c++` (the used C++ standard), `family` (the compiler family; possible values are `clang`, `icc`, `gcc`, `ilecpp`, `msvc`, `pgcpp`, `sunpro`, and `unknown`), and `version` (the compiler version).
+    `copyright` | The copyright line for the library as string.
+    `name`      | The name of the library as string.
+    `platform`  | The used platform as string. Possible values are `win32`, `linux`, `apple`, `unix`, and `unknown`.
+    `url`       | The URL of the project as string.
+    `version`   | The version of the library. It is an object with the following keys: `major`, `minor`, and `patch` as defined by [Semantic Versioning](http://semver.org), and `string` (the version string).
+
+    @liveexample{The following code shows an example output of the `meta()`
+    function.,meta}
+
+    @exceptionsafety Strong guarantee: if an exception is thrown, there are no
+    changes to any JSON value.
+
+    @complexity Constant.
+
+    @since 2.1.0
+    */
+    static basic_json meta()
+    {
+        basic_json result;
+
+        result["copyright"] = "(C) 2013-2017 Niels Lohmann";
+        result["name"] = "JSON for Modern C++";
+        result["url"] = "https://github.com/nlohmann/json";
+        result["version"]["string"] =
+            std::to_string(NLOHMANN_JSON_VERSION_MAJOR) + "." +
+            std::to_string(NLOHMANN_JSON_VERSION_MINOR) + "." +
+            std::to_string(NLOHMANN_JSON_VERSION_PATCH);
+        result["version"]["major"] = NLOHMANN_JSON_VERSION_MAJOR;
+        result["version"]["minor"] = NLOHMANN_JSON_VERSION_MINOR;
+        result["version"]["patch"] = NLOHMANN_JSON_VERSION_PATCH;
+
+#ifdef _WIN32
+        result["platform"] = "win32";
+#elif defined __linux__
+        result["platform"] = "linux";
+#elif defined __APPLE__
+        result["platform"] = "apple";
+#elif defined __unix__
+        result["platform"] = "unix";
+#else
+        result["platform"] = "unknown";
+#endif
+
+#if defined(__ICC) || defined(__INTEL_COMPILER)
+        result["compiler"] = {{"family", "icc"}, {"version", __INTEL_COMPILER}};
+#elif defined(__clang__)
+        result["compiler"] = {{"family", "clang"}, {"version", __clang_version__}};
+#elif defined(__GNUC__) || defined(__GNUG__)
+        result["compiler"] = {{"family", "gcc"}, {"version", std::to_string(__GNUC__) + "." + std::to_string(__GNUC_MINOR__) + "." + std::to_string(__GNUC_PATCHLEVEL__)}};
+#elif defined(__HP_cc) || defined(__HP_aCC)
+        result["compiler"] = "hp"
+#elif defined(__IBMCPP__)
+        result["compiler"] = {{"family", "ilecpp"}, {"version", __IBMCPP__}};
+#elif defined(_MSC_VER)
+        result["compiler"] = {{"family", "msvc"}, {"version", _MSC_VER}};
+#elif defined(__PGI)
+        result["compiler"] = {{"family", "pgcpp"}, {"version", __PGI}};
+#elif defined(__SUNPRO_CC)
+        result["compiler"] = {{"family", "sunpro"}, {"version", __SUNPRO_CC}};
+#else
+        result["compiler"] = {{"family", "unknown"}, {"version", "unknown"}};
+#endif
+
+#ifdef __cplusplus
+        result["compiler"]["c++"] = std::to_string(__cplusplus);
+#else
+        result["compiler"]["c++"] = "unknown";
+#endif
+        return result;
+    }
+
+
+    ///////////////////////////
+    // JSON value data types //
+    ///////////////////////////
+
+    /// @name JSON value data types
+    /// The data types to store a JSON value. These types are derived from
+    /// the template arguments passed to class @ref basic_json.
+    /// @{
+
+#if defined(JSON_HAS_CPP_14)
+    // Use transparent comparator if possible, combined with perfect forwarding
+    // on find() and count() calls prevents unnecessary string construction.
+    using object_comparator_t = std::less<>;
+#else
+    using object_comparator_t = std::less<StringType>;
+#endif
+
+    /*!
+    @brief a type for an object
+
+    [RFC 7159](http://rfc7159.net/rfc7159) describes JSON objects as follows:
+    > An object is an unordered collection of zero or more name/value pairs,
+    > where a name is a string and a value is a string, number, boolean, null,
+    > object, or array.
+
+    To store objects in C++, a type is defined by the template parameters
+    described below.
+
+    @tparam ObjectType  the container to store objects (e.g., `std::map` or
+    `std::unordered_map`)
+    @tparam StringType the type of the keys or names (e.g., `std::string`).
+    The comparison function `std::less<StringType>` is used to order elements
+    inside the container.
+    @tparam AllocatorType the allocator to use for objects (e.g.,
+    `std::allocator`)
+
+    #### Default type
+
+    With the default values for @a ObjectType (`std::map`), @a StringType
+    (`std::string`), and @a AllocatorType (`std::allocator`), the default
+    value for @a object_t is:
+
+    @code {.cpp}
+    std::map<
+      std::string, // key_type
+      basic_json, // value_type
+      std::less<std::string>, // key_compare
+      std::allocator<std::pair<const std::string, basic_json>> // allocator_type
+    >
+    @endcode
+
+    #### Behavior
+
+    The choice of @a object_t influences the behavior of the JSON class. With
+    the default type, objects have the following behavior:
+
+    - When all names are unique, objects will be interoperable in the sense
+      that all software implementations receiving that object will agree on
+      the name-value mappings.
+    - When the names within an object are not unique, it is unspecified which
+      one of the values for a given key will be chosen. For instance,
+      `{"key": 2, "key": 1}` could be equal to either `{"key": 1}` or
+      `{"key": 2}`.
+    - Internally, name/value pairs are stored in lexicographical order of the
+      names. Objects will also be serialized (see @ref dump) in this order.
+      For instance, `{"b": 1, "a": 2}` and `{"a": 2, "b": 1}` will be stored
+      and serialized as `{"a": 2, "b": 1}`.
+    - When comparing objects, the order of the name/value pairs is irrelevant.
+      This makes objects interoperable in the sense that they will not be
+      affected by these differences. For instance, `{"b": 1, "a": 2}` and
+      `{"a": 2, "b": 1}` will be treated as equal.
+
+    #### Limits
+
+    [RFC 7159](http://rfc7159.net/rfc7159) specifies:
+    > An implementation may set limits on the maximum depth of nesting.
+
+    In this class, the object's limit of nesting is not explicitly constrained.
+    However, a maximum depth of nesting may be introduced by the compiler or
+    runtime environment. A theoretical limit can be queried by calling the
+    @ref max_size function of a JSON object.
+
+    #### Storage
+
+    Objects are stored as pointers in a @ref basic_json type. That is, for any
+    access to object values, a pointer of type `object_t*` must be
+    dereferenced.
+
+    @sa @ref array_t -- type for an array value
+
+    @since version 1.0.0
+
+    @note The order name/value pairs are added to the object is *not*
+    preserved by the library. Therefore, iterating an object may return
+    name/value pairs in a different order than they were originally stored. In
+    fact, keys will be traversed in alphabetical order as `std::map` with
+    `std::less` is used by default. Please note this behavior conforms to [RFC
+    7159](http://rfc7159.net/rfc7159), because any order implements the
+    specified "unordered" nature of JSON objects.
+    */
+    using object_t = ObjectType<StringType,
+          basic_json,
+          object_comparator_t,
+          AllocatorType<std::pair<const StringType,
+          basic_json>>>;
+
+    /*!
+    @brief a type for an array
+
+    [RFC 7159](http://rfc7159.net/rfc7159) describes JSON arrays as follows:
+    > An array is an ordered sequence of zero or more values.
+
+    To store objects in C++, a type is defined by the template parameters
+    explained below.
+
+    @tparam ArrayType  container type to store arrays (e.g., `std::vector` or
+    `std::list`)
+    @tparam AllocatorType allocator to use for arrays (e.g., `std::allocator`)
+
+    #### Default type
+
+    With the default values for @a ArrayType (`std::vector`) and @a
+    AllocatorType (`std::allocator`), the default value for @a array_t is:
+
+    @code {.cpp}
+    std::vector<
+      basic_json, // value_type
+      std::allocator<basic_json> // allocator_type
+    >
+    @endcode
+
+    #### Limits
+
+    [RFC 7159](http://rfc7159.net/rfc7159) specifies:
+    > An implementation may set limits on the maximum depth of nesting.
+
+    In this class, the array's limit of nesting is not explicitly constrained.
+    However, a maximum depth of nesting may be introduced by the compiler or
+    runtime environment. A theoretical limit can be queried by calling the
+    @ref max_size function of a JSON array.
+
+    #### Storage
+
+    Arrays are stored as pointers in a @ref basic_json type. That is, for any
+    access to array values, a pointer of type `array_t*` must be dereferenced.
+
+    @sa @ref object_t -- type for an object value
+
+    @since version 1.0.0
+    */
+    using array_t = ArrayType<basic_json, AllocatorType<basic_json>>;
+
+    /*!
+    @brief a type for a string
+
+    [RFC 7159](http://rfc7159.net/rfc7159) describes JSON strings as follows:
+    > A string is a sequence of zero or more Unicode characters.
+
+    To store objects in C++, a type is defined by the template parameter
+    described below. Unicode values are split by the JSON class into
+    byte-sized characters during deserialization.
+
+    @tparam StringType  the container to store strings (e.g., `std::string`).
+    Note this container is used for keys/names in objects, see @ref object_t.
+
+    #### Default type
+
+    With the default values for @a StringType (`std::string`), the default
+    value for @a string_t is:
+
+    @code {.cpp}
+    std::string
+    @endcode
+
+    #### Encoding
+
+    Strings are stored in UTF-8 encoding. Therefore, functions like
+    `std::string::size()` or `std::string::length()` return the number of
+    bytes in the string rather than the number of characters or glyphs.
+
+    #### String comparison
+
+    [RFC 7159](http://rfc7159.net/rfc7159) states:
+    > Software implementations are typically required to test names of object
+    > members for equality. Implementations that transform the textual
+    > representation into sequences of Unicode code units and then perform the
+    > comparison numerically, code unit by code unit, are interoperable in the
+    > sense that implementations will agree in all cases on equality or
+    > inequality of two strings. For example, implementations that compare
+    > strings with escaped characters unconverted may incorrectly find that
+    > `"a\\b"` and `"a\u005Cb"` are not equal.
+
+    This implementation is interoperable as it does compare strings code unit
+    by code unit.
+
+    #### Storage
+
+    String values are stored as pointers in a @ref basic_json type. That is,
+    for any access to string values, a pointer of type `string_t*` must be
+    dereferenced.
+
+    @since version 1.0.0
+    */
+    using string_t = StringType;
+
+    /*!
+    @brief a type for a boolean
+
+    [RFC 7159](http://rfc7159.net/rfc7159) implicitly describes a boolean as a
+    type which differentiates the two literals `true` and `false`.
+
+    To store objects in C++, a type is defined by the template parameter @a
+    BooleanType which chooses the type to use.
+
+    #### Default type
+
+    With the default values for @a BooleanType (`bool`), the default value for
+    @a boolean_t is:
+
+    @code {.cpp}
+    bool
+    @endcode
+
+    #### Storage
+
+    Boolean values are stored directly inside a @ref basic_json type.
+
+    @since version 1.0.0
+    */
+    using boolean_t = BooleanType;
+
+    /*!
+    @brief a type for a number (integer)
+
+    [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows:
+    > The representation of numbers is similar to that used in most
+    > programming languages. A number is represented in base 10 using decimal
+    > digits. It contains an integer component that may be prefixed with an
+    > optional minus sign, which may be followed by a fraction part and/or an
+    > exponent part. Leading zeros are not allowed. (...) Numeric values that
+    > cannot be represented in the grammar below (such as Infinity and NaN)
+    > are not permitted.
+
+    This description includes both integer and floating-point numbers.
+    However, C++ allows more precise storage if it is known whether the number
+    is a signed integer, an unsigned integer or a floating-point number.
+    Therefore, three different types, @ref number_integer_t, @ref
+    number_unsigned_t and @ref number_float_t are used.
+
+    To store integer numbers in C++, a type is defined by the template
+    parameter @a NumberIntegerType which chooses the type to use.
+
+    #### Default type
+
+    With the default values for @a NumberIntegerType (`int64_t`), the default
+    value for @a number_integer_t is:
+
+    @code {.cpp}
+    int64_t
+    @endcode
+
+    #### Default behavior
+
+    - The restrictions about leading zeros is not enforced in C++. Instead,
+      leading zeros in integer literals lead to an interpretation as octal
+      number. Internally, the value will be stored as decimal number. For
+      instance, the C++ integer literal `010` will be serialized to `8`.
+      During deserialization, leading zeros yield an error.
+    - Not-a-number (NaN) values will be serialized to `null`.
+
+    #### Limits
+
+    [RFC 7159](http://rfc7159.net/rfc7159) specifies:
+    > An implementation may set limits on the range and precision of numbers.
+
+    When the default type is used, the maximal integer number that can be
+    stored is `9223372036854775807` (INT64_MAX) and the minimal integer number
+    that can be stored is `-9223372036854775808` (INT64_MIN). Integer numbers
+    that are out of range will yield over/underflow when used in a
+    constructor. During deserialization, too large or small integer numbers
+    will be automatically be stored as @ref number_unsigned_t or @ref
+    number_float_t.
+
+    [RFC 7159](http://rfc7159.net/rfc7159) further states:
+    > Note that when such software is used, numbers that are integers and are
+    > in the range \f$[-2^{53}+1, 2^{53}-1]\f$ are interoperable in the sense
+    > that implementations will agree exactly on their numeric values.
+
+    As this range is a subrange of the exactly supported range [INT64_MIN,
+    INT64_MAX], this class's integer type is interoperable.
+
+    #### Storage
+
+    Integer number values are stored directly inside a @ref basic_json type.
+
+    @sa @ref number_float_t -- type for number values (floating-point)
+
+    @sa @ref number_unsigned_t -- type for number values (unsigned integer)
+
+    @since version 1.0.0
+    */
+    using number_integer_t = NumberIntegerType;
+
+    /*!
+    @brief a type for a number (unsigned)
+
+    [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows:
+    > The representation of numbers is similar to that used in most
+    > programming languages. A number is represented in base 10 using decimal
+    > digits. It contains an integer component that may be prefixed with an
+    > optional minus sign, which may be followed by a fraction part and/or an
+    > exponent part. Leading zeros are not allowed. (...) Numeric values that
+    > cannot be represented in the grammar below (such as Infinity and NaN)
+    > are not permitted.
+
+    This description includes both integer and floating-point numbers.
+    However, C++ allows more precise storage if it is known whether the number
+    is a signed integer, an unsigned integer or a floating-point number.
+    Therefore, three different types, @ref number_integer_t, @ref
+    number_unsigned_t and @ref number_float_t are used.
+
+    To store unsigned integer numbers in C++, a type is defined by the
+    template parameter @a NumberUnsignedType which chooses the type to use.
+
+    #### Default type
+
+    With the default values for @a NumberUnsignedType (`uint64_t`), the
+    default value for @a number_unsigned_t is:
+
+    @code {.cpp}
+    uint64_t
+    @endcode
+
+    #### Default behavior
+
+    - The restrictions about leading zeros is not enforced in C++. Instead,
+      leading zeros in integer literals lead to an interpretation as octal
+      number. Internally, the value will be stored as decimal number. For
+      instance, the C++ integer literal `010` will be serialized to `8`.
+      During deserialization, leading zeros yield an error.
+    - Not-a-number (NaN) values will be serialized to `null`.
+
+    #### Limits
+
+    [RFC 7159](http://rfc7159.net/rfc7159) specifies:
+    > An implementation may set limits on the range and precision of numbers.
+
+    When the default type is used, the maximal integer number that can be
+    stored is `18446744073709551615` (UINT64_MAX) and the minimal integer
+    number that can be stored is `0`. Integer numbers that are out of range
+    will yield over/underflow when used in a constructor. During
+    deserialization, too large or small integer numbers will be automatically
+    be stored as @ref number_integer_t or @ref number_float_t.
+
+    [RFC 7159](http://rfc7159.net/rfc7159) further states:
+    > Note that when such software is used, numbers that are integers and are
+    > in the range \f$[-2^{53}+1, 2^{53}-1]\f$ are interoperable in the sense
+    > that implementations will agree exactly on their numeric values.
+
+    As this range is a subrange (when considered in conjunction with the
+    number_integer_t type) of the exactly supported range [0, UINT64_MAX],
+    this class's integer type is interoperable.
+
+    #### Storage
+
+    Integer number values are stored directly inside a @ref basic_json type.
+
+    @sa @ref number_float_t -- type for number values (floating-point)
+    @sa @ref number_integer_t -- type for number values (integer)
+
+    @since version 2.0.0
+    */
+    using number_unsigned_t = NumberUnsignedType;
+
+    /*!
+    @brief a type for a number (floating-point)
+
+    [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows:
+    > The representation of numbers is similar to that used in most
+    > programming languages. A number is represented in base 10 using decimal
+    > digits. It contains an integer component that may be prefixed with an
+    > optional minus sign, which may be followed by a fraction part and/or an
+    > exponent part. Leading zeros are not allowed. (...) Numeric values that
+    > cannot be represented in the grammar below (such as Infinity and NaN)
+    > are not permitted.
+
+    This description includes both integer and floating-point numbers.
+    However, C++ allows more precise storage if it is known whether the number
+    is a signed integer, an unsigned integer or a floating-point number.
+    Therefore, three different types, @ref number_integer_t, @ref
+    number_unsigned_t and @ref number_float_t are used.
+
+    To store floating-point numbers in C++, a type is defined by the template
+    parameter @a NumberFloatType which chooses the type to use.
+
+    #### Default type
+
+    With the default values for @a NumberFloatType (`double`), the default
+    value for @a number_float_t is:
+
+    @code {.cpp}
+    double
+    @endcode
+
+    #### Default behavior
+
+    - The restrictions about leading zeros is not enforced in C++. Instead,
+      leading zeros in floating-point literals will be ignored. Internally,
+      the value will be stored as decimal number. For instance, the C++
+      floating-point literal `01.2` will be serialized to `1.2`. During
+      deserialization, leading zeros yield an error.
+    - Not-a-number (NaN) values will be serialized to `null`.
+
+    #### Limits
+
+    [RFC 7159](http://rfc7159.net/rfc7159) states:
+    > This specification allows implementations to set limits on the range and
+    > precision of numbers accepted. Since software that implements IEEE
+    > 754-2008 binary64 (double precision) numbers is generally available and
+    > widely used, good interoperability can be achieved by implementations
+    > that expect no more precision or range than these provide, in the sense
+    > that implementations will approximate JSON numbers within the expected
+    > precision.
+
+    This implementation does exactly follow this approach, as it uses double
+    precision floating-point numbers. Note values smaller than
+    `-1.79769313486232e+308` and values greater than `1.79769313486232e+308`
+    will be stored as NaN internally and be serialized to `null`.
+
+    #### Storage
+
+    Floating-point number values are stored directly inside a @ref basic_json
+    type.
+
+    @sa @ref number_integer_t -- type for number values (integer)
+
+    @sa @ref number_unsigned_t -- type for number values (unsigned integer)
+
+    @since version 1.0.0
+    */
+    using number_float_t = NumberFloatType;
+
+    /// @}
+
+  private:
+
+    /// helper for exception-safe object creation
+    template<typename T, typename... Args>
+    static T* create(Args&& ... args)
+    {
+        AllocatorType<T> alloc;
+        using AllocatorTraits = std::allocator_traits<AllocatorType<T>>;
+
+        auto deleter = [&](T * object)
+        {
+            AllocatorTraits::deallocate(alloc, object, 1);
+        };
+        std::unique_ptr<T, decltype(deleter)> object(AllocatorTraits::allocate(alloc, 1), deleter);
+        AllocatorTraits::construct(alloc, object.get(), std::forward<Args>(args)...);
+        assert(object != nullptr);
+        return object.release();
+    }
+
+    ////////////////////////
+    // JSON value storage //
+    ////////////////////////
+
+    /*!
+    @brief a JSON value
+
+    The actual storage for a JSON value of the @ref basic_json class. This
+    union combines the different storage types for the JSON value types
+    defined in @ref value_t.
+
+    JSON type | value_t type    | used type
+    --------- | --------------- | ------------------------
+    object    | object          | pointer to @ref object_t
+    array     | array           | pointer to @ref array_t
+    string    | string          | pointer to @ref string_t
+    boolean   | boolean         | @ref boolean_t
+    number    | number_integer  | @ref number_integer_t
+    number    | number_unsigned | @ref number_unsigned_t
+    number    | number_float    | @ref number_float_t
+    null      | null            | *no value is stored*
+
+    @note Variable-length types (objects, arrays, and strings) are stored as
+    pointers. The size of the union should not exceed 64 bits if the default
+    value types are used.
+
+    @since version 1.0.0
+    */
+    union json_value
+    {
+        /// object (stored with pointer to save storage)
+        object_t* object;
+        /// array (stored with pointer to save storage)
+        array_t* array;
+        /// string (stored with pointer to save storage)
+        string_t* string;
+        /// boolean
+        boolean_t boolean;
+        /// number (integer)
+        number_integer_t number_integer;
+        /// number (unsigned integer)
+        number_unsigned_t number_unsigned;
+        /// number (floating-point)
+        number_float_t number_float;
+
+        /// default constructor (for null values)
+        json_value() = default;
+        /// constructor for booleans
+        json_value(boolean_t v) noexcept : boolean(v) {}
+        /// constructor for numbers (integer)
+        json_value(number_integer_t v) noexcept : number_integer(v) {}
+        /// constructor for numbers (unsigned)
+        json_value(number_unsigned_t v) noexcept : number_unsigned(v) {}
+        /// constructor for numbers (floating-point)
+        json_value(number_float_t v) noexcept : number_float(v) {}
+        /// constructor for empty values of a given type
+        json_value(value_t t)
+        {
+            switch (t)
+            {
+                case value_t::object:
+                {
+                    object = create<object_t>();
+                    break;
+                }
+
+                case value_t::array:
+                {
+                    array = create<array_t>();
+                    break;
+                }
+
+                case value_t::string:
+                {
+                    string = create<string_t>("");
+                    break;
+                }
+
+                case value_t::boolean:
+                {
+                    boolean = boolean_t(false);
+                    break;
+                }
+
+                case value_t::number_integer:
+                {
+                    number_integer = number_integer_t(0);
+                    break;
+                }
+
+                case value_t::number_unsigned:
+                {
+                    number_unsigned = number_unsigned_t(0);
+                    break;
+                }
+
+                case value_t::number_float:
+                {
+                    number_float = number_float_t(0.0);
+                    break;
+                }
+
+                case value_t::null:
+                {
+                    object = nullptr;  // silence warning, see #821
+                    break;
+                }
+
+                default:
+                {
+                    object = nullptr;  // silence warning, see #821
+                    if (JSON_UNLIKELY(t == value_t::null))
+                    {
+                        JSON_THROW(other_error::create(500, "961c151d2e87f2686a955a9be24d316f1362bf21 3.5.0")); // LCOV_EXCL_LINE
+                    }
+                    break;
+                }
+            }
+        }
+
+        /// constructor for strings
+        json_value(const string_t& value)
+        {
+            string = create<string_t>(value);
+        }
+
+        /// constructor for rvalue strings
+        json_value(string_t&& value)
+        {
+            string = create<string_t>(std::move(value));
+        }
+
+        /// constructor for objects
+        json_value(const object_t& value)
+        {
+            object = create<object_t>(value);
+        }
+
+        /// constructor for rvalue objects
+        json_value(object_t&& value)
+        {
+            object = create<object_t>(std::move(value));
+        }
+
+        /// constructor for arrays
+        json_value(const array_t& value)
+        {
+            array = create<array_t>(value);
+        }
+
+        /// constructor for rvalue arrays
+        json_value(array_t&& value)
+        {
+            array = create<array_t>(std::move(value));
+        }
+
+        void destroy(value_t t) noexcept
+        {
+            switch (t)
+            {
+                case value_t::object:
+                {
+                    AllocatorType<object_t> alloc;
+                    std::allocator_traits<decltype(alloc)>::destroy(alloc, object);
+                    std::allocator_traits<decltype(alloc)>::deallocate(alloc, object, 1);
+                    break;
+                }
+
+                case value_t::array:
+                {
+                    AllocatorType<array_t> alloc;
+                    std::allocator_traits<decltype(alloc)>::destroy(alloc, array);
+                    std::allocator_traits<decltype(alloc)>::deallocate(alloc, array, 1);
+                    break;
+                }
+
+                case value_t::string:
+                {
+                    AllocatorType<string_t> alloc;
+                    std::allocator_traits<decltype(alloc)>::destroy(alloc, string);
+                    std::allocator_traits<decltype(alloc)>::deallocate(alloc, string, 1);
+                    break;
+                }
+
+                default:
+                {
+                    break;
+                }
+            }
+        }
+    };
+
+    /*!
+    @brief checks the class invariants
+
+    This function asserts the class invariants. It needs to be called at the
+    end of every constructor to make sure that created objects respect the
+    invariant. Furthermore, it has to be called each time the type of a JSON
+    value is changed, because the invariant expresses a relationship between
+    @a m_type and @a m_value.
+    */
+    void assert_invariant() const noexcept
+    {
+        assert(m_type != value_t::object or m_value.object != nullptr);
+        assert(m_type != value_t::array or m_value.array != nullptr);
+        assert(m_type != value_t::string or m_value.string != nullptr);
+    }
+
+  public:
+    //////////////////////////
+    // JSON parser callback //
+    //////////////////////////
+
+    /*!
+    @brief parser event types
+
+    The parser callback distinguishes the following events:
+    - `object_start`: the parser read `{` and started to process a JSON object
+    - `key`: the parser read a key of a value in an object
+    - `object_end`: the parser read `}` and finished processing a JSON object
+    - `array_start`: the parser read `[` and started to process a JSON array
+    - `array_end`: the parser read `]` and finished processing a JSON array
+    - `value`: the parser finished reading a JSON value
+
+    @image html callback_events.png "Example when certain parse events are triggered"
+
+    @sa @ref parser_callback_t for more information and examples
+    */
+    using parse_event_t = typename parser::parse_event_t;
+
+    /*!
+    @brief per-element parser callback type
+
+    With a parser callback function, the result of parsing a JSON text can be
+    influenced. When passed to @ref parse, it is called on certain events
+    (passed as @ref parse_event_t via parameter @a event) with a set recursion
+    depth @a depth and context JSON value @a parsed. The return value of the
+    callback function is a boolean indicating whether the element that emitted
+    the callback shall be kept or not.
+
+    We distinguish six scenarios (determined by the event type) in which the
+    callback function can be called. The following table describes the values
+    of the parameters @a depth, @a event, and @a parsed.
+
+    parameter @a event | description | parameter @a depth | parameter @a parsed
+    ------------------ | ----------- | ------------------ | -------------------
+    parse_event_t::object_start | the parser read `{` and started to process a JSON object | depth of the parent of the JSON object | a JSON value with type discarded
+    parse_event_t::key | the parser read a key of a value in an object | depth of the currently parsed JSON object | a JSON string containing the key
+    parse_event_t::object_end | the parser read `}` and finished processing a JSON object | depth of the parent of the JSON object | the parsed JSON object
+    parse_event_t::array_start | the parser read `[` and started to process a JSON array | depth of the parent of the JSON array | a JSON value with type discarded
+    parse_event_t::array_end | the parser read `]` and finished processing a JSON array | depth of the parent of the JSON array | the parsed JSON array
+    parse_event_t::value | the parser finished reading a JSON value | depth of the value | the parsed JSON value
+
+    @image html callback_events.png "Example when certain parse events are triggered"
+
+    Discarding a value (i.e., returning `false`) has different effects
+    depending on the context in which function was called:
+
+    - Discarded values in structured types are skipped. That is, the parser
+      will behave as if the discarded value was never read.
+    - In case a value outside a structured type is skipped, it is replaced
+      with `null`. This case happens if the top-level element is skipped.
+
+    @param[in] depth  the depth of the recursion during parsing
+
+    @param[in] event  an event of type parse_event_t indicating the context in
+    the callback function has been called
+
+    @param[in,out] parsed  the current intermediate parse result; note that
+    writing to this value has no effect for parse_event_t::key events
+
+    @return Whether the JSON value which called the function during parsing
+    should be kept (`true`) or not (`false`). In the latter case, it is either
+    skipped completely or replaced by an empty discarded object.
+
+    @sa @ref parse for examples
+
+    @since version 1.0.0
+    */
+    using parser_callback_t = typename parser::parser_callback_t;
+
+    //////////////////
+    // constructors //
+    //////////////////
+
+    /// @name constructors and destructors
+    /// Constructors of class @ref basic_json, copy/move constructor, copy
+    /// assignment, static functions creating objects, and the destructor.
+    /// @{
+
+    /*!
+    @brief create an empty value with a given type
+
+    Create an empty JSON value with a given type. The value will be default
+    initialized with an empty value which depends on the type:
+
+    Value type  | initial value
+    ----------- | -------------
+    null        | `null`
+    boolean     | `false`
+    string      | `""`
+    number      | `0`
+    object      | `{}`
+    array       | `[]`
+
+    @param[in] v  the type of the value to create
+
+    @complexity Constant.
+
+    @exceptionsafety Strong guarantee: if an exception is thrown, there are no
+    changes to any JSON value.
+
+    @liveexample{The following code shows the constructor for different @ref
+    value_t values,basic_json__value_t}
+
+    @sa @ref clear() -- restores the postcondition of this constructor
+
+    @since version 1.0.0
+    */
+    basic_json(const value_t v)
+        : m_type(v), m_value(v)
+    {
+        assert_invariant();
+    }
+
+    /*!
+    @brief create a null object
+
+    Create a `null` JSON value. It either takes a null pointer as parameter
+    (explicitly creating `null`) or no parameter (implicitly creating `null`).
+    The passed null pointer itself is not read -- it is only used to choose
+    the right constructor.
+
+    @complexity Constant.
+
+    @exceptionsafety No-throw guarantee: this constructor never throws
+    exceptions.
+
+    @liveexample{The following code shows the constructor with and without a
+    null pointer parameter.,basic_json__nullptr_t}
+
+    @since version 1.0.0
+    */
+    basic_json(std::nullptr_t = nullptr) noexcept
+        : basic_json(value_t::null)
+    {
+        assert_invariant();
+    }
+
+    /*!
+    @brief create a JSON value
+
+    This is a "catch all" constructor for all compatible JSON types; that is,
+    types for which a `to_json()` method exists. The constructor forwards the
+    parameter @a val to that method (to `json_serializer<U>::to_json` method
+    with `U = uncvref_t<CompatibleType>`, to be exact).
+
+    Template type @a CompatibleType includes, but is not limited to, the
+    following types:
+    - **arrays**: @ref array_t and all kinds of compatible containers such as
+      `std::vector`, `std::deque`, `std::list`, `std::forward_list`,
+      `std::array`, `std::valarray`, `std::set`, `std::unordered_set`,
+      `std::multiset`, and `std::unordered_multiset` with a `value_type` from
+      which a @ref basic_json value can be constructed.
+    - **objects**: @ref object_t and all kinds of compatible associative
+      containers such as `std::map`, `std::unordered_map`, `std::multimap`,
+      and `std::unordered_multimap` with a `key_type` compatible to
+      @ref string_t and a `value_type` from which a @ref basic_json value can
+      be constructed.
+    - **strings**: @ref string_t, string literals, and all compatible string
+      containers can be used.
+    - **numbers**: @ref number_integer_t, @ref number_unsigned_t,
+      @ref number_float_t, and all convertible number types such as `int`,
+      `size_t`, `int64_t`, `float` or `double` can be used.
+    - **boolean**: @ref boolean_t / `bool` can be used.
+
+    See the examples below.
+
+    @tparam CompatibleType a type such that:
+    - @a CompatibleType is not derived from `std::istream`,
+    - @a CompatibleType is not @ref basic_json (to avoid hijacking copy/move
+         constructors),
+    - @a CompatibleType is not a different @ref basic_json type (i.e. with different template arguments)
+    - @a CompatibleType is not a @ref basic_json nested type (e.g.,
+         @ref json_pointer, @ref iterator, etc ...)
+    - @ref @ref json_serializer<U> has a
+         `to_json(basic_json_t&, CompatibleType&&)` method
+
+    @tparam U = `uncvref_t<CompatibleType>`
+
+    @param[in] val the value to be forwarded to the respective constructor
+
+    @complexity Usually linear in the size of the passed @a val, also
+                depending on the implementation of the called `to_json()`
+                method.
+
+    @exceptionsafety Depends on the called constructor. For types directly
+    supported by the library (i.e., all types for which no `to_json()` function
+    was provided), strong guarantee holds: if an exception is thrown, there are
+    no changes to any JSON value.
+
+    @liveexample{The following code shows the constructor with several
+    compatible types.,basic_json__CompatibleType}
+
+    @since version 2.1.0
+    */
+    template <typename CompatibleType,
+              typename U = detail::uncvref_t<CompatibleType>,
+              detail::enable_if_t<
+                  not detail::is_basic_json<U>::value and detail::is_compatible_type<basic_json_t, U>::value, int> = 0>
+    basic_json(CompatibleType && val) noexcept(noexcept(
+                JSONSerializer<U>::to_json(std::declval<basic_json_t&>(),
+                                           std::forward<CompatibleType>(val))))
+    {
+        JSONSerializer<U>::to_json(*this, std::forward<CompatibleType>(val));
+        assert_invariant();
+    }
+
+    /*!
+    @brief create a JSON value from an existing one
+
+    This is a constructor for existing @ref basic_json types.
+    It does not hijack copy/move constructors, since the parameter has different
+    template arguments than the current ones.
+
+    The constructor tries to convert the internal @ref m_value of the parameter.
+
+    @tparam BasicJsonType a type such that:
+    - @a BasicJsonType is a @ref basic_json type.
+    - @a BasicJsonType has different template arguments than @ref basic_json_t.
+
+    @param[in] val the @ref basic_json value to be converted.
+
+    @complexity Usually linear in the size of the passed @a val, also
+                depending on the implementation of the called `to_json()`
+                method.
+
+    @exceptionsafety Depends on the called constructor. For types directly
+    supported by the library (i.e., all types for which no `to_json()` function
+    was provided), strong guarantee holds: if an exception is thrown, there are
+    no changes to any JSON value.
+
+    @since version 3.2.0
+    */
+    template <typename BasicJsonType,
+              detail::enable_if_t<
+                  detail::is_basic_json<BasicJsonType>::value and not std::is_same<basic_json, BasicJsonType>::value, int> = 0>
+    basic_json(const BasicJsonType& val)
+    {
+        using other_boolean_t = typename BasicJsonType::boolean_t;
+        using other_number_float_t = typename BasicJsonType::number_float_t;
+        using other_number_integer_t = typename BasicJsonType::number_integer_t;
+        using other_number_unsigned_t = typename BasicJsonType::number_unsigned_t;
+        using other_string_t = typename BasicJsonType::string_t;
+        using other_object_t = typename BasicJsonType::object_t;
+        using other_array_t = typename BasicJsonType::array_t;
+
+        switch (val.type())
+        {
+            case value_t::boolean:
+                JSONSerializer<other_boolean_t>::to_json(*this, val.template get<other_boolean_t>());
+                break;
+            case value_t::number_float:
+                JSONSerializer<other_number_float_t>::to_json(*this, val.template get<other_number_float_t>());
+                break;
+            case value_t::number_integer:
+                JSONSerializer<other_number_integer_t>::to_json(*this, val.template get<other_number_integer_t>());
+                break;
+            case value_t::number_unsigned:
+                JSONSerializer<other_number_unsigned_t>::to_json(*this, val.template get<other_number_unsigned_t>());
+                break;
+            case value_t::string:
+                JSONSerializer<other_string_t>::to_json(*this, val.template get_ref<const other_string_t&>());
+                break;
+            case value_t::object:
+                JSONSerializer<other_object_t>::to_json(*this, val.template get_ref<const other_object_t&>());
+                break;
+            case value_t::array:
+                JSONSerializer<other_array_t>::to_json(*this, val.template get_ref<const other_array_t&>());
+                break;
+            case value_t::null:
+                *this = nullptr;
+                break;
+            case value_t::discarded:
+                m_type = value_t::discarded;
+                break;
+        }
+        assert_invariant();
+    }
+
+    /*!
+    @brief create a container (array or object) from an initializer list
+
+    Creates a JSON value of type array or object from the passed initializer
+    list @a init. In case @a type_deduction is `true` (default), the type of
+    the JSON value to be created is deducted from the initializer list @a init
+    according to the following rules:
+
+    1. If the list is empty, an empty JSON object value `{}` is created.
+    2. If the list consists of pairs whose first element is a string, a JSON
+       object value is created where the first elements of the pairs are
+       treated as keys and the second elements are as values.
+    3. In all other cases, an array is created.
+
+    The rules aim to create the best fit between a C++ initializer list and
+    JSON values. The rationale is as follows:
+
+    1. The empty initializer list is written as `{}` which is exactly an empty
+       JSON object.
+    2. C++ has no way of describing mapped types other than to list a list of
+       pairs. As JSON requires that keys must be of type string, rule 2 is the
+       weakest constraint one can pose on initializer lists to interpret them
+       as an object.
+    3. In all other cases, the initializer list could not be interpreted as
+       JSON object type, so interpreting it as JSON array type is safe.
+
+    With the rules described above, the following JSON values cannot be
+    expressed by an initializer list:
+
+    - the empty array (`[]`): use @ref array(initializer_list_t)
+      with an empty initializer list in this case
+    - arrays whose elements satisfy rule 2: use @ref
+      array(initializer_list_t) with the same initializer list
+      in this case
+
+    @note When used without parentheses around an empty initializer list, @ref
+    basic_json() is called instead of this function, yielding the JSON null
+    value.
+
+    @param[in] init  initializer list with JSON values
+
+    @param[in] type_deduction internal parameter; when set to `true`, the type
+    of the JSON value is deducted from the initializer list @a init; when set
+    to `false`, the type provided via @a manual_type is forced. This mode is
+    used by the functions @ref array(initializer_list_t) and
+    @ref object(initializer_list_t).
+
+    @param[in] manual_type internal parameter; when @a type_deduction is set
+    to `false`, the created JSON value will use the provided type (only @ref
+    value_t::array and @ref value_t::object are valid); when @a type_deduction
+    is set to `true`, this parameter has no effect
+
+    @throw type_error.301 if @a type_deduction is `false`, @a manual_type is
+    `value_t::object`, but @a init contains an element which is not a pair
+    whose first element is a string. In this case, the constructor could not
+    create an object. If @a type_deduction would have be `true`, an array
+    would have been created. See @ref object(initializer_list_t)
+    for an example.
+
+    @complexity Linear in the size of the initializer list @a init.
+
+    @exceptionsafety Strong guarantee: if an exception is thrown, there are no
+    changes to any JSON value.
+
+    @liveexample{The example below shows how JSON values are created from
+    initializer lists.,basic_json__list_init_t}
+
+    @sa @ref array(initializer_list_t) -- create a JSON array
+    value from an initializer list
+    @sa @ref object(initializer_list_t) -- create a JSON object
+    value from an initializer list
+
+    @since version 1.0.0
+    */
+    basic_json(initializer_list_t init,
+               bool type_deduction = true,
+               value_t manual_type = value_t::array)
+    {
+        // check if each element is an array with two elements whose first
+        // element is a string
+        bool is_an_object = std::all_of(init.begin(), init.end(),
+                                        [](const detail::json_ref<basic_json>& element_ref)
+        {
+            return (element_ref->is_array() and element_ref->size() == 2 and (*element_ref)[0].is_string());
+        });
+
+        // adjust type if type deduction is not wanted
+        if (not type_deduction)
+        {
+            // if array is wanted, do not create an object though possible
+            if (manual_type == value_t::array)
+            {
+                is_an_object = false;
+            }
+
+            // if object is wanted but impossible, throw an exception
+            if (JSON_UNLIKELY(manual_type == value_t::object and not is_an_object))
+            {
+                JSON_THROW(type_error::create(301, "cannot create object from initializer list"));
+            }
+        }
+
+        if (is_an_object)
+        {
+            // the initializer list is a list of pairs -> create object
+            m_type = value_t::object;
+            m_value = value_t::object;
+
+            std::for_each(init.begin(), init.end(), [this](const detail::json_ref<basic_json>& element_ref)
+            {
+                auto element = element_ref.moved_or_copied();
+                m_value.object->emplace(
+                    std::move(*((*element.m_value.array)[0].m_value.string)),
+                    std::move((*element.m_value.array)[1]));
+            });
+        }
+        else
+        {
+            // the initializer list describes an array -> create array
+            m_type = value_t::array;
+            m_value.array = create<array_t>(init.begin(), init.end());
+        }
+
+        assert_invariant();
+    }
+
+    /*!
+    @brief explicitly create an array from an initializer list
+
+    Creates a JSON array value from a given initializer list. That is, given a
+    list of values `a, b, c`, creates the JSON value `[a, b, c]`. If the
+    initializer list is empty, the empty array `[]` is created.
+
+    @note This function is only needed to express two edge cases that cannot
+    be realized with the initializer list constructor (@ref
+    basic_json(initializer_list_t, bool, value_t)). These cases
+    are:
+    1. creating an array whose elements are all pairs whose first element is a
+    string -- in this case, the initializer list constructor would create an
+    object, taking the first elements as keys
+    2. creating an empty array -- passing the empty initializer list to the
+    initializer list constructor yields an empty object
+
+    @param[in] init  initializer list with JSON values to create an array from
+    (optional)
+
+    @return JSON array value
+
+    @complexity Linear in the size of @a init.
+
+    @exceptionsafety Strong guarantee: if an exception is thrown, there are no
+    changes to any JSON value.
+
+    @liveexample{The following code shows an example for the `array`
+    function.,array}
+
+    @sa @ref basic_json(initializer_list_t, bool, value_t) --
+    create a JSON value from an initializer list
+    @sa @ref object(initializer_list_t) -- create a JSON object
+    value from an initializer list
+
+    @since version 1.0.0
+    */
+    static basic_json array(initializer_list_t init = {})
+    {
+        return basic_json(init, false, value_t::array);
+    }
+
+    /*!
+    @brief explicitly create an object from an initializer list
+
+    Creates a JSON object value from a given initializer list. The initializer
+    lists elements must be pairs, and their first elements must be strings. If
+    the initializer list is empty, the empty object `{}` is created.
+
+    @note This function is only added for symmetry reasons. In contrast to the
+    related function @ref array(initializer_list_t), there are
+    no cases which can only be expressed by this function. That is, any
+    initializer list @a init can also be passed to the initializer list
+    constructor @ref basic_json(initializer_list_t, bool, value_t).
+
+    @param[in] init  initializer list to create an object from (optional)
+
+    @return JSON object value
+
+    @throw type_error.301 if @a init is not a list of pairs whose first
+    elements are strings. In this case, no object can be created. When such a
+    value is passed to @ref basic_json(initializer_list_t, bool, value_t),
+    an array would have been created from the passed initializer list @a init.
+    See example below.
+
+    @complexity Linear in the size of @a init.
+
+    @exceptionsafety Strong guarantee: if an exception is thrown, there are no
+    changes to any JSON value.
+
+    @liveexample{The following code shows an example for the `object`
+    function.,object}
+
+    @sa @ref basic_json(initializer_list_t, bool, value_t) --
+    create a JSON value from an initializer list
+    @sa @ref array(initializer_list_t) -- create a JSON array
+    value from an initializer list
+
+    @since version 1.0.0
+    */
+    static basic_json object(initializer_list_t init = {})
+    {
+        return basic_json(init, false, value_t::object);
+    }
+
+    /*!
+    @brief construct an array with count copies of given value
+
+    Constructs a JSON array value by creating @a cnt copies of a passed value.
+    In case @a cnt is `0`, an empty array is created.
+
+    @param[in] cnt  the number of JSON copies of @a val to create
+    @param[in] val  the JSON value to copy
+
+    @post `std::distance(begin(),end()) == cnt` holds.
+
+    @complexity Linear in @a cnt.
+
+    @exceptionsafety Strong guarantee: if an exception is thrown, there are no
+    changes to any JSON value.
+
+    @liveexample{The following code shows examples for the @ref
+    basic_json(size_type\, const basic_json&)
+    constructor.,basic_json__size_type_basic_json}
+
+    @since version 1.0.0
+    */
+    basic_json(size_type cnt, const basic_json& val)
+        : m_type(value_t::array)
+    {
+        m_value.array = create<array_t>(cnt, val);
+        assert_invariant();
+    }
+
+    /*!
+    @brief construct a JSON container given an iterator range
+
+    Constructs the JSON value with the contents of the range `[first, last)`.
+    The semantics depends on the different types a JSON value can have:
+    - In case of a null type, invalid_iterator.206 is thrown.
+    - In case of other primitive types (number, boolean, or string), @a first
+      must be `begin()` and @a last must be `end()`. In this case, the value is
+      copied. Otherwise, invalid_iterator.204 is thrown.
+    - In case of structured types (array, object), the constructor behaves as
+      similar versions for `std::vector` or `std::map`; that is, a JSON array
+      or object is constructed from the values in the range.
+
+    @tparam InputIT an input iterator type (@ref iterator or @ref
+    const_iterator)
+
+    @param[in] first begin of the range to copy from (included)
+    @param[in] last end of the range to copy from (excluded)
+
+    @pre Iterators @a first and @a last must be initialized. **This
+         precondition is enforced with an assertion (see warning).** If
+         assertions are switched off, a violation of this precondition yields
+         undefined behavior.
+
+    @pre Range `[first, last)` is valid. Usually, this precondition cannot be
+         checked efficiently. Only certain edge cases are detected; see the
+         description of the exceptions below. A violation of this precondition
+         yields undefined behavior.
+
+    @warning A precondition is enforced with a runtime assertion that will
+             result in calling `std::abort` if this precondition is not met.
+             Assertions can be disabled by defining `NDEBUG` at compile time.
+             See https://en.cppreference.com/w/cpp/error/assert for more
+             information.
+
+    @throw invalid_iterator.201 if iterators @a first and @a last are not
+    compatible (i.e., do not belong to the same JSON value). In this case,
+    the range `[first, last)` is undefined.
+    @throw invalid_iterator.204 if iterators @a first and @a last belong to a
+    primitive type (number, boolean, or string), but @a first does not point
+    to the first element any more. In this case, the range `[first, last)` is
+    undefined. See example code below.
+    @throw invalid_iterator.206 if iterators @a first and @a last belong to a
+    null value. In this case, the range `[first, last)` is undefined.
+
+    @complexity Linear in distance between @a first and @a last.
+
+    @exceptionsafety Strong guarantee: if an exception is thrown, there are no
+    changes to any JSON value.
+
+    @liveexample{The example below shows several ways to create JSON values by
+    specifying a subrange with iterators.,basic_json__InputIt_InputIt}
+
+    @since version 1.0.0
+    */
+    template<class InputIT, typename std::enable_if<
+                 std::is_same<InputIT, typename basic_json_t::iterator>::value or
+                 std::is_same<InputIT, typename basic_json_t::const_iterator>::value, int>::type = 0>
+    basic_json(InputIT first, InputIT last)
+    {
+        assert(first.m_object != nullptr);
+        assert(last.m_object != nullptr);
+
+        // make sure iterator fits the current value
+        if (JSON_UNLIKELY(first.m_object != last.m_object))
+        {
+            JSON_THROW(invalid_iterator::create(201, "iterators are not compatible"));
+        }
+
+        // copy type from first iterator
+        m_type = first.m_object->m_type;
+
+        // check if iterator range is complete for primitive values
+        switch (m_type)
+        {
+            case value_t::boolean:
+            case value_t::number_float:
+            case value_t::number_integer:
+            case value_t::number_unsigned:
+            case value_t::string:
+            {
+                if (JSON_UNLIKELY(not first.m_it.primitive_iterator.is_begin()
+                                  or not last.m_it.primitive_iterator.is_end()))
+                {
+                    JSON_THROW(invalid_iterator::create(204, "iterators out of range"));
+                }
+                break;
+            }
+
+            default:
+                break;
+        }
+
+        switch (m_type)
+        {
+            case value_t::number_integer:
+            {
+                m_value.number_integer = first.m_object->m_value.number_integer;
+                break;
+            }
+
+            case value_t::number_unsigned:
+            {
+                m_value.number_unsigned = first.m_object->m_value.number_unsigned;
+                break;
+            }
+
+            case value_t::number_float:
+            {
+                m_value.number_float = first.m_object->m_value.number_float;
+                break;
+            }
+
+            case value_t::boolean:
+            {
+                m_value.boolean = first.m_object->m_value.boolean;
+                break;
+            }
+
+            case value_t::string:
+            {
+                m_value = *first.m_object->m_value.string;
+                break;
+            }
+
+            case value_t::object:
+            {
+                m_value.object = create<object_t>(first.m_it.object_iterator,
+                                                  last.m_it.object_iterator);
+                break;
+            }
+
+            case value_t::array:
+            {
+                m_value.array = create<array_t>(first.m_it.array_iterator,
+                                                last.m_it.array_iterator);
+                break;
+            }
+
+            default:
+                JSON_THROW(invalid_iterator::create(206, "cannot construct with iterators from " +
+                                                    std::string(first.m_object->type_name())));
+        }
+
+        assert_invariant();
+    }
+
+
+    ///////////////////////////////////////
+    // other constructors and destructor //
+    ///////////////////////////////////////
+
+    /// @private
+    basic_json(const detail::json_ref<basic_json>& ref)
+        : basic_json(ref.moved_or_copied())
+    {}
+
+    /*!
+    @brief copy constructor
+
+    Creates a copy of a given JSON value.
+
+    @param[in] other  the JSON value to copy
+
+    @post `*this == other`
+
+    @complexity Linear in the size of @a other.
+
+    @exceptionsafety Strong guarantee: if an exception is thrown, there are no
+    changes to any JSON value.
+
+    @requirement This function helps `basic_json` satisfying the
+    [Container](https://en.cppreference.com/w/cpp/named_req/Container)
+    requirements:
+    - The complexity is linear.
+    - As postcondition, it holds: `other == basic_json(other)`.
+
+    @liveexample{The following code shows an example for the copy
+    constructor.,basic_json__basic_json}
+
+    @since version 1.0.0
+    */
+    basic_json(const basic_json& other)
+        : m_type(other.m_type)
+    {
+        // check of passed value is valid
+        other.assert_invariant();
+
+        switch (m_type)
+        {
+            case value_t::object:
+            {
+                m_value = *other.m_value.object;
+                break;
+            }
+
+            case value_t::array:
+            {
+                m_value = *other.m_value.array;
+                break;
+            }
+
+            case value_t::string:
+            {
+                m_value = *other.m_value.string;
+                break;
+            }
+
+            case value_t::boolean:
+            {
+                m_value = other.m_value.boolean;
+                break;
+            }
+
+            case value_t::number_integer:
+            {
+                m_value = other.m_value.number_integer;
+                break;
+            }
+
+            case value_t::number_unsigned:
+            {
+                m_value = other.m_value.number_unsigned;
+                break;
+            }
+
+            case value_t::number_float:
+            {
+                m_value = other.m_value.number_float;
+                break;
+            }
+
+            default:
+                break;
+        }
+
+        assert_invariant();
+    }
+
+    /*!
+    @brief move constructor
+
+    Move constructor. Constructs a JSON value with the contents of the given
+    value @a other using move semantics. It "steals" the resources from @a
+    other and leaves it as JSON null value.
+
+    @param[in,out] other  value to move to this object
+
+    @post `*this` has the same value as @a other before the call.
+    @post @a other is a JSON null value.
+
+    @complexity Constant.
+
+    @exceptionsafety No-throw guarantee: this constructor never throws
+    exceptions.
+
+    @requirement This function helps `basic_json` satisfying the
+    [MoveConstructible](https://en.cppreference.com/w/cpp/named_req/MoveConstructible)
+    requirements.
+
+    @liveexample{The code below shows the move constructor explicitly called
+    via std::move.,basic_json__moveconstructor}
+
+    @since version 1.0.0
+    */
+    basic_json(basic_json&& other) noexcept
+        : m_type(std::move(other.m_type)),
+          m_value(std::move(other.m_value))
+    {
+        // check that passed value is valid
+        other.assert_invariant();
+
+        // invalidate payload
+        other.m_type = value_t::null;
+        other.m_value = {};
+
+        assert_invariant();
+    }
+
+    /*!
+    @brief copy assignment
+
+    Copy assignment operator. Copies a JSON value via the "copy and swap"
+    strategy: It is expressed in terms of the copy constructor, destructor,
+    and the `swap()` member function.
+
+    @param[in] other  value to copy from
+
+    @complexity Linear.
+
+    @requirement This function helps `basic_json` satisfying the
+    [Container](https://en.cppreference.com/w/cpp/named_req/Container)
+    requirements:
+    - The complexity is linear.
+
+    @liveexample{The code below shows and example for the copy assignment. It
+    creates a copy of value `a` which is then swapped with `b`. Finally\, the
+    copy of `a` (which is the null value after the swap) is
+    destroyed.,basic_json__copyassignment}
+
+    @since version 1.0.0
+    */
+    basic_json& operator=(basic_json other) noexcept (
+        std::is_nothrow_move_constructible<value_t>::value and
+        std::is_nothrow_move_assignable<value_t>::value and
+        std::is_nothrow_move_constructible<json_value>::value and
+        std::is_nothrow_move_assignable<json_value>::value
+    )
+    {
+        // check that passed value is valid
+        other.assert_invariant();
+
+        using std::swap;
+        swap(m_type, other.m_type);
+        swap(m_value, other.m_value);
+
+        assert_invariant();
+        return *this;
+    }
+
+    /*!
+    @brief destructor
+
+    Destroys the JSON value and frees all allocated memory.
+
+    @complexity Linear.
+
+    @requirement This function helps `basic_json` satisfying the
+    [Container](https://en.cppreference.com/w/cpp/named_req/Container)
+    requirements:
+    - The complexity is linear.
+    - All stored elements are destroyed and all memory is freed.
+
+    @since version 1.0.0
+    */
+    ~basic_json() noexcept
+    {
+        assert_invariant();
+        m_value.destroy(m_type);
+    }
+
+    /// @}
+
+  public:
+    ///////////////////////
+    // object inspection //
+    ///////////////////////
+
+    /// @name object inspection
+    /// Functions to inspect the type of a JSON value.
+    /// @{
+
+    /*!
+    @brief serialization
+
+    Serialization function for JSON values. The function tries to mimic
+    Python's `json.dumps()` function, and currently supports its @a indent
+    and @a ensure_ascii parameters.
+
+    @param[in] indent If indent is nonnegative, then array elements and object
+    members will be pretty-printed with that indent level. An indent level of
+    `0` will only insert newlines. `-1` (the default) selects the most compact
+    representation.
+    @param[in] indent_char The character to use for indentation if @a indent is
+    greater than `0`. The default is ` ` (space).
+    @param[in] ensure_ascii If @a ensure_ascii is true, all non-ASCII characters
+    in the output are escaped with `\uXXXX` sequences, and the result consists
+    of ASCII characters only.
+    @param[in] error_handler  how to react on decoding errors; there are three
+    possible values: `strict` (throws and exception in case a decoding error
+    occurs; default), `replace` (replace invalid UTF-8 sequences with U+FFFD),
+    and `ignore` (ignore invalid UTF-8 sequences during serialization).
+
+    @return string containing the serialization of the JSON value
+
+    @throw type_error.316 if a string stored inside the JSON value is not
+                          UTF-8 encoded
+
+    @complexity Linear.
+
+    @exceptionsafety Strong guarantee: if an exception is thrown, there are no
+    changes in the JSON value.
+
+    @liveexample{The following example shows the effect of different @a indent\,
+    @a indent_char\, and @a ensure_ascii parameters to the result of the
+    serialization.,dump}
+
+    @see https://docs.python.org/2/library/json.html#json.dump
+
+    @since version 1.0.0; indentation character @a indent_char, option
+           @a ensure_ascii and exceptions added in version 3.0.0; error
+           handlers added in version 3.4.0.
+    */
+    string_t dump(const int indent = -1,
+                  const char indent_char = ' ',
+                  const bool ensure_ascii = false,
+                  const error_handler_t error_handler = error_handler_t::strict) const
+    {
+        string_t result;
+        serializer s(detail::output_adapter<char, string_t>(result), indent_char, error_handler);
+
+        if (indent >= 0)
+        {
+            s.dump(*this, true, ensure_ascii, static_cast<unsigned int>(indent));
+        }
+        else
+        {
+            s.dump(*this, false, ensure_ascii, 0);
+        }
+
+        return result;
+    }
+
+    /*!
+    @brief return the type of the JSON value (explicit)
+
+    Return the type of the JSON value as a value from the @ref value_t
+    enumeration.
+
+    @return the type of the JSON value
+            Value type                | return value
+            ------------------------- | -------------------------
+            null                      | value_t::null
+            boolean                   | value_t::boolean
+            string                    | value_t::string
+            number (integer)          | value_t::number_integer
+            number (unsigned integer) | value_t::number_unsigned
+            number (floating-point)   | value_t::number_float
+            object                    | value_t::object
+            array                     | value_t::array
+            discarded                 | value_t::discarded
+
+    @complexity Constant.
+
+    @exceptionsafety No-throw guarantee: this member function never throws
+    exceptions.
+
+    @liveexample{The following code exemplifies `type()` for all JSON
+    types.,type}
+
+    @sa @ref operator value_t() -- return the type of the JSON value (implicit)
+    @sa @ref type_name() -- return the type as string
+
+    @since version 1.0.0
+    */
+    constexpr value_t type() const noexcept
+    {
+        return m_type;
+    }
+
+    /*!
+    @brief return whether type is primitive
+
+    This function returns true if and only if the JSON type is primitive
+    (string, number, boolean, or null).
+
+    @return `true` if type is primitive (string, number, boolean, or null),
+    `false` otherwise.
+
+    @complexity Constant.
+
+    @exceptionsafety No-throw guarantee: this member function never throws
+    exceptions.
+
+    @liveexample{The following code exemplifies `is_primitive()` for all JSON
+    types.,is_primitive}
+
+    @sa @ref is_structured() -- returns whether JSON value is structured
+    @sa @ref is_null() -- returns whether JSON value is `null`
+    @sa @ref is_string() -- returns whether JSON value is a string
+    @sa @ref is_boolean() -- returns whether JSON value is a boolean
+    @sa @ref is_number() -- returns whether JSON value is a number
+
+    @since version 1.0.0
+    */
+    constexpr bool is_primitive() const noexcept
+    {
+        return is_null() or is_string() or is_boolean() or is_number();
+    }
+
+    /*!
+    @brief return whether type is structured
+
+    This function returns true if and only if the JSON type is structured
+    (array or object).
+
+    @return `true` if type is structured (array or object), `false` otherwise.
+
+    @complexity Constant.
+
+    @exceptionsafety No-throw guarantee: this member function never throws
+    exceptions.
+
+    @liveexample{The following code exemplifies `is_structured()` for all JSON
+    types.,is_structured}
+
+    @sa @ref is_primitive() -- returns whether value is primitive
+    @sa @ref is_array() -- returns whether value is an array
+    @sa @ref is_object() -- returns whether value is an object
+
+    @since version 1.0.0
+    */
+    constexpr bool is_structured() const noexcept
+    {
+        return is_array() or is_object();
+    }
+
+    /*!
+    @brief return whether value is null
+
+    This function returns true if and only if the JSON value is null.
+
+    @return `true` if type is null, `false` otherwise.
+
+    @complexity Constant.
+
+    @exceptionsafety No-throw guarantee: this member function never throws
+    exceptions.
+
+    @liveexample{The following code exemplifies `is_null()` for all JSON
+    types.,is_null}
+
+    @since version 1.0.0
+    */
+    constexpr bool is_null() const noexcept
+    {
+        return (m_type == value_t::null);
+    }
+
+    /*!
+    @brief return whether value is a boolean
+
+    This function returns true if and only if the JSON value is a boolean.
+
+    @return `true` if type is boolean, `false` otherwise.
+
+    @complexity Constant.
+
+    @exceptionsafety No-throw guarantee: this member function never throws
+    exceptions.
+
+    @liveexample{The following code exemplifies `is_boolean()` for all JSON
+    types.,is_boolean}
+
+    @since version 1.0.0
+    */
+    constexpr bool is_boolean() const noexcept
+    {
+        return (m_type == value_t::boolean);
+    }
+
+    /*!
+    @brief return whether value is a number
+
+    This function returns true if and only if the JSON value is a number. This
+    includes both integer (signed and unsigned) and floating-point values.
+
+    @return `true` if type is number (regardless whether integer, unsigned
+    integer or floating-type), `false` otherwise.
+
+    @complexity Constant.
+
+    @exceptionsafety No-throw guarantee: this member function never throws
+    exceptions.
+
+    @liveexample{The following code exemplifies `is_number()` for all JSON
+    types.,is_number}
+
+    @sa @ref is_number_integer() -- check if value is an integer or unsigned
+    integer number
+    @sa @ref is_number_unsigned() -- check if value is an unsigned integer
+    number
+    @sa @ref is_number_float() -- check if value is a floating-point number
+
+    @since version 1.0.0
+    */
+    constexpr bool is_number() const noexcept
+    {
+        return is_number_integer() or is_number_float();
+    }
+
+    /*!
+    @brief return whether value is an integer number
+
+    This function returns true if and only if the JSON value is a signed or
+    unsigned integer number. This excludes floating-point values.
+
+    @return `true` if type is an integer or unsigned integer number, `false`
+    otherwise.
+
+    @complexity Constant.
+
+    @exceptionsafety No-throw guarantee: this member function never throws
+    exceptions.
+
+    @liveexample{The following code exemplifies `is_number_integer()` for all
+    JSON types.,is_number_integer}
+
+    @sa @ref is_number() -- check if value is a number
+    @sa @ref is_number_unsigned() -- check if value is an unsigned integer
+    number
+    @sa @ref is_number_float() -- check if value is a floating-point number
+
+    @since version 1.0.0
+    */
+    constexpr bool is_number_integer() const noexcept
+    {
+        return (m_type == value_t::number_integer or m_type == value_t::number_unsigned);
+    }
+
+    /*!
+    @brief return whether value is an unsigned integer number
+
+    This function returns true if and only if the JSON value is an unsigned
+    integer number. This excludes floating-point and signed integer values.
+
+    @return `true` if type is an unsigned integer number, `false` otherwise.
+
+    @complexity Constant.
+
+    @exceptionsafety No-throw guarantee: this member function never throws
+    exceptions.
+
+    @liveexample{The following code exemplifies `is_number_unsigned()` for all
+    JSON types.,is_number_unsigned}
+
+    @sa @ref is_number() -- check if value is a number
+    @sa @ref is_number_integer() -- check if value is an integer or unsigned
+    integer number
+    @sa @ref is_number_float() -- check if value is a floating-point number
+
+    @since version 2.0.0
+    */
+    constexpr bool is_number_unsigned() const noexcept
+    {
+        return (m_type == value_t::number_unsigned);
+    }
+
+    /*!
+    @brief return whether value is a floating-point number
+
+    This function returns true if and only if the JSON value is a
+    floating-point number. This excludes signed and unsigned integer values.
+
+    @return `true` if type is a floating-point number, `false` otherwise.
+
+    @complexity Constant.
+
+    @exceptionsafety No-throw guarantee: this member function never throws
+    exceptions.
+
+    @liveexample{The following code exemplifies `is_number_float()` for all
+    JSON types.,is_number_float}
+
+    @sa @ref is_number() -- check if value is number
+    @sa @ref is_number_integer() -- check if value is an integer number
+    @sa @ref is_number_unsigned() -- check if value is an unsigned integer
+    number
+
+    @since version 1.0.0
+    */
+    constexpr bool is_number_float() const noexcept
+    {
+        return (m_type == value_t::number_float);
+    }
+
+    /*!
+    @brief return whether value is an object
+
+    This function returns true if and only if the JSON value is an object.
+
+    @return `true` if type is object, `false` otherwise.
+
+    @complexity Constant.
+
+    @exceptionsafety No-throw guarantee: this member function never throws
+    exceptions.
+
+    @liveexample{The following code exemplifies `is_object()` for all JSON
+    types.,is_object}
+
+    @since version 1.0.0
+    */
+    constexpr bool is_object() const noexcept
+    {
+        return (m_type == value_t::object);
+    }
+
+    /*!
+    @brief return whether value is an array
+
+    This function returns true if and only if the JSON value is an array.
+
+    @return `true` if type is array, `false` otherwise.
+
+    @complexity Constant.
+
+    @exceptionsafety No-throw guarantee: this member function never throws
+    exceptions.
+
+    @liveexample{The following code exemplifies `is_array()` for all JSON
+    types.,is_array}
+
+    @since version 1.0.0
+    */
+    constexpr bool is_array() const noexcept
+    {
+        return (m_type == value_t::array);
+    }
+
+    /*!
+    @brief return whether value is a string
+
+    This function returns true if and only if the JSON value is a string.
+
+    @return `true` if type is string, `false` otherwise.
+
+    @complexity Constant.
+
+    @exceptionsafety No-throw guarantee: this member function never throws
+    exceptions.
+
+    @liveexample{The following code exemplifies `is_string()` for all JSON
+    types.,is_string}
+
+    @since version 1.0.0
+    */
+    constexpr bool is_string() const noexcept
+    {
+        return (m_type == value_t::string);
+    }
+
+    /*!
+    @brief return whether value is discarded
+
+    This function returns true if and only if the JSON value was discarded
+    during parsing with a callback function (see @ref parser_callback_t).
+
+    @note This function will always be `false` for JSON values after parsing.
+    That is, discarded values can only occur during parsing, but will be
+    removed when inside a structured value or replaced by null in other cases.
+
+    @return `true` if type is discarded, `false` otherwise.
+
+    @complexity Constant.
+
+    @exceptionsafety No-throw guarantee: this member function never throws
+    exceptions.
+
+    @liveexample{The following code exemplifies `is_discarded()` for all JSON
+    types.,is_discarded}
+
+    @since version 1.0.0
+    */
+    constexpr bool is_discarded() const noexcept
+    {
+        return (m_type == value_t::discarded);
+    }
+
+    /*!
+    @brief return the type of the JSON value (implicit)
+
+    Implicitly return the type of the JSON value as a value from the @ref
+    value_t enumeration.
+
+    @return the type of the JSON value
+
+    @complexity Constant.
+
+    @exceptionsafety No-throw guarantee: this member function never throws
+    exceptions.
+
+    @liveexample{The following code exemplifies the @ref value_t operator for
+    all JSON types.,operator__value_t}
+
+    @sa @ref type() -- return the type of the JSON value (explicit)
+    @sa @ref type_name() -- return the type as string
+
+    @since version 1.0.0
+    */
+    constexpr operator value_t() const noexcept
+    {
+        return m_type;
+    }
+
+    /// @}
+
+  private:
+    //////////////////
+    // value access //
+    //////////////////
+
+    /// get a boolean (explicit)
+    boolean_t get_impl(boolean_t* /*unused*/) const
+    {
+        if (JSON_LIKELY(is_boolean()))
+        {
+            return m_value.boolean;
+        }
+
+        JSON_THROW(type_error::create(302, "type must be boolean, but is " + std::string(type_name())));
+    }
+
+    /// get a pointer to the value (object)
+    object_t* get_impl_ptr(object_t* /*unused*/) noexcept
+    {
+        return is_object() ? m_value.object : nullptr;
+    }
+
+    /// get a pointer to the value (object)
+    constexpr const object_t* get_impl_ptr(const object_t* /*unused*/) const noexcept
+    {
+        return is_object() ? m_value.object : nullptr;
+    }
+
+    /// get a pointer to the value (array)
+    array_t* get_impl_ptr(array_t* /*unused*/) noexcept
+    {
+        return is_array() ? m_value.array : nullptr;
+    }
+
+    /// get a pointer to the value (array)
+    constexpr const array_t* get_impl_ptr(const array_t* /*unused*/) const noexcept
+    {
+        return is_array() ? m_value.array : nullptr;
+    }
+
+    /// get a pointer to the value (string)
+    string_t* get_impl_ptr(string_t* /*unused*/) noexcept
+    {
+        return is_string() ? m_value.string : nullptr;
+    }
+
+    /// get a pointer to the value (string)
+    constexpr const string_t* get_impl_ptr(const string_t* /*unused*/) const noexcept
+    {
+        return is_string() ? m_value.string : nullptr;
+    }
+
+    /// get a pointer to the value (boolean)
+    boolean_t* get_impl_ptr(boolean_t* /*unused*/) noexcept
+    {
+        return is_boolean() ? &m_value.boolean : nullptr;
+    }
+
+    /// get a pointer to the value (boolean)
+    constexpr const boolean_t* get_impl_ptr(const boolean_t* /*unused*/) const noexcept
+    {
+        return is_boolean() ? &m_value.boolean : nullptr;
+    }
+
+    /// get a pointer to the value (integer number)
+    number_integer_t* get_impl_ptr(number_integer_t* /*unused*/) noexcept
+    {
+        return is_number_integer() ? &m_value.number_integer : nullptr;
+    }
+
+    /// get a pointer to the value (integer number)
+    constexpr const number_integer_t* get_impl_ptr(const number_integer_t* /*unused*/) const noexcept
+    {
+        return is_number_integer() ? &m_value.number_integer : nullptr;
+    }
+
+    /// get a pointer to the value (unsigned number)
+    number_unsigned_t* get_impl_ptr(number_unsigned_t* /*unused*/) noexcept
+    {
+        return is_number_unsigned() ? &m_value.number_unsigned : nullptr;
+    }
+
+    /// get a pointer to the value (unsigned number)
+    constexpr const number_unsigned_t* get_impl_ptr(const number_unsigned_t* /*unused*/) const noexcept
+    {
+        return is_number_unsigned() ? &m_value.number_unsigned : nullptr;
+    }
+
+    /// get a pointer to the value (floating-point number)
+    number_float_t* get_impl_ptr(number_float_t* /*unused*/) noexcept
+    {
+        return is_number_float() ? &m_value.number_float : nullptr;
+    }
+
+    /// get a pointer to the value (floating-point number)
+    constexpr const number_float_t* get_impl_ptr(const number_float_t* /*unused*/) const noexcept
+    {
+        return is_number_float() ? &m_value.number_float : nullptr;
+    }
+
+    /*!
+    @brief helper function to implement get_ref()
+
+    This function helps to implement get_ref() without code duplication for
+    const and non-const overloads
+
+    @tparam ThisType will be deduced as `basic_json` or `const basic_json`
+
+    @throw type_error.303 if ReferenceType does not match underlying value
+    type of the current JSON
+    */
+    template<typename ReferenceType, typename ThisType>
+    static ReferenceType get_ref_impl(ThisType& obj)
+    {
+        // delegate the call to get_ptr<>()
+        auto ptr = obj.template get_ptr<typename std::add_pointer<ReferenceType>::type>();
+
+        if (JSON_LIKELY(ptr != nullptr))
+        {
+            return *ptr;
+        }
+
+        JSON_THROW(type_error::create(303, "incompatible ReferenceType for get_ref, actual type is " + std::string(obj.type_name())));
+    }
+
+  public:
+    /// @name value access
+    /// Direct access to the stored value of a JSON value.
+    /// @{
+
+    /*!
+    @brief get special-case overload
+
+    This overloads avoids a lot of template boilerplate, it can be seen as the
+    identity method
+
+    @tparam BasicJsonType == @ref basic_json
+
+    @return a copy of *this
+
+    @complexity Constant.
+
+    @since version 2.1.0
+    */
+    template<typename BasicJsonType, detail::enable_if_t<
+                 std::is_same<typename std::remove_const<BasicJsonType>::type, basic_json_t>::value,
+                 int> = 0>
+    basic_json get() const
+    {
+        return *this;
+    }
+
+    /*!
+    @brief get special-case overload
+
+    This overloads converts the current @ref basic_json in a different
+    @ref basic_json type
+
+    @tparam BasicJsonType == @ref basic_json
+
+    @return a copy of *this, converted into @tparam BasicJsonType
+
+    @complexity Depending on the implementation of the called `from_json()`
+                method.
+
+    @since version 3.2.0
+    */
+    template<typename BasicJsonType, detail::enable_if_t<
+                 not std::is_same<BasicJsonType, basic_json>::value and
+                 detail::is_basic_json<BasicJsonType>::value, int> = 0>
+    BasicJsonType get() const
+    {
+        return *this;
+    }
+
+    /*!
+    @brief get a value (explicit)
+
+    Explicit type conversion between the JSON value and a compatible value
+    which is [CopyConstructible](https://en.cppreference.com/w/cpp/named_req/CopyConstructible)
+    and [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible).
+    The value is converted by calling the @ref json_serializer<ValueType>
+    `from_json()` method.
+
+    The function is equivalent to executing
+    @code {.cpp}
+    ValueType ret;
+    JSONSerializer<ValueType>::from_json(*this, ret);
+    return ret;
+    @endcode
+
+    This overloads is chosen if:
+    - @a ValueType is not @ref basic_json,
+    - @ref json_serializer<ValueType> has a `from_json()` method of the form
+      `void from_json(const basic_json&, ValueType&)`, and
+    - @ref json_serializer<ValueType> does not have a `from_json()` method of
+      the form `ValueType from_json(const basic_json&)`
+
+    @tparam ValueTypeCV the provided value type
+    @tparam ValueType the returned value type
+
+    @return copy of the JSON value, converted to @a ValueType
+
+    @throw what @ref json_serializer<ValueType> `from_json()` method throws
+
+    @liveexample{The example below shows several conversions from JSON values
+    to other types. There a few things to note: (1) Floating-point numbers can
+    be converted to integers\, (2) A JSON array can be converted to a standard
+    `std::vector<short>`\, (3) A JSON object can be converted to C++
+    associative containers such as `std::unordered_map<std::string\,
+    json>`.,get__ValueType_const}
+
+    @since version 2.1.0
+    */
+    template<typename ValueTypeCV, typename ValueType = detail::uncvref_t<ValueTypeCV>,
+             detail::enable_if_t <
+                 not detail::is_basic_json<ValueType>::value and
+                 detail::has_from_json<basic_json_t, ValueType>::value and
+                 not detail::has_non_default_from_json<basic_json_t, ValueType>::value,
+                 int> = 0>
+    ValueType get() const noexcept(noexcept(
+                                       JSONSerializer<ValueType>::from_json(std::declval<const basic_json_t&>(), std::declval<ValueType&>())))
+    {
+        // we cannot static_assert on ValueTypeCV being non-const, because
+        // there is support for get<const basic_json_t>(), which is why we
+        // still need the uncvref
+        static_assert(not std::is_reference<ValueTypeCV>::value,
+                      "get() cannot be used with reference types, you might want to use get_ref()");
+        static_assert(std::is_default_constructible<ValueType>::value,
+                      "types must be DefaultConstructible when used with get()");
+
+        ValueType ret;
+        JSONSerializer<ValueType>::from_json(*this, ret);
+        return ret;
+    }
+
+    /*!
+    @brief get a value (explicit); special case
+
+    Explicit type conversion between the JSON value and a compatible value
+    which is **not** [CopyConstructible](https://en.cppreference.com/w/cpp/named_req/CopyConstructible)
+    and **not** [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible).
+    The value is converted by calling the @ref json_serializer<ValueType>
+    `from_json()` method.
+
+    The function is equivalent to executing
+    @code {.cpp}
+    return JSONSerializer<ValueTypeCV>::from_json(*this);
+    @endcode
+
+    This overloads is chosen if:
+    - @a ValueType is not @ref basic_json and
+    - @ref json_serializer<ValueType> has a `from_json()` method of the form
+      `ValueType from_json(const basic_json&)`
+
+    @note If @ref json_serializer<ValueType> has both overloads of
+    `from_json()`, this one is chosen.
+
+    @tparam ValueTypeCV the provided value type
+    @tparam ValueType the returned value type
+
+    @return copy of the JSON value, converted to @a ValueType
+
+    @throw what @ref json_serializer<ValueType> `from_json()` method throws
+
+    @since version 2.1.0
+    */
+    template<typename ValueTypeCV, typename ValueType = detail::uncvref_t<ValueTypeCV>,
+             detail::enable_if_t<not std::is_same<basic_json_t, ValueType>::value and
+                                 detail::has_non_default_from_json<basic_json_t, ValueType>::value,
+                                 int> = 0>
+    ValueType get() const noexcept(noexcept(
+                                       JSONSerializer<ValueTypeCV>::from_json(std::declval<const basic_json_t&>())))
+    {
+        static_assert(not std::is_reference<ValueTypeCV>::value,
+                      "get() cannot be used with reference types, you might want to use get_ref()");
+        return JSONSerializer<ValueTypeCV>::from_json(*this);
+    }
+
+    /*!
+    @brief get a value (explicit)
+
+    Explicit type conversion between the JSON value and a compatible value.
+    The value is filled into the input parameter by calling the @ref json_serializer<ValueType>
+    `from_json()` method.
+
+    The function is equivalent to executing
+    @code {.cpp}
+    ValueType v;
+    JSONSerializer<ValueType>::from_json(*this, v);
+    @endcode
+
+    This overloads is chosen if:
+    - @a ValueType is not @ref basic_json,
+    - @ref json_serializer<ValueType> has a `from_json()` method of the form
+      `void from_json(const basic_json&, ValueType&)`, and
+
+    @tparam ValueType the input parameter type.
+
+    @return the input parameter, allowing chaining calls.
+
+    @throw what @ref json_serializer<ValueType> `from_json()` method throws
+
+    @liveexample{The example below shows several conversions from JSON values
+    to other types. There a few things to note: (1) Floating-point numbers can
+    be converted to integers\, (2) A JSON array can be converted to a standard
+    `std::vector<short>`\, (3) A JSON object can be converted to C++
+    associative containers such as `std::unordered_map<std::string\,
+    json>`.,get_to}
+
+    @since version 3.3.0
+    */
+    template<typename ValueType,
+             detail::enable_if_t <
+                 not detail::is_basic_json<ValueType>::value and
+                 detail::has_from_json<basic_json_t, ValueType>::value,
+                 int> = 0>
+    ValueType & get_to(ValueType& v) const noexcept(noexcept(
+                JSONSerializer<ValueType>::from_json(std::declval<const basic_json_t&>(), v)))
+    {
+        JSONSerializer<ValueType>::from_json(*this, v);
+        return v;
+    }
+
+
+    /*!
+    @brief get a pointer value (implicit)
+
+    Implicit pointer access to the internally stored JSON value. No copies are
+    made.
+
+    @warning Writing data to the pointee of the result yields an undefined
+    state.
+
+    @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref
+    object_t, @ref string_t, @ref boolean_t, @ref number_integer_t,
+    @ref number_unsigned_t, or @ref number_float_t. Enforced by a static
+    assertion.
+
+    @return pointer to the internally stored JSON value if the requested
+    pointer type @a PointerType fits to the JSON value; `nullptr` otherwise
+
+    @complexity Constant.
+
+    @liveexample{The example below shows how pointers to internal values of a
+    JSON value can be requested. Note that no type conversions are made and a
+    `nullptr` is returned if the value and the requested pointer type does not
+    match.,get_ptr}
+
+    @since version 1.0.0
+    */
+    template<typename PointerType, typename std::enable_if<
+                 std::is_pointer<PointerType>::value, int>::type = 0>
+    auto get_ptr() noexcept -> decltype(std::declval<basic_json_t&>().get_impl_ptr(std::declval<PointerType>()))
+    {
+        // delegate the call to get_impl_ptr<>()
+        return get_impl_ptr(static_cast<PointerType>(nullptr));
+    }
+
+    /*!
+    @brief get a pointer value (implicit)
+    @copydoc get_ptr()
+    */
+    template<typename PointerType, typename std::enable_if<
+                 std::is_pointer<PointerType>::value and
+                 std::is_const<typename std::remove_pointer<PointerType>::type>::value, int>::type = 0>
+    constexpr auto get_ptr() const noexcept -> decltype(std::declval<const basic_json_t&>().get_impl_ptr(std::declval<PointerType>()))
+    {
+        // delegate the call to get_impl_ptr<>() const
+        return get_impl_ptr(static_cast<PointerType>(nullptr));
+    }
+
+    /*!
+    @brief get a pointer value (explicit)
+
+    Explicit pointer access to the internally stored JSON value. No copies are
+    made.
+
+    @warning The pointer becomes invalid if the underlying JSON object
+    changes.
+
+    @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref
+    object_t, @ref string_t, @ref boolean_t, @ref number_integer_t,
+    @ref number_unsigned_t, or @ref number_float_t.
+
+    @return pointer to the internally stored JSON value if the requested
+    pointer type @a PointerType fits to the JSON value; `nullptr` otherwise
+
+    @complexity Constant.
+
+    @liveexample{The example below shows how pointers to internal values of a
+    JSON value can be requested. Note that no type conversions are made and a
+    `nullptr` is returned if the value and the requested pointer type does not
+    match.,get__PointerType}
+
+    @sa @ref get_ptr() for explicit pointer-member access
+
+    @since version 1.0.0
+    */
+    template<typename PointerType, typename std::enable_if<
+                 std::is_pointer<PointerType>::value, int>::type = 0>
+    auto get() noexcept -> decltype(std::declval<basic_json_t&>().template get_ptr<PointerType>())
+    {
+        // delegate the call to get_ptr
+        return get_ptr<PointerType>();
+    }
+
+    /*!
+    @brief get a pointer value (explicit)
+    @copydoc get()
+    */
+    template<typename PointerType, typename std::enable_if<
+                 std::is_pointer<PointerType>::value, int>::type = 0>
+    constexpr auto get() const noexcept -> decltype(std::declval<const basic_json_t&>().template get_ptr<PointerType>())
+    {
+        // delegate the call to get_ptr
+        return get_ptr<PointerType>();
+    }
+
+    /*!
+    @brief get a reference value (implicit)
+
+    Implicit reference access to the internally stored JSON value. No copies
+    are made.
+
+    @warning Writing data to the referee of the result yields an undefined
+    state.
+
+    @tparam ReferenceType reference type; must be a reference to @ref array_t,
+    @ref object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, or
+    @ref number_float_t. Enforced by static assertion.
+
+    @return reference to the internally stored JSON value if the requested
+    reference type @a ReferenceType fits to the JSON value; throws
+    type_error.303 otherwise
+
+    @throw type_error.303 in case passed type @a ReferenceType is incompatible
+    with the stored JSON value; see example below
+
+    @complexity Constant.
+
+    @liveexample{The example shows several calls to `get_ref()`.,get_ref}
+
+    @since version 1.1.0
+    */
+    template<typename ReferenceType, typename std::enable_if<
+                 std::is_reference<ReferenceType>::value, int>::type = 0>
+    ReferenceType get_ref()
+    {
+        // delegate call to get_ref_impl
+        return get_ref_impl<ReferenceType>(*this);
+    }
+
+    /*!
+    @brief get a reference value (implicit)
+    @copydoc get_ref()
+    */
+    template<typename ReferenceType, typename std::enable_if<
+                 std::is_reference<ReferenceType>::value and
+                 std::is_const<typename std::remove_reference<ReferenceType>::type>::value, int>::type = 0>
+    ReferenceType get_ref() const
+    {
+        // delegate call to get_ref_impl
+        return get_ref_impl<ReferenceType>(*this);
+    }
+
+    /*!
+    @brief get a value (implicit)
+
+    Implicit type conversion between the JSON value and a compatible value.
+    The call is realized by calling @ref get() const.
+
+    @tparam ValueType non-pointer type compatible to the JSON value, for
+    instance `int` for JSON integer numbers, `bool` for JSON booleans, or
+    `std::vector` types for JSON arrays. The character type of @ref string_t
+    as well as an initializer list of this type is excluded to avoid
+    ambiguities as these types implicitly convert to `std::string`.
+
+    @return copy of the JSON value, converted to type @a ValueType
+
+    @throw type_error.302 in case passed type @a ValueType is incompatible
+    to the JSON value type (e.g., the JSON value is of type boolean, but a
+    string is requested); see example below
+
+    @complexity Linear in the size of the JSON value.
+
+    @liveexample{The example below shows several conversions from JSON values
+    to other types. There a few things to note: (1) Floating-point numbers can
+    be converted to integers\, (2) A JSON array can be converted to a standard
+    `std::vector<short>`\, (3) A JSON object can be converted to C++
+    associative containers such as `std::unordered_map<std::string\,
+    json>`.,operator__ValueType}
+
+    @since version 1.0.0
+    */
+    template < typename ValueType, typename std::enable_if <
+                   not std::is_pointer<ValueType>::value and
+                   not std::is_same<ValueType, detail::json_ref<basic_json>>::value and
+                   not std::is_same<ValueType, typename string_t::value_type>::value and
+                   not detail::is_basic_json<ValueType>::value
+
+#ifndef _MSC_VER  // fix for issue #167 operator<< ambiguity under VS2015
+                   and not std::is_same<ValueType, std::initializer_list<typename string_t::value_type>>::value
+#if defined(JSON_HAS_CPP_17) && defined(_MSC_VER) and _MSC_VER <= 1914
+                   and not std::is_same<ValueType, typename std::string_view>::value
+#endif
+#endif
+                   and detail::is_detected<detail::get_template_function, const basic_json_t&, ValueType>::value
+                   , int >::type = 0 >
+    operator ValueType() const
+    {
+        // delegate the call to get<>() const
+        return get<ValueType>();
+    }
+
+    /// @}
+
+
+    ////////////////////
+    // element access //
+    ////////////////////
+
+    /// @name element access
+    /// Access to the JSON value.
+    /// @{
+
+    /*!
+    @brief access specified array element with bounds checking
+
+    Returns a reference to the element at specified location @a idx, with
+    bounds checking.
+
+    @param[in] idx  index of the element to access
+
+    @return reference to the element at index @a idx
+
+    @throw type_error.304 if the JSON value is not an array; in this case,
+    calling `at` with an index makes no sense. See example below.
+    @throw out_of_range.401 if the index @a idx is out of range of the array;
+    that is, `idx >= size()`. See example below.
+
+    @exceptionsafety Strong guarantee: if an exception is thrown, there are no
+    changes in the JSON value.
+
+    @complexity Constant.
+
+    @since version 1.0.0
+
+    @liveexample{The example below shows how array elements can be read and
+    written using `at()`. It also demonstrates the different exceptions that
+    can be thrown.,at__size_type}
+    */
+    reference at(size_type idx)
+    {
+        // at only works for arrays
+        if (JSON_LIKELY(is_array()))
+        {
+            JSON_TRY
+            {
+                return m_value.array->at(idx);
+            }
+            JSON_CATCH (std::out_of_range&)
+            {
+                // create better exception explanation
+                JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range"));
+            }
+        }
+        else
+        {
+            JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name())));
+        }
+    }
+
+    /*!
+    @brief access specified array element with bounds checking
+
+    Returns a const reference to the element at specified location @a idx,
+    with bounds checking.
+
+    @param[in] idx  index of the element to access
+
+    @return const reference to the element at index @a idx
+
+    @throw type_error.304 if the JSON value is not an array; in this case,
+    calling `at` with an index makes no sense. See example below.
+    @throw out_of_range.401 if the index @a idx is out of range of the array;
+    that is, `idx >= size()`. See example below.
+
+    @exceptionsafety Strong guarantee: if an exception is thrown, there are no
+    changes in the JSON value.
+
+    @complexity Constant.
+
+    @since version 1.0.0
+
+    @liveexample{The example below shows how array elements can be read using
+    `at()`. It also demonstrates the different exceptions that can be thrown.,
+    at__size_type_const}
+    */
+    const_reference at(size_type idx) const
+    {
+        // at only works for arrays
+        if (JSON_LIKELY(is_array()))
+        {
+            JSON_TRY
+            {
+                return m_value.array->at(idx);
+            }
+            JSON_CATCH (std::out_of_range&)
+            {
+                // create better exception explanation
+                JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range"));
+            }
+        }
+        else
+        {
+            JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name())));
+        }
+    }
+
+    /*!
+    @brief access specified object element with bounds checking
+
+    Returns a reference to the element at with specified key @a key, with
+    bounds checking.
+
+    @param[in] key  key of the element to access
+
+    @return reference to the element at key @a key
+
+    @throw type_error.304 if the JSON value is not an object; in this case,
+    calling `at` with a key makes no sense. See example below.
+    @throw out_of_range.403 if the key @a key is is not stored in the object;
+    that is, `find(key) == end()`. See example below.
+
+    @exceptionsafety Strong guarantee: if an exception is thrown, there are no
+    changes in the JSON value.
+
+    @complexity Logarithmic in the size of the container.
+
+    @sa @ref operator[](const typename object_t::key_type&) for unchecked
+    access by reference
+    @sa @ref value() for access by value with a default value
+
+    @since version 1.0.0
+
+    @liveexample{The example below shows how object elements can be read and
+    written using `at()`. It also demonstrates the different exceptions that
+    can be thrown.,at__object_t_key_type}
+    */
+    reference at(const typename object_t::key_type& key)
+    {
+        // at only works for objects
+        if (JSON_LIKELY(is_object()))
+        {
+            JSON_TRY
+            {
+                return m_value.object->at(key);
+            }
+            JSON_CATCH (std::out_of_range&)
+            {
+                // create better exception explanation
+                JSON_THROW(out_of_range::create(403, "key '" + key + "' not found"));
+            }
+        }
+        else
+        {
+            JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name())));
+        }
+    }
+
+    /*!
+    @brief access specified object element with bounds checking
+
+    Returns a const reference to the element at with specified key @a key,
+    with bounds checking.
+
+    @param[in] key  key of the element to access
+
+    @return const reference to the element at key @a key
+
+    @throw type_error.304 if the JSON value is not an object; in this case,
+    calling `at` with a key makes no sense. See example below.
+    @throw out_of_range.403 if the key @a key is is not stored in the object;
+    that is, `find(key) == end()`. See example below.
+
+    @exceptionsafety Strong guarantee: if an exception is thrown, there are no
+    changes in the JSON value.
+
+    @complexity Logarithmic in the size of the container.
+
+    @sa @ref operator[](const typename object_t::key_type&) for unchecked
+    access by reference
+    @sa @ref value() for access by value with a default value
+
+    @since version 1.0.0
+
+    @liveexample{The example below shows how object elements can be read using
+    `at()`. It also demonstrates the different exceptions that can be thrown.,
+    at__object_t_key_type_const}
+    */
+    const_reference at(const typename object_t::key_type& key) const
+    {
+        // at only works for objects
+        if (JSON_LIKELY(is_object()))
+        {
+            JSON_TRY
+            {
+                return m_value.object->at(key);
+            }
+            JSON_CATCH (std::out_of_range&)
+            {
+                // create better exception explanation
+                JSON_THROW(out_of_range::create(403, "key '" + key + "' not found"));
+            }
+        }
+        else
+        {
+            JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name())));
+        }
+    }
+
+    /*!
+    @brief access specified array element
+
+    Returns a reference to the element at specified location @a idx.
+
+    @note If @a idx is beyond the range of the array (i.e., `idx >= size()`),
+    then the array is silently filled up with `null` values to make `idx` a
+    valid reference to the last stored element.
+
+    @param[in] idx  index of the element to access
+
+    @return reference to the element at index @a idx
+
+    @throw type_error.305 if the JSON value is not an array or null; in that
+    cases, using the [] operator with an index makes no sense.
+
+    @complexity Constant if @a idx is in the range of the array. Otherwise
+    linear in `idx - size()`.
+
+    @liveexample{The example below shows how array elements can be read and
+    written using `[]` operator. Note the addition of `null`
+    values.,operatorarray__size_type}
+
+    @since version 1.0.0
+    */
+    reference operator[](size_type idx)
+    {
+        // implicitly convert null value to an empty array
+        if (is_null())
+        {
+            m_type = value_t::array;
+            m_value.array = create<array_t>();
+            assert_invariant();
+        }
+
+        // operator[] only works for arrays
+        if (JSON_LIKELY(is_array()))
+        {
+            // fill up array with null values if given idx is outside range
+            if (idx >= m_value.array->size())
+            {
+                m_value.array->insert(m_value.array->end(),
+                                      idx - m_value.array->size() + 1,
+                                      basic_json());
+            }
+
+            return m_value.array->operator[](idx);
+        }
+
+        JSON_THROW(type_error::create(305, "cannot use operator[] with a numeric argument with " + std::string(type_name())));
+    }
+
+    /*!
+    @brief access specified array element
+
+    Returns a const reference to the element at specified location @a idx.
+
+    @param[in] idx  index of the element to access
+
+    @return const reference to the element at index @a idx
+
+    @throw type_error.305 if the JSON value is not an array; in that case,
+    using the [] operator with an index makes no sense.
+
+    @complexity Constant.
+
+    @liveexample{The example below shows how array elements can be read using
+    the `[]` operator.,operatorarray__size_type_const}
+
+    @since version 1.0.0
+    */
+    const_reference operator[](size_type idx) const
+    {
+        // const operator[] only works for arrays
+        if (JSON_LIKELY(is_array()))
+        {
+            return m_value.array->operator[](idx);
+        }
+
+        JSON_THROW(type_error::create(305, "cannot use operator[] with a numeric argument with " + std::string(type_name())));
+    }
+
+    /*!
+    @brief access specified object element
+
+    Returns a reference to the element at with specified key @a key.
+
+    @note If @a key is not found in the object, then it is silently added to
+    the object and filled with a `null` value to make `key` a valid reference.
+    In case the value was `null` before, it is converted to an object.
+
+    @param[in] key  key of the element to access
+
+    @return reference to the element at key @a key
+
+    @throw type_error.305 if the JSON value is not an object or null; in that
+    cases, using the [] operator with a key makes no sense.
+
+    @complexity Logarithmic in the size of the container.
+
+    @liveexample{The example below shows how object elements can be read and
+    written using the `[]` operator.,operatorarray__key_type}
+
+    @sa @ref at(const typename object_t::key_type&) for access by reference
+    with range checking
+    @sa @ref value() for access by value with a default value
+
+    @since version 1.0.0
+    */
+    reference operator[](const typename object_t::key_type& key)
+    {
+        // implicitly convert null value to an empty object
+        if (is_null())
+        {
+            m_type = value_t::object;
+            m_value.object = create<object_t>();
+            assert_invariant();
+        }
+
+        // operator[] only works for objects
+        if (JSON_LIKELY(is_object()))
+        {
+            return m_value.object->operator[](key);
+        }
+
+        JSON_THROW(type_error::create(305, "cannot use operator[] with a string argument with " + std::string(type_name())));
+    }
+
+    /*!
+    @brief read-only access specified object element
+
+    Returns a const reference to the element at with specified key @a key. No
+    bounds checking is performed.
+
+    @warning If the element with key @a key does not exist, the behavior is
+    undefined.
+
+    @param[in] key  key of the element to access
+
+    @return const reference to the element at key @a key
+
+    @pre The element with key @a key must exist. **This precondition is
+         enforced with an assertion.**
+
+    @throw type_error.305 if the JSON value is not an object; in that case,
+    using the [] operator with a key makes no sense.
+
+    @complexity Logarithmic in the size of the container.
+
+    @liveexample{The example below shows how object elements can be read using
+    the `[]` operator.,operatorarray__key_type_const}
+
+    @sa @ref at(const typename object_t::key_type&) for access by reference
+    with range checking
+    @sa @ref value() for access by value with a default value
+
+    @since version 1.0.0
+    */
+    const_reference operator[](const typename object_t::key_type& key) const
+    {
+        // const operator[] only works for objects
+        if (JSON_LIKELY(is_object()))
+        {
+            assert(m_value.object->find(key) != m_value.object->end());
+            return m_value.object->find(key)->second;
+        }
+
+        JSON_THROW(type_error::create(305, "cannot use operator[] with a string argument with " + std::string(type_name())));
+    }
+
+    /*!
+    @brief access specified object element
+
+    Returns a reference to the element at with specified key @a key.
+
+    @note If @a key is not found in the object, then it is silently added to
+    the object and filled with a `null` value to make `key` a valid reference.
+    In case the value was `null` before, it is converted to an object.
+
+    @param[in] key  key of the element to access
+
+    @return reference to the element at key @a key
+
+    @throw type_error.305 if the JSON value is not an object or null; in that
+    cases, using the [] operator with a key makes no sense.
+
+    @complexity Logarithmic in the size of the container.
+
+    @liveexample{The example below shows how object elements can be read and
+    written using the `[]` operator.,operatorarray__key_type}
+
+    @sa @ref at(const typename object_t::key_type&) for access by reference
+    with range checking
+    @sa @ref value() for access by value with a default value
+
+    @since version 1.1.0
+    */
+    template<typename T>
+    reference operator[](T* key)
+    {
+        // implicitly convert null to object
+        if (is_null())
+        {
+            m_type = value_t::object;
+            m_value = value_t::object;
+            assert_invariant();
+        }
+
+        // at only works for objects
+        if (JSON_LIKELY(is_object()))
+        {
+            return m_value.object->operator[](key);
+        }
+
+        JSON_THROW(type_error::create(305, "cannot use operator[] with a string argument with " + std::string(type_name())));
+    }
+
+    /*!
+    @brief read-only access specified object element
+
+    Returns a const reference to the element at with specified key @a key. No
+    bounds checking is performed.
+
+    @warning If the element with key @a key does not exist, the behavior is
+    undefined.
+
+    @param[in] key  key of the element to access
+
+    @return const reference to the element at key @a key
+
+    @pre The element with key @a key must exist. **This precondition is
+         enforced with an assertion.**
+
+    @throw type_error.305 if the JSON value is not an object; in that case,
+    using the [] operator with a key makes no sense.
+
+    @complexity Logarithmic in the size of the container.
+
+    @liveexample{The example below shows how object elements can be read using
+    the `[]` operator.,operatorarray__key_type_const}
+
+    @sa @ref at(const typename object_t::key_type&) for access by reference
+    with range checking
+    @sa @ref value() for access by value with a default value
+
+    @since version 1.1.0
+    */
+    template<typename T>
+    const_reference operator[](T* key) const
+    {
+        // at only works for objects
+        if (JSON_LIKELY(is_object()))
+        {
+            assert(m_value.object->find(key) != m_value.object->end());
+            return m_value.object->find(key)->second;
+        }
+
+        JSON_THROW(type_error::create(305, "cannot use operator[] with a string argument with " + std::string(type_name())));
+    }
+
+    /*!
+    @brief access specified object element with default value
+
+    Returns either a copy of an object's element at the specified key @a key
+    or a given default value if no element with key @a key exists.
+
+    The function is basically equivalent to executing
+    @code {.cpp}
+    try {
+        return at(key);
+    } catch(out_of_range) {
+        return default_value;
+    }
+    @endcode
+
+    @note Unlike @ref at(const typename object_t::key_type&), this function
+    does not throw if the given key @a key was not found.
+
+    @note Unlike @ref operator[](const typename object_t::key_type& key), this
+    function does not implicitly add an element to the position defined by @a
+    key. This function is furthermore also applicable to const objects.
+
+    @param[in] key  key of the element to access
+    @param[in] default_value  the value to return if @a key is not found
+
+    @tparam ValueType type compatible to JSON values, for instance `int` for
+    JSON integer numbers, `bool` for JSON booleans, or `std::vector` types for
+    JSON arrays. Note the type of the expected value at @a key and the default
+    value @a default_value must be compatible.
+
+    @return copy of the element at key @a key or @a default_value if @a key
+    is not found
+
+    @throw type_error.306 if the JSON value is not an object; in that case,
+    using `value()` with a key makes no sense.
+
+    @complexity Logarithmic in the size of the container.
+
+    @liveexample{The example below shows how object elements can be queried
+    with a default value.,basic_json__value}
+
+    @sa @ref at(const typename object_t::key_type&) for access by reference
+    with range checking
+    @sa @ref operator[](const typename object_t::key_type&) for unchecked
+    access by reference
+
+    @since version 1.0.0
+    */
+    template<class ValueType, typename std::enable_if<
+                 std::is_convertible<basic_json_t, ValueType>::value, int>::type = 0>
+    ValueType value(const typename object_t::key_type& key, const ValueType& default_value) const
+    {
+        // at only works for objects
+        if (JSON_LIKELY(is_object()))
+        {
+            // if key is found, return value and given default value otherwise
+            const auto it = find(key);
+            if (it != end())
+            {
+                return *it;
+            }
+
+            return default_value;
+        }
+
+        JSON_THROW(type_error::create(306, "cannot use value() with " + std::string(type_name())));
+    }
+
+    /*!
+    @brief overload for a default value of type const char*
+    @copydoc basic_json::value(const typename object_t::key_type&, const ValueType&) const
+    */
+    string_t value(const typename object_t::key_type& key, const char* default_value) const
+    {
+        return value(key, string_t(default_value));
+    }
+
+    /*!
+    @brief access specified object element via JSON Pointer with default value
+
+    Returns either a copy of an object's element at the specified key @a key
+    or a given default value if no element with key @a key exists.
+
+    The function is basically equivalent to executing
+    @code {.cpp}
+    try {
+        return at(ptr);
+    } catch(out_of_range) {
+        return default_value;
+    }
+    @endcode
+
+    @note Unlike @ref at(const json_pointer&), this function does not throw
+    if the given key @a key was not found.
+
+    @param[in] ptr  a JSON pointer to the element to access
+    @param[in] default_value  the value to return if @a ptr found no value
+
+    @tparam ValueType type compatible to JSON values, for instance `int` for
+    JSON integer numbers, `bool` for JSON booleans, or `std::vector` types for
+    JSON arrays. Note the type of the expected value at @a key and the default
+    value @a default_value must be compatible.
+
+    @return copy of the element at key @a key or @a default_value if @a key
+    is not found
+
+    @throw type_error.306 if the JSON value is not an object; in that case,
+    using `value()` with a key makes no sense.
+
+    @complexity Logarithmic in the size of the container.
+
+    @liveexample{The example below shows how object elements can be queried
+    with a default value.,basic_json__value_ptr}
+
+    @sa @ref operator[](const json_pointer&) for unchecked access by reference
+
+    @since version 2.0.2
+    */
+    template<class ValueType, typename std::enable_if<
+                 std::is_convertible<basic_json_t, ValueType>::value, int>::type = 0>
+    ValueType value(const json_pointer& ptr, const ValueType& default_value) const
+    {
+        // at only works for objects
+        if (JSON_LIKELY(is_object()))
+        {
+            // if pointer resolves a value, return it or use default value
+            JSON_TRY
+            {
+                return ptr.get_checked(this);
+            }
+            JSON_INTERNAL_CATCH (out_of_range&)
+            {
+                return default_value;
+            }
+        }
+
+        JSON_THROW(type_error::create(306, "cannot use value() with " + std::string(type_name())));
+    }
+
+    /*!
+    @brief overload for a default value of type const char*
+    @copydoc basic_json::value(const json_pointer&, ValueType) const
+    */
+    string_t value(const json_pointer& ptr, const char* default_value) const
+    {
+        return value(ptr, string_t(default_value));
+    }
+
+    /*!
+    @brief access the first element
+
+    Returns a reference to the first element in the container. For a JSON
+    container `c`, the expression `c.front()` is equivalent to `*c.begin()`.
+
+    @return In case of a structured type (array or object), a reference to the
+    first element is returned. In case of number, string, or boolean values, a
+    reference to the value is returned.
+
+    @complexity Constant.
+
+    @pre The JSON value must not be `null` (would throw `std::out_of_range`)
+    or an empty array or object (undefined behavior, **guarded by
+    assertions**).
+    @post The JSON value remains unchanged.
+
+    @throw invalid_iterator.214 when called on `null` value
+
+    @liveexample{The following code shows an example for `front()`.,front}
+
+    @sa @ref back() -- access the last element
+
+    @since version 1.0.0
+    */
+    reference front()
+    {
+        return *begin();
+    }
+
+    /*!
+    @copydoc basic_json::front()
+    */
+    const_reference front() const
+    {
+        return *cbegin();
+    }
+
+    /*!
+    @brief access the last element
+
+    Returns a reference to the last element in the container. For a JSON
+    container `c`, the expression `c.back()` is equivalent to
+    @code {.cpp}
+    auto tmp = c.end();
+    --tmp;
+    return *tmp;
+    @endcode
+
+    @return In case of a structured type (array or object), a reference to the
+    last element is returned. In case of number, string, or boolean values, a
+    reference to the value is returned.
+
+    @complexity Constant.
+
+    @pre The JSON value must not be `null` (would throw `std::out_of_range`)
+    or an empty array or object (undefined behavior, **guarded by
+    assertions**).
+    @post The JSON value remains unchanged.
+
+    @throw invalid_iterator.214 when called on a `null` value. See example
+    below.
+
+    @liveexample{The following code shows an example for `back()`.,back}
+
+    @sa @ref front() -- access the first element
+
+    @since version 1.0.0
+    */
+    reference back()
+    {
+        auto tmp = end();
+        --tmp;
+        return *tmp;
+    }
+
+    /*!
+    @copydoc basic_json::back()
+    */
+    const_reference back() const
+    {
+        auto tmp = cend();
+        --tmp;
+        return *tmp;
+    }
+
+    /*!
+    @brief remove element given an iterator
+
+    Removes the element specified by iterator @a pos. The iterator @a pos must
+    be valid and dereferenceable. Thus the `end()` iterator (which is valid,
+    but is not dereferenceable) cannot be used as a value for @a pos.
+
+    If called on a primitive type other than `null`, the resulting JSON value
+    will be `null`.
+
+    @param[in] pos iterator to the element to remove
+    @return Iterator following the last removed element. If the iterator @a
+    pos refers to the last element, the `end()` iterator is returned.
+
+    @tparam IteratorType an @ref iterator or @ref const_iterator
+
+    @post Invalidates iterators and references at or after the point of the
+    erase, including the `end()` iterator.
+
+    @throw type_error.307 if called on a `null` value; example: `"cannot use
+    erase() with null"`
+    @throw invalid_iterator.202 if called on an iterator which does not belong
+    to the current JSON value; example: `"iterator does not fit current
+    value"`
+    @throw invalid_iterator.205 if called on a primitive type with invalid
+    iterator (i.e., any iterator which is not `begin()`); example: `"iterator
+    out of range"`
+
+    @complexity The complexity depends on the type:
+    - objects: amortized constant
+    - arrays: linear in distance between @a pos and the end of the container
+    - strings: linear in the length of the string
+    - other types: constant
+
+    @liveexample{The example shows the result of `erase()` for different JSON
+    types.,erase__IteratorType}
+
+    @sa @ref erase(IteratorType, IteratorType) -- removes the elements in
+    the given range
+    @sa @ref erase(const typename object_t::key_type&) -- removes the element
+    from an object at the given key
+    @sa @ref erase(const size_type) -- removes the element from an array at
+    the given index
+
+    @since version 1.0.0
+    */
+    template<class IteratorType, typename std::enable_if<
+                 std::is_same<IteratorType, typename basic_json_t::iterator>::value or
+                 std::is_same<IteratorType, typename basic_json_t::const_iterator>::value, int>::type
+             = 0>
+    IteratorType erase(IteratorType pos)
+    {
+        // make sure iterator fits the current value
+        if (JSON_UNLIKELY(this != pos.m_object))
+        {
+            JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value"));
+        }
+
+        IteratorType result = end();
+
+        switch (m_type)
+        {
+            case value_t::boolean:
+            case value_t::number_float:
+            case value_t::number_integer:
+            case value_t::number_unsigned:
+            case value_t::string:
+            {
+                if (JSON_UNLIKELY(not pos.m_it.primitive_iterator.is_begin()))
+                {
+                    JSON_THROW(invalid_iterator::create(205, "iterator out of range"));
+                }
+
+                if (is_string())
+                {
+                    AllocatorType<string_t> alloc;
+                    std::allocator_traits<decltype(alloc)>::destroy(alloc, m_value.string);
+                    std::allocator_traits<decltype(alloc)>::deallocate(alloc, m_value.string, 1);
+                    m_value.string = nullptr;
+                }
+
+                m_type = value_t::null;
+                assert_invariant();
+                break;
+            }
+
+            case value_t::object:
+            {
+                result.m_it.object_iterator = m_value.object->erase(pos.m_it.object_iterator);
+                break;
+            }
+
+            case value_t::array:
+            {
+                result.m_it.array_iterator = m_value.array->erase(pos.m_it.array_iterator);
+                break;
+            }
+
+            default:
+                JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name())));
+        }
+
+        return result;
+    }
+
+    /*!
+    @brief remove elements given an iterator range
+
+    Removes the element specified by the range `[first; last)`. The iterator
+    @a first does not need to be dereferenceable if `first == last`: erasing
+    an empty range is a no-op.
+
+    If called on a primitive type other than `null`, the resulting JSON value
+    will be `null`.
+
+    @param[in] first iterator to the beginning of the range to remove
+    @param[in] last iterator past the end of the range to remove
+    @return Iterator following the last removed element. If the iterator @a
+    second refers to the last element, the `end()` iterator is returned.
+
+    @tparam IteratorType an @ref iterator or @ref const_iterator
+
+    @post Invalidates iterators and references at or after the point of the
+    erase, including the `end()` iterator.
+
+    @throw type_error.307 if called on a `null` value; example: `"cannot use
+    erase() with null"`
+    @throw invalid_iterator.203 if called on iterators which does not belong
+    to the current JSON value; example: `"iterators do not fit current value"`
+    @throw invalid_iterator.204 if called on a primitive type with invalid
+    iterators (i.e., if `first != begin()` and `last != end()`); example:
+    `"iterators out of range"`
+
+    @complexity The complexity depends on the type:
+    - objects: `log(size()) + std::distance(first, last)`
+    - arrays: linear in the distance between @a first and @a last, plus linear
+      in the distance between @a last and end of the container
+    - strings: linear in the length of the string
+    - other types: constant
+
+    @liveexample{The example shows the result of `erase()` for different JSON
+    types.,erase__IteratorType_IteratorType}
+
+    @sa @ref erase(IteratorType) -- removes the element at a given position
+    @sa @ref erase(const typename object_t::key_type&) -- removes the element
+    from an object at the given key
+    @sa @ref erase(const size_type) -- removes the element from an array at
+    the given index
+
+    @since version 1.0.0
+    */
+    template<class IteratorType, typename std::enable_if<
+                 std::is_same<IteratorType, typename basic_json_t::iterator>::value or
+                 std::is_same<IteratorType, typename basic_json_t::const_iterator>::value, int>::type
+             = 0>
+    IteratorType erase(IteratorType first, IteratorType last)
+    {
+        // make sure iterator fits the current value
+        if (JSON_UNLIKELY(this != first.m_object or this != last.m_object))
+        {
+            JSON_THROW(invalid_iterator::create(203, "iterators do not fit current value"));
+        }
+
+        IteratorType result = end();
+
+        switch (m_type)
+        {
+            case value_t::boolean:
+            case value_t::number_float:
+            case value_t::number_integer:
+            case value_t::number_unsigned:
+            case value_t::string:
+            {
+                if (JSON_LIKELY(not first.m_it.primitive_iterator.is_begin()
+                                or not last.m_it.primitive_iterator.is_end()))
+                {
+                    JSON_THROW(invalid_iterator::create(204, "iterators out of range"));
+                }
+
+                if (is_string())
+                {
+                    AllocatorType<string_t> alloc;
+                    std::allocator_traits<decltype(alloc)>::destroy(alloc, m_value.string);
+                    std::allocator_traits<decltype(alloc)>::deallocate(alloc, m_value.string, 1);
+                    m_value.string = nullptr;
+                }
+
+                m_type = value_t::null;
+                assert_invariant();
+                break;
+            }
+
+            case value_t::object:
+            {
+                result.m_it.object_iterator = m_value.object->erase(first.m_it.object_iterator,
+                                              last.m_it.object_iterator);
+                break;
+            }
+
+            case value_t::array:
+            {
+                result.m_it.array_iterator = m_value.array->erase(first.m_it.array_iterator,
+                                             last.m_it.array_iterator);
+                break;
+            }
+
+            default:
+                JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name())));
+        }
+
+        return result;
+    }
+
+    /*!
+    @brief remove element from a JSON object given a key
+
+    Removes elements from a JSON object with the key value @a key.
+
+    @param[in] key value of the elements to remove
+
+    @return Number of elements removed. If @a ObjectType is the default
+    `std::map` type, the return value will always be `0` (@a key was not
+    found) or `1` (@a key was found).
+
+    @post References and iterators to the erased elements are invalidated.
+    Other references and iterators are not affected.
+
+    @throw type_error.307 when called on a type other than JSON object;
+    example: `"cannot use erase() with null"`
+
+    @complexity `log(size()) + count(key)`
+
+    @liveexample{The example shows the effect of `erase()`.,erase__key_type}
+
+    @sa @ref erase(IteratorType) -- removes the element at a given position
+    @sa @ref erase(IteratorType, IteratorType) -- removes the elements in
+    the given range
+    @sa @ref erase(const size_type) -- removes the element from an array at
+    the given index
+
+    @since version 1.0.0
+    */
+    size_type erase(const typename object_t::key_type& key)
+    {
+        // this erase only works for objects
+        if (JSON_LIKELY(is_object()))
+        {
+            return m_value.object->erase(key);
+        }
+
+        JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name())));
+    }
+
+    /*!
+    @brief remove element from a JSON array given an index
+
+    Removes element from a JSON array at the index @a idx.
+
+    @param[in] idx index of the element to remove
+
+    @throw type_error.307 when called on a type other than JSON object;
+    example: `"cannot use erase() with null"`
+    @throw out_of_range.401 when `idx >= size()`; example: `"array index 17
+    is out of range"`
+
+    @complexity Linear in distance between @a idx and the end of the container.
+
+    @liveexample{The example shows the effect of `erase()`.,erase__size_type}
+
+    @sa @ref erase(IteratorType) -- removes the element at a given position
+    @sa @ref erase(IteratorType, IteratorType) -- removes the elements in
+    the given range
+    @sa @ref erase(const typename object_t::key_type&) -- removes the element
+    from an object at the given key
+
+    @since version 1.0.0
+    */
+    void erase(const size_type idx)
+    {
+        // this erase only works for arrays
+        if (JSON_LIKELY(is_array()))
+        {
+            if (JSON_UNLIKELY(idx >= size()))
+            {
+                JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range"));
+            }
+
+            m_value.array->erase(m_value.array->begin() + static_cast<difference_type>(idx));
+        }
+        else
+        {
+            JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name())));
+        }
+    }
+
+    /// @}
+
+
+    ////////////
+    // lookup //
+    ////////////
+
+    /// @name lookup
+    /// @{
+
+    /*!
+    @brief find an element in a JSON object
+
+    Finds an element in a JSON object with key equivalent to @a key. If the
+    element is not found or the JSON value is not an object, end() is
+    returned.
+
+    @note This method always returns @ref end() when executed on a JSON type
+          that is not an object.
+
+    @param[in] key key value of the element to search for.
+
+    @return Iterator to an element with key equivalent to @a key. If no such
+    element is found or the JSON value is not an object, past-the-end (see
+    @ref end()) iterator is returned.
+
+    @complexity Logarithmic in the size of the JSON object.
+
+    @liveexample{The example shows how `find()` is used.,find__key_type}
+
+    @since version 1.0.0
+    */
+    template<typename KeyT>
+    iterator find(KeyT&& key)
+    {
+        auto result = end();
+
+        if (is_object())
+        {
+            result.m_it.object_iterator = m_value.object->find(std::forward<KeyT>(key));
+        }
+
+        return result;
+    }
+
+    /*!
+    @brief find an element in a JSON object
+    @copydoc find(KeyT&&)
+    */
+    template<typename KeyT>
+    const_iterator find(KeyT&& key) const
+    {
+        auto result = cend();
+
+        if (is_object())
+        {
+            result.m_it.object_iterator = m_value.object->find(std::forward<KeyT>(key));
+        }
+
+        return result;
+    }
+
+    /*!
+    @brief returns the number of occurrences of a key in a JSON object
+
+    Returns the number of elements with key @a key. If ObjectType is the
+    default `std::map` type, the return value will always be `0` (@a key was
+    not found) or `1` (@a key was found).
+
+    @note This method always returns `0` when executed on a JSON type that is
+          not an object.
+
+    @param[in] key key value of the element to count
+
+    @return Number of elements with key @a key. If the JSON value is not an
+    object, the return value will be `0`.
+
+    @complexity Logarithmic in the size of the JSON object.
+
+    @liveexample{The example shows how `count()` is used.,count}
+
+    @since version 1.0.0
+    */
+    template<typename KeyT>
+    size_type count(KeyT&& key) const
+    {
+        // return 0 for all nonobject types
+        return is_object() ? m_value.object->count(std::forward<KeyT>(key)) : 0;
+    }
+
+    /// @}
+
+
+    ///////////////
+    // iterators //
+    ///////////////
+
+    /// @name iterators
+    /// @{
+
+    /*!
+    @brief returns an iterator to the first element
+
+    Returns an iterator to the first element.
+
+    @image html range-begin-end.svg "Illustration from cppreference.com"
+
+    @return iterator to the first element
+
+    @complexity Constant.
+
+    @requirement This function helps `basic_json` satisfying the
+    [Container](https://en.cppreference.com/w/cpp/named_req/Container)
+    requirements:
+    - The complexity is constant.
+
+    @liveexample{The following code shows an example for `begin()`.,begin}
+
+    @sa @ref cbegin() -- returns a const iterator to the beginning
+    @sa @ref end() -- returns an iterator to the end
+    @sa @ref cend() -- returns a const iterator to the end
+
+    @since version 1.0.0
+    */
+    iterator begin() noexcept
+    {
+        iterator result(this);
+        result.set_begin();
+        return result;
+    }
+
+    /*!
+    @copydoc basic_json::cbegin()
+    */
+    const_iterator begin() const noexcept
+    {
+        return cbegin();
+    }
+
+    /*!
+    @brief returns a const iterator to the first element
+
+    Returns a const iterator to the first element.
+
+    @image html range-begin-end.svg "Illustration from cppreference.com"
+
+    @return const iterator to the first element
+
+    @complexity Constant.
+
+    @requirement This function helps `basic_json` satisfying the
+    [Container](https://en.cppreference.com/w/cpp/named_req/Container)
+    requirements:
+    - The complexity is constant.
+    - Has the semantics of `const_cast<const basic_json&>(*this).begin()`.
+
+    @liveexample{The following code shows an example for `cbegin()`.,cbegin}
+
+    @sa @ref begin() -- returns an iterator to the beginning
+    @sa @ref end() -- returns an iterator to the end
+    @sa @ref cend() -- returns a const iterator to the end
+
+    @since version 1.0.0
+    */
+    const_iterator cbegin() const noexcept
+    {
+        const_iterator result(this);
+        result.set_begin();
+        return result;
+    }
+
+    /*!
+    @brief returns an iterator to one past the last element
+
+    Returns an iterator to one past the last element.
+
+    @image html range-begin-end.svg "Illustration from cppreference.com"
+
+    @return iterator one past the last element
+
+    @complexity Constant.
+
+    @requirement This function helps `basic_json` satisfying the
+    [Container](https://en.cppreference.com/w/cpp/named_req/Container)
+    requirements:
+    - The complexity is constant.
+
+    @liveexample{The following code shows an example for `end()`.,end}
+
+    @sa @ref cend() -- returns a const iterator to the end
+    @sa @ref begin() -- returns an iterator to the beginning
+    @sa @ref cbegin() -- returns a const iterator to the beginning
+
+    @since version 1.0.0
+    */
+    iterator end() noexcept
+    {
+        iterator result(this);
+        result.set_end();
+        return result;
+    }
+
+    /*!
+    @copydoc basic_json::cend()
+    */
+    const_iterator end() const noexcept
+    {
+        return cend();
+    }
+
+    /*!
+    @brief returns a const iterator to one past the last element
+
+    Returns a const iterator to one past the last element.
+
+    @image html range-begin-end.svg "Illustration from cppreference.com"
+
+    @return const iterator one past the last element
+
+    @complexity Constant.
+
+    @requirement This function helps `basic_json` satisfying the
+    [Container](https://en.cppreference.com/w/cpp/named_req/Container)
+    requirements:
+    - The complexity is constant.
+    - Has the semantics of `const_cast<const basic_json&>(*this).end()`.
+
+    @liveexample{The following code shows an example for `cend()`.,cend}
+
+    @sa @ref end() -- returns an iterator to the end
+    @sa @ref begin() -- returns an iterator to the beginning
+    @sa @ref cbegin() -- returns a const iterator to the beginning
+
+    @since version 1.0.0
+    */
+    const_iterator cend() const noexcept
+    {
+        const_iterator result(this);
+        result.set_end();
+        return result;
+    }
+
+    /*!
+    @brief returns an iterator to the reverse-beginning
+
+    Returns an iterator to the reverse-beginning; that is, the last element.
+
+    @image html range-rbegin-rend.svg "Illustration from cppreference.com"
+
+    @complexity Constant.
+
+    @requirement This function helps `basic_json` satisfying the
+    [ReversibleContainer](https://en.cppreference.com/w/cpp/named_req/ReversibleContainer)
+    requirements:
+    - The complexity is constant.
+    - Has the semantics of `reverse_iterator(end())`.
+
+    @liveexample{The following code shows an example for `rbegin()`.,rbegin}
+
+    @sa @ref crbegin() -- returns a const reverse iterator to the beginning
+    @sa @ref rend() -- returns a reverse iterator to the end
+    @sa @ref crend() -- returns a const reverse iterator to the end
+
+    @since version 1.0.0
+    */
+    reverse_iterator rbegin() noexcept
+    {
+        return reverse_iterator(end());
+    }
+
+    /*!
+    @copydoc basic_json::crbegin()
+    */
+    const_reverse_iterator rbegin() const noexcept
+    {
+        return crbegin();
+    }
+
+    /*!
+    @brief returns an iterator to the reverse-end
+
+    Returns an iterator to the reverse-end; that is, one before the first
+    element.
+
+    @image html range-rbegin-rend.svg "Illustration from cppreference.com"
+
+    @complexity Constant.
+
+    @requirement This function helps `basic_json` satisfying the
+    [ReversibleContainer](https://en.cppreference.com/w/cpp/named_req/ReversibleContainer)
+    requirements:
+    - The complexity is constant.
+    - Has the semantics of `reverse_iterator(begin())`.
+
+    @liveexample{The following code shows an example for `rend()`.,rend}
+
+    @sa @ref crend() -- returns a const reverse iterator to the end
+    @sa @ref rbegin() -- returns a reverse iterator to the beginning
+    @sa @ref crbegin() -- returns a const reverse iterator to the beginning
+
+    @since version 1.0.0
+    */
+    reverse_iterator rend() noexcept
+    {
+        return reverse_iterator(begin());
+    }
+
+    /*!
+    @copydoc basic_json::crend()
+    */
+    const_reverse_iterator rend() const noexcept
+    {
+        return crend();
+    }
+
+    /*!
+    @brief returns a const reverse iterator to the last element
+
+    Returns a const iterator to the reverse-beginning; that is, the last
+    element.
+
+    @image html range-rbegin-rend.svg "Illustration from cppreference.com"
+
+    @complexity Constant.
+
+    @requirement This function helps `basic_json` satisfying the
+    [ReversibleContainer](https://en.cppreference.com/w/cpp/named_req/ReversibleContainer)
+    requirements:
+    - The complexity is constant.
+    - Has the semantics of `const_cast<const basic_json&>(*this).rbegin()`.
+
+    @liveexample{The following code shows an example for `crbegin()`.,crbegin}
+
+    @sa @ref rbegin() -- returns a reverse iterator to the beginning
+    @sa @ref rend() -- returns a reverse iterator to the end
+    @sa @ref crend() -- returns a const reverse iterator to the end
+
+    @since version 1.0.0
+    */
+    const_reverse_iterator crbegin() const noexcept
+    {
+        return const_reverse_iterator(cend());
+    }
+
+    /*!
+    @brief returns a const reverse iterator to one before the first
+
+    Returns a const reverse iterator to the reverse-end; that is, one before
+    the first element.
+
+    @image html range-rbegin-rend.svg "Illustration from cppreference.com"
+
+    @complexity Constant.
+
+    @requirement This function helps `basic_json` satisfying the
+    [ReversibleContainer](https://en.cppreference.com/w/cpp/named_req/ReversibleContainer)
+    requirements:
+    - The complexity is constant.
+    - Has the semantics of `const_cast<const basic_json&>(*this).rend()`.
+
+    @liveexample{The following code shows an example for `crend()`.,crend}
+
+    @sa @ref rend() -- returns a reverse iterator to the end
+    @sa @ref rbegin() -- returns a reverse iterator to the beginning
+    @sa @ref crbegin() -- returns a const reverse iterator to the beginning
+
+    @since version 1.0.0
+    */
+    const_reverse_iterator crend() const noexcept
+    {
+        return const_reverse_iterator(cbegin());
+    }
+
+  public:
+    /*!
+    @brief wrapper to access iterator member functions in range-based for
+
+    This function allows to access @ref iterator::key() and @ref
+    iterator::value() during range-based for loops. In these loops, a
+    reference to the JSON values is returned, so there is no access to the
+    underlying iterator.
+
+    For loop without iterator_wrapper:
+
+    @code{cpp}
+    for (auto it = j_object.begin(); it != j_object.end(); ++it)
+    {
+        std::cout << "key: " << it.key() << ", value:" << it.value() << '\n';
+    }
+    @endcode
+
+    Range-based for loop without iterator proxy:
+
+    @code{cpp}
+    for (auto it : j_object)
+    {
+        // "it" is of type json::reference and has no key() member
+        std::cout << "value: " << it << '\n';
+    }
+    @endcode
+
+    Range-based for loop with iterator proxy:
+
+    @code{cpp}
+    for (auto it : json::iterator_wrapper(j_object))
+    {
+        std::cout << "key: " << it.key() << ", value:" << it.value() << '\n';
+    }
+    @endcode
+
+    @note When iterating over an array, `key()` will return the index of the
+          element as string (see example).
+
+    @param[in] ref  reference to a JSON value
+    @return iteration proxy object wrapping @a ref with an interface to use in
+            range-based for loops
+
+    @liveexample{The following code shows how the wrapper is used,iterator_wrapper}
+
+    @exceptionsafety Strong guarantee: if an exception is thrown, there are no
+    changes in the JSON value.
+
+    @complexity Constant.
+
+    @note The name of this function is not yet final and may change in the
+    future.
+
+    @deprecated This stream operator is deprecated and will be removed in
+                future 4.0.0 of the library. Please use @ref items() instead;
+                that is, replace `json::iterator_wrapper(j)` with `j.items()`.
+    */
+    JSON_DEPRECATED
+    static iteration_proxy<iterator> iterator_wrapper(reference ref) noexcept
+    {
+        return ref.items();
+    }
+
+    /*!
+    @copydoc iterator_wrapper(reference)
+    */
+    JSON_DEPRECATED
+    static iteration_proxy<const_iterator> iterator_wrapper(const_reference ref) noexcept
+    {
+        return ref.items();
+    }
+
+    /*!
+    @brief helper to access iterator member functions in range-based for
+
+    This function allows to access @ref iterator::key() and @ref
+    iterator::value() during range-based for loops. In these loops, a
+    reference to the JSON values is returned, so there is no access to the
+    underlying iterator.
+
+    For loop without `items()` function:
+
+    @code{cpp}
+    for (auto it = j_object.begin(); it != j_object.end(); ++it)
+    {
+        std::cout << "key: " << it.key() << ", value:" << it.value() << '\n';
+    }
+    @endcode
+
+    Range-based for loop without `items()` function:
+
+    @code{cpp}
+    for (auto it : j_object)
+    {
+        // "it" is of type json::reference and has no key() member
+        std::cout << "value: " << it << '\n';
+    }
+    @endcode
+
+    Range-based for loop with `items()` function:
+
+    @code{cpp}
+    for (auto& el : j_object.items())
+    {
+        std::cout << "key: " << el.key() << ", value:" << el.value() << '\n';
+    }
+    @endcode
+
+    The `items()` function also allows to use
+    [structured bindings](https://en.cppreference.com/w/cpp/language/structured_binding)
+    (C++17):
+
+    @code{cpp}
+    for (auto& [key, val] : j_object.items())
+    {
+        std::cout << "key: " << key << ", value:" << val << '\n';
+    }
+    @endcode
+
+    @note When iterating over an array, `key()` will return the index of the
+          element as string (see example). For primitive types (e.g., numbers),
+          `key()` returns an empty string.
+
+    @return iteration proxy object wrapping @a ref with an interface to use in
+            range-based for loops
+
+    @liveexample{The following code shows how the function is used.,items}
+
+    @exceptionsafety Strong guarantee: if an exception is thrown, there are no
+    changes in the JSON value.
+
+    @complexity Constant.
+
+    @since version 3.1.0, structured bindings support since 3.5.0.
+    */
+    iteration_proxy<iterator> items() noexcept
+    {
+        return iteration_proxy<iterator>(*this);
+    }
+
+    /*!
+    @copydoc items()
+    */
+    iteration_proxy<const_iterator> items() const noexcept
+    {
+        return iteration_proxy<const_iterator>(*this);
+    }
+
+    /// @}
+
+
+    //////////////
+    // capacity //
+    //////////////
+
+    /// @name capacity
+    /// @{
+
+    /*!
+    @brief checks whether the container is empty.
+
+    Checks if a JSON value has no elements (i.e. whether its @ref size is `0`).
+
+    @return The return value depends on the different types and is
+            defined as follows:
+            Value type  | return value
+            ----------- | -------------
+            null        | `true`
+            boolean     | `false`
+            string      | `false`
+            number      | `false`
+            object      | result of function `object_t::empty()`
+            array       | result of function `array_t::empty()`
+
+    @liveexample{The following code uses `empty()` to check if a JSON
+    object contains any elements.,empty}
+
+    @complexity Constant, as long as @ref array_t and @ref object_t satisfy
+    the Container concept; that is, their `empty()` functions have constant
+    complexity.
+
+    @iterators No changes.
+
+    @exceptionsafety No-throw guarantee: this function never throws exceptions.
+
+    @note This function does not return whether a string stored as JSON value
+    is empty - it returns whether the JSON container itself is empty which is
+    false in the case of a string.
+
+    @requirement This function helps `basic_json` satisfying the
+    [Container](https://en.cppreference.com/w/cpp/named_req/Container)
+    requirements:
+    - The complexity is constant.
+    - Has the semantics of `begin() == end()`.
+
+    @sa @ref size() -- returns the number of elements
+
+    @since version 1.0.0
+    */
+    bool empty() const noexcept
+    {
+        switch (m_type)
+        {
+            case value_t::null:
+            {
+                // null values are empty
+                return true;
+            }
+
+            case value_t::array:
+            {
+                // delegate call to array_t::empty()
+                return m_value.array->empty();
+            }
+
+            case value_t::object:
+            {
+                // delegate call to object_t::empty()
+                return m_value.object->empty();
+            }
+
+            default:
+            {
+                // all other types are nonempty
+                return false;
+            }
+        }
+    }
+
+    /*!
+    @brief returns the number of elements
+
+    Returns the number of elements in a JSON value.
+
+    @return The return value depends on the different types and is
+            defined as follows:
+            Value type  | return value
+            ----------- | -------------
+            null        | `0`
+            boolean     | `1`
+            string      | `1`
+            number      | `1`
+            object      | result of function object_t::size()
+            array       | result of function array_t::size()
+
+    @liveexample{The following code calls `size()` on the different value
+    types.,size}
+
+    @complexity Constant, as long as @ref array_t and @ref object_t satisfy
+    the Container concept; that is, their size() functions have constant
+    complexity.
+
+    @iterators No changes.
+
+    @exceptionsafety No-throw guarantee: this function never throws exceptions.
+
+    @note This function does not return the length of a string stored as JSON
+    value - it returns the number of elements in the JSON value which is 1 in
+    the case of a string.
+
+    @requirement This function helps `basic_json` satisfying the
+    [Container](https://en.cppreference.com/w/cpp/named_req/Container)
+    requirements:
+    - The complexity is constant.
+    - Has the semantics of `std::distance(begin(), end())`.
+
+    @sa @ref empty() -- checks whether the container is empty
+    @sa @ref max_size() -- returns the maximal number of elements
+
+    @since version 1.0.0
+    */
+    size_type size() const noexcept
+    {
+        switch (m_type)
+        {
+            case value_t::null:
+            {
+                // null values are empty
+                return 0;
+            }
+
+            case value_t::array:
+            {
+                // delegate call to array_t::size()
+                return m_value.array->size();
+            }
+
+            case value_t::object:
+            {
+                // delegate call to object_t::size()
+                return m_value.object->size();
+            }
+
+            default:
+            {
+                // all other types have size 1
+                return 1;
+            }
+        }
+    }
+
+    /*!
+    @brief returns the maximum possible number of elements
+
+    Returns the maximum number of elements a JSON value is able to hold due to
+    system or library implementation limitations, i.e. `std::distance(begin(),
+    end())` for the JSON value.
+
+    @return The return value depends on the different types and is
+            defined as follows:
+            Value type  | return value
+            ----------- | -------------
+            null        | `0` (same as `size()`)
+            boolean     | `1` (same as `size()`)
+            string      | `1` (same as `size()`)
+            number      | `1` (same as `size()`)
+            object      | result of function `object_t::max_size()`
+            array       | result of function `array_t::max_size()`
+
+    @liveexample{The following code calls `max_size()` on the different value
+    types. Note the output is implementation specific.,max_size}
+
+    @complexity Constant, as long as @ref array_t and @ref object_t satisfy
+    the Container concept; that is, their `max_size()` functions have constant
+    complexity.
+
+    @iterators No changes.
+
+    @exceptionsafety No-throw guarantee: this function never throws exceptions.
+
+    @requirement This function helps `basic_json` satisfying the
+    [Container](https://en.cppreference.com/w/cpp/named_req/Container)
+    requirements:
+    - The complexity is constant.
+    - Has the semantics of returning `b.size()` where `b` is the largest
+      possible JSON value.
+
+    @sa @ref size() -- returns the number of elements
+
+    @since version 1.0.0
+    */
+    size_type max_size() const noexcept
+    {
+        switch (m_type)
+        {
+            case value_t::array:
+            {
+                // delegate call to array_t::max_size()
+                return m_value.array->max_size();
+            }
+
+            case value_t::object:
+            {
+                // delegate call to object_t::max_size()
+                return m_value.object->max_size();
+            }
+
+            default:
+            {
+                // all other types have max_size() == size()
+                return size();
+            }
+        }
+    }
+
+    /// @}
+
+
+    ///////////////
+    // modifiers //
+    ///////////////
+
+    /// @name modifiers
+    /// @{
+
+    /*!
+    @brief clears the contents
+
+    Clears the content of a JSON value and resets it to the default value as
+    if @ref basic_json(value_t) would have been called with the current value
+    type from @ref type():
+
+    Value type  | initial value
+    ----------- | -------------
+    null        | `null`
+    boolean     | `false`
+    string      | `""`
+    number      | `0`
+    object      | `{}`
+    array       | `[]`
+
+    @post Has the same effect as calling
+    @code {.cpp}
+    *this = basic_json(type());
+    @endcode
+
+    @liveexample{The example below shows the effect of `clear()` to different
+    JSON types.,clear}
+
+    @complexity Linear in the size of the JSON value.
+
+    @iterators All iterators, pointers and references related to this container
+               are invalidated.
+
+    @exceptionsafety No-throw guarantee: this function never throws exceptions.
+
+    @sa @ref basic_json(value_t) -- constructor that creates an object with the
+        same value than calling `clear()`
+
+    @since version 1.0.0
+    */
+    void clear() noexcept
+    {
+        switch (m_type)
+        {
+            case value_t::number_integer:
+            {
+                m_value.number_integer = 0;
+                break;
+            }
+
+            case value_t::number_unsigned:
+            {
+                m_value.number_unsigned = 0;
+                break;
+            }
+
+            case value_t::number_float:
+            {
+                m_value.number_float = 0.0;
+                break;
+            }
+
+            case value_t::boolean:
+            {
+                m_value.boolean = false;
+                break;
+            }
+
+            case value_t::string:
+            {
+                m_value.string->clear();
+                break;
+            }
+
+            case value_t::array:
+            {
+                m_value.array->clear();
+                break;
+            }
+
+            case value_t::object:
+            {
+                m_value.object->clear();
+                break;
+            }
+
+            default:
+                break;
+        }
+    }
+
+    /*!
+    @brief add an object to an array
+
+    Appends the given element @a val to the end of the JSON value. If the
+    function is called on a JSON null value, an empty array is created before
+    appending @a val.
+
+    @param[in] val the value to add to the JSON array
+
+    @throw type_error.308 when called on a type other than JSON array or
+    null; example: `"cannot use push_back() with number"`
+
+    @complexity Amortized constant.
+
+    @liveexample{The example shows how `push_back()` and `+=` can be used to
+    add elements to a JSON array. Note how the `null` value was silently
+    converted to a JSON array.,push_back}
+
+    @since version 1.0.0
+    */
+    void push_back(basic_json&& val)
+    {
+        // push_back only works for null objects or arrays
+        if (JSON_UNLIKELY(not(is_null() or is_array())))
+        {
+            JSON_THROW(type_error::create(308, "cannot use push_back() with " + std::string(type_name())));
+        }
+
+        // transform null object into an array
+        if (is_null())
+        {
+            m_type = value_t::array;
+            m_value = value_t::array;
+            assert_invariant();
+        }
+
+        // add element to array (move semantics)
+        m_value.array->push_back(std::move(val));
+        // invalidate object
+        val.m_type = value_t::null;
+    }
+
+    /*!
+    @brief add an object to an array
+    @copydoc push_back(basic_json&&)
+    */
+    reference operator+=(basic_json&& val)
+    {
+        push_back(std::move(val));
+        return *this;
+    }
+
+    /*!
+    @brief add an object to an array
+    @copydoc push_back(basic_json&&)
+    */
+    void push_back(const basic_json& val)
+    {
+        // push_back only works for null objects or arrays
+        if (JSON_UNLIKELY(not(is_null() or is_array())))
+        {
+            JSON_THROW(type_error::create(308, "cannot use push_back() with " + std::string(type_name())));
+        }
+
+        // transform null object into an array
+        if (is_null())
+        {
+            m_type = value_t::array;
+            m_value = value_t::array;
+            assert_invariant();
+        }
+
+        // add element to array
+        m_value.array->push_back(val);
+    }
+
+    /*!
+    @brief add an object to an array
+    @copydoc push_back(basic_json&&)
+    */
+    reference operator+=(const basic_json& val)
+    {
+        push_back(val);
+        return *this;
+    }
+
+    /*!
+    @brief add an object to an object
+
+    Inserts the given element @a val to the JSON object. If the function is
+    called on a JSON null value, an empty object is created before inserting
+    @a val.
+
+    @param[in] val the value to add to the JSON object
+
+    @throw type_error.308 when called on a type other than JSON object or
+    null; example: `"cannot use push_back() with number"`
+
+    @complexity Logarithmic in the size of the container, O(log(`size()`)).
+
+    @liveexample{The example shows how `push_back()` and `+=` can be used to
+    add elements to a JSON object. Note how the `null` value was silently
+    converted to a JSON object.,push_back__object_t__value}
+
+    @since version 1.0.0
+    */
+    void push_back(const typename object_t::value_type& val)
+    {
+        // push_back only works for null objects or objects
+        if (JSON_UNLIKELY(not(is_null() or is_object())))
+        {
+            JSON_THROW(type_error::create(308, "cannot use push_back() with " + std::string(type_name())));
+        }
+
+        // transform null object into an object
+        if (is_null())
+        {
+            m_type = value_t::object;
+            m_value = value_t::object;
+            assert_invariant();
+        }
+
+        // add element to array
+        m_value.object->insert(val);
+    }
+
+    /*!
+    @brief add an object to an object
+    @copydoc push_back(const typename object_t::value_type&)
+    */
+    reference operator+=(const typename object_t::value_type& val)
+    {
+        push_back(val);
+        return *this;
+    }
+
+    /*!
+    @brief add an object to an object
+
+    This function allows to use `push_back` with an initializer list. In case
+
+    1. the current value is an object,
+    2. the initializer list @a init contains only two elements, and
+    3. the first element of @a init is a string,
+
+    @a init is converted into an object element and added using
+    @ref push_back(const typename object_t::value_type&). Otherwise, @a init
+    is converted to a JSON value and added using @ref push_back(basic_json&&).
+
+    @param[in] init  an initializer list
+
+    @complexity Linear in the size of the initializer list @a init.
+
+    @note This function is required to resolve an ambiguous overload error,
+          because pairs like `{"key", "value"}` can be both interpreted as
+          `object_t::value_type` or `std::initializer_list<basic_json>`, see
+          https://github.com/nlohmann/json/issues/235 for more information.
+
+    @liveexample{The example shows how initializer lists are treated as
+    objects when possible.,push_back__initializer_list}
+    */
+    void push_back(initializer_list_t init)
+    {
+        if (is_object() and init.size() == 2 and (*init.begin())->is_string())
+        {
+            basic_json&& key = init.begin()->moved_or_copied();
+            push_back(typename object_t::value_type(
+                          std::move(key.get_ref<string_t&>()), (init.begin() + 1)->moved_or_copied()));
+        }
+        else
+        {
+            push_back(basic_json(init));
+        }
+    }
+
+    /*!
+    @brief add an object to an object
+    @copydoc push_back(initializer_list_t)
+    */
+    reference operator+=(initializer_list_t init)
+    {
+        push_back(init);
+        return *this;
+    }
+
+    /*!
+    @brief add an object to an array
+
+    Creates a JSON value from the passed parameters @a args to the end of the
+    JSON value. If the function is called on a JSON null value, an empty array
+    is created before appending the value created from @a args.
+
+    @param[in] args arguments to forward to a constructor of @ref basic_json
+    @tparam Args compatible types to create a @ref basic_json object
+
+    @throw type_error.311 when called on a type other than JSON array or
+    null; example: `"cannot use emplace_back() with number"`
+
+    @complexity Amortized constant.
+
+    @liveexample{The example shows how `push_back()` can be used to add
+    elements to a JSON array. Note how the `null` value was silently converted
+    to a JSON array.,emplace_back}
+
+    @since version 2.0.8
+    */
+    template<class... Args>
+    void emplace_back(Args&& ... args)
+    {
+        // emplace_back only works for null objects or arrays
+        if (JSON_UNLIKELY(not(is_null() or is_array())))
+        {
+            JSON_THROW(type_error::create(311, "cannot use emplace_back() with " + std::string(type_name())));
+        }
+
+        // transform null object into an array
+        if (is_null())
+        {
+            m_type = value_t::array;
+            m_value = value_t::array;
+            assert_invariant();
+        }
+
+        // add element to array (perfect forwarding)
+        m_value.array->emplace_back(std::forward<Args>(args)...);
+    }
+
+    /*!
+    @brief add an object to an object if key does not exist
+
+    Inserts a new element into a JSON object constructed in-place with the
+    given @a args if there is no element with the key in the container. If the
+    function is called on a JSON null value, an empty object is created before
+    appending the value created from @a args.
+
+    @param[in] args arguments to forward to a constructor of @ref basic_json
+    @tparam Args compatible types to create a @ref basic_json object
+
+    @return a pair consisting of an iterator to the inserted element, or the
+            already-existing element if no insertion happened, and a bool
+            denoting whether the insertion took place.
+
+    @throw type_error.311 when called on a type other than JSON object or
+    null; example: `"cannot use emplace() with number"`
+
+    @complexity Logarithmic in the size of the container, O(log(`size()`)).
+
+    @liveexample{The example shows how `emplace()` can be used to add elements
+    to a JSON object. Note how the `null` value was silently converted to a
+    JSON object. Further note how no value is added if there was already one
+    value stored with the same key.,emplace}
+
+    @since version 2.0.8
+    */
+    template<class... Args>
+    std::pair<iterator, bool> emplace(Args&& ... args)
+    {
+        // emplace only works for null objects or arrays
+        if (JSON_UNLIKELY(not(is_null() or is_object())))
+        {
+            JSON_THROW(type_error::create(311, "cannot use emplace() with " + std::string(type_name())));
+        }
+
+        // transform null object into an object
+        if (is_null())
+        {
+            m_type = value_t::object;
+            m_value = value_t::object;
+            assert_invariant();
+        }
+
+        // add element to array (perfect forwarding)
+        auto res = m_value.object->emplace(std::forward<Args>(args)...);
+        // create result iterator and set iterator to the result of emplace
+        auto it = begin();
+        it.m_it.object_iterator = res.first;
+
+        // return pair of iterator and boolean
+        return {it, res.second};
+    }
+
+    /// Helper for insertion of an iterator
+    /// @note: This uses std::distance to support GCC 4.8,
+    ///        see https://github.com/nlohmann/json/pull/1257
+    template<typename... Args>
+    iterator insert_iterator(const_iterator pos, Args&& ... args)
+    {
+        iterator result(this);
+        assert(m_value.array != nullptr);
+
+        auto insert_pos = std::distance(m_value.array->begin(), pos.m_it.array_iterator);
+        m_value.array->insert(pos.m_it.array_iterator, std::forward<Args>(args)...);
+        result.m_it.array_iterator = m_value.array->begin() + insert_pos;
+
+        // This could have been written as:
+        // result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, cnt, val);
+        // but the return value of insert is missing in GCC 4.8, so it is written this way instead.
+
+        return result;
+    }
+
+    /*!
+    @brief inserts element
+
+    Inserts element @a val before iterator @a pos.
+
+    @param[in] pos iterator before which the content will be inserted; may be
+    the end() iterator
+    @param[in] val element to insert
+    @return iterator pointing to the inserted @a val.
+
+    @throw type_error.309 if called on JSON values other than arrays;
+    example: `"cannot use insert() with string"`
+    @throw invalid_iterator.202 if @a pos is not an iterator of *this;
+    example: `"iterator does not fit current value"`
+
+    @complexity Constant plus linear in the distance between @a pos and end of
+    the container.
+
+    @liveexample{The example shows how `insert()` is used.,insert}
+
+    @since version 1.0.0
+    */
+    iterator insert(const_iterator pos, const basic_json& val)
+    {
+        // insert only works for arrays
+        if (JSON_LIKELY(is_array()))
+        {
+            // check if iterator pos fits to this JSON value
+            if (JSON_UNLIKELY(pos.m_object != this))
+            {
+                JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value"));
+            }
+
+            // insert to array and return iterator
+            return insert_iterator(pos, val);
+        }
+
+        JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name())));
+    }
+
+    /*!
+    @brief inserts element
+    @copydoc insert(const_iterator, const basic_json&)
+    */
+    iterator insert(const_iterator pos, basic_json&& val)
+    {
+        return insert(pos, val);
+    }
+
+    /*!
+    @brief inserts elements
+
+    Inserts @a cnt copies of @a val before iterator @a pos.
+
+    @param[in] pos iterator before which the content will be inserted; may be
+    the end() iterator
+    @param[in] cnt number of copies of @a val to insert
+    @param[in] val element to insert
+    @return iterator pointing to the first element inserted, or @a pos if
+    `cnt==0`
+
+    @throw type_error.309 if called on JSON values other than arrays; example:
+    `"cannot use insert() with string"`
+    @throw invalid_iterator.202 if @a pos is not an iterator of *this;
+    example: `"iterator does not fit current value"`
+
+    @complexity Linear in @a cnt plus linear in the distance between @a pos
+    and end of the container.
+
+    @liveexample{The example shows how `insert()` is used.,insert__count}
+
+    @since version 1.0.0
+    */
+    iterator insert(const_iterator pos, size_type cnt, const basic_json& val)
+    {
+        // insert only works for arrays
+        if (JSON_LIKELY(is_array()))
+        {
+            // check if iterator pos fits to this JSON value
+            if (JSON_UNLIKELY(pos.m_object != this))
+            {
+                JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value"));
+            }
+
+            // insert to array and return iterator
+            return insert_iterator(pos, cnt, val);
+        }
+
+        JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name())));
+    }
+
+    /*!
+    @brief inserts elements
+
+    Inserts elements from range `[first, last)` before iterator @a pos.
+
+    @param[in] pos iterator before which the content will be inserted; may be
+    the end() iterator
+    @param[in] first begin of the range of elements to insert
+    @param[in] last end of the range of elements to insert
+
+    @throw type_error.309 if called on JSON values other than arrays; example:
+    `"cannot use insert() with string"`
+    @throw invalid_iterator.202 if @a pos is not an iterator of *this;
+    example: `"iterator does not fit current value"`
+    @throw invalid_iterator.210 if @a first and @a last do not belong to the
+    same JSON value; example: `"iterators do not fit"`
+    @throw invalid_iterator.211 if @a first or @a last are iterators into
+    container for which insert is called; example: `"passed iterators may not
+    belong to container"`
+
+    @return iterator pointing to the first element inserted, or @a pos if
+    `first==last`
+
+    @complexity Linear in `std::distance(first, last)` plus linear in the
+    distance between @a pos and end of the container.
+
+    @liveexample{The example shows how `insert()` is used.,insert__range}
+
+    @since version 1.0.0
+    */
+    iterator insert(const_iterator pos, const_iterator first, const_iterator last)
+    {
+        // insert only works for arrays
+        if (JSON_UNLIKELY(not is_array()))
+        {
+            JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name())));
+        }
+
+        // check if iterator pos fits to this JSON value
+        if (JSON_UNLIKELY(pos.m_object != this))
+        {
+            JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value"));
+        }
+
+        // check if range iterators belong to the same JSON object
+        if (JSON_UNLIKELY(first.m_object != last.m_object))
+        {
+            JSON_THROW(invalid_iterator::create(210, "iterators do not fit"));
+        }
+
+        if (JSON_UNLIKELY(first.m_object == this))
+        {
+            JSON_THROW(invalid_iterator::create(211, "passed iterators may not belong to container"));
+        }
+
+        // insert to array and return iterator
+        return insert_iterator(pos, first.m_it.array_iterator, last.m_it.array_iterator);
+    }
+
+    /*!
+    @brief inserts elements
+
+    Inserts elements from initializer list @a ilist before iterator @a pos.
+
+    @param[in] pos iterator before which the content will be inserted; may be
+    the end() iterator
+    @param[in] ilist initializer list to insert the values from
+
+    @throw type_error.309 if called on JSON values other than arrays; example:
+    `"cannot use insert() with string"`
+    @throw invalid_iterator.202 if @a pos is not an iterator of *this;
+    example: `"iterator does not fit current value"`
+
+    @return iterator pointing to the first element inserted, or @a pos if
+    `ilist` is empty
+
+    @complexity Linear in `ilist.size()` plus linear in the distance between
+    @a pos and end of the container.
+
+    @liveexample{The example shows how `insert()` is used.,insert__ilist}
+
+    @since version 1.0.0
+    */
+    iterator insert(const_iterator pos, initializer_list_t ilist)
+    {
+        // insert only works for arrays
+        if (JSON_UNLIKELY(not is_array()))
+        {
+            JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name())));
+        }
+
+        // check if iterator pos fits to this JSON value
+        if (JSON_UNLIKELY(pos.m_object != this))
+        {
+            JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value"));
+        }
+
+        // insert to array and return iterator
+        return insert_iterator(pos, ilist.begin(), ilist.end());
+    }
+
+    /*!
+    @brief inserts elements
+
+    Inserts elements from range `[first, last)`.
+
+    @param[in] first begin of the range of elements to insert
+    @param[in] last end of the range of elements to insert
+
+    @throw type_error.309 if called on JSON values other than objects; example:
+    `"cannot use insert() with string"`
+    @throw invalid_iterator.202 if iterator @a first or @a last does does not
+    point to an object; example: `"iterators first and last must point to
+    objects"`
+    @throw invalid_iterator.210 if @a first and @a last do not belong to the
+    same JSON value; example: `"iterators do not fit"`
+
+    @complexity Logarithmic: `O(N*log(size() + N))`, where `N` is the number
+    of elements to insert.
+
+    @liveexample{The example shows how `insert()` is used.,insert__range_object}
+
+    @since version 3.0.0
+    */
+    void insert(const_iterator first, const_iterator last)
+    {
+        // insert only works for objects
+        if (JSON_UNLIKELY(not is_object()))
+        {
+            JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name())));
+        }
+
+        // check if range iterators belong to the same JSON object
+        if (JSON_UNLIKELY(first.m_object != last.m_object))
+        {
+            JSON_THROW(invalid_iterator::create(210, "iterators do not fit"));
+        }
+
+        // passed iterators must belong to objects
+        if (JSON_UNLIKELY(not first.m_object->is_object()))
+        {
+            JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects"));
+        }
+
+        m_value.object->insert(first.m_it.object_iterator, last.m_it.object_iterator);
+    }
+
+    /*!
+    @brief updates a JSON object from another object, overwriting existing keys
+
+    Inserts all values from JSON object @a j and overwrites existing keys.
+
+    @param[in] j  JSON object to read values from
+
+    @throw type_error.312 if called on JSON values other than objects; example:
+    `"cannot use update() with string"`
+
+    @complexity O(N*log(size() + N)), where N is the number of elements to
+                insert.
+
+    @liveexample{The example shows how `update()` is used.,update}
+
+    @sa https://docs.python.org/3.6/library/stdtypes.html#dict.update
+
+    @since version 3.0.0
+    */
+    void update(const_reference j)
+    {
+        // implicitly convert null value to an empty object
+        if (is_null())
+        {
+            m_type = value_t::object;
+            m_value.object = create<object_t>();
+            assert_invariant();
+        }
+
+        if (JSON_UNLIKELY(not is_object()))
+        {
+            JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(type_name())));
+        }
+        if (JSON_UNLIKELY(not j.is_object()))
+        {
+            JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(j.type_name())));
+        }
+
+        for (auto it = j.cbegin(); it != j.cend(); ++it)
+        {
+            m_value.object->operator[](it.key()) = it.value();
+        }
+    }
+
+    /*!
+    @brief updates a JSON object from another object, overwriting existing keys
+
+    Inserts all values from from range `[first, last)` and overwrites existing
+    keys.
+
+    @param[in] first begin of the range of elements to insert
+    @param[in] last end of the range of elements to insert
+
+    @throw type_error.312 if called on JSON values other than objects; example:
+    `"cannot use update() with string"`
+    @throw invalid_iterator.202 if iterator @a first or @a last does does not
+    point to an object; example: `"iterators first and last must point to
+    objects"`
+    @throw invalid_iterator.210 if @a first and @a last do not belong to the
+    same JSON value; example: `"iterators do not fit"`
+
+    @complexity O(N*log(size() + N)), where N is the number of elements to
+                insert.
+
+    @liveexample{The example shows how `update()` is used__range.,update}
+
+    @sa https://docs.python.org/3.6/library/stdtypes.html#dict.update
+
+    @since version 3.0.0
+    */
+    void update(const_iterator first, const_iterator last)
+    {
+        // implicitly convert null value to an empty object
+        if (is_null())
+        {
+            m_type = value_t::object;
+            m_value.object = create<object_t>();
+            assert_invariant();
+        }
+
+        if (JSON_UNLIKELY(not is_object()))
+        {
+            JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(type_name())));
+        }
+
+        // check if range iterators belong to the same JSON object
+        if (JSON_UNLIKELY(first.m_object != last.m_object))
+        {
+            JSON_THROW(invalid_iterator::create(210, "iterators do not fit"));
+        }
+
+        // passed iterators must belong to objects
+        if (JSON_UNLIKELY(not first.m_object->is_object()
+                          or not last.m_object->is_object()))
+        {
+            JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects"));
+        }
+
+        for (auto it = first; it != last; ++it)
+        {
+            m_value.object->operator[](it.key()) = it.value();
+        }
+    }
+
+    /*!
+    @brief exchanges the values
+
+    Exchanges the contents of the JSON value with those of @a other. Does not
+    invoke any move, copy, or swap operations on individual elements. All
+    iterators and references remain valid. The past-the-end iterator is
+    invalidated.
+
+    @param[in,out] other JSON value to exchange the contents with
+
+    @complexity Constant.
+
+    @liveexample{The example below shows how JSON values can be swapped with
+    `swap()`.,swap__reference}
+
+    @since version 1.0.0
+    */
+    void swap(reference other) noexcept (
+        std::is_nothrow_move_constructible<value_t>::value and
+        std::is_nothrow_move_assignable<value_t>::value and
+        std::is_nothrow_move_constructible<json_value>::value and
+        std::is_nothrow_move_assignable<json_value>::value
+    )
+    {
+        std::swap(m_type, other.m_type);
+        std::swap(m_value, other.m_value);
+        assert_invariant();
+    }
+
+    /*!
+    @brief exchanges the values
+
+    Exchanges the contents of a JSON array with those of @a other. Does not
+    invoke any move, copy, or swap operations on individual elements. All
+    iterators and references remain valid. The past-the-end iterator is
+    invalidated.
+
+    @param[in,out] other array to exchange the contents with
+
+    @throw type_error.310 when JSON value is not an array; example: `"cannot
+    use swap() with string"`
+
+    @complexity Constant.
+
+    @liveexample{The example below shows how arrays can be swapped with
+    `swap()`.,swap__array_t}
+
+    @since version 1.0.0
+    */
+    void swap(array_t& other)
+    {
+        // swap only works for arrays
+        if (JSON_LIKELY(is_array()))
+        {
+            std::swap(*(m_value.array), other);
+        }
+        else
+        {
+            JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name())));
+        }
+    }
+
+    /*!
+    @brief exchanges the values
+
+    Exchanges the contents of a JSON object with those of @a other. Does not
+    invoke any move, copy, or swap operations on individual elements. All
+    iterators and references remain valid. The past-the-end iterator is
+    invalidated.
+
+    @param[in,out] other object to exchange the contents with
+
+    @throw type_error.310 when JSON value is not an object; example:
+    `"cannot use swap() with string"`
+
+    @complexity Constant.
+
+    @liveexample{The example below shows how objects can be swapped with
+    `swap()`.,swap__object_t}
+
+    @since version 1.0.0
+    */
+    void swap(object_t& other)
+    {
+        // swap only works for objects
+        if (JSON_LIKELY(is_object()))
+        {
+            std::swap(*(m_value.object), other);
+        }
+        else
+        {
+            JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name())));
+        }
+    }
+
+    /*!
+    @brief exchanges the values
+
+    Exchanges the contents of a JSON string with those of @a other. Does not
+    invoke any move, copy, or swap operations on individual elements. All
+    iterators and references remain valid. The past-the-end iterator is
+    invalidated.
+
+    @param[in,out] other string to exchange the contents with
+
+    @throw type_error.310 when JSON value is not a string; example: `"cannot
+    use swap() with boolean"`
+
+    @complexity Constant.
+
+    @liveexample{The example below shows how strings can be swapped with
+    `swap()`.,swap__string_t}
+
+    @since version 1.0.0
+    */
+    void swap(string_t& other)
+    {
+        // swap only works for strings
+        if (JSON_LIKELY(is_string()))
+        {
+            std::swap(*(m_value.string), other);
+        }
+        else
+        {
+            JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name())));
+        }
+    }
+
+    /// @}
+
+  public:
+    //////////////////////////////////////////
+    // lexicographical comparison operators //
+    //////////////////////////////////////////
+
+    /// @name lexicographical comparison operators
+    /// @{
+
+    /*!
+    @brief comparison: equal
+
+    Compares two JSON values for equality according to the following rules:
+    - Two JSON values are equal if (1) they are from the same type and (2)
+      their stored values are the same according to their respective
+      `operator==`.
+    - Integer and floating-point numbers are automatically converted before
+      comparison. Note than two NaN values are always treated as unequal.
+    - Two JSON null values are equal.
+
+    @note Floating-point inside JSON values numbers are compared with
+    `json::number_float_t::operator==` which is `double::operator==` by
+    default. To compare floating-point while respecting an epsilon, an alternative
+    [comparison function](https://github.com/mariokonrad/marnav/blob/master/src/marnav/math/floatingpoint.hpp#L34-#L39)
+    could be used, for instance
+    @code {.cpp}
+    template<typename T, typename = typename std::enable_if<std::is_floating_point<T>::value, T>::type>
+    inline bool is_same(T a, T b, T epsilon = std::numeric_limits<T>::epsilon()) noexcept
+    {
+        return std::abs(a - b) <= epsilon;
+    }
+    @endcode
+
+    @note NaN values never compare equal to themselves or to other NaN values.
+
+    @param[in] lhs  first JSON value to consider
+    @param[in] rhs  second JSON value to consider
+    @return whether the values @a lhs and @a rhs are equal
+
+    @exceptionsafety No-throw guarantee: this function never throws exceptions.
+
+    @complexity Linear.
+
+    @liveexample{The example demonstrates comparing several JSON
+    types.,operator__equal}
+
+    @since version 1.0.0
+    */
+    friend bool operator==(const_reference lhs, const_reference rhs) noexcept
+    {
+        const auto lhs_type = lhs.type();
+        const auto rhs_type = rhs.type();
+
+        if (lhs_type == rhs_type)
+        {
+            switch (lhs_type)
+            {
+                case value_t::array:
+                    return (*lhs.m_value.array == *rhs.m_value.array);
+
+                case value_t::object:
+                    return (*lhs.m_value.object == *rhs.m_value.object);
+
+                case value_t::null:
+                    return true;
+
+                case value_t::string:
+                    return (*lhs.m_value.string == *rhs.m_value.string);
+
+                case value_t::boolean:
+                    return (lhs.m_value.boolean == rhs.m_value.boolean);
+
+                case value_t::number_integer:
+                    return (lhs.m_value.number_integer == rhs.m_value.number_integer);
+
+                case value_t::number_unsigned:
+                    return (lhs.m_value.number_unsigned == rhs.m_value.number_unsigned);
+
+                case value_t::number_float:
+                    return (lhs.m_value.number_float == rhs.m_value.number_float);
+
+                default:
+                    return false;
+            }
+        }
+        else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_float)
+        {
+            return (static_cast<number_float_t>(lhs.m_value.number_integer) == rhs.m_value.number_float);
+        }
+        else if (lhs_type == value_t::number_float and rhs_type == value_t::number_integer)
+        {
+            return (lhs.m_value.number_float == static_cast<number_float_t>(rhs.m_value.number_integer));
+        }
+        else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_float)
+        {
+            return (static_cast<number_float_t>(lhs.m_value.number_unsigned) == rhs.m_value.number_float);
+        }
+        else if (lhs_type == value_t::number_float and rhs_type == value_t::number_unsigned)
+        {
+            return (lhs.m_value.number_float == static_cast<number_float_t>(rhs.m_value.number_unsigned));
+        }
+        else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_integer)
+        {
+            return (static_cast<number_integer_t>(lhs.m_value.number_unsigned) == rhs.m_value.number_integer);
+        }
+        else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_unsigned)
+        {
+            return (lhs.m_value.number_integer == static_cast<number_integer_t>(rhs.m_value.number_unsigned));
+        }
+
+        return false;
+    }
+
+    /*!
+    @brief comparison: equal
+    @copydoc operator==(const_reference, const_reference)
+    */
+    template<typename ScalarType, typename std::enable_if<
+                 std::is_scalar<ScalarType>::value, int>::type = 0>
+    friend bool operator==(const_reference lhs, const ScalarType rhs) noexcept
+    {
+        return (lhs == basic_json(rhs));
+    }
+
+    /*!
+    @brief comparison: equal
+    @copydoc operator==(const_reference, const_reference)
+    */
+    template<typename ScalarType, typename std::enable_if<
+                 std::is_scalar<ScalarType>::value, int>::type = 0>
+    friend bool operator==(const ScalarType lhs, const_reference rhs) noexcept
+    {
+        return (basic_json(lhs) == rhs);
+    }
+
+    /*!
+    @brief comparison: not equal
+
+    Compares two JSON values for inequality by calculating `not (lhs == rhs)`.
+
+    @param[in] lhs  first JSON value to consider
+    @param[in] rhs  second JSON value to consider
+    @return whether the values @a lhs and @a rhs are not equal
+
+    @complexity Linear.
+
+    @exceptionsafety No-throw guarantee: this function never throws exceptions.
+
+    @liveexample{The example demonstrates comparing several JSON
+    types.,operator__notequal}
+
+    @since version 1.0.0
+    */
+    friend bool operator!=(const_reference lhs, const_reference rhs) noexcept
+    {
+        return not (lhs == rhs);
+    }
+
+    /*!
+    @brief comparison: not equal
+    @copydoc operator!=(const_reference, const_reference)
+    */
+    template<typename ScalarType, typename std::enable_if<
+                 std::is_scalar<ScalarType>::value, int>::type = 0>
+    friend bool operator!=(const_reference lhs, const ScalarType rhs) noexcept
+    {
+        return (lhs != basic_json(rhs));
+    }
+
+    /*!
+    @brief comparison: not equal
+    @copydoc operator!=(const_reference, const_reference)
+    */
+    template<typename ScalarType, typename std::enable_if<
+                 std::is_scalar<ScalarType>::value, int>::type = 0>
+    friend bool operator!=(const ScalarType lhs, const_reference rhs) noexcept
+    {
+        return (basic_json(lhs) != rhs);
+    }
+
+    /*!
+    @brief comparison: less than
+
+    Compares whether one JSON value @a lhs is less than another JSON value @a
+    rhs according to the following rules:
+    - If @a lhs and @a rhs have the same type, the values are compared using
+      the default `<` operator.
+    - Integer and floating-point numbers are automatically converted before
+      comparison
+    - In case @a lhs and @a rhs have different types, the values are ignored
+      and the order of the types is considered, see
+      @ref operator<(const value_t, const value_t).
+
+    @param[in] lhs  first JSON value to consider
+    @param[in] rhs  second JSON value to consider
+    @return whether @a lhs is less than @a rhs
+
+    @complexity Linear.
+
+    @exceptionsafety No-throw guarantee: this function never throws exceptions.
+
+    @liveexample{The example demonstrates comparing several JSON
+    types.,operator__less}
+
+    @since version 1.0.0
+    */
+    friend bool operator<(const_reference lhs, const_reference rhs) noexcept
+    {
+        const auto lhs_type = lhs.type();
+        const auto rhs_type = rhs.type();
+
+        if (lhs_type == rhs_type)
+        {
+            switch (lhs_type)
+            {
+                case value_t::array:
+                    return (*lhs.m_value.array) < (*rhs.m_value.array);
+
+                case value_t::object:
+                    return *lhs.m_value.object < *rhs.m_value.object;
+
+                case value_t::null:
+                    return false;
+
+                case value_t::string:
+                    return *lhs.m_value.string < *rhs.m_value.string;
+
+                case value_t::boolean:
+                    return lhs.m_value.boolean < rhs.m_value.boolean;
+
+                case value_t::number_integer:
+                    return lhs.m_value.number_integer < rhs.m_value.number_integer;
+
+                case value_t::number_unsigned:
+                    return lhs.m_value.number_unsigned < rhs.m_value.number_unsigned;
+
+                case value_t::number_float:
+                    return lhs.m_value.number_float < rhs.m_value.number_float;
+
+                default:
+                    return false;
+            }
+        }
+        else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_float)
+        {
+            return static_cast<number_float_t>(lhs.m_value.number_integer) < rhs.m_value.number_float;
+        }
+        else if (lhs_type == value_t::number_float and rhs_type == value_t::number_integer)
+        {
+            return lhs.m_value.number_float < static_cast<number_float_t>(rhs.m_value.number_integer);
+        }
+        else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_float)
+        {
+            return static_cast<number_float_t>(lhs.m_value.number_unsigned) < rhs.m_value.number_float;
+        }
+        else if (lhs_type == value_t::number_float and rhs_type == value_t::number_unsigned)
+        {
+            return lhs.m_value.number_float < static_cast<number_float_t>(rhs.m_value.number_unsigned);
+        }
+        else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_unsigned)
+        {
+            return lhs.m_value.number_integer < static_cast<number_integer_t>(rhs.m_value.number_unsigned);
+        }
+        else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_integer)
+        {
+            return static_cast<number_integer_t>(lhs.m_value.number_unsigned) < rhs.m_value.number_integer;
+        }
+
+        // We only reach this line if we cannot compare values. In that case,
+        // we compare types. Note we have to call the operator explicitly,
+        // because MSVC has problems otherwise.
+        return operator<(lhs_type, rhs_type);
+    }
+
+    /*!
+    @brief comparison: less than
+    @copydoc operator<(const_reference, const_reference)
+    */
+    template<typename ScalarType, typename std::enable_if<
+                 std::is_scalar<ScalarType>::value, int>::type = 0>
+    friend bool operator<(const_reference lhs, const ScalarType rhs) noexcept
+    {
+        return (lhs < basic_json(rhs));
+    }
+
+    /*!
+    @brief comparison: less than
+    @copydoc operator<(const_reference, const_reference)
+    */
+    template<typename ScalarType, typename std::enable_if<
+                 std::is_scalar<ScalarType>::value, int>::type = 0>
+    friend bool operator<(const ScalarType lhs, const_reference rhs) noexcept
+    {
+        return (basic_json(lhs) < rhs);
+    }
+
+    /*!
+    @brief comparison: less than or equal
+
+    Compares whether one JSON value @a lhs is less than or equal to another
+    JSON value by calculating `not (rhs < lhs)`.
+
+    @param[in] lhs  first JSON value to consider
+    @param[in] rhs  second JSON value to consider
+    @return whether @a lhs is less than or equal to @a rhs
+
+    @complexity Linear.
+
+    @exceptionsafety No-throw guarantee: this function never throws exceptions.
+
+    @liveexample{The example demonstrates comparing several JSON
+    types.,operator__greater}
+
+    @since version 1.0.0
+    */
+    friend bool operator<=(const_reference lhs, const_reference rhs) noexcept
+    {
+        return not (rhs < lhs);
+    }
+
+    /*!
+    @brief comparison: less than or equal
+    @copydoc operator<=(const_reference, const_reference)
+    */
+    template<typename ScalarType, typename std::enable_if<
+                 std::is_scalar<ScalarType>::value, int>::type = 0>
+    friend bool operator<=(const_reference lhs, const ScalarType rhs) noexcept
+    {
+        return (lhs <= basic_json(rhs));
+    }
+
+    /*!
+    @brief comparison: less than or equal
+    @copydoc operator<=(const_reference, const_reference)
+    */
+    template<typename ScalarType, typename std::enable_if<
+                 std::is_scalar<ScalarType>::value, int>::type = 0>
+    friend bool operator<=(const ScalarType lhs, const_reference rhs) noexcept
+    {
+        return (basic_json(lhs) <= rhs);
+    }
+
+    /*!
+    @brief comparison: greater than
+
+    Compares whether one JSON value @a lhs is greater than another
+    JSON value by calculating `not (lhs <= rhs)`.
+
+    @param[in] lhs  first JSON value to consider
+    @param[in] rhs  second JSON value to consider
+    @return whether @a lhs is greater than to @a rhs
+
+    @complexity Linear.
+
+    @exceptionsafety No-throw guarantee: this function never throws exceptions.
+
+    @liveexample{The example demonstrates comparing several JSON
+    types.,operator__lessequal}
+
+    @since version 1.0.0
+    */
+    friend bool operator>(const_reference lhs, const_reference rhs) noexcept
+    {
+        return not (lhs <= rhs);
+    }
+
+    /*!
+    @brief comparison: greater than
+    @copydoc operator>(const_reference, const_reference)
+    */
+    template<typename ScalarType, typename std::enable_if<
+                 std::is_scalar<ScalarType>::value, int>::type = 0>
+    friend bool operator>(const_reference lhs, const ScalarType rhs) noexcept
+    {
+        return (lhs > basic_json(rhs));
+    }
+
+    /*!
+    @brief comparison: greater than
+    @copydoc operator>(const_reference, const_reference)
+    */
+    template<typename ScalarType, typename std::enable_if<
+                 std::is_scalar<ScalarType>::value, int>::type = 0>
+    friend bool operator>(const ScalarType lhs, const_reference rhs) noexcept
+    {
+        return (basic_json(lhs) > rhs);
+    }
+
+    /*!
+    @brief comparison: greater than or equal
+
+    Compares whether one JSON value @a lhs is greater than or equal to another
+    JSON value by calculating `not (lhs < rhs)`.
+
+    @param[in] lhs  first JSON value to consider
+    @param[in] rhs  second JSON value to consider
+    @return whether @a lhs is greater than or equal to @a rhs
+
+    @complexity Linear.
+
+    @exceptionsafety No-throw guarantee: this function never throws exceptions.
+
+    @liveexample{The example demonstrates comparing several JSON
+    types.,operator__greaterequal}
+
+    @since version 1.0.0
+    */
+    friend bool operator>=(const_reference lhs, const_reference rhs) noexcept
+    {
+        return not (lhs < rhs);
+    }
+
+    /*!
+    @brief comparison: greater than or equal
+    @copydoc operator>=(const_reference, const_reference)
+    */
+    template<typename ScalarType, typename std::enable_if<
+                 std::is_scalar<ScalarType>::value, int>::type = 0>
+    friend bool operator>=(const_reference lhs, const ScalarType rhs) noexcept
+    {
+        return (lhs >= basic_json(rhs));
+    }
+
+    /*!
+    @brief comparison: greater than or equal
+    @copydoc operator>=(const_reference, const_reference)
+    */
+    template<typename ScalarType, typename std::enable_if<
+                 std::is_scalar<ScalarType>::value, int>::type = 0>
+    friend bool operator>=(const ScalarType lhs, const_reference rhs) noexcept
+    {
+        return (basic_json(lhs) >= rhs);
+    }
+
+    /// @}
+
+    ///////////////////
+    // serialization //
+    ///////////////////
+
+    /// @name serialization
+    /// @{
+
+    /*!
+    @brief serialize to stream
+
+    Serialize the given JSON value @a j to the output stream @a o. The JSON
+    value will be serialized using the @ref dump member function.
+
+    - The indentation of the output can be controlled with the member variable
+      `width` of the output stream @a o. For instance, using the manipulator
+      `std::setw(4)` on @a o sets the indentation level to `4` and the
+      serialization result is the same as calling `dump(4)`.
+
+    - The indentation character can be controlled with the member variable
+      `fill` of the output stream @a o. For instance, the manipulator
+      `std::setfill('\\t')` sets indentation to use a tab character rather than
+      the default space character.
+
+    @param[in,out] o  stream to serialize to
+    @param[in] j  JSON value to serialize
+
+    @return the stream @a o
+
+    @throw type_error.316 if a string stored inside the JSON value is not
+                          UTF-8 encoded
+
+    @complexity Linear.
+
+    @liveexample{The example below shows the serialization with different
+    parameters to `width` to adjust the indentation level.,operator_serialize}
+
+    @since version 1.0.0; indentation character added in version 3.0.0
+    */
+    friend std::ostream& operator<<(std::ostream& o, const basic_json& j)
+    {
+        // read width member and use it as indentation parameter if nonzero
+        const bool pretty_print = (o.width() > 0);
+        const auto indentation = (pretty_print ? o.width() : 0);
+
+        // reset width to 0 for subsequent calls to this stream
+        o.width(0);
+
+        // do the actual serialization
+        serializer s(detail::output_adapter<char>(o), o.fill());
+        s.dump(j, pretty_print, false, static_cast<unsigned int>(indentation));
+        return o;
+    }
+
+    /*!
+    @brief serialize to stream
+    @deprecated This stream operator is deprecated and will be removed in
+                future 4.0.0 of the library. Please use
+                @ref operator<<(std::ostream&, const basic_json&)
+                instead; that is, replace calls like `j >> o;` with `o << j;`.
+    @since version 1.0.0; deprecated since version 3.0.0
+    */
+    JSON_DEPRECATED
+    friend std::ostream& operator>>(const basic_json& j, std::ostream& o)
+    {
+        return o << j;
+    }
+
+    /// @}
+
+
+    /////////////////////
+    // deserialization //
+    /////////////////////
+
+    /// @name deserialization
+    /// @{
+
+    /*!
+    @brief deserialize from a compatible input
+
+    This function reads from a compatible input. Examples are:
+    - an array of 1-byte values
+    - strings with character/literal type with size of 1 byte
+    - input streams
+    - container with contiguous storage of 1-byte values. Compatible container
+      types include `std::vector`, `std::string`, `std::array`,
+      `std::valarray`, and `std::initializer_list`. Furthermore, C-style
+      arrays can be used with `std::begin()`/`std::end()`. User-defined
+      containers can be used as long as they implement random-access iterators
+      and a contiguous storage.
+
+    @pre Each element of the container has a size of 1 byte. Violating this
+    precondition yields undefined behavior. **This precondition is enforced
+    with a static assertion.**
+
+    @pre The container storage is contiguous. Violating this precondition
+    yields undefined behavior. **This precondition is enforced with an
+    assertion.**
+    @pre Each element of the container has a size of 1 byte. Violating this
+    precondition yields undefined behavior. **This precondition is enforced
+    with a static assertion.**
+
+    @warning There is no way to enforce all preconditions at compile-time. If
+             the function is called with a noncompliant container and with
+             assertions switched off, the behavior is undefined and will most
+             likely yield segmentation violation.
+
+    @param[in] i  input to read from
+    @param[in] cb  a parser callback function of type @ref parser_callback_t
+    which is used to control the deserialization by filtering unwanted values
+    (optional)
+    @param[in] allow_exceptions  whether to throw exceptions in case of a
+    parse error (optional, true by default)
+
+    @return result of the deserialization
+
+    @throw parse_error.101 if a parse error occurs; example: `""unexpected end
+    of input; expected string literal""`
+    @throw parse_error.102 if to_unicode fails or surrogate error
+    @throw parse_error.103 if to_unicode fails
+
+    @complexity Linear in the length of the input. The parser is a predictive
+    LL(1) parser. The complexity can be higher if the parser callback function
+    @a cb has a super-linear complexity.
+
+    @note A UTF-8 byte order mark is silently ignored.
+
+    @liveexample{The example below demonstrates the `parse()` function reading
+    from an array.,parse__array__parser_callback_t}
+
+    @liveexample{The example below demonstrates the `parse()` function with
+    and without callback function.,parse__string__parser_callback_t}
+
+    @liveexample{The example below demonstrates the `parse()` function with
+    and without callback function.,parse__istream__parser_callback_t}
+
+    @liveexample{The example below demonstrates the `parse()` function reading
+    from a contiguous container.,parse__contiguouscontainer__parser_callback_t}
+
+    @since version 2.0.3 (contiguous containers)
+    */
+    static basic_json parse(detail::input_adapter&& i,
+                            const parser_callback_t cb = nullptr,
+                            const bool allow_exceptions = true)
+    {
+        basic_json result;
+        parser(i, cb, allow_exceptions).parse(true, result);
+        return result;
+    }
+
+    static bool accept(detail::input_adapter&& i)
+    {
+        return parser(i).accept(true);
+    }
+
+    /*!
+    @brief generate SAX events
+
+    The SAX event lister must follow the interface of @ref json_sax.
+
+    This function reads from a compatible input. Examples are:
+    - an array of 1-byte values
+    - strings with character/literal type with size of 1 byte
+    - input streams
+    - container with contiguous storage of 1-byte values. Compatible container
+      types include `std::vector`, `std::string`, `std::array`,
+      `std::valarray`, and `std::initializer_list`. Furthermore, C-style
+      arrays can be used with `std::begin()`/`std::end()`. User-defined
+      containers can be used as long as they implement random-access iterators
+      and a contiguous storage.
+
+    @pre Each element of the container has a size of 1 byte. Violating this
+    precondition yields undefined behavior. **This precondition is enforced
+    with a static assertion.**
+
+    @pre The container storage is contiguous. Violating this precondition
+    yields undefined behavior. **This precondition is enforced with an
+    assertion.**
+    @pre Each element of the container has a size of 1 byte. Violating this
+    precondition yields undefined behavior. **This precondition is enforced
+    with a static assertion.**
+
+    @warning There is no way to enforce all preconditions at compile-time. If
+             the function is called with a noncompliant container and with
+             assertions switched off, the behavior is undefined and will most
+             likely yield segmentation violation.
+
+    @param[in] i  input to read from
+    @param[in,out] sax  SAX event listener
+    @param[in] format  the format to parse (JSON, CBOR, MessagePack, or UBJSON)
+    @param[in] strict  whether the input has to be consumed completely
+
+    @return return value of the last processed SAX event
+
+    @throw parse_error.101 if a parse error occurs; example: `""unexpected end
+    of input; expected string literal""`
+    @throw parse_error.102 if to_unicode fails or surrogate error
+    @throw parse_error.103 if to_unicode fails
+
+    @complexity Linear in the length of the input. The parser is a predictive
+    LL(1) parser. The complexity can be higher if the SAX consumer @a sax has
+    a super-linear complexity.
+
+    @note A UTF-8 byte order mark is silently ignored.
+
+    @liveexample{The example below demonstrates the `sax_parse()` function
+    reading from string and processing the events with a user-defined SAX
+    event consumer.,sax_parse}
+
+    @since version 3.2.0
+    */
+    template <typename SAX>
+    static bool sax_parse(detail::input_adapter&& i, SAX* sax,
+                          input_format_t format = input_format_t::json,
+                          const bool strict = true)
+    {
+        assert(sax);
+        switch (format)
+        {
+            case input_format_t::json:
+                return parser(std::move(i)).sax_parse(sax, strict);
+            default:
+                return detail::binary_reader<basic_json, SAX>(std::move(i)).sax_parse(format, sax, strict);
+        }
+    }
+
+    /*!
+    @brief deserialize from an iterator range with contiguous storage
+
+    This function reads from an iterator range of a container with contiguous
+    storage of 1-byte values. Compatible container types include
+    `std::vector`, `std::string`, `std::array`, `std::valarray`, and
+    `std::initializer_list`. Furthermore, C-style arrays can be used with
+    `std::begin()`/`std::end()`. User-defined containers can be used as long
+    as they implement random-access iterators and a contiguous storage.
+
+    @pre The iterator range is contiguous. Violating this precondition yields
+    undefined behavior. **This precondition is enforced with an assertion.**
+    @pre Each element in the range has a size of 1 byte. Violating this
+    precondition yields undefined behavior. **This precondition is enforced
+    with a static assertion.**
+
+    @warning There is no way to enforce all preconditions at compile-time. If
+             the function is called with noncompliant iterators and with
+             assertions switched off, the behavior is undefined and will most
+             likely yield segmentation violation.
+
+    @tparam IteratorType iterator of container with contiguous storage
+    @param[in] first  begin of the range to parse (included)
+    @param[in] last  end of the range to parse (excluded)
+    @param[in] cb  a parser callback function of type @ref parser_callback_t
+    which is used to control the deserialization by filtering unwanted values
+    (optional)
+    @param[in] allow_exceptions  whether to throw exceptions in case of a
+    parse error (optional, true by default)
+
+    @return result of the deserialization
+
+    @throw parse_error.101 in case of an unexpected token
+    @throw parse_error.102 if to_unicode fails or surrogate error
+    @throw parse_error.103 if to_unicode fails
+
+    @complexity Linear in the length of the input. The parser is a predictive
+    LL(1) parser. The complexity can be higher if the parser callback function
+    @a cb has a super-linear complexity.
+
+    @note A UTF-8 byte order mark is silently ignored.
+
+    @liveexample{The example below demonstrates the `parse()` function reading
+    from an iterator range.,parse__iteratortype__parser_callback_t}
+
+    @since version 2.0.3
+    */
+    template<class IteratorType, typename std::enable_if<
+                 std::is_base_of<
+                     std::random_access_iterator_tag,
+                     typename std::iterator_traits<IteratorType>::iterator_category>::value, int>::type = 0>
+    static basic_json parse(IteratorType first, IteratorType last,
+                            const parser_callback_t cb = nullptr,
+                            const bool allow_exceptions = true)
+    {
+        basic_json result;
+        parser(detail::input_adapter(first, last), cb, allow_exceptions).parse(true, result);
+        return result;
+    }
+
+    template<class IteratorType, typename std::enable_if<
+                 std::is_base_of<
+                     std::random_access_iterator_tag,
+                     typename std::iterator_traits<IteratorType>::iterator_category>::value, int>::type = 0>
+    static bool accept(IteratorType first, IteratorType last)
+    {
+        return parser(detail::input_adapter(first, last)).accept(true);
+    }
+
+    template<class IteratorType, class SAX, typename std::enable_if<
+                 std::is_base_of<
+                     std::random_access_iterator_tag,
+                     typename std::iterator_traits<IteratorType>::iterator_category>::value, int>::type = 0>
+    static bool sax_parse(IteratorType first, IteratorType last, SAX* sax)
+    {
+        return parser(detail::input_adapter(first, last)).sax_parse(sax);
+    }
+
+    /*!
+    @brief deserialize from stream
+    @deprecated This stream operator is deprecated and will be removed in
+                version 4.0.0 of the library. Please use
+                @ref operator>>(std::istream&, basic_json&)
+                instead; that is, replace calls like `j << i;` with `i >> j;`.
+    @since version 1.0.0; deprecated since version 3.0.0
+    */
+    JSON_DEPRECATED
+    friend std::istream& operator<<(basic_json& j, std::istream& i)
+    {
+        return operator>>(i, j);
+    }
+
+    /*!
+    @brief deserialize from stream
+
+    Deserializes an input stream to a JSON value.
+
+    @param[in,out] i  input stream to read a serialized JSON value from
+    @param[in,out] j  JSON value to write the deserialized input to
+
+    @throw parse_error.101 in case of an unexpected token
+    @throw parse_error.102 if to_unicode fails or surrogate error
+    @throw parse_error.103 if to_unicode fails
+
+    @complexity Linear in the length of the input. The parser is a predictive
+    LL(1) parser.
+
+    @note A UTF-8 byte order mark is silently ignored.
+
+    @liveexample{The example below shows how a JSON value is constructed by
+    reading a serialization from a stream.,operator_deserialize}
+
+    @sa parse(std::istream&, const parser_callback_t) for a variant with a
+    parser callback function to filter values while parsing
+
+    @since version 1.0.0
+    */
+    friend std::istream& operator>>(std::istream& i, basic_json& j)
+    {
+        parser(detail::input_adapter(i)).parse(false, j);
+        return i;
+    }
+
+    /// @}
+
+    ///////////////////////////
+    // convenience functions //
+    ///////////////////////////
+
+    /*!
+    @brief return the type as string
+
+    Returns the type name as string to be used in error messages - usually to
+    indicate that a function was called on a wrong JSON type.
+
+    @return a string representation of a the @a m_type member:
+            Value type  | return value
+            ----------- | -------------
+            null        | `"null"`
+            boolean     | `"boolean"`
+            string      | `"string"`
+            number      | `"number"` (for all number types)
+            object      | `"object"`
+            array       | `"array"`
+            discarded   | `"discarded"`
+
+    @exceptionsafety No-throw guarantee: this function never throws exceptions.
+
+    @complexity Constant.
+
+    @liveexample{The following code exemplifies `type_name()` for all JSON
+    types.,type_name}
+
+    @sa @ref type() -- return the type of the JSON value
+    @sa @ref operator value_t() -- return the type of the JSON value (implicit)
+
+    @since version 1.0.0, public since 2.1.0, `const char*` and `noexcept`
+    since 3.0.0
+    */
+    const char* type_name() const noexcept
+    {
+        {
+            switch (m_type)
+            {
+                case value_t::null:
+                    return "null";
+                case value_t::object:
+                    return "object";
+                case value_t::array:
+                    return "array";
+                case value_t::string:
+                    return "string";
+                case value_t::boolean:
+                    return "boolean";
+                case value_t::discarded:
+                    return "discarded";
+                default:
+                    return "number";
+            }
+        }
+    }
+
+
+  private:
+    //////////////////////
+    // member variables //
+    //////////////////////
+
+    /// the type of the current element
+    value_t m_type = value_t::null;
+
+    /// the value of the current element
+    json_value m_value = {};
+
+    //////////////////////////////////////////
+    // binary serialization/deserialization //
+    //////////////////////////////////////////
+
+    /// @name binary serialization/deserialization support
+    /// @{
+
+  public:
+    /*!
+    @brief create a CBOR serialization of a given JSON value
+
+    Serializes a given JSON value @a j to a byte vector using the CBOR (Concise
+    Binary Object Representation) serialization format. CBOR is a binary
+    serialization format which aims to be more compact than JSON itself, yet
+    more efficient to parse.
+
+    The library uses the following mapping from JSON values types to
+    CBOR types according to the CBOR specification (RFC 7049):
+
+    JSON value type | value/range                                | CBOR type                          | first byte
+    --------------- | ------------------------------------------ | ---------------------------------- | ---------------
+    null            | `null`                                     | Null                               | 0xF6
+    boolean         | `true`                                     | True                               | 0xF5
+    boolean         | `false`                                    | False                              | 0xF4
+    number_integer  | -9223372036854775808..-2147483649          | Negative integer (8 bytes follow)  | 0x3B
+    number_integer  | -2147483648..-32769                        | Negative integer (4 bytes follow)  | 0x3A
+    number_integer  | -32768..-129                               | Negative integer (2 bytes follow)  | 0x39
+    number_integer  | -128..-25                                  | Negative integer (1 byte follow)   | 0x38
+    number_integer  | -24..-1                                    | Negative integer                   | 0x20..0x37
+    number_integer  | 0..23                                      | Integer                            | 0x00..0x17
+    number_integer  | 24..255                                    | Unsigned integer (1 byte follow)   | 0x18
+    number_integer  | 256..65535                                 | Unsigned integer (2 bytes follow)  | 0x19
+    number_integer  | 65536..4294967295                          | Unsigned integer (4 bytes follow)  | 0x1A
+    number_integer  | 4294967296..18446744073709551615           | Unsigned integer (8 bytes follow)  | 0x1B
+    number_unsigned | 0..23                                      | Integer                            | 0x00..0x17
+    number_unsigned | 24..255                                    | Unsigned integer (1 byte follow)   | 0x18
+    number_unsigned | 256..65535                                 | Unsigned integer (2 bytes follow)  | 0x19
+    number_unsigned | 65536..4294967295                          | Unsigned integer (4 bytes follow)  | 0x1A
+    number_unsigned | 4294967296..18446744073709551615           | Unsigned integer (8 bytes follow)  | 0x1B
+    number_float    | *any value*                                | Double-Precision Float             | 0xFB
+    string          | *length*: 0..23                            | UTF-8 string                       | 0x60..0x77
+    string          | *length*: 23..255                          | UTF-8 string (1 byte follow)       | 0x78
+    string          | *length*: 256..65535                       | UTF-8 string (2 bytes follow)      | 0x79
+    string          | *length*: 65536..4294967295                | UTF-8 string (4 bytes follow)      | 0x7A
+    string          | *length*: 4294967296..18446744073709551615 | UTF-8 string (8 bytes follow)      | 0x7B
+    array           | *size*: 0..23                              | array                              | 0x80..0x97
+    array           | *size*: 23..255                            | array (1 byte follow)              | 0x98
+    array           | *size*: 256..65535                         | array (2 bytes follow)             | 0x99
+    array           | *size*: 65536..4294967295                  | array (4 bytes follow)             | 0x9A
+    array           | *size*: 4294967296..18446744073709551615   | array (8 bytes follow)             | 0x9B
+    object          | *size*: 0..23                              | map                                | 0xA0..0xB7
+    object          | *size*: 23..255                            | map (1 byte follow)                | 0xB8
+    object          | *size*: 256..65535                         | map (2 bytes follow)               | 0xB9
+    object          | *size*: 65536..4294967295                  | map (4 bytes follow)               | 0xBA
+    object          | *size*: 4294967296..18446744073709551615   | map (8 bytes follow)               | 0xBB
+
+    @note The mapping is **complete** in the sense that any JSON value type
+          can be converted to a CBOR value.
+
+    @note If NaN or Infinity are stored inside a JSON number, they are
+          serialized properly. This behavior differs from the @ref dump()
+          function which serializes NaN or Infinity to `null`.
+
+    @note The following CBOR types are not used in the conversion:
+          - byte strings (0x40..0x5F)
+          - UTF-8 strings terminated by "break" (0x7F)
+          - arrays terminated by "break" (0x9F)
+          - maps terminated by "break" (0xBF)
+          - date/time (0xC0..0xC1)
+          - bignum (0xC2..0xC3)
+          - decimal fraction (0xC4)
+          - bigfloat (0xC5)
+          - tagged items (0xC6..0xD4, 0xD8..0xDB)
+          - expected conversions (0xD5..0xD7)
+          - simple values (0xE0..0xF3, 0xF8)
+          - undefined (0xF7)
+          - half and single-precision floats (0xF9-0xFA)
+          - break (0xFF)
+
+    @param[in] j  JSON value to serialize
+    @return MessagePack serialization as byte vector
+
+    @complexity Linear in the size of the JSON value @a j.
+
+    @liveexample{The example shows the serialization of a JSON value to a byte
+    vector in CBOR format.,to_cbor}
+
+    @sa http://cbor.io
+    @sa @ref from_cbor(detail::input_adapter&&, const bool, const bool) for the
+        analogous deserialization
+    @sa @ref to_msgpack(const basic_json&) for the related MessagePack format
+    @sa @ref to_ubjson(const basic_json&, const bool, const bool) for the
+             related UBJSON format
+
+    @since version 2.0.9
+    */
+    static std::vector<uint8_t> to_cbor(const basic_json& j)
+    {
+        std::vector<uint8_t> result;
+        to_cbor(j, result);
+        return result;
+    }
+
+    static void to_cbor(const basic_json& j, detail::output_adapter<uint8_t> o)
+    {
+        binary_writer<uint8_t>(o).write_cbor(j);
+    }
+
+    static void to_cbor(const basic_json& j, detail::output_adapter<char> o)
+    {
+        binary_writer<char>(o).write_cbor(j);
+    }
+
+    /*!
+    @brief create a MessagePack serialization of a given JSON value
+
+    Serializes a given JSON value @a j to a byte vector using the MessagePack
+    serialization format. MessagePack is a binary serialization format which
+    aims to be more compact than JSON itself, yet more efficient to parse.
+
+    The library uses the following mapping from JSON values types to
+    MessagePack types according to the MessagePack specification:
+
+    JSON value type | value/range                       | MessagePack type | first byte
+    --------------- | --------------------------------- | ---------------- | ----------
+    null            | `null`                            | nil              | 0xC0
+    boolean         | `true`                            | true             | 0xC3
+    boolean         | `false`                           | false            | 0xC2
+    number_integer  | -9223372036854775808..-2147483649 | int64            | 0xD3
+    number_integer  | -2147483648..-32769               | int32            | 0xD2
+    number_integer  | -32768..-129                      | int16            | 0xD1
+    number_integer  | -128..-33                         | int8             | 0xD0
+    number_integer  | -32..-1                           | negative fixint  | 0xE0..0xFF
+    number_integer  | 0..127                            | positive fixint  | 0x00..0x7F
+    number_integer  | 128..255                          | uint 8           | 0xCC
+    number_integer  | 256..65535                        | uint 16          | 0xCD
+    number_integer  | 65536..4294967295                 | uint 32          | 0xCE
+    number_integer  | 4294967296..18446744073709551615  | uint 64          | 0xCF
+    number_unsigned | 0..127                            | positive fixint  | 0x00..0x7F
+    number_unsigned | 128..255                          | uint 8           | 0xCC
+    number_unsigned | 256..65535                        | uint 16          | 0xCD
+    number_unsigned | 65536..4294967295                 | uint 32          | 0xCE
+    number_unsigned | 4294967296..18446744073709551615  | uint 64          | 0xCF
+    number_float    | *any value*                       | float 64         | 0xCB
+    string          | *length*: 0..31                   | fixstr           | 0xA0..0xBF
+    string          | *length*: 32..255                 | str 8            | 0xD9
+    string          | *length*: 256..65535              | str 16           | 0xDA
+    string          | *length*: 65536..4294967295       | str 32           | 0xDB
+    array           | *size*: 0..15                     | fixarray         | 0x90..0x9F
+    array           | *size*: 16..65535                 | array 16         | 0xDC
+    array           | *size*: 65536..4294967295         | array 32         | 0xDD
+    object          | *size*: 0..15                     | fix map          | 0x80..0x8F
+    object          | *size*: 16..65535                 | map 16           | 0xDE
+    object          | *size*: 65536..4294967295         | map 32           | 0xDF
+
+    @note The mapping is **complete** in the sense that any JSON value type
+          can be converted to a MessagePack value.
+
+    @note The following values can **not** be converted to a MessagePack value:
+          - strings with more than 4294967295 bytes
+          - arrays with more than 4294967295 elements
+          - objects with more than 4294967295 elements
+
+    @note The following MessagePack types are not used in the conversion:
+          - bin 8 - bin 32 (0xC4..0xC6)
+          - ext 8 - ext 32 (0xC7..0xC9)
+          - float 32 (0xCA)
+          - fixext 1 - fixext 16 (0xD4..0xD8)
+
+    @note Any MessagePack output created @ref to_msgpack can be successfully
+          parsed by @ref from_msgpack.
+
+    @note If NaN or Infinity are stored inside a JSON number, they are
+          serialized properly. This behavior differs from the @ref dump()
+          function which serializes NaN or Infinity to `null`.
+
+    @param[in] j  JSON value to serialize
+    @return MessagePack serialization as byte vector
+
+    @complexity Linear in the size of the JSON value @a j.
+
+    @liveexample{The example shows the serialization of a JSON value to a byte
+    vector in MessagePack format.,to_msgpack}
+
+    @sa http://msgpack.org
+    @sa @ref from_msgpack for the analogous deserialization
+    @sa @ref to_cbor(const basic_json& for the related CBOR format
+    @sa @ref to_ubjson(const basic_json&, const bool, const bool) for the
+             related UBJSON format
+
+    @since version 2.0.9
+    */
+    static std::vector<uint8_t> to_msgpack(const basic_json& j)
+    {
+        std::vector<uint8_t> result;
+        to_msgpack(j, result);
+        return result;
+    }
+
+    static void to_msgpack(const basic_json& j, detail::output_adapter<uint8_t> o)
+    {
+        binary_writer<uint8_t>(o).write_msgpack(j);
+    }
+
+    static void to_msgpack(const basic_json& j, detail::output_adapter<char> o)
+    {
+        binary_writer<char>(o).write_msgpack(j);
+    }
+
+    /*!
+    @brief create a UBJSON serialization of a given JSON value
+
+    Serializes a given JSON value @a j to a byte vector using the UBJSON
+    (Universal Binary JSON) serialization format. UBJSON aims to be more compact
+    than JSON itself, yet more efficient to parse.
+
+    The library uses the following mapping from JSON values types to
+    UBJSON types according to the UBJSON specification:
+
+    JSON value type | value/range                       | UBJSON type | marker
+    --------------- | --------------------------------- | ----------- | ------
+    null            | `null`                            | null        | `Z`
+    boolean         | `true`                            | true        | `T`
+    boolean         | `false`                           | false       | `F`
+    number_integer  | -9223372036854775808..-2147483649 | int64       | `L`
+    number_integer  | -2147483648..-32769               | int32       | `l`
+    number_integer  | -32768..-129                      | int16       | `I`
+    number_integer  | -128..127                         | int8        | `i`
+    number_integer  | 128..255                          | uint8       | `U`
+    number_integer  | 256..32767                        | int16       | `I`
+    number_integer  | 32768..2147483647                 | int32       | `l`
+    number_integer  | 2147483648..9223372036854775807   | int64       | `L`
+    number_unsigned | 0..127                            | int8        | `i`
+    number_unsigned | 128..255                          | uint8       | `U`
+    number_unsigned | 256..32767                        | int16       | `I`
+    number_unsigned | 32768..2147483647                 | int32       | `l`
+    number_unsigned | 2147483648..9223372036854775807   | int64       | `L`
+    number_float    | *any value*                       | float64     | `D`
+    string          | *with shortest length indicator*  | string      | `S`
+    array           | *see notes on optimized format*   | array       | `[`
+    object          | *see notes on optimized format*   | map         | `{`
+
+    @note The mapping is **complete** in the sense that any JSON value type
+          can be converted to a UBJSON value.
+
+    @note The following values can **not** be converted to a UBJSON value:
+          - strings with more than 9223372036854775807 bytes (theoretical)
+          - unsigned integer numbers above 9223372036854775807
+
+    @note The following markers are not used in the conversion:
+          - `Z`: no-op values are not created.
+          - `C`: single-byte strings are serialized with `S` markers.
+
+    @note Any UBJSON output created @ref to_ubjson can be successfully parsed
+          by @ref from_ubjson.
+
+    @note If NaN or Infinity are stored inside a JSON number, they are
+          serialized properly. This behavior differs from the @ref dump()
+          function which serializes NaN or Infinity to `null`.
+
+    @note The optimized formats for containers are supported: Parameter
+          @a use_size adds size information to the beginning of a container and
+          removes the closing marker. Parameter @a use_type further checks
+          whether all elements of a container have the same type and adds the
+          type marker to the beginning of the container. The @a use_type
+          parameter must only be used together with @a use_size = true. Note
+          that @a use_size = true alone may result in larger representations -
+          the benefit of this parameter is that the receiving side is
+          immediately informed on the number of elements of the container.
+
+    @param[in] j  JSON value to serialize
+    @param[in] use_size  whether to add size annotations to container types
+    @param[in] use_type  whether to add type annotations to container types
+                         (must be combined with @a use_size = true)
+    @return UBJSON serialization as byte vector
+
+    @complexity Linear in the size of the JSON value @a j.
+
+    @liveexample{The example shows the serialization of a JSON value to a byte
+    vector in UBJSON format.,to_ubjson}
+
+    @sa http://ubjson.org
+    @sa @ref from_ubjson(detail::input_adapter&&, const bool, const bool) for the
+        analogous deserialization
+    @sa @ref to_cbor(const basic_json& for the related CBOR format
+    @sa @ref to_msgpack(const basic_json&) for the related MessagePack format
+
+    @since version 3.1.0
+    */
+    static std::vector<uint8_t> to_ubjson(const basic_json& j,
+                                          const bool use_size = false,
+                                          const bool use_type = false)
+    {
+        std::vector<uint8_t> result;
+        to_ubjson(j, result, use_size, use_type);
+        return result;
+    }
+
+    static void to_ubjson(const basic_json& j, detail::output_adapter<uint8_t> o,
+                          const bool use_size = false, const bool use_type = false)
+    {
+        binary_writer<uint8_t>(o).write_ubjson(j, use_size, use_type);
+    }
+
+    static void to_ubjson(const basic_json& j, detail::output_adapter<char> o,
+                          const bool use_size = false, const bool use_type = false)
+    {
+        binary_writer<char>(o).write_ubjson(j, use_size, use_type);
+    }
+
+
+    /*!
+    @brief Serializes the given JSON object `j` to BSON and returns a vector
+           containing the corresponding BSON-representation.
+
+    BSON (Binary JSON) is a binary format in which zero or more ordered key/value pairs are
+    stored as a single entity (a so-called document).
+
+    The library uses the following mapping from JSON values types to BSON types:
+
+    JSON value type | value/range                       | BSON type   | marker
+    --------------- | --------------------------------- | ----------- | ------
+    null            | `null`                            | null        | 0x0A
+    boolean         | `true`, `false`                   | boolean     | 0x08
+    number_integer  | -9223372036854775808..-2147483649 | int64       | 0x12
+    number_integer  | -2147483648..2147483647           | int32       | 0x10
+    number_integer  | 2147483648..9223372036854775807   | int64       | 0x12
+    number_unsigned | 0..2147483647                     | int32       | 0x10
+    number_unsigned | 2147483648..9223372036854775807   | int64       | 0x12
+    number_unsigned | 9223372036854775808..18446744073709551615| --   | --
+    number_float    | *any value*                       | double      | 0x01
+    string          | *any value*                       | string      | 0x02
+    array           | *any value*                       | document    | 0x04
+    object          | *any value*                       | document    | 0x03
+
+    @warning The mapping is **incomplete**, since only JSON-objects (and things
+    contained therein) can be serialized to BSON.
+    Also, integers larger than 9223372036854775807 cannot be serialized to BSON,
+    and the keys may not contain U+0000, since they are serialized a
+    zero-terminated c-strings.
+
+    @throw out_of_range.407  if `j.is_number_unsigned() && j.get<std::uint64_t>() > 9223372036854775807`
+    @throw out_of_range.409  if a key in `j` contains a NULL (U+0000)
+    @throw type_error.317    if `!j.is_object()`
+
+    @pre The input `j` is required to be an object: `j.is_object() == true`.
+
+    @note Any BSON output created via @ref to_bson can be successfully parsed
+          by @ref from_bson.
+
+    @param[in] j  JSON value to serialize
+    @return BSON serialization as byte vector
+
+    @complexity Linear in the size of the JSON value @a j.
+
+    @liveexample{The example shows the serialization of a JSON value to a byte
+    vector in BSON format.,to_bson}
+
+    @sa http://bsonspec.org/spec.html
+    @sa @ref from_bson(detail::input_adapter&&, const bool strict) for the
+        analogous deserialization
+    @sa @ref to_ubjson(const basic_json&, const bool, const bool) for the
+             related UBJSON format
+    @sa @ref to_cbor(const basic_json&) for the related CBOR format
+    @sa @ref to_msgpack(const basic_json&) for the related MessagePack format
+    */
+    static std::vector<uint8_t> to_bson(const basic_json& j)
+    {
+        std::vector<uint8_t> result;
+        to_bson(j, result);
+        return result;
+    }
+
+    /*!
+    @brief Serializes the given JSON object `j` to BSON and forwards the
+           corresponding BSON-representation to the given output_adapter `o`.
+    @param j The JSON object to convert to BSON.
+    @param o The output adapter that receives the binary BSON representation.
+    @pre The input `j` shall be an object: `j.is_object() == true`
+    @sa @ref to_bson(const basic_json&)
+    */
+    static void to_bson(const basic_json& j, detail::output_adapter<uint8_t> o)
+    {
+        binary_writer<uint8_t>(o).write_bson(j);
+    }
+
+    /*!
+    @copydoc to_bson(const basic_json&, detail::output_adapter<uint8_t>)
+    */
+    static void to_bson(const basic_json& j, detail::output_adapter<char> o)
+    {
+        binary_writer<char>(o).write_bson(j);
+    }
+
+
+    /*!
+    @brief create a JSON value from an input in CBOR format
+
+    Deserializes a given input @a i to a JSON value using the CBOR (Concise
+    Binary Object Representation) serialization format.
+
+    The library maps CBOR types to JSON value types as follows:
+
+    CBOR type              | JSON value type | first byte
+    ---------------------- | --------------- | ----------
+    Integer                | number_unsigned | 0x00..0x17
+    Unsigned integer       | number_unsigned | 0x18
+    Unsigned integer       | number_unsigned | 0x19
+    Unsigned integer       | number_unsigned | 0x1A
+    Unsigned integer       | number_unsigned | 0x1B
+    Negative integer       | number_integer  | 0x20..0x37
+    Negative integer       | number_integer  | 0x38
+    Negative integer       | number_integer  | 0x39
+    Negative integer       | number_integer  | 0x3A
+    Negative integer       | number_integer  | 0x3B
+    Negative integer       | number_integer  | 0x40..0x57
+    UTF-8 string           | string          | 0x60..0x77
+    UTF-8 string           | string          | 0x78
+    UTF-8 string           | string          | 0x79
+    UTF-8 string           | string          | 0x7A
+    UTF-8 string           | string          | 0x7B
+    UTF-8 string           | string          | 0x7F
+    array                  | array           | 0x80..0x97
+    array                  | array           | 0x98
+    array                  | array           | 0x99
+    array                  | array           | 0x9A
+    array                  | array           | 0x9B
+    array                  | array           | 0x9F
+    map                    | object          | 0xA0..0xB7
+    map                    | object          | 0xB8
+    map                    | object          | 0xB9
+    map                    | object          | 0xBA
+    map                    | object          | 0xBB
+    map                    | object          | 0xBF
+    False                  | `false`         | 0xF4
+    True                   | `true`          | 0xF5
+    Null                   | `null`          | 0xF6
+    Half-Precision Float   | number_float    | 0xF9
+    Single-Precision Float | number_float    | 0xFA
+    Double-Precision Float | number_float    | 0xFB
+
+    @warning The mapping is **incomplete** in the sense that not all CBOR
+             types can be converted to a JSON value. The following CBOR types
+             are not supported and will yield parse errors (parse_error.112):
+             - byte strings (0x40..0x5F)
+             - date/time (0xC0..0xC1)
+             - bignum (0xC2..0xC3)
+             - decimal fraction (0xC4)
+             - bigfloat (0xC5)
+             - tagged items (0xC6..0xD4, 0xD8..0xDB)
+             - expected conversions (0xD5..0xD7)
+             - simple values (0xE0..0xF3, 0xF8)
+             - undefined (0xF7)
+
+    @warning CBOR allows map keys of any type, whereas JSON only allows
+             strings as keys in object values. Therefore, CBOR maps with keys
+             other than UTF-8 strings are rejected (parse_error.113).
+
+    @note Any CBOR output created @ref to_cbor can be successfully parsed by
+          @ref from_cbor.
+
+    @param[in] i  an input in CBOR format convertible to an input adapter
+    @param[in] strict  whether to expect the input to be consumed until EOF
+                       (true by default)
+    @param[in] allow_exceptions  whether to throw exceptions in case of a
+    parse error (optional, true by default)
+
+    @return deserialized JSON value
+
+    @throw parse_error.110 if the given input ends prematurely or the end of
+    file was not reached when @a strict was set to true
+    @throw parse_error.112 if unsupported features from CBOR were
+    used in the given input @a v or if the input is not valid CBOR
+    @throw parse_error.113 if a string was expected as map key, but not found
+
+    @complexity Linear in the size of the input @a i.
+
+    @liveexample{The example shows the deserialization of a byte vector in CBOR
+    format to a JSON value.,from_cbor}
+
+    @sa http://cbor.io
+    @sa @ref to_cbor(const basic_json&) for the analogous serialization
+    @sa @ref from_msgpack(detail::input_adapter&&, const bool, const bool) for the
+        related MessagePack format
+    @sa @ref from_ubjson(detail::input_adapter&&, const bool, const bool) for the
+        related UBJSON format
+
+    @since version 2.0.9; parameter @a start_index since 2.1.1; changed to
+           consume input adapters, removed start_index parameter, and added
+           @a strict parameter since 3.0.0; added @a allow_exceptions parameter
+           since 3.2.0
+    */
+    static basic_json from_cbor(detail::input_adapter&& i,
+                                const bool strict = true,
+                                const bool allow_exceptions = true)
+    {
+        basic_json result;
+        detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);
+        const bool res = binary_reader(detail::input_adapter(i)).sax_parse(input_format_t::cbor, &sdp, strict);
+        return res ? result : basic_json(value_t::discarded);
+    }
+
+    /*!
+    @copydoc from_cbor(detail::input_adapter&&, const bool, const bool)
+    */
+    template<typename A1, typename A2,
+             detail::enable_if_t<std::is_constructible<detail::input_adapter, A1, A2>::value, int> = 0>
+    static basic_json from_cbor(A1 && a1, A2 && a2,
+                                const bool strict = true,
+                                const bool allow_exceptions = true)
+    {
+        basic_json result;
+        detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);
+        const bool res = binary_reader(detail::input_adapter(std::forward<A1>(a1), std::forward<A2>(a2))).sax_parse(input_format_t::cbor, &sdp, strict);
+        return res ? result : basic_json(value_t::discarded);
+    }
+
+    /*!
+    @brief create a JSON value from an input in MessagePack format
+
+    Deserializes a given input @a i to a JSON value using the MessagePack
+    serialization format.
+
+    The library maps MessagePack types to JSON value types as follows:
+
+    MessagePack type | JSON value type | first byte
+    ---------------- | --------------- | ----------
+    positive fixint  | number_unsigned | 0x00..0x7F
+    fixmap           | object          | 0x80..0x8F
+    fixarray         | array           | 0x90..0x9F
+    fixstr           | string          | 0xA0..0xBF
+    nil              | `null`          | 0xC0
+    false            | `false`         | 0xC2
+    true             | `true`          | 0xC3
+    float 32         | number_float    | 0xCA
+    float 64         | number_float    | 0xCB
+    uint 8           | number_unsigned | 0xCC
+    uint 16          | number_unsigned | 0xCD
+    uint 32          | number_unsigned | 0xCE
+    uint 64          | number_unsigned | 0xCF
+    int 8            | number_integer  | 0xD0
+    int 16           | number_integer  | 0xD1
+    int 32           | number_integer  | 0xD2
+    int 64           | number_integer  | 0xD3
+    str 8            | string          | 0xD9
+    str 16           | string          | 0xDA
+    str 32           | string          | 0xDB
+    array 16         | array           | 0xDC
+    array 32         | array           | 0xDD
+    map 16           | object          | 0xDE
+    map 32           | object          | 0xDF
+    negative fixint  | number_integer  | 0xE0-0xFF
+
+    @warning The mapping is **incomplete** in the sense that not all
+             MessagePack types can be converted to a JSON value. The following
+             MessagePack types are not supported and will yield parse errors:
+              - bin 8 - bin 32 (0xC4..0xC6)
+              - ext 8 - ext 32 (0xC7..0xC9)
+              - fixext 1 - fixext 16 (0xD4..0xD8)
+
+    @note Any MessagePack output created @ref to_msgpack can be successfully
+          parsed by @ref from_msgpack.
+
+    @param[in] i  an input in MessagePack format convertible to an input
+                  adapter
+    @param[in] strict  whether to expect the input to be consumed until EOF
+                       (true by default)
+    @param[in] allow_exceptions  whether to throw exceptions in case of a
+    parse error (optional, true by default)
+
+    @return deserialized JSON value
+
+    @throw parse_error.110 if the given input ends prematurely or the end of
+    file was not reached when @a strict was set to true
+    @throw parse_error.112 if unsupported features from MessagePack were
+    used in the given input @a i or if the input is not valid MessagePack
+    @throw parse_error.113 if a string was expected as map key, but not found
+
+    @complexity Linear in the size of the input @a i.
+
+    @liveexample{The example shows the deserialization of a byte vector in
+    MessagePack format to a JSON value.,from_msgpack}
+
+    @sa http://msgpack.org
+    @sa @ref to_msgpack(const basic_json&) for the analogous serialization
+    @sa @ref from_cbor(detail::input_adapter&&, const bool, const bool) for the
+        related CBOR format
+    @sa @ref from_ubjson(detail::input_adapter&&, const bool, const bool) for
+        the related UBJSON format
+    @sa @ref from_bson(detail::input_adapter&&, const bool, const bool) for
+        the related BSON format
+
+    @since version 2.0.9; parameter @a start_index since 2.1.1; changed to
+           consume input adapters, removed start_index parameter, and added
+           @a strict parameter since 3.0.0; added @a allow_exceptions parameter
+           since 3.2.0
+    */
+    static basic_json from_msgpack(detail::input_adapter&& i,
+                                   const bool strict = true,
+                                   const bool allow_exceptions = true)
+    {
+        basic_json result;
+        detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);
+        const bool res = binary_reader(detail::input_adapter(i)).sax_parse(input_format_t::msgpack, &sdp, strict);
+        return res ? result : basic_json(value_t::discarded);
+    }
+
+    /*!
+    @copydoc from_msgpack(detail::input_adapter&&, const bool, const bool)
+    */
+    template<typename A1, typename A2,
+             detail::enable_if_t<std::is_constructible<detail::input_adapter, A1, A2>::value, int> = 0>
+    static basic_json from_msgpack(A1 && a1, A2 && a2,
+                                   const bool strict = true,
+                                   const bool allow_exceptions = true)
+    {
+        basic_json result;
+        detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);
+        const bool res = binary_reader(detail::input_adapter(std::forward<A1>(a1), std::forward<A2>(a2))).sax_parse(input_format_t::msgpack, &sdp, strict);
+        return res ? result : basic_json(value_t::discarded);
+    }
+
+    /*!
+    @brief create a JSON value from an input in UBJSON format
+
+    Deserializes a given input @a i to a JSON value using the UBJSON (Universal
+    Binary JSON) serialization format.
+
+    The library maps UBJSON types to JSON value types as follows:
+
+    UBJSON type | JSON value type                         | marker
+    ----------- | --------------------------------------- | ------
+    no-op       | *no value, next value is read*          | `N`
+    null        | `null`                                  | `Z`
+    false       | `false`                                 | `F`
+    true        | `true`                                  | `T`
+    float32     | number_float                            | `d`
+    float64     | number_float                            | `D`
+    uint8       | number_unsigned                         | `U`
+    int8        | number_integer                          | `i`
+    int16       | number_integer                          | `I`
+    int32       | number_integer                          | `l`
+    int64       | number_integer                          | `L`
+    string      | string                                  | `S`
+    char        | string                                  | `C`
+    array       | array (optimized values are supported)  | `[`
+    object      | object (optimized values are supported) | `{`
+
+    @note The mapping is **complete** in the sense that any UBJSON value can
+          be converted to a JSON value.
+
+    @param[in] i  an input in UBJSON format convertible to an input adapter
+    @param[in] strict  whether to expect the input to be consumed until EOF
+                       (true by default)
+    @param[in] allow_exceptions  whether to throw exceptions in case of a
+    parse error (optional, true by default)
+
+    @return deserialized JSON value
+
+    @throw parse_error.110 if the given input ends prematurely or the end of
+    file was not reached when @a strict was set to true
+    @throw parse_error.112 if a parse error occurs
+    @throw parse_error.113 if a string could not be parsed successfully
+
+    @complexity Linear in the size of the input @a i.
+
+    @liveexample{The example shows the deserialization of a byte vector in
+    UBJSON format to a JSON value.,from_ubjson}
+
+    @sa http://ubjson.org
+    @sa @ref to_ubjson(const basic_json&, const bool, const bool) for the
+             analogous serialization
+    @sa @ref from_cbor(detail::input_adapter&&, const bool, const bool) for the
+        related CBOR format
+    @sa @ref from_msgpack(detail::input_adapter&&, const bool, const bool) for
+        the related MessagePack format
+    @sa @ref from_bson(detail::input_adapter&&, const bool, const bool) for
+        the related BSON format
+
+    @since version 3.1.0; added @a allow_exceptions parameter since 3.2.0
+    */
+    static basic_json from_ubjson(detail::input_adapter&& i,
+                                  const bool strict = true,
+                                  const bool allow_exceptions = true)
+    {
+        basic_json result;
+        detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);
+        const bool res = binary_reader(detail::input_adapter(i)).sax_parse(input_format_t::ubjson, &sdp, strict);
+        return res ? result : basic_json(value_t::discarded);
+    }
+
+    /*!
+    @copydoc from_ubjson(detail::input_adapter&&, const bool, const bool)
+    */
+    template<typename A1, typename A2,
+             detail::enable_if_t<std::is_constructible<detail::input_adapter, A1, A2>::value, int> = 0>
+    static basic_json from_ubjson(A1 && a1, A2 && a2,
+                                  const bool strict = true,
+                                  const bool allow_exceptions = true)
+    {
+        basic_json result;
+        detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);
+        const bool res = binary_reader(detail::input_adapter(std::forward<A1>(a1), std::forward<A2>(a2))).sax_parse(input_format_t::ubjson, &sdp, strict);
+        return res ? result : basic_json(value_t::discarded);
+    }
+
+    /*!
+    @brief Create a JSON value from an input in BSON format
+
+    Deserializes a given input @a i to a JSON value using the BSON (Binary JSON)
+    serialization format.
+
+    The library maps BSON record types to JSON value types as follows:
+
+    BSON type       | BSON marker byte | JSON value type
+    --------------- | ---------------- | ---------------------------
+    double          | 0x01             | number_float
+    string          | 0x02             | string
+    document        | 0x03             | object
+    array           | 0x04             | array
+    binary          | 0x05             | still unsupported
+    undefined       | 0x06             | still unsupported
+    ObjectId        | 0x07             | still unsupported
+    boolean         | 0x08             | boolean
+    UTC Date-Time   | 0x09             | still unsupported
+    null            | 0x0A             | null
+    Regular Expr.   | 0x0B             | still unsupported
+    DB Pointer      | 0x0C             | still unsupported
+    JavaScript Code | 0x0D             | still unsupported
+    Symbol          | 0x0E             | still unsupported
+    JavaScript Code | 0x0F             | still unsupported
+    int32           | 0x10             | number_integer
+    Timestamp       | 0x11             | still unsupported
+    128-bit decimal float | 0x13       | still unsupported
+    Max Key         | 0x7F             | still unsupported
+    Min Key         | 0xFF             | still unsupported
+
+    @warning The mapping is **incomplete**. The unsupported mappings
+             are indicated in the table above.
+
+    @param[in] i  an input in BSON format convertible to an input adapter
+    @param[in] strict  whether to expect the input to be consumed until EOF
+                       (true by default)
+    @param[in] allow_exceptions  whether to throw exceptions in case of a
+    parse error (optional, true by default)
+
+    @return deserialized JSON value
+
+    @throw parse_error.114 if an unsupported BSON record type is encountered
+
+    @complexity Linear in the size of the input @a i.
+
+    @liveexample{The example shows the deserialization of a byte vector in
+    BSON format to a JSON value.,from_bson}
+
+    @sa http://bsonspec.org/spec.html
+    @sa @ref to_bson(const basic_json&) for the analogous serialization
+    @sa @ref from_cbor(detail::input_adapter&&, const bool, const bool) for the
+        related CBOR format
+    @sa @ref from_msgpack(detail::input_adapter&&, const bool, const bool) for
+        the related MessagePack format
+    @sa @ref from_ubjson(detail::input_adapter&&, const bool, const bool) for the
+        related UBJSON format
+    */
+    static basic_json from_bson(detail::input_adapter&& i,
+                                const bool strict = true,
+                                const bool allow_exceptions = true)
+    {
+        basic_json result;
+        detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);
+        const bool res = binary_reader(detail::input_adapter(i)).sax_parse(input_format_t::bson, &sdp, strict);
+        return res ? result : basic_json(value_t::discarded);
+    }
+
+    /*!
+    @copydoc from_bson(detail::input_adapter&&, const bool, const bool)
+    */
+    template<typename A1, typename A2,
+             detail::enable_if_t<std::is_constructible<detail::input_adapter, A1, A2>::value, int> = 0>
+    static basic_json from_bson(A1 && a1, A2 && a2,
+                                const bool strict = true,
+                                const bool allow_exceptions = true)
+    {
+        basic_json result;
+        detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);
+        const bool res = binary_reader(detail::input_adapter(std::forward<A1>(a1), std::forward<A2>(a2))).sax_parse(input_format_t::bson, &sdp, strict);
+        return res ? result : basic_json(value_t::discarded);
+    }
+
+
+
+    /// @}
+
+    //////////////////////////
+    // JSON Pointer support //
+    //////////////////////////
+
+    /// @name JSON Pointer functions
+    /// @{
+
+    /*!
+    @brief access specified element via JSON Pointer
+
+    Uses a JSON pointer to retrieve a reference to the respective JSON value.
+    No bound checking is performed. Similar to @ref operator[](const typename
+    object_t::key_type&), `null` values are created in arrays and objects if
+    necessary.
+
+    In particular:
+    - If the JSON pointer points to an object key that does not exist, it
+      is created an filled with a `null` value before a reference to it
+      is returned.
+    - If the JSON pointer points to an array index that does not exist, it
+      is created an filled with a `null` value before a reference to it
+      is returned. All indices between the current maximum and the given
+      index are also filled with `null`.
+    - The special value `-` is treated as a synonym for the index past the
+      end.
+
+    @param[in] ptr  a JSON pointer
+
+    @return reference to the element pointed to by @a ptr
+
+    @complexity Constant.
+
+    @throw parse_error.106   if an array index begins with '0'
+    @throw parse_error.109   if an array index was not a number
+    @throw out_of_range.404  if the JSON pointer can not be resolved
+
+    @liveexample{The behavior is shown in the example.,operatorjson_pointer}
+
+    @since version 2.0.0
+    */
+    reference operator[](const json_pointer& ptr)
+    {
+        return ptr.get_unchecked(this);
+    }
+
+    /*!
+    @brief access specified element via JSON Pointer
+
+    Uses a JSON pointer to retrieve a reference to the respective JSON value.
+    No bound checking is performed. The function does not change the JSON
+    value; no `null` values are created. In particular, the the special value
+    `-` yields an exception.
+
+    @param[in] ptr  JSON pointer to the desired element
+
+    @return const reference to the element pointed to by @a ptr
+
+    @complexity Constant.
+
+    @throw parse_error.106   if an array index begins with '0'
+    @throw parse_error.109   if an array index was not a number
+    @throw out_of_range.402  if the array index '-' is used
+    @throw out_of_range.404  if the JSON pointer can not be resolved
+
+    @liveexample{The behavior is shown in the example.,operatorjson_pointer_const}
+
+    @since version 2.0.0
+    */
+    const_reference operator[](const json_pointer& ptr) const
+    {
+        return ptr.get_unchecked(this);
+    }
+
+    /*!
+    @brief access specified element via JSON Pointer
+
+    Returns a reference to the element at with specified JSON pointer @a ptr,
+    with bounds checking.
+
+    @param[in] ptr  JSON pointer to the desired element
+
+    @return reference to the element pointed to by @a ptr
+
+    @throw parse_error.106 if an array index in the passed JSON pointer @a ptr
+    begins with '0'. See example below.
+
+    @throw parse_error.109 if an array index in the passed JSON pointer @a ptr
+    is not a number. See example below.
+
+    @throw out_of_range.401 if an array index in the passed JSON pointer @a ptr
+    is out of range. See example below.
+
+    @throw out_of_range.402 if the array index '-' is used in the passed JSON
+    pointer @a ptr. As `at` provides checked access (and no elements are
+    implicitly inserted), the index '-' is always invalid. See example below.
+
+    @throw out_of_range.403 if the JSON pointer describes a key of an object
+    which cannot be found. See example below.
+
+    @throw out_of_range.404 if the JSON pointer @a ptr can not be resolved.
+    See example below.
+
+    @exceptionsafety Strong guarantee: if an exception is thrown, there are no
+    changes in the JSON value.
+
+    @complexity Constant.
+
+    @since version 2.0.0
+
+    @liveexample{The behavior is shown in the example.,at_json_pointer}
+    */
+    reference at(const json_pointer& ptr)
+    {
+        return ptr.get_checked(this);
+    }
+
+    /*!
+    @brief access specified element via JSON Pointer
+
+    Returns a const reference to the element at with specified JSON pointer @a
+    ptr, with bounds checking.
+
+    @param[in] ptr  JSON pointer to the desired element
+
+    @return reference to the element pointed to by @a ptr
+
+    @throw parse_error.106 if an array index in the passed JSON pointer @a ptr
+    begins with '0'. See example below.
+
+    @throw parse_error.109 if an array index in the passed JSON pointer @a ptr
+    is not a number. See example below.
+
+    @throw out_of_range.401 if an array index in the passed JSON pointer @a ptr
+    is out of range. See example below.
+
+    @throw out_of_range.402 if the array index '-' is used in the passed JSON
+    pointer @a ptr. As `at` provides checked access (and no elements are
+    implicitly inserted), the index '-' is always invalid. See example below.
+
+    @throw out_of_range.403 if the JSON pointer describes a key of an object
+    which cannot be found. See example below.
+
+    @throw out_of_range.404 if the JSON pointer @a ptr can not be resolved.
+    See example below.
+
+    @exceptionsafety Strong guarantee: if an exception is thrown, there are no
+    changes in the JSON value.
+
+    @complexity Constant.
+
+    @since version 2.0.0
+
+    @liveexample{The behavior is shown in the example.,at_json_pointer_const}
+    */
+    const_reference at(const json_pointer& ptr) const
+    {
+        return ptr.get_checked(this);
+    }
+
+    /*!
+    @brief return flattened JSON value
+
+    The function creates a JSON object whose keys are JSON pointers (see [RFC
+    6901](https://tools.ietf.org/html/rfc6901)) and whose values are all
+    primitive. The original JSON value can be restored using the @ref
+    unflatten() function.
+
+    @return an object that maps JSON pointers to primitive values
+
+    @note Empty objects and arrays are flattened to `null` and will not be
+          reconstructed correctly by the @ref unflatten() function.
+
+    @complexity Linear in the size the JSON value.
+
+    @liveexample{The following code shows how a JSON object is flattened to an
+    object whose keys consist of JSON pointers.,flatten}
+
+    @sa @ref unflatten() for the reverse function
+
+    @since version 2.0.0
+    */
+    basic_json flatten() const
+    {
+        basic_json result(value_t::object);
+        json_pointer::flatten("", *this, result);
+        return result;
+    }
+
+    /*!
+    @brief unflatten a previously flattened JSON value
+
+    The function restores the arbitrary nesting of a JSON value that has been
+    flattened before using the @ref flatten() function. The JSON value must
+    meet certain constraints:
+    1. The value must be an object.
+    2. The keys must be JSON pointers (see
+       [RFC 6901](https://tools.ietf.org/html/rfc6901))
+    3. The mapped values must be primitive JSON types.
+
+    @return the original JSON from a flattened version
+
+    @note Empty objects and arrays are flattened by @ref flatten() to `null`
+          values and can not unflattened to their original type. Apart from
+          this example, for a JSON value `j`, the following is always true:
+          `j == j.flatten().unflatten()`.
+
+    @complexity Linear in the size the JSON value.
+
+    @throw type_error.314  if value is not an object
+    @throw type_error.315  if object values are not primitive
+
+    @liveexample{The following code shows how a flattened JSON object is
+    unflattened into the original nested JSON object.,unflatten}
+
+    @sa @ref flatten() for the reverse function
+
+    @since version 2.0.0
+    */
+    basic_json unflatten() const
+    {
+        return json_pointer::unflatten(*this);
+    }
+
+    /// @}
+
+    //////////////////////////
+    // JSON Patch functions //
+    //////////////////////////
+
+    /// @name JSON Patch functions
+    /// @{
+
+    /*!
+    @brief applies a JSON patch
+
+    [JSON Patch](http://jsonpatch.com) defines a JSON document structure for
+    expressing a sequence of operations to apply to a JSON) document. With
+    this function, a JSON Patch is applied to the current JSON value by
+    executing all operations from the patch.
+
+    @param[in] json_patch  JSON patch document
+    @return patched document
+
+    @note The application of a patch is atomic: Either all operations succeed
+          and the patched document is returned or an exception is thrown. In
+          any case, the original value is not changed: the patch is applied
+          to a copy of the value.
+
+    @throw parse_error.104 if the JSON patch does not consist of an array of
+    objects
+
+    @throw parse_error.105 if the JSON patch is malformed (e.g., mandatory
+    attributes are missing); example: `"operation add must have member path"`
+
+    @throw out_of_range.401 if an array index is out of range.
+
+    @throw out_of_range.403 if a JSON pointer inside the patch could not be
+    resolved successfully in the current JSON value; example: `"key baz not
+    found"`
+
+    @throw out_of_range.405 if JSON pointer has no parent ("add", "remove",
+    "move")
+
+    @throw other_error.501 if "test" operation was unsuccessful
+
+    @complexity Linear in the size of the JSON value and the length of the
+    JSON patch. As usually only a fraction of the JSON value is affected by
+    the patch, the complexity can usually be neglected.
+
+    @liveexample{The following code shows how a JSON patch is applied to a
+    value.,patch}
+
+    @sa @ref diff -- create a JSON patch by comparing two JSON values
+
+    @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902)
+    @sa [RFC 6901 (JSON Pointer)](https://tools.ietf.org/html/rfc6901)
+
+    @since version 2.0.0
+    */
+    basic_json patch(const basic_json& json_patch) const
+    {
+        // make a working copy to apply the patch to
+        basic_json result = *this;
+
+        // the valid JSON Patch operations
+        enum class patch_operations {add, remove, replace, move, copy, test, invalid};
+
+        const auto get_op = [](const std::string & op)
+        {
+            if (op == "add")
+            {
+                return patch_operations::add;
+            }
+            if (op == "remove")
+            {
+                return patch_operations::remove;
+            }
+            if (op == "replace")
+            {
+                return patch_operations::replace;
+            }
+            if (op == "move")
+            {
+                return patch_operations::move;
+            }
+            if (op == "copy")
+            {
+                return patch_operations::copy;
+            }
+            if (op == "test")
+            {
+                return patch_operations::test;
+            }
+
+            return patch_operations::invalid;
+        };
+
+        // wrapper for "add" operation; add value at ptr
+        const auto operation_add = [&result](json_pointer & ptr, basic_json val)
+        {
+            // adding to the root of the target document means replacing it
+            if (ptr.is_root())
+            {
+                result = val;
+            }
+            else
+            {
+                // make sure the top element of the pointer exists
+                json_pointer top_pointer = ptr.top();
+                if (top_pointer != ptr)
+                {
+                    result.at(top_pointer);
+                }
+
+                // get reference to parent of JSON pointer ptr
+                const auto last_path = ptr.pop_back();
+                basic_json& parent = result[ptr];
+
+                switch (parent.m_type)
+                {
+                    case value_t::null:
+                    case value_t::object:
+                    {
+                        // use operator[] to add value
+                        parent[last_path] = val;
+                        break;
+                    }
+
+                    case value_t::array:
+                    {
+                        if (last_path == "-")
+                        {
+                            // special case: append to back
+                            parent.push_back(val);
+                        }
+                        else
+                        {
+                            const auto idx = json_pointer::array_index(last_path);
+                            if (JSON_UNLIKELY(static_cast<size_type>(idx) > parent.size()))
+                            {
+                                // avoid undefined behavior
+                                JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range"));
+                            }
+
+                            // default case: insert add offset
+                            parent.insert(parent.begin() + static_cast<difference_type>(idx), val);
+                        }
+                        break;
+                    }
+
+                    // LCOV_EXCL_START
+                    default:
+                    {
+                        // if there exists a parent it cannot be primitive
+                        assert(false);
+                    }
+                        // LCOV_EXCL_STOP
+                }
+            }
+        };
+
+        // wrapper for "remove" operation; remove value at ptr
+        const auto operation_remove = [&result](json_pointer & ptr)
+        {
+            // get reference to parent of JSON pointer ptr
+            const auto last_path = ptr.pop_back();
+            basic_json& parent = result.at(ptr);
+
+            // remove child
+            if (parent.is_object())
+            {
+                // perform range check
+                auto it = parent.find(last_path);
+                if (JSON_LIKELY(it != parent.end()))
+                {
+                    parent.erase(it);
+                }
+                else
+                {
+                    JSON_THROW(out_of_range::create(403, "key '" + last_path + "' not found"));
+                }
+            }
+            else if (parent.is_array())
+            {
+                // note erase performs range check
+                parent.erase(static_cast<size_type>(json_pointer::array_index(last_path)));
+            }
+        };
+
+        // type check: top level value must be an array
+        if (JSON_UNLIKELY(not json_patch.is_array()))
+        {
+            JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects"));
+        }
+
+        // iterate and apply the operations
+        for (const auto& val : json_patch)
+        {
+            // wrapper to get a value for an operation
+            const auto get_value = [&val](const std::string & op,
+                                          const std::string & member,
+                                          bool string_type) -> basic_json &
+            {
+                // find value
+                auto it = val.m_value.object->find(member);
+
+                // context-sensitive error message
+                const auto error_msg = (op == "op") ? "operation" : "operation '" + op + "'";
+
+                // check if desired value is present
+                if (JSON_UNLIKELY(it == val.m_value.object->end()))
+                {
+                    JSON_THROW(parse_error::create(105, 0, error_msg + " must have member '" + member + "'"));
+                }
+
+                // check if result is of type string
+                if (JSON_UNLIKELY(string_type and not it->second.is_string()))
+                {
+                    JSON_THROW(parse_error::create(105, 0, error_msg + " must have string member '" + member + "'"));
+                }
+
+                // no error: return value
+                return it->second;
+            };
+
+            // type check: every element of the array must be an object
+            if (JSON_UNLIKELY(not val.is_object()))
+            {
+                JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects"));
+            }
+
+            // collect mandatory members
+            const std::string op = get_value("op", "op", true);
+            const std::string path = get_value(op, "path", true);
+            json_pointer ptr(path);
+
+            switch (get_op(op))
+            {
+                case patch_operations::add:
+                {
+                    operation_add(ptr, get_value("add", "value", false));
+                    break;
+                }
+
+                case patch_operations::remove:
+                {
+                    operation_remove(ptr);
+                    break;
+                }
+
+                case patch_operations::replace:
+                {
+                    // the "path" location must exist - use at()
+                    result.at(ptr) = get_value("replace", "value", false);
+                    break;
+                }
+
+                case patch_operations::move:
+                {
+                    const std::string from_path = get_value("move", "from", true);
+                    json_pointer from_ptr(from_path);
+
+                    // the "from" location must exist - use at()
+                    basic_json v = result.at(from_ptr);
+
+                    // The move operation is functionally identical to a
+                    // "remove" operation on the "from" location, followed
+                    // immediately by an "add" operation at the target
+                    // location with the value that was just removed.
+                    operation_remove(from_ptr);
+                    operation_add(ptr, v);
+                    break;
+                }
+
+                case patch_operations::copy:
+                {
+                    const std::string from_path = get_value("copy", "from", true);
+                    const json_pointer from_ptr(from_path);
+
+                    // the "from" location must exist - use at()
+                    basic_json v = result.at(from_ptr);
+
+                    // The copy is functionally identical to an "add"
+                    // operation at the target location using the value
+                    // specified in the "from" member.
+                    operation_add(ptr, v);
+                    break;
+                }
+
+                case patch_operations::test:
+                {
+                    bool success = false;
+                    JSON_TRY
+                    {
+                        // check if "value" matches the one at "path"
+                        // the "path" location must exist - use at()
+                        success = (result.at(ptr) == get_value("test", "value", false));
+                    }
+                    JSON_INTERNAL_CATCH (out_of_range&)
+                    {
+                        // ignore out of range errors: success remains false
+                    }
+
+                    // throw an exception if test fails
+                    if (JSON_UNLIKELY(not success))
+                    {
+                        JSON_THROW(other_error::create(501, "unsuccessful: " + val.dump()));
+                    }
+
+                    break;
+                }
+
+                case patch_operations::invalid:
+                {
+                    // op must be "add", "remove", "replace", "move", "copy", or
+                    // "test"
+                    JSON_THROW(parse_error::create(105, 0, "operation value '" + op + "' is invalid"));
+                }
+            }
+        }
+
+        return result;
+    }
+
+    /*!
+    @brief creates a diff as a JSON patch
+
+    Creates a [JSON Patch](http://jsonpatch.com) so that value @a source can
+    be changed into the value @a target by calling @ref patch function.
+
+    @invariant For two JSON values @a source and @a target, the following code
+    yields always `true`:
+    @code {.cpp}
+    source.patch(diff(source, target)) == target;
+    @endcode
+
+    @note Currently, only `remove`, `add`, and `replace` operations are
+          generated.
+
+    @param[in] source  JSON value to compare from
+    @param[in] target  JSON value to compare against
+    @param[in] path    helper value to create JSON pointers
+
+    @return a JSON patch to convert the @a source to @a target
+
+    @complexity Linear in the lengths of @a source and @a target.
+
+    @liveexample{The following code shows how a JSON patch is created as a
+    diff for two JSON values.,diff}
+
+    @sa @ref patch -- apply a JSON patch
+    @sa @ref merge_patch -- apply a JSON Merge Patch
+
+    @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902)
+
+    @since version 2.0.0
+    */
+    static basic_json diff(const basic_json& source, const basic_json& target,
+                           const std::string& path = "")
+    {
+        // the patch
+        basic_json result(value_t::array);
+
+        // if the values are the same, return empty patch
+        if (source == target)
+        {
+            return result;
+        }
+
+        if (source.type() != target.type())
+        {
+            // different types: replace value
+            result.push_back(
+            {
+                {"op", "replace"}, {"path", path}, {"value", target}
+            });
+        }
+        else
+        {
+            switch (source.type())
+            {
+                case value_t::array:
+                {
+                    // first pass: traverse common elements
+                    std::size_t i = 0;
+                    while (i < source.size() and i < target.size())
+                    {
+                        // recursive call to compare array values at index i
+                        auto temp_diff = diff(source[i], target[i], path + "/" + std::to_string(i));
+                        result.insert(result.end(), temp_diff.begin(), temp_diff.end());
+                        ++i;
+                    }
+
+                    // i now reached the end of at least one array
+                    // in a second pass, traverse the remaining elements
+
+                    // remove my remaining elements
+                    const auto end_index = static_cast<difference_type>(result.size());
+                    while (i < source.size())
+                    {
+                        // add operations in reverse order to avoid invalid
+                        // indices
+                        result.insert(result.begin() + end_index, object(
+                        {
+                            {"op", "remove"},
+                            {"path", path + "/" + std::to_string(i)}
+                        }));
+                        ++i;
+                    }
+
+                    // add other remaining elements
+                    while (i < target.size())
+                    {
+                        result.push_back(
+                        {
+                            {"op", "add"},
+                            {"path", path + "/" + std::to_string(i)},
+                            {"value", target[i]}
+                        });
+                        ++i;
+                    }
+
+                    break;
+                }
+
+                case value_t::object:
+                {
+                    // first pass: traverse this object's elements
+                    for (auto it = source.cbegin(); it != source.cend(); ++it)
+                    {
+                        // escape the key name to be used in a JSON patch
+                        const auto key = json_pointer::escape(it.key());
+
+                        if (target.find(it.key()) != target.end())
+                        {
+                            // recursive call to compare object values at key it
+                            auto temp_diff = diff(it.value(), target[it.key()], path + "/" + key);
+                            result.insert(result.end(), temp_diff.begin(), temp_diff.end());
+                        }
+                        else
+                        {
+                            // found a key that is not in o -> remove it
+                            result.push_back(object(
+                            {
+                                {"op", "remove"}, {"path", path + "/" + key}
+                            }));
+                        }
+                    }
+
+                    // second pass: traverse other object's elements
+                    for (auto it = target.cbegin(); it != target.cend(); ++it)
+                    {
+                        if (source.find(it.key()) == source.end())
+                        {
+                            // found a key that is not in this -> add it
+                            const auto key = json_pointer::escape(it.key());
+                            result.push_back(
+                            {
+                                {"op", "add"}, {"path", path + "/" + key},
+                                {"value", it.value()}
+                            });
+                        }
+                    }
+
+                    break;
+                }
+
+                default:
+                {
+                    // both primitive type: replace value
+                    result.push_back(
+                    {
+                        {"op", "replace"}, {"path", path}, {"value", target}
+                    });
+                    break;
+                }
+            }
+        }
+
+        return result;
+    }
+
+    /// @}
+
+    ////////////////////////////////
+    // JSON Merge Patch functions //
+    ////////////////////////////////
+
+    /// @name JSON Merge Patch functions
+    /// @{
+
+    /*!
+    @brief applies a JSON Merge Patch
+
+    The merge patch format is primarily intended for use with the HTTP PATCH
+    method as a means of describing a set of modifications to a target
+    resource's content. This function applies a merge patch to the current
+    JSON value.
+
+    The function implements the following algorithm from Section 2 of
+    [RFC 7396 (JSON Merge Patch)](https://tools.ietf.org/html/rfc7396):
+
+    ```
+    define MergePatch(Target, Patch):
+      if Patch is an Object:
+        if Target is not an Object:
+          Target = {} // Ignore the contents and set it to an empty Object
+        for each Name/Value pair in Patch:
+          if Value is null:
+            if Name exists in Target:
+              remove the Name/Value pair from Target
+          else:
+            Target[Name] = MergePatch(Target[Name], Value)
+        return Target
+      else:
+        return Patch
+    ```
+
+    Thereby, `Target` is the current object; that is, the patch is applied to
+    the current value.
+
+    @param[in] apply_patch  the patch to apply
+
+    @complexity Linear in the lengths of @a patch.
+
+    @liveexample{The following code shows how a JSON Merge Patch is applied to
+    a JSON document.,merge_patch}
+
+    @sa @ref patch -- apply a JSON patch
+    @sa [RFC 7396 (JSON Merge Patch)](https://tools.ietf.org/html/rfc7396)
+
+    @since version 3.0.0
+    */
+    void merge_patch(const basic_json& apply_patch)
+    {
+        if (apply_patch.is_object())
+        {
+            if (not is_object())
+            {
+                *this = object();
+            }
+            for (auto it = apply_patch.begin(); it != apply_patch.end(); ++it)
+            {
+                if (it.value().is_null())
+                {
+                    erase(it.key());
+                }
+                else
+                {
+                    operator[](it.key()).merge_patch(it.value());
+                }
+            }
+        }
+        else
+        {
+            *this = apply_patch;
+        }
+    }
+
+    /// @}
+};
+} // namespace nlohmann
+
+///////////////////////
+// nonmember support //
+///////////////////////
+
+// specialization of std::swap, and std::hash
+namespace std
+{
+
+/// hash value for JSON objects
+template<>
+struct hash<nlohmann::json>
+{
+    /*!
+    @brief return a hash value for a JSON object
+
+    @since version 1.0.0
+    */
+    std::size_t operator()(const nlohmann::json& j) const
+    {
+        // a naive hashing via the string representation
+        const auto& h = hash<nlohmann::json::string_t>();
+        return h(j.dump());
+    }
+};
+
+/// specialization for std::less<value_t>
+/// @note: do not remove the space after '<',
+///        see https://github.com/nlohmann/json/pull/679
+template<>
+struct less< ::nlohmann::detail::value_t>
+{
+    /*!
+    @brief compare two value_t enum values
+    @since version 3.0.0
+    */
+    bool operator()(nlohmann::detail::value_t lhs,
+                    nlohmann::detail::value_t rhs) const noexcept
+    {
+        return nlohmann::detail::operator<(lhs, rhs);
+    }
+};
+
+/*!
+@brief exchanges the values of two JSON objects
+
+@since version 1.0.0
+*/
+template<>
+inline void swap<nlohmann::json>(nlohmann::json& j1, nlohmann::json& j2) noexcept(
+    is_nothrow_move_constructible<nlohmann::json>::value and
+    is_nothrow_move_assignable<nlohmann::json>::value
+)
+{
+    j1.swap(j2);
+}
+
+} // namespace std
+
+/*!
+@brief user-defined string literal for JSON values
+
+This operator implements a user-defined string literal for JSON objects. It
+can be used by adding `"_json"` to a string literal and returns a JSON object
+if no parse error occurred.
+
+@param[in] s  a string representation of a JSON object
+@param[in] n  the length of string @a s
+@return a JSON object
+
+@since version 1.0.0
+*/
+inline nlohmann::json operator "" _json(const char* s, std::size_t n)
+{
+    return nlohmann::json::parse(s, s + n);
+}
+
+/*!
+@brief user-defined string literal for JSON pointer
+
+This operator implements a user-defined string literal for JSON Pointers. It
+can be used by adding `"_json_pointer"` to a string literal and returns a JSON pointer
+object if no parse error occurred.
+
+@param[in] s  a string representation of a JSON Pointer
+@param[in] n  the length of string @a s
+@return a JSON pointer object
+
+@since version 2.0.0
+*/
+inline nlohmann::json::json_pointer operator "" _json_pointer(const char* s, std::size_t n)
+{
+    return nlohmann::json::json_pointer(std::string(s, n));
+}
+
+// #include <nlohmann/detail/macro_unscope.hpp>
+
+
+// restore GCC/clang diagnostic settings
+#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__)
+    #pragma GCC diagnostic pop
+#endif
+#if defined(__clang__)
+    #pragma GCC diagnostic pop
+#endif
+
+// clean up
+#undef JSON_INTERNAL_CATCH
+#undef JSON_CATCH
+#undef JSON_THROW
+#undef JSON_TRY
+#undef JSON_LIKELY
+#undef JSON_UNLIKELY
+#undef JSON_DEPRECATED
+#undef JSON_HAS_CPP_14
+#undef JSON_HAS_CPP_17
+#undef NLOHMANN_BASIC_JSON_TPL_DECLARATION
+#undef NLOHMANN_BASIC_JSON_TPL
+
+
+#endif
diff --git a/external/Vulkan/external/tinygltf/stb_image.h b/external/Vulkan/external/tinygltf/stb_image.h
new file mode 100644
index 0000000000000000000000000000000000000000..8b710609e3b9d13a3cb38f4052938aceb1a6dde4
--- /dev/null
+++ b/external/Vulkan/external/tinygltf/stb_image.h
@@ -0,0 +1,7530 @@
+/* stb_image - v2.21 - public domain image loader - http://nothings.org/stb
+                                  no warranty implied; use at your own risk
+
+   Do this:
+      #define STB_IMAGE_IMPLEMENTATION
+   before you include this file in *one* C or C++ file to create the implementation.
+
+   // i.e. it should look like this:
+   #include ...
+   #include ...
+   #include ...
+   #define STB_IMAGE_IMPLEMENTATION
+   #include "stb_image.h"
+
+   You can #define STBI_ASSERT(x) before the #include to avoid using assert.h.
+   And #define STBI_MALLOC, STBI_REALLOC, and STBI_FREE to avoid using malloc,realloc,free
+
+
+   QUICK NOTES:
+      Primarily of interest to game developers and other people who can
+          avoid problematic images and only need the trivial interface
+
+      JPEG baseline & progressive (12 bpc/arithmetic not supported, same as stock IJG lib)
+      PNG 1/2/4/8/16-bit-per-channel
+
+      TGA (not sure what subset, if a subset)
+      BMP non-1bpp, non-RLE
+      PSD (composited view only, no extra channels, 8/16 bit-per-channel)
+
+      GIF (*comp always reports as 4-channel)
+      HDR (radiance rgbE format)
+      PIC (Softimage PIC)
+      PNM (PPM and PGM binary only)
+
+      Animated GIF still needs a proper API, but here's one way to do it:
+          http://gist.github.com/urraka/685d9a6340b26b830d49
+
+      - decode from memory or through FILE (define STBI_NO_STDIO to remove code)
+      - decode from arbitrary I/O callbacks
+      - SIMD acceleration on x86/x64 (SSE2) and ARM (NEON)
+
+   Full documentation under "DOCUMENTATION" below.
+
+
+LICENSE
+
+  See end of file for license information.
+
+RECENT REVISION HISTORY:
+
+      2.21  (2019-02-25) fix typo in comment
+      2.20  (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs 
+      2.19  (2018-02-11) fix warning
+      2.18  (2018-01-30) fix warnings
+      2.17  (2018-01-29) bugfix, 1-bit BMP, 16-bitness query, fix warnings
+      2.16  (2017-07-23) all functions have 16-bit variants; optimizations; bugfixes
+      2.15  (2017-03-18) fix png-1,2,4; all Imagenet JPGs; no runtime SSE detection on GCC
+      2.14  (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs
+      2.13  (2016-12-04) experimental 16-bit API, only for PNG so far; fixes
+      2.12  (2016-04-02) fix typo in 2.11 PSD fix that caused crashes
+      2.11  (2016-04-02) 16-bit PNGS; enable SSE2 in non-gcc x64
+                         RGB-format JPEG; remove white matting in PSD;
+                         allocate large structures on the stack;
+                         correct channel count for PNG & BMP
+      2.10  (2016-01-22) avoid warning introduced in 2.09
+      2.09  (2016-01-16) 16-bit TGA; comments in PNM files; STBI_REALLOC_SIZED
+
+   See end of file for full revision history.
+
+
+ ============================    Contributors    =========================
+
+ Image formats                          Extensions, features
+    Sean Barrett (jpeg, png, bmp)          Jetro Lauha (stbi_info)
+    Nicolas Schulz (hdr, psd)              Martin "SpartanJ" Golini (stbi_info)
+    Jonathan Dummer (tga)                  James "moose2000" Brown (iPhone PNG)
+    Jean-Marc Lienher (gif)                Ben "Disch" Wenger (io callbacks)
+    Tom Seddon (pic)                       Omar Cornut (1/2/4-bit PNG)
+    Thatcher Ulrich (psd)                  Nicolas Guillemot (vertical flip)
+    Ken Miller (pgm, ppm)                  Richard Mitton (16-bit PSD)
+    github:urraka (animated gif)           Junggon Kim (PNM comments)
+    Christopher Forseth (animated gif)     Daniel Gibson (16-bit TGA)
+                                           socks-the-fox (16-bit PNG)
+                                           Jeremy Sawicki (handle all ImageNet JPGs)
+ Optimizations & bugfixes                  Mikhail Morozov (1-bit BMP)
+    Fabian "ryg" Giesen                    Anael Seghezzi (is-16-bit query)
+    Arseny Kapoulkine
+    John-Mark Allen
+    Carmelo J Fdez-Aguera
+
+ Bug & warning fixes
+    Marc LeBlanc            David Woo          Guillaume George   Martins Mozeiko
+    Christpher Lloyd        Jerry Jansson      Joseph Thomson     Phil Jordan
+    Dave Moore              Roy Eltham         Hayaki Saito       Nathan Reed
+    Won Chun                Luke Graham        Johan Duparc       Nick Verigakis
+    the Horde3D community   Thomas Ruf         Ronny Chevalier    github:rlyeh
+    Janez Zemva             John Bartholomew   Michal Cichon      github:romigrou
+    Jonathan Blow           Ken Hamada         Tero Hanninen      github:svdijk
+    Laurent Gomila          Cort Stratton      Sergio Gonzalez    github:snagar
+    Aruelien Pocheville     Thibault Reuille   Cass Everitt       github:Zelex
+    Ryamond Barbiero        Paul Du Bois       Engin Manap        github:grim210
+    Aldo Culquicondor       Philipp Wiesemann  Dale Weiler        github:sammyhw
+    Oriol Ferrer Mesia      Josh Tobin         Matthew Gregan     github:phprus
+    Julian Raschke          Gregory Mullen     Baldur Karlsson    github:poppolopoppo
+    Christian Floisand      Kevin Schmidt      JR Smith           github:darealshinji
+    Blazej Dariusz Roszkowski                                     github:Michaelangel007
+*/
+
+#ifndef STBI_INCLUDE_STB_IMAGE_H
+#define STBI_INCLUDE_STB_IMAGE_H
+
+// DOCUMENTATION
+//
+// Limitations:
+//    - no 12-bit-per-channel JPEG
+//    - no JPEGs with arithmetic coding
+//    - GIF always returns *comp=4
+//
+// Basic usage (see HDR discussion below for HDR usage):
+//    int x,y,n;
+//    unsigned char *data = stbi_load(filename, &x, &y, &n, 0);
+//    // ... process data if not NULL ...
+//    // ... x = width, y = height, n = # 8-bit components per pixel ...
+//    // ... replace '0' with '1'..'4' to force that many components per pixel
+//    // ... but 'n' will always be the number that it would have been if you said 0
+//    stbi_image_free(data)
+//
+// Standard parameters:
+//    int *x                 -- outputs image width in pixels
+//    int *y                 -- outputs image height in pixels
+//    int *channels_in_file  -- outputs # of image components in image file
+//    int desired_channels   -- if non-zero, # of image components requested in result
+//
+// The return value from an image loader is an 'unsigned char *' which points
+// to the pixel data, or NULL on an allocation failure or if the image is
+// corrupt or invalid. The pixel data consists of *y scanlines of *x pixels,
+// with each pixel consisting of N interleaved 8-bit components; the first
+// pixel pointed to is top-left-most in the image. There is no padding between
+// image scanlines or between pixels, regardless of format. The number of
+// components N is 'desired_channels' if desired_channels is non-zero, or
+// *channels_in_file otherwise. If desired_channels is non-zero,
+// *channels_in_file has the number of components that _would_ have been
+// output otherwise. E.g. if you set desired_channels to 4, you will always
+// get RGBA output, but you can check *channels_in_file to see if it's trivially
+// opaque because e.g. there were only 3 channels in the source image.
+//
+// An output image with N components has the following components interleaved
+// in this order in each pixel:
+//
+//     N=#comp     components
+//       1           grey
+//       2           grey, alpha
+//       3           red, green, blue
+//       4           red, green, blue, alpha
+//
+// If image loading fails for any reason, the return value will be NULL,
+// and *x, *y, *channels_in_file will be unchanged. The function
+// stbi_failure_reason() can be queried for an extremely brief, end-user
+// unfriendly explanation of why the load failed. Define STBI_NO_FAILURE_STRINGS
+// to avoid compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly
+// more user-friendly ones.
+//
+// Paletted PNG, BMP, GIF, and PIC images are automatically depalettized.
+//
+// ===========================================================================
+//
+// UNICODE:
+//
+//   If compiling for Windows and you wish to use Unicode filenames, compile
+//   with
+//       #define STBI_WINDOWS_UTF8
+//   and pass utf8-encoded filenames. Call stbi_convert_wchar_to_utf8 to convert
+//   Windows wchar_t filenames to utf8.
+//
+// ===========================================================================
+//
+// Philosophy
+//
+// stb libraries are designed with the following priorities:
+//
+//    1. easy to use
+//    2. easy to maintain
+//    3. good performance
+//
+// Sometimes I let "good performance" creep up in priority over "easy to maintain",
+// and for best performance I may provide less-easy-to-use APIs that give higher
+// performance, in addition to the easy-to-use ones. Nevertheless, it's important
+// to keep in mind that from the standpoint of you, a client of this library,
+// all you care about is #1 and #3, and stb libraries DO NOT emphasize #3 above all.
+//
+// Some secondary priorities arise directly from the first two, some of which
+// provide more explicit reasons why performance can't be emphasized.
+//
+//    - Portable ("ease of use")
+//    - Small source code footprint ("easy to maintain")
+//    - No dependencies ("ease of use")
+//
+// ===========================================================================
+//
+// I/O callbacks
+//
+// I/O callbacks allow you to read from arbitrary sources, like packaged
+// files or some other source. Data read from callbacks are processed
+// through a small internal buffer (currently 128 bytes) to try to reduce
+// overhead.
+//
+// The three functions you must define are "read" (reads some bytes of data),
+// "skip" (skips some bytes of data), "eof" (reports if the stream is at the end).
+//
+// ===========================================================================
+//
+// SIMD support
+//
+// The JPEG decoder will try to automatically use SIMD kernels on x86 when
+// supported by the compiler. For ARM Neon support, you must explicitly
+// request it.
+//
+// (The old do-it-yourself SIMD API is no longer supported in the current
+// code.)
+//
+// On x86, SSE2 will automatically be used when available based on a run-time
+// test; if not, the generic C versions are used as a fall-back. On ARM targets,
+// the typical path is to have separate builds for NEON and non-NEON devices
+// (at least this is true for iOS and Android). Therefore, the NEON support is
+// toggled by a build flag: define STBI_NEON to get NEON loops.
+//
+// If for some reason you do not want to use any of SIMD code, or if
+// you have issues compiling it, you can disable it entirely by
+// defining STBI_NO_SIMD.
+//
+// ===========================================================================
+//
+// HDR image support   (disable by defining STBI_NO_HDR)
+//
+// stb_image supports loading HDR images in general, and currently the Radiance
+// .HDR file format specifically. You can still load any file through the existing
+// interface; if you attempt to load an HDR file, it will be automatically remapped
+// to LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1;
+// both of these constants can be reconfigured through this interface:
+//
+//     stbi_hdr_to_ldr_gamma(2.2f);
+//     stbi_hdr_to_ldr_scale(1.0f);
+//
+// (note, do not use _inverse_ constants; stbi_image will invert them
+// appropriately).
+//
+// Additionally, there is a new, parallel interface for loading files as
+// (linear) floats to preserve the full dynamic range:
+//
+//    float *data = stbi_loadf(filename, &x, &y, &n, 0);
+//
+// If you load LDR images through this interface, those images will
+// be promoted to floating point values, run through the inverse of
+// constants corresponding to the above:
+//
+//     stbi_ldr_to_hdr_scale(1.0f);
+//     stbi_ldr_to_hdr_gamma(2.2f);
+//
+// Finally, given a filename (or an open file or memory block--see header
+// file for details) containing image data, you can query for the "most
+// appropriate" interface to use (that is, whether the image is HDR or
+// not), using:
+//
+//     stbi_is_hdr(char *filename);
+//
+// ===========================================================================
+//
+// iPhone PNG support:
+//
+// By default we convert iphone-formatted PNGs back to RGB, even though
+// they are internally encoded differently. You can disable this conversion
+// by calling stbi_convert_iphone_png_to_rgb(0), in which case
+// you will always just get the native iphone "format" through (which
+// is BGR stored in RGB).
+//
+// Call stbi_set_unpremultiply_on_load(1) as well to force a divide per
+// pixel to remove any premultiplied alpha *only* if the image file explicitly
+// says there's premultiplied data (currently only happens in iPhone images,
+// and only if iPhone convert-to-rgb processing is on).
+//
+// ===========================================================================
+//
+// ADDITIONAL CONFIGURATION
+//
+//  - You can suppress implementation of any of the decoders to reduce
+//    your code footprint by #defining one or more of the following
+//    symbols before creating the implementation.
+//
+//        STBI_NO_JPEG
+//        STBI_NO_PNG
+//        STBI_NO_BMP
+//        STBI_NO_PSD
+//        STBI_NO_TGA
+//        STBI_NO_GIF
+//        STBI_NO_HDR
+//        STBI_NO_PIC
+//        STBI_NO_PNM   (.ppm and .pgm)
+//
+//  - You can request *only* certain decoders and suppress all other ones
+//    (this will be more forward-compatible, as addition of new decoders
+//    doesn't require you to disable them explicitly):
+//
+//        STBI_ONLY_JPEG
+//        STBI_ONLY_PNG
+//        STBI_ONLY_BMP
+//        STBI_ONLY_PSD
+//        STBI_ONLY_TGA
+//        STBI_ONLY_GIF
+//        STBI_ONLY_HDR
+//        STBI_ONLY_PIC
+//        STBI_ONLY_PNM   (.ppm and .pgm)
+//
+//   - If you use STBI_NO_PNG (or _ONLY_ without PNG), and you still
+//     want the zlib decoder to be available, #define STBI_SUPPORT_ZLIB
+//
+
+
+#ifndef STBI_NO_STDIO
+#include <stdio.h>
+#endif // STBI_NO_STDIO
+
+#define STBI_VERSION 1
+
+enum
+{
+   STBI_default = 0, // only used for desired_channels
+
+   STBI_grey       = 1,
+   STBI_grey_alpha = 2,
+   STBI_rgb        = 3,
+   STBI_rgb_alpha  = 4
+};
+
+#include <stdlib.h>
+typedef unsigned char stbi_uc;
+typedef unsigned short stbi_us;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef STB_IMAGE_STATIC
+#define STBIDEF static
+#else
+#define STBIDEF extern
+#endif
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// PRIMARY API - works on images of any type
+//
+
+//
+// load image by filename, open file, or memory buffer
+//
+
+typedef struct
+{
+   int      (*read)  (void *user,char *data,int size);   // fill 'data' with 'size' bytes.  return number of bytes actually read
+   void     (*skip)  (void *user,int n);                 // skip the next 'n' bytes, or 'unget' the last -n bytes if negative
+   int      (*eof)   (void *user);                       // returns nonzero if we are at end of file/data
+} stbi_io_callbacks;
+
+////////////////////////////////////
+//
+// 8-bits-per-channel interface
+//
+
+STBIDEF stbi_uc *stbi_load_from_memory   (stbi_uc           const *buffer, int len   , int *x, int *y, int *channels_in_file, int desired_channels);
+STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk  , void *user, int *x, int *y, int *channels_in_file, int desired_channels);
+
+#ifndef STBI_NO_STDIO
+STBIDEF stbi_uc *stbi_load            (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels);
+STBIDEF stbi_uc *stbi_load_from_file  (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels);
+// for stbi_load_from_file, file pointer is left pointing immediately after image
+#endif
+
+#ifndef STBI_NO_GIF
+STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp);
+#endif
+
+#ifdef STBI_WINDOWS_UTF8
+STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input);
+#endif
+
+////////////////////////////////////
+//
+// 16-bits-per-channel interface
+//
+
+STBIDEF stbi_us *stbi_load_16_from_memory   (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels);
+STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels);
+
+#ifndef STBI_NO_STDIO
+STBIDEF stbi_us *stbi_load_16          (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels);
+STBIDEF stbi_us *stbi_load_from_file_16(FILE *f, int *x, int *y, int *channels_in_file, int desired_channels);
+#endif
+
+////////////////////////////////////
+//
+// float-per-channel interface
+//
+#ifndef STBI_NO_LINEAR
+   STBIDEF float *stbi_loadf_from_memory     (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels);
+   STBIDEF float *stbi_loadf_from_callbacks  (stbi_io_callbacks const *clbk, void *user, int *x, int *y,  int *channels_in_file, int desired_channels);
+
+   #ifndef STBI_NO_STDIO
+   STBIDEF float *stbi_loadf            (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels);
+   STBIDEF float *stbi_loadf_from_file  (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels);
+   #endif
+#endif
+
+#ifndef STBI_NO_HDR
+   STBIDEF void   stbi_hdr_to_ldr_gamma(float gamma);
+   STBIDEF void   stbi_hdr_to_ldr_scale(float scale);
+#endif // STBI_NO_HDR
+
+#ifndef STBI_NO_LINEAR
+   STBIDEF void   stbi_ldr_to_hdr_gamma(float gamma);
+   STBIDEF void   stbi_ldr_to_hdr_scale(float scale);
+#endif // STBI_NO_LINEAR
+
+// stbi_is_hdr is always defined, but always returns false if STBI_NO_HDR
+STBIDEF int    stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user);
+STBIDEF int    stbi_is_hdr_from_memory(stbi_uc const *buffer, int len);
+#ifndef STBI_NO_STDIO
+STBIDEF int      stbi_is_hdr          (char const *filename);
+STBIDEF int      stbi_is_hdr_from_file(FILE *f);
+#endif // STBI_NO_STDIO
+
+
+// get a VERY brief reason for failure
+// NOT THREADSAFE
+STBIDEF const char *stbi_failure_reason  (void);
+
+// free the loaded image -- this is just free()
+STBIDEF void     stbi_image_free      (void *retval_from_stbi_load);
+
+// get image dimensions & components without fully decoding
+STBIDEF int      stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp);
+STBIDEF int      stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp);
+STBIDEF int      stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len);
+STBIDEF int      stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *clbk, void *user);
+
+#ifndef STBI_NO_STDIO
+STBIDEF int      stbi_info               (char const *filename,     int *x, int *y, int *comp);
+STBIDEF int      stbi_info_from_file     (FILE *f,                  int *x, int *y, int *comp);
+STBIDEF int      stbi_is_16_bit          (char const *filename);
+STBIDEF int      stbi_is_16_bit_from_file(FILE *f);
+#endif
+
+
+
+// for image formats that explicitly notate that they have premultiplied alpha,
+// we just return the colors as stored in the file. set this flag to force
+// unpremultiplication. results are undefined if the unpremultiply overflow.
+STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply);
+
+// indicate whether we should process iphone images back to canonical format,
+// or just pass them through "as-is"
+STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert);
+
+// flip the image vertically, so the first pixel in the output array is the bottom left
+STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip);
+
+// ZLIB client - used by PNG, available for other purposes
+
+STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen);
+STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header);
+STBIDEF char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen);
+STBIDEF int   stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen);
+
+STBIDEF char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen);
+STBIDEF int   stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+//
+//
+////   end header file   /////////////////////////////////////////////////////
+#endif // STBI_INCLUDE_STB_IMAGE_H
+
+#ifdef STB_IMAGE_IMPLEMENTATION
+
+#if defined(STBI_ONLY_JPEG) || defined(STBI_ONLY_PNG) || defined(STBI_ONLY_BMP) \
+  || defined(STBI_ONLY_TGA) || defined(STBI_ONLY_GIF) || defined(STBI_ONLY_PSD) \
+  || defined(STBI_ONLY_HDR) || defined(STBI_ONLY_PIC) || defined(STBI_ONLY_PNM) \
+  || defined(STBI_ONLY_ZLIB)
+   #ifndef STBI_ONLY_JPEG
+   #define STBI_NO_JPEG
+   #endif
+   #ifndef STBI_ONLY_PNG
+   #define STBI_NO_PNG
+   #endif
+   #ifndef STBI_ONLY_BMP
+   #define STBI_NO_BMP
+   #endif
+   #ifndef STBI_ONLY_PSD
+   #define STBI_NO_PSD
+   #endif
+   #ifndef STBI_ONLY_TGA
+   #define STBI_NO_TGA
+   #endif
+   #ifndef STBI_ONLY_GIF
+   #define STBI_NO_GIF
+   #endif
+   #ifndef STBI_ONLY_HDR
+   #define STBI_NO_HDR
+   #endif
+   #ifndef STBI_ONLY_PIC
+   #define STBI_NO_PIC
+   #endif
+   #ifndef STBI_ONLY_PNM
+   #define STBI_NO_PNM
+   #endif
+#endif
+
+#if defined(STBI_NO_PNG) && !defined(STBI_SUPPORT_ZLIB) && !defined(STBI_NO_ZLIB)
+#define STBI_NO_ZLIB
+#endif
+
+
+#include <stdarg.h>
+#include <stddef.h> // ptrdiff_t on osx
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR)
+#include <math.h>  // ldexp, pow
+#endif
+
+#ifndef STBI_NO_STDIO
+#include <stdio.h>
+#endif
+
+#ifndef STBI_ASSERT
+#include <assert.h>
+#define STBI_ASSERT(x) assert(x)
+#endif
+
+#ifdef __cplusplus
+#define STBI_EXTERN extern "C"
+#else
+#define STBI_EXTERN extern
+#endif
+
+
+#ifndef _MSC_VER
+   #ifdef __cplusplus
+   #define stbi_inline inline
+   #else
+   #define stbi_inline
+   #endif
+#else
+   #define stbi_inline __forceinline
+#endif
+
+
+#ifdef _MSC_VER
+typedef unsigned short stbi__uint16;
+typedef   signed short stbi__int16;
+typedef unsigned int   stbi__uint32;
+typedef   signed int   stbi__int32;
+#else
+#include <stdint.h>
+typedef uint16_t stbi__uint16;
+typedef int16_t  stbi__int16;
+typedef uint32_t stbi__uint32;
+typedef int32_t  stbi__int32;
+#endif
+
+// should produce compiler error if size is wrong
+typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1];
+
+#ifdef _MSC_VER
+#define STBI_NOTUSED(v)  (void)(v)
+#else
+#define STBI_NOTUSED(v)  (void)sizeof(v)
+#endif
+
+#ifdef _MSC_VER
+#define STBI_HAS_LROTL
+#endif
+
+#ifdef STBI_HAS_LROTL
+   #define stbi_lrot(x,y)  _lrotl(x,y)
+#else
+   #define stbi_lrot(x,y)  (((x) << (y)) | ((x) >> (32 - (y))))
+#endif
+
+#if defined(STBI_MALLOC) && defined(STBI_FREE) && (defined(STBI_REALLOC) || defined(STBI_REALLOC_SIZED))
+// ok
+#elif !defined(STBI_MALLOC) && !defined(STBI_FREE) && !defined(STBI_REALLOC) && !defined(STBI_REALLOC_SIZED)
+// ok
+#else
+#error "Must define all or none of STBI_MALLOC, STBI_FREE, and STBI_REALLOC (or STBI_REALLOC_SIZED)."
+#endif
+
+#ifndef STBI_MALLOC
+#define STBI_MALLOC(sz)           malloc(sz)
+#define STBI_REALLOC(p,newsz)     realloc(p,newsz)
+#define STBI_FREE(p)              free(p)
+#endif
+
+#ifndef STBI_REALLOC_SIZED
+#define STBI_REALLOC_SIZED(p,oldsz,newsz) STBI_REALLOC(p,newsz)
+#endif
+
+// x86/x64 detection
+#if defined(__x86_64__) || defined(_M_X64)
+#define STBI__X64_TARGET
+#elif defined(__i386) || defined(_M_IX86)
+#define STBI__X86_TARGET
+#endif
+
+#if defined(__GNUC__) && defined(STBI__X86_TARGET) && !defined(__SSE2__) && !defined(STBI_NO_SIMD)
+// gcc doesn't support sse2 intrinsics unless you compile with -msse2,
+// which in turn means it gets to use SSE2 everywhere. This is unfortunate,
+// but previous attempts to provide the SSE2 functions with runtime
+// detection caused numerous issues. The way architecture extensions are
+// exposed in GCC/Clang is, sadly, not really suited for one-file libs.
+// New behavior: if compiled with -msse2, we use SSE2 without any
+// detection; if not, we don't use it at all.
+#define STBI_NO_SIMD
+#endif
+
+#if defined(__MINGW32__) && defined(STBI__X86_TARGET) && !defined(STBI_MINGW_ENABLE_SSE2) && !defined(STBI_NO_SIMD)
+// Note that __MINGW32__ doesn't actually mean 32-bit, so we have to avoid STBI__X64_TARGET
+//
+// 32-bit MinGW wants ESP to be 16-byte aligned, but this is not in the
+// Windows ABI and VC++ as well as Windows DLLs don't maintain that invariant.
+// As a result, enabling SSE2 on 32-bit MinGW is dangerous when not
+// simultaneously enabling "-mstackrealign".
+//
+// See https://github.com/nothings/stb/issues/81 for more information.
+//
+// So default to no SSE2 on 32-bit MinGW. If you've read this far and added
+// -mstackrealign to your build settings, feel free to #define STBI_MINGW_ENABLE_SSE2.
+#define STBI_NO_SIMD
+#endif
+
+#if !defined(STBI_NO_SIMD) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET))
+#define STBI_SSE2
+#include <emmintrin.h>
+
+#ifdef _MSC_VER
+
+#if _MSC_VER >= 1400  // not VC6
+#include <intrin.h> // __cpuid
+static int stbi__cpuid3(void)
+{
+   int info[4];
+   __cpuid(info,1);
+   return info[3];
+}
+#else
+static int stbi__cpuid3(void)
+{
+   int res;
+   __asm {
+      mov  eax,1
+      cpuid
+      mov  res,edx
+   }
+   return res;
+}
+#endif
+
+#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name
+
+#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2)
+static int stbi__sse2_available(void)
+{
+   int info3 = stbi__cpuid3();
+   return ((info3 >> 26) & 1) != 0;
+}
+#endif
+
+#else // assume GCC-style if not VC++
+#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16)))
+
+#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2)
+static int stbi__sse2_available(void)
+{
+   // If we're even attempting to compile this on GCC/Clang, that means
+   // -msse2 is on, which means the compiler is allowed to use SSE2
+   // instructions at will, and so are we.
+   return 1;
+}
+#endif
+
+#endif
+#endif
+
+// ARM NEON
+#if defined(STBI_NO_SIMD) && defined(STBI_NEON)
+#undef STBI_NEON
+#endif
+
+#ifdef STBI_NEON
+#include <arm_neon.h>
+// assume GCC or Clang on ARM targets
+#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16)))
+#endif
+
+#ifndef STBI_SIMD_ALIGN
+#define STBI_SIMD_ALIGN(type, name) type name
+#endif
+
+///////////////////////////////////////////////
+//
+//  stbi__context struct and start_xxx functions
+
+// stbi__context structure is our basic context used by all images, so it
+// contains all the IO context, plus some basic image information
+typedef struct
+{
+   stbi__uint32 img_x, img_y;
+   int img_n, img_out_n;
+
+   stbi_io_callbacks io;
+   void *io_user_data;
+
+   int read_from_callbacks;
+   int buflen;
+   stbi_uc buffer_start[128];
+
+   stbi_uc *img_buffer, *img_buffer_end;
+   stbi_uc *img_buffer_original, *img_buffer_original_end;
+} stbi__context;
+
+
+static void stbi__refill_buffer(stbi__context *s);
+
+// initialize a memory-decode context
+static void stbi__start_mem(stbi__context *s, stbi_uc const *buffer, int len)
+{
+   s->io.read = NULL;
+   s->read_from_callbacks = 0;
+   s->img_buffer = s->img_buffer_original = (stbi_uc *) buffer;
+   s->img_buffer_end = s->img_buffer_original_end = (stbi_uc *) buffer+len;
+}
+
+// initialize a callback-based context
+static void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void *user)
+{
+   s->io = *c;
+   s->io_user_data = user;
+   s->buflen = sizeof(s->buffer_start);
+   s->read_from_callbacks = 1;
+   s->img_buffer_original = s->buffer_start;
+   stbi__refill_buffer(s);
+   s->img_buffer_original_end = s->img_buffer_end;
+}
+
+#ifndef STBI_NO_STDIO
+
+static int stbi__stdio_read(void *user, char *data, int size)
+{
+   return (int) fread(data,1,size,(FILE*) user);
+}
+
+static void stbi__stdio_skip(void *user, int n)
+{
+   fseek((FILE*) user, n, SEEK_CUR);
+}
+
+static int stbi__stdio_eof(void *user)
+{
+   return feof((FILE*) user);
+}
+
+static stbi_io_callbacks stbi__stdio_callbacks =
+{
+   stbi__stdio_read,
+   stbi__stdio_skip,
+   stbi__stdio_eof,
+};
+
+static void stbi__start_file(stbi__context *s, FILE *f)
+{
+   stbi__start_callbacks(s, &stbi__stdio_callbacks, (void *) f);
+}
+
+//static void stop_file(stbi__context *s) { }
+
+#endif // !STBI_NO_STDIO
+
+static void stbi__rewind(stbi__context *s)
+{
+   // conceptually rewind SHOULD rewind to the beginning of the stream,
+   // but we just rewind to the beginning of the initial buffer, because
+   // we only use it after doing 'test', which only ever looks at at most 92 bytes
+   s->img_buffer = s->img_buffer_original;
+   s->img_buffer_end = s->img_buffer_original_end;
+}
+
+enum
+{
+   STBI_ORDER_RGB,
+   STBI_ORDER_BGR
+};
+
+typedef struct
+{
+   int bits_per_channel;
+   int num_channels;
+   int channel_order;
+} stbi__result_info;
+
+#ifndef STBI_NO_JPEG
+static int      stbi__jpeg_test(stbi__context *s);
+static void    *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);
+static int      stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp);
+#endif
+
+#ifndef STBI_NO_PNG
+static int      stbi__png_test(stbi__context *s);
+static void    *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);
+static int      stbi__png_info(stbi__context *s, int *x, int *y, int *comp);
+static int      stbi__png_is16(stbi__context *s);
+#endif
+
+#ifndef STBI_NO_BMP
+static int      stbi__bmp_test(stbi__context *s);
+static void    *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);
+static int      stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp);
+#endif
+
+#ifndef STBI_NO_TGA
+static int      stbi__tga_test(stbi__context *s);
+static void    *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);
+static int      stbi__tga_info(stbi__context *s, int *x, int *y, int *comp);
+#endif
+
+#ifndef STBI_NO_PSD
+static int      stbi__psd_test(stbi__context *s);
+static void    *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc);
+static int      stbi__psd_info(stbi__context *s, int *x, int *y, int *comp);
+static int      stbi__psd_is16(stbi__context *s);
+#endif
+
+#ifndef STBI_NO_HDR
+static int      stbi__hdr_test(stbi__context *s);
+static float   *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);
+static int      stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp);
+#endif
+
+#ifndef STBI_NO_PIC
+static int      stbi__pic_test(stbi__context *s);
+static void    *stbi__pic_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);
+static int      stbi__pic_info(stbi__context *s, int *x, int *y, int *comp);
+#endif
+
+#ifndef STBI_NO_GIF
+static int      stbi__gif_test(stbi__context *s);
+static void    *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);
+static void    *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp);
+static int      stbi__gif_info(stbi__context *s, int *x, int *y, int *comp);
+#endif
+
+#ifndef STBI_NO_PNM
+static int      stbi__pnm_test(stbi__context *s);
+static void    *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);
+static int      stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp);
+#endif
+
+// this is not threadsafe
+static const char *stbi__g_failure_reason;
+
+STBIDEF const char *stbi_failure_reason(void)
+{
+   return stbi__g_failure_reason;
+}
+
+static int stbi__err(const char *str)
+{
+   stbi__g_failure_reason = str;
+   return 0;
+}
+
+static void *stbi__malloc(size_t size)
+{
+    return STBI_MALLOC(size);
+}
+
+// stb_image uses ints pervasively, including for offset calculations.
+// therefore the largest decoded image size we can support with the
+// current code, even on 64-bit targets, is INT_MAX. this is not a
+// significant limitation for the intended use case.
+//
+// we do, however, need to make sure our size calculations don't
+// overflow. hence a few helper functions for size calculations that
+// multiply integers together, making sure that they're non-negative
+// and no overflow occurs.
+
+// return 1 if the sum is valid, 0 on overflow.
+// negative terms are considered invalid.
+static int stbi__addsizes_valid(int a, int b)
+{
+   if (b < 0) return 0;
+   // now 0 <= b <= INT_MAX, hence also
+   // 0 <= INT_MAX - b <= INTMAX.
+   // And "a + b <= INT_MAX" (which might overflow) is the
+   // same as a <= INT_MAX - b (no overflow)
+   return a <= INT_MAX - b;
+}
+
+// returns 1 if the product is valid, 0 on overflow.
+// negative factors are considered invalid.
+static int stbi__mul2sizes_valid(int a, int b)
+{
+   if (a < 0 || b < 0) return 0;
+   if (b == 0) return 1; // mul-by-0 is always safe
+   // portable way to check for no overflows in a*b
+   return a <= INT_MAX/b;
+}
+
+// returns 1 if "a*b + add" has no negative terms/factors and doesn't overflow
+static int stbi__mad2sizes_valid(int a, int b, int add)
+{
+   return stbi__mul2sizes_valid(a, b) && stbi__addsizes_valid(a*b, add);
+}
+
+// returns 1 if "a*b*c + add" has no negative terms/factors and doesn't overflow
+static int stbi__mad3sizes_valid(int a, int b, int c, int add)
+{
+   return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) &&
+      stbi__addsizes_valid(a*b*c, add);
+}
+
+// returns 1 if "a*b*c*d + add" has no negative terms/factors and doesn't overflow
+#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR)
+static int stbi__mad4sizes_valid(int a, int b, int c, int d, int add)
+{
+   return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) &&
+      stbi__mul2sizes_valid(a*b*c, d) && stbi__addsizes_valid(a*b*c*d, add);
+}
+#endif
+
+// mallocs with size overflow checking
+static void *stbi__malloc_mad2(int a, int b, int add)
+{
+   if (!stbi__mad2sizes_valid(a, b, add)) return NULL;
+   return stbi__malloc(a*b + add);
+}
+
+static void *stbi__malloc_mad3(int a, int b, int c, int add)
+{
+   if (!stbi__mad3sizes_valid(a, b, c, add)) return NULL;
+   return stbi__malloc(a*b*c + add);
+}
+
+#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR)
+static void *stbi__malloc_mad4(int a, int b, int c, int d, int add)
+{
+   if (!stbi__mad4sizes_valid(a, b, c, d, add)) return NULL;
+   return stbi__malloc(a*b*c*d + add);
+}
+#endif
+
+// stbi__err - error
+// stbi__errpf - error returning pointer to float
+// stbi__errpuc - error returning pointer to unsigned char
+
+#ifdef STBI_NO_FAILURE_STRINGS
+   #define stbi__err(x,y)  0
+#elif defined(STBI_FAILURE_USERMSG)
+   #define stbi__err(x,y)  stbi__err(y)
+#else
+   #define stbi__err(x,y)  stbi__err(x)
+#endif
+
+#define stbi__errpf(x,y)   ((float *)(size_t) (stbi__err(x,y)?NULL:NULL))
+#define stbi__errpuc(x,y)  ((unsigned char *)(size_t) (stbi__err(x,y)?NULL:NULL))
+
+STBIDEF void stbi_image_free(void *retval_from_stbi_load)
+{
+   STBI_FREE(retval_from_stbi_load);
+}
+
+#ifndef STBI_NO_LINEAR
+static float   *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp);
+#endif
+
+#ifndef STBI_NO_HDR
+static stbi_uc *stbi__hdr_to_ldr(float   *data, int x, int y, int comp);
+#endif
+
+static int stbi__vertically_flip_on_load = 0;
+
+STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip)
+{
+    stbi__vertically_flip_on_load = flag_true_if_should_flip;
+}
+
+static void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc)
+{
+   memset(ri, 0, sizeof(*ri)); // make sure it's initialized if we add new fields
+   ri->bits_per_channel = 8; // default is 8 so most paths don't have to be changed
+   ri->channel_order = STBI_ORDER_RGB; // all current input & output are this, but this is here so we can add BGR order
+   ri->num_channels = 0;
+
+   #ifndef STBI_NO_JPEG
+   if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp, ri);
+   #endif
+   #ifndef STBI_NO_PNG
+   if (stbi__png_test(s))  return stbi__png_load(s,x,y,comp,req_comp, ri);
+   #endif
+   #ifndef STBI_NO_BMP
+   if (stbi__bmp_test(s))  return stbi__bmp_load(s,x,y,comp,req_comp, ri);
+   #endif
+   #ifndef STBI_NO_GIF
+   if (stbi__gif_test(s))  return stbi__gif_load(s,x,y,comp,req_comp, ri);
+   #endif
+   #ifndef STBI_NO_PSD
+   if (stbi__psd_test(s))  return stbi__psd_load(s,x,y,comp,req_comp, ri, bpc);
+   #endif
+   #ifndef STBI_NO_PIC
+   if (stbi__pic_test(s))  return stbi__pic_load(s,x,y,comp,req_comp, ri);
+   #endif
+   #ifndef STBI_NO_PNM
+   if (stbi__pnm_test(s))  return stbi__pnm_load(s,x,y,comp,req_comp, ri);
+   #endif
+
+   #ifndef STBI_NO_HDR
+   if (stbi__hdr_test(s)) {
+      float *hdr = stbi__hdr_load(s, x,y,comp,req_comp, ri);
+      return stbi__hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp);
+   }
+   #endif
+
+   #ifndef STBI_NO_TGA
+   // test tga last because it's a crappy test!
+   if (stbi__tga_test(s))
+      return stbi__tga_load(s,x,y,comp,req_comp, ri);
+   #endif
+
+   return stbi__errpuc("unknown image type", "Image not of any known type, or corrupt");
+}
+
+static stbi_uc *stbi__convert_16_to_8(stbi__uint16 *orig, int w, int h, int channels)
+{
+   int i;
+   int img_len = w * h * channels;
+   stbi_uc *reduced;
+
+   reduced = (stbi_uc *) stbi__malloc(img_len);
+   if (reduced == NULL) return stbi__errpuc("outofmem", "Out of memory");
+
+   for (i = 0; i < img_len; ++i)
+      reduced[i] = (stbi_uc)((orig[i] >> 8) & 0xFF); // top half of each byte is sufficient approx of 16->8 bit scaling
+
+   STBI_FREE(orig);
+   return reduced;
+}
+
+static stbi__uint16 *stbi__convert_8_to_16(stbi_uc *orig, int w, int h, int channels)
+{
+   int i;
+   int img_len = w * h * channels;
+   stbi__uint16 *enlarged;
+
+   enlarged = (stbi__uint16 *) stbi__malloc(img_len*2);
+   if (enlarged == NULL) return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory");
+
+   for (i = 0; i < img_len; ++i)
+      enlarged[i] = (stbi__uint16)((orig[i] << 8) + orig[i]); // replicate to high and low byte, maps 0->0, 255->0xffff
+
+   STBI_FREE(orig);
+   return enlarged;
+}
+
+static void stbi__vertical_flip(void *image, int w, int h, int bytes_per_pixel)
+{
+   int row;
+   size_t bytes_per_row = (size_t)w * bytes_per_pixel;
+   stbi_uc temp[2048];
+   stbi_uc *bytes = (stbi_uc *)image;
+
+   for (row = 0; row < (h>>1); row++) {
+      stbi_uc *row0 = bytes + row*bytes_per_row;
+      stbi_uc *row1 = bytes + (h - row - 1)*bytes_per_row;
+      // swap row0 with row1
+      size_t bytes_left = bytes_per_row;
+      while (bytes_left) {
+         size_t bytes_copy = (bytes_left < sizeof(temp)) ? bytes_left : sizeof(temp);
+         memcpy(temp, row0, bytes_copy);
+         memcpy(row0, row1, bytes_copy);
+         memcpy(row1, temp, bytes_copy);
+         row0 += bytes_copy;
+         row1 += bytes_copy;
+         bytes_left -= bytes_copy;
+      }
+   }
+}
+
+#ifndef STBI_NO_GIF
+static void stbi__vertical_flip_slices(void *image, int w, int h, int z, int bytes_per_pixel)
+{
+   int slice;
+   int slice_size = w * h * bytes_per_pixel;
+
+   stbi_uc *bytes = (stbi_uc *)image;
+   for (slice = 0; slice < z; ++slice) {
+      stbi__vertical_flip(bytes, w, h, bytes_per_pixel); 
+      bytes += slice_size; 
+   }
+}
+#endif
+
+static unsigned char *stbi__load_and_postprocess_8bit(stbi__context *s, int *x, int *y, int *comp, int req_comp)
+{
+   stbi__result_info ri;
+   void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 8);
+
+   if (result == NULL)
+      return NULL;
+
+   if (ri.bits_per_channel != 8) {
+      STBI_ASSERT(ri.bits_per_channel == 16);
+      result = stbi__convert_16_to_8((stbi__uint16 *) result, *x, *y, req_comp == 0 ? *comp : req_comp);
+      ri.bits_per_channel = 8;
+   }
+
+   // @TODO: move stbi__convert_format to here
+
+   if (stbi__vertically_flip_on_load) {
+      int channels = req_comp ? req_comp : *comp;
+      stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi_uc));
+   }
+
+   return (unsigned char *) result;
+}
+
+static stbi__uint16 *stbi__load_and_postprocess_16bit(stbi__context *s, int *x, int *y, int *comp, int req_comp)
+{
+   stbi__result_info ri;
+   void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 16);
+
+   if (result == NULL)
+      return NULL;
+
+   if (ri.bits_per_channel != 16) {
+      STBI_ASSERT(ri.bits_per_channel == 8);
+      result = stbi__convert_8_to_16((stbi_uc *) result, *x, *y, req_comp == 0 ? *comp : req_comp);
+      ri.bits_per_channel = 16;
+   }
+
+   // @TODO: move stbi__convert_format16 to here
+   // @TODO: special case RGB-to-Y (and RGBA-to-YA) for 8-bit-to-16-bit case to keep more precision
+
+   if (stbi__vertically_flip_on_load) {
+      int channels = req_comp ? req_comp : *comp;
+      stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi__uint16));
+   }
+
+   return (stbi__uint16 *) result;
+}
+
+#if !defined(STBI_NO_HDR) && !defined(STBI_NO_LINEAR)
+static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp)
+{
+   if (stbi__vertically_flip_on_load && result != NULL) {
+      int channels = req_comp ? req_comp : *comp;
+      stbi__vertical_flip(result, *x, *y, channels * sizeof(float));
+   }
+}
+#endif
+
+#ifndef STBI_NO_STDIO
+
+#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8)
+STBI_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide);
+STBI_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default);
+#endif
+
+#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8)
+STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input)
+{
+	return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, bufferlen, NULL, NULL);
+}
+#endif
+
+static FILE *stbi__fopen(char const *filename, char const *mode)
+{
+   FILE *f;
+#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8)
+   wchar_t wMode[64];
+   wchar_t wFilename[1024];
+	if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename)))
+      return 0;
+	
+	if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode)))
+      return 0;
+
+#if _MSC_VER >= 1400
+	if (0 != _wfopen_s(&f, wFilename, wMode))
+		f = 0;
+#else
+   f = _wfopen(wFilename, wMode);
+#endif
+
+#elif defined(_MSC_VER) && _MSC_VER >= 1400
+   if (0 != fopen_s(&f, filename, mode))
+      f=0;
+#else
+   f = fopen(filename, mode);
+#endif
+   return f;
+}
+
+
+STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp)
+{
+   FILE *f = stbi__fopen(filename, "rb");
+   unsigned char *result;
+   if (!f) return stbi__errpuc("can't fopen", "Unable to open file");
+   result = stbi_load_from_file(f,x,y,comp,req_comp);
+   fclose(f);
+   return result;
+}
+
+STBIDEF stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp)
+{
+   unsigned char *result;
+   stbi__context s;
+   stbi__start_file(&s,f);
+   result = stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp);
+   if (result) {
+      // need to 'unget' all the characters in the IO buffer
+      fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR);
+   }
+   return result;
+}
+
+STBIDEF stbi__uint16 *stbi_load_from_file_16(FILE *f, int *x, int *y, int *comp, int req_comp)
+{
+   stbi__uint16 *result;
+   stbi__context s;
+   stbi__start_file(&s,f);
+   result = stbi__load_and_postprocess_16bit(&s,x,y,comp,req_comp);
+   if (result) {
+      // need to 'unget' all the characters in the IO buffer
+      fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR);
+   }
+   return result;
+}
+
+STBIDEF stbi_us *stbi_load_16(char const *filename, int *x, int *y, int *comp, int req_comp)
+{
+   FILE *f = stbi__fopen(filename, "rb");
+   stbi__uint16 *result;
+   if (!f) return (stbi_us *) stbi__errpuc("can't fopen", "Unable to open file");
+   result = stbi_load_from_file_16(f,x,y,comp,req_comp);
+   fclose(f);
+   return result;
+}
+
+
+#endif //!STBI_NO_STDIO
+
+STBIDEF stbi_us *stbi_load_16_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels)
+{
+   stbi__context s;
+   stbi__start_mem(&s,buffer,len);
+   return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels);
+}
+
+STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels)
+{
+   stbi__context s;
+   stbi__start_callbacks(&s, (stbi_io_callbacks *)clbk, user);
+   return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels);
+}
+
+STBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp)
+{
+   stbi__context s;
+   stbi__start_mem(&s,buffer,len);
+   return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp);
+}
+
+STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp)
+{
+   stbi__context s;
+   stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user);
+   return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp);
+}
+
+#ifndef STBI_NO_GIF
+STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp)
+{
+   unsigned char *result;
+   stbi__context s; 
+   stbi__start_mem(&s,buffer,len); 
+   
+   result = (unsigned char*) stbi__load_gif_main(&s, delays, x, y, z, comp, req_comp);
+   if (stbi__vertically_flip_on_load) {
+      stbi__vertical_flip_slices( result, *x, *y, *z, *comp ); 
+   }
+
+   return result; 
+}
+#endif
+
+#ifndef STBI_NO_LINEAR
+static float *stbi__loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp)
+{
+   unsigned char *data;
+   #ifndef STBI_NO_HDR
+   if (stbi__hdr_test(s)) {
+      stbi__result_info ri;
+      float *hdr_data = stbi__hdr_load(s,x,y,comp,req_comp, &ri);
+      if (hdr_data)
+         stbi__float_postprocess(hdr_data,x,y,comp,req_comp);
+      return hdr_data;
+   }
+   #endif
+   data = stbi__load_and_postprocess_8bit(s, x, y, comp, req_comp);
+   if (data)
+      return stbi__ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp);
+   return stbi__errpf("unknown image type", "Image not of any known type, or corrupt");
+}
+
+STBIDEF float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp)
+{
+   stbi__context s;
+   stbi__start_mem(&s,buffer,len);
+   return stbi__loadf_main(&s,x,y,comp,req_comp);
+}
+
+STBIDEF float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp)
+{
+   stbi__context s;
+   stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user);
+   return stbi__loadf_main(&s,x,y,comp,req_comp);
+}
+
+#ifndef STBI_NO_STDIO
+STBIDEF float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp)
+{
+   float *result;
+   FILE *f = stbi__fopen(filename, "rb");
+   if (!f) return stbi__errpf("can't fopen", "Unable to open file");
+   result = stbi_loadf_from_file(f,x,y,comp,req_comp);
+   fclose(f);
+   return result;
+}
+
+STBIDEF float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp)
+{
+   stbi__context s;
+   stbi__start_file(&s,f);
+   return stbi__loadf_main(&s,x,y,comp,req_comp);
+}
+#endif // !STBI_NO_STDIO
+
+#endif // !STBI_NO_LINEAR
+
+// these is-hdr-or-not is defined independent of whether STBI_NO_LINEAR is
+// defined, for API simplicity; if STBI_NO_LINEAR is defined, it always
+// reports false!
+
+STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len)
+{
+   #ifndef STBI_NO_HDR
+   stbi__context s;
+   stbi__start_mem(&s,buffer,len);
+   return stbi__hdr_test(&s);
+   #else
+   STBI_NOTUSED(buffer);
+   STBI_NOTUSED(len);
+   return 0;
+   #endif
+}
+
+#ifndef STBI_NO_STDIO
+STBIDEF int      stbi_is_hdr          (char const *filename)
+{
+   FILE *f = stbi__fopen(filename, "rb");
+   int result=0;
+   if (f) {
+      result = stbi_is_hdr_from_file(f);
+      fclose(f);
+   }
+   return result;
+}
+
+STBIDEF int stbi_is_hdr_from_file(FILE *f)
+{
+   #ifndef STBI_NO_HDR
+   long pos = ftell(f);
+   int res;
+   stbi__context s;
+   stbi__start_file(&s,f);
+   res = stbi__hdr_test(&s);
+   fseek(f, pos, SEEK_SET);
+   return res;
+   #else
+   STBI_NOTUSED(f);
+   return 0;
+   #endif
+}
+#endif // !STBI_NO_STDIO
+
+STBIDEF int      stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user)
+{
+   #ifndef STBI_NO_HDR
+   stbi__context s;
+   stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user);
+   return stbi__hdr_test(&s);
+   #else
+   STBI_NOTUSED(clbk);
+   STBI_NOTUSED(user);
+   return 0;
+   #endif
+}
+
+#ifndef STBI_NO_LINEAR
+static float stbi__l2h_gamma=2.2f, stbi__l2h_scale=1.0f;
+
+STBIDEF void   stbi_ldr_to_hdr_gamma(float gamma) { stbi__l2h_gamma = gamma; }
+STBIDEF void   stbi_ldr_to_hdr_scale(float scale) { stbi__l2h_scale = scale; }
+#endif
+
+static float stbi__h2l_gamma_i=1.0f/2.2f, stbi__h2l_scale_i=1.0f;
+
+STBIDEF void   stbi_hdr_to_ldr_gamma(float gamma) { stbi__h2l_gamma_i = 1/gamma; }
+STBIDEF void   stbi_hdr_to_ldr_scale(float scale) { stbi__h2l_scale_i = 1/scale; }
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// Common code used by all image loaders
+//
+
+enum
+{
+   STBI__SCAN_load=0,
+   STBI__SCAN_type,
+   STBI__SCAN_header
+};
+
+static void stbi__refill_buffer(stbi__context *s)
+{
+   int n = (s->io.read)(s->io_user_data,(char*)s->buffer_start,s->buflen);
+   if (n == 0) {
+      // at end of file, treat same as if from memory, but need to handle case
+      // where s->img_buffer isn't pointing to safe memory, e.g. 0-byte file
+      s->read_from_callbacks = 0;
+      s->img_buffer = s->buffer_start;
+      s->img_buffer_end = s->buffer_start+1;
+      *s->img_buffer = 0;
+   } else {
+      s->img_buffer = s->buffer_start;
+      s->img_buffer_end = s->buffer_start + n;
+   }
+}
+
+stbi_inline static stbi_uc stbi__get8(stbi__context *s)
+{
+   if (s->img_buffer < s->img_buffer_end)
+      return *s->img_buffer++;
+   if (s->read_from_callbacks) {
+      stbi__refill_buffer(s);
+      return *s->img_buffer++;
+   }
+   return 0;
+}
+
+stbi_inline static int stbi__at_eof(stbi__context *s)
+{
+   if (s->io.read) {
+      if (!(s->io.eof)(s->io_user_data)) return 0;
+      // if feof() is true, check if buffer = end
+      // special case: we've only got the special 0 character at the end
+      if (s->read_from_callbacks == 0) return 1;
+   }
+
+   return s->img_buffer >= s->img_buffer_end;
+}
+
+static void stbi__skip(stbi__context *s, int n)
+{
+   if (n < 0) {
+      s->img_buffer = s->img_buffer_end;
+      return;
+   }
+   if (s->io.read) {
+      int blen = (int) (s->img_buffer_end - s->img_buffer);
+      if (blen < n) {
+         s->img_buffer = s->img_buffer_end;
+         (s->io.skip)(s->io_user_data, n - blen);
+         return;
+      }
+   }
+   s->img_buffer += n;
+}
+
+static int stbi__getn(stbi__context *s, stbi_uc *buffer, int n)
+{
+   if (s->io.read) {
+      int blen = (int) (s->img_buffer_end - s->img_buffer);
+      if (blen < n) {
+         int res, count;
+
+         memcpy(buffer, s->img_buffer, blen);
+
+         count = (s->io.read)(s->io_user_data, (char*) buffer + blen, n - blen);
+         res = (count == (n-blen));
+         s->img_buffer = s->img_buffer_end;
+         return res;
+      }
+   }
+
+   if (s->img_buffer+n <= s->img_buffer_end) {
+      memcpy(buffer, s->img_buffer, n);
+      s->img_buffer += n;
+      return 1;
+   } else
+      return 0;
+}
+
+static int stbi__get16be(stbi__context *s)
+{
+   int z = stbi__get8(s);
+   return (z << 8) + stbi__get8(s);
+}
+
+static stbi__uint32 stbi__get32be(stbi__context *s)
+{
+   stbi__uint32 z = stbi__get16be(s);
+   return (z << 16) + stbi__get16be(s);
+}
+
+#if defined(STBI_NO_BMP) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF)
+// nothing
+#else
+static int stbi__get16le(stbi__context *s)
+{
+   int z = stbi__get8(s);
+   return z + (stbi__get8(s) << 8);
+}
+#endif
+
+#ifndef STBI_NO_BMP
+static stbi__uint32 stbi__get32le(stbi__context *s)
+{
+   stbi__uint32 z = stbi__get16le(s);
+   return z + (stbi__get16le(s) << 16);
+}
+#endif
+
+#define STBI__BYTECAST(x)  ((stbi_uc) ((x) & 255))  // truncate int to byte without warnings
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+//  generic converter from built-in img_n to req_comp
+//    individual types do this automatically as much as possible (e.g. jpeg
+//    does all cases internally since it needs to colorspace convert anyway,
+//    and it never has alpha, so very few cases ). png can automatically
+//    interleave an alpha=255 channel, but falls back to this for other cases
+//
+//  assume data buffer is malloced, so malloc a new one and free that one
+//  only failure mode is malloc failing
+
+static stbi_uc stbi__compute_y(int r, int g, int b)
+{
+   return (stbi_uc) (((r*77) + (g*150) +  (29*b)) >> 8);
+}
+
+static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y)
+{
+   int i,j;
+   unsigned char *good;
+
+   if (req_comp == img_n) return data;
+   STBI_ASSERT(req_comp >= 1 && req_comp <= 4);
+
+   good = (unsigned char *) stbi__malloc_mad3(req_comp, x, y, 0);
+   if (good == NULL) {
+      STBI_FREE(data);
+      return stbi__errpuc("outofmem", "Out of memory");
+   }
+
+   for (j=0; j < (int) y; ++j) {
+      unsigned char *src  = data + j * x * img_n   ;
+      unsigned char *dest = good + j * x * req_comp;
+
+      #define STBI__COMBO(a,b)  ((a)*8+(b))
+      #define STBI__CASE(a,b)   case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b)
+      // convert source image with img_n components to one with req_comp components;
+      // avoid switch per pixel, so use switch per scanline and massive macros
+      switch (STBI__COMBO(img_n, req_comp)) {
+         STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=255;                                     } break;
+         STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0];                                  } break;
+         STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=255;                     } break;
+         STBI__CASE(2,1) { dest[0]=src[0];                                                  } break;
+         STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0];                                  } break;
+         STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1];                  } break;
+         STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=255;        } break;
+         STBI__CASE(3,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]);                   } break;
+         STBI__CASE(3,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = 255;    } break;
+         STBI__CASE(4,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]);                   } break;
+         STBI__CASE(4,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = src[3]; } break;
+         STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];                    } break;
+         default: STBI_ASSERT(0);
+      }
+      #undef STBI__CASE
+   }
+
+   STBI_FREE(data);
+   return good;
+}
+
+static stbi__uint16 stbi__compute_y_16(int r, int g, int b)
+{
+   return (stbi__uint16) (((r*77) + (g*150) +  (29*b)) >> 8);
+}
+
+static stbi__uint16 *stbi__convert_format16(stbi__uint16 *data, int img_n, int req_comp, unsigned int x, unsigned int y)
+{
+   int i,j;
+   stbi__uint16 *good;
+
+   if (req_comp == img_n) return data;
+   STBI_ASSERT(req_comp >= 1 && req_comp <= 4);
+
+   good = (stbi__uint16 *) stbi__malloc(req_comp * x * y * 2);
+   if (good == NULL) {
+      STBI_FREE(data);
+      return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory");
+   }
+
+   for (j=0; j < (int) y; ++j) {
+      stbi__uint16 *src  = data + j * x * img_n   ;
+      stbi__uint16 *dest = good + j * x * req_comp;
+
+      #define STBI__COMBO(a,b)  ((a)*8+(b))
+      #define STBI__CASE(a,b)   case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b)
+      // convert source image with img_n components to one with req_comp components;
+      // avoid switch per pixel, so use switch per scanline and massive macros
+      switch (STBI__COMBO(img_n, req_comp)) {
+         STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=0xffff;                                     } break;
+         STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0];                                     } break;
+         STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=0xffff;                     } break;
+         STBI__CASE(2,1) { dest[0]=src[0];                                                     } break;
+         STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0];                                     } break;
+         STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1];                     } break;
+         STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=0xffff;        } break;
+         STBI__CASE(3,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]);                   } break;
+         STBI__CASE(3,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = 0xffff; } break;
+         STBI__CASE(4,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]);                   } break;
+         STBI__CASE(4,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = src[3]; } break;
+         STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];                       } break;
+         default: STBI_ASSERT(0);
+      }
+      #undef STBI__CASE
+   }
+
+   STBI_FREE(data);
+   return good;
+}
+
+#ifndef STBI_NO_LINEAR
+static float   *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp)
+{
+   int i,k,n;
+   float *output;
+   if (!data) return NULL;
+   output = (float *) stbi__malloc_mad4(x, y, comp, sizeof(float), 0);
+   if (output == NULL) { STBI_FREE(data); return stbi__errpf("outofmem", "Out of memory"); }
+   // compute number of non-alpha components
+   if (comp & 1) n = comp; else n = comp-1;
+   for (i=0; i < x*y; ++i) {
+      for (k=0; k < n; ++k) {
+         output[i*comp + k] = (float) (pow(data[i*comp+k]/255.0f, stbi__l2h_gamma) * stbi__l2h_scale);
+      }
+   }
+   if (n < comp) {
+      for (i=0; i < x*y; ++i) {
+         output[i*comp + n] = data[i*comp + n]/255.0f;
+      }
+   }
+   STBI_FREE(data);
+   return output;
+}
+#endif
+
+#ifndef STBI_NO_HDR
+#define stbi__float2int(x)   ((int) (x))
+static stbi_uc *stbi__hdr_to_ldr(float   *data, int x, int y, int comp)
+{
+   int i,k,n;
+   stbi_uc *output;
+   if (!data) return NULL;
+   output = (stbi_uc *) stbi__malloc_mad3(x, y, comp, 0);
+   if (output == NULL) { STBI_FREE(data); return stbi__errpuc("outofmem", "Out of memory"); }
+   // compute number of non-alpha components
+   if (comp & 1) n = comp; else n = comp-1;
+   for (i=0; i < x*y; ++i) {
+      for (k=0; k < n; ++k) {
+         float z = (float) pow(data[i*comp+k]*stbi__h2l_scale_i, stbi__h2l_gamma_i) * 255 + 0.5f;
+         if (z < 0) z = 0;
+         if (z > 255) z = 255;
+         output[i*comp + k] = (stbi_uc) stbi__float2int(z);
+      }
+      if (k < comp) {
+         float z = data[i*comp+k] * 255 + 0.5f;
+         if (z < 0) z = 0;
+         if (z > 255) z = 255;
+         output[i*comp + k] = (stbi_uc) stbi__float2int(z);
+      }
+   }
+   STBI_FREE(data);
+   return output;
+}
+#endif
+
+//////////////////////////////////////////////////////////////////////////////
+//
+//  "baseline" JPEG/JFIF decoder
+//
+//    simple implementation
+//      - doesn't support delayed output of y-dimension
+//      - simple interface (only one output format: 8-bit interleaved RGB)
+//      - doesn't try to recover corrupt jpegs
+//      - doesn't allow partial loading, loading multiple at once
+//      - still fast on x86 (copying globals into locals doesn't help x86)
+//      - allocates lots of intermediate memory (full size of all components)
+//        - non-interleaved case requires this anyway
+//        - allows good upsampling (see next)
+//    high-quality
+//      - upsampled channels are bilinearly interpolated, even across blocks
+//      - quality integer IDCT derived from IJG's 'slow'
+//    performance
+//      - fast huffman; reasonable integer IDCT
+//      - some SIMD kernels for common paths on targets with SSE2/NEON
+//      - uses a lot of intermediate memory, could cache poorly
+
+#ifndef STBI_NO_JPEG
+
+// huffman decoding acceleration
+#define FAST_BITS   9  // larger handles more cases; smaller stomps less cache
+
+typedef struct
+{
+   stbi_uc  fast[1 << FAST_BITS];
+   // weirdly, repacking this into AoS is a 10% speed loss, instead of a win
+   stbi__uint16 code[256];
+   stbi_uc  values[256];
+   stbi_uc  size[257];
+   unsigned int maxcode[18];
+   int    delta[17];   // old 'firstsymbol' - old 'firstcode'
+} stbi__huffman;
+
+typedef struct
+{
+   stbi__context *s;
+   stbi__huffman huff_dc[4];
+   stbi__huffman huff_ac[4];
+   stbi__uint16 dequant[4][64];
+   stbi__int16 fast_ac[4][1 << FAST_BITS];
+
+// sizes for components, interleaved MCUs
+   int img_h_max, img_v_max;
+   int img_mcu_x, img_mcu_y;
+   int img_mcu_w, img_mcu_h;
+
+// definition of jpeg image component
+   struct
+   {
+      int id;
+      int h,v;
+      int tq;
+      int hd,ha;
+      int dc_pred;
+
+      int x,y,w2,h2;
+      stbi_uc *data;
+      void *raw_data, *raw_coeff;
+      stbi_uc *linebuf;
+      short   *coeff;   // progressive only
+      int      coeff_w, coeff_h; // number of 8x8 coefficient blocks
+   } img_comp[4];
+
+   stbi__uint32   code_buffer; // jpeg entropy-coded buffer
+   int            code_bits;   // number of valid bits
+   unsigned char  marker;      // marker seen while filling entropy buffer
+   int            nomore;      // flag if we saw a marker so must stop
+
+   int            progressive;
+   int            spec_start;
+   int            spec_end;
+   int            succ_high;
+   int            succ_low;
+   int            eob_run;
+   int            jfif;
+   int            app14_color_transform; // Adobe APP14 tag
+   int            rgb;
+
+   int scan_n, order[4];
+   int restart_interval, todo;
+
+// kernels
+   void (*idct_block_kernel)(stbi_uc *out, int out_stride, short data[64]);
+   void (*YCbCr_to_RGB_kernel)(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step);
+   stbi_uc *(*resample_row_hv_2_kernel)(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs);
+} stbi__jpeg;
+
+static int stbi__build_huffman(stbi__huffman *h, int *count)
+{
+   int i,j,k=0;
+   unsigned int code;
+   // build size list for each symbol (from JPEG spec)
+   for (i=0; i < 16; ++i)
+      for (j=0; j < count[i]; ++j)
+         h->size[k++] = (stbi_uc) (i+1);
+   h->size[k] = 0;
+
+   // compute actual symbols (from jpeg spec)
+   code = 0;
+   k = 0;
+   for(j=1; j <= 16; ++j) {
+      // compute delta to add to code to compute symbol id
+      h->delta[j] = k - code;
+      if (h->size[k] == j) {
+         while (h->size[k] == j)
+            h->code[k++] = (stbi__uint16) (code++);
+         if (code-1 >= (1u << j)) return stbi__err("bad code lengths","Corrupt JPEG");
+      }
+      // compute largest code + 1 for this size, preshifted as needed later
+      h->maxcode[j] = code << (16-j);
+      code <<= 1;
+   }
+   h->maxcode[j] = 0xffffffff;
+
+   // build non-spec acceleration table; 255 is flag for not-accelerated
+   memset(h->fast, 255, 1 << FAST_BITS);
+   for (i=0; i < k; ++i) {
+      int s = h->size[i];
+      if (s <= FAST_BITS) {
+         int c = h->code[i] << (FAST_BITS-s);
+         int m = 1 << (FAST_BITS-s);
+         for (j=0; j < m; ++j) {
+            h->fast[c+j] = (stbi_uc) i;
+         }
+      }
+   }
+   return 1;
+}
+
+// build a table that decodes both magnitude and value of small ACs in
+// one go.
+static void stbi__build_fast_ac(stbi__int16 *fast_ac, stbi__huffman *h)
+{
+   int i;
+   for (i=0; i < (1 << FAST_BITS); ++i) {
+      stbi_uc fast = h->fast[i];
+      fast_ac[i] = 0;
+      if (fast < 255) {
+         int rs = h->values[fast];
+         int run = (rs >> 4) & 15;
+         int magbits = rs & 15;
+         int len = h->size[fast];
+
+         if (magbits && len + magbits <= FAST_BITS) {
+            // magnitude code followed by receive_extend code
+            int k = ((i << len) & ((1 << FAST_BITS) - 1)) >> (FAST_BITS - magbits);
+            int m = 1 << (magbits - 1);
+            if (k < m) k += (~0U << magbits) + 1;
+            // if the result is small enough, we can fit it in fast_ac table
+            if (k >= -128 && k <= 127)
+               fast_ac[i] = (stbi__int16) ((k * 256) + (run * 16) + (len + magbits));
+         }
+      }
+   }
+}
+
+static void stbi__grow_buffer_unsafe(stbi__jpeg *j)
+{
+   do {
+      unsigned int b = j->nomore ? 0 : stbi__get8(j->s);
+      if (b == 0xff) {
+         int c = stbi__get8(j->s);
+         while (c == 0xff) c = stbi__get8(j->s); // consume fill bytes
+         if (c != 0) {
+            j->marker = (unsigned char) c;
+            j->nomore = 1;
+            return;
+         }
+      }
+      j->code_buffer |= b << (24 - j->code_bits);
+      j->code_bits += 8;
+   } while (j->code_bits <= 24);
+}
+
+// (1 << n) - 1
+static const stbi__uint32 stbi__bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535};
+
+// decode a jpeg huffman value from the bitstream
+stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h)
+{
+   unsigned int temp;
+   int c,k;
+
+   if (j->code_bits < 16) stbi__grow_buffer_unsafe(j);
+
+   // look at the top FAST_BITS and determine what symbol ID it is,
+   // if the code is <= FAST_BITS
+   c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1);
+   k = h->fast[c];
+   if (k < 255) {
+      int s = h->size[k];
+      if (s > j->code_bits)
+         return -1;
+      j->code_buffer <<= s;
+      j->code_bits -= s;
+      return h->values[k];
+   }
+
+   // naive test is to shift the code_buffer down so k bits are
+   // valid, then test against maxcode. To speed this up, we've
+   // preshifted maxcode left so that it has (16-k) 0s at the
+   // end; in other words, regardless of the number of bits, it
+   // wants to be compared against something shifted to have 16;
+   // that way we don't need to shift inside the loop.
+   temp = j->code_buffer >> 16;
+   for (k=FAST_BITS+1 ; ; ++k)
+      if (temp < h->maxcode[k])
+         break;
+   if (k == 17) {
+      // error! code not found
+      j->code_bits -= 16;
+      return -1;
+   }
+
+   if (k > j->code_bits)
+      return -1;
+
+   // convert the huffman code to the symbol id
+   c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k];
+   STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]);
+
+   // convert the id to a symbol
+   j->code_bits -= k;
+   j->code_buffer <<= k;
+   return h->values[c];
+}
+
+// bias[n] = (-1<<n) + 1
+static const int stbi__jbias[16] = {0,-1,-3,-7,-15,-31,-63,-127,-255,-511,-1023,-2047,-4095,-8191,-16383,-32767};
+
+// combined JPEG 'receive' and JPEG 'extend', since baseline
+// always extends everything it receives.
+stbi_inline static int stbi__extend_receive(stbi__jpeg *j, int n)
+{
+   unsigned int k;
+   int sgn;
+   if (j->code_bits < n) stbi__grow_buffer_unsafe(j);
+
+   sgn = (stbi__int32)j->code_buffer >> 31; // sign bit is always in MSB
+   k = stbi_lrot(j->code_buffer, n);
+   STBI_ASSERT(n >= 0 && n < (int) (sizeof(stbi__bmask)/sizeof(*stbi__bmask)));
+   j->code_buffer = k & ~stbi__bmask[n];
+   k &= stbi__bmask[n];
+   j->code_bits -= n;
+   return k + (stbi__jbias[n] & ~sgn);
+}
+
+// get some unsigned bits
+stbi_inline static int stbi__jpeg_get_bits(stbi__jpeg *j, int n)
+{
+   unsigned int k;
+   if (j->code_bits < n) stbi__grow_buffer_unsafe(j);
+   k = stbi_lrot(j->code_buffer, n);
+   j->code_buffer = k & ~stbi__bmask[n];
+   k &= stbi__bmask[n];
+   j->code_bits -= n;
+   return k;
+}
+
+stbi_inline static int stbi__jpeg_get_bit(stbi__jpeg *j)
+{
+   unsigned int k;
+   if (j->code_bits < 1) stbi__grow_buffer_unsafe(j);
+   k = j->code_buffer;
+   j->code_buffer <<= 1;
+   --j->code_bits;
+   return k & 0x80000000;
+}
+
+// given a value that's at position X in the zigzag stream,
+// where does it appear in the 8x8 matrix coded as row-major?
+static const stbi_uc stbi__jpeg_dezigzag[64+15] =
+{
+    0,  1,  8, 16,  9,  2,  3, 10,
+   17, 24, 32, 25, 18, 11,  4,  5,
+   12, 19, 26, 33, 40, 48, 41, 34,
+   27, 20, 13,  6,  7, 14, 21, 28,
+   35, 42, 49, 56, 57, 50, 43, 36,
+   29, 22, 15, 23, 30, 37, 44, 51,
+   58, 59, 52, 45, 38, 31, 39, 46,
+   53, 60, 61, 54, 47, 55, 62, 63,
+   // let corrupt input sample past end
+   63, 63, 63, 63, 63, 63, 63, 63,
+   63, 63, 63, 63, 63, 63, 63
+};
+
+// decode one 64-entry block--
+static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman *hdc, stbi__huffman *hac, stbi__int16 *fac, int b, stbi__uint16 *dequant)
+{
+   int diff,dc,k;
+   int t;
+
+   if (j->code_bits < 16) stbi__grow_buffer_unsafe(j);
+   t = stbi__jpeg_huff_decode(j, hdc);
+   if (t < 0) return stbi__err("bad huffman code","Corrupt JPEG");
+
+   // 0 all the ac values now so we can do it 32-bits at a time
+   memset(data,0,64*sizeof(data[0]));
+
+   diff = t ? stbi__extend_receive(j, t) : 0;
+   dc = j->img_comp[b].dc_pred + diff;
+   j->img_comp[b].dc_pred = dc;
+   data[0] = (short) (dc * dequant[0]);
+
+   // decode AC components, see JPEG spec
+   k = 1;
+   do {
+      unsigned int zig;
+      int c,r,s;
+      if (j->code_bits < 16) stbi__grow_buffer_unsafe(j);
+      c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1);
+      r = fac[c];
+      if (r) { // fast-AC path
+         k += (r >> 4) & 15; // run
+         s = r & 15; // combined length
+         j->code_buffer <<= s;
+         j->code_bits -= s;
+         // decode into unzigzag'd location
+         zig = stbi__jpeg_dezigzag[k++];
+         data[zig] = (short) ((r >> 8) * dequant[zig]);
+      } else {
+         int rs = stbi__jpeg_huff_decode(j, hac);
+         if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG");
+         s = rs & 15;
+         r = rs >> 4;
+         if (s == 0) {
+            if (rs != 0xf0) break; // end block
+            k += 16;
+         } else {
+            k += r;
+            // decode into unzigzag'd location
+            zig = stbi__jpeg_dezigzag[k++];
+            data[zig] = (short) (stbi__extend_receive(j,s) * dequant[zig]);
+         }
+      }
+   } while (k < 64);
+   return 1;
+}
+
+static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__huffman *hdc, int b)
+{
+   int diff,dc;
+   int t;
+   if (j->spec_end != 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG");
+
+   if (j->code_bits < 16) stbi__grow_buffer_unsafe(j);
+
+   if (j->succ_high == 0) {
+      // first scan for DC coefficient, must be first
+      memset(data,0,64*sizeof(data[0])); // 0 all the ac values now
+      t = stbi__jpeg_huff_decode(j, hdc);
+      diff = t ? stbi__extend_receive(j, t) : 0;
+
+      dc = j->img_comp[b].dc_pred + diff;
+      j->img_comp[b].dc_pred = dc;
+      data[0] = (short) (dc << j->succ_low);
+   } else {
+      // refinement scan for DC coefficient
+      if (stbi__jpeg_get_bit(j))
+         data[0] += (short) (1 << j->succ_low);
+   }
+   return 1;
+}
+
+// @OPTIMIZE: store non-zigzagged during the decode passes,
+// and only de-zigzag when dequantizing
+static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__huffman *hac, stbi__int16 *fac)
+{
+   int k;
+   if (j->spec_start == 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG");
+
+   if (j->succ_high == 0) {
+      int shift = j->succ_low;
+
+      if (j->eob_run) {
+         --j->eob_run;
+         return 1;
+      }
+
+      k = j->spec_start;
+      do {
+         unsigned int zig;
+         int c,r,s;
+         if (j->code_bits < 16) stbi__grow_buffer_unsafe(j);
+         c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1);
+         r = fac[c];
+         if (r) { // fast-AC path
+            k += (r >> 4) & 15; // run
+            s = r & 15; // combined length
+            j->code_buffer <<= s;
+            j->code_bits -= s;
+            zig = stbi__jpeg_dezigzag[k++];
+            data[zig] = (short) ((r >> 8) << shift);
+         } else {
+            int rs = stbi__jpeg_huff_decode(j, hac);
+            if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG");
+            s = rs & 15;
+            r = rs >> 4;
+            if (s == 0) {
+               if (r < 15) {
+                  j->eob_run = (1 << r);
+                  if (r)
+                     j->eob_run += stbi__jpeg_get_bits(j, r);
+                  --j->eob_run;
+                  break;
+               }
+               k += 16;
+            } else {
+               k += r;
+               zig = stbi__jpeg_dezigzag[k++];
+               data[zig] = (short) (stbi__extend_receive(j,s) << shift);
+            }
+         }
+      } while (k <= j->spec_end);
+   } else {
+      // refinement scan for these AC coefficients
+
+      short bit = (short) (1 << j->succ_low);
+
+      if (j->eob_run) {
+         --j->eob_run;
+         for (k = j->spec_start; k <= j->spec_end; ++k) {
+            short *p = &data[stbi__jpeg_dezigzag[k]];
+            if (*p != 0)
+               if (stbi__jpeg_get_bit(j))
+                  if ((*p & bit)==0) {
+                     if (*p > 0)
+                        *p += bit;
+                     else
+                        *p -= bit;
+                  }
+         }
+      } else {
+         k = j->spec_start;
+         do {
+            int r,s;
+            int rs = stbi__jpeg_huff_decode(j, hac); // @OPTIMIZE see if we can use the fast path here, advance-by-r is so slow, eh
+            if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG");
+            s = rs & 15;
+            r = rs >> 4;
+            if (s == 0) {
+               if (r < 15) {
+                  j->eob_run = (1 << r) - 1;
+                  if (r)
+                     j->eob_run += stbi__jpeg_get_bits(j, r);
+                  r = 64; // force end of block
+               } else {
+                  // r=15 s=0 should write 16 0s, so we just do
+                  // a run of 15 0s and then write s (which is 0),
+                  // so we don't have to do anything special here
+               }
+            } else {
+               if (s != 1) return stbi__err("bad huffman code", "Corrupt JPEG");
+               // sign bit
+               if (stbi__jpeg_get_bit(j))
+                  s = bit;
+               else
+                  s = -bit;
+            }
+
+            // advance by r
+            while (k <= j->spec_end) {
+               short *p = &data[stbi__jpeg_dezigzag[k++]];
+               if (*p != 0) {
+                  if (stbi__jpeg_get_bit(j))
+                     if ((*p & bit)==0) {
+                        if (*p > 0)
+                           *p += bit;
+                        else
+                           *p -= bit;
+                     }
+               } else {
+                  if (r == 0) {
+                     *p = (short) s;
+                     break;
+                  }
+                  --r;
+               }
+            }
+         } while (k <= j->spec_end);
+      }
+   }
+   return 1;
+}
+
+// take a -128..127 value and stbi__clamp it and convert to 0..255
+stbi_inline static stbi_uc stbi__clamp(int x)
+{
+   // trick to use a single test to catch both cases
+   if ((unsigned int) x > 255) {
+      if (x < 0) return 0;
+      if (x > 255) return 255;
+   }
+   return (stbi_uc) x;
+}
+
+#define stbi__f2f(x)  ((int) (((x) * 4096 + 0.5)))
+#define stbi__fsh(x)  ((x) * 4096)
+
+// derived from jidctint -- DCT_ISLOW
+#define STBI__IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \
+   int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \
+   p2 = s2;                                    \
+   p3 = s6;                                    \
+   p1 = (p2+p3) * stbi__f2f(0.5411961f);       \
+   t2 = p1 + p3*stbi__f2f(-1.847759065f);      \
+   t3 = p1 + p2*stbi__f2f( 0.765366865f);      \
+   p2 = s0;                                    \
+   p3 = s4;                                    \
+   t0 = stbi__fsh(p2+p3);                      \
+   t1 = stbi__fsh(p2-p3);                      \
+   x0 = t0+t3;                                 \
+   x3 = t0-t3;                                 \
+   x1 = t1+t2;                                 \
+   x2 = t1-t2;                                 \
+   t0 = s7;                                    \
+   t1 = s5;                                    \
+   t2 = s3;                                    \
+   t3 = s1;                                    \
+   p3 = t0+t2;                                 \
+   p4 = t1+t3;                                 \
+   p1 = t0+t3;                                 \
+   p2 = t1+t2;                                 \
+   p5 = (p3+p4)*stbi__f2f( 1.175875602f);      \
+   t0 = t0*stbi__f2f( 0.298631336f);           \
+   t1 = t1*stbi__f2f( 2.053119869f);           \
+   t2 = t2*stbi__f2f( 3.072711026f);           \
+   t3 = t3*stbi__f2f( 1.501321110f);           \
+   p1 = p5 + p1*stbi__f2f(-0.899976223f);      \
+   p2 = p5 + p2*stbi__f2f(-2.562915447f);      \
+   p3 = p3*stbi__f2f(-1.961570560f);           \
+   p4 = p4*stbi__f2f(-0.390180644f);           \
+   t3 += p1+p4;                                \
+   t2 += p2+p3;                                \
+   t1 += p2+p4;                                \
+   t0 += p1+p3;
+
+static void stbi__idct_block(stbi_uc *out, int out_stride, short data[64])
+{
+   int i,val[64],*v=val;
+   stbi_uc *o;
+   short *d = data;
+
+   // columns
+   for (i=0; i < 8; ++i,++d, ++v) {
+      // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing
+      if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0
+           && d[40]==0 && d[48]==0 && d[56]==0) {
+         //    no shortcut                 0     seconds
+         //    (1|2|3|4|5|6|7)==0          0     seconds
+         //    all separate               -0.047 seconds
+         //    1 && 2|3 && 4|5 && 6|7:    -0.047 seconds
+         int dcterm = d[0]*4;
+         v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm;
+      } else {
+         STBI__IDCT_1D(d[ 0],d[ 8],d[16],d[24],d[32],d[40],d[48],d[56])
+         // constants scaled things up by 1<<12; let's bring them back
+         // down, but keep 2 extra bits of precision
+         x0 += 512; x1 += 512; x2 += 512; x3 += 512;
+         v[ 0] = (x0+t3) >> 10;
+         v[56] = (x0-t3) >> 10;
+         v[ 8] = (x1+t2) >> 10;
+         v[48] = (x1-t2) >> 10;
+         v[16] = (x2+t1) >> 10;
+         v[40] = (x2-t1) >> 10;
+         v[24] = (x3+t0) >> 10;
+         v[32] = (x3-t0) >> 10;
+      }
+   }
+
+   for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) {
+      // no fast case since the first 1D IDCT spread components out
+      STBI__IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7])
+      // constants scaled things up by 1<<12, plus we had 1<<2 from first
+      // loop, plus horizontal and vertical each scale by sqrt(8) so together
+      // we've got an extra 1<<3, so 1<<17 total we need to remove.
+      // so we want to round that, which means adding 0.5 * 1<<17,
+      // aka 65536. Also, we'll end up with -128 to 127 that we want
+      // to encode as 0..255 by adding 128, so we'll add that before the shift
+      x0 += 65536 + (128<<17);
+      x1 += 65536 + (128<<17);
+      x2 += 65536 + (128<<17);
+      x3 += 65536 + (128<<17);
+      // tried computing the shifts into temps, or'ing the temps to see
+      // if any were out of range, but that was slower
+      o[0] = stbi__clamp((x0+t3) >> 17);
+      o[7] = stbi__clamp((x0-t3) >> 17);
+      o[1] = stbi__clamp((x1+t2) >> 17);
+      o[6] = stbi__clamp((x1-t2) >> 17);
+      o[2] = stbi__clamp((x2+t1) >> 17);
+      o[5] = stbi__clamp((x2-t1) >> 17);
+      o[3] = stbi__clamp((x3+t0) >> 17);
+      o[4] = stbi__clamp((x3-t0) >> 17);
+   }
+}
+
+#ifdef STBI_SSE2
+// sse2 integer IDCT. not the fastest possible implementation but it
+// produces bit-identical results to the generic C version so it's
+// fully "transparent".
+static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64])
+{
+   // This is constructed to match our regular (generic) integer IDCT exactly.
+   __m128i row0, row1, row2, row3, row4, row5, row6, row7;
+   __m128i tmp;
+
+   // dot product constant: even elems=x, odd elems=y
+   #define dct_const(x,y)  _mm_setr_epi16((x),(y),(x),(y),(x),(y),(x),(y))
+
+   // out(0) = c0[even]*x + c0[odd]*y   (c0, x, y 16-bit, out 32-bit)
+   // out(1) = c1[even]*x + c1[odd]*y
+   #define dct_rot(out0,out1, x,y,c0,c1) \
+      __m128i c0##lo = _mm_unpacklo_epi16((x),(y)); \
+      __m128i c0##hi = _mm_unpackhi_epi16((x),(y)); \
+      __m128i out0##_l = _mm_madd_epi16(c0##lo, c0); \
+      __m128i out0##_h = _mm_madd_epi16(c0##hi, c0); \
+      __m128i out1##_l = _mm_madd_epi16(c0##lo, c1); \
+      __m128i out1##_h = _mm_madd_epi16(c0##hi, c1)
+
+   // out = in << 12  (in 16-bit, out 32-bit)
+   #define dct_widen(out, in) \
+      __m128i out##_l = _mm_srai_epi32(_mm_unpacklo_epi16(_mm_setzero_si128(), (in)), 4); \
+      __m128i out##_h = _mm_srai_epi32(_mm_unpackhi_epi16(_mm_setzero_si128(), (in)), 4)
+
+   // wide add
+   #define dct_wadd(out, a, b) \
+      __m128i out##_l = _mm_add_epi32(a##_l, b##_l); \
+      __m128i out##_h = _mm_add_epi32(a##_h, b##_h)
+
+   // wide sub
+   #define dct_wsub(out, a, b) \
+      __m128i out##_l = _mm_sub_epi32(a##_l, b##_l); \
+      __m128i out##_h = _mm_sub_epi32(a##_h, b##_h)
+
+   // butterfly a/b, add bias, then shift by "s" and pack
+   #define dct_bfly32o(out0, out1, a,b,bias,s) \
+      { \
+         __m128i abiased_l = _mm_add_epi32(a##_l, bias); \
+         __m128i abiased_h = _mm_add_epi32(a##_h, bias); \
+         dct_wadd(sum, abiased, b); \
+         dct_wsub(dif, abiased, b); \
+         out0 = _mm_packs_epi32(_mm_srai_epi32(sum_l, s), _mm_srai_epi32(sum_h, s)); \
+         out1 = _mm_packs_epi32(_mm_srai_epi32(dif_l, s), _mm_srai_epi32(dif_h, s)); \
+      }
+
+   // 8-bit interleave step (for transposes)
+   #define dct_interleave8(a, b) \
+      tmp = a; \
+      a = _mm_unpacklo_epi8(a, b); \
+      b = _mm_unpackhi_epi8(tmp, b)
+
+   // 16-bit interleave step (for transposes)
+   #define dct_interleave16(a, b) \
+      tmp = a; \
+      a = _mm_unpacklo_epi16(a, b); \
+      b = _mm_unpackhi_epi16(tmp, b)
+
+   #define dct_pass(bias,shift) \
+      { \
+         /* even part */ \
+         dct_rot(t2e,t3e, row2,row6, rot0_0,rot0_1); \
+         __m128i sum04 = _mm_add_epi16(row0, row4); \
+         __m128i dif04 = _mm_sub_epi16(row0, row4); \
+         dct_widen(t0e, sum04); \
+         dct_widen(t1e, dif04); \
+         dct_wadd(x0, t0e, t3e); \
+         dct_wsub(x3, t0e, t3e); \
+         dct_wadd(x1, t1e, t2e); \
+         dct_wsub(x2, t1e, t2e); \
+         /* odd part */ \
+         dct_rot(y0o,y2o, row7,row3, rot2_0,rot2_1); \
+         dct_rot(y1o,y3o, row5,row1, rot3_0,rot3_1); \
+         __m128i sum17 = _mm_add_epi16(row1, row7); \
+         __m128i sum35 = _mm_add_epi16(row3, row5); \
+         dct_rot(y4o,y5o, sum17,sum35, rot1_0,rot1_1); \
+         dct_wadd(x4, y0o, y4o); \
+         dct_wadd(x5, y1o, y5o); \
+         dct_wadd(x6, y2o, y5o); \
+         dct_wadd(x7, y3o, y4o); \
+         dct_bfly32o(row0,row7, x0,x7,bias,shift); \
+         dct_bfly32o(row1,row6, x1,x6,bias,shift); \
+         dct_bfly32o(row2,row5, x2,x5,bias,shift); \
+         dct_bfly32o(row3,row4, x3,x4,bias,shift); \
+      }
+
+   __m128i rot0_0 = dct_const(stbi__f2f(0.5411961f), stbi__f2f(0.5411961f) + stbi__f2f(-1.847759065f));
+   __m128i rot0_1 = dct_const(stbi__f2f(0.5411961f) + stbi__f2f( 0.765366865f), stbi__f2f(0.5411961f));
+   __m128i rot1_0 = dct_const(stbi__f2f(1.175875602f) + stbi__f2f(-0.899976223f), stbi__f2f(1.175875602f));
+   __m128i rot1_1 = dct_const(stbi__f2f(1.175875602f), stbi__f2f(1.175875602f) + stbi__f2f(-2.562915447f));
+   __m128i rot2_0 = dct_const(stbi__f2f(-1.961570560f) + stbi__f2f( 0.298631336f), stbi__f2f(-1.961570560f));
+   __m128i rot2_1 = dct_const(stbi__f2f(-1.961570560f), stbi__f2f(-1.961570560f) + stbi__f2f( 3.072711026f));
+   __m128i rot3_0 = dct_const(stbi__f2f(-0.390180644f) + stbi__f2f( 2.053119869f), stbi__f2f(-0.390180644f));
+   __m128i rot3_1 = dct_const(stbi__f2f(-0.390180644f), stbi__f2f(-0.390180644f) + stbi__f2f( 1.501321110f));
+
+   // rounding biases in column/row passes, see stbi__idct_block for explanation.
+   __m128i bias_0 = _mm_set1_epi32(512);
+   __m128i bias_1 = _mm_set1_epi32(65536 + (128<<17));
+
+   // load
+   row0 = _mm_load_si128((const __m128i *) (data + 0*8));
+   row1 = _mm_load_si128((const __m128i *) (data + 1*8));
+   row2 = _mm_load_si128((const __m128i *) (data + 2*8));
+   row3 = _mm_load_si128((const __m128i *) (data + 3*8));
+   row4 = _mm_load_si128((const __m128i *) (data + 4*8));
+   row5 = _mm_load_si128((const __m128i *) (data + 5*8));
+   row6 = _mm_load_si128((const __m128i *) (data + 6*8));
+   row7 = _mm_load_si128((const __m128i *) (data + 7*8));
+
+   // column pass
+   dct_pass(bias_0, 10);
+
+   {
+      // 16bit 8x8 transpose pass 1
+      dct_interleave16(row0, row4);
+      dct_interleave16(row1, row5);
+      dct_interleave16(row2, row6);
+      dct_interleave16(row3, row7);
+
+      // transpose pass 2
+      dct_interleave16(row0, row2);
+      dct_interleave16(row1, row3);
+      dct_interleave16(row4, row6);
+      dct_interleave16(row5, row7);
+
+      // transpose pass 3
+      dct_interleave16(row0, row1);
+      dct_interleave16(row2, row3);
+      dct_interleave16(row4, row5);
+      dct_interleave16(row6, row7);
+   }
+
+   // row pass
+   dct_pass(bias_1, 17);
+
+   {
+      // pack
+      __m128i p0 = _mm_packus_epi16(row0, row1); // a0a1a2a3...a7b0b1b2b3...b7
+      __m128i p1 = _mm_packus_epi16(row2, row3);
+      __m128i p2 = _mm_packus_epi16(row4, row5);
+      __m128i p3 = _mm_packus_epi16(row6, row7);
+
+      // 8bit 8x8 transpose pass 1
+      dct_interleave8(p0, p2); // a0e0a1e1...
+      dct_interleave8(p1, p3); // c0g0c1g1...
+
+      // transpose pass 2
+      dct_interleave8(p0, p1); // a0c0e0g0...
+      dct_interleave8(p2, p3); // b0d0f0h0...
+
+      // transpose pass 3
+      dct_interleave8(p0, p2); // a0b0c0d0...
+      dct_interleave8(p1, p3); // a4b4c4d4...
+
+      // store
+      _mm_storel_epi64((__m128i *) out, p0); out += out_stride;
+      _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p0, 0x4e)); out += out_stride;
+      _mm_storel_epi64((__m128i *) out, p2); out += out_stride;
+      _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p2, 0x4e)); out += out_stride;
+      _mm_storel_epi64((__m128i *) out, p1); out += out_stride;
+      _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p1, 0x4e)); out += out_stride;
+      _mm_storel_epi64((__m128i *) out, p3); out += out_stride;
+      _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p3, 0x4e));
+   }
+
+#undef dct_const
+#undef dct_rot
+#undef dct_widen
+#undef dct_wadd
+#undef dct_wsub
+#undef dct_bfly32o
+#undef dct_interleave8
+#undef dct_interleave16
+#undef dct_pass
+}
+
+#endif // STBI_SSE2
+
+#ifdef STBI_NEON
+
+// NEON integer IDCT. should produce bit-identical
+// results to the generic C version.
+static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64])
+{
+   int16x8_t row0, row1, row2, row3, row4, row5, row6, row7;
+
+   int16x4_t rot0_0 = vdup_n_s16(stbi__f2f(0.5411961f));
+   int16x4_t rot0_1 = vdup_n_s16(stbi__f2f(-1.847759065f));
+   int16x4_t rot0_2 = vdup_n_s16(stbi__f2f( 0.765366865f));
+   int16x4_t rot1_0 = vdup_n_s16(stbi__f2f( 1.175875602f));
+   int16x4_t rot1_1 = vdup_n_s16(stbi__f2f(-0.899976223f));
+   int16x4_t rot1_2 = vdup_n_s16(stbi__f2f(-2.562915447f));
+   int16x4_t rot2_0 = vdup_n_s16(stbi__f2f(-1.961570560f));
+   int16x4_t rot2_1 = vdup_n_s16(stbi__f2f(-0.390180644f));
+   int16x4_t rot3_0 = vdup_n_s16(stbi__f2f( 0.298631336f));
+   int16x4_t rot3_1 = vdup_n_s16(stbi__f2f( 2.053119869f));
+   int16x4_t rot3_2 = vdup_n_s16(stbi__f2f( 3.072711026f));
+   int16x4_t rot3_3 = vdup_n_s16(stbi__f2f( 1.501321110f));
+
+#define dct_long_mul(out, inq, coeff) \
+   int32x4_t out##_l = vmull_s16(vget_low_s16(inq), coeff); \
+   int32x4_t out##_h = vmull_s16(vget_high_s16(inq), coeff)
+
+#define dct_long_mac(out, acc, inq, coeff) \
+   int32x4_t out##_l = vmlal_s16(acc##_l, vget_low_s16(inq), coeff); \
+   int32x4_t out##_h = vmlal_s16(acc##_h, vget_high_s16(inq), coeff)
+
+#define dct_widen(out, inq) \
+   int32x4_t out##_l = vshll_n_s16(vget_low_s16(inq), 12); \
+   int32x4_t out##_h = vshll_n_s16(vget_high_s16(inq), 12)
+
+// wide add
+#define dct_wadd(out, a, b) \
+   int32x4_t out##_l = vaddq_s32(a##_l, b##_l); \
+   int32x4_t out##_h = vaddq_s32(a##_h, b##_h)
+
+// wide sub
+#define dct_wsub(out, a, b) \
+   int32x4_t out##_l = vsubq_s32(a##_l, b##_l); \
+   int32x4_t out##_h = vsubq_s32(a##_h, b##_h)
+
+// butterfly a/b, then shift using "shiftop" by "s" and pack
+#define dct_bfly32o(out0,out1, a,b,shiftop,s) \
+   { \
+      dct_wadd(sum, a, b); \
+      dct_wsub(dif, a, b); \
+      out0 = vcombine_s16(shiftop(sum_l, s), shiftop(sum_h, s)); \
+      out1 = vcombine_s16(shiftop(dif_l, s), shiftop(dif_h, s)); \
+   }
+
+#define dct_pass(shiftop, shift) \
+   { \
+      /* even part */ \
+      int16x8_t sum26 = vaddq_s16(row2, row6); \
+      dct_long_mul(p1e, sum26, rot0_0); \
+      dct_long_mac(t2e, p1e, row6, rot0_1); \
+      dct_long_mac(t3e, p1e, row2, rot0_2); \
+      int16x8_t sum04 = vaddq_s16(row0, row4); \
+      int16x8_t dif04 = vsubq_s16(row0, row4); \
+      dct_widen(t0e, sum04); \
+      dct_widen(t1e, dif04); \
+      dct_wadd(x0, t0e, t3e); \
+      dct_wsub(x3, t0e, t3e); \
+      dct_wadd(x1, t1e, t2e); \
+      dct_wsub(x2, t1e, t2e); \
+      /* odd part */ \
+      int16x8_t sum15 = vaddq_s16(row1, row5); \
+      int16x8_t sum17 = vaddq_s16(row1, row7); \
+      int16x8_t sum35 = vaddq_s16(row3, row5); \
+      int16x8_t sum37 = vaddq_s16(row3, row7); \
+      int16x8_t sumodd = vaddq_s16(sum17, sum35); \
+      dct_long_mul(p5o, sumodd, rot1_0); \
+      dct_long_mac(p1o, p5o, sum17, rot1_1); \
+      dct_long_mac(p2o, p5o, sum35, rot1_2); \
+      dct_long_mul(p3o, sum37, rot2_0); \
+      dct_long_mul(p4o, sum15, rot2_1); \
+      dct_wadd(sump13o, p1o, p3o); \
+      dct_wadd(sump24o, p2o, p4o); \
+      dct_wadd(sump23o, p2o, p3o); \
+      dct_wadd(sump14o, p1o, p4o); \
+      dct_long_mac(x4, sump13o, row7, rot3_0); \
+      dct_long_mac(x5, sump24o, row5, rot3_1); \
+      dct_long_mac(x6, sump23o, row3, rot3_2); \
+      dct_long_mac(x7, sump14o, row1, rot3_3); \
+      dct_bfly32o(row0,row7, x0,x7,shiftop,shift); \
+      dct_bfly32o(row1,row6, x1,x6,shiftop,shift); \
+      dct_bfly32o(row2,row5, x2,x5,shiftop,shift); \
+      dct_bfly32o(row3,row4, x3,x4,shiftop,shift); \
+   }
+
+   // load
+   row0 = vld1q_s16(data + 0*8);
+   row1 = vld1q_s16(data + 1*8);
+   row2 = vld1q_s16(data + 2*8);
+   row3 = vld1q_s16(data + 3*8);
+   row4 = vld1q_s16(data + 4*8);
+   row5 = vld1q_s16(data + 5*8);
+   row6 = vld1q_s16(data + 6*8);
+   row7 = vld1q_s16(data + 7*8);
+
+   // add DC bias
+   row0 = vaddq_s16(row0, vsetq_lane_s16(1024, vdupq_n_s16(0), 0));
+
+   // column pass
+   dct_pass(vrshrn_n_s32, 10);
+
+   // 16bit 8x8 transpose
+   {
+// these three map to a single VTRN.16, VTRN.32, and VSWP, respectively.
+// whether compilers actually get this is another story, sadly.
+#define dct_trn16(x, y) { int16x8x2_t t = vtrnq_s16(x, y); x = t.val[0]; y = t.val[1]; }
+#define dct_trn32(x, y) { int32x4x2_t t = vtrnq_s32(vreinterpretq_s32_s16(x), vreinterpretq_s32_s16(y)); x = vreinterpretq_s16_s32(t.val[0]); y = vreinterpretq_s16_s32(t.val[1]); }
+#define dct_trn64(x, y) { int16x8_t x0 = x; int16x8_t y0 = y; x = vcombine_s16(vget_low_s16(x0), vget_low_s16(y0)); y = vcombine_s16(vget_high_s16(x0), vget_high_s16(y0)); }
+
+      // pass 1
+      dct_trn16(row0, row1); // a0b0a2b2a4b4a6b6
+      dct_trn16(row2, row3);
+      dct_trn16(row4, row5);
+      dct_trn16(row6, row7);
+
+      // pass 2
+      dct_trn32(row0, row2); // a0b0c0d0a4b4c4d4
+      dct_trn32(row1, row3);
+      dct_trn32(row4, row6);
+      dct_trn32(row5, row7);
+
+      // pass 3
+      dct_trn64(row0, row4); // a0b0c0d0e0f0g0h0
+      dct_trn64(row1, row5);
+      dct_trn64(row2, row6);
+      dct_trn64(row3, row7);
+
+#undef dct_trn16
+#undef dct_trn32
+#undef dct_trn64
+   }
+
+   // row pass
+   // vrshrn_n_s32 only supports shifts up to 16, we need
+   // 17. so do a non-rounding shift of 16 first then follow
+   // up with a rounding shift by 1.
+   dct_pass(vshrn_n_s32, 16);
+
+   {
+      // pack and round
+      uint8x8_t p0 = vqrshrun_n_s16(row0, 1);
+      uint8x8_t p1 = vqrshrun_n_s16(row1, 1);
+      uint8x8_t p2 = vqrshrun_n_s16(row2, 1);
+      uint8x8_t p3 = vqrshrun_n_s16(row3, 1);
+      uint8x8_t p4 = vqrshrun_n_s16(row4, 1);
+      uint8x8_t p5 = vqrshrun_n_s16(row5, 1);
+      uint8x8_t p6 = vqrshrun_n_s16(row6, 1);
+      uint8x8_t p7 = vqrshrun_n_s16(row7, 1);
+
+      // again, these can translate into one instruction, but often don't.
+#define dct_trn8_8(x, y) { uint8x8x2_t t = vtrn_u8(x, y); x = t.val[0]; y = t.val[1]; }
+#define dct_trn8_16(x, y) { uint16x4x2_t t = vtrn_u16(vreinterpret_u16_u8(x), vreinterpret_u16_u8(y)); x = vreinterpret_u8_u16(t.val[0]); y = vreinterpret_u8_u16(t.val[1]); }
+#define dct_trn8_32(x, y) { uint32x2x2_t t = vtrn_u32(vreinterpret_u32_u8(x), vreinterpret_u32_u8(y)); x = vreinterpret_u8_u32(t.val[0]); y = vreinterpret_u8_u32(t.val[1]); }
+
+      // sadly can't use interleaved stores here since we only write
+      // 8 bytes to each scan line!
+
+      // 8x8 8-bit transpose pass 1
+      dct_trn8_8(p0, p1);
+      dct_trn8_8(p2, p3);
+      dct_trn8_8(p4, p5);
+      dct_trn8_8(p6, p7);
+
+      // pass 2
+      dct_trn8_16(p0, p2);
+      dct_trn8_16(p1, p3);
+      dct_trn8_16(p4, p6);
+      dct_trn8_16(p5, p7);
+
+      // pass 3
+      dct_trn8_32(p0, p4);
+      dct_trn8_32(p1, p5);
+      dct_trn8_32(p2, p6);
+      dct_trn8_32(p3, p7);
+
+      // store
+      vst1_u8(out, p0); out += out_stride;
+      vst1_u8(out, p1); out += out_stride;
+      vst1_u8(out, p2); out += out_stride;
+      vst1_u8(out, p3); out += out_stride;
+      vst1_u8(out, p4); out += out_stride;
+      vst1_u8(out, p5); out += out_stride;
+      vst1_u8(out, p6); out += out_stride;
+      vst1_u8(out, p7);
+
+#undef dct_trn8_8
+#undef dct_trn8_16
+#undef dct_trn8_32
+   }
+
+#undef dct_long_mul
+#undef dct_long_mac
+#undef dct_widen
+#undef dct_wadd
+#undef dct_wsub
+#undef dct_bfly32o
+#undef dct_pass
+}
+
+#endif // STBI_NEON
+
+#define STBI__MARKER_none  0xff
+// if there's a pending marker from the entropy stream, return that
+// otherwise, fetch from the stream and get a marker. if there's no
+// marker, return 0xff, which is never a valid marker value
+static stbi_uc stbi__get_marker(stbi__jpeg *j)
+{
+   stbi_uc x;
+   if (j->marker != STBI__MARKER_none) { x = j->marker; j->marker = STBI__MARKER_none; return x; }
+   x = stbi__get8(j->s);
+   if (x != 0xff) return STBI__MARKER_none;
+   while (x == 0xff)
+      x = stbi__get8(j->s); // consume repeated 0xff fill bytes
+   return x;
+}
+
+// in each scan, we'll have scan_n components, and the order
+// of the components is specified by order[]
+#define STBI__RESTART(x)     ((x) >= 0xd0 && (x) <= 0xd7)
+
+// after a restart interval, stbi__jpeg_reset the entropy decoder and
+// the dc prediction
+static void stbi__jpeg_reset(stbi__jpeg *j)
+{
+   j->code_bits = 0;
+   j->code_buffer = 0;
+   j->nomore = 0;
+   j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = j->img_comp[3].dc_pred = 0;
+   j->marker = STBI__MARKER_none;
+   j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff;
+   j->eob_run = 0;
+   // no more than 1<<31 MCUs if no restart_interal? that's plenty safe,
+   // since we don't even allow 1<<30 pixels
+}
+
+static int stbi__parse_entropy_coded_data(stbi__jpeg *z)
+{
+   stbi__jpeg_reset(z);
+   if (!z->progressive) {
+      if (z->scan_n == 1) {
+         int i,j;
+         STBI_SIMD_ALIGN(short, data[64]);
+         int n = z->order[0];
+         // non-interleaved data, we just need to process one block at a time,
+         // in trivial scanline order
+         // number of blocks to do just depends on how many actual "pixels" this
+         // component has, independent of interleaved MCU blocking and such
+         int w = (z->img_comp[n].x+7) >> 3;
+         int h = (z->img_comp[n].y+7) >> 3;
+         for (j=0; j < h; ++j) {
+            for (i=0; i < w; ++i) {
+               int ha = z->img_comp[n].ha;
+               if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0;
+               z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data);
+               // every data block is an MCU, so countdown the restart interval
+               if (--z->todo <= 0) {
+                  if (z->code_bits < 24) stbi__grow_buffer_unsafe(z);
+                  // if it's NOT a restart, then just bail, so we get corrupt data
+                  // rather than no data
+                  if (!STBI__RESTART(z->marker)) return 1;
+                  stbi__jpeg_reset(z);
+               }
+            }
+         }
+         return 1;
+      } else { // interleaved
+         int i,j,k,x,y;
+         STBI_SIMD_ALIGN(short, data[64]);
+         for (j=0; j < z->img_mcu_y; ++j) {
+            for (i=0; i < z->img_mcu_x; ++i) {
+               // scan an interleaved mcu... process scan_n components in order
+               for (k=0; k < z->scan_n; ++k) {
+                  int n = z->order[k];
+                  // scan out an mcu's worth of this component; that's just determined
+                  // by the basic H and V specified for the component
+                  for (y=0; y < z->img_comp[n].v; ++y) {
+                     for (x=0; x < z->img_comp[n].h; ++x) {
+                        int x2 = (i*z->img_comp[n].h + x)*8;
+                        int y2 = (j*z->img_comp[n].v + y)*8;
+                        int ha = z->img_comp[n].ha;
+                        if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0;
+                        z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data);
+                     }
+                  }
+               }
+               // after all interleaved components, that's an interleaved MCU,
+               // so now count down the restart interval
+               if (--z->todo <= 0) {
+                  if (z->code_bits < 24) stbi__grow_buffer_unsafe(z);
+                  if (!STBI__RESTART(z->marker)) return 1;
+                  stbi__jpeg_reset(z);
+               }
+            }
+         }
+         return 1;
+      }
+   } else {
+      if (z->scan_n == 1) {
+         int i,j;
+         int n = z->order[0];
+         // non-interleaved data, we just need to process one block at a time,
+         // in trivial scanline order
+         // number of blocks to do just depends on how many actual "pixels" this
+         // component has, independent of interleaved MCU blocking and such
+         int w = (z->img_comp[n].x+7) >> 3;
+         int h = (z->img_comp[n].y+7) >> 3;
+         for (j=0; j < h; ++j) {
+            for (i=0; i < w; ++i) {
+               short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w);
+               if (z->spec_start == 0) {
+                  if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n))
+                     return 0;
+               } else {
+                  int ha = z->img_comp[n].ha;
+                  if (!stbi__jpeg_decode_block_prog_ac(z, data, &z->huff_ac[ha], z->fast_ac[ha]))
+                     return 0;
+               }
+               // every data block is an MCU, so countdown the restart interval
+               if (--z->todo <= 0) {
+                  if (z->code_bits < 24) stbi__grow_buffer_unsafe(z);
+                  if (!STBI__RESTART(z->marker)) return 1;
+                  stbi__jpeg_reset(z);
+               }
+            }
+         }
+         return 1;
+      } else { // interleaved
+         int i,j,k,x,y;
+         for (j=0; j < z->img_mcu_y; ++j) {
+            for (i=0; i < z->img_mcu_x; ++i) {
+               // scan an interleaved mcu... process scan_n components in order
+               for (k=0; k < z->scan_n; ++k) {
+                  int n = z->order[k];
+                  // scan out an mcu's worth of this component; that's just determined
+                  // by the basic H and V specified for the component
+                  for (y=0; y < z->img_comp[n].v; ++y) {
+                     for (x=0; x < z->img_comp[n].h; ++x) {
+                        int x2 = (i*z->img_comp[n].h + x);
+                        int y2 = (j*z->img_comp[n].v + y);
+                        short *data = z->img_comp[n].coeff + 64 * (x2 + y2 * z->img_comp[n].coeff_w);
+                        if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n))
+                           return 0;
+                     }
+                  }
+               }
+               // after all interleaved components, that's an interleaved MCU,
+               // so now count down the restart interval
+               if (--z->todo <= 0) {
+                  if (z->code_bits < 24) stbi__grow_buffer_unsafe(z);
+                  if (!STBI__RESTART(z->marker)) return 1;
+                  stbi__jpeg_reset(z);
+               }
+            }
+         }
+         return 1;
+      }
+   }
+}
+
+static void stbi__jpeg_dequantize(short *data, stbi__uint16 *dequant)
+{
+   int i;
+   for (i=0; i < 64; ++i)
+      data[i] *= dequant[i];
+}
+
+static void stbi__jpeg_finish(stbi__jpeg *z)
+{
+   if (z->progressive) {
+      // dequantize and idct the data
+      int i,j,n;
+      for (n=0; n < z->s->img_n; ++n) {
+         int w = (z->img_comp[n].x+7) >> 3;
+         int h = (z->img_comp[n].y+7) >> 3;
+         for (j=0; j < h; ++j) {
+            for (i=0; i < w; ++i) {
+               short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w);
+               stbi__jpeg_dequantize(data, z->dequant[z->img_comp[n].tq]);
+               z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data);
+            }
+         }
+      }
+   }
+}
+
+static int stbi__process_marker(stbi__jpeg *z, int m)
+{
+   int L;
+   switch (m) {
+      case STBI__MARKER_none: // no marker found
+         return stbi__err("expected marker","Corrupt JPEG");
+
+      case 0xDD: // DRI - specify restart interval
+         if (stbi__get16be(z->s) != 4) return stbi__err("bad DRI len","Corrupt JPEG");
+         z->restart_interval = stbi__get16be(z->s);
+         return 1;
+
+      case 0xDB: // DQT - define quantization table
+         L = stbi__get16be(z->s)-2;
+         while (L > 0) {
+            int q = stbi__get8(z->s);
+            int p = q >> 4, sixteen = (p != 0);
+            int t = q & 15,i;
+            if (p != 0 && p != 1) return stbi__err("bad DQT type","Corrupt JPEG");
+            if (t > 3) return stbi__err("bad DQT table","Corrupt JPEG");
+
+            for (i=0; i < 64; ++i)
+               z->dequant[t][stbi__jpeg_dezigzag[i]] = (stbi__uint16)(sixteen ? stbi__get16be(z->s) : stbi__get8(z->s));
+            L -= (sixteen ? 129 : 65);
+         }
+         return L==0;
+
+      case 0xC4: // DHT - define huffman table
+         L = stbi__get16be(z->s)-2;
+         while (L > 0) {
+            stbi_uc *v;
+            int sizes[16],i,n=0;
+            int q = stbi__get8(z->s);
+            int tc = q >> 4;
+            int th = q & 15;
+            if (tc > 1 || th > 3) return stbi__err("bad DHT header","Corrupt JPEG");
+            for (i=0; i < 16; ++i) {
+               sizes[i] = stbi__get8(z->s);
+               n += sizes[i];
+            }
+            L -= 17;
+            if (tc == 0) {
+               if (!stbi__build_huffman(z->huff_dc+th, sizes)) return 0;
+               v = z->huff_dc[th].values;
+            } else {
+               if (!stbi__build_huffman(z->huff_ac+th, sizes)) return 0;
+               v = z->huff_ac[th].values;
+            }
+            for (i=0; i < n; ++i)
+               v[i] = stbi__get8(z->s);
+            if (tc != 0)
+               stbi__build_fast_ac(z->fast_ac[th], z->huff_ac + th);
+            L -= n;
+         }
+         return L==0;
+   }
+
+   // check for comment block or APP blocks
+   if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) {
+      L = stbi__get16be(z->s);
+      if (L < 2) {
+         if (m == 0xFE)
+            return stbi__err("bad COM len","Corrupt JPEG");
+         else
+            return stbi__err("bad APP len","Corrupt JPEG");
+      }
+      L -= 2;
+
+      if (m == 0xE0 && L >= 5) { // JFIF APP0 segment
+         static const unsigned char tag[5] = {'J','F','I','F','\0'};
+         int ok = 1;
+         int i;
+         for (i=0; i < 5; ++i)
+            if (stbi__get8(z->s) != tag[i])
+               ok = 0;
+         L -= 5;
+         if (ok)
+            z->jfif = 1;
+      } else if (m == 0xEE && L >= 12) { // Adobe APP14 segment
+         static const unsigned char tag[6] = {'A','d','o','b','e','\0'};
+         int ok = 1;
+         int i;
+         for (i=0; i < 6; ++i)
+            if (stbi__get8(z->s) != tag[i])
+               ok = 0;
+         L -= 6;
+         if (ok) {
+            stbi__get8(z->s); // version
+            stbi__get16be(z->s); // flags0
+            stbi__get16be(z->s); // flags1
+            z->app14_color_transform = stbi__get8(z->s); // color transform
+            L -= 6;
+         }
+      }
+
+      stbi__skip(z->s, L);
+      return 1;
+   }
+
+   return stbi__err("unknown marker","Corrupt JPEG");
+}
+
+// after we see SOS
+static int stbi__process_scan_header(stbi__jpeg *z)
+{
+   int i;
+   int Ls = stbi__get16be(z->s);
+   z->scan_n = stbi__get8(z->s);
+   if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s->img_n) return stbi__err("bad SOS component count","Corrupt JPEG");
+   if (Ls != 6+2*z->scan_n) return stbi__err("bad SOS len","Corrupt JPEG");
+   for (i=0; i < z->scan_n; ++i) {
+      int id = stbi__get8(z->s), which;
+      int q = stbi__get8(z->s);
+      for (which = 0; which < z->s->img_n; ++which)
+         if (z->img_comp[which].id == id)
+            break;
+      if (which == z->s->img_n) return 0; // no match
+      z->img_comp[which].hd = q >> 4;   if (z->img_comp[which].hd > 3) return stbi__err("bad DC huff","Corrupt JPEG");
+      z->img_comp[which].ha = q & 15;   if (z->img_comp[which].ha > 3) return stbi__err("bad AC huff","Corrupt JPEG");
+      z->order[i] = which;
+   }
+
+   {
+      int aa;
+      z->spec_start = stbi__get8(z->s);
+      z->spec_end   = stbi__get8(z->s); // should be 63, but might be 0
+      aa = stbi__get8(z->s);
+      z->succ_high = (aa >> 4);
+      z->succ_low  = (aa & 15);
+      if (z->progressive) {
+         if (z->spec_start > 63 || z->spec_end > 63  || z->spec_start > z->spec_end || z->succ_high > 13 || z->succ_low > 13)
+            return stbi__err("bad SOS", "Corrupt JPEG");
+      } else {
+         if (z->spec_start != 0) return stbi__err("bad SOS","Corrupt JPEG");
+         if (z->succ_high != 0 || z->succ_low != 0) return stbi__err("bad SOS","Corrupt JPEG");
+         z->spec_end = 63;
+      }
+   }
+
+   return 1;
+}
+
+static int stbi__free_jpeg_components(stbi__jpeg *z, int ncomp, int why)
+{
+   int i;
+   for (i=0; i < ncomp; ++i) {
+      if (z->img_comp[i].raw_data) {
+         STBI_FREE(z->img_comp[i].raw_data);
+         z->img_comp[i].raw_data = NULL;
+         z->img_comp[i].data = NULL;
+      }
+      if (z->img_comp[i].raw_coeff) {
+         STBI_FREE(z->img_comp[i].raw_coeff);
+         z->img_comp[i].raw_coeff = 0;
+         z->img_comp[i].coeff = 0;
+      }
+      if (z->img_comp[i].linebuf) {
+         STBI_FREE(z->img_comp[i].linebuf);
+         z->img_comp[i].linebuf = NULL;
+      }
+   }
+   return why;
+}
+
+static int stbi__process_frame_header(stbi__jpeg *z, int scan)
+{
+   stbi__context *s = z->s;
+   int Lf,p,i,q, h_max=1,v_max=1,c;
+   Lf = stbi__get16be(s);         if (Lf < 11) return stbi__err("bad SOF len","Corrupt JPEG"); // JPEG
+   p  = stbi__get8(s);            if (p != 8) return stbi__err("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline
+   s->img_y = stbi__get16be(s);   if (s->img_y == 0) return stbi__err("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG
+   s->img_x = stbi__get16be(s);   if (s->img_x == 0) return stbi__err("0 width","Corrupt JPEG"); // JPEG requires
+   c = stbi__get8(s);
+   if (c != 3 && c != 1 && c != 4) return stbi__err("bad component count","Corrupt JPEG");
+   s->img_n = c;
+   for (i=0; i < c; ++i) {
+      z->img_comp[i].data = NULL;
+      z->img_comp[i].linebuf = NULL;
+   }
+
+   if (Lf != 8+3*s->img_n) return stbi__err("bad SOF len","Corrupt JPEG");
+
+   z->rgb = 0;
+   for (i=0; i < s->img_n; ++i) {
+      static const unsigned char rgb[3] = { 'R', 'G', 'B' };
+      z->img_comp[i].id = stbi__get8(s);
+      if (s->img_n == 3 && z->img_comp[i].id == rgb[i])
+         ++z->rgb;
+      q = stbi__get8(s);
+      z->img_comp[i].h = (q >> 4);  if (!z->img_comp[i].h || z->img_comp[i].h > 4) return stbi__err("bad H","Corrupt JPEG");
+      z->img_comp[i].v = q & 15;    if (!z->img_comp[i].v || z->img_comp[i].v > 4) return stbi__err("bad V","Corrupt JPEG");
+      z->img_comp[i].tq = stbi__get8(s);  if (z->img_comp[i].tq > 3) return stbi__err("bad TQ","Corrupt JPEG");
+   }
+
+   if (scan != STBI__SCAN_load) return 1;
+
+   if (!stbi__mad3sizes_valid(s->img_x, s->img_y, s->img_n, 0)) return stbi__err("too large", "Image too large to decode");
+
+   for (i=0; i < s->img_n; ++i) {
+      if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h;
+      if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v;
+   }
+
+   // compute interleaved mcu info
+   z->img_h_max = h_max;
+   z->img_v_max = v_max;
+   z->img_mcu_w = h_max * 8;
+   z->img_mcu_h = v_max * 8;
+   // these sizes can't be more than 17 bits
+   z->img_mcu_x = (s->img_x + z->img_mcu_w-1) / z->img_mcu_w;
+   z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h;
+
+   for (i=0; i < s->img_n; ++i) {
+      // number of effective pixels (e.g. for non-interleaved MCU)
+      z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max-1) / h_max;
+      z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max-1) / v_max;
+      // to simplify generation, we'll allocate enough memory to decode
+      // the bogus oversized data from using interleaved MCUs and their
+      // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't
+      // discard the extra data until colorspace conversion
+      //
+      // img_mcu_x, img_mcu_y: <=17 bits; comp[i].h and .v are <=4 (checked earlier)
+      // so these muls can't overflow with 32-bit ints (which we require)
+      z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8;
+      z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8;
+      z->img_comp[i].coeff = 0;
+      z->img_comp[i].raw_coeff = 0;
+      z->img_comp[i].linebuf = NULL;
+      z->img_comp[i].raw_data = stbi__malloc_mad2(z->img_comp[i].w2, z->img_comp[i].h2, 15);
+      if (z->img_comp[i].raw_data == NULL)
+         return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory"));
+      // align blocks for idct using mmx/sse
+      z->img_comp[i].data = (stbi_uc*) (((size_t) z->img_comp[i].raw_data + 15) & ~15);
+      if (z->progressive) {
+         // w2, h2 are multiples of 8 (see above)
+         z->img_comp[i].coeff_w = z->img_comp[i].w2 / 8;
+         z->img_comp[i].coeff_h = z->img_comp[i].h2 / 8;
+         z->img_comp[i].raw_coeff = stbi__malloc_mad3(z->img_comp[i].w2, z->img_comp[i].h2, sizeof(short), 15);
+         if (z->img_comp[i].raw_coeff == NULL)
+            return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory"));
+         z->img_comp[i].coeff = (short*) (((size_t) z->img_comp[i].raw_coeff + 15) & ~15);
+      }
+   }
+
+   return 1;
+}
+
+// use comparisons since in some cases we handle more than one case (e.g. SOF)
+#define stbi__DNL(x)         ((x) == 0xdc)
+#define stbi__SOI(x)         ((x) == 0xd8)
+#define stbi__EOI(x)         ((x) == 0xd9)
+#define stbi__SOF(x)         ((x) == 0xc0 || (x) == 0xc1 || (x) == 0xc2)
+#define stbi__SOS(x)         ((x) == 0xda)
+
+#define stbi__SOF_progressive(x)   ((x) == 0xc2)
+
+static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan)
+{
+   int m;
+   z->jfif = 0;
+   z->app14_color_transform = -1; // valid values are 0,1,2
+   z->marker = STBI__MARKER_none; // initialize cached marker to empty
+   m = stbi__get_marker(z);
+   if (!stbi__SOI(m)) return stbi__err("no SOI","Corrupt JPEG");
+   if (scan == STBI__SCAN_type) return 1;
+   m = stbi__get_marker(z);
+   while (!stbi__SOF(m)) {
+      if (!stbi__process_marker(z,m)) return 0;
+      m = stbi__get_marker(z);
+      while (m == STBI__MARKER_none) {
+         // some files have extra padding after their blocks, so ok, we'll scan
+         if (stbi__at_eof(z->s)) return stbi__err("no SOF", "Corrupt JPEG");
+         m = stbi__get_marker(z);
+      }
+   }
+   z->progressive = stbi__SOF_progressive(m);
+   if (!stbi__process_frame_header(z, scan)) return 0;
+   return 1;
+}
+
+// decode image to YCbCr format
+static int stbi__decode_jpeg_image(stbi__jpeg *j)
+{
+   int m;
+   for (m = 0; m < 4; m++) {
+      j->img_comp[m].raw_data = NULL;
+      j->img_comp[m].raw_coeff = NULL;
+   }
+   j->restart_interval = 0;
+   if (!stbi__decode_jpeg_header(j, STBI__SCAN_load)) return 0;
+   m = stbi__get_marker(j);
+   while (!stbi__EOI(m)) {
+      if (stbi__SOS(m)) {
+         if (!stbi__process_scan_header(j)) return 0;
+         if (!stbi__parse_entropy_coded_data(j)) return 0;
+         if (j->marker == STBI__MARKER_none ) {
+            // handle 0s at the end of image data from IP Kamera 9060
+            while (!stbi__at_eof(j->s)) {
+               int x = stbi__get8(j->s);
+               if (x == 255) {
+                  j->marker = stbi__get8(j->s);
+                  break;
+               }
+            }
+            // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0
+         }
+      } else if (stbi__DNL(m)) {
+         int Ld = stbi__get16be(j->s);
+         stbi__uint32 NL = stbi__get16be(j->s);
+         if (Ld != 4) return stbi__err("bad DNL len", "Corrupt JPEG");
+         if (NL != j->s->img_y) return stbi__err("bad DNL height", "Corrupt JPEG");
+      } else {
+         if (!stbi__process_marker(j, m)) return 0;
+      }
+      m = stbi__get_marker(j);
+   }
+   if (j->progressive)
+      stbi__jpeg_finish(j);
+   return 1;
+}
+
+// static jfif-centered resampling (across block boundaries)
+
+typedef stbi_uc *(*resample_row_func)(stbi_uc *out, stbi_uc *in0, stbi_uc *in1,
+                                    int w, int hs);
+
+#define stbi__div4(x) ((stbi_uc) ((x) >> 2))
+
+static stbi_uc *resample_row_1(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs)
+{
+   STBI_NOTUSED(out);
+   STBI_NOTUSED(in_far);
+   STBI_NOTUSED(w);
+   STBI_NOTUSED(hs);
+   return in_near;
+}
+
+static stbi_uc* stbi__resample_row_v_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs)
+{
+   // need to generate two samples vertically for every one in input
+   int i;
+   STBI_NOTUSED(hs);
+   for (i=0; i < w; ++i)
+      out[i] = stbi__div4(3*in_near[i] + in_far[i] + 2);
+   return out;
+}
+
+static stbi_uc*  stbi__resample_row_h_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs)
+{
+   // need to generate two samples horizontally for every one in input
+   int i;
+   stbi_uc *input = in_near;
+
+   if (w == 1) {
+      // if only one sample, can't do any interpolation
+      out[0] = out[1] = input[0];
+      return out;
+   }
+
+   out[0] = input[0];
+   out[1] = stbi__div4(input[0]*3 + input[1] + 2);
+   for (i=1; i < w-1; ++i) {
+      int n = 3*input[i]+2;
+      out[i*2+0] = stbi__div4(n+input[i-1]);
+      out[i*2+1] = stbi__div4(n+input[i+1]);
+   }
+   out[i*2+0] = stbi__div4(input[w-2]*3 + input[w-1] + 2);
+   out[i*2+1] = input[w-1];
+
+   STBI_NOTUSED(in_far);
+   STBI_NOTUSED(hs);
+
+   return out;
+}
+
+#define stbi__div16(x) ((stbi_uc) ((x) >> 4))
+
+static stbi_uc *stbi__resample_row_hv_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs)
+{
+   // need to generate 2x2 samples for every one in input
+   int i,t0,t1;
+   if (w == 1) {
+      out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2);
+      return out;
+   }
+
+   t1 = 3*in_near[0] + in_far[0];
+   out[0] = stbi__div4(t1+2);
+   for (i=1; i < w; ++i) {
+      t0 = t1;
+      t1 = 3*in_near[i]+in_far[i];
+      out[i*2-1] = stbi__div16(3*t0 + t1 + 8);
+      out[i*2  ] = stbi__div16(3*t1 + t0 + 8);
+   }
+   out[w*2-1] = stbi__div4(t1+2);
+
+   STBI_NOTUSED(hs);
+
+   return out;
+}
+
+#if defined(STBI_SSE2) || defined(STBI_NEON)
+static stbi_uc *stbi__resample_row_hv_2_simd(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs)
+{
+   // need to generate 2x2 samples for every one in input
+   int i=0,t0,t1;
+
+   if (w == 1) {
+      out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2);
+      return out;
+   }
+
+   t1 = 3*in_near[0] + in_far[0];
+   // process groups of 8 pixels for as long as we can.
+   // note we can't handle the last pixel in a row in this loop
+   // because we need to handle the filter boundary conditions.
+   for (; i < ((w-1) & ~7); i += 8) {
+#if defined(STBI_SSE2)
+      // load and perform the vertical filtering pass
+      // this uses 3*x + y = 4*x + (y - x)
+      __m128i zero  = _mm_setzero_si128();
+      __m128i farb  = _mm_loadl_epi64((__m128i *) (in_far + i));
+      __m128i nearb = _mm_loadl_epi64((__m128i *) (in_near + i));
+      __m128i farw  = _mm_unpacklo_epi8(farb, zero);
+      __m128i nearw = _mm_unpacklo_epi8(nearb, zero);
+      __m128i diff  = _mm_sub_epi16(farw, nearw);
+      __m128i nears = _mm_slli_epi16(nearw, 2);
+      __m128i curr  = _mm_add_epi16(nears, diff); // current row
+
+      // horizontal filter works the same based on shifted vers of current
+      // row. "prev" is current row shifted right by 1 pixel; we need to
+      // insert the previous pixel value (from t1).
+      // "next" is current row shifted left by 1 pixel, with first pixel
+      // of next block of 8 pixels added in.
+      __m128i prv0 = _mm_slli_si128(curr, 2);
+      __m128i nxt0 = _mm_srli_si128(curr, 2);
+      __m128i prev = _mm_insert_epi16(prv0, t1, 0);
+      __m128i next = _mm_insert_epi16(nxt0, 3*in_near[i+8] + in_far[i+8], 7);
+
+      // horizontal filter, polyphase implementation since it's convenient:
+      // even pixels = 3*cur + prev = cur*4 + (prev - cur)
+      // odd  pixels = 3*cur + next = cur*4 + (next - cur)
+      // note the shared term.
+      __m128i bias  = _mm_set1_epi16(8);
+      __m128i curs = _mm_slli_epi16(curr, 2);
+      __m128i prvd = _mm_sub_epi16(prev, curr);
+      __m128i nxtd = _mm_sub_epi16(next, curr);
+      __m128i curb = _mm_add_epi16(curs, bias);
+      __m128i even = _mm_add_epi16(prvd, curb);
+      __m128i odd  = _mm_add_epi16(nxtd, curb);
+
+      // interleave even and odd pixels, then undo scaling.
+      __m128i int0 = _mm_unpacklo_epi16(even, odd);
+      __m128i int1 = _mm_unpackhi_epi16(even, odd);
+      __m128i de0  = _mm_srli_epi16(int0, 4);
+      __m128i de1  = _mm_srli_epi16(int1, 4);
+
+      // pack and write output
+      __m128i outv = _mm_packus_epi16(de0, de1);
+      _mm_storeu_si128((__m128i *) (out + i*2), outv);
+#elif defined(STBI_NEON)
+      // load and perform the vertical filtering pass
+      // this uses 3*x + y = 4*x + (y - x)
+      uint8x8_t farb  = vld1_u8(in_far + i);
+      uint8x8_t nearb = vld1_u8(in_near + i);
+      int16x8_t diff  = vreinterpretq_s16_u16(vsubl_u8(farb, nearb));
+      int16x8_t nears = vreinterpretq_s16_u16(vshll_n_u8(nearb, 2));
+      int16x8_t curr  = vaddq_s16(nears, diff); // current row
+
+      // horizontal filter works the same based on shifted vers of current
+      // row. "prev" is current row shifted right by 1 pixel; we need to
+      // insert the previous pixel value (from t1).
+      // "next" is current row shifted left by 1 pixel, with first pixel
+      // of next block of 8 pixels added in.
+      int16x8_t prv0 = vextq_s16(curr, curr, 7);
+      int16x8_t nxt0 = vextq_s16(curr, curr, 1);
+      int16x8_t prev = vsetq_lane_s16(t1, prv0, 0);
+      int16x8_t next = vsetq_lane_s16(3*in_near[i+8] + in_far[i+8], nxt0, 7);
+
+      // horizontal filter, polyphase implementation since it's convenient:
+      // even pixels = 3*cur + prev = cur*4 + (prev - cur)
+      // odd  pixels = 3*cur + next = cur*4 + (next - cur)
+      // note the shared term.
+      int16x8_t curs = vshlq_n_s16(curr, 2);
+      int16x8_t prvd = vsubq_s16(prev, curr);
+      int16x8_t nxtd = vsubq_s16(next, curr);
+      int16x8_t even = vaddq_s16(curs, prvd);
+      int16x8_t odd  = vaddq_s16(curs, nxtd);
+
+      // undo scaling and round, then store with even/odd phases interleaved
+      uint8x8x2_t o;
+      o.val[0] = vqrshrun_n_s16(even, 4);
+      o.val[1] = vqrshrun_n_s16(odd,  4);
+      vst2_u8(out + i*2, o);
+#endif
+
+      // "previous" value for next iter
+      t1 = 3*in_near[i+7] + in_far[i+7];
+   }
+
+   t0 = t1;
+   t1 = 3*in_near[i] + in_far[i];
+   out[i*2] = stbi__div16(3*t1 + t0 + 8);
+
+   for (++i; i < w; ++i) {
+      t0 = t1;
+      t1 = 3*in_near[i]+in_far[i];
+      out[i*2-1] = stbi__div16(3*t0 + t1 + 8);
+      out[i*2  ] = stbi__div16(3*t1 + t0 + 8);
+   }
+   out[w*2-1] = stbi__div4(t1+2);
+
+   STBI_NOTUSED(hs);
+
+   return out;
+}
+#endif
+
+static stbi_uc *stbi__resample_row_generic(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs)
+{
+   // resample with nearest-neighbor
+   int i,j;
+   STBI_NOTUSED(in_far);
+   for (i=0; i < w; ++i)
+      for (j=0; j < hs; ++j)
+         out[i*hs+j] = in_near[i];
+   return out;
+}
+
+// this is a reduced-precision calculation of YCbCr-to-RGB introduced
+// to make sure the code produces the same results in both SIMD and scalar
+#define stbi__float2fixed(x)  (((int) ((x) * 4096.0f + 0.5f)) << 8)
+static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step)
+{
+   int i;
+   for (i=0; i < count; ++i) {
+      int y_fixed = (y[i] << 20) + (1<<19); // rounding
+      int r,g,b;
+      int cr = pcr[i] - 128;
+      int cb = pcb[i] - 128;
+      r = y_fixed +  cr* stbi__float2fixed(1.40200f);
+      g = y_fixed + (cr*-stbi__float2fixed(0.71414f)) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000);
+      b = y_fixed                                     +   cb* stbi__float2fixed(1.77200f);
+      r >>= 20;
+      g >>= 20;
+      b >>= 20;
+      if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; }
+      if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; }
+      if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; }
+      out[0] = (stbi_uc)r;
+      out[1] = (stbi_uc)g;
+      out[2] = (stbi_uc)b;
+      out[3] = 255;
+      out += step;
+   }
+}
+
+#if defined(STBI_SSE2) || defined(STBI_NEON)
+static void stbi__YCbCr_to_RGB_simd(stbi_uc *out, stbi_uc const *y, stbi_uc const *pcb, stbi_uc const *pcr, int count, int step)
+{
+   int i = 0;
+
+#ifdef STBI_SSE2
+   // step == 3 is pretty ugly on the final interleave, and i'm not convinced
+   // it's useful in practice (you wouldn't use it for textures, for example).
+   // so just accelerate step == 4 case.
+   if (step == 4) {
+      // this is a fairly straightforward implementation and not super-optimized.
+      __m128i signflip  = _mm_set1_epi8(-0x80);
+      __m128i cr_const0 = _mm_set1_epi16(   (short) ( 1.40200f*4096.0f+0.5f));
+      __m128i cr_const1 = _mm_set1_epi16( - (short) ( 0.71414f*4096.0f+0.5f));
+      __m128i cb_const0 = _mm_set1_epi16( - (short) ( 0.34414f*4096.0f+0.5f));
+      __m128i cb_const1 = _mm_set1_epi16(   (short) ( 1.77200f*4096.0f+0.5f));
+      __m128i y_bias = _mm_set1_epi8((char) (unsigned char) 128);
+      __m128i xw = _mm_set1_epi16(255); // alpha channel
+
+      for (; i+7 < count; i += 8) {
+         // load
+         __m128i y_bytes = _mm_loadl_epi64((__m128i *) (y+i));
+         __m128i cr_bytes = _mm_loadl_epi64((__m128i *) (pcr+i));
+         __m128i cb_bytes = _mm_loadl_epi64((__m128i *) (pcb+i));
+         __m128i cr_biased = _mm_xor_si128(cr_bytes, signflip); // -128
+         __m128i cb_biased = _mm_xor_si128(cb_bytes, signflip); // -128
+
+         // unpack to short (and left-shift cr, cb by 8)
+         __m128i yw  = _mm_unpacklo_epi8(y_bias, y_bytes);
+         __m128i crw = _mm_unpacklo_epi8(_mm_setzero_si128(), cr_biased);
+         __m128i cbw = _mm_unpacklo_epi8(_mm_setzero_si128(), cb_biased);
+
+         // color transform
+         __m128i yws = _mm_srli_epi16(yw, 4);
+         __m128i cr0 = _mm_mulhi_epi16(cr_const0, crw);
+         __m128i cb0 = _mm_mulhi_epi16(cb_const0, cbw);
+         __m128i cb1 = _mm_mulhi_epi16(cbw, cb_const1);
+         __m128i cr1 = _mm_mulhi_epi16(crw, cr_const1);
+         __m128i rws = _mm_add_epi16(cr0, yws);
+         __m128i gwt = _mm_add_epi16(cb0, yws);
+         __m128i bws = _mm_add_epi16(yws, cb1);
+         __m128i gws = _mm_add_epi16(gwt, cr1);
+
+         // descale
+         __m128i rw = _mm_srai_epi16(rws, 4);
+         __m128i bw = _mm_srai_epi16(bws, 4);
+         __m128i gw = _mm_srai_epi16(gws, 4);
+
+         // back to byte, set up for transpose
+         __m128i brb = _mm_packus_epi16(rw, bw);
+         __m128i gxb = _mm_packus_epi16(gw, xw);
+
+         // transpose to interleave channels
+         __m128i t0 = _mm_unpacklo_epi8(brb, gxb);
+         __m128i t1 = _mm_unpackhi_epi8(brb, gxb);
+         __m128i o0 = _mm_unpacklo_epi16(t0, t1);
+         __m128i o1 = _mm_unpackhi_epi16(t0, t1);
+
+         // store
+         _mm_storeu_si128((__m128i *) (out + 0), o0);
+         _mm_storeu_si128((__m128i *) (out + 16), o1);
+         out += 32;
+      }
+   }
+#endif
+
+#ifdef STBI_NEON
+   // in this version, step=3 support would be easy to add. but is there demand?
+   if (step == 4) {
+      // this is a fairly straightforward implementation and not super-optimized.
+      uint8x8_t signflip = vdup_n_u8(0x80);
+      int16x8_t cr_const0 = vdupq_n_s16(   (short) ( 1.40200f*4096.0f+0.5f));
+      int16x8_t cr_const1 = vdupq_n_s16( - (short) ( 0.71414f*4096.0f+0.5f));
+      int16x8_t cb_const0 = vdupq_n_s16( - (short) ( 0.34414f*4096.0f+0.5f));
+      int16x8_t cb_const1 = vdupq_n_s16(   (short) ( 1.77200f*4096.0f+0.5f));
+
+      for (; i+7 < count; i += 8) {
+         // load
+         uint8x8_t y_bytes  = vld1_u8(y + i);
+         uint8x8_t cr_bytes = vld1_u8(pcr + i);
+         uint8x8_t cb_bytes = vld1_u8(pcb + i);
+         int8x8_t cr_biased = vreinterpret_s8_u8(vsub_u8(cr_bytes, signflip));
+         int8x8_t cb_biased = vreinterpret_s8_u8(vsub_u8(cb_bytes, signflip));
+
+         // expand to s16
+         int16x8_t yws = vreinterpretq_s16_u16(vshll_n_u8(y_bytes, 4));
+         int16x8_t crw = vshll_n_s8(cr_biased, 7);
+         int16x8_t cbw = vshll_n_s8(cb_biased, 7);
+
+         // color transform
+         int16x8_t cr0 = vqdmulhq_s16(crw, cr_const0);
+         int16x8_t cb0 = vqdmulhq_s16(cbw, cb_const0);
+         int16x8_t cr1 = vqdmulhq_s16(crw, cr_const1);
+         int16x8_t cb1 = vqdmulhq_s16(cbw, cb_const1);
+         int16x8_t rws = vaddq_s16(yws, cr0);
+         int16x8_t gws = vaddq_s16(vaddq_s16(yws, cb0), cr1);
+         int16x8_t bws = vaddq_s16(yws, cb1);
+
+         // undo scaling, round, convert to byte
+         uint8x8x4_t o;
+         o.val[0] = vqrshrun_n_s16(rws, 4);
+         o.val[1] = vqrshrun_n_s16(gws, 4);
+         o.val[2] = vqrshrun_n_s16(bws, 4);
+         o.val[3] = vdup_n_u8(255);
+
+         // store, interleaving r/g/b/a
+         vst4_u8(out, o);
+         out += 8*4;
+      }
+   }
+#endif
+
+   for (; i < count; ++i) {
+      int y_fixed = (y[i] << 20) + (1<<19); // rounding
+      int r,g,b;
+      int cr = pcr[i] - 128;
+      int cb = pcb[i] - 128;
+      r = y_fixed + cr* stbi__float2fixed(1.40200f);
+      g = y_fixed + cr*-stbi__float2fixed(0.71414f) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000);
+      b = y_fixed                                   +   cb* stbi__float2fixed(1.77200f);
+      r >>= 20;
+      g >>= 20;
+      b >>= 20;
+      if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; }
+      if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; }
+      if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; }
+      out[0] = (stbi_uc)r;
+      out[1] = (stbi_uc)g;
+      out[2] = (stbi_uc)b;
+      out[3] = 255;
+      out += step;
+   }
+}
+#endif
+
+// set up the kernels
+static void stbi__setup_jpeg(stbi__jpeg *j)
+{
+   j->idct_block_kernel = stbi__idct_block;
+   j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_row;
+   j->resample_row_hv_2_kernel = stbi__resample_row_hv_2;
+
+#ifdef STBI_SSE2
+   if (stbi__sse2_available()) {
+      j->idct_block_kernel = stbi__idct_simd;
+      j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd;
+      j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd;
+   }
+#endif
+
+#ifdef STBI_NEON
+   j->idct_block_kernel = stbi__idct_simd;
+   j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd;
+   j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd;
+#endif
+}
+
+// clean up the temporary component buffers
+static void stbi__cleanup_jpeg(stbi__jpeg *j)
+{
+   stbi__free_jpeg_components(j, j->s->img_n, 0);
+}
+
+typedef struct
+{
+   resample_row_func resample;
+   stbi_uc *line0,*line1;
+   int hs,vs;   // expansion factor in each axis
+   int w_lores; // horizontal pixels pre-expansion
+   int ystep;   // how far through vertical expansion we are
+   int ypos;    // which pre-expansion row we're on
+} stbi__resample;
+
+// fast 0..255 * 0..255 => 0..255 rounded multiplication
+static stbi_uc stbi__blinn_8x8(stbi_uc x, stbi_uc y)
+{
+   unsigned int t = x*y + 128;
+   return (stbi_uc) ((t + (t >>8)) >> 8);
+}
+
+static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp, int req_comp)
+{
+   int n, decode_n, is_rgb;
+   z->s->img_n = 0; // make stbi__cleanup_jpeg safe
+
+   // validate req_comp
+   if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error");
+
+   // load a jpeg image from whichever source, but leave in YCbCr format
+   if (!stbi__decode_jpeg_image(z)) { stbi__cleanup_jpeg(z); return NULL; }
+
+   // determine actual number of components to generate
+   n = req_comp ? req_comp : z->s->img_n >= 3 ? 3 : 1;
+
+   is_rgb = z->s->img_n == 3 && (z->rgb == 3 || (z->app14_color_transform == 0 && !z->jfif));
+
+   if (z->s->img_n == 3 && n < 3 && !is_rgb)
+      decode_n = 1;
+   else
+      decode_n = z->s->img_n;
+
+   // resample and color-convert
+   {
+      int k;
+      unsigned int i,j;
+      stbi_uc *output;
+      stbi_uc *coutput[4];
+
+      stbi__resample res_comp[4];
+
+      for (k=0; k < decode_n; ++k) {
+         stbi__resample *r = &res_comp[k];
+
+         // allocate line buffer big enough for upsampling off the edges
+         // with upsample factor of 4
+         z->img_comp[k].linebuf = (stbi_uc *) stbi__malloc(z->s->img_x + 3);
+         if (!z->img_comp[k].linebuf) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); }
+
+         r->hs      = z->img_h_max / z->img_comp[k].h;
+         r->vs      = z->img_v_max / z->img_comp[k].v;
+         r->ystep   = r->vs >> 1;
+         r->w_lores = (z->s->img_x + r->hs-1) / r->hs;
+         r->ypos    = 0;
+         r->line0   = r->line1 = z->img_comp[k].data;
+
+         if      (r->hs == 1 && r->vs == 1) r->resample = resample_row_1;
+         else if (r->hs == 1 && r->vs == 2) r->resample = stbi__resample_row_v_2;
+         else if (r->hs == 2 && r->vs == 1) r->resample = stbi__resample_row_h_2;
+         else if (r->hs == 2 && r->vs == 2) r->resample = z->resample_row_hv_2_kernel;
+         else                               r->resample = stbi__resample_row_generic;
+      }
+
+      // can't error after this so, this is safe
+      output = (stbi_uc *) stbi__malloc_mad3(n, z->s->img_x, z->s->img_y, 1);
+      if (!output) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); }
+
+      // now go ahead and resample
+      for (j=0; j < z->s->img_y; ++j) {
+         stbi_uc *out = output + n * z->s->img_x * j;
+         for (k=0; k < decode_n; ++k) {
+            stbi__resample *r = &res_comp[k];
+            int y_bot = r->ystep >= (r->vs >> 1);
+            coutput[k] = r->resample(z->img_comp[k].linebuf,
+                                     y_bot ? r->line1 : r->line0,
+                                     y_bot ? r->line0 : r->line1,
+                                     r->w_lores, r->hs);
+            if (++r->ystep >= r->vs) {
+               r->ystep = 0;
+               r->line0 = r->line1;
+               if (++r->ypos < z->img_comp[k].y)
+                  r->line1 += z->img_comp[k].w2;
+            }
+         }
+         if (n >= 3) {
+            stbi_uc *y = coutput[0];
+            if (z->s->img_n == 3) {
+               if (is_rgb) {
+                  for (i=0; i < z->s->img_x; ++i) {
+                     out[0] = y[i];
+                     out[1] = coutput[1][i];
+                     out[2] = coutput[2][i];
+                     out[3] = 255;
+                     out += n;
+                  }
+               } else {
+                  z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n);
+               }
+            } else if (z->s->img_n == 4) {
+               if (z->app14_color_transform == 0) { // CMYK
+                  for (i=0; i < z->s->img_x; ++i) {
+                     stbi_uc m = coutput[3][i];
+                     out[0] = stbi__blinn_8x8(coutput[0][i], m);
+                     out[1] = stbi__blinn_8x8(coutput[1][i], m);
+                     out[2] = stbi__blinn_8x8(coutput[2][i], m);
+                     out[3] = 255;
+                     out += n;
+                  }
+               } else if (z->app14_color_transform == 2) { // YCCK
+                  z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n);
+                  for (i=0; i < z->s->img_x; ++i) {
+                     stbi_uc m = coutput[3][i];
+                     out[0] = stbi__blinn_8x8(255 - out[0], m);
+                     out[1] = stbi__blinn_8x8(255 - out[1], m);
+                     out[2] = stbi__blinn_8x8(255 - out[2], m);
+                     out += n;
+                  }
+               } else { // YCbCr + alpha?  Ignore the fourth channel for now
+                  z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n);
+               }
+            } else
+               for (i=0; i < z->s->img_x; ++i) {
+                  out[0] = out[1] = out[2] = y[i];
+                  out[3] = 255; // not used if n==3
+                  out += n;
+               }
+         } else {
+            if (is_rgb) {
+               if (n == 1)
+                  for (i=0; i < z->s->img_x; ++i)
+                     *out++ = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]);
+               else {
+                  for (i=0; i < z->s->img_x; ++i, out += 2) {
+                     out[0] = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]);
+                     out[1] = 255;
+                  }
+               }
+            } else if (z->s->img_n == 4 && z->app14_color_transform == 0) {
+               for (i=0; i < z->s->img_x; ++i) {
+                  stbi_uc m = coutput[3][i];
+                  stbi_uc r = stbi__blinn_8x8(coutput[0][i], m);
+                  stbi_uc g = stbi__blinn_8x8(coutput[1][i], m);
+                  stbi_uc b = stbi__blinn_8x8(coutput[2][i], m);
+                  out[0] = stbi__compute_y(r, g, b);
+                  out[1] = 255;
+                  out += n;
+               }
+            } else if (z->s->img_n == 4 && z->app14_color_transform == 2) {
+               for (i=0; i < z->s->img_x; ++i) {
+                  out[0] = stbi__blinn_8x8(255 - coutput[0][i], coutput[3][i]);
+                  out[1] = 255;
+                  out += n;
+               }
+            } else {
+               stbi_uc *y = coutput[0];
+               if (n == 1)
+                  for (i=0; i < z->s->img_x; ++i) out[i] = y[i];
+               else
+                  for (i=0; i < z->s->img_x; ++i) { *out++ = y[i]; *out++ = 255; }
+            }
+         }
+      }
+      stbi__cleanup_jpeg(z);
+      *out_x = z->s->img_x;
+      *out_y = z->s->img_y;
+      if (comp) *comp = z->s->img_n >= 3 ? 3 : 1; // report original components, not output
+      return output;
+   }
+}
+
+static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri)
+{
+   unsigned char* result;
+   stbi__jpeg* j = (stbi__jpeg*) stbi__malloc(sizeof(stbi__jpeg));
+   STBI_NOTUSED(ri);
+   j->s = s;
+   stbi__setup_jpeg(j);
+   result = load_jpeg_image(j, x,y,comp,req_comp);
+   STBI_FREE(j);
+   return result;
+}
+
+static int stbi__jpeg_test(stbi__context *s)
+{
+   int r;
+   stbi__jpeg* j = (stbi__jpeg*)stbi__malloc(sizeof(stbi__jpeg));
+   j->s = s;
+   stbi__setup_jpeg(j);
+   r = stbi__decode_jpeg_header(j, STBI__SCAN_type);
+   stbi__rewind(s);
+   STBI_FREE(j);
+   return r;
+}
+
+static int stbi__jpeg_info_raw(stbi__jpeg *j, int *x, int *y, int *comp)
+{
+   if (!stbi__decode_jpeg_header(j, STBI__SCAN_header)) {
+      stbi__rewind( j->s );
+      return 0;
+   }
+   if (x) *x = j->s->img_x;
+   if (y) *y = j->s->img_y;
+   if (comp) *comp = j->s->img_n >= 3 ? 3 : 1;
+   return 1;
+}
+
+static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp)
+{
+   int result;
+   stbi__jpeg* j = (stbi__jpeg*) (stbi__malloc(sizeof(stbi__jpeg)));
+   j->s = s;
+   result = stbi__jpeg_info_raw(j, x, y, comp);
+   STBI_FREE(j);
+   return result;
+}
+#endif
+
+// public domain zlib decode    v0.2  Sean Barrett 2006-11-18
+//    simple implementation
+//      - all input must be provided in an upfront buffer
+//      - all output is written to a single output buffer (can malloc/realloc)
+//    performance
+//      - fast huffman
+
+#ifndef STBI_NO_ZLIB
+
+// fast-way is faster to check than jpeg huffman, but slow way is slower
+#define STBI__ZFAST_BITS  9 // accelerate all cases in default tables
+#define STBI__ZFAST_MASK  ((1 << STBI__ZFAST_BITS) - 1)
+
+// zlib-style huffman encoding
+// (jpegs packs from left, zlib from right, so can't share code)
+typedef struct
+{
+   stbi__uint16 fast[1 << STBI__ZFAST_BITS];
+   stbi__uint16 firstcode[16];
+   int maxcode[17];
+   stbi__uint16 firstsymbol[16];
+   stbi_uc  size[288];
+   stbi__uint16 value[288];
+} stbi__zhuffman;
+
+stbi_inline static int stbi__bitreverse16(int n)
+{
+  n = ((n & 0xAAAA) >>  1) | ((n & 0x5555) << 1);
+  n = ((n & 0xCCCC) >>  2) | ((n & 0x3333) << 2);
+  n = ((n & 0xF0F0) >>  4) | ((n & 0x0F0F) << 4);
+  n = ((n & 0xFF00) >>  8) | ((n & 0x00FF) << 8);
+  return n;
+}
+
+stbi_inline static int stbi__bit_reverse(int v, int bits)
+{
+   STBI_ASSERT(bits <= 16);
+   // to bit reverse n bits, reverse 16 and shift
+   // e.g. 11 bits, bit reverse and shift away 5
+   return stbi__bitreverse16(v) >> (16-bits);
+}
+
+static int stbi__zbuild_huffman(stbi__zhuffman *z, const stbi_uc *sizelist, int num)
+{
+   int i,k=0;
+   int code, next_code[16], sizes[17];
+
+   // DEFLATE spec for generating codes
+   memset(sizes, 0, sizeof(sizes));
+   memset(z->fast, 0, sizeof(z->fast));
+   for (i=0; i < num; ++i)
+      ++sizes[sizelist[i]];
+   sizes[0] = 0;
+   for (i=1; i < 16; ++i)
+      if (sizes[i] > (1 << i))
+         return stbi__err("bad sizes", "Corrupt PNG");
+   code = 0;
+   for (i=1; i < 16; ++i) {
+      next_code[i] = code;
+      z->firstcode[i] = (stbi__uint16) code;
+      z->firstsymbol[i] = (stbi__uint16) k;
+      code = (code + sizes[i]);
+      if (sizes[i])
+         if (code-1 >= (1 << i)) return stbi__err("bad codelengths","Corrupt PNG");
+      z->maxcode[i] = code << (16-i); // preshift for inner loop
+      code <<= 1;
+      k += sizes[i];
+   }
+   z->maxcode[16] = 0x10000; // sentinel
+   for (i=0; i < num; ++i) {
+      int s = sizelist[i];
+      if (s) {
+         int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s];
+         stbi__uint16 fastv = (stbi__uint16) ((s << 9) | i);
+         z->size [c] = (stbi_uc     ) s;
+         z->value[c] = (stbi__uint16) i;
+         if (s <= STBI__ZFAST_BITS) {
+            int j = stbi__bit_reverse(next_code[s],s);
+            while (j < (1 << STBI__ZFAST_BITS)) {
+               z->fast[j] = fastv;
+               j += (1 << s);
+            }
+         }
+         ++next_code[s];
+      }
+   }
+   return 1;
+}
+
+// zlib-from-memory implementation for PNG reading
+//    because PNG allows splitting the zlib stream arbitrarily,
+//    and it's annoying structurally to have PNG call ZLIB call PNG,
+//    we require PNG read all the IDATs and combine them into a single
+//    memory buffer
+
+typedef struct
+{
+   stbi_uc *zbuffer, *zbuffer_end;
+   int num_bits;
+   stbi__uint32 code_buffer;
+
+   char *zout;
+   char *zout_start;
+   char *zout_end;
+   int   z_expandable;
+
+   stbi__zhuffman z_length, z_distance;
+} stbi__zbuf;
+
+stbi_inline static stbi_uc stbi__zget8(stbi__zbuf *z)
+{
+   if (z->zbuffer >= z->zbuffer_end) return 0;
+   return *z->zbuffer++;
+}
+
+static void stbi__fill_bits(stbi__zbuf *z)
+{
+   do {
+      STBI_ASSERT(z->code_buffer < (1U << z->num_bits));
+      z->code_buffer |= (unsigned int) stbi__zget8(z) << z->num_bits;
+      z->num_bits += 8;
+   } while (z->num_bits <= 24);
+}
+
+stbi_inline static unsigned int stbi__zreceive(stbi__zbuf *z, int n)
+{
+   unsigned int k;
+   if (z->num_bits < n) stbi__fill_bits(z);
+   k = z->code_buffer & ((1 << n) - 1);
+   z->code_buffer >>= n;
+   z->num_bits -= n;
+   return k;
+}
+
+static int stbi__zhuffman_decode_slowpath(stbi__zbuf *a, stbi__zhuffman *z)
+{
+   int b,s,k;
+   // not resolved by fast table, so compute it the slow way
+   // use jpeg approach, which requires MSbits at top
+   k = stbi__bit_reverse(a->code_buffer, 16);
+   for (s=STBI__ZFAST_BITS+1; ; ++s)
+      if (k < z->maxcode[s])
+         break;
+   if (s == 16) return -1; // invalid code!
+   // code size is s, so:
+   b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s];
+   STBI_ASSERT(z->size[b] == s);
+   a->code_buffer >>= s;
+   a->num_bits -= s;
+   return z->value[b];
+}
+
+stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z)
+{
+   int b,s;
+   if (a->num_bits < 16) stbi__fill_bits(a);
+   b = z->fast[a->code_buffer & STBI__ZFAST_MASK];
+   if (b) {
+      s = b >> 9;
+      a->code_buffer >>= s;
+      a->num_bits -= s;
+      return b & 511;
+   }
+   return stbi__zhuffman_decode_slowpath(a, z);
+}
+
+static int stbi__zexpand(stbi__zbuf *z, char *zout, int n)  // need to make room for n bytes
+{
+   char *q;
+   int cur, limit, old_limit;
+   z->zout = zout;
+   if (!z->z_expandable) return stbi__err("output buffer limit","Corrupt PNG");
+   cur   = (int) (z->zout     - z->zout_start);
+   limit = old_limit = (int) (z->zout_end - z->zout_start);
+   while (cur + n > limit)
+      limit *= 2;
+   q = (char *) STBI_REALLOC_SIZED(z->zout_start, old_limit, limit);
+   STBI_NOTUSED(old_limit);
+   if (q == NULL) return stbi__err("outofmem", "Out of memory");
+   z->zout_start = q;
+   z->zout       = q + cur;
+   z->zout_end   = q + limit;
+   return 1;
+}
+
+static const int stbi__zlength_base[31] = {
+   3,4,5,6,7,8,9,10,11,13,
+   15,17,19,23,27,31,35,43,51,59,
+   67,83,99,115,131,163,195,227,258,0,0 };
+
+static const int stbi__zlength_extra[31]=
+{ 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 };
+
+static const int stbi__zdist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,
+257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0};
+
+static const int stbi__zdist_extra[32] =
+{ 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13};
+
+static int stbi__parse_huffman_block(stbi__zbuf *a)
+{
+   char *zout = a->zout;
+   for(;;) {
+      int z = stbi__zhuffman_decode(a, &a->z_length);
+      if (z < 256) {
+         if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); // error in huffman codes
+         if (zout >= a->zout_end) {
+            if (!stbi__zexpand(a, zout, 1)) return 0;
+            zout = a->zout;
+         }
+         *zout++ = (char) z;
+      } else {
+         stbi_uc *p;
+         int len,dist;
+         if (z == 256) {
+            a->zout = zout;
+            return 1;
+         }
+         z -= 257;
+         len = stbi__zlength_base[z];
+         if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]);
+         z = stbi__zhuffman_decode(a, &a->z_distance);
+         if (z < 0) return stbi__err("bad huffman code","Corrupt PNG");
+         dist = stbi__zdist_base[z];
+         if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]);
+         if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG");
+         if (zout + len > a->zout_end) {
+            if (!stbi__zexpand(a, zout, len)) return 0;
+            zout = a->zout;
+         }
+         p = (stbi_uc *) (zout - dist);
+         if (dist == 1) { // run of one byte; common in images.
+            stbi_uc v = *p;
+            if (len) { do *zout++ = v; while (--len); }
+         } else {
+            if (len) { do *zout++ = *p++; while (--len); }
+         }
+      }
+   }
+}
+
+static int stbi__compute_huffman_codes(stbi__zbuf *a)
+{
+   static const stbi_uc length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 };
+   stbi__zhuffman z_codelength;
+   stbi_uc lencodes[286+32+137];//padding for maximum single op
+   stbi_uc codelength_sizes[19];
+   int i,n;
+
+   int hlit  = stbi__zreceive(a,5) + 257;
+   int hdist = stbi__zreceive(a,5) + 1;
+   int hclen = stbi__zreceive(a,4) + 4;
+   int ntot  = hlit + hdist;
+
+   memset(codelength_sizes, 0, sizeof(codelength_sizes));
+   for (i=0; i < hclen; ++i) {
+      int s = stbi__zreceive(a,3);
+      codelength_sizes[length_dezigzag[i]] = (stbi_uc) s;
+   }
+   if (!stbi__zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0;
+
+   n = 0;
+   while (n < ntot) {
+      int c = stbi__zhuffman_decode(a, &z_codelength);
+      if (c < 0 || c >= 19) return stbi__err("bad codelengths", "Corrupt PNG");
+      if (c < 16)
+         lencodes[n++] = (stbi_uc) c;
+      else {
+         stbi_uc fill = 0;
+         if (c == 16) {
+            c = stbi__zreceive(a,2)+3;
+            if (n == 0) return stbi__err("bad codelengths", "Corrupt PNG");
+            fill = lencodes[n-1];
+         } else if (c == 17)
+            c = stbi__zreceive(a,3)+3;
+         else {
+            STBI_ASSERT(c == 18);
+            c = stbi__zreceive(a,7)+11;
+         }
+         if (ntot - n < c) return stbi__err("bad codelengths", "Corrupt PNG");
+         memset(lencodes+n, fill, c);
+         n += c;
+      }
+   }
+   if (n != ntot) return stbi__err("bad codelengths","Corrupt PNG");
+   if (!stbi__zbuild_huffman(&a->z_length, lencodes, hlit)) return 0;
+   if (!stbi__zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0;
+   return 1;
+}
+
+static int stbi__parse_uncompressed_block(stbi__zbuf *a)
+{
+   stbi_uc header[4];
+   int len,nlen,k;
+   if (a->num_bits & 7)
+      stbi__zreceive(a, a->num_bits & 7); // discard
+   // drain the bit-packed data into header
+   k = 0;
+   while (a->num_bits > 0) {
+      header[k++] = (stbi_uc) (a->code_buffer & 255); // suppress MSVC run-time check
+      a->code_buffer >>= 8;
+      a->num_bits -= 8;
+   }
+   STBI_ASSERT(a->num_bits == 0);
+   // now fill header the normal way
+   while (k < 4)
+      header[k++] = stbi__zget8(a);
+   len  = header[1] * 256 + header[0];
+   nlen = header[3] * 256 + header[2];
+   if (nlen != (len ^ 0xffff)) return stbi__err("zlib corrupt","Corrupt PNG");
+   if (a->zbuffer + len > a->zbuffer_end) return stbi__err("read past buffer","Corrupt PNG");
+   if (a->zout + len > a->zout_end)
+      if (!stbi__zexpand(a, a->zout, len)) return 0;
+   memcpy(a->zout, a->zbuffer, len);
+   a->zbuffer += len;
+   a->zout += len;
+   return 1;
+}
+
+static int stbi__parse_zlib_header(stbi__zbuf *a)
+{
+   int cmf   = stbi__zget8(a);
+   int cm    = cmf & 15;
+   /* int cinfo = cmf >> 4; */
+   int flg   = stbi__zget8(a);
+   if ((cmf*256+flg) % 31 != 0) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec
+   if (flg & 32) return stbi__err("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png
+   if (cm != 8) return stbi__err("bad compression","Corrupt PNG"); // DEFLATE required for png
+   // window = 1 << (8 + cinfo)... but who cares, we fully buffer output
+   return 1;
+}
+
+static const stbi_uc stbi__zdefault_length[288] =
+{
+   8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
+   8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
+   8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
+   8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
+   8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
+   9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
+   9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
+   9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
+   7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,8,8,8,8,8,8,8,8
+};
+static const stbi_uc stbi__zdefault_distance[32] =
+{
+   5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5
+};
+/*
+Init algorithm:
+{
+   int i;   // use <= to match clearly with spec
+   for (i=0; i <= 143; ++i)     stbi__zdefault_length[i]   = 8;
+   for (   ; i <= 255; ++i)     stbi__zdefault_length[i]   = 9;
+   for (   ; i <= 279; ++i)     stbi__zdefault_length[i]   = 7;
+   for (   ; i <= 287; ++i)     stbi__zdefault_length[i]   = 8;
+
+   for (i=0; i <=  31; ++i)     stbi__zdefault_distance[i] = 5;
+}
+*/
+
+static int stbi__parse_zlib(stbi__zbuf *a, int parse_header)
+{
+   int final, type;
+   if (parse_header)
+      if (!stbi__parse_zlib_header(a)) return 0;
+   a->num_bits = 0;
+   a->code_buffer = 0;
+   do {
+      final = stbi__zreceive(a,1);
+      type = stbi__zreceive(a,2);
+      if (type == 0) {
+         if (!stbi__parse_uncompressed_block(a)) return 0;
+      } else if (type == 3) {
+         return 0;
+      } else {
+         if (type == 1) {
+            // use fixed code lengths
+            if (!stbi__zbuild_huffman(&a->z_length  , stbi__zdefault_length  , 288)) return 0;
+            if (!stbi__zbuild_huffman(&a->z_distance, stbi__zdefault_distance,  32)) return 0;
+         } else {
+            if (!stbi__compute_huffman_codes(a)) return 0;
+         }
+         if (!stbi__parse_huffman_block(a)) return 0;
+      }
+   } while (!final);
+   return 1;
+}
+
+static int stbi__do_zlib(stbi__zbuf *a, char *obuf, int olen, int exp, int parse_header)
+{
+   a->zout_start = obuf;
+   a->zout       = obuf;
+   a->zout_end   = obuf + olen;
+   a->z_expandable = exp;
+
+   return stbi__parse_zlib(a, parse_header);
+}
+
+STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen)
+{
+   stbi__zbuf a;
+   char *p = (char *) stbi__malloc(initial_size);
+   if (p == NULL) return NULL;
+   a.zbuffer = (stbi_uc *) buffer;
+   a.zbuffer_end = (stbi_uc *) buffer + len;
+   if (stbi__do_zlib(&a, p, initial_size, 1, 1)) {
+      if (outlen) *outlen = (int) (a.zout - a.zout_start);
+      return a.zout_start;
+   } else {
+      STBI_FREE(a.zout_start);
+      return NULL;
+   }
+}
+
+STBIDEF char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen)
+{
+   return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen);
+}
+
+STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header)
+{
+   stbi__zbuf a;
+   char *p = (char *) stbi__malloc(initial_size);
+   if (p == NULL) return NULL;
+   a.zbuffer = (stbi_uc *) buffer;
+   a.zbuffer_end = (stbi_uc *) buffer + len;
+   if (stbi__do_zlib(&a, p, initial_size, 1, parse_header)) {
+      if (outlen) *outlen = (int) (a.zout - a.zout_start);
+      return a.zout_start;
+   } else {
+      STBI_FREE(a.zout_start);
+      return NULL;
+   }
+}
+
+STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen)
+{
+   stbi__zbuf a;
+   a.zbuffer = (stbi_uc *) ibuffer;
+   a.zbuffer_end = (stbi_uc *) ibuffer + ilen;
+   if (stbi__do_zlib(&a, obuffer, olen, 0, 1))
+      return (int) (a.zout - a.zout_start);
+   else
+      return -1;
+}
+
+STBIDEF char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen)
+{
+   stbi__zbuf a;
+   char *p = (char *) stbi__malloc(16384);
+   if (p == NULL) return NULL;
+   a.zbuffer = (stbi_uc *) buffer;
+   a.zbuffer_end = (stbi_uc *) buffer+len;
+   if (stbi__do_zlib(&a, p, 16384, 1, 0)) {
+      if (outlen) *outlen = (int) (a.zout - a.zout_start);
+      return a.zout_start;
+   } else {
+      STBI_FREE(a.zout_start);
+      return NULL;
+   }
+}
+
+STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen)
+{
+   stbi__zbuf a;
+   a.zbuffer = (stbi_uc *) ibuffer;
+   a.zbuffer_end = (stbi_uc *) ibuffer + ilen;
+   if (stbi__do_zlib(&a, obuffer, olen, 0, 0))
+      return (int) (a.zout - a.zout_start);
+   else
+      return -1;
+}
+#endif
+
+// public domain "baseline" PNG decoder   v0.10  Sean Barrett 2006-11-18
+//    simple implementation
+//      - only 8-bit samples
+//      - no CRC checking
+//      - allocates lots of intermediate memory
+//        - avoids problem of streaming data between subsystems
+//        - avoids explicit window management
+//    performance
+//      - uses stb_zlib, a PD zlib implementation with fast huffman decoding
+
+#ifndef STBI_NO_PNG
+typedef struct
+{
+   stbi__uint32 length;
+   stbi__uint32 type;
+} stbi__pngchunk;
+
+static stbi__pngchunk stbi__get_chunk_header(stbi__context *s)
+{
+   stbi__pngchunk c;
+   c.length = stbi__get32be(s);
+   c.type   = stbi__get32be(s);
+   return c;
+}
+
+static int stbi__check_png_header(stbi__context *s)
+{
+   static const stbi_uc png_sig[8] = { 137,80,78,71,13,10,26,10 };
+   int i;
+   for (i=0; i < 8; ++i)
+      if (stbi__get8(s) != png_sig[i]) return stbi__err("bad png sig","Not a PNG");
+   return 1;
+}
+
+typedef struct
+{
+   stbi__context *s;
+   stbi_uc *idata, *expanded, *out;
+   int depth;
+} stbi__png;
+
+
+enum {
+   STBI__F_none=0,
+   STBI__F_sub=1,
+   STBI__F_up=2,
+   STBI__F_avg=3,
+   STBI__F_paeth=4,
+   // synthetic filters used for first scanline to avoid needing a dummy row of 0s
+   STBI__F_avg_first,
+   STBI__F_paeth_first
+};
+
+static stbi_uc first_row_filter[5] =
+{
+   STBI__F_none,
+   STBI__F_sub,
+   STBI__F_none,
+   STBI__F_avg_first,
+   STBI__F_paeth_first
+};
+
+static int stbi__paeth(int a, int b, int c)
+{
+   int p = a + b - c;
+   int pa = abs(p-a);
+   int pb = abs(p-b);
+   int pc = abs(p-c);
+   if (pa <= pb && pa <= pc) return a;
+   if (pb <= pc) return b;
+   return c;
+}
+
+static const stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 };
+
+// create the png data from post-deflated data
+static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color)
+{
+   int bytes = (depth == 16? 2 : 1);
+   stbi__context *s = a->s;
+   stbi__uint32 i,j,stride = x*out_n*bytes;
+   stbi__uint32 img_len, img_width_bytes;
+   int k;
+   int img_n = s->img_n; // copy it into a local for later
+
+   int output_bytes = out_n*bytes;
+   int filter_bytes = img_n*bytes;
+   int width = x;
+
+   STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1);
+   a->out = (stbi_uc *) stbi__malloc_mad3(x, y, output_bytes, 0); // extra bytes to write off the end into
+   if (!a->out) return stbi__err("outofmem", "Out of memory");
+
+   if (!stbi__mad3sizes_valid(img_n, x, depth, 7)) return stbi__err("too large", "Corrupt PNG");
+   img_width_bytes = (((img_n * x * depth) + 7) >> 3);
+   img_len = (img_width_bytes + 1) * y;
+
+   // we used to check for exact match between raw_len and img_len on non-interlaced PNGs,
+   // but issue #276 reported a PNG in the wild that had extra data at the end (all zeros),
+   // so just check for raw_len < img_len always.
+   if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG");
+
+   for (j=0; j < y; ++j) {
+      stbi_uc *cur = a->out + stride*j;
+      stbi_uc *prior;
+      int filter = *raw++;
+
+      if (filter > 4)
+         return stbi__err("invalid filter","Corrupt PNG");
+
+      if (depth < 8) {
+         STBI_ASSERT(img_width_bytes <= x);
+         cur += x*out_n - img_width_bytes; // store output to the rightmost img_len bytes, so we can decode in place
+         filter_bytes = 1;
+         width = img_width_bytes;
+      }
+      prior = cur - stride; // bugfix: need to compute this after 'cur +=' computation above
+
+      // if first row, use special filter that doesn't sample previous row
+      if (j == 0) filter = first_row_filter[filter];
+
+      // handle first byte explicitly
+      for (k=0; k < filter_bytes; ++k) {
+         switch (filter) {
+            case STBI__F_none       : cur[k] = raw[k]; break;
+            case STBI__F_sub        : cur[k] = raw[k]; break;
+            case STBI__F_up         : cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break;
+            case STBI__F_avg        : cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); break;
+            case STBI__F_paeth      : cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(0,prior[k],0)); break;
+            case STBI__F_avg_first  : cur[k] = raw[k]; break;
+            case STBI__F_paeth_first: cur[k] = raw[k]; break;
+         }
+      }
+
+      if (depth == 8) {
+         if (img_n != out_n)
+            cur[img_n] = 255; // first pixel
+         raw += img_n;
+         cur += out_n;
+         prior += out_n;
+      } else if (depth == 16) {
+         if (img_n != out_n) {
+            cur[filter_bytes]   = 255; // first pixel top byte
+            cur[filter_bytes+1] = 255; // first pixel bottom byte
+         }
+         raw += filter_bytes;
+         cur += output_bytes;
+         prior += output_bytes;
+      } else {
+         raw += 1;
+         cur += 1;
+         prior += 1;
+      }
+
+      // this is a little gross, so that we don't switch per-pixel or per-component
+      if (depth < 8 || img_n == out_n) {
+         int nk = (width - 1)*filter_bytes;
+         #define STBI__CASE(f) \
+             case f:     \
+                for (k=0; k < nk; ++k)
+         switch (filter) {
+            // "none" filter turns into a memcpy here; make that explicit.
+            case STBI__F_none:         memcpy(cur, raw, nk); break;
+            STBI__CASE(STBI__F_sub)          { cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); } break;
+            STBI__CASE(STBI__F_up)           { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break;
+            STBI__CASE(STBI__F_avg)          { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); } break;
+            STBI__CASE(STBI__F_paeth)        { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],prior[k],prior[k-filter_bytes])); } break;
+            STBI__CASE(STBI__F_avg_first)    { cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); } break;
+            STBI__CASE(STBI__F_paeth_first)  { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],0,0)); } break;
+         }
+         #undef STBI__CASE
+         raw += nk;
+      } else {
+         STBI_ASSERT(img_n+1 == out_n);
+         #define STBI__CASE(f) \
+             case f:     \
+                for (i=x-1; i >= 1; --i, cur[filter_bytes]=255,raw+=filter_bytes,cur+=output_bytes,prior+=output_bytes) \
+                   for (k=0; k < filter_bytes; ++k)
+         switch (filter) {
+            STBI__CASE(STBI__F_none)         { cur[k] = raw[k]; } break;
+            STBI__CASE(STBI__F_sub)          { cur[k] = STBI__BYTECAST(raw[k] + cur[k- output_bytes]); } break;
+            STBI__CASE(STBI__F_up)           { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break;
+            STBI__CASE(STBI__F_avg)          { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k- output_bytes])>>1)); } break;
+            STBI__CASE(STBI__F_paeth)        { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],prior[k],prior[k- output_bytes])); } break;
+            STBI__CASE(STBI__F_avg_first)    { cur[k] = STBI__BYTECAST(raw[k] + (cur[k- output_bytes] >> 1)); } break;
+            STBI__CASE(STBI__F_paeth_first)  { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],0,0)); } break;
+         }
+         #undef STBI__CASE
+
+         // the loop above sets the high byte of the pixels' alpha, but for
+         // 16 bit png files we also need the low byte set. we'll do that here.
+         if (depth == 16) {
+            cur = a->out + stride*j; // start at the beginning of the row again
+            for (i=0; i < x; ++i,cur+=output_bytes) {
+               cur[filter_bytes+1] = 255;
+            }
+         }
+      }
+   }
+
+   // we make a separate pass to expand bits to pixels; for performance,
+   // this could run two scanlines behind the above code, so it won't
+   // intefere with filtering but will still be in the cache.
+   if (depth < 8) {
+      for (j=0; j < y; ++j) {
+         stbi_uc *cur = a->out + stride*j;
+         stbi_uc *in  = a->out + stride*j + x*out_n - img_width_bytes;
+         // unpack 1/2/4-bit into a 8-bit buffer. allows us to keep the common 8-bit path optimal at minimal cost for 1/2/4-bit
+         // png guarante byte alignment, if width is not multiple of 8/4/2 we'll decode dummy trailing data that will be skipped in the later loop
+         stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range
+
+         // note that the final byte might overshoot and write more data than desired.
+         // we can allocate enough data that this never writes out of memory, but it
+         // could also overwrite the next scanline. can it overwrite non-empty data
+         // on the next scanline? yes, consider 1-pixel-wide scanlines with 1-bit-per-pixel.
+         // so we need to explicitly clamp the final ones
+
+         if (depth == 4) {
+            for (k=x*img_n; k >= 2; k-=2, ++in) {
+               *cur++ = scale * ((*in >> 4)       );
+               *cur++ = scale * ((*in     ) & 0x0f);
+            }
+            if (k > 0) *cur++ = scale * ((*in >> 4)       );
+         } else if (depth == 2) {
+            for (k=x*img_n; k >= 4; k-=4, ++in) {
+               *cur++ = scale * ((*in >> 6)       );
+               *cur++ = scale * ((*in >> 4) & 0x03);
+               *cur++ = scale * ((*in >> 2) & 0x03);
+               *cur++ = scale * ((*in     ) & 0x03);
+            }
+            if (k > 0) *cur++ = scale * ((*in >> 6)       );
+            if (k > 1) *cur++ = scale * ((*in >> 4) & 0x03);
+            if (k > 2) *cur++ = scale * ((*in >> 2) & 0x03);
+         } else if (depth == 1) {
+            for (k=x*img_n; k >= 8; k-=8, ++in) {
+               *cur++ = scale * ((*in >> 7)       );
+               *cur++ = scale * ((*in >> 6) & 0x01);
+               *cur++ = scale * ((*in >> 5) & 0x01);
+               *cur++ = scale * ((*in >> 4) & 0x01);
+               *cur++ = scale * ((*in >> 3) & 0x01);
+               *cur++ = scale * ((*in >> 2) & 0x01);
+               *cur++ = scale * ((*in >> 1) & 0x01);
+               *cur++ = scale * ((*in     ) & 0x01);
+            }
+            if (k > 0) *cur++ = scale * ((*in >> 7)       );
+            if (k > 1) *cur++ = scale * ((*in >> 6) & 0x01);
+            if (k > 2) *cur++ = scale * ((*in >> 5) & 0x01);
+            if (k > 3) *cur++ = scale * ((*in >> 4) & 0x01);
+            if (k > 4) *cur++ = scale * ((*in >> 3) & 0x01);
+            if (k > 5) *cur++ = scale * ((*in >> 2) & 0x01);
+            if (k > 6) *cur++ = scale * ((*in >> 1) & 0x01);
+         }
+         if (img_n != out_n) {
+            int q;
+            // insert alpha = 255
+            cur = a->out + stride*j;
+            if (img_n == 1) {
+               for (q=x-1; q >= 0; --q) {
+                  cur[q*2+1] = 255;
+                  cur[q*2+0] = cur[q];
+               }
+            } else {
+               STBI_ASSERT(img_n == 3);
+               for (q=x-1; q >= 0; --q) {
+                  cur[q*4+3] = 255;
+                  cur[q*4+2] = cur[q*3+2];
+                  cur[q*4+1] = cur[q*3+1];
+                  cur[q*4+0] = cur[q*3+0];
+               }
+            }
+         }
+      }
+   } else if (depth == 16) {
+      // force the image data from big-endian to platform-native.
+      // this is done in a separate pass due to the decoding relying
+      // on the data being untouched, but could probably be done
+      // per-line during decode if care is taken.
+      stbi_uc *cur = a->out;
+      stbi__uint16 *cur16 = (stbi__uint16*)cur;
+
+      for(i=0; i < x*y*out_n; ++i,cur16++,cur+=2) {
+         *cur16 = (cur[0] << 8) | cur[1];
+      }
+   }
+
+   return 1;
+}
+
+static int stbi__create_png_image(stbi__png *a, stbi_uc *image_data, stbi__uint32 image_data_len, int out_n, int depth, int color, int interlaced)
+{
+   int bytes = (depth == 16 ? 2 : 1);
+   int out_bytes = out_n * bytes;
+   stbi_uc *final;
+   int p;
+   if (!interlaced)
+      return stbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color);
+
+   // de-interlacing
+   final = (stbi_uc *) stbi__malloc_mad3(a->s->img_x, a->s->img_y, out_bytes, 0);
+   for (p=0; p < 7; ++p) {
+      int xorig[] = { 0,4,0,2,0,1,0 };
+      int yorig[] = { 0,0,4,0,2,0,1 };
+      int xspc[]  = { 8,8,4,4,2,2,1 };
+      int yspc[]  = { 8,8,8,4,4,2,2 };
+      int i,j,x,y;
+      // pass1_x[4] = 0, pass1_x[5] = 1, pass1_x[12] = 1
+      x = (a->s->img_x - xorig[p] + xspc[p]-1) / xspc[p];
+      y = (a->s->img_y - yorig[p] + yspc[p]-1) / yspc[p];
+      if (x && y) {
+         stbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y;
+         if (!stbi__create_png_image_raw(a, image_data, image_data_len, out_n, x, y, depth, color)) {
+            STBI_FREE(final);
+            return 0;
+         }
+         for (j=0; j < y; ++j) {
+            for (i=0; i < x; ++i) {
+               int out_y = j*yspc[p]+yorig[p];
+               int out_x = i*xspc[p]+xorig[p];
+               memcpy(final + out_y*a->s->img_x*out_bytes + out_x*out_bytes,
+                      a->out + (j*x+i)*out_bytes, out_bytes);
+            }
+         }
+         STBI_FREE(a->out);
+         image_data += img_len;
+         image_data_len -= img_len;
+      }
+   }
+   a->out = final;
+
+   return 1;
+}
+
+static int stbi__compute_transparency(stbi__png *z, stbi_uc tc[3], int out_n)
+{
+   stbi__context *s = z->s;
+   stbi__uint32 i, pixel_count = s->img_x * s->img_y;
+   stbi_uc *p = z->out;
+
+   // compute color-based transparency, assuming we've
+   // already got 255 as the alpha value in the output
+   STBI_ASSERT(out_n == 2 || out_n == 4);
+
+   if (out_n == 2) {
+      for (i=0; i < pixel_count; ++i) {
+         p[1] = (p[0] == tc[0] ? 0 : 255);
+         p += 2;
+      }
+   } else {
+      for (i=0; i < pixel_count; ++i) {
+         if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2])
+            p[3] = 0;
+         p += 4;
+      }
+   }
+   return 1;
+}
+
+static int stbi__compute_transparency16(stbi__png *z, stbi__uint16 tc[3], int out_n)
+{
+   stbi__context *s = z->s;
+   stbi__uint32 i, pixel_count = s->img_x * s->img_y;
+   stbi__uint16 *p = (stbi__uint16*) z->out;
+
+   // compute color-based transparency, assuming we've
+   // already got 65535 as the alpha value in the output
+   STBI_ASSERT(out_n == 2 || out_n == 4);
+
+   if (out_n == 2) {
+      for (i = 0; i < pixel_count; ++i) {
+         p[1] = (p[0] == tc[0] ? 0 : 65535);
+         p += 2;
+      }
+   } else {
+      for (i = 0; i < pixel_count; ++i) {
+         if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2])
+            p[3] = 0;
+         p += 4;
+      }
+   }
+   return 1;
+}
+
+static int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int pal_img_n)
+{
+   stbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y;
+   stbi_uc *p, *temp_out, *orig = a->out;
+
+   p = (stbi_uc *) stbi__malloc_mad2(pixel_count, pal_img_n, 0);
+   if (p == NULL) return stbi__err("outofmem", "Out of memory");
+
+   // between here and free(out) below, exitting would leak
+   temp_out = p;
+
+   if (pal_img_n == 3) {
+      for (i=0; i < pixel_count; ++i) {
+         int n = orig[i]*4;
+         p[0] = palette[n  ];
+         p[1] = palette[n+1];
+         p[2] = palette[n+2];
+         p += 3;
+      }
+   } else {
+      for (i=0; i < pixel_count; ++i) {
+         int n = orig[i]*4;
+         p[0] = palette[n  ];
+         p[1] = palette[n+1];
+         p[2] = palette[n+2];
+         p[3] = palette[n+3];
+         p += 4;
+      }
+   }
+   STBI_FREE(a->out);
+   a->out = temp_out;
+
+   STBI_NOTUSED(len);
+
+   return 1;
+}
+
+static int stbi__unpremultiply_on_load = 0;
+static int stbi__de_iphone_flag = 0;
+
+STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply)
+{
+   stbi__unpremultiply_on_load = flag_true_if_should_unpremultiply;
+}
+
+STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert)
+{
+   stbi__de_iphone_flag = flag_true_if_should_convert;
+}
+
+static void stbi__de_iphone(stbi__png *z)
+{
+   stbi__context *s = z->s;
+   stbi__uint32 i, pixel_count = s->img_x * s->img_y;
+   stbi_uc *p = z->out;
+
+   if (s->img_out_n == 3) {  // convert bgr to rgb
+      for (i=0; i < pixel_count; ++i) {
+         stbi_uc t = p[0];
+         p[0] = p[2];
+         p[2] = t;
+         p += 3;
+      }
+   } else {
+      STBI_ASSERT(s->img_out_n == 4);
+      if (stbi__unpremultiply_on_load) {
+         // convert bgr to rgb and unpremultiply
+         for (i=0; i < pixel_count; ++i) {
+            stbi_uc a = p[3];
+            stbi_uc t = p[0];
+            if (a) {
+               stbi_uc half = a / 2;
+               p[0] = (p[2] * 255 + half) / a;
+               p[1] = (p[1] * 255 + half) / a;
+               p[2] = ( t   * 255 + half) / a;
+            } else {
+               p[0] = p[2];
+               p[2] = t;
+            }
+            p += 4;
+         }
+      } else {
+         // convert bgr to rgb
+         for (i=0; i < pixel_count; ++i) {
+            stbi_uc t = p[0];
+            p[0] = p[2];
+            p[2] = t;
+            p += 4;
+         }
+      }
+   }
+}
+
+#define STBI__PNG_TYPE(a,b,c,d)  (((unsigned) (a) << 24) + ((unsigned) (b) << 16) + ((unsigned) (c) << 8) + (unsigned) (d))
+
+static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp)
+{
+   stbi_uc palette[1024], pal_img_n=0;
+   stbi_uc has_trans=0, tc[3]={0};
+   stbi__uint16 tc16[3];
+   stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0;
+   int first=1,k,interlace=0, color=0, is_iphone=0;
+   stbi__context *s = z->s;
+
+   z->expanded = NULL;
+   z->idata = NULL;
+   z->out = NULL;
+
+   if (!stbi__check_png_header(s)) return 0;
+
+   if (scan == STBI__SCAN_type) return 1;
+
+   for (;;) {
+      stbi__pngchunk c = stbi__get_chunk_header(s);
+      switch (c.type) {
+         case STBI__PNG_TYPE('C','g','B','I'):
+            is_iphone = 1;
+            stbi__skip(s, c.length);
+            break;
+         case STBI__PNG_TYPE('I','H','D','R'): {
+            int comp,filter;
+            if (!first) return stbi__err("multiple IHDR","Corrupt PNG");
+            first = 0;
+            if (c.length != 13) return stbi__err("bad IHDR len","Corrupt PNG");
+            s->img_x = stbi__get32be(s); if (s->img_x > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)");
+            s->img_y = stbi__get32be(s); if (s->img_y > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)");
+            z->depth = stbi__get8(s);  if (z->depth != 1 && z->depth != 2 && z->depth != 4 && z->depth != 8 && z->depth != 16)  return stbi__err("1/2/4/8/16-bit only","PNG not supported: 1/2/4/8/16-bit only");
+            color = stbi__get8(s);  if (color > 6)         return stbi__err("bad ctype","Corrupt PNG");
+            if (color == 3 && z->depth == 16)                  return stbi__err("bad ctype","Corrupt PNG");
+            if (color == 3) pal_img_n = 3; else if (color & 1) return stbi__err("bad ctype","Corrupt PNG");
+            comp  = stbi__get8(s);  if (comp) return stbi__err("bad comp method","Corrupt PNG");
+            filter= stbi__get8(s);  if (filter) return stbi__err("bad filter method","Corrupt PNG");
+            interlace = stbi__get8(s); if (interlace>1) return stbi__err("bad interlace method","Corrupt PNG");
+            if (!s->img_x || !s->img_y) return stbi__err("0-pixel image","Corrupt PNG");
+            if (!pal_img_n) {
+               s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0);
+               if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode");
+               if (scan == STBI__SCAN_header) return 1;
+            } else {
+               // if paletted, then pal_n is our final components, and
+               // img_n is # components to decompress/filter.
+               s->img_n = 1;
+               if ((1 << 30) / s->img_x / 4 < s->img_y) return stbi__err("too large","Corrupt PNG");
+               // if SCAN_header, have to scan to see if we have a tRNS
+            }
+            break;
+         }
+
+         case STBI__PNG_TYPE('P','L','T','E'):  {
+            if (first) return stbi__err("first not IHDR", "Corrupt PNG");
+            if (c.length > 256*3) return stbi__err("invalid PLTE","Corrupt PNG");
+            pal_len = c.length / 3;
+            if (pal_len * 3 != c.length) return stbi__err("invalid PLTE","Corrupt PNG");
+            for (i=0; i < pal_len; ++i) {
+               palette[i*4+0] = stbi__get8(s);
+               palette[i*4+1] = stbi__get8(s);
+               palette[i*4+2] = stbi__get8(s);
+               palette[i*4+3] = 255;
+            }
+            break;
+         }
+
+         case STBI__PNG_TYPE('t','R','N','S'): {
+            if (first) return stbi__err("first not IHDR", "Corrupt PNG");
+            if (z->idata) return stbi__err("tRNS after IDAT","Corrupt PNG");
+            if (pal_img_n) {
+               if (scan == STBI__SCAN_header) { s->img_n = 4; return 1; }
+               if (pal_len == 0) return stbi__err("tRNS before PLTE","Corrupt PNG");
+               if (c.length > pal_len) return stbi__err("bad tRNS len","Corrupt PNG");
+               pal_img_n = 4;
+               for (i=0; i < c.length; ++i)
+                  palette[i*4+3] = stbi__get8(s);
+            } else {
+               if (!(s->img_n & 1)) return stbi__err("tRNS with alpha","Corrupt PNG");
+               if (c.length != (stbi__uint32) s->img_n*2) return stbi__err("bad tRNS len","Corrupt PNG");
+               has_trans = 1;
+               if (z->depth == 16) {
+                  for (k = 0; k < s->img_n; ++k) tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is
+               } else {
+                  for (k = 0; k < s->img_n; ++k) tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger
+               }
+            }
+            break;
+         }
+
+         case STBI__PNG_TYPE('I','D','A','T'): {
+            if (first) return stbi__err("first not IHDR", "Corrupt PNG");
+            if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG");
+            if (scan == STBI__SCAN_header) { s->img_n = pal_img_n; return 1; }
+            if ((int)(ioff + c.length) < (int)ioff) return 0;
+            if (ioff + c.length > idata_limit) {
+               stbi__uint32 idata_limit_old = idata_limit;
+               stbi_uc *p;
+               if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096;
+               while (ioff + c.length > idata_limit)
+                  idata_limit *= 2;
+               STBI_NOTUSED(idata_limit_old);
+               p = (stbi_uc *) STBI_REALLOC_SIZED(z->idata, idata_limit_old, idata_limit); if (p == NULL) return stbi__err("outofmem", "Out of memory");
+               z->idata = p;
+            }
+            if (!stbi__getn(s, z->idata+ioff,c.length)) return stbi__err("outofdata","Corrupt PNG");
+            ioff += c.length;
+            break;
+         }
+
+         case STBI__PNG_TYPE('I','E','N','D'): {
+            stbi__uint32 raw_len, bpl;
+            if (first) return stbi__err("first not IHDR", "Corrupt PNG");
+            if (scan != STBI__SCAN_load) return 1;
+            if (z->idata == NULL) return stbi__err("no IDAT","Corrupt PNG");
+            // initial guess for decoded data size to avoid unnecessary reallocs
+            bpl = (s->img_x * z->depth + 7) / 8; // bytes per line, per component
+            raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */;
+            z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, raw_len, (int *) &raw_len, !is_iphone);
+            if (z->expanded == NULL) return 0; // zlib should set error
+            STBI_FREE(z->idata); z->idata = NULL;
+            if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans)
+               s->img_out_n = s->img_n+1;
+            else
+               s->img_out_n = s->img_n;
+            if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, z->depth, color, interlace)) return 0;
+            if (has_trans) {
+               if (z->depth == 16) {
+                  if (!stbi__compute_transparency16(z, tc16, s->img_out_n)) return 0;
+               } else {
+                  if (!stbi__compute_transparency(z, tc, s->img_out_n)) return 0;
+               }
+            }
+            if (is_iphone && stbi__de_iphone_flag && s->img_out_n > 2)
+               stbi__de_iphone(z);
+            if (pal_img_n) {
+               // pal_img_n == 3 or 4
+               s->img_n = pal_img_n; // record the actual colors we had
+               s->img_out_n = pal_img_n;
+               if (req_comp >= 3) s->img_out_n = req_comp;
+               if (!stbi__expand_png_palette(z, palette, pal_len, s->img_out_n))
+                  return 0;
+            } else if (has_trans) {
+               // non-paletted image with tRNS -> source image has (constant) alpha
+               ++s->img_n;
+            }
+            STBI_FREE(z->expanded); z->expanded = NULL;
+            return 1;
+         }
+
+         default:
+            // if critical, fail
+            if (first) return stbi__err("first not IHDR", "Corrupt PNG");
+            if ((c.type & (1 << 29)) == 0) {
+               #ifndef STBI_NO_FAILURE_STRINGS
+               // not threadsafe
+               static char invalid_chunk[] = "XXXX PNG chunk not known";
+               invalid_chunk[0] = STBI__BYTECAST(c.type >> 24);
+               invalid_chunk[1] = STBI__BYTECAST(c.type >> 16);
+               invalid_chunk[2] = STBI__BYTECAST(c.type >>  8);
+               invalid_chunk[3] = STBI__BYTECAST(c.type >>  0);
+               #endif
+               return stbi__err(invalid_chunk, "PNG not supported: unknown PNG chunk type");
+            }
+            stbi__skip(s, c.length);
+            break;
+      }
+      // end of PNG chunk, read and skip CRC
+      stbi__get32be(s);
+   }
+}
+
+static void *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp, stbi__result_info *ri)
+{
+   void *result=NULL;
+   if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error");
+   if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp)) {
+      if (p->depth < 8)
+         ri->bits_per_channel = 8;
+      else
+         ri->bits_per_channel = p->depth;
+      result = p->out;
+      p->out = NULL;
+      if (req_comp && req_comp != p->s->img_out_n) {
+         if (ri->bits_per_channel == 8)
+            result = stbi__convert_format((unsigned char *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y);
+         else
+            result = stbi__convert_format16((stbi__uint16 *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y);
+         p->s->img_out_n = req_comp;
+         if (result == NULL) return result;
+      }
+      *x = p->s->img_x;
+      *y = p->s->img_y;
+      if (n) *n = p->s->img_n;
+   }
+   STBI_FREE(p->out);      p->out      = NULL;
+   STBI_FREE(p->expanded); p->expanded = NULL;
+   STBI_FREE(p->idata);    p->idata    = NULL;
+
+   return result;
+}
+
+static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri)
+{
+   stbi__png p;
+   p.s = s;
+   return stbi__do_png(&p, x,y,comp,req_comp, ri);
+}
+
+static int stbi__png_test(stbi__context *s)
+{
+   int r;
+   r = stbi__check_png_header(s);
+   stbi__rewind(s);
+   return r;
+}
+
+static int stbi__png_info_raw(stbi__png *p, int *x, int *y, int *comp)
+{
+   if (!stbi__parse_png_file(p, STBI__SCAN_header, 0)) {
+      stbi__rewind( p->s );
+      return 0;
+   }
+   if (x) *x = p->s->img_x;
+   if (y) *y = p->s->img_y;
+   if (comp) *comp = p->s->img_n;
+   return 1;
+}
+
+static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp)
+{
+   stbi__png p;
+   p.s = s;
+   return stbi__png_info_raw(&p, x, y, comp);
+}
+
+static int stbi__png_is16(stbi__context *s)
+{
+   stbi__png p;
+   p.s = s;
+   if (!stbi__png_info_raw(&p, NULL, NULL, NULL))
+	   return 0;
+   if (p.depth != 16) {
+      stbi__rewind(p.s);
+      return 0;
+   }
+   return 1;
+}
+#endif
+
+// Microsoft/Windows BMP image
+
+#ifndef STBI_NO_BMP
+static int stbi__bmp_test_raw(stbi__context *s)
+{
+   int r;
+   int sz;
+   if (stbi__get8(s) != 'B') return 0;
+   if (stbi__get8(s) != 'M') return 0;
+   stbi__get32le(s); // discard filesize
+   stbi__get16le(s); // discard reserved
+   stbi__get16le(s); // discard reserved
+   stbi__get32le(s); // discard data offset
+   sz = stbi__get32le(s);
+   r = (sz == 12 || sz == 40 || sz == 56 || sz == 108 || sz == 124);
+   return r;
+}
+
+static int stbi__bmp_test(stbi__context *s)
+{
+   int r = stbi__bmp_test_raw(s);
+   stbi__rewind(s);
+   return r;
+}
+
+
+// returns 0..31 for the highest set bit
+static int stbi__high_bit(unsigned int z)
+{
+   int n=0;
+   if (z == 0) return -1;
+   if (z >= 0x10000) { n += 16; z >>= 16; }
+   if (z >= 0x00100) { n +=  8; z >>=  8; }
+   if (z >= 0x00010) { n +=  4; z >>=  4; }
+   if (z >= 0x00004) { n +=  2; z >>=  2; }
+   if (z >= 0x00002) { n +=  1; z >>=  1; }
+   return n;
+}
+
+static int stbi__bitcount(unsigned int a)
+{
+   a = (a & 0x55555555) + ((a >>  1) & 0x55555555); // max 2
+   a = (a & 0x33333333) + ((a >>  2) & 0x33333333); // max 4
+   a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits
+   a = (a + (a >> 8)); // max 16 per 8 bits
+   a = (a + (a >> 16)); // max 32 per 8 bits
+   return a & 0xff;
+}
+
+// extract an arbitrarily-aligned N-bit value (N=bits)
+// from v, and then make it 8-bits long and fractionally
+// extend it to full full range.
+static int stbi__shiftsigned(unsigned int v, int shift, int bits)
+{
+   static unsigned int mul_table[9] = {
+      0,
+      0xff/*0b11111111*/, 0x55/*0b01010101*/, 0x49/*0b01001001*/, 0x11/*0b00010001*/,
+      0x21/*0b00100001*/, 0x41/*0b01000001*/, 0x81/*0b10000001*/, 0x01/*0b00000001*/,
+   };
+   static unsigned int shift_table[9] = {
+      0, 0,0,1,0,2,4,6,0,
+   };
+   if (shift < 0)
+      v <<= -shift;
+   else
+      v >>= shift;
+   STBI_ASSERT(v >= 0 && v < 256);
+   v >>= (8-bits);
+   STBI_ASSERT(bits >= 0 && bits <= 8);
+   return (int) ((unsigned) v * mul_table[bits]) >> shift_table[bits];
+}
+
+typedef struct
+{
+   int bpp, offset, hsz;
+   unsigned int mr,mg,mb,ma, all_a;
+} stbi__bmp_data;
+
+static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info)
+{
+   int hsz;
+   if (stbi__get8(s) != 'B' || stbi__get8(s) != 'M') return stbi__errpuc("not BMP", "Corrupt BMP");
+   stbi__get32le(s); // discard filesize
+   stbi__get16le(s); // discard reserved
+   stbi__get16le(s); // discard reserved
+   info->offset = stbi__get32le(s);
+   info->hsz = hsz = stbi__get32le(s);
+   info->mr = info->mg = info->mb = info->ma = 0;
+
+   if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) return stbi__errpuc("unknown BMP", "BMP type not supported: unknown");
+   if (hsz == 12) {
+      s->img_x = stbi__get16le(s);
+      s->img_y = stbi__get16le(s);
+   } else {
+      s->img_x = stbi__get32le(s);
+      s->img_y = stbi__get32le(s);
+   }
+   if (stbi__get16le(s) != 1) return stbi__errpuc("bad BMP", "bad BMP");
+   info->bpp = stbi__get16le(s);
+   if (hsz != 12) {
+      int compress = stbi__get32le(s);
+      if (compress == 1 || compress == 2) return stbi__errpuc("BMP RLE", "BMP type not supported: RLE");
+      stbi__get32le(s); // discard sizeof
+      stbi__get32le(s); // discard hres
+      stbi__get32le(s); // discard vres
+      stbi__get32le(s); // discard colorsused
+      stbi__get32le(s); // discard max important
+      if (hsz == 40 || hsz == 56) {
+         if (hsz == 56) {
+            stbi__get32le(s);
+            stbi__get32le(s);
+            stbi__get32le(s);
+            stbi__get32le(s);
+         }
+         if (info->bpp == 16 || info->bpp == 32) {
+            if (compress == 0) {
+               if (info->bpp == 32) {
+                  info->mr = 0xffu << 16;
+                  info->mg = 0xffu <<  8;
+                  info->mb = 0xffu <<  0;
+                  info->ma = 0xffu << 24;
+                  info->all_a = 0; // if all_a is 0 at end, then we loaded alpha channel but it was all 0
+               } else {
+                  info->mr = 31u << 10;
+                  info->mg = 31u <<  5;
+                  info->mb = 31u <<  0;
+               }
+            } else if (compress == 3) {
+               info->mr = stbi__get32le(s);
+               info->mg = stbi__get32le(s);
+               info->mb = stbi__get32le(s);
+               // not documented, but generated by photoshop and handled by mspaint
+               if (info->mr == info->mg && info->mg == info->mb) {
+                  // ?!?!?
+                  return stbi__errpuc("bad BMP", "bad BMP");
+               }
+            } else
+               return stbi__errpuc("bad BMP", "bad BMP");
+         }
+      } else {
+         int i;
+         if (hsz != 108 && hsz != 124)
+            return stbi__errpuc("bad BMP", "bad BMP");
+         info->mr = stbi__get32le(s);
+         info->mg = stbi__get32le(s);
+         info->mb = stbi__get32le(s);
+         info->ma = stbi__get32le(s);
+         stbi__get32le(s); // discard color space
+         for (i=0; i < 12; ++i)
+            stbi__get32le(s); // discard color space parameters
+         if (hsz == 124) {
+            stbi__get32le(s); // discard rendering intent
+            stbi__get32le(s); // discard offset of profile data
+            stbi__get32le(s); // discard size of profile data
+            stbi__get32le(s); // discard reserved
+         }
+      }
+   }
+   return (void *) 1;
+}
+
+
+static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri)
+{
+   stbi_uc *out;
+   unsigned int mr=0,mg=0,mb=0,ma=0, all_a;
+   stbi_uc pal[256][4];
+   int psize=0,i,j,width;
+   int flip_vertically, pad, target;
+   stbi__bmp_data info;
+   STBI_NOTUSED(ri);
+
+   info.all_a = 255;
+   if (stbi__bmp_parse_header(s, &info) == NULL)
+      return NULL; // error code already set
+
+   flip_vertically = ((int) s->img_y) > 0;
+   s->img_y = abs((int) s->img_y);
+
+   mr = info.mr;
+   mg = info.mg;
+   mb = info.mb;
+   ma = info.ma;
+   all_a = info.all_a;
+
+   if (info.hsz == 12) {
+      if (info.bpp < 24)
+         psize = (info.offset - 14 - 24) / 3;
+   } else {
+      if (info.bpp < 16)
+         psize = (info.offset - 14 - info.hsz) >> 2;
+   }
+
+   s->img_n = ma ? 4 : 3;
+   if (req_comp && req_comp >= 3) // we can directly decode 3 or 4
+      target = req_comp;
+   else
+      target = s->img_n; // if they want monochrome, we'll post-convert
+
+   // sanity-check size
+   if (!stbi__mad3sizes_valid(target, s->img_x, s->img_y, 0))
+      return stbi__errpuc("too large", "Corrupt BMP");
+
+   out = (stbi_uc *) stbi__malloc_mad3(target, s->img_x, s->img_y, 0);
+   if (!out) return stbi__errpuc("outofmem", "Out of memory");
+   if (info.bpp < 16) {
+      int z=0;
+      if (psize == 0 || psize > 256) { STBI_FREE(out); return stbi__errpuc("invalid", "Corrupt BMP"); }
+      for (i=0; i < psize; ++i) {
+         pal[i][2] = stbi__get8(s);
+         pal[i][1] = stbi__get8(s);
+         pal[i][0] = stbi__get8(s);
+         if (info.hsz != 12) stbi__get8(s);
+         pal[i][3] = 255;
+      }
+      stbi__skip(s, info.offset - 14 - info.hsz - psize * (info.hsz == 12 ? 3 : 4));
+      if (info.bpp == 1) width = (s->img_x + 7) >> 3;
+      else if (info.bpp == 4) width = (s->img_x + 1) >> 1;
+      else if (info.bpp == 8) width = s->img_x;
+      else { STBI_FREE(out); return stbi__errpuc("bad bpp", "Corrupt BMP"); }
+      pad = (-width)&3;
+      if (info.bpp == 1) {
+         for (j=0; j < (int) s->img_y; ++j) {
+            int bit_offset = 7, v = stbi__get8(s);
+            for (i=0; i < (int) s->img_x; ++i) {
+               int color = (v>>bit_offset)&0x1;
+               out[z++] = pal[color][0];
+               out[z++] = pal[color][1];
+               out[z++] = pal[color][2];
+               if (target == 4) out[z++] = 255;
+               if (i+1 == (int) s->img_x) break;
+               if((--bit_offset) < 0) {
+                  bit_offset = 7;
+                  v = stbi__get8(s);
+               }
+            }
+            stbi__skip(s, pad);
+         }
+      } else {
+         for (j=0; j < (int) s->img_y; ++j) {
+            for (i=0; i < (int) s->img_x; i += 2) {
+               int v=stbi__get8(s),v2=0;
+               if (info.bpp == 4) {
+                  v2 = v & 15;
+                  v >>= 4;
+               }
+               out[z++] = pal[v][0];
+               out[z++] = pal[v][1];
+               out[z++] = pal[v][2];
+               if (target == 4) out[z++] = 255;
+               if (i+1 == (int) s->img_x) break;
+               v = (info.bpp == 8) ? stbi__get8(s) : v2;
+               out[z++] = pal[v][0];
+               out[z++] = pal[v][1];
+               out[z++] = pal[v][2];
+               if (target == 4) out[z++] = 255;
+            }
+            stbi__skip(s, pad);
+         }
+      }
+   } else {
+      int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0;
+      int z = 0;
+      int easy=0;
+      stbi__skip(s, info.offset - 14 - info.hsz);
+      if (info.bpp == 24) width = 3 * s->img_x;
+      else if (info.bpp == 16) width = 2*s->img_x;
+      else /* bpp = 32 and pad = 0 */ width=0;
+      pad = (-width) & 3;
+      if (info.bpp == 24) {
+         easy = 1;
+      } else if (info.bpp == 32) {
+         if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000)
+            easy = 2;
+      }
+      if (!easy) {
+         if (!mr || !mg || !mb) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); }
+         // right shift amt to put high bit in position #7
+         rshift = stbi__high_bit(mr)-7; rcount = stbi__bitcount(mr);
+         gshift = stbi__high_bit(mg)-7; gcount = stbi__bitcount(mg);
+         bshift = stbi__high_bit(mb)-7; bcount = stbi__bitcount(mb);
+         ashift = stbi__high_bit(ma)-7; acount = stbi__bitcount(ma);
+      }
+      for (j=0; j < (int) s->img_y; ++j) {
+         if (easy) {
+            for (i=0; i < (int) s->img_x; ++i) {
+               unsigned char a;
+               out[z+2] = stbi__get8(s);
+               out[z+1] = stbi__get8(s);
+               out[z+0] = stbi__get8(s);
+               z += 3;
+               a = (easy == 2 ? stbi__get8(s) : 255);
+               all_a |= a;
+               if (target == 4) out[z++] = a;
+            }
+         } else {
+            int bpp = info.bpp;
+            for (i=0; i < (int) s->img_x; ++i) {
+               stbi__uint32 v = (bpp == 16 ? (stbi__uint32) stbi__get16le(s) : stbi__get32le(s));
+               unsigned int a;
+               out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mr, rshift, rcount));
+               out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount));
+               out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mb, bshift, bcount));
+               a = (ma ? stbi__shiftsigned(v & ma, ashift, acount) : 255);
+               all_a |= a;
+               if (target == 4) out[z++] = STBI__BYTECAST(a);
+            }
+         }
+         stbi__skip(s, pad);
+      }
+   }
+
+   // if alpha channel is all 0s, replace with all 255s
+   if (target == 4 && all_a == 0)
+      for (i=4*s->img_x*s->img_y-1; i >= 0; i -= 4)
+         out[i] = 255;
+
+   if (flip_vertically) {
+      stbi_uc t;
+      for (j=0; j < (int) s->img_y>>1; ++j) {
+         stbi_uc *p1 = out +      j     *s->img_x*target;
+         stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target;
+         for (i=0; i < (int) s->img_x*target; ++i) {
+            t = p1[i]; p1[i] = p2[i]; p2[i] = t;
+         }
+      }
+   }
+
+   if (req_comp && req_comp != target) {
+      out = stbi__convert_format(out, target, req_comp, s->img_x, s->img_y);
+      if (out == NULL) return out; // stbi__convert_format frees input on failure
+   }
+
+   *x = s->img_x;
+   *y = s->img_y;
+   if (comp) *comp = s->img_n;
+   return out;
+}
+#endif
+
+// Targa Truevision - TGA
+// by Jonathan Dummer
+#ifndef STBI_NO_TGA
+// returns STBI_rgb or whatever, 0 on error
+static int stbi__tga_get_comp(int bits_per_pixel, int is_grey, int* is_rgb16)
+{
+   // only RGB or RGBA (incl. 16bit) or grey allowed
+   if (is_rgb16) *is_rgb16 = 0;
+   switch(bits_per_pixel) {
+      case 8:  return STBI_grey;
+      case 16: if(is_grey) return STBI_grey_alpha;
+               // fallthrough
+      case 15: if(is_rgb16) *is_rgb16 = 1;
+               return STBI_rgb;
+      case 24: // fallthrough
+      case 32: return bits_per_pixel/8;
+      default: return 0;
+   }
+}
+
+static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp)
+{
+    int tga_w, tga_h, tga_comp, tga_image_type, tga_bits_per_pixel, tga_colormap_bpp;
+    int sz, tga_colormap_type;
+    stbi__get8(s);                   // discard Offset
+    tga_colormap_type = stbi__get8(s); // colormap type
+    if( tga_colormap_type > 1 ) {
+        stbi__rewind(s);
+        return 0;      // only RGB or indexed allowed
+    }
+    tga_image_type = stbi__get8(s); // image type
+    if ( tga_colormap_type == 1 ) { // colormapped (paletted) image
+        if (tga_image_type != 1 && tga_image_type != 9) {
+            stbi__rewind(s);
+            return 0;
+        }
+        stbi__skip(s,4);       // skip index of first colormap entry and number of entries
+        sz = stbi__get8(s);    //   check bits per palette color entry
+        if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) {
+            stbi__rewind(s);
+            return 0;
+        }
+        stbi__skip(s,4);       // skip image x and y origin
+        tga_colormap_bpp = sz;
+    } else { // "normal" image w/o colormap - only RGB or grey allowed, +/- RLE
+        if ( (tga_image_type != 2) && (tga_image_type != 3) && (tga_image_type != 10) && (tga_image_type != 11) ) {
+            stbi__rewind(s);
+            return 0; // only RGB or grey allowed, +/- RLE
+        }
+        stbi__skip(s,9); // skip colormap specification and image x/y origin
+        tga_colormap_bpp = 0;
+    }
+    tga_w = stbi__get16le(s);
+    if( tga_w < 1 ) {
+        stbi__rewind(s);
+        return 0;   // test width
+    }
+    tga_h = stbi__get16le(s);
+    if( tga_h < 1 ) {
+        stbi__rewind(s);
+        return 0;   // test height
+    }
+    tga_bits_per_pixel = stbi__get8(s); // bits per pixel
+    stbi__get8(s); // ignore alpha bits
+    if (tga_colormap_bpp != 0) {
+        if((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16)) {
+            // when using a colormap, tga_bits_per_pixel is the size of the indexes
+            // I don't think anything but 8 or 16bit indexes makes sense
+            stbi__rewind(s);
+            return 0;
+        }
+        tga_comp = stbi__tga_get_comp(tga_colormap_bpp, 0, NULL);
+    } else {
+        tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3) || (tga_image_type == 11), NULL);
+    }
+    if(!tga_comp) {
+      stbi__rewind(s);
+      return 0;
+    }
+    if (x) *x = tga_w;
+    if (y) *y = tga_h;
+    if (comp) *comp = tga_comp;
+    return 1;                   // seems to have passed everything
+}
+
+static int stbi__tga_test(stbi__context *s)
+{
+   int res = 0;
+   int sz, tga_color_type;
+   stbi__get8(s);      //   discard Offset
+   tga_color_type = stbi__get8(s);   //   color type
+   if ( tga_color_type > 1 ) goto errorEnd;   //   only RGB or indexed allowed
+   sz = stbi__get8(s);   //   image type
+   if ( tga_color_type == 1 ) { // colormapped (paletted) image
+      if (sz != 1 && sz != 9) goto errorEnd; // colortype 1 demands image type 1 or 9
+      stbi__skip(s,4);       // skip index of first colormap entry and number of entries
+      sz = stbi__get8(s);    //   check bits per palette color entry
+      if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd;
+      stbi__skip(s,4);       // skip image x and y origin
+   } else { // "normal" image w/o colormap
+      if ( (sz != 2) && (sz != 3) && (sz != 10) && (sz != 11) ) goto errorEnd; // only RGB or grey allowed, +/- RLE
+      stbi__skip(s,9); // skip colormap specification and image x/y origin
+   }
+   if ( stbi__get16le(s) < 1 ) goto errorEnd;      //   test width
+   if ( stbi__get16le(s) < 1 ) goto errorEnd;      //   test height
+   sz = stbi__get8(s);   //   bits per pixel
+   if ( (tga_color_type == 1) && (sz != 8) && (sz != 16) ) goto errorEnd; // for colormapped images, bpp is size of an index
+   if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd;
+
+   res = 1; // if we got this far, everything's good and we can return 1 instead of 0
+
+errorEnd:
+   stbi__rewind(s);
+   return res;
+}
+
+// read 16bit value and convert to 24bit RGB
+static void stbi__tga_read_rgb16(stbi__context *s, stbi_uc* out)
+{
+   stbi__uint16 px = (stbi__uint16)stbi__get16le(s);
+   stbi__uint16 fiveBitMask = 31;
+   // we have 3 channels with 5bits each
+   int r = (px >> 10) & fiveBitMask;
+   int g = (px >> 5) & fiveBitMask;
+   int b = px & fiveBitMask;
+   // Note that this saves the data in RGB(A) order, so it doesn't need to be swapped later
+   out[0] = (stbi_uc)((r * 255)/31);
+   out[1] = (stbi_uc)((g * 255)/31);
+   out[2] = (stbi_uc)((b * 255)/31);
+
+   // some people claim that the most significant bit might be used for alpha
+   // (possibly if an alpha-bit is set in the "image descriptor byte")
+   // but that only made 16bit test images completely translucent..
+   // so let's treat all 15 and 16bit TGAs as RGB with no alpha.
+}
+
+static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri)
+{
+   //   read in the TGA header stuff
+   int tga_offset = stbi__get8(s);
+   int tga_indexed = stbi__get8(s);
+   int tga_image_type = stbi__get8(s);
+   int tga_is_RLE = 0;
+   int tga_palette_start = stbi__get16le(s);
+   int tga_palette_len = stbi__get16le(s);
+   int tga_palette_bits = stbi__get8(s);
+   int tga_x_origin = stbi__get16le(s);
+   int tga_y_origin = stbi__get16le(s);
+   int tga_width = stbi__get16le(s);
+   int tga_height = stbi__get16le(s);
+   int tga_bits_per_pixel = stbi__get8(s);
+   int tga_comp, tga_rgb16=0;
+   int tga_inverted = stbi__get8(s);
+   // int tga_alpha_bits = tga_inverted & 15; // the 4 lowest bits - unused (useless?)
+   //   image data
+   unsigned char *tga_data;
+   unsigned char *tga_palette = NULL;
+   int i, j;
+   unsigned char raw_data[4] = {0};
+   int RLE_count = 0;
+   int RLE_repeating = 0;
+   int read_next_pixel = 1;
+   STBI_NOTUSED(ri);
+
+   //   do a tiny bit of precessing
+   if ( tga_image_type >= 8 )
+   {
+      tga_image_type -= 8;
+      tga_is_RLE = 1;
+   }
+   tga_inverted = 1 - ((tga_inverted >> 5) & 1);
+
+   //   If I'm paletted, then I'll use the number of bits from the palette
+   if ( tga_indexed ) tga_comp = stbi__tga_get_comp(tga_palette_bits, 0, &tga_rgb16);
+   else tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3), &tga_rgb16);
+
+   if(!tga_comp) // shouldn't really happen, stbi__tga_test() should have ensured basic consistency
+      return stbi__errpuc("bad format", "Can't find out TGA pixelformat");
+
+   //   tga info
+   *x = tga_width;
+   *y = tga_height;
+   if (comp) *comp = tga_comp;
+
+   if (!stbi__mad3sizes_valid(tga_width, tga_height, tga_comp, 0))
+      return stbi__errpuc("too large", "Corrupt TGA");
+
+   tga_data = (unsigned char*)stbi__malloc_mad3(tga_width, tga_height, tga_comp, 0);
+   if (!tga_data) return stbi__errpuc("outofmem", "Out of memory");
+
+   // skip to the data's starting position (offset usually = 0)
+   stbi__skip(s, tga_offset );
+
+   if ( !tga_indexed && !tga_is_RLE && !tga_rgb16 ) {
+      for (i=0; i < tga_height; ++i) {
+         int row = tga_inverted ? tga_height -i - 1 : i;
+         stbi_uc *tga_row = tga_data + row*tga_width*tga_comp;
+         stbi__getn(s, tga_row, tga_width * tga_comp);
+      }
+   } else  {
+      //   do I need to load a palette?
+      if ( tga_indexed)
+      {
+         //   any data to skip? (offset usually = 0)
+         stbi__skip(s, tga_palette_start );
+         //   load the palette
+         tga_palette = (unsigned char*)stbi__malloc_mad2(tga_palette_len, tga_comp, 0);
+         if (!tga_palette) {
+            STBI_FREE(tga_data);
+            return stbi__errpuc("outofmem", "Out of memory");
+         }
+         if (tga_rgb16) {
+            stbi_uc *pal_entry = tga_palette;
+            STBI_ASSERT(tga_comp == STBI_rgb);
+            for (i=0; i < tga_palette_len; ++i) {
+               stbi__tga_read_rgb16(s, pal_entry);
+               pal_entry += tga_comp;
+            }
+         } else if (!stbi__getn(s, tga_palette, tga_palette_len * tga_comp)) {
+               STBI_FREE(tga_data);
+               STBI_FREE(tga_palette);
+               return stbi__errpuc("bad palette", "Corrupt TGA");
+         }
+      }
+      //   load the data
+      for (i=0; i < tga_width * tga_height; ++i)
+      {
+         //   if I'm in RLE mode, do I need to get a RLE stbi__pngchunk?
+         if ( tga_is_RLE )
+         {
+            if ( RLE_count == 0 )
+            {
+               //   yep, get the next byte as a RLE command
+               int RLE_cmd = stbi__get8(s);
+               RLE_count = 1 + (RLE_cmd & 127);
+               RLE_repeating = RLE_cmd >> 7;
+               read_next_pixel = 1;
+            } else if ( !RLE_repeating )
+            {
+               read_next_pixel = 1;
+            }
+         } else
+         {
+            read_next_pixel = 1;
+         }
+         //   OK, if I need to read a pixel, do it now
+         if ( read_next_pixel )
+         {
+            //   load however much data we did have
+            if ( tga_indexed )
+            {
+               // read in index, then perform the lookup
+               int pal_idx = (tga_bits_per_pixel == 8) ? stbi__get8(s) : stbi__get16le(s);
+               if ( pal_idx >= tga_palette_len ) {
+                  // invalid index
+                  pal_idx = 0;
+               }
+               pal_idx *= tga_comp;
+               for (j = 0; j < tga_comp; ++j) {
+                  raw_data[j] = tga_palette[pal_idx+j];
+               }
+            } else if(tga_rgb16) {
+               STBI_ASSERT(tga_comp == STBI_rgb);
+               stbi__tga_read_rgb16(s, raw_data);
+            } else {
+               //   read in the data raw
+               for (j = 0; j < tga_comp; ++j) {
+                  raw_data[j] = stbi__get8(s);
+               }
+            }
+            //   clear the reading flag for the next pixel
+            read_next_pixel = 0;
+         } // end of reading a pixel
+
+         // copy data
+         for (j = 0; j < tga_comp; ++j)
+           tga_data[i*tga_comp+j] = raw_data[j];
+
+         //   in case we're in RLE mode, keep counting down
+         --RLE_count;
+      }
+      //   do I need to invert the image?
+      if ( tga_inverted )
+      {
+         for (j = 0; j*2 < tga_height; ++j)
+         {
+            int index1 = j * tga_width * tga_comp;
+            int index2 = (tga_height - 1 - j) * tga_width * tga_comp;
+            for (i = tga_width * tga_comp; i > 0; --i)
+            {
+               unsigned char temp = tga_data[index1];
+               tga_data[index1] = tga_data[index2];
+               tga_data[index2] = temp;
+               ++index1;
+               ++index2;
+            }
+         }
+      }
+      //   clear my palette, if I had one
+      if ( tga_palette != NULL )
+      {
+         STBI_FREE( tga_palette );
+      }
+   }
+
+   // swap RGB - if the source data was RGB16, it already is in the right order
+   if (tga_comp >= 3 && !tga_rgb16)
+   {
+      unsigned char* tga_pixel = tga_data;
+      for (i=0; i < tga_width * tga_height; ++i)
+      {
+         unsigned char temp = tga_pixel[0];
+         tga_pixel[0] = tga_pixel[2];
+         tga_pixel[2] = temp;
+         tga_pixel += tga_comp;
+      }
+   }
+
+   // convert to target component count
+   if (req_comp && req_comp != tga_comp)
+      tga_data = stbi__convert_format(tga_data, tga_comp, req_comp, tga_width, tga_height);
+
+   //   the things I do to get rid of an error message, and yet keep
+   //   Microsoft's C compilers happy... [8^(
+   tga_palette_start = tga_palette_len = tga_palette_bits =
+         tga_x_origin = tga_y_origin = 0;
+   //   OK, done
+   return tga_data;
+}
+#endif
+
+// *************************************************************************************************
+// Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB
+
+#ifndef STBI_NO_PSD
+static int stbi__psd_test(stbi__context *s)
+{
+   int r = (stbi__get32be(s) == 0x38425053);
+   stbi__rewind(s);
+   return r;
+}
+
+static int stbi__psd_decode_rle(stbi__context *s, stbi_uc *p, int pixelCount)
+{
+   int count, nleft, len;
+
+   count = 0;
+   while ((nleft = pixelCount - count) > 0) {
+      len = stbi__get8(s);
+      if (len == 128) {
+         // No-op.
+      } else if (len < 128) {
+         // Copy next len+1 bytes literally.
+         len++;
+         if (len > nleft) return 0; // corrupt data
+         count += len;
+         while (len) {
+            *p = stbi__get8(s);
+            p += 4;
+            len--;
+         }
+      } else if (len > 128) {
+         stbi_uc   val;
+         // Next -len+1 bytes in the dest are replicated from next source byte.
+         // (Interpret len as a negative 8-bit int.)
+         len = 257 - len;
+         if (len > nleft) return 0; // corrupt data
+         val = stbi__get8(s);
+         count += len;
+         while (len) {
+            *p = val;
+            p += 4;
+            len--;
+         }
+      }
+   }
+
+   return 1;
+}
+
+static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc)
+{
+   int pixelCount;
+   int channelCount, compression;
+   int channel, i;
+   int bitdepth;
+   int w,h;
+   stbi_uc *out;
+   STBI_NOTUSED(ri);
+
+   // Check identifier
+   if (stbi__get32be(s) != 0x38425053)   // "8BPS"
+      return stbi__errpuc("not PSD", "Corrupt PSD image");
+
+   // Check file type version.
+   if (stbi__get16be(s) != 1)
+      return stbi__errpuc("wrong version", "Unsupported version of PSD image");
+
+   // Skip 6 reserved bytes.
+   stbi__skip(s, 6 );
+
+   // Read the number of channels (R, G, B, A, etc).
+   channelCount = stbi__get16be(s);
+   if (channelCount < 0 || channelCount > 16)
+      return stbi__errpuc("wrong channel count", "Unsupported number of channels in PSD image");
+
+   // Read the rows and columns of the image.
+   h = stbi__get32be(s);
+   w = stbi__get32be(s);
+
+   // Make sure the depth is 8 bits.
+   bitdepth = stbi__get16be(s);
+   if (bitdepth != 8 && bitdepth != 16)
+      return stbi__errpuc("unsupported bit depth", "PSD bit depth is not 8 or 16 bit");
+
+   // Make sure the color mode is RGB.
+   // Valid options are:
+   //   0: Bitmap
+   //   1: Grayscale
+   //   2: Indexed color
+   //   3: RGB color
+   //   4: CMYK color
+   //   7: Multichannel
+   //   8: Duotone
+   //   9: Lab color
+   if (stbi__get16be(s) != 3)
+      return stbi__errpuc("wrong color format", "PSD is not in RGB color format");
+
+   // Skip the Mode Data.  (It's the palette for indexed color; other info for other modes.)
+   stbi__skip(s,stbi__get32be(s) );
+
+   // Skip the image resources.  (resolution, pen tool paths, etc)
+   stbi__skip(s, stbi__get32be(s) );
+
+   // Skip the reserved data.
+   stbi__skip(s, stbi__get32be(s) );
+
+   // Find out if the data is compressed.
+   // Known values:
+   //   0: no compression
+   //   1: RLE compressed
+   compression = stbi__get16be(s);
+   if (compression > 1)
+      return stbi__errpuc("bad compression", "PSD has an unknown compression format");
+
+   // Check size
+   if (!stbi__mad3sizes_valid(4, w, h, 0))
+      return stbi__errpuc("too large", "Corrupt PSD");
+
+   // Create the destination image.
+
+   if (!compression && bitdepth == 16 && bpc == 16) {
+      out = (stbi_uc *) stbi__malloc_mad3(8, w, h, 0);
+      ri->bits_per_channel = 16;
+   } else
+      out = (stbi_uc *) stbi__malloc(4 * w*h);
+
+   if (!out) return stbi__errpuc("outofmem", "Out of memory");
+   pixelCount = w*h;
+
+   // Initialize the data to zero.
+   //memset( out, 0, pixelCount * 4 );
+
+   // Finally, the image data.
+   if (compression) {
+      // RLE as used by .PSD and .TIFF
+      // Loop until you get the number of unpacked bytes you are expecting:
+      //     Read the next source byte into n.
+      //     If n is between 0 and 127 inclusive, copy the next n+1 bytes literally.
+      //     Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times.
+      //     Else if n is 128, noop.
+      // Endloop
+
+      // The RLE-compressed data is preceded by a 2-byte data count for each row in the data,
+      // which we're going to just skip.
+      stbi__skip(s, h * channelCount * 2 );
+
+      // Read the RLE data by channel.
+      for (channel = 0; channel < 4; channel++) {
+         stbi_uc *p;
+
+         p = out+channel;
+         if (channel >= channelCount) {
+            // Fill this channel with default data.
+            for (i = 0; i < pixelCount; i++, p += 4)
+               *p = (channel == 3 ? 255 : 0);
+         } else {
+            // Read the RLE data.
+            if (!stbi__psd_decode_rle(s, p, pixelCount)) {
+               STBI_FREE(out);
+               return stbi__errpuc("corrupt", "bad RLE data");
+            }
+         }
+      }
+
+   } else {
+      // We're at the raw image data.  It's each channel in order (Red, Green, Blue, Alpha, ...)
+      // where each channel consists of an 8-bit (or 16-bit) value for each pixel in the image.
+
+      // Read the data by channel.
+      for (channel = 0; channel < 4; channel++) {
+         if (channel >= channelCount) {
+            // Fill this channel with default data.
+            if (bitdepth == 16 && bpc == 16) {
+               stbi__uint16 *q = ((stbi__uint16 *) out) + channel;
+               stbi__uint16 val = channel == 3 ? 65535 : 0;
+               for (i = 0; i < pixelCount; i++, q += 4)
+                  *q = val;
+            } else {
+               stbi_uc *p = out+channel;
+               stbi_uc val = channel == 3 ? 255 : 0;
+               for (i = 0; i < pixelCount; i++, p += 4)
+                  *p = val;
+            }
+         } else {
+            if (ri->bits_per_channel == 16) {    // output bpc
+               stbi__uint16 *q = ((stbi__uint16 *) out) + channel;
+               for (i = 0; i < pixelCount; i++, q += 4)
+                  *q = (stbi__uint16) stbi__get16be(s);
+            } else {
+               stbi_uc *p = out+channel;
+               if (bitdepth == 16) {  // input bpc
+                  for (i = 0; i < pixelCount; i++, p += 4)
+                     *p = (stbi_uc) (stbi__get16be(s) >> 8);
+               } else {
+                  for (i = 0; i < pixelCount; i++, p += 4)
+                     *p = stbi__get8(s);
+               }
+            }
+         }
+      }
+   }
+
+   // remove weird white matte from PSD
+   if (channelCount >= 4) {
+      if (ri->bits_per_channel == 16) {
+         for (i=0; i < w*h; ++i) {
+            stbi__uint16 *pixel = (stbi__uint16 *) out + 4*i;
+            if (pixel[3] != 0 && pixel[3] != 65535) {
+               float a = pixel[3] / 65535.0f;
+               float ra = 1.0f / a;
+               float inv_a = 65535.0f * (1 - ra);
+               pixel[0] = (stbi__uint16) (pixel[0]*ra + inv_a);
+               pixel[1] = (stbi__uint16) (pixel[1]*ra + inv_a);
+               pixel[2] = (stbi__uint16) (pixel[2]*ra + inv_a);
+            }
+         }
+      } else {
+         for (i=0; i < w*h; ++i) {
+            unsigned char *pixel = out + 4*i;
+            if (pixel[3] != 0 && pixel[3] != 255) {
+               float a = pixel[3] / 255.0f;
+               float ra = 1.0f / a;
+               float inv_a = 255.0f * (1 - ra);
+               pixel[0] = (unsigned char) (pixel[0]*ra + inv_a);
+               pixel[1] = (unsigned char) (pixel[1]*ra + inv_a);
+               pixel[2] = (unsigned char) (pixel[2]*ra + inv_a);
+            }
+         }
+      }
+   }
+
+   // convert to desired output format
+   if (req_comp && req_comp != 4) {
+      if (ri->bits_per_channel == 16)
+         out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, 4, req_comp, w, h);
+      else
+         out = stbi__convert_format(out, 4, req_comp, w, h);
+      if (out == NULL) return out; // stbi__convert_format frees input on failure
+   }
+
+   if (comp) *comp = 4;
+   *y = h;
+   *x = w;
+
+   return out;
+}
+#endif
+
+// *************************************************************************************************
+// Softimage PIC loader
+// by Tom Seddon
+//
+// See http://softimage.wiki.softimage.com/index.php/INFO:_PIC_file_format
+// See http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/softimagepic/
+
+#ifndef STBI_NO_PIC
+static int stbi__pic_is4(stbi__context *s,const char *str)
+{
+   int i;
+   for (i=0; i<4; ++i)
+      if (stbi__get8(s) != (stbi_uc)str[i])
+         return 0;
+
+   return 1;
+}
+
+static int stbi__pic_test_core(stbi__context *s)
+{
+   int i;
+
+   if (!stbi__pic_is4(s,"\x53\x80\xF6\x34"))
+      return 0;
+
+   for(i=0;i<84;++i)
+      stbi__get8(s);
+
+   if (!stbi__pic_is4(s,"PICT"))
+      return 0;
+
+   return 1;
+}
+
+typedef struct
+{
+   stbi_uc size,type,channel;
+} stbi__pic_packet;
+
+static stbi_uc *stbi__readval(stbi__context *s, int channel, stbi_uc *dest)
+{
+   int mask=0x80, i;
+
+   for (i=0; i<4; ++i, mask>>=1) {
+      if (channel & mask) {
+         if (stbi__at_eof(s)) return stbi__errpuc("bad file","PIC file too short");
+         dest[i]=stbi__get8(s);
+      }
+   }
+
+   return dest;
+}
+
+static void stbi__copyval(int channel,stbi_uc *dest,const stbi_uc *src)
+{
+   int mask=0x80,i;
+
+   for (i=0;i<4; ++i, mask>>=1)
+      if (channel&mask)
+         dest[i]=src[i];
+}
+
+static stbi_uc *stbi__pic_load_core(stbi__context *s,int width,int height,int *comp, stbi_uc *result)
+{
+   int act_comp=0,num_packets=0,y,chained;
+   stbi__pic_packet packets[10];
+
+   // this will (should...) cater for even some bizarre stuff like having data
+    // for the same channel in multiple packets.
+   do {
+      stbi__pic_packet *packet;
+
+      if (num_packets==sizeof(packets)/sizeof(packets[0]))
+         return stbi__errpuc("bad format","too many packets");
+
+      packet = &packets[num_packets++];
+
+      chained = stbi__get8(s);
+      packet->size    = stbi__get8(s);
+      packet->type    = stbi__get8(s);
+      packet->channel = stbi__get8(s);
+
+      act_comp |= packet->channel;
+
+      if (stbi__at_eof(s))          return stbi__errpuc("bad file","file too short (reading packets)");
+      if (packet->size != 8)  return stbi__errpuc("bad format","packet isn't 8bpp");
+   } while (chained);
+
+   *comp = (act_comp & 0x10 ? 4 : 3); // has alpha channel?
+
+   for(y=0; y<height; ++y) {
+      int packet_idx;
+
+      for(packet_idx=0; packet_idx < num_packets; ++packet_idx) {
+         stbi__pic_packet *packet = &packets[packet_idx];
+         stbi_uc *dest = result+y*width*4;
+
+         switch (packet->type) {
+            default:
+               return stbi__errpuc("bad format","packet has bad compression type");
+
+            case 0: {//uncompressed
+               int x;
+
+               for(x=0;x<width;++x, dest+=4)
+                  if (!stbi__readval(s,packet->channel,dest))
+                     return 0;
+               break;
+            }
+
+            case 1://Pure RLE
+               {
+                  int left=width, i;
+
+                  while (left>0) {
+                     stbi_uc count,value[4];
+
+                     count=stbi__get8(s);
+                     if (stbi__at_eof(s))   return stbi__errpuc("bad file","file too short (pure read count)");
+
+                     if (count > left)
+                        count = (stbi_uc) left;
+
+                     if (!stbi__readval(s,packet->channel,value))  return 0;
+
+                     for(i=0; i<count; ++i,dest+=4)
+                        stbi__copyval(packet->channel,dest,value);
+                     left -= count;
+                  }
+               }
+               break;
+
+            case 2: {//Mixed RLE
+               int left=width;
+               while (left>0) {
+                  int count = stbi__get8(s), i;
+                  if (stbi__at_eof(s))  return stbi__errpuc("bad file","file too short (mixed read count)");
+
+                  if (count >= 128) { // Repeated
+                     stbi_uc value[4];
+
+                     if (count==128)
+                        count = stbi__get16be(s);
+                     else
+                        count -= 127;
+                     if (count > left)
+                        return stbi__errpuc("bad file","scanline overrun");
+
+                     if (!stbi__readval(s,packet->channel,value))
+                        return 0;
+
+                     for(i=0;i<count;++i, dest += 4)
+                        stbi__copyval(packet->channel,dest,value);
+                  } else { // Raw
+                     ++count;
+                     if (count>left) return stbi__errpuc("bad file","scanline overrun");
+
+                     for(i=0;i<count;++i, dest+=4)
+                        if (!stbi__readval(s,packet->channel,dest))
+                           return 0;
+                  }
+                  left-=count;
+               }
+               break;
+            }
+         }
+      }
+   }
+
+   return result;
+}
+
+static void *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_comp, stbi__result_info *ri)
+{
+   stbi_uc *result;
+   int i, x,y, internal_comp;
+   STBI_NOTUSED(ri);
+
+   if (!comp) comp = &internal_comp;
+
+   for (i=0; i<92; ++i)
+      stbi__get8(s);
+
+   x = stbi__get16be(s);
+   y = stbi__get16be(s);
+   if (stbi__at_eof(s))  return stbi__errpuc("bad file","file too short (pic header)");
+   if (!stbi__mad3sizes_valid(x, y, 4, 0)) return stbi__errpuc("too large", "PIC image too large to decode");
+
+   stbi__get32be(s); //skip `ratio'
+   stbi__get16be(s); //skip `fields'
+   stbi__get16be(s); //skip `pad'
+
+   // intermediate buffer is RGBA
+   result = (stbi_uc *) stbi__malloc_mad3(x, y, 4, 0);
+   memset(result, 0xff, x*y*4);
+
+   if (!stbi__pic_load_core(s,x,y,comp, result)) {
+      STBI_FREE(result);
+      result=0;
+   }
+   *px = x;
+   *py = y;
+   if (req_comp == 0) req_comp = *comp;
+   result=stbi__convert_format(result,4,req_comp,x,y);
+
+   return result;
+}
+
+static int stbi__pic_test(stbi__context *s)
+{
+   int r = stbi__pic_test_core(s);
+   stbi__rewind(s);
+   return r;
+}
+#endif
+
+// *************************************************************************************************
+// GIF loader -- public domain by Jean-Marc Lienher -- simplified/shrunk by stb
+
+#ifndef STBI_NO_GIF
+typedef struct
+{
+   stbi__int16 prefix;
+   stbi_uc first;
+   stbi_uc suffix;
+} stbi__gif_lzw;
+
+typedef struct
+{
+   int w,h;
+   stbi_uc *out;                 // output buffer (always 4 components)
+   stbi_uc *background;          // The current "background" as far as a gif is concerned
+   stbi_uc *history; 
+   int flags, bgindex, ratio, transparent, eflags;
+   stbi_uc  pal[256][4];
+   stbi_uc lpal[256][4];
+   stbi__gif_lzw codes[8192];
+   stbi_uc *color_table;
+   int parse, step;
+   int lflags;
+   int start_x, start_y;
+   int max_x, max_y;
+   int cur_x, cur_y;
+   int line_size;
+   int delay;
+} stbi__gif;
+
+static int stbi__gif_test_raw(stbi__context *s)
+{
+   int sz;
+   if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') return 0;
+   sz = stbi__get8(s);
+   if (sz != '9' && sz != '7') return 0;
+   if (stbi__get8(s) != 'a') return 0;
+   return 1;
+}
+
+static int stbi__gif_test(stbi__context *s)
+{
+   int r = stbi__gif_test_raw(s);
+   stbi__rewind(s);
+   return r;
+}
+
+static void stbi__gif_parse_colortable(stbi__context *s, stbi_uc pal[256][4], int num_entries, int transp)
+{
+   int i;
+   for (i=0; i < num_entries; ++i) {
+      pal[i][2] = stbi__get8(s);
+      pal[i][1] = stbi__get8(s);
+      pal[i][0] = stbi__get8(s);
+      pal[i][3] = transp == i ? 0 : 255;
+   }
+}
+
+static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_info)
+{
+   stbi_uc version;
+   if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8')
+      return stbi__err("not GIF", "Corrupt GIF");
+
+   version = stbi__get8(s);
+   if (version != '7' && version != '9')    return stbi__err("not GIF", "Corrupt GIF");
+   if (stbi__get8(s) != 'a')                return stbi__err("not GIF", "Corrupt GIF");
+
+   stbi__g_failure_reason = "";
+   g->w = stbi__get16le(s);
+   g->h = stbi__get16le(s);
+   g->flags = stbi__get8(s);
+   g->bgindex = stbi__get8(s);
+   g->ratio = stbi__get8(s);
+   g->transparent = -1;
+
+   if (comp != 0) *comp = 4;  // can't actually tell whether it's 3 or 4 until we parse the comments
+
+   if (is_info) return 1;
+
+   if (g->flags & 0x80)
+      stbi__gif_parse_colortable(s,g->pal, 2 << (g->flags & 7), -1);
+
+   return 1;
+}
+
+static int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp)
+{
+   stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif));
+   if (!stbi__gif_header(s, g, comp, 1)) {
+      STBI_FREE(g);
+      stbi__rewind( s );
+      return 0;
+   }
+   if (x) *x = g->w;
+   if (y) *y = g->h;
+   STBI_FREE(g);
+   return 1;
+}
+
+static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code)
+{
+   stbi_uc *p, *c;
+   int idx; 
+
+   // recurse to decode the prefixes, since the linked-list is backwards,
+   // and working backwards through an interleaved image would be nasty
+   if (g->codes[code].prefix >= 0)
+      stbi__out_gif_code(g, g->codes[code].prefix);
+
+   if (g->cur_y >= g->max_y) return;
+
+   idx = g->cur_x + g->cur_y; 
+   p = &g->out[idx];
+   g->history[idx / 4] = 1;  
+
+   c = &g->color_table[g->codes[code].suffix * 4];
+   if (c[3] > 128) { // don't render transparent pixels; 
+      p[0] = c[2];
+      p[1] = c[1];
+      p[2] = c[0];
+      p[3] = c[3];
+   }
+   g->cur_x += 4;
+
+   if (g->cur_x >= g->max_x) {
+      g->cur_x = g->start_x;
+      g->cur_y += g->step;
+
+      while (g->cur_y >= g->max_y && g->parse > 0) {
+         g->step = (1 << g->parse) * g->line_size;
+         g->cur_y = g->start_y + (g->step >> 1);
+         --g->parse;
+      }
+   }
+}
+
+static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g)
+{
+   stbi_uc lzw_cs;
+   stbi__int32 len, init_code;
+   stbi__uint32 first;
+   stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear;
+   stbi__gif_lzw *p;
+
+   lzw_cs = stbi__get8(s);
+   if (lzw_cs > 12) return NULL;
+   clear = 1 << lzw_cs;
+   first = 1;
+   codesize = lzw_cs + 1;
+   codemask = (1 << codesize) - 1;
+   bits = 0;
+   valid_bits = 0;
+   for (init_code = 0; init_code < clear; init_code++) {
+      g->codes[init_code].prefix = -1;
+      g->codes[init_code].first = (stbi_uc) init_code;
+      g->codes[init_code].suffix = (stbi_uc) init_code;
+   }
+
+   // support no starting clear code
+   avail = clear+2;
+   oldcode = -1;
+
+   len = 0;
+   for(;;) {
+      if (valid_bits < codesize) {
+         if (len == 0) {
+            len = stbi__get8(s); // start new block
+            if (len == 0)
+               return g->out;
+         }
+         --len;
+         bits |= (stbi__int32) stbi__get8(s) << valid_bits;
+         valid_bits += 8;
+      } else {
+         stbi__int32 code = bits & codemask;
+         bits >>= codesize;
+         valid_bits -= codesize;
+         // @OPTIMIZE: is there some way we can accelerate the non-clear path?
+         if (code == clear) {  // clear code
+            codesize = lzw_cs + 1;
+            codemask = (1 << codesize) - 1;
+            avail = clear + 2;
+            oldcode = -1;
+            first = 0;
+         } else if (code == clear + 1) { // end of stream code
+            stbi__skip(s, len);
+            while ((len = stbi__get8(s)) > 0)
+               stbi__skip(s,len);
+            return g->out;
+         } else if (code <= avail) {
+            if (first) {
+               return stbi__errpuc("no clear code", "Corrupt GIF");
+            }
+
+            if (oldcode >= 0) {
+               p = &g->codes[avail++];
+               if (avail > 8192) {
+                  return stbi__errpuc("too many codes", "Corrupt GIF");
+               }
+
+               p->prefix = (stbi__int16) oldcode;
+               p->first = g->codes[oldcode].first;
+               p->suffix = (code == avail) ? p->first : g->codes[code].first;
+            } else if (code == avail)
+               return stbi__errpuc("illegal code in raster", "Corrupt GIF");
+
+            stbi__out_gif_code(g, (stbi__uint16) code);
+
+            if ((avail & codemask) == 0 && avail <= 0x0FFF) {
+               codesize++;
+               codemask = (1 << codesize) - 1;
+            }
+
+            oldcode = code;
+         } else {
+            return stbi__errpuc("illegal code in raster", "Corrupt GIF");
+         }
+      }
+   }
+}
+
+// this function is designed to support animated gifs, although stb_image doesn't support it
+// two back is the image from two frames ago, used for a very specific disposal format
+static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp, stbi_uc *two_back)
+{
+   int dispose; 
+   int first_frame; 
+   int pi; 
+   int pcount; 
+   STBI_NOTUSED(req_comp);
+
+   // on first frame, any non-written pixels get the background colour (non-transparent)
+   first_frame = 0; 
+   if (g->out == 0) {
+      if (!stbi__gif_header(s, g, comp,0))     return 0; // stbi__g_failure_reason set by stbi__gif_header
+      g->out = (stbi_uc *) stbi__malloc(4 * g->w * g->h);
+      g->background = (stbi_uc *) stbi__malloc(4 * g->w * g->h); 
+      g->history = (stbi_uc *) stbi__malloc(g->w * g->h); 
+      if (g->out == 0)                      return stbi__errpuc("outofmem", "Out of memory");
+
+      // image is treated as "transparent" at the start - ie, nothing overwrites the current background; 
+      // background colour is only used for pixels that are not rendered first frame, after that "background"
+      // color refers to the color that was there the previous frame. 
+      memset( g->out, 0x00, 4 * g->w * g->h ); 
+      memset( g->background, 0x00, 4 * g->w * g->h ); // state of the background (starts transparent)
+      memset( g->history, 0x00, g->w * g->h );        // pixels that were affected previous frame
+      first_frame = 1; 
+   } else {
+      // second frame - how do we dispoase of the previous one?
+      dispose = (g->eflags & 0x1C) >> 2; 
+      pcount = g->w * g->h; 
+
+      if ((dispose == 3) && (two_back == 0)) {
+         dispose = 2; // if I don't have an image to revert back to, default to the old background
+      }
+
+      if (dispose == 3) { // use previous graphic
+         for (pi = 0; pi < pcount; ++pi) {
+            if (g->history[pi]) {
+               memcpy( &g->out[pi * 4], &two_back[pi * 4], 4 ); 
+            }
+         }
+      } else if (dispose == 2) { 
+         // restore what was changed last frame to background before that frame; 
+         for (pi = 0; pi < pcount; ++pi) {
+            if (g->history[pi]) {
+               memcpy( &g->out[pi * 4], &g->background[pi * 4], 4 ); 
+            }
+         }
+      } else {
+         // This is a non-disposal case eithe way, so just 
+         // leave the pixels as is, and they will become the new background
+         // 1: do not dispose
+         // 0:  not specified.
+      }
+
+      // background is what out is after the undoing of the previou frame; 
+      memcpy( g->background, g->out, 4 * g->w * g->h ); 
+   }
+
+   // clear my history; 
+   memset( g->history, 0x00, g->w * g->h );        // pixels that were affected previous frame
+
+   for (;;) {
+      int tag = stbi__get8(s); 
+      switch (tag) {
+         case 0x2C: /* Image Descriptor */
+         {
+            stbi__int32 x, y, w, h;
+            stbi_uc *o;
+
+            x = stbi__get16le(s);
+            y = stbi__get16le(s);
+            w = stbi__get16le(s);
+            h = stbi__get16le(s);
+            if (((x + w) > (g->w)) || ((y + h) > (g->h)))
+               return stbi__errpuc("bad Image Descriptor", "Corrupt GIF");
+
+            g->line_size = g->w * 4;
+            g->start_x = x * 4;
+            g->start_y = y * g->line_size;
+            g->max_x   = g->start_x + w * 4;
+            g->max_y   = g->start_y + h * g->line_size;
+            g->cur_x   = g->start_x;
+            g->cur_y   = g->start_y;
+
+            g->lflags = stbi__get8(s);
+
+            if (g->lflags & 0x40) {
+               g->step = 8 * g->line_size; // first interlaced spacing
+               g->parse = 3;
+            } else {
+               g->step = g->line_size;
+               g->parse = 0;
+            }
+
+            if (g->lflags & 0x80) {
+               stbi__gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1);
+               g->color_table = (stbi_uc *) g->lpal;
+            } else if (g->flags & 0x80) {
+               g->color_table = (stbi_uc *) g->pal;
+            } else
+               return stbi__errpuc("missing color table", "Corrupt GIF");            
+            
+            o = stbi__process_gif_raster(s, g);
+            if (o == NULL) return NULL;
+
+            // if this was the first frame, 
+            pcount = g->w * g->h; 
+            if (first_frame && (g->bgindex > 0)) {
+               // if first frame, any pixel not drawn to gets the background color
+               for (pi = 0; pi < pcount; ++pi) {
+                  if (g->history[pi] == 0) {
+                     g->pal[g->bgindex][3] = 255; // just in case it was made transparent, undo that; It will be reset next frame if need be; 
+                     memcpy( &g->out[pi * 4], &g->pal[g->bgindex], 4 ); 
+                  }
+               }
+            }
+
+            return o;
+         }
+
+         case 0x21: // Comment Extension.
+         {
+            int len;
+            int ext = stbi__get8(s); 
+            if (ext == 0xF9) { // Graphic Control Extension.
+               len = stbi__get8(s);
+               if (len == 4) {
+                  g->eflags = stbi__get8(s);
+                  g->delay = 10 * stbi__get16le(s); // delay - 1/100th of a second, saving as 1/1000ths.
+
+                  // unset old transparent
+                  if (g->transparent >= 0) {
+                     g->pal[g->transparent][3] = 255; 
+                  } 
+                  if (g->eflags & 0x01) {
+                     g->transparent = stbi__get8(s);
+                     if (g->transparent >= 0) {
+                        g->pal[g->transparent][3] = 0; 
+                     }
+                  } else {
+                     // don't need transparent
+                     stbi__skip(s, 1); 
+                     g->transparent = -1; 
+                  }
+               } else {
+                  stbi__skip(s, len);
+                  break;
+               }
+            } 
+            while ((len = stbi__get8(s)) != 0) {
+               stbi__skip(s, len);
+            }
+            break;
+         }
+
+         case 0x3B: // gif stream termination code
+            return (stbi_uc *) s; // using '1' causes warning on some compilers
+
+         default:
+            return stbi__errpuc("unknown code", "Corrupt GIF");
+      }
+   }
+}
+
+static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp)
+{
+   if (stbi__gif_test(s)) {
+      int layers = 0; 
+      stbi_uc *u = 0;
+      stbi_uc *out = 0;
+      stbi_uc *two_back = 0; 
+      stbi__gif g;
+      int stride; 
+      memset(&g, 0, sizeof(g));
+      if (delays) {
+         *delays = 0; 
+      }
+
+      do {
+         u = stbi__gif_load_next(s, &g, comp, req_comp, two_back);
+         if (u == (stbi_uc *) s) u = 0;  // end of animated gif marker
+
+         if (u) {
+            *x = g.w;
+            *y = g.h;
+            ++layers; 
+            stride = g.w * g.h * 4; 
+         
+            if (out) {
+               out = (stbi_uc*) STBI_REALLOC( out, layers * stride ); 
+               if (delays) {
+                  *delays = (int*) STBI_REALLOC( *delays, sizeof(int) * layers ); 
+               }
+            } else {
+               out = (stbi_uc*)stbi__malloc( layers * stride ); 
+               if (delays) {
+                  *delays = (int*) stbi__malloc( layers * sizeof(int) ); 
+               }
+            }
+            memcpy( out + ((layers - 1) * stride), u, stride ); 
+            if (layers >= 2) {
+               two_back = out - 2 * stride; 
+            }
+
+            if (delays) {
+               (*delays)[layers - 1U] = g.delay; 
+            }
+         }
+      } while (u != 0); 
+
+      // free temp buffer; 
+      STBI_FREE(g.out); 
+      STBI_FREE(g.history); 
+      STBI_FREE(g.background); 
+
+      // do the final conversion after loading everything; 
+      if (req_comp && req_comp != 4)
+         out = stbi__convert_format(out, 4, req_comp, layers * g.w, g.h);
+
+      *z = layers; 
+      return out;
+   } else {
+      return stbi__errpuc("not GIF", "Image was not as a gif type."); 
+   }
+}
+
+static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri)
+{
+   stbi_uc *u = 0;
+   stbi__gif g;
+   memset(&g, 0, sizeof(g));
+   STBI_NOTUSED(ri);
+
+   u = stbi__gif_load_next(s, &g, comp, req_comp, 0);
+   if (u == (stbi_uc *) s) u = 0;  // end of animated gif marker
+   if (u) {
+      *x = g.w;
+      *y = g.h;
+
+      // moved conversion to after successful load so that the same
+      // can be done for multiple frames. 
+      if (req_comp && req_comp != 4)
+         u = stbi__convert_format(u, 4, req_comp, g.w, g.h);
+   }
+
+   // free buffers needed for multiple frame loading; 
+   STBI_FREE(g.history);
+   STBI_FREE(g.background); 
+
+   return u;
+}
+
+static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp)
+{
+   return stbi__gif_info_raw(s,x,y,comp);
+}
+#endif
+
+// *************************************************************************************************
+// Radiance RGBE HDR loader
+// originally by Nicolas Schulz
+#ifndef STBI_NO_HDR
+static int stbi__hdr_test_core(stbi__context *s, const char *signature)
+{
+   int i;
+   for (i=0; signature[i]; ++i)
+      if (stbi__get8(s) != signature[i])
+          return 0;
+   stbi__rewind(s);
+   return 1;
+}
+
+static int stbi__hdr_test(stbi__context* s)
+{
+   int r = stbi__hdr_test_core(s, "#?RADIANCE\n");
+   stbi__rewind(s);
+   if(!r) {
+       r = stbi__hdr_test_core(s, "#?RGBE\n");
+       stbi__rewind(s);
+   }
+   return r;
+}
+
+#define STBI__HDR_BUFLEN  1024
+static char *stbi__hdr_gettoken(stbi__context *z, char *buffer)
+{
+   int len=0;
+   char c = '\0';
+
+   c = (char) stbi__get8(z);
+
+   while (!stbi__at_eof(z) && c != '\n') {
+      buffer[len++] = c;
+      if (len == STBI__HDR_BUFLEN-1) {
+         // flush to end of line
+         while (!stbi__at_eof(z) && stbi__get8(z) != '\n')
+            ;
+         break;
+      }
+      c = (char) stbi__get8(z);
+   }
+
+   buffer[len] = 0;
+   return buffer;
+}
+
+static void stbi__hdr_convert(float *output, stbi_uc *input, int req_comp)
+{
+   if ( input[3] != 0 ) {
+      float f1;
+      // Exponent
+      f1 = (float) ldexp(1.0f, input[3] - (int)(128 + 8));
+      if (req_comp <= 2)
+         output[0] = (input[0] + input[1] + input[2]) * f1 / 3;
+      else {
+         output[0] = input[0] * f1;
+         output[1] = input[1] * f1;
+         output[2] = input[2] * f1;
+      }
+      if (req_comp == 2) output[1] = 1;
+      if (req_comp == 4) output[3] = 1;
+   } else {
+      switch (req_comp) {
+         case 4: output[3] = 1; /* fallthrough */
+         case 3: output[0] = output[1] = output[2] = 0;
+                 break;
+         case 2: output[1] = 1; /* fallthrough */
+         case 1: output[0] = 0;
+                 break;
+      }
+   }
+}
+
+static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri)
+{
+   char buffer[STBI__HDR_BUFLEN];
+   char *token;
+   int valid = 0;
+   int width, height;
+   stbi_uc *scanline;
+   float *hdr_data;
+   int len;
+   unsigned char count, value;
+   int i, j, k, c1,c2, z;
+   const char *headerToken;
+   STBI_NOTUSED(ri);
+
+   // Check identifier
+   headerToken = stbi__hdr_gettoken(s,buffer);
+   if (strcmp(headerToken, "#?RADIANCE") != 0 && strcmp(headerToken, "#?RGBE") != 0)
+      return stbi__errpf("not HDR", "Corrupt HDR image");
+
+   // Parse header
+   for(;;) {
+      token = stbi__hdr_gettoken(s,buffer);
+      if (token[0] == 0) break;
+      if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1;
+   }
+
+   if (!valid)    return stbi__errpf("unsupported format", "Unsupported HDR format");
+
+   // Parse width and height
+   // can't use sscanf() if we're not using stdio!
+   token = stbi__hdr_gettoken(s,buffer);
+   if (strncmp(token, "-Y ", 3))  return stbi__errpf("unsupported data layout", "Unsupported HDR format");
+   token += 3;
+   height = (int) strtol(token, &token, 10);
+   while (*token == ' ') ++token;
+   if (strncmp(token, "+X ", 3))  return stbi__errpf("unsupported data layout", "Unsupported HDR format");
+   token += 3;
+   width = (int) strtol(token, NULL, 10);
+
+   *x = width;
+   *y = height;
+
+   if (comp) *comp = 3;
+   if (req_comp == 0) req_comp = 3;
+
+   if (!stbi__mad4sizes_valid(width, height, req_comp, sizeof(float), 0))
+      return stbi__errpf("too large", "HDR image is too large");
+
+   // Read data
+   hdr_data = (float *) stbi__malloc_mad4(width, height, req_comp, sizeof(float), 0);
+   if (!hdr_data)
+      return stbi__errpf("outofmem", "Out of memory");
+
+   // Load image data
+   // image data is stored as some number of sca
+   if ( width < 8 || width >= 32768) {
+      // Read flat data
+      for (j=0; j < height; ++j) {
+         for (i=0; i < width; ++i) {
+            stbi_uc rgbe[4];
+           main_decode_loop:
+            stbi__getn(s, rgbe, 4);
+            stbi__hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp);
+         }
+      }
+   } else {
+      // Read RLE-encoded data
+      scanline = NULL;
+
+      for (j = 0; j < height; ++j) {
+         c1 = stbi__get8(s);
+         c2 = stbi__get8(s);
+         len = stbi__get8(s);
+         if (c1 != 2 || c2 != 2 || (len & 0x80)) {
+            // not run-length encoded, so we have to actually use THIS data as a decoded
+            // pixel (note this can't be a valid pixel--one of RGB must be >= 128)
+            stbi_uc rgbe[4];
+            rgbe[0] = (stbi_uc) c1;
+            rgbe[1] = (stbi_uc) c2;
+            rgbe[2] = (stbi_uc) len;
+            rgbe[3] = (stbi_uc) stbi__get8(s);
+            stbi__hdr_convert(hdr_data, rgbe, req_comp);
+            i = 1;
+            j = 0;
+            STBI_FREE(scanline);
+            goto main_decode_loop; // yes, this makes no sense
+         }
+         len <<= 8;
+         len |= stbi__get8(s);
+         if (len != width) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("invalid decoded scanline length", "corrupt HDR"); }
+         if (scanline == NULL) {
+            scanline = (stbi_uc *) stbi__malloc_mad2(width, 4, 0);
+            if (!scanline) {
+               STBI_FREE(hdr_data);
+               return stbi__errpf("outofmem", "Out of memory");
+            }
+         }
+
+         for (k = 0; k < 4; ++k) {
+            int nleft;
+            i = 0;
+            while ((nleft = width - i) > 0) {
+               count = stbi__get8(s);
+               if (count > 128) {
+                  // Run
+                  value = stbi__get8(s);
+                  count -= 128;
+                  if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); }
+                  for (z = 0; z < count; ++z)
+                     scanline[i++ * 4 + k] = value;
+               } else {
+                  // Dump
+                  if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); }
+                  for (z = 0; z < count; ++z)
+                     scanline[i++ * 4 + k] = stbi__get8(s);
+               }
+            }
+         }
+         for (i=0; i < width; ++i)
+            stbi__hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp);
+      }
+      if (scanline)
+         STBI_FREE(scanline);
+   }
+
+   return hdr_data;
+}
+
+static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp)
+{
+   char buffer[STBI__HDR_BUFLEN];
+   char *token;
+   int valid = 0;
+   int dummy;
+
+   if (!x) x = &dummy;
+   if (!y) y = &dummy;
+   if (!comp) comp = &dummy;
+
+   if (stbi__hdr_test(s) == 0) {
+       stbi__rewind( s );
+       return 0;
+   }
+
+   for(;;) {
+      token = stbi__hdr_gettoken(s,buffer);
+      if (token[0] == 0) break;
+      if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1;
+   }
+
+   if (!valid) {
+       stbi__rewind( s );
+       return 0;
+   }
+   token = stbi__hdr_gettoken(s,buffer);
+   if (strncmp(token, "-Y ", 3)) {
+       stbi__rewind( s );
+       return 0;
+   }
+   token += 3;
+   *y = (int) strtol(token, &token, 10);
+   while (*token == ' ') ++token;
+   if (strncmp(token, "+X ", 3)) {
+       stbi__rewind( s );
+       return 0;
+   }
+   token += 3;
+   *x = (int) strtol(token, NULL, 10);
+   *comp = 3;
+   return 1;
+}
+#endif // STBI_NO_HDR
+
+#ifndef STBI_NO_BMP
+static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp)
+{
+   void *p;
+   stbi__bmp_data info;
+
+   info.all_a = 255;
+   p = stbi__bmp_parse_header(s, &info);
+   stbi__rewind( s );
+   if (p == NULL)
+      return 0;
+   if (x) *x = s->img_x;
+   if (y) *y = s->img_y;
+   if (comp) *comp = info.ma ? 4 : 3;
+   return 1;
+}
+#endif
+
+#ifndef STBI_NO_PSD
+static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp)
+{
+   int channelCount, dummy, depth;
+   if (!x) x = &dummy;
+   if (!y) y = &dummy;
+   if (!comp) comp = &dummy;
+   if (stbi__get32be(s) != 0x38425053) {
+       stbi__rewind( s );
+       return 0;
+   }
+   if (stbi__get16be(s) != 1) {
+       stbi__rewind( s );
+       return 0;
+   }
+   stbi__skip(s, 6);
+   channelCount = stbi__get16be(s);
+   if (channelCount < 0 || channelCount > 16) {
+       stbi__rewind( s );
+       return 0;
+   }
+   *y = stbi__get32be(s);
+   *x = stbi__get32be(s);
+   depth = stbi__get16be(s);
+   if (depth != 8 && depth != 16) {
+       stbi__rewind( s );
+       return 0;
+   }
+   if (stbi__get16be(s) != 3) {
+       stbi__rewind( s );
+       return 0;
+   }
+   *comp = 4;
+   return 1;
+}
+
+static int stbi__psd_is16(stbi__context *s)
+{
+   int channelCount, depth;
+   if (stbi__get32be(s) != 0x38425053) {
+       stbi__rewind( s );
+       return 0;
+   }
+   if (stbi__get16be(s) != 1) {
+       stbi__rewind( s );
+       return 0;
+   }
+   stbi__skip(s, 6);
+   channelCount = stbi__get16be(s);
+   if (channelCount < 0 || channelCount > 16) {
+       stbi__rewind( s );
+       return 0;
+   }
+   (void) stbi__get32be(s);
+   (void) stbi__get32be(s);
+   depth = stbi__get16be(s);
+   if (depth != 16) {
+       stbi__rewind( s );
+       return 0;
+   }
+   return 1;
+}
+#endif
+
+#ifndef STBI_NO_PIC
+static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp)
+{
+   int act_comp=0,num_packets=0,chained,dummy;
+   stbi__pic_packet packets[10];
+
+   if (!x) x = &dummy;
+   if (!y) y = &dummy;
+   if (!comp) comp = &dummy;
+
+   if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) {
+      stbi__rewind(s);
+      return 0;
+   }
+
+   stbi__skip(s, 88);
+
+   *x = stbi__get16be(s);
+   *y = stbi__get16be(s);
+   if (stbi__at_eof(s)) {
+      stbi__rewind( s);
+      return 0;
+   }
+   if ( (*x) != 0 && (1 << 28) / (*x) < (*y)) {
+      stbi__rewind( s );
+      return 0;
+   }
+
+   stbi__skip(s, 8);
+
+   do {
+      stbi__pic_packet *packet;
+
+      if (num_packets==sizeof(packets)/sizeof(packets[0]))
+         return 0;
+
+      packet = &packets[num_packets++];
+      chained = stbi__get8(s);
+      packet->size    = stbi__get8(s);
+      packet->type    = stbi__get8(s);
+      packet->channel = stbi__get8(s);
+      act_comp |= packet->channel;
+
+      if (stbi__at_eof(s)) {
+          stbi__rewind( s );
+          return 0;
+      }
+      if (packet->size != 8) {
+          stbi__rewind( s );
+          return 0;
+      }
+   } while (chained);
+
+   *comp = (act_comp & 0x10 ? 4 : 3);
+
+   return 1;
+}
+#endif
+
+// *************************************************************************************************
+// Portable Gray Map and Portable Pixel Map loader
+// by Ken Miller
+//
+// PGM: http://netpbm.sourceforge.net/doc/pgm.html
+// PPM: http://netpbm.sourceforge.net/doc/ppm.html
+//
+// Known limitations:
+//    Does not support comments in the header section
+//    Does not support ASCII image data (formats P2 and P3)
+//    Does not support 16-bit-per-channel
+
+#ifndef STBI_NO_PNM
+
+static int      stbi__pnm_test(stbi__context *s)
+{
+   char p, t;
+   p = (char) stbi__get8(s);
+   t = (char) stbi__get8(s);
+   if (p != 'P' || (t != '5' && t != '6')) {
+       stbi__rewind( s );
+       return 0;
+   }
+   return 1;
+}
+
+static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri)
+{
+   stbi_uc *out;
+   STBI_NOTUSED(ri);
+
+   if (!stbi__pnm_info(s, (int *)&s->img_x, (int *)&s->img_y, (int *)&s->img_n))
+      return 0;
+
+   *x = s->img_x;
+   *y = s->img_y;
+   if (comp) *comp = s->img_n;
+
+   if (!stbi__mad3sizes_valid(s->img_n, s->img_x, s->img_y, 0))
+      return stbi__errpuc("too large", "PNM too large");
+
+   out = (stbi_uc *) stbi__malloc_mad3(s->img_n, s->img_x, s->img_y, 0);
+   if (!out) return stbi__errpuc("outofmem", "Out of memory");
+   stbi__getn(s, out, s->img_n * s->img_x * s->img_y);
+
+   if (req_comp && req_comp != s->img_n) {
+      out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y);
+      if (out == NULL) return out; // stbi__convert_format frees input on failure
+   }
+   return out;
+}
+
+static int      stbi__pnm_isspace(char c)
+{
+   return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r';
+}
+
+static void     stbi__pnm_skip_whitespace(stbi__context *s, char *c)
+{
+   for (;;) {
+      while (!stbi__at_eof(s) && stbi__pnm_isspace(*c))
+         *c = (char) stbi__get8(s);
+
+      if (stbi__at_eof(s) || *c != '#')
+         break;
+
+      while (!stbi__at_eof(s) && *c != '\n' && *c != '\r' )
+         *c = (char) stbi__get8(s);
+   }
+}
+
+static int      stbi__pnm_isdigit(char c)
+{
+   return c >= '0' && c <= '9';
+}
+
+static int      stbi__pnm_getinteger(stbi__context *s, char *c)
+{
+   int value = 0;
+
+   while (!stbi__at_eof(s) && stbi__pnm_isdigit(*c)) {
+      value = value*10 + (*c - '0');
+      *c = (char) stbi__get8(s);
+   }
+
+   return value;
+}
+
+static int      stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp)
+{
+   int maxv, dummy;
+   char c, p, t;
+
+   if (!x) x = &dummy;
+   if (!y) y = &dummy;
+   if (!comp) comp = &dummy;
+
+   stbi__rewind(s);
+
+   // Get identifier
+   p = (char) stbi__get8(s);
+   t = (char) stbi__get8(s);
+   if (p != 'P' || (t != '5' && t != '6')) {
+       stbi__rewind(s);
+       return 0;
+   }
+
+   *comp = (t == '6') ? 3 : 1;  // '5' is 1-component .pgm; '6' is 3-component .ppm
+
+   c = (char) stbi__get8(s);
+   stbi__pnm_skip_whitespace(s, &c);
+
+   *x = stbi__pnm_getinteger(s, &c); // read width
+   stbi__pnm_skip_whitespace(s, &c);
+
+   *y = stbi__pnm_getinteger(s, &c); // read height
+   stbi__pnm_skip_whitespace(s, &c);
+
+   maxv = stbi__pnm_getinteger(s, &c);  // read max value
+
+   if (maxv > 255)
+      return stbi__err("max value > 255", "PPM image not 8-bit");
+   else
+      return 1;
+}
+#endif
+
+static int stbi__info_main(stbi__context *s, int *x, int *y, int *comp)
+{
+   #ifndef STBI_NO_JPEG
+   if (stbi__jpeg_info(s, x, y, comp)) return 1;
+   #endif
+
+   #ifndef STBI_NO_PNG
+   if (stbi__png_info(s, x, y, comp))  return 1;
+   #endif
+
+   #ifndef STBI_NO_GIF
+   if (stbi__gif_info(s, x, y, comp))  return 1;
+   #endif
+
+   #ifndef STBI_NO_BMP
+   if (stbi__bmp_info(s, x, y, comp))  return 1;
+   #endif
+
+   #ifndef STBI_NO_PSD
+   if (stbi__psd_info(s, x, y, comp))  return 1;
+   #endif
+
+   #ifndef STBI_NO_PIC
+   if (stbi__pic_info(s, x, y, comp))  return 1;
+   #endif
+
+   #ifndef STBI_NO_PNM
+   if (stbi__pnm_info(s, x, y, comp))  return 1;
+   #endif
+
+   #ifndef STBI_NO_HDR
+   if (stbi__hdr_info(s, x, y, comp))  return 1;
+   #endif
+
+   // test tga last because it's a crappy test!
+   #ifndef STBI_NO_TGA
+   if (stbi__tga_info(s, x, y, comp))
+       return 1;
+   #endif
+   return stbi__err("unknown image type", "Image not of any known type, or corrupt");
+}
+
+static int stbi__is_16_main(stbi__context *s)
+{
+   #ifndef STBI_NO_PNG
+   if (stbi__png_is16(s))  return 1;
+   #endif
+
+   #ifndef STBI_NO_PSD
+   if (stbi__psd_is16(s))  return 1;
+   #endif
+
+   return 0;
+}
+
+#ifndef STBI_NO_STDIO
+STBIDEF int stbi_info(char const *filename, int *x, int *y, int *comp)
+{
+    FILE *f = stbi__fopen(filename, "rb");
+    int result;
+    if (!f) return stbi__err("can't fopen", "Unable to open file");
+    result = stbi_info_from_file(f, x, y, comp);
+    fclose(f);
+    return result;
+}
+
+STBIDEF int stbi_info_from_file(FILE *f, int *x, int *y, int *comp)
+{
+   int r;
+   stbi__context s;
+   long pos = ftell(f);
+   stbi__start_file(&s, f);
+   r = stbi__info_main(&s,x,y,comp);
+   fseek(f,pos,SEEK_SET);
+   return r;
+}
+
+STBIDEF int stbi_is_16_bit(char const *filename)
+{
+    FILE *f = stbi__fopen(filename, "rb");
+    int result;
+    if (!f) return stbi__err("can't fopen", "Unable to open file");
+    result = stbi_is_16_bit_from_file(f);
+    fclose(f);
+    return result;
+}
+
+STBIDEF int stbi_is_16_bit_from_file(FILE *f)
+{
+   int r;
+   stbi__context s;
+   long pos = ftell(f);
+   stbi__start_file(&s, f);
+   r = stbi__is_16_main(&s);
+   fseek(f,pos,SEEK_SET);
+   return r;
+}
+#endif // !STBI_NO_STDIO
+
+STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp)
+{
+   stbi__context s;
+   stbi__start_mem(&s,buffer,len);
+   return stbi__info_main(&s,x,y,comp);
+}
+
+STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int *x, int *y, int *comp)
+{
+   stbi__context s;
+   stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user);
+   return stbi__info_main(&s,x,y,comp);
+}
+
+STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len)
+{
+   stbi__context s;
+   stbi__start_mem(&s,buffer,len);
+   return stbi__is_16_main(&s);
+}
+
+STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *c, void *user)
+{
+   stbi__context s;
+   stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user);
+   return stbi__is_16_main(&s);
+}
+
+#endif // STB_IMAGE_IMPLEMENTATION
+
+/*
+   revision history:
+      2.20  (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs 
+      2.19  (2018-02-11) fix warning
+      2.18  (2018-01-30) fix warnings
+      2.17  (2018-01-29) change sbti__shiftsigned to avoid clang -O2 bug
+                         1-bit BMP
+                         *_is_16_bit api
+                         avoid warnings
+      2.16  (2017-07-23) all functions have 16-bit variants;
+                         STBI_NO_STDIO works again;
+                         compilation fixes;
+                         fix rounding in unpremultiply;
+                         optimize vertical flip;
+                         disable raw_len validation;
+                         documentation fixes
+      2.15  (2017-03-18) fix png-1,2,4 bug; now all Imagenet JPGs decode;
+                         warning fixes; disable run-time SSE detection on gcc;
+                         uniform handling of optional "return" values;
+                         thread-safe initialization of zlib tables
+      2.14  (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs
+      2.13  (2016-11-29) add 16-bit API, only supported for PNG right now
+      2.12  (2016-04-02) fix typo in 2.11 PSD fix that caused crashes
+      2.11  (2016-04-02) allocate large structures on the stack
+                         remove white matting for transparent PSD
+                         fix reported channel count for PNG & BMP
+                         re-enable SSE2 in non-gcc 64-bit
+                         support RGB-formatted JPEG
+                         read 16-bit PNGs (only as 8-bit)
+      2.10  (2016-01-22) avoid warning introduced in 2.09 by STBI_REALLOC_SIZED
+      2.09  (2016-01-16) allow comments in PNM files
+                         16-bit-per-pixel TGA (not bit-per-component)
+                         info() for TGA could break due to .hdr handling
+                         info() for BMP to shares code instead of sloppy parse
+                         can use STBI_REALLOC_SIZED if allocator doesn't support realloc
+                         code cleanup
+      2.08  (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA
+      2.07  (2015-09-13) fix compiler warnings
+                         partial animated GIF support
+                         limited 16-bpc PSD support
+                         #ifdef unused functions
+                         bug with < 92 byte PIC,PNM,HDR,TGA
+      2.06  (2015-04-19) fix bug where PSD returns wrong '*comp' value
+      2.05  (2015-04-19) fix bug in progressive JPEG handling, fix warning
+      2.04  (2015-04-15) try to re-enable SIMD on MinGW 64-bit
+      2.03  (2015-04-12) extra corruption checking (mmozeiko)
+                         stbi_set_flip_vertically_on_load (nguillemot)
+                         fix NEON support; fix mingw support
+      2.02  (2015-01-19) fix incorrect assert, fix warning
+      2.01  (2015-01-17) fix various warnings; suppress SIMD on gcc 32-bit without -msse2
+      2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG
+      2.00  (2014-12-25) optimize JPG, including x86 SSE2 & NEON SIMD (ryg)
+                         progressive JPEG (stb)
+                         PGM/PPM support (Ken Miller)
+                         STBI_MALLOC,STBI_REALLOC,STBI_FREE
+                         GIF bugfix -- seemingly never worked
+                         STBI_NO_*, STBI_ONLY_*
+      1.48  (2014-12-14) fix incorrectly-named assert()
+      1.47  (2014-12-14) 1/2/4-bit PNG support, both direct and paletted (Omar Cornut & stb)
+                         optimize PNG (ryg)
+                         fix bug in interlaced PNG with user-specified channel count (stb)
+      1.46  (2014-08-26)
+              fix broken tRNS chunk (colorkey-style transparency) in non-paletted PNG
+      1.45  (2014-08-16)
+              fix MSVC-ARM internal compiler error by wrapping malloc
+      1.44  (2014-08-07)
+              various warning fixes from Ronny Chevalier
+      1.43  (2014-07-15)
+              fix MSVC-only compiler problem in code changed in 1.42
+      1.42  (2014-07-09)
+              don't define _CRT_SECURE_NO_WARNINGS (affects user code)
+              fixes to stbi__cleanup_jpeg path
+              added STBI_ASSERT to avoid requiring assert.h
+      1.41  (2014-06-25)
+              fix search&replace from 1.36 that messed up comments/error messages
+      1.40  (2014-06-22)
+              fix gcc struct-initialization warning
+      1.39  (2014-06-15)
+              fix to TGA optimization when req_comp != number of components in TGA;
+              fix to GIF loading because BMP wasn't rewinding (whoops, no GIFs in my test suite)
+              add support for BMP version 5 (more ignored fields)
+      1.38  (2014-06-06)
+              suppress MSVC warnings on integer casts truncating values
+              fix accidental rename of 'skip' field of I/O
+      1.37  (2014-06-04)
+              remove duplicate typedef
+      1.36  (2014-06-03)
+              convert to header file single-file library
+              if de-iphone isn't set, load iphone images color-swapped instead of returning NULL
+      1.35  (2014-05-27)
+              various warnings
+              fix broken STBI_SIMD path
+              fix bug where stbi_load_from_file no longer left file pointer in correct place
+              fix broken non-easy path for 32-bit BMP (possibly never used)
+              TGA optimization by Arseny Kapoulkine
+      1.34  (unknown)
+              use STBI_NOTUSED in stbi__resample_row_generic(), fix one more leak in tga failure case
+      1.33  (2011-07-14)
+              make stbi_is_hdr work in STBI_NO_HDR (as specified), minor compiler-friendly improvements
+      1.32  (2011-07-13)
+              support for "info" function for all supported filetypes (SpartanJ)
+      1.31  (2011-06-20)
+              a few more leak fixes, bug in PNG handling (SpartanJ)
+      1.30  (2011-06-11)
+              added ability to load files via callbacks to accomidate custom input streams (Ben Wenger)
+              removed deprecated format-specific test/load functions
+              removed support for installable file formats (stbi_loader) -- would have been broken for IO callbacks anyway
+              error cases in bmp and tga give messages and don't leak (Raymond Barbiero, grisha)
+              fix inefficiency in decoding 32-bit BMP (David Woo)
+      1.29  (2010-08-16)
+              various warning fixes from Aurelien Pocheville
+      1.28  (2010-08-01)
+              fix bug in GIF palette transparency (SpartanJ)
+      1.27  (2010-08-01)
+              cast-to-stbi_uc to fix warnings
+      1.26  (2010-07-24)
+              fix bug in file buffering for PNG reported by SpartanJ
+      1.25  (2010-07-17)
+              refix trans_data warning (Won Chun)
+      1.24  (2010-07-12)
+              perf improvements reading from files on platforms with lock-heavy fgetc()
+              minor perf improvements for jpeg
+              deprecated type-specific functions so we'll get feedback if they're needed
+              attempt to fix trans_data warning (Won Chun)
+      1.23    fixed bug in iPhone support
+      1.22  (2010-07-10)
+              removed image *writing* support
+              stbi_info support from Jetro Lauha
+              GIF support from Jean-Marc Lienher
+              iPhone PNG-extensions from James Brown
+              warning-fixes from Nicolas Schulz and Janez Zemva (i.stbi__err. Janez (U+017D)emva)
+      1.21    fix use of 'stbi_uc' in header (reported by jon blow)
+      1.20    added support for Softimage PIC, by Tom Seddon
+      1.19    bug in interlaced PNG corruption check (found by ryg)
+      1.18  (2008-08-02)
+              fix a threading bug (local mutable static)
+      1.17    support interlaced PNG
+      1.16    major bugfix - stbi__convert_format converted one too many pixels
+      1.15    initialize some fields for thread safety
+      1.14    fix threadsafe conversion bug
+              header-file-only version (#define STBI_HEADER_FILE_ONLY before including)
+      1.13    threadsafe
+      1.12    const qualifiers in the API
+      1.11    Support installable IDCT, colorspace conversion routines
+      1.10    Fixes for 64-bit (don't use "unsigned long")
+              optimized upsampling by Fabian "ryg" Giesen
+      1.09    Fix format-conversion for PSD code (bad global variables!)
+      1.08    Thatcher Ulrich's PSD code integrated by Nicolas Schulz
+      1.07    attempt to fix C++ warning/errors again
+      1.06    attempt to fix C++ warning/errors again
+      1.05    fix TGA loading to return correct *comp and use good luminance calc
+      1.04    default float alpha is 1, not 255; use 'void *' for stbi_image_free
+      1.03    bugfixes to STBI_NO_STDIO, STBI_NO_HDR
+      1.02    support for (subset of) HDR files, float interface for preferred access to them
+      1.01    fix bug: possible bug in handling right-side up bmps... not sure
+              fix bug: the stbi__bmp_load() and stbi__tga_load() functions didn't work at all
+      1.00    interface to zlib that skips zlib header
+      0.99    correct handling of alpha in palette
+      0.98    TGA loader by lonesock; dynamically add loaders (untested)
+      0.97    jpeg errors on too large a file; also catch another malloc failure
+      0.96    fix detection of invalid v value - particleman@mollyrocket forum
+      0.95    during header scan, seek to markers in case of padding
+      0.94    STBI_NO_STDIO to disable stdio usage; rename all #defines the same
+      0.93    handle jpegtran output; verbose errors
+      0.92    read 4,8,16,24,32-bit BMP files of several formats
+      0.91    output 24-bit Windows 3.0 BMP files
+      0.90    fix a few more warnings; bump version number to approach 1.0
+      0.61    bugfixes due to Marc LeBlanc, Christopher Lloyd
+      0.60    fix compiling as c++
+      0.59    fix warnings: merge Dave Moore's -Wall fixes
+      0.58    fix bug: zlib uncompressed mode len/nlen was wrong endian
+      0.57    fix bug: jpg last huffman symbol before marker was >9 bits but less than 16 available
+      0.56    fix bug: zlib uncompressed mode len vs. nlen
+      0.55    fix bug: restart_interval not initialized to 0
+      0.54    allow NULL for 'int *comp'
+      0.53    fix bug in png 3->4; speedup png decoding
+      0.52    png handles req_comp=3,4 directly; minor cleanup; jpeg comments
+      0.51    obey req_comp requests, 1-component jpegs return as 1-component,
+              on 'test' only check type, not whether we support this variant
+      0.50  (2006-11-19)
+              first released version
+*/
+
+
+/*
+------------------------------------------------------------------------------
+This software is available under 2 licenses -- choose whichever you prefer.
+------------------------------------------------------------------------------
+ALTERNATIVE A - MIT License
+Copyright (c) 2017 Sean Barrett
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+------------------------------------------------------------------------------
+ALTERNATIVE B - Public Domain (www.unlicense.org)
+This is free and unencumbered software released into the public domain.
+Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
+software, either in source code form or as a compiled binary, for any purpose,
+commercial or non-commercial, and by any means.
+In jurisdictions that recognize copyright laws, the author or authors of this
+software dedicate any and all copyright interest in the software to the public
+domain. We make this dedication for the benefit of the public at large and to
+the detriment of our heirs and successors. We intend this dedication to be an
+overt act of relinquishment in perpetuity of all present and future rights to
+this software under copyright law.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+------------------------------------------------------------------------------
+*/
diff --git a/external/Vulkan/external/tinygltf/tiny_gltf.h b/external/Vulkan/external/tinygltf/tiny_gltf.h
new file mode 100644
index 0000000000000000000000000000000000000000..710e9d9370e6d10feb0128557cd11f2e0ac0a533
--- /dev/null
+++ b/external/Vulkan/external/tinygltf/tiny_gltf.h
@@ -0,0 +1,7584 @@
+//
+// Header-only tiny glTF 2.0 loader and serializer.
+//
+//
+// The MIT License (MIT)
+//
+// Copyright (c) 2015 - 2020 Syoyo Fujita, Aurélien Chatelain and many
+// contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+// Version:
+//  - v2.4.2 Decode percent-encoded URI.
+//  - v2.4.1 Fix some glTF object class does not have `extensions` and/or
+//  `extras` property.
+//  - v2.4.0 Experimental RapidJSON and C++14 support(Thanks to @jrkoone).
+//  - v2.3.1 Set default value of minFilter and magFilter in Sampler to -1.
+//  - v2.3.0 Modified Material representation according to glTF 2.0 schema
+//           (and introduced TextureInfo class)
+//           Change the behavior of `Value::IsNumber`. It return true either the
+//           value is int or real.
+//  - v2.2.0 Add loading 16bit PNG support. Add Sparse accessor support(Thanks
+//  to @Ybalrid)
+//  - v2.1.0 Add draco compression.
+//  - v2.0.1 Add comparsion feature(Thanks to @Selmar).
+//  - v2.0.0 glTF 2.0!.
+//
+// Tiny glTF loader is using following third party libraries:
+//
+//  - jsonhpp: C++ JSON library.
+//  - base64: base64 decode/encode library.
+//  - stb_image: Image loading library.
+//
+#ifndef TINY_GLTF_H_
+#define TINY_GLTF_H_
+
+#include <array>
+#include <cassert>
+#include <cstdint>
+#include <cstdlib>
+#include <cstring>
+#include <limits>
+#include <map>
+#include <string>
+#include <vector>
+
+#ifndef TINYGLTF_USE_CPP14
+#include <functional>
+#endif
+
+#ifdef __ANDROID__
+#ifdef TINYGLTF_ANDROID_LOAD_FROM_ASSETS
+#include <android/asset_manager.h>
+#endif
+#endif
+
+#ifdef __GNUC__
+#if (__GNUC__ < 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ <= 8))
+#define TINYGLTF_NOEXCEPT
+#else
+#define TINYGLTF_NOEXCEPT noexcept
+#endif
+#else
+#define TINYGLTF_NOEXCEPT noexcept
+#endif
+
+#define DEFAULT_METHODS(x)             \
+  ~x() = default;                      \
+  x(const x &) = default;              \
+  x(x &&) TINYGLTF_NOEXCEPT = default; \
+  x &operator=(const x &) = default;   \
+  x &operator=(x &&) TINYGLTF_NOEXCEPT = default;
+
+namespace tinygltf {
+
+#define TINYGLTF_MODE_POINTS (0)
+#define TINYGLTF_MODE_LINE (1)
+#define TINYGLTF_MODE_LINE_LOOP (2)
+#define TINYGLTF_MODE_LINE_STRIP (3)
+#define TINYGLTF_MODE_TRIANGLES (4)
+#define TINYGLTF_MODE_TRIANGLE_STRIP (5)
+#define TINYGLTF_MODE_TRIANGLE_FAN (6)
+
+#define TINYGLTF_COMPONENT_TYPE_BYTE (5120)
+#define TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE (5121)
+#define TINYGLTF_COMPONENT_TYPE_SHORT (5122)
+#define TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT (5123)
+#define TINYGLTF_COMPONENT_TYPE_INT (5124)
+#define TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT (5125)
+#define TINYGLTF_COMPONENT_TYPE_FLOAT (5126)
+#define TINYGLTF_COMPONENT_TYPE_DOUBLE (5130)
+
+#define TINYGLTF_TEXTURE_FILTER_NEAREST (9728)
+#define TINYGLTF_TEXTURE_FILTER_LINEAR (9729)
+#define TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_NEAREST (9984)
+#define TINYGLTF_TEXTURE_FILTER_LINEAR_MIPMAP_NEAREST (9985)
+#define TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_LINEAR (9986)
+#define TINYGLTF_TEXTURE_FILTER_LINEAR_MIPMAP_LINEAR (9987)
+
+#define TINYGLTF_TEXTURE_WRAP_REPEAT (10497)
+#define TINYGLTF_TEXTURE_WRAP_CLAMP_TO_EDGE (33071)
+#define TINYGLTF_TEXTURE_WRAP_MIRRORED_REPEAT (33648)
+
+// Redeclarations of the above for technique.parameters.
+#define TINYGLTF_PARAMETER_TYPE_BYTE (5120)
+#define TINYGLTF_PARAMETER_TYPE_UNSIGNED_BYTE (5121)
+#define TINYGLTF_PARAMETER_TYPE_SHORT (5122)
+#define TINYGLTF_PARAMETER_TYPE_UNSIGNED_SHORT (5123)
+#define TINYGLTF_PARAMETER_TYPE_INT (5124)
+#define TINYGLTF_PARAMETER_TYPE_UNSIGNED_INT (5125)
+#define TINYGLTF_PARAMETER_TYPE_FLOAT (5126)
+
+#define TINYGLTF_PARAMETER_TYPE_FLOAT_VEC2 (35664)
+#define TINYGLTF_PARAMETER_TYPE_FLOAT_VEC3 (35665)
+#define TINYGLTF_PARAMETER_TYPE_FLOAT_VEC4 (35666)
+
+#define TINYGLTF_PARAMETER_TYPE_INT_VEC2 (35667)
+#define TINYGLTF_PARAMETER_TYPE_INT_VEC3 (35668)
+#define TINYGLTF_PARAMETER_TYPE_INT_VEC4 (35669)
+
+#define TINYGLTF_PARAMETER_TYPE_BOOL (35670)
+#define TINYGLTF_PARAMETER_TYPE_BOOL_VEC2 (35671)
+#define TINYGLTF_PARAMETER_TYPE_BOOL_VEC3 (35672)
+#define TINYGLTF_PARAMETER_TYPE_BOOL_VEC4 (35673)
+
+#define TINYGLTF_PARAMETER_TYPE_FLOAT_MAT2 (35674)
+#define TINYGLTF_PARAMETER_TYPE_FLOAT_MAT3 (35675)
+#define TINYGLTF_PARAMETER_TYPE_FLOAT_MAT4 (35676)
+
+#define TINYGLTF_PARAMETER_TYPE_SAMPLER_2D (35678)
+
+// End parameter types
+
+#define TINYGLTF_TYPE_VEC2 (2)
+#define TINYGLTF_TYPE_VEC3 (3)
+#define TINYGLTF_TYPE_VEC4 (4)
+#define TINYGLTF_TYPE_MAT2 (32 + 2)
+#define TINYGLTF_TYPE_MAT3 (32 + 3)
+#define TINYGLTF_TYPE_MAT4 (32 + 4)
+#define TINYGLTF_TYPE_SCALAR (64 + 1)
+#define TINYGLTF_TYPE_VECTOR (64 + 4)
+#define TINYGLTF_TYPE_MATRIX (64 + 16)
+
+#define TINYGLTF_IMAGE_FORMAT_JPEG (0)
+#define TINYGLTF_IMAGE_FORMAT_PNG (1)
+#define TINYGLTF_IMAGE_FORMAT_BMP (2)
+#define TINYGLTF_IMAGE_FORMAT_GIF (3)
+
+#define TINYGLTF_TEXTURE_FORMAT_ALPHA (6406)
+#define TINYGLTF_TEXTURE_FORMAT_RGB (6407)
+#define TINYGLTF_TEXTURE_FORMAT_RGBA (6408)
+#define TINYGLTF_TEXTURE_FORMAT_LUMINANCE (6409)
+#define TINYGLTF_TEXTURE_FORMAT_LUMINANCE_ALPHA (6410)
+
+#define TINYGLTF_TEXTURE_TARGET_TEXTURE2D (3553)
+#define TINYGLTF_TEXTURE_TYPE_UNSIGNED_BYTE (5121)
+
+#define TINYGLTF_TARGET_ARRAY_BUFFER (34962)
+#define TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER (34963)
+
+#define TINYGLTF_SHADER_TYPE_VERTEX_SHADER (35633)
+#define TINYGLTF_SHADER_TYPE_FRAGMENT_SHADER (35632)
+
+#define TINYGLTF_DOUBLE_EPS (1.e-12)
+#define TINYGLTF_DOUBLE_EQUAL(a, b) (std::fabs((b) - (a)) < TINYGLTF_DOUBLE_EPS)
+
+#ifdef __ANDROID__
+#ifdef TINYGLTF_ANDROID_LOAD_FROM_ASSETS
+AAssetManager *asset_manager = nullptr;
+#endif
+#endif
+
+typedef enum {
+  NULL_TYPE = 0,
+  REAL_TYPE = 1,
+  INT_TYPE = 2,
+  BOOL_TYPE = 3,
+  STRING_TYPE = 4,
+  ARRAY_TYPE = 5,
+  BINARY_TYPE = 6,
+  OBJECT_TYPE = 7
+} Type;
+
+static inline int32_t GetComponentSizeInBytes(uint32_t componentType) {
+  if (componentType == TINYGLTF_COMPONENT_TYPE_BYTE) {
+    return 1;
+  } else if (componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) {
+    return 1;
+  } else if (componentType == TINYGLTF_COMPONENT_TYPE_SHORT) {
+    return 2;
+  } else if (componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) {
+    return 2;
+  } else if (componentType == TINYGLTF_COMPONENT_TYPE_INT) {
+    return 4;
+  } else if (componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT) {
+    return 4;
+  } else if (componentType == TINYGLTF_COMPONENT_TYPE_FLOAT) {
+    return 4;
+  } else if (componentType == TINYGLTF_COMPONENT_TYPE_DOUBLE) {
+    return 8;
+  } else {
+    // Unknown componenty type
+    return -1;
+  }
+}
+
+static inline int32_t GetNumComponentsInType(uint32_t ty) {
+  if (ty == TINYGLTF_TYPE_SCALAR) {
+    return 1;
+  } else if (ty == TINYGLTF_TYPE_VEC2) {
+    return 2;
+  } else if (ty == TINYGLTF_TYPE_VEC3) {
+    return 3;
+  } else if (ty == TINYGLTF_TYPE_VEC4) {
+    return 4;
+  } else if (ty == TINYGLTF_TYPE_MAT2) {
+    return 4;
+  } else if (ty == TINYGLTF_TYPE_MAT3) {
+    return 9;
+  } else if (ty == TINYGLTF_TYPE_MAT4) {
+    return 16;
+  } else {
+    // Unknown componenty type
+    return -1;
+  }
+}
+
+// TODO(syoyo): Move these functions to TinyGLTF class
+bool IsDataURI(const std::string &in);
+bool DecodeDataURI(std::vector<unsigned char> *out, std::string &mime_type,
+                   const std::string &in, size_t reqBytes, bool checkSize);
+
+#ifdef __clang__
+#pragma clang diagnostic push
+// Suppress warning for : static Value null_value
+// https://stackoverflow.com/questions/15708411/how-to-deal-with-global-constructor-warning-in-clang
+#pragma clang diagnostic ignored "-Wexit-time-destructors"
+#pragma clang diagnostic ignored "-Wpadded"
+#endif
+
+// Simple class to represent JSON object
+class Value {
+ public:
+  typedef std::vector<Value> Array;
+  typedef std::map<std::string, Value> Object;
+
+  Value()
+      : type_(NULL_TYPE),
+        int_value_(0),
+        real_value_(0.0),
+        boolean_value_(false) {}
+
+  explicit Value(bool b) : type_(BOOL_TYPE) { boolean_value_ = b; }
+  explicit Value(int i) : type_(INT_TYPE) {
+    int_value_ = i;
+    real_value_ = i;
+  }
+  explicit Value(double n) : type_(REAL_TYPE) { real_value_ = n; }
+  explicit Value(const std::string &s) : type_(STRING_TYPE) {
+    string_value_ = s;
+  }
+  explicit Value(std::string &&s)
+      : type_(STRING_TYPE), string_value_(std::move(s)) {}
+  explicit Value(const unsigned char *p, size_t n) : type_(BINARY_TYPE) {
+    binary_value_.resize(n);
+    memcpy(binary_value_.data(), p, n);
+  }
+  explicit Value(std::vector<unsigned char> &&v) noexcept
+      : type_(BINARY_TYPE),
+        binary_value_(std::move(v)) {}
+  explicit Value(const Array &a) : type_(ARRAY_TYPE) { array_value_ = a; }
+  explicit Value(Array &&a) noexcept : type_(ARRAY_TYPE),
+                                       array_value_(std::move(a)) {}
+
+  explicit Value(const Object &o) : type_(OBJECT_TYPE) { object_value_ = o; }
+  explicit Value(Object &&o) noexcept : type_(OBJECT_TYPE),
+                                        object_value_(std::move(o)) {}
+
+  DEFAULT_METHODS(Value)
+
+  char Type() const { return static_cast<const char>(type_); }
+
+  bool IsBool() const { return (type_ == BOOL_TYPE); }
+
+  bool IsInt() const { return (type_ == INT_TYPE); }
+
+  bool IsNumber() const { return (type_ == REAL_TYPE) || (type_ == INT_TYPE); }
+
+  bool IsReal() const { return (type_ == REAL_TYPE); }
+
+  bool IsString() const { return (type_ == STRING_TYPE); }
+
+  bool IsBinary() const { return (type_ == BINARY_TYPE); }
+
+  bool IsArray() const { return (type_ == ARRAY_TYPE); }
+
+  bool IsObject() const { return (type_ == OBJECT_TYPE); }
+
+  // Use this function if you want to have number value as double.
+  double GetNumberAsDouble() const {
+    if (type_ == INT_TYPE) {
+      return double(int_value_);
+    } else {
+      return real_value_;
+    }
+  }
+
+  // Use this function if you want to have number value as int.
+  double GetNumberAsInt() const {
+    if (type_ == REAL_TYPE) {
+      return int(real_value_);
+    } else {
+      return int_value_;
+    }
+  }
+
+  // Accessor
+  template <typename T>
+  const T &Get() const;
+  template <typename T>
+  T &Get();
+
+  // Lookup value from an array
+  const Value &Get(int idx) const {
+    static Value null_value;
+    assert(IsArray());
+    assert(idx >= 0);
+    return (static_cast<size_t>(idx) < array_value_.size())
+               ? array_value_[static_cast<size_t>(idx)]
+               : null_value;
+  }
+
+  // Lookup value from a key-value pair
+  const Value &Get(const std::string &key) const {
+    static Value null_value;
+    assert(IsObject());
+    Object::const_iterator it = object_value_.find(key);
+    return (it != object_value_.end()) ? it->second : null_value;
+  }
+
+  size_t ArrayLen() const {
+    if (!IsArray()) return 0;
+    return array_value_.size();
+  }
+
+  // Valid only for object type.
+  bool Has(const std::string &key) const {
+    if (!IsObject()) return false;
+    Object::const_iterator it = object_value_.find(key);
+    return (it != object_value_.end()) ? true : false;
+  }
+
+  // List keys
+  std::vector<std::string> Keys() const {
+    std::vector<std::string> keys;
+    if (!IsObject()) return keys;  // empty
+
+    for (Object::const_iterator it = object_value_.begin();
+         it != object_value_.end(); ++it) {
+      keys.push_back(it->first);
+    }
+
+    return keys;
+  }
+
+  size_t Size() const { return (IsArray() ? ArrayLen() : Keys().size()); }
+
+  bool operator==(const tinygltf::Value &other) const;
+
+ protected:
+  int type_ = NULL_TYPE;
+
+  int int_value_ = 0;
+  double real_value_ = 0.0;
+  std::string string_value_;
+  std::vector<unsigned char> binary_value_;
+  Array array_value_;
+  Object object_value_;
+  bool boolean_value_ = false;
+};
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+#define TINYGLTF_VALUE_GET(ctype, var)            \
+  template <>                                     \
+  inline const ctype &Value::Get<ctype>() const { \
+    return var;                                   \
+  }                                               \
+  template <>                                     \
+  inline ctype &Value::Get<ctype>() {             \
+    return var;                                   \
+  }
+TINYGLTF_VALUE_GET(bool, boolean_value_)
+TINYGLTF_VALUE_GET(double, real_value_)
+TINYGLTF_VALUE_GET(int, int_value_)
+TINYGLTF_VALUE_GET(std::string, string_value_)
+TINYGLTF_VALUE_GET(std::vector<unsigned char>, binary_value_)
+TINYGLTF_VALUE_GET(Value::Array, array_value_)
+TINYGLTF_VALUE_GET(Value::Object, object_value_)
+#undef TINYGLTF_VALUE_GET
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wc++98-compat"
+#pragma clang diagnostic ignored "-Wpadded"
+#endif
+
+/// Agregate object for representing a color
+using ColorValue = std::array<double, 4>;
+
+// === legacy interface ====
+// TODO(syoyo): Deprecate `Parameter` class.
+struct Parameter {
+  bool bool_value = false;
+  bool has_number_value = false;
+  std::string string_value;
+  std::vector<double> number_array;
+  std::map<std::string, double> json_double_value;
+  double number_value = 0.0;
+
+  // context sensitive methods. depending the type of the Parameter you are
+  // accessing, these are either valid or not
+  // If this parameter represent a texture map in a material, will return the
+  // texture index
+
+  /// Return the index of a texture if this Parameter is a texture map.
+  /// Returned value is only valid if the parameter represent a texture from a
+  /// material
+  int TextureIndex() const {
+    const auto it = json_double_value.find("index");
+    if (it != std::end(json_double_value)) {
+      return int(it->second);
+    }
+    return -1;
+  }
+
+  /// Return the index of a texture coordinate set if this Parameter is a
+  /// texture map. Returned value is only valid if the parameter represent a
+  /// texture from a material
+  int TextureTexCoord() const {
+    const auto it = json_double_value.find("texCoord");
+    if (it != std::end(json_double_value)) {
+      return int(it->second);
+    }
+    // As per the spec, if texCoord is ommited, this parameter is 0
+    return 0;
+  }
+
+  /// Return the scale of a texture if this Parameter is a normal texture map.
+  /// Returned value is only valid if the parameter represent a normal texture
+  /// from a material
+  double TextureScale() const {
+    const auto it = json_double_value.find("scale");
+    if (it != std::end(json_double_value)) {
+      return it->second;
+    }
+    // As per the spec, if scale is ommited, this paramter is 1
+    return 1;
+  }
+
+  /// Return the strength of a texture if this Parameter is a an occlusion map.
+  /// Returned value is only valid if the parameter represent an occlusion map
+  /// from a material
+  double TextureStrength() const {
+    const auto it = json_double_value.find("strength");
+    if (it != std::end(json_double_value)) {
+      return it->second;
+    }
+    // As per the spec, if strenghth is ommited, this parameter is 1
+    return 1;
+  }
+
+  /// Material factor, like the roughness or metalness of a material
+  /// Returned value is only valid if the parameter represent a texture from a
+  /// material
+  double Factor() const { return number_value; }
+
+  /// Return the color of a material
+  /// Returned value is only valid if the parameter represent a texture from a
+  /// material
+  ColorValue ColorFactor() const {
+    return {
+        {// this agregate intialize the std::array object, and uses C++11 RVO.
+         number_array[0], number_array[1], number_array[2],
+         (number_array.size() > 3 ? number_array[3] : 1.0)}};
+  }
+
+  Parameter() = default;
+  DEFAULT_METHODS(Parameter)
+  bool operator==(const Parameter &) const;
+};
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wpadded"
+#endif
+
+typedef std::map<std::string, Parameter> ParameterMap;
+typedef std::map<std::string, Value> ExtensionMap;
+
+struct AnimationChannel {
+  int sampler;              // required
+  int target_node;          // required (index of the node to target)
+  std::string target_path;  // required in ["translation", "rotation", "scale",
+                            // "weights"]
+  Value extras;
+  ExtensionMap extensions;
+  ExtensionMap target_extensions;
+
+  // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
+  std::string extras_json_string;
+  std::string extensions_json_string;
+  std::string target_extensions_json_string;
+
+  AnimationChannel() : sampler(-1), target_node(-1) {}
+  DEFAULT_METHODS(AnimationChannel)
+  bool operator==(const AnimationChannel &) const;
+};
+
+struct AnimationSampler {
+  int input;                  // required
+  int output;                 // required
+  std::string interpolation;  // "LINEAR", "STEP","CUBICSPLINE" or user defined
+                              // string. default "LINEAR"
+  Value extras;
+  ExtensionMap extensions;
+
+  // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
+  std::string extras_json_string;
+  std::string extensions_json_string;
+
+  AnimationSampler() : input(-1), output(-1), interpolation("LINEAR") {}
+  DEFAULT_METHODS(AnimationSampler)
+  bool operator==(const AnimationSampler &) const;
+};
+
+struct Animation {
+  std::string name;
+  std::vector<AnimationChannel> channels;
+  std::vector<AnimationSampler> samplers;
+  Value extras;
+  ExtensionMap extensions;
+
+  // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
+  std::string extras_json_string;
+  std::string extensions_json_string;
+
+  Animation() = default;
+  DEFAULT_METHODS(Animation)
+  bool operator==(const Animation &) const;
+};
+
+struct Skin {
+  std::string name;
+  int inverseBindMatrices;  // required here but not in the spec
+  int skeleton;             // The index of the node used as a skeleton root
+  std::vector<int> joints;  // Indices of skeleton nodes
+
+  Value extras;
+  ExtensionMap extensions;
+
+  // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
+  std::string extras_json_string;
+  std::string extensions_json_string;
+
+  Skin() {
+    inverseBindMatrices = -1;
+    skeleton = -1;
+  }
+  DEFAULT_METHODS(Skin)
+  bool operator==(const Skin &) const;
+};
+
+struct Sampler {
+  std::string name;
+  // glTF 2.0 spec does not define default value for `minFilter` and
+  // `magFilter`. Set -1 in TinyGLTF(issue #186)
+  int minFilter =
+      -1;  // optional. -1 = no filter defined. ["NEAREST", "LINEAR",
+           // "NEAREST_MIPMAP_LINEAR", "LINEAR_MIPMAP_NEAREST",
+           // "NEAREST_MIPMAP_LINEAR", "LINEAR_MIPMAP_LINEAR"]
+  int magFilter =
+      -1;  // optional. -1 = no filter defined. ["NEAREST", "LINEAR"]
+  int wrapS =
+      TINYGLTF_TEXTURE_WRAP_REPEAT;  // ["CLAMP_TO_EDGE", "MIRRORED_REPEAT",
+                                     // "REPEAT"], default "REPEAT"
+  int wrapT =
+      TINYGLTF_TEXTURE_WRAP_REPEAT;  // ["CLAMP_TO_EDGE", "MIRRORED_REPEAT",
+                                     // "REPEAT"], default "REPEAT"
+  int wrapR = TINYGLTF_TEXTURE_WRAP_REPEAT;  // TinyGLTF extension
+
+  Value extras;
+  ExtensionMap extensions;
+
+  // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
+  std::string extras_json_string;
+  std::string extensions_json_string;
+
+  Sampler()
+      : minFilter(-1),
+        magFilter(-1),
+        wrapS(TINYGLTF_TEXTURE_WRAP_REPEAT),
+        wrapT(TINYGLTF_TEXTURE_WRAP_REPEAT),
+        wrapR(TINYGLTF_TEXTURE_WRAP_REPEAT) {}
+  DEFAULT_METHODS(Sampler)
+  bool operator==(const Sampler &) const;
+};
+
+struct Image {
+  std::string name;
+  int width;
+  int height;
+  int component;
+  int bits;        // bit depth per channel. 8(byte), 16 or 32.
+  int pixel_type;  // pixel type(TINYGLTF_COMPONENT_TYPE_***). usually
+                   // UBYTE(bits = 8) or USHORT(bits = 16)
+  std::vector<unsigned char> image;
+  int bufferView;        // (required if no uri)
+  std::string mimeType;  // (required if no uri) ["image/jpeg", "image/png",
+                         // "image/bmp", "image/gif"]
+  std::string uri;       // (required if no mimeType) uri is not decoded(e.g.
+                         // whitespace may be represented as %20)
+  Value extras;
+  ExtensionMap extensions;
+
+  // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
+  std::string extras_json_string;
+  std::string extensions_json_string;
+
+  // When this flag is true, data is stored to `image` in as-is format(e.g. jpeg
+  // compressed for "image/jpeg" mime) This feature is good if you use custom
+  // image loader function. (e.g. delayed decoding of images for faster glTF
+  // parsing) Default parser for Image does not provide as-is loading feature at
+  // the moment. (You can manipulate this by providing your own LoadImageData
+  // function)
+  bool as_is;
+
+  Image() : as_is(false) {
+    bufferView = -1;
+    width = -1;
+    height = -1;
+    component = -1;
+  }
+  DEFAULT_METHODS(Image)
+
+  bool operator==(const Image &) const;
+};
+
+struct Texture {
+  std::string name;
+
+  int sampler;
+  int source;
+  Value extras;
+  ExtensionMap extensions;
+
+  // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
+  std::string extras_json_string;
+  std::string extensions_json_string;
+
+  Texture() : sampler(-1), source(-1) {}
+  DEFAULT_METHODS(Texture)
+
+  bool operator==(const Texture &) const;
+};
+
+struct TextureInfo {
+  int index = -1;  // required.
+  int texCoord;    // The set index of texture's TEXCOORD attribute used for
+                   // texture coordinate mapping.
+
+  Value extras;
+  ExtensionMap extensions;
+
+  // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
+  std::string extras_json_string;
+  std::string extensions_json_string;
+
+  TextureInfo() : index(-1), texCoord(0) {}
+  DEFAULT_METHODS(TextureInfo)
+  bool operator==(const TextureInfo &) const;
+};
+
+struct NormalTextureInfo {
+  int index = -1;  // required
+  int texCoord;    // The set index of texture's TEXCOORD attribute used for
+                   // texture coordinate mapping.
+  double scale;    // scaledNormal = normalize((<sampled normal texture value>
+                   // * 2.0 - 1.0) * vec3(<normal scale>, <normal scale>, 1.0))
+
+  Value extras;
+  ExtensionMap extensions;
+
+  // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
+  std::string extras_json_string;
+  std::string extensions_json_string;
+
+  NormalTextureInfo() : index(-1), texCoord(0), scale(1.0) {}
+  DEFAULT_METHODS(NormalTextureInfo)
+  bool operator==(const NormalTextureInfo &) const;
+};
+
+struct OcclusionTextureInfo {
+  int index = -1;   // required
+  int texCoord;     // The set index of texture's TEXCOORD attribute used for
+                    // texture coordinate mapping.
+  double strength;  // occludedColor = lerp(color, color * <sampled occlusion
+                    // texture value>, <occlusion strength>)
+
+  Value extras;
+  ExtensionMap extensions;
+
+  // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
+  std::string extras_json_string;
+  std::string extensions_json_string;
+
+  OcclusionTextureInfo() : index(-1), texCoord(0), strength(1.0) {}
+  DEFAULT_METHODS(OcclusionTextureInfo)
+  bool operator==(const OcclusionTextureInfo &) const;
+};
+
+// pbrMetallicRoughness class defined in glTF 2.0 spec.
+struct PbrMetallicRoughness {
+  std::vector<double> baseColorFactor;  // len = 4. default [1,1,1,1]
+  TextureInfo baseColorTexture;
+  double metallicFactor;   // default 1
+  double roughnessFactor;  // default 1
+  TextureInfo metallicRoughnessTexture;
+
+  Value extras;
+  ExtensionMap extensions;
+
+  // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
+  std::string extras_json_string;
+  std::string extensions_json_string;
+
+  PbrMetallicRoughness()
+      : baseColorFactor(std::vector<double>{1.0, 1.0, 1.0, 1.0}),
+        metallicFactor(1.0),
+        roughnessFactor(1.0) {}
+  DEFAULT_METHODS(PbrMetallicRoughness)
+  bool operator==(const PbrMetallicRoughness &) const;
+};
+
+// Each extension should be stored in a ParameterMap.
+// members not in the values could be included in the ParameterMap
+// to keep a single material model
+struct Material {
+  std::string name;
+
+  std::vector<double> emissiveFactor;  // length 3. default [0, 0, 0]
+  std::string alphaMode;               // default "OPAQUE"
+  double alphaCutoff;                  // default 0.5
+  bool doubleSided;                    // default false;
+
+  PbrMetallicRoughness pbrMetallicRoughness;
+
+  NormalTextureInfo normalTexture;
+  OcclusionTextureInfo occlusionTexture;
+  TextureInfo emissiveTexture;
+
+  // For backward compatibility
+  // TODO(syoyo): Remove `values` and `additionalValues` in the next release.
+  ParameterMap values;
+  ParameterMap additionalValues;
+
+  ExtensionMap extensions;
+  Value extras;
+
+  // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
+  std::string extras_json_string;
+  std::string extensions_json_string;
+
+  Material() : alphaMode("OPAQUE"), alphaCutoff(0.5), doubleSided(false) {}
+  DEFAULT_METHODS(Material)
+
+  bool operator==(const Material &) const;
+};
+
+struct BufferView {
+  std::string name;
+  int buffer{-1};        // Required
+  size_t byteOffset{0};  // minimum 0, default 0
+  size_t byteLength{0};  // required, minimum 1. 0 = invalid
+  size_t byteStride{0};  // minimum 4, maximum 252 (multiple of 4), default 0 =
+                         // understood to be tightly packed
+  int target{0};  // ["ARRAY_BUFFER", "ELEMENT_ARRAY_BUFFER"] for vertex indices
+                  // or atttribs. Could be 0 for other data
+  Value extras;
+  ExtensionMap extensions;
+
+  // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
+  std::string extras_json_string;
+  std::string extensions_json_string;
+
+  bool dracoDecoded{false};  // Flag indicating this has been draco decoded
+
+  BufferView()
+      : buffer(-1),
+        byteOffset(0),
+        byteLength(0),
+        byteStride(0),
+        target(0),
+        dracoDecoded(false) {}
+  DEFAULT_METHODS(BufferView)
+  bool operator==(const BufferView &) const;
+};
+
+struct Accessor {
+  int bufferView;  // optional in spec but required here since sparse accessor
+                   // are not supported
+  std::string name;
+  size_t byteOffset;
+  bool normalized;    // optional.
+  int componentType;  // (required) One of TINYGLTF_COMPONENT_TYPE_***
+  size_t count;       // required
+  int type;           // (required) One of TINYGLTF_TYPE_***   ..
+  Value extras;
+  ExtensionMap extensions;
+
+  // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
+  std::string extras_json_string;
+  std::string extensions_json_string;
+
+  std::vector<double> minValues;  // optional
+  std::vector<double> maxValues;  // optional
+
+  struct {
+    int count;
+    bool isSparse;
+    struct {
+      int byteOffset;
+      int bufferView;
+      int componentType;  // a TINYGLTF_COMPONENT_TYPE_ value
+    } indices;
+    struct {
+      int bufferView;
+      int byteOffset;
+    } values;
+  } sparse;
+
+  ///
+  /// Utility function to compute byteStride for a given bufferView object.
+  /// Returns -1 upon invalid glTF value or parameter configuration.
+  ///
+  int ByteStride(const BufferView &bufferViewObject) const {
+    if (bufferViewObject.byteStride == 0) {
+      // Assume data is tightly packed.
+      int componentSizeInBytes =
+          GetComponentSizeInBytes(static_cast<uint32_t>(componentType));
+      if (componentSizeInBytes <= 0) {
+        return -1;
+      }
+
+      int numComponents = GetNumComponentsInType(static_cast<uint32_t>(type));
+      if (numComponents <= 0) {
+        return -1;
+      }
+
+      return componentSizeInBytes * numComponents;
+    } else {
+      // Check if byteStride is a mulple of the size of the accessor's component
+      // type.
+      int componentSizeInBytes =
+          GetComponentSizeInBytes(static_cast<uint32_t>(componentType));
+      if (componentSizeInBytes <= 0) {
+        return -1;
+      }
+
+      if ((bufferViewObject.byteStride % uint32_t(componentSizeInBytes)) != 0) {
+        return -1;
+      }
+      return static_cast<int>(bufferViewObject.byteStride);
+    }
+
+    // unreachable return 0;
+  }
+
+  Accessor()
+      : bufferView(-1),
+        byteOffset(0),
+        normalized(false),
+        componentType(-1),
+        count(0),
+        type(-1) {
+    sparse.isSparse = false;
+  }
+  DEFAULT_METHODS(Accessor)
+  bool operator==(const tinygltf::Accessor &) const;
+};
+
+struct PerspectiveCamera {
+  double aspectRatio;  // min > 0
+  double yfov;         // required. min > 0
+  double zfar;         // min > 0
+  double znear;        // required. min > 0
+
+  PerspectiveCamera()
+      : aspectRatio(0.0),
+        yfov(0.0),
+        zfar(0.0)  // 0 = use infinite projecton matrix
+        ,
+        znear(0.0) {}
+  DEFAULT_METHODS(PerspectiveCamera)
+  bool operator==(const PerspectiveCamera &) const;
+
+  ExtensionMap extensions;
+  Value extras;
+
+  // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
+  std::string extras_json_string;
+  std::string extensions_json_string;
+};
+
+struct OrthographicCamera {
+  double xmag;   // required. must not be zero.
+  double ymag;   // required. must not be zero.
+  double zfar;   // required. `zfar` must be greater than `znear`.
+  double znear;  // required
+
+  OrthographicCamera() : xmag(0.0), ymag(0.0), zfar(0.0), znear(0.0) {}
+  DEFAULT_METHODS(OrthographicCamera)
+  bool operator==(const OrthographicCamera &) const;
+
+  ExtensionMap extensions;
+  Value extras;
+
+  // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
+  std::string extras_json_string;
+  std::string extensions_json_string;
+};
+
+struct Camera {
+  std::string type;  // required. "perspective" or "orthographic"
+  std::string name;
+
+  PerspectiveCamera perspective;
+  OrthographicCamera orthographic;
+
+  Camera() {}
+  DEFAULT_METHODS(Camera)
+  bool operator==(const Camera &) const;
+
+  ExtensionMap extensions;
+  Value extras;
+
+  // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
+  std::string extras_json_string;
+  std::string extensions_json_string;
+};
+
+struct Primitive {
+  std::map<std::string, int> attributes;  // (required) A dictionary object of
+                                          // integer, where each integer
+                                          // is the index of the accessor
+                                          // containing an attribute.
+  int material;  // The index of the material to apply to this primitive
+                 // when rendering.
+  int indices;   // The index of the accessor that contains the indices.
+  int mode;      // one of TINYGLTF_MODE_***
+  std::vector<std::map<std::string, int> > targets;  // array of morph targets,
+  // where each target is a dict with attribues in ["POSITION, "NORMAL",
+  // "TANGENT"] pointing
+  // to their corresponding accessors
+  ExtensionMap extensions;
+  Value extras;
+
+  // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
+  std::string extras_json_string;
+  std::string extensions_json_string;
+
+  Primitive() {
+    material = -1;
+    indices = -1;
+  }
+  DEFAULT_METHODS(Primitive)
+  bool operator==(const Primitive &) const;
+};
+
+struct Mesh {
+  std::string name;
+  std::vector<Primitive> primitives;
+  std::vector<double> weights;  // weights to be applied to the Morph Targets
+  ExtensionMap extensions;
+  Value extras;
+
+  // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
+  std::string extras_json_string;
+  std::string extensions_json_string;
+
+  Mesh() = default;
+  DEFAULT_METHODS(Mesh)
+  bool operator==(const Mesh &) const;
+};
+
+class Node {
+ public:
+  Node() : camera(-1), skin(-1), mesh(-1) {}
+
+  DEFAULT_METHODS(Node)
+
+  bool operator==(const Node &) const;
+
+  int camera;  // the index of the camera referenced by this node
+
+  std::string name;
+  int skin;
+  int mesh;
+  std::vector<int> children;
+  std::vector<double> rotation;     // length must be 0 or 4
+  std::vector<double> scale;        // length must be 0 or 3
+  std::vector<double> translation;  // length must be 0 or 3
+  std::vector<double> matrix;       // length must be 0 or 16
+  std::vector<double> weights;  // The weights of the instantiated Morph Target
+
+  ExtensionMap extensions;
+  Value extras;
+
+  // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
+  std::string extras_json_string;
+  std::string extensions_json_string;
+};
+
+struct Buffer {
+  std::string name;
+  std::vector<unsigned char> data;
+  std::string
+      uri;  // considered as required here but not in the spec (need to clarify)
+            // uri is not decoded(e.g. whitespace may be represented as %20)
+  Value extras;
+  ExtensionMap extensions;
+
+  // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
+  std::string extras_json_string;
+  std::string extensions_json_string;
+
+  Buffer() = default;
+  DEFAULT_METHODS(Buffer)
+  bool operator==(const Buffer &) const;
+};
+
+struct Asset {
+  std::string version;  // required
+  std::string generator;
+  std::string minVersion;
+  std::string copyright;
+  ExtensionMap extensions;
+  Value extras;
+
+  // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
+  std::string extras_json_string;
+  std::string extensions_json_string;
+
+  Asset() = default;
+  DEFAULT_METHODS(Asset)
+  bool operator==(const Asset &) const;
+};
+
+struct Scene {
+  std::string name;
+  std::vector<int> nodes;
+
+  ExtensionMap extensions;
+  Value extras;
+
+  // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
+  std::string extras_json_string;
+  std::string extensions_json_string;
+
+  Scene() = default;
+  DEFAULT_METHODS(Scene)
+  bool operator==(const Scene &) const;
+};
+
+struct SpotLight {
+  double innerConeAngle;
+  double outerConeAngle;
+
+  SpotLight() : innerConeAngle(0.0), outerConeAngle(0.7853981634) {}
+  DEFAULT_METHODS(SpotLight)
+  bool operator==(const SpotLight &) const;
+
+  ExtensionMap extensions;
+  Value extras;
+
+  // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
+  std::string extras_json_string;
+  std::string extensions_json_string;
+};
+
+struct Light {
+  std::string name;
+  std::vector<double> color;
+  double intensity;
+  std::string type;
+  double range;
+  SpotLight spot;
+
+  Light() : intensity(1.0), range(0.0) {}
+  DEFAULT_METHODS(Light)
+
+  bool operator==(const Light &) const;
+
+  ExtensionMap extensions;
+  Value extras;
+
+  // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
+  std::string extras_json_string;
+  std::string extensions_json_string;
+};
+
+class Model {
+ public:
+  Model() = default;
+  DEFAULT_METHODS(Model)
+
+  bool operator==(const Model &) const;
+
+  std::vector<Accessor> accessors;
+  std::vector<Animation> animations;
+  std::vector<Buffer> buffers;
+  std::vector<BufferView> bufferViews;
+  std::vector<Material> materials;
+  std::vector<Mesh> meshes;
+  std::vector<Node> nodes;
+  std::vector<Texture> textures;
+  std::vector<Image> images;
+  std::vector<Skin> skins;
+  std::vector<Sampler> samplers;
+  std::vector<Camera> cameras;
+  std::vector<Scene> scenes;
+  std::vector<Light> lights;
+
+  int defaultScene = -1;
+  std::vector<std::string> extensionsUsed;
+  std::vector<std::string> extensionsRequired;
+
+  Asset asset;
+
+  Value extras;
+  ExtensionMap extensions;
+
+  // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
+  std::string extras_json_string;
+  std::string extensions_json_string;
+};
+
+enum SectionCheck {
+  NO_REQUIRE = 0x00,
+  REQUIRE_VERSION = 0x01,
+  REQUIRE_SCENE = 0x02,
+  REQUIRE_SCENES = 0x04,
+  REQUIRE_NODES = 0x08,
+  REQUIRE_ACCESSORS = 0x10,
+  REQUIRE_BUFFERS = 0x20,
+  REQUIRE_BUFFER_VIEWS = 0x40,
+  REQUIRE_ALL = 0x7f
+};
+
+///
+/// LoadImageDataFunction type. Signature for custom image loading callbacks.
+///
+typedef bool (*LoadImageDataFunction)(Image *, const int, std::string *,
+                                      std::string *, int, int,
+                                      const unsigned char *, int, void *);
+
+///
+/// WriteImageDataFunction type. Signature for custom image writing callbacks.
+///
+typedef bool (*WriteImageDataFunction)(const std::string *, const std::string *,
+                                       Image *, bool, void *);
+
+#ifndef TINYGLTF_NO_STB_IMAGE
+// Declaration of default image loader callback
+bool LoadImageData(Image *image, const int image_idx, std::string *err,
+                   std::string *warn, int req_width, int req_height,
+                   const unsigned char *bytes, int size, void *);
+#endif
+
+#ifndef TINYGLTF_NO_STB_IMAGE_WRITE
+// Declaration of default image writer callback
+bool WriteImageData(const std::string *basepath, const std::string *filename,
+                    Image *image, bool embedImages, void *);
+#endif
+
+///
+/// FilExistsFunction type. Signature for custom filesystem callbacks.
+///
+typedef bool (*FileExistsFunction)(const std::string &abs_filename, void *);
+
+///
+/// ExpandFilePathFunction type. Signature for custom filesystem callbacks.
+///
+typedef std::string (*ExpandFilePathFunction)(const std::string &, void *);
+
+///
+/// ReadWholeFileFunction type. Signature for custom filesystem callbacks.
+///
+typedef bool (*ReadWholeFileFunction)(std::vector<unsigned char> *,
+                                      std::string *, const std::string &,
+                                      void *);
+
+///
+/// WriteWholeFileFunction type. Signature for custom filesystem callbacks.
+///
+typedef bool (*WriteWholeFileFunction)(std::string *, const std::string &,
+                                       const std::vector<unsigned char> &,
+                                       void *);
+
+///
+/// A structure containing all required filesystem callbacks and a pointer to
+/// their user data.
+///
+struct FsCallbacks {
+  FileExistsFunction FileExists;
+  ExpandFilePathFunction ExpandFilePath;
+  ReadWholeFileFunction ReadWholeFile;
+  WriteWholeFileFunction WriteWholeFile;
+
+  void *user_data;  // An argument that is passed to all fs callbacks
+};
+
+#ifndef TINYGLTF_NO_FS
+// Declaration of default filesystem callbacks
+
+bool FileExists(const std::string &abs_filename, void *);
+
+std::string ExpandFilePath(const std::string &filepath, void *);
+
+bool ReadWholeFile(std::vector<unsigned char> *out, std::string *err,
+                   const std::string &filepath, void *);
+
+bool WriteWholeFile(std::string *err, const std::string &filepath,
+                    const std::vector<unsigned char> &contents, void *);
+#endif
+
+///
+/// glTF Parser/Serialier context.
+///
+class TinyGLTF {
+ public:
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wc++98-compat"
+#endif
+
+  TinyGLTF() : bin_data_(nullptr), bin_size_(0), is_binary_(false) {}
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+  ~TinyGLTF() {}
+
+  ///
+  /// Loads glTF ASCII asset from a file.
+  /// Set warning message to `warn` for example it fails to load asserts.
+  /// Returns false and set error string to `err` if there's an error.
+  ///
+  bool LoadASCIIFromFile(Model *model, std::string *err, std::string *warn,
+                         const std::string &filename,
+                         unsigned int check_sections = REQUIRE_VERSION);
+
+  ///
+  /// Loads glTF ASCII asset from string(memory).
+  /// `length` = strlen(str);
+  /// Set warning message to `warn` for example it fails to load asserts.
+  /// Returns false and set error string to `err` if there's an error.
+  ///
+  bool LoadASCIIFromString(Model *model, std::string *err, std::string *warn,
+                           const char *str, const unsigned int length,
+                           const std::string &base_dir,
+                           unsigned int check_sections = REQUIRE_VERSION);
+
+  ///
+  /// Loads glTF binary asset from a file.
+  /// Set warning message to `warn` for example it fails to load asserts.
+  /// Returns false and set error string to `err` if there's an error.
+  ///
+  bool LoadBinaryFromFile(Model *model, std::string *err, std::string *warn,
+                          const std::string &filename,
+                          unsigned int check_sections = REQUIRE_VERSION);
+
+  ///
+  /// Loads glTF binary asset from memory.
+  /// `length` = strlen(str);
+  /// Set warning message to `warn` for example it fails to load asserts.
+  /// Returns false and set error string to `err` if there's an error.
+  ///
+  bool LoadBinaryFromMemory(Model *model, std::string *err, std::string *warn,
+                            const unsigned char *bytes,
+                            const unsigned int length,
+                            const std::string &base_dir = "",
+                            unsigned int check_sections = REQUIRE_VERSION);
+
+  ///
+  /// Write glTF to stream, buffers and images will be embeded
+  ///
+  bool WriteGltfSceneToStream(Model *model, std::ostream &stream,
+                              bool prettyPrint, bool writeBinary);
+
+  ///
+  /// Write glTF to file.
+  ///
+  bool WriteGltfSceneToFile(Model *model, const std::string &filename,
+                            bool embedImages, bool embedBuffers,
+                            bool prettyPrint, bool writeBinary);
+
+  ///
+  /// Set callback to use for loading image data
+  ///
+  void SetImageLoader(LoadImageDataFunction LoadImageData, void *user_data);
+
+  ///
+  /// Set callback to use for writing image data
+  ///
+  void SetImageWriter(WriteImageDataFunction WriteImageData, void *user_data);
+
+  ///
+  /// Set callbacks to use for filesystem (fs) access and their user data
+  ///
+  void SetFsCallbacks(FsCallbacks callbacks);
+
+  ///
+  /// Set serializing default values(default = false).
+  /// When true, default values are force serialized to .glTF.
+  /// This may be helpfull if you want to serialize a full description of glTF
+  /// data.
+  ///
+  /// TODO(LTE): Supply parsing option as function arguments to
+  /// `LoadASCIIFromFile()` and others, not by a class method
+  ///
+  void SetSerializeDefaultValues(const bool enabled) {
+    serialize_default_values_ = enabled;
+  }
+
+  bool GetSerializeDefaultValues() const { return serialize_default_values_; }
+
+  ///
+  /// Store original JSON string for `extras` and `extensions`.
+  /// This feature will be useful when the user want to reconstruct custom data
+  /// structure from JSON string.
+  ///
+  void SetStoreOriginalJSONForExtrasAndExtensions(const bool enabled) {
+    store_original_json_for_extras_and_extensions_ = enabled;
+  }
+
+  bool GetStoreOriginalJSONForExtrasAndExtensions() const {
+    return store_original_json_for_extras_and_extensions_;
+  }
+
+ private:
+  ///
+  /// Loads glTF asset from string(memory).
+  /// `length` = strlen(str);
+  /// Set warning message to `warn` for example it fails to load asserts
+  /// Returns false and set error string to `err` if there's an error.
+  ///
+  bool LoadFromString(Model *model, std::string *err, std::string *warn,
+                      const char *str, const unsigned int length,
+                      const std::string &base_dir, unsigned int check_sections);
+
+  const unsigned char *bin_data_ = nullptr;
+  size_t bin_size_ = 0;
+  bool is_binary_ = false;
+
+  bool serialize_default_values_ = false;  ///< Serialize default values?
+
+  bool store_original_json_for_extras_and_extensions_ = false;
+
+  FsCallbacks fs = {
+#ifndef TINYGLTF_NO_FS
+      &tinygltf::FileExists, &tinygltf::ExpandFilePath,
+      &tinygltf::ReadWholeFile, &tinygltf::WriteWholeFile,
+
+      nullptr  // Fs callback user data
+#else
+      nullptr, nullptr, nullptr, nullptr,
+
+      nullptr  // Fs callback user data
+#endif
+  };
+
+  LoadImageDataFunction LoadImageData =
+#ifndef TINYGLTF_NO_STB_IMAGE
+      &tinygltf::LoadImageData;
+#else
+      nullptr;
+#endif
+  void *load_image_user_data_ = reinterpret_cast<void *>(&fs);
+
+  WriteImageDataFunction WriteImageData =
+#ifndef TINYGLTF_NO_STB_IMAGE_WRITE
+      &tinygltf::WriteImageData;
+#else
+      nullptr;
+#endif
+  void *write_image_user_data_ = reinterpret_cast<void *>(&fs);
+};
+
+#ifdef __clang__
+#pragma clang diagnostic pop  // -Wpadded
+#endif
+
+}  // namespace tinygltf
+
+#endif  // TINY_GLTF_H_
+
+#if defined(TINYGLTF_IMPLEMENTATION) || defined(__INTELLISENSE__)
+#include <algorithm>
+//#include <cassert>
+#ifndef TINYGLTF_NO_FS
+#include <cstdio>
+#include <fstream>
+#endif
+#include <sstream>
+
+#ifdef __clang__
+// Disable some warnings for external files.
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wfloat-equal"
+#pragma clang diagnostic ignored "-Wexit-time-destructors"
+#pragma clang diagnostic ignored "-Wconversion"
+#pragma clang diagnostic ignored "-Wold-style-cast"
+#pragma clang diagnostic ignored "-Wglobal-constructors"
+#if __has_warning("-Wreserved-id-macro")
+#pragma clang diagnostic ignored "-Wreserved-id-macro"
+#endif
+#pragma clang diagnostic ignored "-Wdisabled-macro-expansion"
+#pragma clang diagnostic ignored "-Wpadded"
+#pragma clang diagnostic ignored "-Wc++98-compat"
+#pragma clang diagnostic ignored "-Wc++98-compat-pedantic"
+#pragma clang diagnostic ignored "-Wdocumentation-unknown-command"
+#pragma clang diagnostic ignored "-Wswitch-enum"
+#pragma clang diagnostic ignored "-Wimplicit-fallthrough"
+#pragma clang diagnostic ignored "-Wweak-vtables"
+#pragma clang diagnostic ignored "-Wcovered-switch-default"
+#if __has_warning("-Wdouble-promotion")
+#pragma clang diagnostic ignored "-Wdouble-promotion"
+#endif
+#if __has_warning("-Wcomma")
+#pragma clang diagnostic ignored "-Wcomma"
+#endif
+#if __has_warning("-Wzero-as-null-pointer-constant")
+#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant"
+#endif
+#if __has_warning("-Wcast-qual")
+#pragma clang diagnostic ignored "-Wcast-qual"
+#endif
+#if __has_warning("-Wmissing-variable-declarations")
+#pragma clang diagnostic ignored "-Wmissing-variable-declarations"
+#endif
+#if __has_warning("-Wmissing-prototypes")
+#pragma clang diagnostic ignored "-Wmissing-prototypes"
+#endif
+#if __has_warning("-Wcast-align")
+#pragma clang diagnostic ignored "-Wcast-align"
+#endif
+#if __has_warning("-Wnewline-eof")
+#pragma clang diagnostic ignored "-Wnewline-eof"
+#endif
+#if __has_warning("-Wunused-parameter")
+#pragma clang diagnostic ignored "-Wunused-parameter"
+#endif
+#if __has_warning("-Wmismatched-tags")
+#pragma clang diagnostic ignored "-Wmismatched-tags"
+#endif
+#if __has_warning("-Wextra-semi-stmt")
+#pragma clang diagnostic ignored "-Wextra-semi-stmt"
+#endif
+#endif
+
+// Disable GCC warnigs
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wtype-limits"
+#endif  // __GNUC__
+
+#ifndef TINYGLTF_NO_INCLUDE_JSON
+#ifndef TINYGLTF_USE_RAPIDJSON
+#include "json.hpp"
+#else
+#include "document.h"
+#include "prettywriter.h"
+#include "rapidjson.h"
+#include "stringbuffer.h"
+#include "writer.h"
+#endif
+#endif
+
+#ifdef TINYGLTF_ENABLE_DRACO
+#include "draco/compression/decode.h"
+#include "draco/core/decoder_buffer.h"
+#endif
+
+#ifndef TINYGLTF_NO_STB_IMAGE
+#ifndef TINYGLTF_NO_INCLUDE_STB_IMAGE
+#include "stb_image.h"
+#endif
+#endif
+
+#ifndef TINYGLTF_NO_STB_IMAGE_WRITE
+#ifndef TINYGLTF_NO_INCLUDE_STB_IMAGE_WRITE
+#include "stb_image_write.h"
+#endif
+#endif
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+
+#ifdef _WIN32
+
+// issue 143.
+// Define NOMINMAX to avoid min/max defines,
+// but undef it after included windows.h
+#ifndef NOMINMAX
+#define TINYGLTF_INTERNAL_NOMINMAX
+#define NOMINMAX
+#endif
+
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#define TINYGLTF_INTERNAL_WIN32_LEAN_AND_MEAN
+#endif
+#include <windows.h>  // include API for expanding a file path
+
+#ifdef TINYGLTF_INTERNAL_WIN32_LEAN_AND_MEAN
+#undef WIN32_LEAN_AND_MEAN
+#endif
+
+#if defined(TINYGLTF_INTERNAL_NOMINMAX)
+#undef NOMINMAX
+#endif
+
+#if defined(__GLIBCXX__)  // mingw
+
+#include <fcntl.h>  // _O_RDONLY
+
+#include <ext/stdio_filebuf.h>  // fstream (all sorts of IO stuff) + stdio_filebuf (=streambuf)
+
+#endif
+
+#elif !defined(__ANDROID__)
+#include <wordexp.h>
+#endif
+
+#if defined(__sparcv9)
+// Big endian
+#else
+#if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || MINIZ_X86_OR_X64_CPU
+#define TINYGLTF_LITTLE_ENDIAN 1
+#endif
+#endif
+
+namespace {
+#ifdef TINYGLTF_USE_RAPIDJSON
+
+#ifdef TINYGLTF_USE_RAPIDJSON_CRTALLOCATOR
+// This uses the RapidJSON CRTAllocator.  It is thread safe and multiple
+// documents may be active at once.
+using json =
+    rapidjson::GenericValue<rapidjson::UTF8<>, rapidjson::CrtAllocator>;
+using json_const_iterator = json::ConstMemberIterator;
+using json_const_array_iterator = json const *;
+using JsonDocument =
+    rapidjson::GenericDocument<rapidjson::UTF8<>, rapidjson::CrtAllocator>;
+rapidjson::CrtAllocator s_CrtAllocator;  // stateless and thread safe
+rapidjson::CrtAllocator &GetAllocator() { return s_CrtAllocator; }
+#else
+// This uses the default RapidJSON MemoryPoolAllocator.  It is very fast, but
+// not thread safe. Only a single JsonDocument may be active at any one time,
+// meaning only a single gltf load/save can be active any one time.
+using json = rapidjson::Value;
+using json_const_iterator = json::ConstMemberIterator;
+using json_const_array_iterator = json const *;
+rapidjson::Document *s_pActiveDocument = nullptr;
+rapidjson::Document::AllocatorType &GetAllocator() {
+  assert(s_pActiveDocument);  // Root json node must be JsonDocument type
+  return s_pActiveDocument->GetAllocator();
+}
+
+#ifdef __clang__
+#pragma clang diagnostic push
+// Suppress JsonDocument(JsonDocument &&rhs) noexcept
+#pragma clang diagnostic ignored "-Wunused-member-function"
+#endif
+
+struct JsonDocument : public rapidjson::Document {
+  JsonDocument() {
+    assert(s_pActiveDocument ==
+           nullptr);  // When using default allocator, only one document can be
+                      // active at a time, if you need multiple active at once,
+                      // define TINYGLTF_USE_RAPIDJSON_CRTALLOCATOR
+    s_pActiveDocument = this;
+  }
+  JsonDocument(const JsonDocument &) = delete;
+  JsonDocument(JsonDocument &&rhs) noexcept
+      : rapidjson::Document(std::move(rhs)) {
+    s_pActiveDocument = this;
+    rhs.isNil = true;
+  }
+  ~JsonDocument() {
+    if (!isNil) {
+      s_pActiveDocument = nullptr;
+    }
+  }
+
+ private:
+  bool isNil = false;
+};
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+#endif  // TINYGLTF_USE_RAPIDJSON_CRTALLOCATOR
+
+#else
+using nlohmann::json;
+using json_const_iterator = json::const_iterator;
+using json_const_array_iterator = json_const_iterator;
+using JsonDocument = json;
+#endif
+
+void JsonParse(JsonDocument &doc, const char *str, size_t length,
+               bool throwExc = false) {
+#ifdef TINYGLTF_USE_RAPIDJSON
+  (void)throwExc;
+  doc.Parse(str, length);
+#else
+  doc = json::parse(str, str + length, nullptr, throwExc);
+#endif
+}
+}  // namespace
+
+#ifdef __APPLE__
+#include "TargetConditionals.h"
+#endif
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wc++98-compat"
+#endif
+
+namespace tinygltf {
+
+// Equals function for Value, for recursivity
+static bool Equals(const tinygltf::Value &one, const tinygltf::Value &other) {
+  if (one.Type() != other.Type()) return false;
+
+  switch (one.Type()) {
+    case NULL_TYPE:
+      return true;
+    case BOOL_TYPE:
+      return one.Get<bool>() == other.Get<bool>();
+    case REAL_TYPE:
+      return TINYGLTF_DOUBLE_EQUAL(one.Get<double>(), other.Get<double>());
+    case INT_TYPE:
+      return one.Get<int>() == other.Get<int>();
+    case OBJECT_TYPE: {
+      auto oneObj = one.Get<tinygltf::Value::Object>();
+      auto otherObj = other.Get<tinygltf::Value::Object>();
+      if (oneObj.size() != otherObj.size()) return false;
+      for (auto &it : oneObj) {
+        auto otherIt = otherObj.find(it.first);
+        if (otherIt == otherObj.end()) return false;
+
+        if (!Equals(it.second, otherIt->second)) return false;
+      }
+      return true;
+    }
+    case ARRAY_TYPE: {
+      if (one.Size() != other.Size()) return false;
+      for (int i = 0; i < int(one.Size()); ++i)
+        if (!Equals(one.Get(i), other.Get(i))) return false;
+      return true;
+    }
+    case STRING_TYPE:
+      return one.Get<std::string>() == other.Get<std::string>();
+    case BINARY_TYPE:
+      return one.Get<std::vector<unsigned char> >() ==
+             other.Get<std::vector<unsigned char> >();
+    default: {
+      // unhandled type
+      return false;
+    }
+  }
+}
+
+// Equals function for std::vector<double> using TINYGLTF_DOUBLE_EPSILON
+static bool Equals(const std::vector<double> &one,
+                   const std::vector<double> &other) {
+  if (one.size() != other.size()) return false;
+  for (int i = 0; i < int(one.size()); ++i) {
+    if (!TINYGLTF_DOUBLE_EQUAL(one[size_t(i)], other[size_t(i)])) return false;
+  }
+  return true;
+}
+
+bool Accessor::operator==(const Accessor &other) const {
+  return this->bufferView == other.bufferView &&
+         this->byteOffset == other.byteOffset &&
+         this->componentType == other.componentType &&
+         this->count == other.count && this->extensions == other.extensions &&
+         this->extras == other.extras &&
+         Equals(this->maxValues, other.maxValues) &&
+         Equals(this->minValues, other.minValues) && this->name == other.name &&
+         this->normalized == other.normalized && this->type == other.type;
+}
+bool Animation::operator==(const Animation &other) const {
+  return this->channels == other.channels &&
+         this->extensions == other.extensions && this->extras == other.extras &&
+         this->name == other.name && this->samplers == other.samplers;
+}
+bool AnimationChannel::operator==(const AnimationChannel &other) const {
+  return this->extensions == other.extensions && this->extras == other.extras &&
+         this->target_node == other.target_node &&
+         this->target_path == other.target_path &&
+         this->sampler == other.sampler;
+}
+bool AnimationSampler::operator==(const AnimationSampler &other) const {
+  return this->extras == other.extras && this->extensions == other.extensions &&
+         this->input == other.input &&
+         this->interpolation == other.interpolation &&
+         this->output == other.output;
+}
+bool Asset::operator==(const Asset &other) const {
+  return this->copyright == other.copyright &&
+         this->extensions == other.extensions && this->extras == other.extras &&
+         this->generator == other.generator &&
+         this->minVersion == other.minVersion && this->version == other.version;
+}
+bool Buffer::operator==(const Buffer &other) const {
+  return this->data == other.data && this->extensions == other.extensions &&
+         this->extras == other.extras && this->name == other.name &&
+         this->uri == other.uri;
+}
+bool BufferView::operator==(const BufferView &other) const {
+  return this->buffer == other.buffer && this->byteLength == other.byteLength &&
+         this->byteOffset == other.byteOffset &&
+         this->byteStride == other.byteStride && this->name == other.name &&
+         this->target == other.target && this->extensions == other.extensions &&
+         this->extras == other.extras &&
+         this->dracoDecoded == other.dracoDecoded;
+}
+bool Camera::operator==(const Camera &other) const {
+  return this->name == other.name && this->extensions == other.extensions &&
+         this->extras == other.extras &&
+         this->orthographic == other.orthographic &&
+         this->perspective == other.perspective && this->type == other.type;
+}
+bool Image::operator==(const Image &other) const {
+  return this->bufferView == other.bufferView &&
+         this->component == other.component &&
+         this->extensions == other.extensions && this->extras == other.extras &&
+         this->height == other.height && this->image == other.image &&
+         this->mimeType == other.mimeType && this->name == other.name &&
+         this->uri == other.uri && this->width == other.width;
+}
+bool Light::operator==(const Light &other) const {
+  return Equals(this->color, other.color) && this->name == other.name &&
+         this->type == other.type;
+}
+bool Material::operator==(const Material &other) const {
+  return (this->pbrMetallicRoughness == other.pbrMetallicRoughness) &&
+         (this->normalTexture == other.normalTexture) &&
+         (this->occlusionTexture == other.occlusionTexture) &&
+         (this->emissiveTexture == other.emissiveTexture) &&
+         Equals(this->emissiveFactor, other.emissiveFactor) &&
+         (this->alphaMode == other.alphaMode) &&
+         TINYGLTF_DOUBLE_EQUAL(this->alphaCutoff, other.alphaCutoff) &&
+         (this->doubleSided == other.doubleSided) &&
+         (this->extensions == other.extensions) &&
+         (this->extras == other.extras) && (this->values == other.values) &&
+         (this->additionalValues == other.additionalValues) &&
+         (this->name == other.name);
+}
+bool Mesh::operator==(const Mesh &other) const {
+  return this->extensions == other.extensions && this->extras == other.extras &&
+         this->name == other.name && Equals(this->weights, other.weights) &&
+         this->primitives == other.primitives;
+}
+bool Model::operator==(const Model &other) const {
+  return this->accessors == other.accessors &&
+         this->animations == other.animations && this->asset == other.asset &&
+         this->buffers == other.buffers &&
+         this->bufferViews == other.bufferViews &&
+         this->cameras == other.cameras &&
+         this->defaultScene == other.defaultScene &&
+         this->extensions == other.extensions &&
+         this->extensionsRequired == other.extensionsRequired &&
+         this->extensionsUsed == other.extensionsUsed &&
+         this->extras == other.extras && this->images == other.images &&
+         this->lights == other.lights && this->materials == other.materials &&
+         this->meshes == other.meshes && this->nodes == other.nodes &&
+         this->samplers == other.samplers && this->scenes == other.scenes &&
+         this->skins == other.skins && this->textures == other.textures;
+}
+bool Node::operator==(const Node &other) const {
+  return this->camera == other.camera && this->children == other.children &&
+         this->extensions == other.extensions && this->extras == other.extras &&
+         Equals(this->matrix, other.matrix) && this->mesh == other.mesh &&
+         this->name == other.name && Equals(this->rotation, other.rotation) &&
+         Equals(this->scale, other.scale) && this->skin == other.skin &&
+         Equals(this->translation, other.translation) &&
+         Equals(this->weights, other.weights);
+}
+bool SpotLight::operator==(const SpotLight &other) const {
+  return this->extensions == other.extensions && this->extras == other.extras &&
+         TINYGLTF_DOUBLE_EQUAL(this->innerConeAngle, other.innerConeAngle) &&
+         TINYGLTF_DOUBLE_EQUAL(this->outerConeAngle, other.outerConeAngle);
+}
+bool OrthographicCamera::operator==(const OrthographicCamera &other) const {
+  return this->extensions == other.extensions && this->extras == other.extras &&
+         TINYGLTF_DOUBLE_EQUAL(this->xmag, other.xmag) &&
+         TINYGLTF_DOUBLE_EQUAL(this->ymag, other.ymag) &&
+         TINYGLTF_DOUBLE_EQUAL(this->zfar, other.zfar) &&
+         TINYGLTF_DOUBLE_EQUAL(this->znear, other.znear);
+}
+bool Parameter::operator==(const Parameter &other) const {
+  if (this->bool_value != other.bool_value ||
+      this->has_number_value != other.has_number_value)
+    return false;
+
+  if (!TINYGLTF_DOUBLE_EQUAL(this->number_value, other.number_value))
+    return false;
+
+  if (this->json_double_value.size() != other.json_double_value.size())
+    return false;
+  for (auto &it : this->json_double_value) {
+    auto otherIt = other.json_double_value.find(it.first);
+    if (otherIt == other.json_double_value.end()) return false;
+
+    if (!TINYGLTF_DOUBLE_EQUAL(it.second, otherIt->second)) return false;
+  }
+
+  if (!Equals(this->number_array, other.number_array)) return false;
+
+  if (this->string_value != other.string_value) return false;
+
+  return true;
+}
+bool PerspectiveCamera::operator==(const PerspectiveCamera &other) const {
+  return TINYGLTF_DOUBLE_EQUAL(this->aspectRatio, other.aspectRatio) &&
+         this->extensions == other.extensions && this->extras == other.extras &&
+         TINYGLTF_DOUBLE_EQUAL(this->yfov, other.yfov) &&
+         TINYGLTF_DOUBLE_EQUAL(this->zfar, other.zfar) &&
+         TINYGLTF_DOUBLE_EQUAL(this->znear, other.znear);
+}
+bool Primitive::operator==(const Primitive &other) const {
+  return this->attributes == other.attributes && this->extras == other.extras &&
+         this->indices == other.indices && this->material == other.material &&
+         this->mode == other.mode && this->targets == other.targets;
+}
+bool Sampler::operator==(const Sampler &other) const {
+  return this->extensions == other.extensions && this->extras == other.extras &&
+         this->magFilter == other.magFilter &&
+         this->minFilter == other.minFilter && this->name == other.name &&
+         this->wrapR == other.wrapR && this->wrapS == other.wrapS &&
+         this->wrapT == other.wrapT;
+}
+bool Scene::operator==(const Scene &other) const {
+  return this->extensions == other.extensions && this->extras == other.extras &&
+         this->name == other.name && this->nodes == other.nodes;
+}
+bool Skin::operator==(const Skin &other) const {
+  return this->extensions == other.extensions && this->extras == other.extras &&
+         this->inverseBindMatrices == other.inverseBindMatrices &&
+         this->joints == other.joints && this->name == other.name &&
+         this->skeleton == other.skeleton;
+}
+bool Texture::operator==(const Texture &other) const {
+  return this->extensions == other.extensions && this->extras == other.extras &&
+         this->name == other.name && this->sampler == other.sampler &&
+         this->source == other.source;
+}
+bool TextureInfo::operator==(const TextureInfo &other) const {
+  return this->extensions == other.extensions && this->extras == other.extras &&
+         this->index == other.index && this->texCoord == other.texCoord;
+}
+bool NormalTextureInfo::operator==(const NormalTextureInfo &other) const {
+  return this->extensions == other.extensions && this->extras == other.extras &&
+         this->index == other.index && this->texCoord == other.texCoord &&
+         TINYGLTF_DOUBLE_EQUAL(this->scale, other.scale);
+}
+bool OcclusionTextureInfo::operator==(const OcclusionTextureInfo &other) const {
+  return this->extensions == other.extensions && this->extras == other.extras &&
+         this->index == other.index && this->texCoord == other.texCoord &&
+         TINYGLTF_DOUBLE_EQUAL(this->strength, other.strength);
+}
+bool PbrMetallicRoughness::operator==(const PbrMetallicRoughness &other) const {
+  return this->extensions == other.extensions && this->extras == other.extras &&
+         (this->baseColorTexture == other.baseColorTexture) &&
+         (this->metallicRoughnessTexture == other.metallicRoughnessTexture) &&
+         Equals(this->baseColorFactor, other.baseColorFactor) &&
+         TINYGLTF_DOUBLE_EQUAL(this->metallicFactor, other.metallicFactor) &&
+         TINYGLTF_DOUBLE_EQUAL(this->roughnessFactor, other.roughnessFactor);
+}
+bool Value::operator==(const Value &other) const {
+  return Equals(*this, other);
+}
+
+static void swap4(unsigned int *val) {
+#ifdef TINYGLTF_LITTLE_ENDIAN
+  (void)val;
+#else
+  unsigned int tmp = *val;
+  unsigned char *dst = reinterpret_cast<unsigned char *>(val);
+  unsigned char *src = reinterpret_cast<unsigned char *>(&tmp);
+
+  dst[0] = src[3];
+  dst[1] = src[2];
+  dst[2] = src[1];
+  dst[3] = src[0];
+#endif
+}
+
+static std::string JoinPath(const std::string &path0,
+                            const std::string &path1) {
+  if (path0.empty()) {
+    return path1;
+  } else {
+    // check '/'
+    char lastChar = *path0.rbegin();
+    if (lastChar != '/') {
+      return path0 + std::string("/") + path1;
+    } else {
+      return path0 + path1;
+    }
+  }
+}
+
+static std::string FindFile(const std::vector<std::string> &paths,
+                            const std::string &filepath, FsCallbacks *fs) {
+  if (fs == nullptr || fs->ExpandFilePath == nullptr ||
+      fs->FileExists == nullptr) {
+    // Error, fs callback[s] missing
+    return std::string();
+  }
+
+  for (size_t i = 0; i < paths.size(); i++) {
+    std::string absPath =
+        fs->ExpandFilePath(JoinPath(paths[i], filepath), fs->user_data);
+    if (fs->FileExists(absPath, fs->user_data)) {
+      return absPath;
+    }
+  }
+
+  return std::string();
+}
+
+static std::string GetFilePathExtension(const std::string &FileName) {
+  if (FileName.find_last_of(".") != std::string::npos)
+    return FileName.substr(FileName.find_last_of(".") + 1);
+  return "";
+}
+
+static std::string GetBaseDir(const std::string &filepath) {
+  if (filepath.find_last_of("/\\") != std::string::npos)
+    return filepath.substr(0, filepath.find_last_of("/\\"));
+  return "";
+}
+
+// https://stackoverflow.com/questions/8520560/get-a-file-name-from-a-path
+static std::string GetBaseFilename(const std::string &filepath) {
+  return filepath.substr(filepath.find_last_of("/\\") + 1);
+}
+
+std::string base64_encode(unsigned char const *, unsigned int len);
+std::string base64_decode(std::string const &s);
+
+/*
+   base64.cpp and base64.h
+
+   Copyright (C) 2004-2008 René Nyffenegger
+
+   This source code is provided 'as-is', without any express or implied
+   warranty. In no event will the author be held liable for any damages
+   arising from the use of this software.
+
+   Permission is granted to anyone to use this software for any purpose,
+   including commercial applications, and to alter it and redistribute it
+   freely, subject to the following restrictions:
+
+   1. The origin of this source code must not be misrepresented; you must not
+      claim that you wrote the original source code. If you use this source code
+      in a product, an acknowledgment in the product documentation would be
+      appreciated but is not required.
+
+   2. Altered source versions must be plainly marked as such, and must not be
+      misrepresented as being the original source code.
+
+   3. This notice may not be removed or altered from any source distribution.
+
+   René Nyffenegger rene.nyffenegger@adp-gmbh.ch
+
+*/
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wsign-conversion"
+#pragma clang diagnostic ignored "-Wconversion"
+#endif
+
+static inline bool is_base64(unsigned char c) {
+  return (isalnum(c) || (c == '+') || (c == '/'));
+}
+
+std::string base64_encode(unsigned char const *bytes_to_encode,
+                          unsigned int in_len) {
+  std::string ret;
+  int i = 0;
+  int j = 0;
+  unsigned char char_array_3[3];
+  unsigned char char_array_4[4];
+
+  const char *base64_chars =
+      "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+      "abcdefghijklmnopqrstuvwxyz"
+      "0123456789+/";
+
+  while (in_len--) {
+    char_array_3[i++] = *(bytes_to_encode++);
+    if (i == 3) {
+      char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
+      char_array_4[1] =
+          ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
+      char_array_4[2] =
+          ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
+      char_array_4[3] = char_array_3[2] & 0x3f;
+
+      for (i = 0; (i < 4); i++) ret += base64_chars[char_array_4[i]];
+      i = 0;
+    }
+  }
+
+  if (i) {
+    for (j = i; j < 3; j++) char_array_3[j] = '\0';
+
+    char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
+    char_array_4[1] =
+        ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
+    char_array_4[2] =
+        ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
+
+    for (j = 0; (j < i + 1); j++) ret += base64_chars[char_array_4[j]];
+
+    while ((i++ < 3)) ret += '=';
+  }
+
+  return ret;
+}
+
+std::string base64_decode(std::string const &encoded_string) {
+  int in_len = static_cast<int>(encoded_string.size());
+  int i = 0;
+  int j = 0;
+  int in_ = 0;
+  unsigned char char_array_4[4], char_array_3[3];
+  std::string ret;
+
+  const std::string base64_chars =
+      "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+      "abcdefghijklmnopqrstuvwxyz"
+      "0123456789+/";
+
+  while (in_len-- && (encoded_string[in_] != '=') &&
+         is_base64(encoded_string[in_])) {
+    char_array_4[i++] = encoded_string[in_];
+    in_++;
+    if (i == 4) {
+      for (i = 0; i < 4; i++)
+        char_array_4[i] =
+            static_cast<unsigned char>(base64_chars.find(char_array_4[i]));
+
+      char_array_3[0] =
+          (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
+      char_array_3[1] =
+          ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
+      char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
+
+      for (i = 0; (i < 3); i++) ret += char_array_3[i];
+      i = 0;
+    }
+  }
+
+  if (i) {
+    for (j = i; j < 4; j++) char_array_4[j] = 0;
+
+    for (j = 0; j < 4; j++)
+      char_array_4[j] =
+          static_cast<unsigned char>(base64_chars.find(char_array_4[j]));
+
+    char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
+    char_array_3[1] =
+        ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
+    char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
+
+    for (j = 0; (j < i - 1); j++) ret += char_array_3[j];
+  }
+
+  return ret;
+}
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+// https://github.com/syoyo/tinygltf/issues/228
+// TODO(syoyo): Use uriparser https://uriparser.github.io/ for stricter Uri
+// decoding?
+//
+// https://stackoverflow.com/questions/18307429/encode-decode-url-in-c
+// http://dlib.net/dlib/server/server_http.cpp.html
+
+// --- dlib beign ------------------------------------------------------------
+// Copyright (C) 2003  Davis E. King (davis@dlib.net)
+// License: Boost Software License   See LICENSE.txt for the full license.
+
+namespace dlib {
+
+#if 0
+        inline unsigned char to_hex( unsigned char x )
+        {
+            return x + (x > 9 ? ('A'-10) : '0');
+        }
+
+        const std::string urlencode( const std::string& s )
+        {
+            std::ostringstream os;
+
+            for ( std::string::const_iterator ci = s.begin(); ci != s.end(); ++ci )
+            {
+                if ( (*ci >= 'a' && *ci <= 'z') ||
+                     (*ci >= 'A' && *ci <= 'Z') ||
+                     (*ci >= '0' && *ci <= '9') )
+                { // allowed
+                    os << *ci;
+                }
+                else if ( *ci == ' ')
+                {
+                    os << '+';
+                }
+                else
+                {
+                    os << '%' << to_hex(static_cast<unsigned char>(*ci >> 4)) << to_hex(static_cast<unsigned char>(*ci % 16));
+                }
+            }
+
+            return os.str();
+        }
+#endif
+
+inline unsigned char from_hex(unsigned char ch) {
+  if (ch <= '9' && ch >= '0')
+    ch -= '0';
+  else if (ch <= 'f' && ch >= 'a')
+    ch -= 'a' - 10;
+  else if (ch <= 'F' && ch >= 'A')
+    ch -= 'A' - 10;
+  else
+    ch = 0;
+  return ch;
+}
+
+static const std::string urldecode(const std::string &str) {
+  using namespace std;
+  string result;
+  string::size_type i;
+  for (i = 0; i < str.size(); ++i) {
+    if (str[i] == '+') {
+      result += ' ';
+    } else if (str[i] == '%' && str.size() > i + 2) {
+      const unsigned char ch1 =
+          from_hex(static_cast<unsigned char>(str[i + 1]));
+      const unsigned char ch2 =
+          from_hex(static_cast<unsigned char>(str[i + 2]));
+      const unsigned char ch = static_cast<unsigned char>((ch1 << 4) | ch2);
+      result += static_cast<char>(ch);
+      i += 2;
+    } else {
+      result += str[i];
+    }
+  }
+  return result;
+}
+
+}  // namespace dlib
+// --- dlib end --------------------------------------------------------------
+
+static bool LoadExternalFile(std::vector<unsigned char> *out, std::string *err,
+                             std::string *warn, const std::string &filename,
+                             const std::string &basedir, bool required,
+                             size_t reqBytes, bool checkSize, FsCallbacks *fs) {
+  if (fs == nullptr || fs->FileExists == nullptr ||
+      fs->ExpandFilePath == nullptr || fs->ReadWholeFile == nullptr) {
+    // This is a developer error, assert() ?
+    if (err) {
+      (*err) += "FS callback[s] not set\n";
+    }
+    return false;
+  }
+
+  std::string *failMsgOut = required ? err : warn;
+
+  out->clear();
+
+  std::vector<std::string> paths;
+  paths.push_back(basedir);
+  paths.push_back(".");
+
+  std::string filepath = FindFile(paths, filename, fs);
+  if (filepath.empty() || filename.empty()) {
+    if (failMsgOut) {
+      (*failMsgOut) += "File not found : " + filename + "\n";
+    }
+    return false;
+  }
+
+  std::vector<unsigned char> buf;
+  std::string fileReadErr;
+  bool fileRead =
+      fs->ReadWholeFile(&buf, &fileReadErr, filepath, fs->user_data);
+  if (!fileRead) {
+    if (failMsgOut) {
+      (*failMsgOut) +=
+          "File read error : " + filepath + " : " + fileReadErr + "\n";
+    }
+    return false;
+  }
+
+  size_t sz = buf.size();
+  if (sz == 0) {
+    if (failMsgOut) {
+      (*failMsgOut) += "File is empty : " + filepath + "\n";
+    }
+    return false;
+  }
+
+  if (checkSize) {
+    if (reqBytes == sz) {
+      out->swap(buf);
+      return true;
+    } else {
+      std::stringstream ss;
+      ss << "File size mismatch : " << filepath << ", requestedBytes "
+         << reqBytes << ", but got " << sz << std::endl;
+      if (failMsgOut) {
+        (*failMsgOut) += ss.str();
+      }
+      return false;
+    }
+  }
+
+  out->swap(buf);
+  return true;
+}
+
+void TinyGLTF::SetImageLoader(LoadImageDataFunction func, void *user_data) {
+  LoadImageData = func;
+  load_image_user_data_ = user_data;
+}
+
+#ifndef TINYGLTF_NO_STB_IMAGE
+bool LoadImageData(Image *image, const int image_idx, std::string *err,
+                   std::string *warn, int req_width, int req_height,
+                   const unsigned char *bytes, int size, void *user_data) {
+  (void)user_data;
+  (void)warn;
+
+  int w = 0, h = 0, comp = 0, req_comp = 0;
+
+  unsigned char *data = nullptr;
+
+  // force 32-bit textures for common Vulkan compatibility. It appears that
+  // some GPU drivers do not support 24-bit images for Vulkan
+  req_comp = 4;
+  int bits = 8;
+  int pixel_type = TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE;
+
+  // It is possible that the image we want to load is a 16bit per channel image
+  // We are going to attempt to load it as 16bit per channel, and if it worked,
+  // set the image data accodingly. We are casting the returned pointer into
+  // unsigned char, because we are representing "bytes". But we are updating
+  // the Image metadata to signal that this image uses 2 bytes (16bits) per
+  // channel:
+  if (stbi_is_16_bit_from_memory(bytes, size)) {
+    data = reinterpret_cast<unsigned char *>(
+        stbi_load_16_from_memory(bytes, size, &w, &h, &comp, req_comp));
+    if (data) {
+      bits = 16;
+      pixel_type = TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT;
+    }
+  }
+
+  // at this point, if data is still NULL, it means that the image wasn't
+  // 16bit per channel, we are going to load it as a normal 8bit per channel
+  // mage as we used to do:
+  // if image cannot be decoded, ignore parsing and keep it by its path
+  // don't break in this case
+  // FIXME we should only enter this function if the image is embedded. If
+  // image->uri references
+  // an image file, it should be left as it is. Image loading should not be
+  // mandatory (to support other formats)
+  if (!data) data = stbi_load_from_memory(bytes, size, &w, &h, &comp, req_comp);
+  if (!data) {
+    // NOTE: you can use `warn` instead of `err`
+    if (err) {
+      (*err) +=
+          "Unknown image format. STB cannot decode image data for image[" +
+          std::to_string(image_idx) + "] name = \"" + image->name + "\".\n";
+    }
+    return false;
+  }
+
+  if ((w < 1) || (h < 1)) {
+    stbi_image_free(data);
+    if (err) {
+      (*err) += "Invalid image data for image[" + std::to_string(image_idx) +
+                "] name = \"" + image->name + "\"\n";
+    }
+    return false;
+  }
+
+  if (req_width > 0) {
+    if (req_width != w) {
+      stbi_image_free(data);
+      if (err) {
+        (*err) += "Image width mismatch for image[" +
+                  std::to_string(image_idx) + "] name = \"" + image->name +
+                  "\"\n";
+      }
+      return false;
+    }
+  }
+
+  if (req_height > 0) {
+    if (req_height != h) {
+      stbi_image_free(data);
+      if (err) {
+        (*err) += "Image height mismatch. for image[" +
+                  std::to_string(image_idx) + "] name = \"" + image->name +
+                  "\"\n";
+      }
+      return false;
+    }
+  }
+
+  image->width = w;
+  image->height = h;
+  image->component = req_comp;
+  image->bits = bits;
+  image->pixel_type = pixel_type;
+  image->image.resize(static_cast<size_t>(w * h * req_comp) * size_t(bits / 8));
+  std::copy(data, data + w * h * req_comp * (bits / 8), image->image.begin());
+  stbi_image_free(data);
+
+  return true;
+}
+#endif
+
+void TinyGLTF::SetImageWriter(WriteImageDataFunction func, void *user_data) {
+  WriteImageData = func;
+  write_image_user_data_ = user_data;
+}
+
+#ifndef TINYGLTF_NO_STB_IMAGE_WRITE
+static void WriteToMemory_stbi(void *context, void *data, int size) {
+  std::vector<unsigned char> *buffer =
+      reinterpret_cast<std::vector<unsigned char> *>(context);
+
+  unsigned char *pData = reinterpret_cast<unsigned char *>(data);
+
+  buffer->insert(buffer->end(), pData, pData + size);
+}
+
+bool WriteImageData(const std::string *basepath, const std::string *filename,
+                    Image *image, bool embedImages, void *fsPtr) {
+  const std::string ext = GetFilePathExtension(*filename);
+
+  // Write image to temporary buffer
+  std::string header;
+  std::vector<unsigned char> data;
+
+  if (ext == "png") {
+    if ((image->bits != 8) ||
+        (image->pixel_type != TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE)) {
+      // Unsupported pixel format
+      return false;
+    }
+
+    if (!stbi_write_png_to_func(WriteToMemory_stbi, &data, image->width,
+                                image->height, image->component,
+                                &image->image[0], 0)) {
+      return false;
+    }
+    header = "data:image/png;base64,";
+  } else if (ext == "jpg") {
+    if (!stbi_write_jpg_to_func(WriteToMemory_stbi, &data, image->width,
+                                image->height, image->component,
+                                &image->image[0], 100)) {
+      return false;
+    }
+    header = "data:image/jpeg;base64,";
+  } else if (ext == "bmp") {
+    if (!stbi_write_bmp_to_func(WriteToMemory_stbi, &data, image->width,
+                                image->height, image->component,
+                                &image->image[0])) {
+      return false;
+    }
+    header = "data:image/bmp;base64,";
+  } else if (!embedImages) {
+    // Error: can't output requested format to file
+    return false;
+  }
+
+  if (embedImages) {
+    // Embed base64-encoded image into URI
+    if (data.size()) {
+      image->uri =
+          header +
+          base64_encode(&data[0], static_cast<unsigned int>(data.size()));
+    } else {
+      // Throw error?
+    }
+  } else {
+    // Write image to disc
+    FsCallbacks *fs = reinterpret_cast<FsCallbacks *>(fsPtr);
+    if ((fs != nullptr) && (fs->WriteWholeFile != nullptr)) {
+      const std::string imagefilepath = JoinPath(*basepath, *filename);
+      std::string writeError;
+      if (!fs->WriteWholeFile(&writeError, imagefilepath, data,
+                              fs->user_data)) {
+        // Could not write image file to disc; Throw error ?
+        return false;
+      }
+    } else {
+      // Throw error?
+    }
+    image->uri = *filename;
+  }
+
+  return true;
+}
+#endif
+
+void TinyGLTF::SetFsCallbacks(FsCallbacks callbacks) { fs = callbacks; }
+
+#ifdef _WIN32
+static inline std::wstring UTF8ToWchar(const std::string &str) {
+  int wstr_size =
+      MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0);
+  std::wstring wstr(wstr_size, 0);
+  MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), &wstr[0],
+                      (int)wstr.size());
+  return wstr;
+}
+#endif
+
+#ifndef TINYGLTF_NO_FS
+// Default implementations of filesystem functions
+
+bool FileExists(const std::string &abs_filename, void *) {
+  bool ret;
+#ifdef TINYGLTF_ANDROID_LOAD_FROM_ASSETS
+  if (asset_manager) {
+    AAsset *asset = AAssetManager_open(asset_manager, abs_filename.c_str(),
+                                       AASSET_MODE_STREAMING);
+    if (!asset) {
+      return false;
+    }
+    AAsset_close(asset);
+    ret = true;
+  } else {
+    return false;
+  }
+#else
+#ifdef _WIN32
+#if defined(_MSC_VER) || defined(__GLIBCXX__)
+  FILE *fp = nullptr;
+  errno_t err = _wfopen_s(&fp, UTF8ToWchar(abs_filename).c_str(), L"rb");
+  if (err != 0) {
+    return false;
+  }
+#else
+  FILE *fp = nullptr;
+  errno_t err = fopen_s(&fp, abs_filename.c_str(), "rb");
+  if (err != 0) {
+    return false;
+  }
+#endif
+
+#else
+  FILE *fp = fopen(abs_filename.c_str(), "rb");
+#endif
+  if (fp) {
+    ret = true;
+    fclose(fp);
+  } else {
+    ret = false;
+  }
+#endif
+
+  return ret;
+}
+
+std::string ExpandFilePath(const std::string &filepath, void *) {
+#ifdef _WIN32
+  DWORD len = ExpandEnvironmentStringsA(filepath.c_str(), NULL, 0);
+  char *str = new char[len];
+  ExpandEnvironmentStringsA(filepath.c_str(), str, len);
+
+  std::string s(str);
+
+  delete[] str;
+
+  return s;
+#else
+
+#if defined(TARGET_OS_IPHONE) || defined(TARGET_IPHONE_SIMULATOR) || \
+    defined(__ANDROID__) || defined(__EMSCRIPTEN__)
+  // no expansion
+  std::string s = filepath;
+#else
+  std::string s;
+  wordexp_t p;
+
+  if (filepath.empty()) {
+    return "";
+  }
+
+  // Quote the string to keep any spaces in filepath intact.
+  std::string quoted_path = "\"" + filepath + "\"";
+  // char** w;
+  int ret = wordexp(quoted_path.c_str(), &p, 0);
+  if (ret) {
+    // err
+    s = filepath;
+    return s;
+  }
+
+  // Use first element only.
+  if (p.we_wordv) {
+    s = std::string(p.we_wordv[0]);
+    wordfree(&p);
+  } else {
+    s = filepath;
+  }
+
+#endif
+
+  return s;
+#endif
+}
+
+bool ReadWholeFile(std::vector<unsigned char> *out, std::string *err,
+                   const std::string &filepath, void *) {
+#ifdef TINYGLTF_ANDROID_LOAD_FROM_ASSETS
+  if (asset_manager) {
+    AAsset *asset = AAssetManager_open(asset_manager, filepath.c_str(),
+                                       AASSET_MODE_STREAMING);
+    if (!asset) {
+      if (err) {
+        (*err) += "File open error : " + filepath + "\n";
+      }
+      return false;
+    }
+    size_t size = AAsset_getLength(asset);
+    if (size <= 0) {
+      if (err) {
+        (*err) += "Invalid file size : " + filepath +
+                  " (does the path point to a directory?)";
+      }
+    }
+    out->resize(size);
+    AAsset_read(asset, reinterpret_cast<char *>(&out->at(0)), size);
+    AAsset_close(asset);
+    return true;
+  } else {
+    if (err) {
+      (*err) += "No asset manager specified : " + filepath + "\n";
+    }
+    return false;
+  }
+#else
+#ifdef _WIN32
+#if defined(__GLIBCXX__)  // mingw
+  int file_descriptor =
+      _wopen(UTF8ToWchar(filepath).c_str(), _O_RDONLY | _O_BINARY);
+  __gnu_cxx::stdio_filebuf<char> wfile_buf(file_descriptor, std::ios_base::in);
+  std::istream f(&wfile_buf);
+#elif defined(_MSC_VER)
+  std::ifstream f(UTF8ToWchar(filepath).c_str(), std::ifstream::binary);
+#else  // clang?
+  std::ifstream f(filepath.c_str(), std::ifstream::binary);
+#endif
+#else
+  std::ifstream f(filepath.c_str(), std::ifstream::binary);
+#endif
+  if (!f) {
+    if (err) {
+      (*err) += "File open error : " + filepath + "\n";
+    }
+    return false;
+  }
+
+  f.seekg(0, f.end);
+  size_t sz = static_cast<size_t>(f.tellg());
+  f.seekg(0, f.beg);
+
+  if (int64_t(sz) < 0) {
+    if (err) {
+      (*err) += "Invalid file size : " + filepath +
+                " (does the path point to a directory?)";
+    }
+    return false;
+  } else if (sz == 0) {
+    if (err) {
+      (*err) += "File is empty : " + filepath + "\n";
+    }
+    return false;
+  }
+
+  out->resize(sz);
+  f.read(reinterpret_cast<char *>(&out->at(0)),
+         static_cast<std::streamsize>(sz));
+
+  return true;
+#endif
+}
+
+bool WriteWholeFile(std::string *err, const std::string &filepath,
+                    const std::vector<unsigned char> &contents, void *) {
+#ifdef _WIN32
+#if defined(__GLIBCXX__)  // mingw
+  int file_descriptor =
+      _wopen(UTF8ToWchar(filepath).c_str(), _O_CREAT | _O_WRONLY |
+                                            _O_TRUNC | _O_BINARY);
+  __gnu_cxx::stdio_filebuf<char> wfile_buf(file_descriptor,
+                                           std::ios_base::out |
+                                           std::ios_base::binary);
+  std::ostream f(&wfile_buf);
+#elif defined(_MSC_VER)
+  std::ofstream f(UTF8ToWchar(filepath).c_str(), std::ofstream::binary);
+#else  // clang?
+  std::ofstream f(filepath.c_str(), std::ofstream::binary);
+#endif
+#else
+  std::ofstream f(filepath.c_str(), std::ofstream::binary);
+#endif
+  if (!f) {
+    if (err) {
+      (*err) += "File open error for writing : " + filepath + "\n";
+    }
+    return false;
+  }
+
+  f.write(reinterpret_cast<const char *>(&contents.at(0)),
+          static_cast<std::streamsize>(contents.size()));
+  if (!f) {
+    if (err) {
+      (*err) += "File write error: " + filepath + "\n";
+    }
+    return false;
+  }
+
+  return true;
+}
+
+#endif  // TINYGLTF_NO_FS
+
+static std::string MimeToExt(const std::string &mimeType) {
+  if (mimeType == "image/jpeg") {
+    return "jpg";
+  } else if (mimeType == "image/png") {
+    return "png";
+  } else if (mimeType == "image/bmp") {
+    return "bmp";
+  } else if (mimeType == "image/gif") {
+    return "gif";
+  }
+
+  return "";
+}
+
+static void UpdateImageObject(Image &image, std::string &baseDir, int index,
+                              bool embedImages,
+                              WriteImageDataFunction *WriteImageData = nullptr,
+                              void *user_data = nullptr) {
+  std::string filename;
+  std::string ext;
+  // If image has uri, use it it as a filename
+  if (image.uri.size()) {
+    filename = GetBaseFilename(image.uri);
+    ext = GetFilePathExtension(filename);
+  }
+  else if (image.bufferView != -1) {
+	  //If there's no URI and the data exists in a buffer,
+	  //don't change properties or write images
+  } else if (image.name.size()) {
+    ext = MimeToExt(image.mimeType);
+    // Otherwise use name as filename
+    filename = image.name + "." + ext;
+  } else {
+    ext = MimeToExt(image.mimeType);
+    // Fallback to index of image as filename
+    filename = std::to_string(index) + "." + ext;
+  }
+
+  // If callback is set, modify image data object
+  if (*WriteImageData != nullptr && !filename.empty()) {
+    std::string uri;
+    (*WriteImageData)(&baseDir, &filename, &image, embedImages, user_data);
+  }
+}
+
+bool IsDataURI(const std::string &in) {
+  std::string header = "data:application/octet-stream;base64,";
+  if (in.find(header) == 0) {
+    return true;
+  }
+
+  header = "data:image/jpeg;base64,";
+  if (in.find(header) == 0) {
+    return true;
+  }
+
+  header = "data:image/png;base64,";
+  if (in.find(header) == 0) {
+    return true;
+  }
+
+  header = "data:image/bmp;base64,";
+  if (in.find(header) == 0) {
+    return true;
+  }
+
+  header = "data:image/gif;base64,";
+  if (in.find(header) == 0) {
+    return true;
+  }
+
+  header = "data:text/plain;base64,";
+  if (in.find(header) == 0) {
+    return true;
+  }
+
+  header = "data:application/gltf-buffer;base64,";
+  if (in.find(header) == 0) {
+    return true;
+  }
+
+  return false;
+}
+
+bool DecodeDataURI(std::vector<unsigned char> *out, std::string &mime_type,
+                   const std::string &in, size_t reqBytes, bool checkSize) {
+  std::string header = "data:application/octet-stream;base64,";
+  std::string data;
+  if (in.find(header) == 0) {
+    data = base64_decode(in.substr(header.size()));  // cut mime string.
+  }
+
+  if (data.empty()) {
+    header = "data:image/jpeg;base64,";
+    if (in.find(header) == 0) {
+      mime_type = "image/jpeg";
+      data = base64_decode(in.substr(header.size()));  // cut mime string.
+    }
+  }
+
+  if (data.empty()) {
+    header = "data:image/png;base64,";
+    if (in.find(header) == 0) {
+      mime_type = "image/png";
+      data = base64_decode(in.substr(header.size()));  // cut mime string.
+    }
+  }
+
+  if (data.empty()) {
+    header = "data:image/bmp;base64,";
+    if (in.find(header) == 0) {
+      mime_type = "image/bmp";
+      data = base64_decode(in.substr(header.size()));  // cut mime string.
+    }
+  }
+
+  if (data.empty()) {
+    header = "data:image/gif;base64,";
+    if (in.find(header) == 0) {
+      mime_type = "image/gif";
+      data = base64_decode(in.substr(header.size()));  // cut mime string.
+    }
+  }
+
+  if (data.empty()) {
+    header = "data:text/plain;base64,";
+    if (in.find(header) == 0) {
+      mime_type = "text/plain";
+      data = base64_decode(in.substr(header.size()));
+    }
+  }
+
+  if (data.empty()) {
+    header = "data:application/gltf-buffer;base64,";
+    if (in.find(header) == 0) {
+      data = base64_decode(in.substr(header.size()));
+    }
+  }
+
+  // TODO(syoyo): Allow empty buffer? #229
+  if (data.empty()) {
+    return false;
+  }
+
+  if (checkSize) {
+    if (data.size() != reqBytes) {
+      return false;
+    }
+    out->resize(reqBytes);
+  } else {
+    out->resize(data.size());
+  }
+  std::copy(data.begin(), data.end(), out->begin());
+  return true;
+}
+
+namespace {
+bool GetInt(const json &o, int &val) {
+#ifdef TINYGLTF_USE_RAPIDJSON
+  if (!o.IsDouble()) {
+    if (o.IsInt()) {
+      val = o.GetInt();
+      return true;
+    } else if (o.IsUint()) {
+      val = static_cast<int>(o.GetUint());
+      return true;
+    } else if (o.IsInt64()) {
+      val = static_cast<int>(o.GetInt64());
+      return true;
+    } else if (o.IsUint64()) {
+      val = static_cast<int>(o.GetUint64());
+      return true;
+    }
+  }
+
+  return false;
+#else
+  auto type = o.type();
+
+  if ((type == json::value_t::number_integer) ||
+      (type == json::value_t::number_unsigned)) {
+    val = static_cast<int>(o.get<int64_t>());
+    return true;
+  }
+
+  return false;
+#endif
+}
+
+#ifdef TINYGLTF_USE_RAPIDJSON
+bool GetDouble(const json &o, double &val) {
+  if (o.IsDouble()) {
+    val = o.GetDouble();
+    return true;
+  }
+
+  return false;
+}
+#endif
+
+bool GetNumber(const json &o, double &val) {
+#ifdef TINYGLTF_USE_RAPIDJSON
+  if (o.IsNumber()) {
+    val = o.GetDouble();
+    return true;
+  }
+
+  return false;
+#else
+  if (o.is_number()) {
+    val = o.get<double>();
+    return true;
+  }
+
+  return false;
+#endif
+}
+
+bool GetString(const json &o, std::string &val) {
+#ifdef TINYGLTF_USE_RAPIDJSON
+  if (o.IsString()) {
+    val = o.GetString();
+    return true;
+  }
+
+  return false;
+#else
+  if (o.type() == json::value_t::string) {
+    val = o.get<std::string>();
+    return true;
+  }
+
+  return false;
+#endif
+}
+
+bool IsArray(const json &o) {
+#ifdef TINYGLTF_USE_RAPIDJSON
+  return o.IsArray();
+#else
+  return o.is_array();
+#endif
+}
+
+json_const_array_iterator ArrayBegin(const json &o) {
+#ifdef TINYGLTF_USE_RAPIDJSON
+  return o.Begin();
+#else
+  return o.begin();
+#endif
+}
+
+json_const_array_iterator ArrayEnd(const json &o) {
+#ifdef TINYGLTF_USE_RAPIDJSON
+  return o.End();
+#else
+  return o.end();
+#endif
+}
+
+bool IsObject(const json &o) {
+#ifdef TINYGLTF_USE_RAPIDJSON
+  return o.IsObject();
+#else
+  return o.is_object();
+#endif
+}
+
+json_const_iterator ObjectBegin(const json &o) {
+#ifdef TINYGLTF_USE_RAPIDJSON
+  return o.MemberBegin();
+#else
+  return o.begin();
+#endif
+}
+
+json_const_iterator ObjectEnd(const json &o) {
+#ifdef TINYGLTF_USE_RAPIDJSON
+  return o.MemberEnd();
+#else
+  return o.end();
+#endif
+}
+
+const char *GetKey(json_const_iterator &it) {
+#ifdef TINYGLTF_USE_RAPIDJSON
+  return it->name.GetString();
+#else
+  return it.key().c_str();
+#endif
+}
+
+bool FindMember(const json &o, const char *member, json_const_iterator &it) {
+#ifdef TINYGLTF_USE_RAPIDJSON
+  if (!o.IsObject()) {
+    return false;
+  }
+  it = o.FindMember(member);
+  return it != o.MemberEnd();
+#else
+  it = o.find(member);
+  return it != o.end();
+#endif
+}
+
+const json &GetValue(json_const_iterator &it) {
+#ifdef TINYGLTF_USE_RAPIDJSON
+  return it->value;
+#else
+  return it.value();
+#endif
+}
+
+std::string JsonToString(const json &o, int spacing = -1) {
+#ifdef TINYGLTF_USE_RAPIDJSON
+  using namespace rapidjson;
+  StringBuffer buffer;
+  if (spacing == -1) {
+    Writer<StringBuffer> writer(buffer);
+    o.Accept(writer);
+  } else {
+    PrettyWriter<StringBuffer> writer(buffer);
+    writer.SetIndent(' ', uint32_t(spacing));
+    o.Accept(writer);
+  }
+  return buffer.GetString();
+#else
+  return o.dump(spacing);
+#endif
+}
+
+}  // namespace
+
+static bool ParseJsonAsValue(Value *ret, const json &o) {
+  Value val{};
+#ifdef TINYGLTF_USE_RAPIDJSON
+  using rapidjson::Type;
+  switch (o.GetType()) {
+    case Type::kObjectType: {
+      Value::Object value_object;
+      for (auto it = o.MemberBegin(); it != o.MemberEnd(); ++it) {
+        Value entry;
+        ParseJsonAsValue(&entry, it->value);
+        if (entry.Type() != NULL_TYPE)
+          value_object.emplace(GetKey(it), std::move(entry));
+      }
+      if (value_object.size() > 0) val = Value(std::move(value_object));
+    } break;
+    case Type::kArrayType: {
+      Value::Array value_array;
+      value_array.reserve(o.Size());
+      for (auto it = o.Begin(); it != o.End(); ++it) {
+        Value entry;
+        ParseJsonAsValue(&entry, *it);
+        if (entry.Type() != NULL_TYPE)
+          value_array.emplace_back(std::move(entry));
+      }
+      if (value_array.size() > 0) val = Value(std::move(value_array));
+    } break;
+    case Type::kStringType:
+      val = Value(std::string(o.GetString()));
+      break;
+    case Type::kFalseType:
+    case Type::kTrueType:
+      val = Value(o.GetBool());
+      break;
+    case Type::kNumberType:
+      if (!o.IsDouble()) {
+        int i = 0;
+        GetInt(o, i);
+        val = Value(i);
+      } else {
+        double d = 0.0;
+        GetDouble(o, d);
+        val = Value(d);
+      }
+      break;
+    case Type::kNullType:
+      break;
+      // all types are covered, so no `case default`
+  }
+#else
+  switch (o.type()) {
+    case json::value_t::object: {
+      Value::Object value_object;
+      for (auto it = o.begin(); it != o.end(); it++) {
+        Value entry;
+        ParseJsonAsValue(&entry, it.value());
+        if (entry.Type() != NULL_TYPE)
+          value_object.emplace(it.key(), std::move(entry));
+      }
+      if (value_object.size() > 0) val = Value(std::move(value_object));
+    } break;
+    case json::value_t::array: {
+      Value::Array value_array;
+      value_array.reserve(o.size());
+      for (auto it = o.begin(); it != o.end(); it++) {
+        Value entry;
+        ParseJsonAsValue(&entry, it.value());
+        if (entry.Type() != NULL_TYPE)
+          value_array.emplace_back(std::move(entry));
+      }
+      if (value_array.size() > 0) val = Value(std::move(value_array));
+    } break;
+    case json::value_t::string:
+      val = Value(o.get<std::string>());
+      break;
+    case json::value_t::boolean:
+      val = Value(o.get<bool>());
+      break;
+    case json::value_t::number_integer:
+    case json::value_t::number_unsigned:
+      val = Value(static_cast<int>(o.get<int64_t>()));
+      break;
+    case json::value_t::number_float:
+      val = Value(o.get<double>());
+      break;
+    case json::value_t::null:
+    case json::value_t::discarded:
+      // default:
+      break;
+  }
+#endif
+  if (ret) *ret = std::move(val);
+
+  return val.Type() != NULL_TYPE;
+}
+
+static bool ParseExtrasProperty(Value *ret, const json &o) {
+  json_const_iterator it;
+  if (!FindMember(o, "extras", it)) {
+    return false;
+  }
+
+  return ParseJsonAsValue(ret, GetValue(it));
+}
+
+static bool ParseBooleanProperty(bool *ret, std::string *err, const json &o,
+                                 const std::string &property,
+                                 const bool required,
+                                 const std::string &parent_node = "") {
+  json_const_iterator it;
+  if (!FindMember(o, property.c_str(), it)) {
+    if (required) {
+      if (err) {
+        (*err) += "'" + property + "' property is missing";
+        if (!parent_node.empty()) {
+          (*err) += " in " + parent_node;
+        }
+        (*err) += ".\n";
+      }
+    }
+    return false;
+  }
+
+  auto &value = GetValue(it);
+
+  bool isBoolean;
+  bool boolValue = false;
+#ifdef TINYGLTF_USE_RAPIDJSON
+  isBoolean = value.IsBool();
+  if (isBoolean) {
+    boolValue = value.GetBool();
+  }
+#else
+  isBoolean = value.is_boolean();
+  if (isBoolean) {
+    boolValue = value.get<bool>();
+  }
+#endif
+  if (!isBoolean) {
+    if (required) {
+      if (err) {
+        (*err) += "'" + property + "' property is not a bool type.\n";
+      }
+    }
+    return false;
+  }
+
+  if (ret) {
+    (*ret) = boolValue;
+  }
+
+  return true;
+}
+
+static bool ParseIntegerProperty(int *ret, std::string *err, const json &o,
+                                 const std::string &property,
+                                 const bool required,
+                                 const std::string &parent_node = "") {
+  json_const_iterator it;
+  if (!FindMember(o, property.c_str(), it)) {
+    if (required) {
+      if (err) {
+        (*err) += "'" + property + "' property is missing";
+        if (!parent_node.empty()) {
+          (*err) += " in " + parent_node;
+        }
+        (*err) += ".\n";
+      }
+    }
+    return false;
+  }
+
+  int intValue;
+  bool isInt = GetInt(GetValue(it), intValue);
+  if (!isInt) {
+    if (required) {
+      if (err) {
+        (*err) += "'" + property + "' property is not an integer type.\n";
+      }
+    }
+    return false;
+  }
+
+  if (ret) {
+    (*ret) = intValue;
+  }
+
+  return true;
+}
+
+static bool ParseUnsignedProperty(size_t *ret, std::string *err, const json &o,
+                                  const std::string &property,
+                                  const bool required,
+                                  const std::string &parent_node = "") {
+  json_const_iterator it;
+  if (!FindMember(o, property.c_str(), it)) {
+    if (required) {
+      if (err) {
+        (*err) += "'" + property + "' property is missing";
+        if (!parent_node.empty()) {
+          (*err) += " in " + parent_node;
+        }
+        (*err) += ".\n";
+      }
+    }
+    return false;
+  }
+
+  auto &value = GetValue(it);
+
+  size_t uValue = 0;
+  bool isUValue;
+#ifdef TINYGLTF_USE_RAPIDJSON
+  isUValue = false;
+  if (value.IsUint()) {
+    uValue = value.GetUint();
+    isUValue = true;
+  } else if (value.IsUint64()) {
+    uValue = value.GetUint64();
+    isUValue = true;
+  }
+#else
+  isUValue = value.is_number_unsigned();
+  if (isUValue) {
+    uValue = value.get<size_t>();
+  }
+#endif
+  if (!isUValue) {
+    if (required) {
+      if (err) {
+        (*err) += "'" + property + "' property is not a positive integer.\n";
+      }
+    }
+    return false;
+  }
+
+  if (ret) {
+    (*ret) = uValue;
+  }
+
+  return true;
+}
+
+static bool ParseNumberProperty(double *ret, std::string *err, const json &o,
+                                const std::string &property,
+                                const bool required,
+                                const std::string &parent_node = "") {
+  json_const_iterator it;
+
+  if (!FindMember(o, property.c_str(), it)) {
+    if (required) {
+      if (err) {
+        (*err) += "'" + property + "' property is missing";
+        if (!parent_node.empty()) {
+          (*err) += " in " + parent_node;
+        }
+        (*err) += ".\n";
+      }
+    }
+    return false;
+  }
+
+  double numberValue;
+  bool isNumber = GetNumber(GetValue(it), numberValue);
+
+  if (!isNumber) {
+    if (required) {
+      if (err) {
+        (*err) += "'" + property + "' property is not a number type.\n";
+      }
+    }
+    return false;
+  }
+
+  if (ret) {
+    (*ret) = numberValue;
+  }
+
+  return true;
+}
+
+static bool ParseNumberArrayProperty(std::vector<double> *ret, std::string *err,
+                                     const json &o, const std::string &property,
+                                     bool required,
+                                     const std::string &parent_node = "") {
+  json_const_iterator it;
+  if (!FindMember(o, property.c_str(), it)) {
+    if (required) {
+      if (err) {
+        (*err) += "'" + property + "' property is missing";
+        if (!parent_node.empty()) {
+          (*err) += " in " + parent_node;
+        }
+        (*err) += ".\n";
+      }
+    }
+    return false;
+  }
+
+  if (!IsArray(GetValue(it))) {
+    if (required) {
+      if (err) {
+        (*err) += "'" + property + "' property is not an array";
+        if (!parent_node.empty()) {
+          (*err) += " in " + parent_node;
+        }
+        (*err) += ".\n";
+      }
+    }
+    return false;
+  }
+
+  ret->clear();
+  auto end = ArrayEnd(GetValue(it));
+  for (auto i = ArrayBegin(GetValue(it)); i != end; ++i) {
+    double numberValue;
+    const bool isNumber = GetNumber(*i, numberValue);
+    if (!isNumber) {
+      if (required) {
+        if (err) {
+          (*err) += "'" + property + "' property is not a number.\n";
+          if (!parent_node.empty()) {
+            (*err) += " in " + parent_node;
+          }
+          (*err) += ".\n";
+        }
+      }
+      return false;
+    }
+    ret->push_back(numberValue);
+  }
+
+  return true;
+}
+
+static bool ParseIntegerArrayProperty(std::vector<int> *ret, std::string *err,
+                                      const json &o,
+                                      const std::string &property,
+                                      bool required,
+                                      const std::string &parent_node = "") {
+  json_const_iterator it;
+  if (!FindMember(o, property.c_str(), it)) {
+    if (required) {
+      if (err) {
+        (*err) += "'" + property + "' property is missing";
+        if (!parent_node.empty()) {
+          (*err) += " in " + parent_node;
+        }
+        (*err) += ".\n";
+      }
+    }
+    return false;
+  }
+
+  if (!IsArray(GetValue(it))) {
+    if (required) {
+      if (err) {
+        (*err) += "'" + property + "' property is not an array";
+        if (!parent_node.empty()) {
+          (*err) += " in " + parent_node;
+        }
+        (*err) += ".\n";
+      }
+    }
+    return false;
+  }
+
+  ret->clear();
+  auto end = ArrayEnd(GetValue(it));
+  for (auto i = ArrayBegin(GetValue(it)); i != end; ++i) {
+    int numberValue;
+    bool isNumber = GetInt(*i, numberValue);
+    if (!isNumber) {
+      if (required) {
+        if (err) {
+          (*err) += "'" + property + "' property is not an integer type.\n";
+          if (!parent_node.empty()) {
+            (*err) += " in " + parent_node;
+          }
+          (*err) += ".\n";
+        }
+      }
+      return false;
+    }
+    ret->push_back(numberValue);
+  }
+
+  return true;
+}
+
+static bool ParseStringProperty(
+    std::string *ret, std::string *err, const json &o,
+    const std::string &property, bool required,
+    const std::string &parent_node = std::string()) {
+  json_const_iterator it;
+  if (!FindMember(o, property.c_str(), it)) {
+    if (required) {
+      if (err) {
+        (*err) += "'" + property + "' property is missing";
+        if (parent_node.empty()) {
+          (*err) += ".\n";
+        } else {
+          (*err) += " in `" + parent_node + "'.\n";
+        }
+      }
+    }
+    return false;
+  }
+
+  std::string strValue;
+  if (!GetString(GetValue(it), strValue)) {
+    if (required) {
+      if (err) {
+        (*err) += "'" + property + "' property is not a string type.\n";
+      }
+    }
+    return false;
+  }
+
+  if (ret) {
+    (*ret) = std::move(strValue);
+  }
+
+  return true;
+}
+
+static bool ParseStringIntegerProperty(std::map<std::string, int> *ret,
+                                       std::string *err, const json &o,
+                                       const std::string &property,
+                                       bool required,
+                                       const std::string &parent = "") {
+  json_const_iterator it;
+  if (!FindMember(o, property.c_str(), it)) {
+    if (required) {
+      if (err) {
+        if (!parent.empty()) {
+          (*err) +=
+              "'" + property + "' property is missing in " + parent + ".\n";
+        } else {
+          (*err) += "'" + property + "' property is missing.\n";
+        }
+      }
+    }
+    return false;
+  }
+
+  const json &dict = GetValue(it);
+
+  // Make sure we are dealing with an object / dictionary.
+  if (!IsObject(dict)) {
+    if (required) {
+      if (err) {
+        (*err) += "'" + property + "' property is not an object.\n";
+      }
+    }
+    return false;
+  }
+
+  ret->clear();
+
+  json_const_iterator dictIt(ObjectBegin(dict));
+  json_const_iterator dictItEnd(ObjectEnd(dict));
+
+  for (; dictIt != dictItEnd; ++dictIt) {
+    int intVal;
+    if (!GetInt(GetValue(dictIt), intVal)) {
+      if (required) {
+        if (err) {
+          (*err) += "'" + property + "' value is not an integer type.\n";
+        }
+      }
+      return false;
+    }
+
+    // Insert into the list.
+    (*ret)[GetKey(dictIt)] = intVal;
+  }
+  return true;
+}
+
+static bool ParseJSONProperty(std::map<std::string, double> *ret,
+                              std::string *err, const json &o,
+                              const std::string &property, bool required) {
+  json_const_iterator it;
+  if (!FindMember(o, property.c_str(), it)) {
+    if (required) {
+      if (err) {
+        (*err) += "'" + property + "' property is missing. \n'";
+      }
+    }
+    return false;
+  }
+
+  const json &obj = GetValue(it);
+
+  if (!IsObject(obj)) {
+    if (required) {
+      if (err) {
+        (*err) += "'" + property + "' property is not a JSON object.\n";
+      }
+    }
+    return false;
+  }
+
+  ret->clear();
+
+  json_const_iterator it2(ObjectBegin(obj));
+  json_const_iterator itEnd(ObjectEnd(obj));
+  for (; it2 != itEnd; ++it2) {
+    double numVal;
+    if (GetNumber(GetValue(it2), numVal))
+      ret->emplace(std::string(GetKey(it2)), numVal);
+  }
+
+  return true;
+}
+
+static bool ParseParameterProperty(Parameter *param, std::string *err,
+                                   const json &o, const std::string &prop,
+                                   bool required) {
+  // A parameter value can either be a string or an array of either a boolean or
+  // a number. Booleans of any kind aren't supported here. Granted, it
+  // complicates the Parameter structure and breaks it semantically in the sense
+  // that the client probably works off the assumption that if the string is
+  // empty the vector is used, etc. Would a tagged union work?
+  if (ParseStringProperty(&param->string_value, err, o, prop, false)) {
+    // Found string property.
+    return true;
+  } else if (ParseNumberArrayProperty(&param->number_array, err, o, prop,
+                                      false)) {
+    // Found a number array.
+    return true;
+  } else if (ParseNumberProperty(&param->number_value, err, o, prop, false)) {
+    return param->has_number_value = true;
+  } else if (ParseJSONProperty(&param->json_double_value, err, o, prop,
+                               false)) {
+    return true;
+  } else if (ParseBooleanProperty(&param->bool_value, err, o, prop, false)) {
+    return true;
+  } else {
+    if (required) {
+      if (err) {
+        (*err) += "parameter must be a string or number / number array.\n";
+      }
+    }
+    return false;
+  }
+}
+
+static bool ParseExtensionsProperty(ExtensionMap *ret, std::string *err,
+                                    const json &o) {
+  (void)err;
+
+  json_const_iterator it;
+  if (!FindMember(o, "extensions", it)) {
+    return false;
+  }
+
+  auto &obj = GetValue(it);
+  if (!IsObject(obj)) {
+    return false;
+  }
+  ExtensionMap extensions;
+  json_const_iterator extIt = ObjectBegin(obj);  // it.value().begin();
+  json_const_iterator extEnd = ObjectEnd(obj);
+  for (; extIt != extEnd; ++extIt) {
+    auto &itObj = GetValue(extIt);
+    if (!IsObject(itObj)) continue;
+    std::string key(GetKey(extIt));
+    if (!ParseJsonAsValue(&extensions[key], itObj)) {
+      if (!key.empty()) {
+        // create empty object so that an extension object is still of type
+        // object
+        extensions[key] = Value{Value::Object{}};
+      }
+    }
+  }
+  if (ret) {
+    (*ret) = std::move(extensions);
+  }
+  return true;
+}
+
+static bool ParseAsset(Asset *asset, std::string *err, const json &o,
+                       bool store_original_json_for_extras_and_extensions) {
+  ParseStringProperty(&asset->version, err, o, "version", true, "Asset");
+  ParseStringProperty(&asset->generator, err, o, "generator", false, "Asset");
+  ParseStringProperty(&asset->minVersion, err, o, "minVersion", false, "Asset");
+  ParseStringProperty(&asset->copyright, err, o, "copyright", false, "Asset");
+
+  ParseExtensionsProperty(&asset->extensions, err, o);
+
+  // Unity exporter version is added as extra here
+  ParseExtrasProperty(&(asset->extras), o);
+
+  if (store_original_json_for_extras_and_extensions) {
+    {
+      json_const_iterator it;
+      if (FindMember(o, "extensions", it)) {
+        asset->extensions_json_string = JsonToString(GetValue(it));
+      }
+    }
+    {
+      json_const_iterator it;
+      if (FindMember(o, "extras", it)) {
+        asset->extras_json_string = JsonToString(GetValue(it));
+      }
+    }
+  }
+
+  return true;
+}
+
+static bool ParseImage(Image *image, const int image_idx, std::string *err,
+                       std::string *warn, const json &o,
+                       bool store_original_json_for_extras_and_extensions,
+                       const std::string &basedir, FsCallbacks *fs,
+                       LoadImageDataFunction *LoadImageData = nullptr,
+                       void *load_image_user_data = nullptr) {
+  // A glTF image must either reference a bufferView or an image uri
+
+  // schema says oneOf [`bufferView`, `uri`]
+  // TODO(syoyo): Check the type of each parameters.
+  json_const_iterator it;
+  bool hasBufferView = FindMember(o, "bufferView", it);
+  bool hasURI = FindMember(o, "uri", it);
+
+  ParseStringProperty(&image->name, err, o, "name", false);
+
+  if (hasBufferView && hasURI) {
+    // Should not both defined.
+    if (err) {
+      (*err) +=
+          "Only one of `bufferView` or `uri` should be defined, but both are "
+          "defined for image[" +
+          std::to_string(image_idx) + "] name = \"" + image->name + "\"\n";
+    }
+    return false;
+  }
+
+  if (!hasBufferView && !hasURI) {
+    if (err) {
+      (*err) += "Neither required `bufferView` nor `uri` defined for image[" +
+                std::to_string(image_idx) + "] name = \"" + image->name +
+                "\"\n";
+    }
+    return false;
+  }
+
+  ParseExtensionsProperty(&image->extensions, err, o);
+  ParseExtrasProperty(&image->extras, o);
+
+  if (store_original_json_for_extras_and_extensions) {
+    {
+      json_const_iterator eit;
+      if (FindMember(o, "extensions", eit)) {
+        image->extensions_json_string = JsonToString(GetValue(eit));
+      }
+    }
+    {
+      json_const_iterator eit;
+      if (FindMember(o, "extras", eit)) {
+        image->extras_json_string = JsonToString(GetValue(eit));
+      }
+    }
+  }
+
+  if (hasBufferView) {
+    int bufferView = -1;
+    if (!ParseIntegerProperty(&bufferView, err, o, "bufferView", true)) {
+      if (err) {
+        (*err) += "Failed to parse `bufferView` for image[" +
+                  std::to_string(image_idx) + "] name = \"" + image->name +
+                  "\"\n";
+      }
+      return false;
+    }
+
+    std::string mime_type;
+    ParseStringProperty(&mime_type, err, o, "mimeType", false);
+
+    int width = 0;
+    ParseIntegerProperty(&width, err, o, "width", false);
+
+    int height = 0;
+    ParseIntegerProperty(&height, err, o, "height", false);
+
+    // Just only save some information here. Loading actual image data from
+    // bufferView is done after this `ParseImage` function.
+    image->bufferView = bufferView;
+    image->mimeType = mime_type;
+    image->width = width;
+    image->height = height;
+
+    return true;
+  }
+
+  // Parse URI & Load image data.
+
+  std::string uri;
+  std::string tmp_err;
+  if (!ParseStringProperty(&uri, &tmp_err, o, "uri", true)) {
+    if (err) {
+      (*err) += "Failed to parse `uri` for image[" + std::to_string(image_idx) +
+                "] name = \"" + image->name + "\".\n";
+    }
+    return false;
+  }
+
+  std::vector<unsigned char> img;
+
+  if (IsDataURI(uri)) {
+    if (!DecodeDataURI(&img, image->mimeType, uri, 0, false)) {
+      if (err) {
+        (*err) += "Failed to decode 'uri' for image[" +
+                  std::to_string(image_idx) + "] name = [" + image->name +
+                  "]\n";
+      }
+      return false;
+    }
+  } else {
+    // Assume external file
+    // Keep texture path (for textures that cannot be decoded)
+    image->uri = uri;
+#ifdef TINYGLTF_NO_EXTERNAL_IMAGE
+    return true;
+#endif
+    std::string decoded_uri = dlib::urldecode(uri);
+    if (!LoadExternalFile(&img, err, warn, decoded_uri, basedir,
+                          /* required */ false, /* required bytes */ 0,
+                          /* checksize */ false, fs)) {
+      if (warn) {
+        (*warn) += "Failed to load external 'uri' for image[" +
+                   std::to_string(image_idx) + "] name = [" + image->name +
+                   "]\n";
+      }
+      // If the image cannot be loaded, keep uri as image->uri.
+      return true;
+    }
+
+    if (img.empty()) {
+      if (warn) {
+        (*warn) += "Image data is empty for image[" +
+                   std::to_string(image_idx) + "] name = [" + image->name +
+                   "] \n";
+      }
+      return false;
+    }
+  }
+
+  if (*LoadImageData == nullptr) {
+    if (err) {
+      (*err) += "No LoadImageData callback specified.\n";
+    }
+    return false;
+  }
+  return (*LoadImageData)(image, image_idx, err, warn, 0, 0, &img.at(0),
+                          static_cast<int>(img.size()), load_image_user_data);
+}
+
+static bool ParseTexture(Texture *texture, std::string *err, const json &o,
+                         bool store_original_json_for_extras_and_extensions,
+                         const std::string &basedir) {
+  (void)basedir;
+  int sampler = -1;
+  int source = -1;
+  ParseIntegerProperty(&sampler, err, o, "sampler", false);
+
+  ParseIntegerProperty(&source, err, o, "source", false);
+
+  texture->sampler = sampler;
+  texture->source = source;
+
+  ParseExtensionsProperty(&texture->extensions, err, o);
+  ParseExtrasProperty(&texture->extras, o);
+
+  if (store_original_json_for_extras_and_extensions) {
+    {
+      json_const_iterator it;
+      if (FindMember(o, "extensions", it)) {
+        texture->extensions_json_string = JsonToString(GetValue(it));
+      }
+    }
+    {
+      json_const_iterator it;
+      if (FindMember(o, "extras", it)) {
+        texture->extras_json_string = JsonToString(GetValue(it));
+      }
+    }
+  }
+
+  ParseStringProperty(&texture->name, err, o, "name", false);
+
+  return true;
+}
+
+static bool ParseTextureInfo(
+    TextureInfo *texinfo, std::string *err, const json &o,
+    bool store_original_json_for_extras_and_extensions) {
+  if (texinfo == nullptr) {
+    return false;
+  }
+
+  if (!ParseIntegerProperty(&texinfo->index, err, o, "index",
+                            /* required */ true, "TextureInfo")) {
+    return false;
+  }
+
+  ParseIntegerProperty(&texinfo->texCoord, err, o, "texCoord", false);
+
+  ParseExtensionsProperty(&texinfo->extensions, err, o);
+  ParseExtrasProperty(&texinfo->extras, o);
+
+  if (store_original_json_for_extras_and_extensions) {
+    {
+      json_const_iterator it;
+      if (FindMember(o, "extensions", it)) {
+        texinfo->extensions_json_string = JsonToString(GetValue(it));
+      }
+    }
+    {
+      json_const_iterator it;
+      if (FindMember(o, "extras", it)) {
+        texinfo->extras_json_string = JsonToString(GetValue(it));
+      }
+    }
+  }
+
+  return true;
+}
+
+static bool ParseNormalTextureInfo(
+    NormalTextureInfo *texinfo, std::string *err, const json &o,
+    bool store_original_json_for_extras_and_extensions) {
+  if (texinfo == nullptr) {
+    return false;
+  }
+
+  if (!ParseIntegerProperty(&texinfo->index, err, o, "index",
+                            /* required */ true, "NormalTextureInfo")) {
+    return false;
+  }
+
+  ParseIntegerProperty(&texinfo->texCoord, err, o, "texCoord", false);
+  ParseNumberProperty(&texinfo->scale, err, o, "scale", false);
+
+  ParseExtensionsProperty(&texinfo->extensions, err, o);
+  ParseExtrasProperty(&texinfo->extras, o);
+
+  if (store_original_json_for_extras_and_extensions) {
+    {
+      json_const_iterator it;
+      if (FindMember(o, "extensions", it)) {
+        texinfo->extensions_json_string = JsonToString(GetValue(it));
+      }
+    }
+    {
+      json_const_iterator it;
+      if (FindMember(o, "extras", it)) {
+        texinfo->extras_json_string = JsonToString(GetValue(it));
+      }
+    }
+  }
+
+  return true;
+}
+
+static bool ParseOcclusionTextureInfo(
+    OcclusionTextureInfo *texinfo, std::string *err, const json &o,
+    bool store_original_json_for_extras_and_extensions) {
+  if (texinfo == nullptr) {
+    return false;
+  }
+
+  if (!ParseIntegerProperty(&texinfo->index, err, o, "index",
+                            /* required */ true, "NormalTextureInfo")) {
+    return false;
+  }
+
+  ParseIntegerProperty(&texinfo->texCoord, err, o, "texCoord", false);
+  ParseNumberProperty(&texinfo->strength, err, o, "strength", false);
+
+  ParseExtensionsProperty(&texinfo->extensions, err, o);
+  ParseExtrasProperty(&texinfo->extras, o);
+
+  if (store_original_json_for_extras_and_extensions) {
+    {
+      json_const_iterator it;
+      if (FindMember(o, "extensions", it)) {
+        texinfo->extensions_json_string = JsonToString(GetValue(it));
+      }
+    }
+    {
+      json_const_iterator it;
+      if (FindMember(o, "extras", it)) {
+        texinfo->extras_json_string = JsonToString(GetValue(it));
+      }
+    }
+  }
+
+  return true;
+}
+
+static bool ParseBuffer(Buffer *buffer, std::string *err, const json &o,
+                        bool store_original_json_for_extras_and_extensions,
+                        FsCallbacks *fs, const std::string &basedir,
+                        bool is_binary = false,
+                        const unsigned char *bin_data = nullptr,
+                        size_t bin_size = 0) {
+  size_t byteLength;
+  if (!ParseUnsignedProperty(&byteLength, err, o, "byteLength", true,
+                             "Buffer")) {
+    return false;
+  }
+
+  // In glTF 2.0, uri is not mandatory anymore
+  buffer->uri.clear();
+  ParseStringProperty(&buffer->uri, err, o, "uri", false, "Buffer");
+
+  // having an empty uri for a non embedded image should not be valid
+  if (!is_binary && buffer->uri.empty()) {
+    if (err) {
+      (*err) += "'uri' is missing from non binary glTF file buffer.\n";
+    }
+  }
+
+  json_const_iterator type;
+  if (FindMember(o, "type", type)) {
+    std::string typeStr;
+    if (GetString(GetValue(type), typeStr)) {
+      if (typeStr.compare("arraybuffer") == 0) {
+        // buffer.type = "arraybuffer";
+      }
+    }
+  }
+
+  if (is_binary) {
+    // Still binary glTF accepts external dataURI.
+    if (!buffer->uri.empty()) {
+      // First try embedded data URI.
+      if (IsDataURI(buffer->uri)) {
+        std::string mime_type;
+        if (!DecodeDataURI(&buffer->data, mime_type, buffer->uri, byteLength,
+                           true)) {
+          if (err) {
+            (*err) +=
+                "Failed to decode 'uri' : " + buffer->uri + " in Buffer\n";
+          }
+          return false;
+        }
+      } else {
+        // External .bin file.
+        std::string decoded_uri = dlib::urldecode(buffer->uri);
+        if (!LoadExternalFile(&buffer->data, err, /* warn */ nullptr,
+                              decoded_uri, basedir, /* required */ true,
+                              byteLength, /* checkSize */ true, fs)) {
+          return false;
+        }
+      }
+    } else {
+      // load data from (embedded) binary data
+
+      if ((bin_size == 0) || (bin_data == nullptr)) {
+        if (err) {
+          (*err) += "Invalid binary data in `Buffer'.\n";
+        }
+        return false;
+      }
+
+      if (byteLength > bin_size) {
+        if (err) {
+          std::stringstream ss;
+          ss << "Invalid `byteLength'. Must be equal or less than binary size: "
+                "`byteLength' = "
+             << byteLength << ", binary size = " << bin_size << std::endl;
+          (*err) += ss.str();
+        }
+        return false;
+      }
+
+      // Read buffer data
+      buffer->data.resize(static_cast<size_t>(byteLength));
+      memcpy(&(buffer->data.at(0)), bin_data, static_cast<size_t>(byteLength));
+    }
+
+  } else {
+    if (IsDataURI(buffer->uri)) {
+      std::string mime_type;
+      if (!DecodeDataURI(&buffer->data, mime_type, buffer->uri, byteLength,
+                         true)) {
+        if (err) {
+          (*err) += "Failed to decode 'uri' : " + buffer->uri + " in Buffer\n";
+        }
+        return false;
+      }
+    } else {
+      // Assume external .bin file.
+      std::string decoded_uri = dlib::urldecode(buffer->uri);
+      if (!LoadExternalFile(&buffer->data, err, /* warn */ nullptr, decoded_uri,
+                            basedir, /* required */ true, byteLength,
+                            /* checkSize */ true, fs)) {
+        return false;
+      }
+    }
+  }
+
+  ParseStringProperty(&buffer->name, err, o, "name", false);
+
+  ParseExtensionsProperty(&buffer->extensions, err, o);
+  ParseExtrasProperty(&buffer->extras, o);
+
+  if (store_original_json_for_extras_and_extensions) {
+    {
+      json_const_iterator it;
+      if (FindMember(o, "extensions", it)) {
+        buffer->extensions_json_string = JsonToString(GetValue(it));
+      }
+    }
+    {
+      json_const_iterator it;
+      if (FindMember(o, "extras", it)) {
+        buffer->extras_json_string = JsonToString(GetValue(it));
+      }
+    }
+  }
+
+  return true;
+}
+
+static bool ParseBufferView(
+    BufferView *bufferView, std::string *err, const json &o,
+    bool store_original_json_for_extras_and_extensions) {
+  int buffer = -1;
+  if (!ParseIntegerProperty(&buffer, err, o, "buffer", true, "BufferView")) {
+    return false;
+  }
+
+  size_t byteOffset = 0;
+  ParseUnsignedProperty(&byteOffset, err, o, "byteOffset", false);
+
+  size_t byteLength = 1;
+  if (!ParseUnsignedProperty(&byteLength, err, o, "byteLength", true,
+                             "BufferView")) {
+    return false;
+  }
+
+  size_t byteStride = 0;
+  if (!ParseUnsignedProperty(&byteStride, err, o, "byteStride", false)) {
+    // Spec says: When byteStride of referenced bufferView is not defined, it
+    // means that accessor elements are tightly packed, i.e., effective stride
+    // equals the size of the element.
+    // We cannot determine the actual byteStride until Accessor are parsed, thus
+    // set 0(= tightly packed) here(as done in OpenGL's VertexAttribPoiner)
+    byteStride = 0;
+  }
+
+  if ((byteStride > 252) || ((byteStride % 4) != 0)) {
+    if (err) {
+      std::stringstream ss;
+      ss << "Invalid `byteStride' value. `byteStride' must be the multiple of "
+            "4 : "
+         << byteStride << std::endl;
+
+      (*err) += ss.str();
+    }
+    return false;
+  }
+
+  int target = 0;
+  ParseIntegerProperty(&target, err, o, "target", false);
+  if ((target == TINYGLTF_TARGET_ARRAY_BUFFER) ||
+      (target == TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER)) {
+    // OK
+  } else {
+    target = 0;
+  }
+  bufferView->target = target;
+
+  ParseStringProperty(&bufferView->name, err, o, "name", false);
+
+  ParseExtensionsProperty(&bufferView->extensions, err, o);
+  ParseExtrasProperty(&bufferView->extras, o);
+
+  if (store_original_json_for_extras_and_extensions) {
+    {
+      json_const_iterator it;
+      if (FindMember(o, "extensions", it)) {
+        bufferView->extensions_json_string = JsonToString(GetValue(it));
+      }
+    }
+    {
+      json_const_iterator it;
+      if (FindMember(o, "extras", it)) {
+        bufferView->extras_json_string = JsonToString(GetValue(it));
+      }
+    }
+  }
+
+  bufferView->buffer = buffer;
+  bufferView->byteOffset = byteOffset;
+  bufferView->byteLength = byteLength;
+  bufferView->byteStride = byteStride;
+  return true;
+}
+
+static bool ParseSparseAccessor(Accessor *accessor, std::string *err,
+                                const json &o) {
+  accessor->sparse.isSparse = true;
+
+  int count = 0;
+  ParseIntegerProperty(&count, err, o, "count", true);
+
+  json_const_iterator indices_iterator;
+  json_const_iterator values_iterator;
+  if (!FindMember(o, "indices", indices_iterator)) {
+    (*err) = "the sparse object of this accessor doesn't have indices";
+    return false;
+  }
+
+  if (!FindMember(o, "values", values_iterator)) {
+    (*err) = "the sparse object ob ths accessor doesn't have values";
+    return false;
+  }
+
+  const json &indices_obj = GetValue(indices_iterator);
+  const json &values_obj = GetValue(values_iterator);
+
+  int indices_buffer_view = 0, indices_byte_offset = 0, component_type = 0;
+  ParseIntegerProperty(&indices_buffer_view, err, indices_obj, "bufferView",
+                       true);
+  ParseIntegerProperty(&indices_byte_offset, err, indices_obj, "byteOffset",
+                       true);
+  ParseIntegerProperty(&component_type, err, indices_obj, "componentType",
+                       true);
+
+  int values_buffer_view = 0, values_byte_offset = 0;
+  ParseIntegerProperty(&values_buffer_view, err, values_obj, "bufferView",
+                       true);
+  ParseIntegerProperty(&values_byte_offset, err, values_obj, "byteOffset",
+                       true);
+
+  accessor->sparse.count = count;
+  accessor->sparse.indices.bufferView = indices_buffer_view;
+  accessor->sparse.indices.byteOffset = indices_byte_offset;
+  accessor->sparse.indices.componentType = component_type;
+  accessor->sparse.values.bufferView = values_buffer_view;
+  accessor->sparse.values.byteOffset = values_byte_offset;
+
+  // todo check theses values
+
+  return true;
+}
+
+static bool ParseAccessor(Accessor *accessor, std::string *err, const json &o,
+                          bool store_original_json_for_extras_and_extensions) {
+  int bufferView = -1;
+  ParseIntegerProperty(&bufferView, err, o, "bufferView", false, "Accessor");
+
+  size_t byteOffset = 0;
+  ParseUnsignedProperty(&byteOffset, err, o, "byteOffset", false, "Accessor");
+
+  bool normalized = false;
+  ParseBooleanProperty(&normalized, err, o, "normalized", false, "Accessor");
+
+  size_t componentType = 0;
+  if (!ParseUnsignedProperty(&componentType, err, o, "componentType", true,
+                             "Accessor")) {
+    return false;
+  }
+
+  size_t count = 0;
+  if (!ParseUnsignedProperty(&count, err, o, "count", true, "Accessor")) {
+    return false;
+  }
+
+  std::string type;
+  if (!ParseStringProperty(&type, err, o, "type", true, "Accessor")) {
+    return false;
+  }
+
+  if (type.compare("SCALAR") == 0) {
+    accessor->type = TINYGLTF_TYPE_SCALAR;
+  } else if (type.compare("VEC2") == 0) {
+    accessor->type = TINYGLTF_TYPE_VEC2;
+  } else if (type.compare("VEC3") == 0) {
+    accessor->type = TINYGLTF_TYPE_VEC3;
+  } else if (type.compare("VEC4") == 0) {
+    accessor->type = TINYGLTF_TYPE_VEC4;
+  } else if (type.compare("MAT2") == 0) {
+    accessor->type = TINYGLTF_TYPE_MAT2;
+  } else if (type.compare("MAT3") == 0) {
+    accessor->type = TINYGLTF_TYPE_MAT3;
+  } else if (type.compare("MAT4") == 0) {
+    accessor->type = TINYGLTF_TYPE_MAT4;
+  } else {
+    std::stringstream ss;
+    ss << "Unsupported `type` for accessor object. Got \"" << type << "\"\n";
+    if (err) {
+      (*err) += ss.str();
+    }
+    return false;
+  }
+
+  ParseStringProperty(&accessor->name, err, o, "name", false);
+
+  accessor->minValues.clear();
+  accessor->maxValues.clear();
+  ParseNumberArrayProperty(&accessor->minValues, err, o, "min", false,
+                           "Accessor");
+
+  ParseNumberArrayProperty(&accessor->maxValues, err, o, "max", false,
+                           "Accessor");
+
+  accessor->count = count;
+  accessor->bufferView = bufferView;
+  accessor->byteOffset = byteOffset;
+  accessor->normalized = normalized;
+  {
+    if (componentType >= TINYGLTF_COMPONENT_TYPE_BYTE &&
+        componentType <= TINYGLTF_COMPONENT_TYPE_DOUBLE) {
+      // OK
+      accessor->componentType = int(componentType);
+    } else {
+      std::stringstream ss;
+      ss << "Invalid `componentType` in accessor. Got " << componentType
+         << "\n";
+      if (err) {
+        (*err) += ss.str();
+      }
+      return false;
+    }
+  }
+
+  ParseExtensionsProperty(&(accessor->extensions), err, o);
+  ParseExtrasProperty(&(accessor->extras), o);
+
+  if (store_original_json_for_extras_and_extensions) {
+    {
+      json_const_iterator it;
+      if (FindMember(o, "extensions", it)) {
+        accessor->extensions_json_string = JsonToString(GetValue(it));
+      }
+    }
+    {
+      json_const_iterator it;
+      if (FindMember(o, "extras", it)) {
+        accessor->extras_json_string = JsonToString(GetValue(it));
+      }
+    }
+  }
+
+  // check if accessor has a "sparse" object:
+  json_const_iterator iterator;
+  if (FindMember(o, "sparse", iterator)) {
+    // here this accessor has a "sparse" subobject
+    return ParseSparseAccessor(accessor, err, GetValue(iterator));
+  }
+
+  return true;
+}
+
+#ifdef TINYGLTF_ENABLE_DRACO
+
+static void DecodeIndexBuffer(draco::Mesh *mesh, size_t componentSize,
+                              std::vector<uint8_t> &outBuffer) {
+  if (componentSize == 4) {
+    assert(sizeof(mesh->face(draco::FaceIndex(0))[0]) == componentSize);
+    memcpy(outBuffer.data(), &mesh->face(draco::FaceIndex(0))[0],
+           outBuffer.size());
+  } else {
+    size_t faceStride = componentSize * 3;
+    for (draco::FaceIndex f(0); f < mesh->num_faces(); ++f) {
+      const draco::Mesh::Face &face = mesh->face(f);
+      if (componentSize == 2) {
+        uint16_t indices[3] = {(uint16_t)face[0].value(),
+                               (uint16_t)face[1].value(),
+                               (uint16_t)face[2].value()};
+        memcpy(outBuffer.data() + f.value() * faceStride, &indices[0],
+               faceStride);
+      } else {
+        uint8_t indices[3] = {(uint8_t)face[0].value(),
+                              (uint8_t)face[1].value(),
+                              (uint8_t)face[2].value()};
+        memcpy(outBuffer.data() + f.value() * faceStride, &indices[0],
+               faceStride);
+      }
+    }
+  }
+}
+
+template <typename T>
+static bool GetAttributeForAllPoints(draco::Mesh *mesh,
+                                     const draco::PointAttribute *pAttribute,
+                                     std::vector<uint8_t> &outBuffer) {
+  size_t byteOffset = 0;
+  T values[4] = {0, 0, 0, 0};
+  for (draco::PointIndex i(0); i < mesh->num_points(); ++i) {
+    const draco::AttributeValueIndex val_index = pAttribute->mapped_index(i);
+    if (!pAttribute->ConvertValue<T>(val_index, pAttribute->num_components(),
+                                     values))
+      return false;
+
+    memcpy(outBuffer.data() + byteOffset, &values[0],
+           sizeof(T) * pAttribute->num_components());
+    byteOffset += sizeof(T) * pAttribute->num_components();
+  }
+
+  return true;
+}
+
+static bool GetAttributeForAllPoints(uint32_t componentType, draco::Mesh *mesh,
+                                     const draco::PointAttribute *pAttribute,
+                                     std::vector<uint8_t> &outBuffer) {
+  bool decodeResult = false;
+  switch (componentType) {
+    case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE:
+      decodeResult =
+          GetAttributeForAllPoints<uint8_t>(mesh, pAttribute, outBuffer);
+      break;
+    case TINYGLTF_COMPONENT_TYPE_BYTE:
+      decodeResult =
+          GetAttributeForAllPoints<int8_t>(mesh, pAttribute, outBuffer);
+      break;
+    case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT:
+      decodeResult =
+          GetAttributeForAllPoints<uint16_t>(mesh, pAttribute, outBuffer);
+      break;
+    case TINYGLTF_COMPONENT_TYPE_SHORT:
+      decodeResult =
+          GetAttributeForAllPoints<int16_t>(mesh, pAttribute, outBuffer);
+      break;
+    case TINYGLTF_COMPONENT_TYPE_INT:
+      decodeResult =
+          GetAttributeForAllPoints<int32_t>(mesh, pAttribute, outBuffer);
+      break;
+    case TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT:
+      decodeResult =
+          GetAttributeForAllPoints<uint32_t>(mesh, pAttribute, outBuffer);
+      break;
+    case TINYGLTF_COMPONENT_TYPE_FLOAT:
+      decodeResult =
+          GetAttributeForAllPoints<float>(mesh, pAttribute, outBuffer);
+      break;
+    case TINYGLTF_COMPONENT_TYPE_DOUBLE:
+      decodeResult =
+          GetAttributeForAllPoints<double>(mesh, pAttribute, outBuffer);
+      break;
+    default:
+      return false;
+  }
+
+  return decodeResult;
+}
+
+static bool ParseDracoExtension(Primitive *primitive, Model *model,
+                                std::string *err,
+                                const Value &dracoExtensionValue) {
+  auto bufferViewValue = dracoExtensionValue.Get("bufferView");
+  if (!bufferViewValue.IsInt()) return false;
+  auto attributesValue = dracoExtensionValue.Get("attributes");
+  if (!attributesValue.IsObject()) return false;
+
+  auto attributesObject = attributesValue.Get<Value::Object>();
+  int bufferView = bufferViewValue.Get<int>();
+
+  BufferView &view = model->bufferViews[bufferView];
+  Buffer &buffer = model->buffers[view.buffer];
+  // BufferView has already been decoded
+  if (view.dracoDecoded) return true;
+  view.dracoDecoded = true;
+
+  const char *bufferViewData =
+      reinterpret_cast<const char *>(buffer.data.data() + view.byteOffset);
+  size_t bufferViewSize = view.byteLength;
+
+  // decode draco
+  draco::DecoderBuffer decoderBuffer;
+  decoderBuffer.Init(bufferViewData, bufferViewSize);
+  draco::Decoder decoder;
+  auto decodeResult = decoder.DecodeMeshFromBuffer(&decoderBuffer);
+  if (!decodeResult.ok()) {
+    return false;
+  }
+  const std::unique_ptr<draco::Mesh> &mesh = decodeResult.value();
+
+  // create new bufferView for indices
+  if (primitive->indices >= 0) {
+    int32_t componentSize = GetComponentSizeInBytes(
+        model->accessors[primitive->indices].componentType);
+    Buffer decodedIndexBuffer;
+    decodedIndexBuffer.data.resize(mesh->num_faces() * 3 * componentSize);
+
+    DecodeIndexBuffer(mesh.get(), componentSize, decodedIndexBuffer.data);
+
+    model->buffers.emplace_back(std::move(decodedIndexBuffer));
+
+    BufferView decodedIndexBufferView;
+    decodedIndexBufferView.buffer = int(model->buffers.size() - 1);
+    decodedIndexBufferView.byteLength =
+        int(mesh->num_faces() * 3 * componentSize);
+    decodedIndexBufferView.byteOffset = 0;
+    decodedIndexBufferView.byteStride = 0;
+    decodedIndexBufferView.target = TINYGLTF_TARGET_ARRAY_BUFFER;
+    model->bufferViews.emplace_back(std::move(decodedIndexBufferView));
+
+    model->accessors[primitive->indices].bufferView =
+        int(model->bufferViews.size() - 1);
+    model->accessors[primitive->indices].count = int(mesh->num_faces() * 3);
+  }
+
+  for (const auto &attribute : attributesObject) {
+    if (!attribute.second.IsInt()) return false;
+    auto primitiveAttribute = primitive->attributes.find(attribute.first);
+    if (primitiveAttribute == primitive->attributes.end()) return false;
+
+    int dracoAttributeIndex = attribute.second.Get<int>();
+    const auto pAttribute = mesh->GetAttributeByUniqueId(dracoAttributeIndex);
+    const auto pBuffer = pAttribute->buffer();
+    const auto componentType =
+        model->accessors[primitiveAttribute->second].componentType;
+
+    // Create a new buffer for this decoded buffer
+    Buffer decodedBuffer;
+    size_t bufferSize = mesh->num_points() * pAttribute->num_components() *
+                        GetComponentSizeInBytes(componentType);
+    decodedBuffer.data.resize(bufferSize);
+
+    if (!GetAttributeForAllPoints(componentType, mesh.get(), pAttribute,
+                                  decodedBuffer.data))
+      return false;
+
+    model->buffers.emplace_back(std::move(decodedBuffer));
+
+    BufferView decodedBufferView;
+    decodedBufferView.buffer = int(model->buffers.size() - 1);
+    decodedBufferView.byteLength = bufferSize;
+    decodedBufferView.byteOffset = pAttribute->byte_offset();
+    decodedBufferView.byteStride = pAttribute->byte_stride();
+    decodedBufferView.target = primitive->indices >= 0
+                                   ? TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER
+                                   : TINYGLTF_TARGET_ARRAY_BUFFER;
+    model->bufferViews.emplace_back(std::move(decodedBufferView));
+
+    model->accessors[primitiveAttribute->second].bufferView =
+        int(model->bufferViews.size() - 1);
+    model->accessors[primitiveAttribute->second].count =
+        int(mesh->num_points());
+  }
+
+  return true;
+}
+#endif
+
+static bool ParsePrimitive(Primitive *primitive, Model *model, std::string *err,
+                           const json &o,
+                           bool store_original_json_for_extras_and_extensions) {
+  int material = -1;
+  ParseIntegerProperty(&material, err, o, "material", false);
+  primitive->material = material;
+
+  int mode = TINYGLTF_MODE_TRIANGLES;
+  ParseIntegerProperty(&mode, err, o, "mode", false);
+  primitive->mode = mode;  // Why only triangled were supported ?
+
+  int indices = -1;
+  ParseIntegerProperty(&indices, err, o, "indices", false);
+  primitive->indices = indices;
+  if (!ParseStringIntegerProperty(&primitive->attributes, err, o, "attributes",
+                                  true, "Primitive")) {
+    return false;
+  }
+
+  // Look for morph targets
+  json_const_iterator targetsObject;
+  if (FindMember(o, "targets", targetsObject) &&
+      IsArray(GetValue(targetsObject))) {
+    auto targetsObjectEnd = ArrayEnd(GetValue(targetsObject));
+    for (json_const_array_iterator i = ArrayBegin(GetValue(targetsObject));
+         i != targetsObjectEnd; ++i) {
+      std::map<std::string, int> targetAttribues;
+
+      const json &dict = *i;
+      if (IsObject(dict)) {
+        json_const_iterator dictIt(ObjectBegin(dict));
+        json_const_iterator dictItEnd(ObjectEnd(dict));
+
+        for (; dictIt != dictItEnd; ++dictIt) {
+          int iVal;
+          if (GetInt(GetValue(dictIt), iVal))
+            targetAttribues[GetKey(dictIt)] = iVal;
+        }
+        primitive->targets.emplace_back(std::move(targetAttribues));
+      }
+    }
+  }
+
+  ParseExtrasProperty(&(primitive->extras), o);
+  ParseExtensionsProperty(&primitive->extensions, err, o);
+
+  if (store_original_json_for_extras_and_extensions) {
+    {
+      json_const_iterator it;
+      if (FindMember(o, "extensions", it)) {
+        primitive->extensions_json_string = JsonToString(GetValue(it));
+      }
+    }
+    {
+      json_const_iterator it;
+      if (FindMember(o, "extras", it)) {
+        primitive->extras_json_string = JsonToString(GetValue(it));
+      }
+    }
+  }
+
+#ifdef TINYGLTF_ENABLE_DRACO
+  auto dracoExtension =
+      primitive->extensions.find("KHR_draco_mesh_compression");
+  if (dracoExtension != primitive->extensions.end()) {
+    ParseDracoExtension(primitive, model, err, dracoExtension->second);
+  }
+#else
+  (void)model;
+#endif
+
+  return true;
+}
+
+static bool ParseMesh(Mesh *mesh, Model *model, std::string *err, const json &o,
+                      bool store_original_json_for_extras_and_extensions) {
+  ParseStringProperty(&mesh->name, err, o, "name", false);
+
+  mesh->primitives.clear();
+  json_const_iterator primObject;
+  if (FindMember(o, "primitives", primObject) &&
+      IsArray(GetValue(primObject))) {
+    json_const_array_iterator primEnd = ArrayEnd(GetValue(primObject));
+    for (json_const_array_iterator i = ArrayBegin(GetValue(primObject));
+         i != primEnd; ++i) {
+      Primitive primitive;
+      if (ParsePrimitive(&primitive, model, err, *i,
+                         store_original_json_for_extras_and_extensions)) {
+        // Only add the primitive if the parsing succeeds.
+        mesh->primitives.emplace_back(std::move(primitive));
+      }
+    }
+  }
+
+  // Should probably check if has targets and if dimensions fit
+  ParseNumberArrayProperty(&mesh->weights, err, o, "weights", false);
+
+  ParseExtensionsProperty(&mesh->extensions, err, o);
+  ParseExtrasProperty(&(mesh->extras), o);
+
+  if (store_original_json_for_extras_and_extensions) {
+    {
+      json_const_iterator it;
+      if (FindMember(o, "extensions", it)) {
+        mesh->extensions_json_string = JsonToString(GetValue(it));
+      }
+    }
+    {
+      json_const_iterator it;
+      if (FindMember(o, "extras", it)) {
+        mesh->extras_json_string = JsonToString(GetValue(it));
+      }
+    }
+  }
+
+  return true;
+}
+
+static bool ParseNode(Node *node, std::string *err, const json &o,
+                      bool store_original_json_for_extras_and_extensions) {
+  ParseStringProperty(&node->name, err, o, "name", false);
+
+  int skin = -1;
+  ParseIntegerProperty(&skin, err, o, "skin", false);
+  node->skin = skin;
+
+  // Matrix and T/R/S are exclusive
+  if (!ParseNumberArrayProperty(&node->matrix, err, o, "matrix", false)) {
+    ParseNumberArrayProperty(&node->rotation, err, o, "rotation", false);
+    ParseNumberArrayProperty(&node->scale, err, o, "scale", false);
+    ParseNumberArrayProperty(&node->translation, err, o, "translation", false);
+  }
+
+  int camera = -1;
+  ParseIntegerProperty(&camera, err, o, "camera", false);
+  node->camera = camera;
+
+  int mesh = -1;
+  ParseIntegerProperty(&mesh, err, o, "mesh", false);
+  node->mesh = mesh;
+
+  node->children.clear();
+  ParseIntegerArrayProperty(&node->children, err, o, "children", false);
+
+  ParseNumberArrayProperty(&node->weights, err, o, "weights", false);
+
+  ParseExtensionsProperty(&node->extensions, err, o);
+  ParseExtrasProperty(&(node->extras), o);
+
+  if (store_original_json_for_extras_and_extensions) {
+    {
+      json_const_iterator it;
+      if (FindMember(o, "extensions", it)) {
+        node->extensions_json_string = JsonToString(GetValue(it));
+      }
+    }
+    {
+      json_const_iterator it;
+      if (FindMember(o, "extras", it)) {
+        node->extras_json_string = JsonToString(GetValue(it));
+      }
+    }
+  }
+
+  return true;
+}
+
+static bool ParsePbrMetallicRoughness(
+    PbrMetallicRoughness *pbr, std::string *err, const json &o,
+    bool store_original_json_for_extras_and_extensions) {
+  if (pbr == nullptr) {
+    return false;
+  }
+
+  std::vector<double> baseColorFactor;
+  if (ParseNumberArrayProperty(&baseColorFactor, err, o, "baseColorFactor",
+                               /* required */ false)) {
+    if (baseColorFactor.size() != 4) {
+      if (err) {
+        (*err) +=
+            "Array length of `baseColorFactor` parameter in "
+            "pbrMetallicRoughness must be 4, but got " +
+            std::to_string(baseColorFactor.size()) + "\n";
+      }
+      return false;
+    }
+    pbr->baseColorFactor = baseColorFactor;
+  }
+
+  {
+    json_const_iterator it;
+    if (FindMember(o, "baseColorTexture", it)) {
+      ParseTextureInfo(&pbr->baseColorTexture, err, GetValue(it),
+                       store_original_json_for_extras_and_extensions);
+    }
+  }
+
+  {
+    json_const_iterator it;
+    if (FindMember(o, "metallicRoughnessTexture", it)) {
+      ParseTextureInfo(&pbr->metallicRoughnessTexture, err, GetValue(it),
+                       store_original_json_for_extras_and_extensions);
+    }
+  }
+
+  ParseNumberProperty(&pbr->metallicFactor, err, o, "metallicFactor", false);
+  ParseNumberProperty(&pbr->roughnessFactor, err, o, "roughnessFactor", false);
+
+  ParseExtensionsProperty(&pbr->extensions, err, o);
+  ParseExtrasProperty(&pbr->extras, o);
+
+  if (store_original_json_for_extras_and_extensions) {
+    {
+      json_const_iterator it;
+      if (FindMember(o, "extensions", it)) {
+        pbr->extensions_json_string = JsonToString(GetValue(it));
+      }
+    }
+    {
+      json_const_iterator it;
+      if (FindMember(o, "extras", it)) {
+        pbr->extras_json_string = JsonToString(GetValue(it));
+      }
+    }
+  }
+
+  return true;
+}
+
+static bool ParseMaterial(Material *material, std::string *err, const json &o,
+                          bool store_original_json_for_extras_and_extensions) {
+  ParseStringProperty(&material->name, err, o, "name", /* required */ false);
+
+  if (ParseNumberArrayProperty(&material->emissiveFactor, err, o,
+                               "emissiveFactor",
+                               /* required */ false)) {
+    if (material->emissiveFactor.size() != 3) {
+      if (err) {
+        (*err) +=
+            "Array length of `emissiveFactor` parameter in "
+            "material must be 3, but got " +
+            std::to_string(material->emissiveFactor.size()) + "\n";
+      }
+      return false;
+    }
+  } else {
+    // fill with default values
+    material->emissiveFactor = {0.0, 0.0, 0.0};
+  }
+
+  ParseStringProperty(&material->alphaMode, err, o, "alphaMode",
+                      /* required */ false);
+  ParseNumberProperty(&material->alphaCutoff, err, o, "alphaCutoff",
+                      /* required */ false);
+  ParseBooleanProperty(&material->doubleSided, err, o, "doubleSided",
+                       /* required */ false);
+
+  {
+    json_const_iterator it;
+    if (FindMember(o, "pbrMetallicRoughness", it)) {
+      ParsePbrMetallicRoughness(&material->pbrMetallicRoughness, err,
+                                GetValue(it),
+                                store_original_json_for_extras_and_extensions);
+    }
+  }
+
+  {
+    json_const_iterator it;
+    if (FindMember(o, "normalTexture", it)) {
+      ParseNormalTextureInfo(&material->normalTexture, err, GetValue(it),
+                             store_original_json_for_extras_and_extensions);
+    }
+  }
+
+  {
+    json_const_iterator it;
+    if (FindMember(o, "occlusionTexture", it)) {
+      ParseOcclusionTextureInfo(&material->occlusionTexture, err, GetValue(it),
+                                store_original_json_for_extras_and_extensions);
+    }
+  }
+
+  {
+    json_const_iterator it;
+    if (FindMember(o, "emissiveTexture", it)) {
+      ParseTextureInfo(&material->emissiveTexture, err, GetValue(it),
+                       store_original_json_for_extras_and_extensions);
+    }
+  }
+
+  // Old code path. For backward compatibility, we still store material values
+  // as Parameter. This will create duplicated information for
+  // example(pbrMetallicRoughness), but should be neglible in terms of memory
+  // consumption.
+  // TODO(syoyo): Remove in the next major release.
+  material->values.clear();
+  material->additionalValues.clear();
+
+  json_const_iterator it(ObjectBegin(o));
+  json_const_iterator itEnd(ObjectEnd(o));
+
+  for (; it != itEnd; ++it) {
+    std::string key(GetKey(it));
+    if (key == "pbrMetallicRoughness") {
+      if (IsObject(GetValue(it))) {
+        const json &values_object = GetValue(it);
+
+        json_const_iterator itVal(ObjectBegin(values_object));
+        json_const_iterator itValEnd(ObjectEnd(values_object));
+
+        for (; itVal != itValEnd; ++itVal) {
+          Parameter param;
+          if (ParseParameterProperty(&param, err, values_object, GetKey(itVal),
+                                     false)) {
+            material->values.emplace(GetKey(itVal), std::move(param));
+          }
+        }
+      }
+    } else if (key == "extensions" || key == "extras") {
+      // done later, skip, otherwise poorly parsed contents will be saved in the
+      // parametermap and serialized again later
+    } else {
+      Parameter param;
+      if (ParseParameterProperty(&param, err, o, key, false)) {
+        // names of materials have already been parsed. Putting it in this map
+        // doesn't correctly reflext the glTF specification
+        if (key != "name")
+          material->additionalValues.emplace(std::move(key), std::move(param));
+      }
+    }
+  }
+
+  material->extensions.clear();
+  ParseExtensionsProperty(&material->extensions, err, o);
+  ParseExtrasProperty(&(material->extras), o);
+
+  if (store_original_json_for_extras_and_extensions) {
+    {
+      json_const_iterator eit;
+      if (FindMember(o, "extensions", eit)) {
+        material->extensions_json_string = JsonToString(GetValue(eit));
+      }
+    }
+    {
+      json_const_iterator eit;
+      if (FindMember(o, "extras", eit)) {
+        material->extras_json_string = JsonToString(GetValue(eit));
+      }
+    }
+  }
+
+  return true;
+}
+
+static bool ParseAnimationChannel(
+    AnimationChannel *channel, std::string *err, const json &o,
+    bool store_original_json_for_extras_and_extensions) {
+  int samplerIndex = -1;
+  int targetIndex = -1;
+  if (!ParseIntegerProperty(&samplerIndex, err, o, "sampler", true,
+                            "AnimationChannel")) {
+    if (err) {
+      (*err) += "`sampler` field is missing in animation channels\n";
+    }
+    return false;
+  }
+
+  json_const_iterator targetIt;
+  if (FindMember(o, "target", targetIt) && IsObject(GetValue(targetIt))) {
+    const json &target_object = GetValue(targetIt);
+
+    if (!ParseIntegerProperty(&targetIndex, err, target_object, "node", true)) {
+      if (err) {
+        (*err) += "`node` field is missing in animation.channels.target\n";
+      }
+      return false;
+    }
+
+    if (!ParseStringProperty(&channel->target_path, err, target_object, "path",
+                             true)) {
+      if (err) {
+        (*err) += "`path` field is missing in animation.channels.target\n";
+      }
+      return false;
+    }
+	ParseExtensionsProperty(&channel->target_extensions, err, target_object);
+	if (store_original_json_for_extras_and_extensions) {
+      json_const_iterator it;
+      if (FindMember(target_object, "extensions", it)) {
+        channel->target_extensions_json_string = JsonToString(GetValue(it));
+      }
+	}
+  }
+
+  channel->sampler = samplerIndex;
+  channel->target_node = targetIndex;
+
+  ParseExtensionsProperty(&channel->extensions, err, o);
+  ParseExtrasProperty(&(channel->extras), o);
+
+  if (store_original_json_for_extras_and_extensions) {
+    {
+      json_const_iterator it;
+      if (FindMember(o, "extensions", it)) {
+        channel->extensions_json_string = JsonToString(GetValue(it));
+      }
+    }
+    {
+      json_const_iterator it;
+      if (FindMember(o, "extras", it)) {
+        channel->extras_json_string = JsonToString(GetValue(it));
+      }
+    }
+  }
+
+  return true;
+}
+
+static bool ParseAnimation(Animation *animation, std::string *err,
+                           const json &o,
+                           bool store_original_json_for_extras_and_extensions) {
+  {
+    json_const_iterator channelsIt;
+    if (FindMember(o, "channels", channelsIt) &&
+        IsArray(GetValue(channelsIt))) {
+      json_const_array_iterator channelEnd = ArrayEnd(GetValue(channelsIt));
+      for (json_const_array_iterator i = ArrayBegin(GetValue(channelsIt));
+           i != channelEnd; ++i) {
+        AnimationChannel channel;
+        if (ParseAnimationChannel(
+                &channel, err, *i,
+                store_original_json_for_extras_and_extensions)) {
+          // Only add the channel if the parsing succeeds.
+          animation->channels.emplace_back(std::move(channel));
+        }
+      }
+    }
+  }
+
+  {
+    json_const_iterator samplerIt;
+    if (FindMember(o, "samplers", samplerIt) && IsArray(GetValue(samplerIt))) {
+      const json &sampler_array = GetValue(samplerIt);
+
+      json_const_array_iterator it = ArrayBegin(sampler_array);
+      json_const_array_iterator itEnd = ArrayEnd(sampler_array);
+
+      for (; it != itEnd; ++it) {
+        const json &s = *it;
+
+        AnimationSampler sampler;
+        int inputIndex = -1;
+        int outputIndex = -1;
+        if (!ParseIntegerProperty(&inputIndex, err, s, "input", true)) {
+          if (err) {
+            (*err) += "`input` field is missing in animation.sampler\n";
+          }
+          return false;
+        }
+        ParseStringProperty(&sampler.interpolation, err, s, "interpolation",
+                            false);
+        if (!ParseIntegerProperty(&outputIndex, err, s, "output", true)) {
+          if (err) {
+            (*err) += "`output` field is missing in animation.sampler\n";
+          }
+          return false;
+        }
+        sampler.input = inputIndex;
+        sampler.output = outputIndex;
+        ParseExtensionsProperty(&(sampler.extensions), err, o);
+        ParseExtrasProperty(&(sampler.extras), s);
+
+        if (store_original_json_for_extras_and_extensions) {
+          {
+            json_const_iterator eit;
+            if (FindMember(o, "extensions", eit)) {
+              sampler.extensions_json_string = JsonToString(GetValue(eit));
+            }
+          }
+          {
+            json_const_iterator eit;
+            if (FindMember(o, "extras", eit)) {
+              sampler.extras_json_string = JsonToString(GetValue(eit));
+            }
+          }
+        }
+
+        animation->samplers.emplace_back(std::move(sampler));
+      }
+    }
+  }
+
+  ParseStringProperty(&animation->name, err, o, "name", false);
+
+  ParseExtensionsProperty(&animation->extensions, err, o);
+  ParseExtrasProperty(&(animation->extras), o);
+
+  if (store_original_json_for_extras_and_extensions) {
+    {
+      json_const_iterator it;
+      if (FindMember(o, "extensions", it)) {
+        animation->extensions_json_string = JsonToString(GetValue(it));
+      }
+    }
+    {
+      json_const_iterator it;
+      if (FindMember(o, "extras", it)) {
+        animation->extras_json_string = JsonToString(GetValue(it));
+      }
+    }
+  }
+
+  return true;
+}
+
+static bool ParseSampler(Sampler *sampler, std::string *err, const json &o,
+                         bool store_original_json_for_extras_and_extensions) {
+  ParseStringProperty(&sampler->name, err, o, "name", false);
+
+  int minFilter = -1;
+  int magFilter = -1;
+  int wrapS = TINYGLTF_TEXTURE_WRAP_REPEAT;
+  int wrapT = TINYGLTF_TEXTURE_WRAP_REPEAT;
+  int wrapR = TINYGLTF_TEXTURE_WRAP_REPEAT;
+  ParseIntegerProperty(&minFilter, err, o, "minFilter", false);
+  ParseIntegerProperty(&magFilter, err, o, "magFilter", false);
+  ParseIntegerProperty(&wrapS, err, o, "wrapS", false);
+  ParseIntegerProperty(&wrapT, err, o, "wrapT", false);
+  ParseIntegerProperty(&wrapR, err, o, "wrapR", false);  // tinygltf extension
+
+  // TODO(syoyo): Check the value is alloed one.
+  // (e.g. we allow 9728(NEAREST), but don't allow 9727)
+
+  sampler->minFilter = minFilter;
+  sampler->magFilter = magFilter;
+  sampler->wrapS = wrapS;
+  sampler->wrapT = wrapT;
+  sampler->wrapR = wrapR;
+
+  ParseExtensionsProperty(&(sampler->extensions), err, o);
+  ParseExtrasProperty(&(sampler->extras), o);
+
+  if (store_original_json_for_extras_and_extensions) {
+    {
+      json_const_iterator it;
+      if (FindMember(o, "extensions", it)) {
+        sampler->extensions_json_string = JsonToString(GetValue(it));
+      }
+    }
+    {
+      json_const_iterator it;
+      if (FindMember(o, "extras", it)) {
+        sampler->extras_json_string = JsonToString(GetValue(it));
+      }
+    }
+  }
+
+  return true;
+}
+
+static bool ParseSkin(Skin *skin, std::string *err, const json &o,
+                      bool store_original_json_for_extras_and_extensions) {
+  ParseStringProperty(&skin->name, err, o, "name", false, "Skin");
+
+  std::vector<int> joints;
+  if (!ParseIntegerArrayProperty(&joints, err, o, "joints", false, "Skin")) {
+    return false;
+  }
+  skin->joints = std::move(joints);
+
+  int skeleton = -1;
+  ParseIntegerProperty(&skeleton, err, o, "skeleton", false, "Skin");
+  skin->skeleton = skeleton;
+
+  int invBind = -1;
+  ParseIntegerProperty(&invBind, err, o, "inverseBindMatrices", true, "Skin");
+  skin->inverseBindMatrices = invBind;
+
+  ParseExtensionsProperty(&(skin->extensions), err, o);
+  ParseExtrasProperty(&(skin->extras), o);
+
+  if (store_original_json_for_extras_and_extensions) {
+    {
+      json_const_iterator it;
+      if (FindMember(o, "extensions", it)) {
+        skin->extensions_json_string = JsonToString(GetValue(it));
+      }
+    }
+    {
+      json_const_iterator it;
+      if (FindMember(o, "extras", it)) {
+        skin->extras_json_string = JsonToString(GetValue(it));
+      }
+    }
+  }
+
+  return true;
+}
+
+static bool ParsePerspectiveCamera(
+    PerspectiveCamera *camera, std::string *err, const json &o,
+    bool store_original_json_for_extras_and_extensions) {
+  double yfov = 0.0;
+  if (!ParseNumberProperty(&yfov, err, o, "yfov", true, "OrthographicCamera")) {
+    return false;
+  }
+
+  double znear = 0.0;
+  if (!ParseNumberProperty(&znear, err, o, "znear", true,
+                           "PerspectiveCamera")) {
+    return false;
+  }
+
+  double aspectRatio = 0.0;  // = invalid
+  ParseNumberProperty(&aspectRatio, err, o, "aspectRatio", false,
+                      "PerspectiveCamera");
+
+  double zfar = 0.0;  // = invalid
+  ParseNumberProperty(&zfar, err, o, "zfar", false, "PerspectiveCamera");
+
+  camera->aspectRatio = aspectRatio;
+  camera->zfar = zfar;
+  camera->yfov = yfov;
+  camera->znear = znear;
+
+  ParseExtensionsProperty(&camera->extensions, err, o);
+  ParseExtrasProperty(&(camera->extras), o);
+
+  if (store_original_json_for_extras_and_extensions) {
+    {
+      json_const_iterator it;
+      if (FindMember(o, "extensions", it)) {
+        camera->extensions_json_string = JsonToString(GetValue(it));
+      }
+    }
+    {
+      json_const_iterator it;
+      if (FindMember(o, "extras", it)) {
+        camera->extras_json_string = JsonToString(GetValue(it));
+      }
+    }
+  }
+
+  // TODO(syoyo): Validate parameter values.
+
+  return true;
+}
+
+static bool ParseSpotLight(SpotLight *light, std::string *err, const json &o,
+                           bool store_original_json_for_extras_and_extensions) {
+  ParseNumberProperty(&light->innerConeAngle, err, o, "innerConeAngle", false);
+  ParseNumberProperty(&light->outerConeAngle, err, o, "outerConeAngle", false);
+
+  ParseExtensionsProperty(&light->extensions, err, o);
+  ParseExtrasProperty(&light->extras, o);
+
+  if (store_original_json_for_extras_and_extensions) {
+    {
+      json_const_iterator it;
+      if (FindMember(o, "extensions", it)) {
+        light->extensions_json_string = JsonToString(GetValue(it));
+      }
+    }
+    {
+      json_const_iterator it;
+      if (FindMember(o, "extras", it)) {
+        light->extras_json_string = JsonToString(GetValue(it));
+      }
+    }
+  }
+
+  // TODO(syoyo): Validate parameter values.
+
+  return true;
+}
+
+static bool ParseOrthographicCamera(
+    OrthographicCamera *camera, std::string *err, const json &o,
+    bool store_original_json_for_extras_and_extensions) {
+  double xmag = 0.0;
+  if (!ParseNumberProperty(&xmag, err, o, "xmag", true, "OrthographicCamera")) {
+    return false;
+  }
+
+  double ymag = 0.0;
+  if (!ParseNumberProperty(&ymag, err, o, "ymag", true, "OrthographicCamera")) {
+    return false;
+  }
+
+  double zfar = 0.0;
+  if (!ParseNumberProperty(&zfar, err, o, "zfar", true, "OrthographicCamera")) {
+    return false;
+  }
+
+  double znear = 0.0;
+  if (!ParseNumberProperty(&znear, err, o, "znear", true,
+                           "OrthographicCamera")) {
+    return false;
+  }
+
+  ParseExtensionsProperty(&camera->extensions, err, o);
+  ParseExtrasProperty(&(camera->extras), o);
+
+  if (store_original_json_for_extras_and_extensions) {
+    {
+      json_const_iterator it;
+      if (FindMember(o, "extensions", it)) {
+        camera->extensions_json_string = JsonToString(GetValue(it));
+      }
+    }
+    {
+      json_const_iterator it;
+      if (FindMember(o, "extras", it)) {
+        camera->extras_json_string = JsonToString(GetValue(it));
+      }
+    }
+  }
+
+  camera->xmag = xmag;
+  camera->ymag = ymag;
+  camera->zfar = zfar;
+  camera->znear = znear;
+
+  // TODO(syoyo): Validate parameter values.
+
+  return true;
+}
+
+static bool ParseCamera(Camera *camera, std::string *err, const json &o,
+                        bool store_original_json_for_extras_and_extensions) {
+  if (!ParseStringProperty(&camera->type, err, o, "type", true, "Camera")) {
+    return false;
+  }
+
+  if (camera->type.compare("orthographic") == 0) {
+    json_const_iterator orthoIt;
+    if (!FindMember(o, "orthographic", orthoIt)) {
+      if (err) {
+        std::stringstream ss;
+        ss << "Orhographic camera description not found." << std::endl;
+        (*err) += ss.str();
+      }
+      return false;
+    }
+
+    const json &v = GetValue(orthoIt);
+    if (!IsObject(v)) {
+      if (err) {
+        std::stringstream ss;
+        ss << "\"orthographic\" is not a JSON object." << std::endl;
+        (*err) += ss.str();
+      }
+      return false;
+    }
+
+    if (!ParseOrthographicCamera(
+            &camera->orthographic, err, v,
+            store_original_json_for_extras_and_extensions)) {
+      return false;
+    }
+  } else if (camera->type.compare("perspective") == 0) {
+    json_const_iterator perspIt;
+    if (!FindMember(o, "perspective", perspIt)) {
+      if (err) {
+        std::stringstream ss;
+        ss << "Perspective camera description not found." << std::endl;
+        (*err) += ss.str();
+      }
+      return false;
+    }
+
+    const json &v = GetValue(perspIt);
+    if (!IsObject(v)) {
+      if (err) {
+        std::stringstream ss;
+        ss << "\"perspective\" is not a JSON object." << std::endl;
+        (*err) += ss.str();
+      }
+      return false;
+    }
+
+    if (!ParsePerspectiveCamera(
+            &camera->perspective, err, v,
+            store_original_json_for_extras_and_extensions)) {
+      return false;
+    }
+  } else {
+    if (err) {
+      std::stringstream ss;
+      ss << "Invalid camera type: \"" << camera->type
+         << "\". Must be \"perspective\" or \"orthographic\"" << std::endl;
+      (*err) += ss.str();
+    }
+    return false;
+  }
+
+  ParseStringProperty(&camera->name, err, o, "name", false);
+
+  ParseExtensionsProperty(&camera->extensions, err, o);
+  ParseExtrasProperty(&(camera->extras), o);
+
+  if (store_original_json_for_extras_and_extensions) {
+    {
+      json_const_iterator it;
+      if (FindMember(o, "extensions", it)) {
+        camera->extensions_json_string = JsonToString(GetValue(it));
+      }
+    }
+    {
+      json_const_iterator it;
+      if (FindMember(o, "extras", it)) {
+        camera->extras_json_string = JsonToString(GetValue(it));
+      }
+    }
+  }
+
+  return true;
+}
+
+static bool ParseLight(Light *light, std::string *err, const json &o,
+                       bool store_original_json_for_extras_and_extensions) {
+  if (!ParseStringProperty(&light->type, err, o, "type", true)) {
+    return false;
+  }
+
+  if (light->type == "spot") {
+    json_const_iterator spotIt;
+    if (!FindMember(o, "spot", spotIt)) {
+      if (err) {
+        std::stringstream ss;
+        ss << "Spot light description not found." << std::endl;
+        (*err) += ss.str();
+      }
+      return false;
+    }
+
+    const json &v = GetValue(spotIt);
+    if (!IsObject(v)) {
+      if (err) {
+        std::stringstream ss;
+        ss << "\"spot\" is not a JSON object." << std::endl;
+        (*err) += ss.str();
+      }
+      return false;
+    }
+
+    if (!ParseSpotLight(&light->spot, err, v,
+                        store_original_json_for_extras_and_extensions)) {
+      return false;
+    }
+  }
+
+  ParseStringProperty(&light->name, err, o, "name", false);
+  ParseNumberArrayProperty(&light->color, err, o, "color", false);
+  ParseNumberProperty(&light->range, err, o, "range", false);
+  ParseNumberProperty(&light->intensity, err, o, "intensity", false);
+  ParseExtensionsProperty(&light->extensions, err, o);
+  ParseExtrasProperty(&(light->extras), o);
+
+  if (store_original_json_for_extras_and_extensions) {
+    {
+      json_const_iterator it;
+      if (FindMember(o, "extensions", it)) {
+        light->extensions_json_string = JsonToString(GetValue(it));
+      }
+    }
+    {
+      json_const_iterator it;
+      if (FindMember(o, "extras", it)) {
+        light->extras_json_string = JsonToString(GetValue(it));
+      }
+    }
+  }
+
+  return true;
+}
+
+bool TinyGLTF::LoadFromString(Model *model, std::string *err, std::string *warn,
+                              const char *json_str,
+                              unsigned int json_str_length,
+                              const std::string &base_dir,
+                              unsigned int check_sections) {
+  if (json_str_length < 4) {
+    if (err) {
+      (*err) = "JSON string too short.\n";
+    }
+    return false;
+  }
+
+  JsonDocument v;
+
+#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || \
+     defined(_CPPUNWIND)) &&                               \
+    !defined(TINYGLTF_NOEXCEPTION)
+  try {
+    JsonParse(v, json_str, json_str_length, true);
+
+  } catch (const std::exception &e) {
+    if (err) {
+      (*err) = e.what();
+    }
+    return false;
+  }
+#else
+  {
+    JsonParse(v, json_str, json_str_length);
+
+    if (!IsObject(v)) {
+      // Assume parsing was failed.
+      if (err) {
+        (*err) = "Failed to parse JSON object\n";
+      }
+      return false;
+    }
+  }
+#endif
+
+  if (!IsObject(v)) {
+    // root is not an object.
+    if (err) {
+      (*err) = "Root element is not a JSON object\n";
+    }
+    return false;
+  }
+
+  {
+    bool version_found = false;
+    json_const_iterator it;
+    if (FindMember(v, "asset", it) && IsObject(GetValue(it))) {
+      auto &itObj = GetValue(it);
+      json_const_iterator version_it;
+      std::string versionStr;
+      if (FindMember(itObj, "version", version_it) &&
+          GetString(GetValue(version_it), versionStr)) {
+        version_found = true;
+      }
+    }
+    if (version_found) {
+      // OK
+    } else if (check_sections & REQUIRE_VERSION) {
+      if (err) {
+        (*err) += "\"asset\" object not found in .gltf or not an object type\n";
+      }
+      return false;
+    }
+  }
+
+  // scene is not mandatory.
+  // FIXME Maybe a better way to handle it than removing the code
+
+  auto IsArrayMemberPresent = [](const json &_v, const char *name) -> bool {
+    json_const_iterator it;
+    return FindMember(_v, name, it) && IsArray(GetValue(it));
+  };
+
+  {
+    if ((check_sections & REQUIRE_SCENES) &&
+        !IsArrayMemberPresent(v, "scenes")) {
+      if (err) {
+        (*err) += "\"scenes\" object not found in .gltf or not an array type\n";
+      }
+      return false;
+    }
+  }
+
+  {
+    if ((check_sections & REQUIRE_NODES) && !IsArrayMemberPresent(v, "nodes")) {
+      if (err) {
+        (*err) += "\"nodes\" object not found in .gltf\n";
+      }
+      return false;
+    }
+  }
+
+  {
+    if ((check_sections & REQUIRE_ACCESSORS) &&
+        !IsArrayMemberPresent(v, "accessors")) {
+      if (err) {
+        (*err) += "\"accessors\" object not found in .gltf\n";
+      }
+      return false;
+    }
+  }
+
+  {
+    if ((check_sections & REQUIRE_BUFFERS) &&
+        !IsArrayMemberPresent(v, "buffers")) {
+      if (err) {
+        (*err) += "\"buffers\" object not found in .gltf\n";
+      }
+      return false;
+    }
+  }
+
+  {
+    if ((check_sections & REQUIRE_BUFFER_VIEWS) &&
+        !IsArrayMemberPresent(v, "bufferViews")) {
+      if (err) {
+        (*err) += "\"bufferViews\" object not found in .gltf\n";
+      }
+      return false;
+    }
+  }
+
+  model->buffers.clear();
+  model->bufferViews.clear();
+  model->accessors.clear();
+  model->meshes.clear();
+  model->cameras.clear();
+  model->nodes.clear();
+  model->extensionsUsed.clear();
+  model->extensionsRequired.clear();
+  model->extensions.clear();
+  model->defaultScene = -1;
+
+  // 1. Parse Asset
+  {
+    json_const_iterator it;
+    if (FindMember(v, "asset", it) && IsObject(GetValue(it))) {
+      const json &root = GetValue(it);
+
+      ParseAsset(&model->asset, err, root,
+                 store_original_json_for_extras_and_extensions_);
+    }
+  }
+
+#ifdef TINYGLTF_USE_CPP14
+  auto ForEachInArray = [](const json &_v, const char *member,
+                           const auto &cb) -> bool
+#else
+  // The std::function<> implementation can be less efficient because it will
+  // allocate heap when the size of the captured lambda is above 16 bytes with
+  // clang and gcc, but it does not require C++14.
+  auto ForEachInArray = [](const json &_v, const char *member,
+                           const std::function<bool(const json &)> &cb) -> bool
+#endif
+  {
+    json_const_iterator itm;
+    if (FindMember(_v, member, itm) && IsArray(GetValue(itm))) {
+      const json &root = GetValue(itm);
+      auto it = ArrayBegin(root);
+      auto end = ArrayEnd(root);
+      for (; it != end; ++it) {
+        if (!cb(*it)) return false;
+      }
+    }
+    return true;
+  };
+
+  // 2. Parse extensionUsed
+  {
+    ForEachInArray(v, "extensionsUsed", [&](const json &o) {
+      std::string str;
+      GetString(o, str);
+      model->extensionsUsed.emplace_back(std::move(str));
+      return true;
+    });
+  }
+
+  {
+    ForEachInArray(v, "extensionsRequired", [&](const json &o) {
+      std::string str;
+      GetString(o, str);
+      model->extensionsRequired.emplace_back(std::move(str));
+      return true;
+    });
+  }
+
+  // 3. Parse Buffer
+  {
+    bool success = ForEachInArray(v, "buffers", [&](const json &o) {
+      if (!IsObject(o)) {
+        if (err) {
+          (*err) += "`buffers' does not contain an JSON object.";
+        }
+        return false;
+      }
+      Buffer buffer;
+      if (!ParseBuffer(&buffer, err, o,
+                       store_original_json_for_extras_and_extensions_, &fs,
+                       base_dir, is_binary_, bin_data_, bin_size_)) {
+        return false;
+      }
+
+      model->buffers.emplace_back(std::move(buffer));
+      return true;
+    });
+
+    if (!success) {
+      return false;
+    }
+  }
+  // 4. Parse BufferView
+  {
+    bool success = ForEachInArray(v, "bufferViews", [&](const json &o) {
+      if (!IsObject(o)) {
+        if (err) {
+          (*err) += "`bufferViews' does not contain an JSON object.";
+        }
+        return false;
+      }
+      BufferView bufferView;
+      if (!ParseBufferView(&bufferView, err, o,
+                           store_original_json_for_extras_and_extensions_)) {
+        return false;
+      }
+
+      model->bufferViews.emplace_back(std::move(bufferView));
+      return true;
+    });
+
+    if (!success) {
+      return false;
+    }
+  }
+
+  // 5. Parse Accessor
+  {
+    bool success = ForEachInArray(v, "accessors", [&](const json &o) {
+      if (!IsObject(o)) {
+        if (err) {
+          (*err) += "`accessors' does not contain an JSON object.";
+        }
+        return false;
+      }
+      Accessor accessor;
+      if (!ParseAccessor(&accessor, err, o,
+                         store_original_json_for_extras_and_extensions_)) {
+        return false;
+      }
+
+      model->accessors.emplace_back(std::move(accessor));
+      return true;
+    });
+
+    if (!success) {
+      return false;
+    }
+  }
+
+  // 6. Parse Mesh
+  {
+    bool success = ForEachInArray(v, "meshes", [&](const json &o) {
+      if (!IsObject(o)) {
+        if (err) {
+          (*err) += "`meshes' does not contain an JSON object.";
+        }
+        return false;
+      }
+      Mesh mesh;
+      if (!ParseMesh(&mesh, model, err, o,
+                     store_original_json_for_extras_and_extensions_)) {
+        return false;
+      }
+
+      model->meshes.emplace_back(std::move(mesh));
+      return true;
+    });
+
+    if (!success) {
+      return false;
+    }
+  }
+
+  // Assign missing bufferView target types
+  // - Look for missing Mesh indices
+  // - Look for missing Mesh attributes
+  for (auto &mesh : model->meshes) {
+    for (auto &primitive : mesh.primitives) {
+      if (primitive.indices >
+          -1)  // has indices from parsing step, must be Element Array Buffer
+      {
+        if (size_t(primitive.indices) >= model->accessors.size()) {
+          if (err) {
+            (*err) += "primitive indices accessor out of bounds";
+          }
+          return false;
+        }
+
+        auto bufferView =
+            model->accessors[size_t(primitive.indices)].bufferView;
+        if (bufferView < 0 || size_t(bufferView) >= model->bufferViews.size()) {
+          if (err) {
+            (*err) += "accessor[" + std::to_string(primitive.indices) +
+                      "] invalid bufferView";
+          }
+          return false;
+        }
+
+        model->bufferViews[size_t(bufferView)].target =
+            TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER;
+        // we could optionally check if acessors' bufferView type is Scalar, as
+        // it should be
+      }
+
+      for (auto &attribute : primitive.attributes) {
+        model
+            ->bufferViews[size_t(
+                model->accessors[size_t(attribute.second)].bufferView)]
+            .target = TINYGLTF_TARGET_ARRAY_BUFFER;
+      }
+
+      for(auto &target : primitive.targets) {
+        for(auto &attribute : target) {
+          model->bufferViews[size_t(model->accessors[size_t(attribute.second)].bufferView)]
+              .target = TINYGLTF_TARGET_ARRAY_BUFFER;
+        }
+      }
+    }
+  }
+
+  // 7. Parse Node
+  {
+    bool success = ForEachInArray(v, "nodes", [&](const json &o) {
+      if (!IsObject(o)) {
+        if (err) {
+          (*err) += "`nodes' does not contain an JSON object.";
+        }
+        return false;
+      }
+      Node node;
+      if (!ParseNode(&node, err, o,
+                     store_original_json_for_extras_and_extensions_)) {
+        return false;
+      }
+
+      model->nodes.emplace_back(std::move(node));
+      return true;
+    });
+
+    if (!success) {
+      return false;
+    }
+  }
+
+  // 8. Parse scenes.
+  {
+    bool success = ForEachInArray(v, "scenes", [&](const json &o) {
+      if (!IsObject(o)) {
+        if (err) {
+          (*err) += "`scenes' does not contain an JSON object.";
+        }
+        return false;
+      }
+      std::vector<int> nodes;
+      ParseIntegerArrayProperty(&nodes, err, o, "nodes", false);
+
+      Scene scene;
+      scene.nodes = std::move(nodes);
+
+      ParseStringProperty(&scene.name, err, o, "name", false);
+
+      ParseExtensionsProperty(&scene.extensions, err, o);
+      ParseExtrasProperty(&scene.extras, o);
+
+      if (store_original_json_for_extras_and_extensions_) {
+        {
+          json_const_iterator it;
+          if (FindMember(o, "extensions", it)) {
+            model->extensions_json_string = JsonToString(GetValue(it));
+          }
+        }
+        {
+          json_const_iterator it;
+          if (FindMember(o, "extras", it)) {
+            model->extras_json_string = JsonToString(GetValue(it));
+          }
+        }
+      }
+
+      model->scenes.emplace_back(std::move(scene));
+      return true;
+    });
+
+    if (!success) {
+      return false;
+    }
+  }
+
+  // 9. Parse default scenes.
+  {
+    json_const_iterator rootIt;
+    int iVal;
+    if (FindMember(v, "scene", rootIt) && GetInt(GetValue(rootIt), iVal)) {
+      model->defaultScene = iVal;
+    }
+  }
+
+  // 10. Parse Material
+  {
+    bool success = ForEachInArray(v, "materials", [&](const json &o) {
+      if (!IsObject(o)) {
+        if (err) {
+          (*err) += "`materials' does not contain an JSON object.";
+        }
+        return false;
+      }
+      Material material;
+      ParseStringProperty(&material.name, err, o, "name", false);
+
+      if (!ParseMaterial(&material, err, o,
+                         store_original_json_for_extras_and_extensions_)) {
+        return false;
+      }
+
+      model->materials.emplace_back(std::move(material));
+      return true;
+    });
+
+    if (!success) {
+      return false;
+    }
+  }
+
+  // 11. Parse Image
+  {
+    int idx = 0;
+    bool success = ForEachInArray(v, "images", [&](const json &o) {
+      if (!IsObject(o)) {
+        if (err) {
+          (*err) += "image[" + std::to_string(idx) + "] is not a JSON object.";
+        }
+        return false;
+      }
+      Image image;
+      if (!ParseImage(&image, idx, err, warn, o,
+                      store_original_json_for_extras_and_extensions_, base_dir,
+                      &fs, &this->LoadImageData, load_image_user_data_)) {
+        return false;
+      }
+
+      if (image.bufferView != -1) {
+        // Load image from the buffer view.
+        if (size_t(image.bufferView) >= model->bufferViews.size()) {
+          if (err) {
+            std::stringstream ss;
+            ss << "image[" << idx << "] bufferView \"" << image.bufferView
+               << "\" not found in the scene." << std::endl;
+            (*err) += ss.str();
+          }
+          return false;
+        }
+
+        const BufferView &bufferView =
+            model->bufferViews[size_t(image.bufferView)];
+        if (size_t(bufferView.buffer) >= model->buffers.size()) {
+          if (err) {
+            std::stringstream ss;
+            ss << "image[" << idx << "] buffer \"" << bufferView.buffer
+               << "\" not found in the scene." << std::endl;
+            (*err) += ss.str();
+          }
+          return false;
+        }
+        const Buffer &buffer = model->buffers[size_t(bufferView.buffer)];
+
+        if (*LoadImageData == nullptr) {
+          if (err) {
+            (*err) += "No LoadImageData callback specified.\n";
+          }
+          return false;
+        }
+        bool ret = LoadImageData(
+            &image, idx, err, warn, image.width, image.height,
+            &buffer.data[bufferView.byteOffset],
+            static_cast<int>(bufferView.byteLength), load_image_user_data_);
+        if (!ret) {
+          return false;
+        }
+      }
+
+      model->images.emplace_back(std::move(image));
+      ++idx;
+      return true;
+    });
+
+    if (!success) {
+      return false;
+    }
+  }
+
+  // 12. Parse Texture
+  {
+    bool success = ForEachInArray(v, "textures", [&](const json &o) {
+      if (!IsObject(o)) {
+        if (err) {
+          (*err) += "`textures' does not contain an JSON object.";
+        }
+        return false;
+      }
+      Texture texture;
+      if (!ParseTexture(&texture, err, o,
+                        store_original_json_for_extras_and_extensions_,
+                        base_dir)) {
+        return false;
+      }
+
+      model->textures.emplace_back(std::move(texture));
+      return true;
+    });
+
+    if (!success) {
+      return false;
+    }
+  }
+
+  // 13. Parse Animation
+  {
+    bool success = ForEachInArray(v, "animations", [&](const json &o) {
+      if (!IsObject(o)) {
+        if (err) {
+          (*err) += "`animations' does not contain an JSON object.";
+        }
+        return false;
+      }
+      Animation animation;
+      if (!ParseAnimation(&animation, err, o,
+                          store_original_json_for_extras_and_extensions_)) {
+        return false;
+      }
+
+      model->animations.emplace_back(std::move(animation));
+      return true;
+    });
+
+    if (!success) {
+      return false;
+    }
+  }
+
+  // 14. Parse Skin
+  {
+    bool success = ForEachInArray(v, "skins", [&](const json &o) {
+      if (!IsObject(o)) {
+        if (err) {
+          (*err) += "`skins' does not contain an JSON object.";
+        }
+        return false;
+      }
+      Skin skin;
+      if (!ParseSkin(&skin, err, o,
+                     store_original_json_for_extras_and_extensions_)) {
+        return false;
+      }
+
+      model->skins.emplace_back(std::move(skin));
+      return true;
+    });
+
+    if (!success) {
+      return false;
+    }
+  }
+
+  // 15. Parse Sampler
+  {
+    bool success = ForEachInArray(v, "samplers", [&](const json &o) {
+      if (!IsObject(o)) {
+        if (err) {
+          (*err) += "`samplers' does not contain an JSON object.";
+        }
+        return false;
+      }
+      Sampler sampler;
+      if (!ParseSampler(&sampler, err, o,
+                        store_original_json_for_extras_and_extensions_)) {
+        return false;
+      }
+
+      model->samplers.emplace_back(std::move(sampler));
+      return true;
+    });
+
+    if (!success) {
+      return false;
+    }
+  }
+
+  // 16. Parse Camera
+  {
+    bool success = ForEachInArray(v, "cameras", [&](const json &o) {
+      if (!IsObject(o)) {
+        if (err) {
+          (*err) += "`cameras' does not contain an JSON object.";
+        }
+        return false;
+      }
+      Camera camera;
+      if (!ParseCamera(&camera, err, o,
+                       store_original_json_for_extras_and_extensions_)) {
+        return false;
+      }
+
+      model->cameras.emplace_back(std::move(camera));
+      return true;
+    });
+
+    if (!success) {
+      return false;
+    }
+  }
+
+  // 17. Parse Extensions
+  ParseExtensionsProperty(&model->extensions, err, v);
+
+  // 18. Specific extension implementations
+  {
+    json_const_iterator rootIt;
+    if (FindMember(v, "extensions", rootIt) && IsObject(GetValue(rootIt))) {
+      const json &root = GetValue(rootIt);
+
+      json_const_iterator it(ObjectBegin(root));
+      json_const_iterator itEnd(ObjectEnd(root));
+      for (; it != itEnd; ++it) {
+        // parse KHR_lights_punctual extension
+        std::string key(GetKey(it));
+        if ((key == "KHR_lights_punctual") && IsObject(GetValue(it))) {
+          const json &object = GetValue(it);
+          json_const_iterator itLight;
+          if (FindMember(object, "lights", itLight)) {
+            const json &lights = GetValue(itLight);
+            if (!IsArray(lights)) {
+              continue;
+            }
+
+            auto arrayIt(ArrayBegin(lights));
+            auto arrayItEnd(ArrayEnd(lights));
+            for (; arrayIt != arrayItEnd; ++arrayIt) {
+              Light light;
+              if (!ParseLight(&light, err, *arrayIt,
+                              store_original_json_for_extras_and_extensions_)) {
+                return false;
+              }
+              model->lights.emplace_back(std::move(light));
+            }
+          }
+        }
+      }
+    }
+  }
+
+  // 19. Parse Extras
+  ParseExtrasProperty(&model->extras, v);
+
+  if (store_original_json_for_extras_and_extensions_) {
+    model->extras_json_string = JsonToString(v["extras"]);
+    model->extensions_json_string = JsonToString(v["extensions"]);
+  }
+
+  return true;
+}
+
+bool TinyGLTF::LoadASCIIFromString(Model *model, std::string *err,
+                                   std::string *warn, const char *str,
+                                   unsigned int length,
+                                   const std::string &base_dir,
+                                   unsigned int check_sections) {
+  is_binary_ = false;
+  bin_data_ = nullptr;
+  bin_size_ = 0;
+
+  return LoadFromString(model, err, warn, str, length, base_dir,
+                        check_sections);
+}
+
+bool TinyGLTF::LoadASCIIFromFile(Model *model, std::string *err,
+                                 std::string *warn, const std::string &filename,
+                                 unsigned int check_sections) {
+  std::stringstream ss;
+
+  if (fs.ReadWholeFile == nullptr) {
+    // Programmer error, assert() ?
+    ss << "Failed to read file: " << filename
+       << ": one or more FS callback not set" << std::endl;
+    if (err) {
+      (*err) = ss.str();
+    }
+    return false;
+  }
+
+  std::vector<unsigned char> data;
+  std::string fileerr;
+  bool fileread = fs.ReadWholeFile(&data, &fileerr, filename, fs.user_data);
+  if (!fileread) {
+    ss << "Failed to read file: " << filename << ": " << fileerr << std::endl;
+    if (err) {
+      (*err) = ss.str();
+    }
+    return false;
+  }
+
+  size_t sz = data.size();
+  if (sz == 0) {
+    if (err) {
+      (*err) = "Empty file.";
+    }
+    return false;
+  }
+
+  std::string basedir = GetBaseDir(filename);
+
+  bool ret = LoadASCIIFromString(
+      model, err, warn, reinterpret_cast<const char *>(&data.at(0)),
+      static_cast<unsigned int>(data.size()), basedir, check_sections);
+
+  return ret;
+}
+
+bool TinyGLTF::LoadBinaryFromMemory(Model *model, std::string *err,
+                                    std::string *warn,
+                                    const unsigned char *bytes,
+                                    unsigned int size,
+                                    const std::string &base_dir,
+                                    unsigned int check_sections) {
+  if (size < 20) {
+    if (err) {
+      (*err) = "Too short data size for glTF Binary.";
+    }
+    return false;
+  }
+
+  if (bytes[0] == 'g' && bytes[1] == 'l' && bytes[2] == 'T' &&
+      bytes[3] == 'F') {
+    // ok
+  } else {
+    if (err) {
+      (*err) = "Invalid magic.";
+    }
+    return false;
+  }
+
+  unsigned int version;       // 4 bytes
+  unsigned int length;        // 4 bytes
+  unsigned int model_length;  // 4 bytes
+  unsigned int model_format;  // 4 bytes;
+
+  // @todo { Endian swap for big endian machine. }
+  memcpy(&version, bytes + 4, 4);
+  swap4(&version);
+  memcpy(&length, bytes + 8, 4);
+  swap4(&length);
+  memcpy(&model_length, bytes + 12, 4);
+  swap4(&model_length);
+  memcpy(&model_format, bytes + 16, 4);
+  swap4(&model_format);
+
+  // In case the Bin buffer is not present, the size is exactly 20 + size of
+  // JSON contents,
+  // so use "greater than" operator.
+  if ((20 + model_length > size) || (model_length < 1) || (length > size) ||
+      (20 + model_length > length) ||
+      (model_format != 0x4E4F534A)) {  // 0x4E4F534A = JSON format.
+    if (err) {
+      (*err) = "Invalid glTF binary.";
+    }
+    return false;
+  }
+
+  // Extract JSON string.
+  std::string jsonString(reinterpret_cast<const char *>(&bytes[20]),
+                         model_length);
+
+  is_binary_ = true;
+  bin_data_ = bytes + 20 + model_length +
+              8;  // 4 bytes (buffer_length) + 4 bytes(buffer_format)
+  bin_size_ =
+      length - (20 + model_length);  // extract header + JSON scene data.
+
+  bool ret = LoadFromString(model, err, warn,
+                            reinterpret_cast<const char *>(&bytes[20]),
+                            model_length, base_dir, check_sections);
+  if (!ret) {
+    return ret;
+  }
+
+  return true;
+}
+
+bool TinyGLTF::LoadBinaryFromFile(Model *model, std::string *err,
+                                  std::string *warn,
+                                  const std::string &filename,
+                                  unsigned int check_sections) {
+  std::stringstream ss;
+
+  if (fs.ReadWholeFile == nullptr) {
+    // Programmer error, assert() ?
+    ss << "Failed to read file: " << filename
+       << ": one or more FS callback not set" << std::endl;
+    if (err) {
+      (*err) = ss.str();
+    }
+    return false;
+  }
+
+  std::vector<unsigned char> data;
+  std::string fileerr;
+  bool fileread = fs.ReadWholeFile(&data, &fileerr, filename, fs.user_data);
+  if (!fileread) {
+    ss << "Failed to read file: " << filename << ": " << fileerr << std::endl;
+    if (err) {
+      (*err) = ss.str();
+    }
+    return false;
+  }
+
+  std::string basedir = GetBaseDir(filename);
+
+  bool ret = LoadBinaryFromMemory(model, err, warn, &data.at(0),
+                                  static_cast<unsigned int>(data.size()),
+                                  basedir, check_sections);
+
+  return ret;
+}
+
+///////////////////////
+// GLTF Serialization
+///////////////////////
+namespace {
+json JsonFromString(const char *s) {
+#ifdef TINYGLTF_USE_RAPIDJSON
+  return json(s, GetAllocator());
+#else
+  return json(s);
+#endif
+}
+
+void JsonAssign(json &dest, const json &src) {
+#ifdef TINYGLTF_USE_RAPIDJSON
+  dest.CopyFrom(src, GetAllocator());
+#else
+  dest = src;
+#endif
+}
+
+void JsonAddMember(json &o, const char *key, json &&value) {
+#ifdef TINYGLTF_USE_RAPIDJSON
+  if (!o.IsObject()) {
+    o.SetObject();
+  }
+  o.AddMember(json(key, GetAllocator()), std::move(value), GetAllocator());
+#else
+  o[key] = std::move(value);
+#endif
+}
+
+void JsonPushBack(json &o, json &&value) {
+#ifdef TINYGLTF_USE_RAPIDJSON
+  o.PushBack(std::move(value), GetAllocator());
+#else
+  o.push_back(std::move(value));
+#endif
+}
+
+bool JsonIsNull(const json &o) {
+#ifdef TINYGLTF_USE_RAPIDJSON
+  return o.IsNull();
+#else
+  return o.is_null();
+#endif
+}
+
+void JsonSetObject(json &o) {
+#ifdef TINYGLTF_USE_RAPIDJSON
+  o.SetObject();
+#else
+  o = o.object({});
+#endif
+}
+
+void JsonReserveArray(json &o, size_t s) {
+#ifdef TINYGLTF_USE_RAPIDJSON
+  o.SetArray();
+  o.Reserve(static_cast<rapidjson::SizeType>(s), GetAllocator());
+#endif
+  (void)(o);
+  (void)(s);
+}
+}  // namespace
+
+// typedef std::pair<std::string, json> json_object_pair;
+
+template <typename T>
+static void SerializeNumberProperty(const std::string &key, T number,
+                                    json &obj) {
+  // obj.insert(
+  //    json_object_pair(key, json(static_cast<double>(number))));
+  // obj[key] = static_cast<double>(number);
+  JsonAddMember(obj, key.c_str(), json(number));
+}
+
+template <typename T>
+static void SerializeNumberArrayProperty(const std::string &key,
+                                         const std::vector<T> &value,
+                                         json &obj) {
+  if (value.empty()) return;
+
+  json ary;
+  JsonReserveArray(ary, value.size());
+  for (const auto &s : value) {
+    JsonPushBack(ary, json(s));
+  }
+  JsonAddMember(obj, key.c_str(), std::move(ary));
+}
+
+static void SerializeStringProperty(const std::string &key,
+                                    const std::string &value, json &obj) {
+  JsonAddMember(obj, key.c_str(), JsonFromString(value.c_str()));
+}
+
+static void SerializeStringArrayProperty(const std::string &key,
+                                         const std::vector<std::string> &value,
+                                         json &obj) {
+  json ary;
+  JsonReserveArray(ary, value.size());
+  for (auto &s : value) {
+    JsonPushBack(ary, JsonFromString(s.c_str()));
+  }
+  JsonAddMember(obj, key.c_str(), std::move(ary));
+}
+
+static bool ValueToJson(const Value &value, json *ret) {
+  json obj;
+#ifdef TINYGLTF_USE_RAPIDJSON
+  switch (value.Type()) {
+    case REAL_TYPE:
+      obj.SetDouble(value.Get<double>());
+      break;
+    case INT_TYPE:
+      obj.SetInt(value.Get<int>());
+      break;
+    case BOOL_TYPE:
+      obj.SetBool(value.Get<bool>());
+      break;
+    case STRING_TYPE:
+      obj.SetString(value.Get<std::string>().c_str(), GetAllocator());
+      break;
+    case ARRAY_TYPE: {
+      obj.SetArray();
+      obj.Reserve(static_cast<rapidjson::SizeType>(value.ArrayLen()),
+                  GetAllocator());
+      for (unsigned int i = 0; i < value.ArrayLen(); ++i) {
+        Value elementValue = value.Get(int(i));
+        json elementJson;
+        if (ValueToJson(value.Get(int(i)), &elementJson))
+          obj.PushBack(std::move(elementJson), GetAllocator());
+      }
+      break;
+    }
+    case BINARY_TYPE:
+      // TODO
+      // obj = json(value.Get<std::vector<unsigned char>>());
+      return false;
+      break;
+    case OBJECT_TYPE: {
+      obj.SetObject();
+      Value::Object objMap = value.Get<Value::Object>();
+      for (auto &it : objMap) {
+        json elementJson;
+        if (ValueToJson(it.second, &elementJson)) {
+          obj.AddMember(json(it.first.c_str(), GetAllocator()),
+                        std::move(elementJson), GetAllocator());
+        }
+      }
+      break;
+    }
+    case NULL_TYPE:
+    default:
+      return false;
+  }
+#else
+  switch (value.Type()) {
+    case REAL_TYPE:
+      obj = json(value.Get<double>());
+      break;
+    case INT_TYPE:
+      obj = json(value.Get<int>());
+      break;
+    case BOOL_TYPE:
+      obj = json(value.Get<bool>());
+      break;
+    case STRING_TYPE:
+      obj = json(value.Get<std::string>());
+      break;
+    case ARRAY_TYPE: {
+      for (unsigned int i = 0; i < value.ArrayLen(); ++i) {
+        Value elementValue = value.Get(int(i));
+        json elementJson;
+        if (ValueToJson(value.Get(int(i)), &elementJson))
+          obj.push_back(elementJson);
+      }
+      break;
+    }
+    case BINARY_TYPE:
+      // TODO
+      // obj = json(value.Get<std::vector<unsigned char>>());
+      return false;
+      break;
+    case OBJECT_TYPE: {
+      Value::Object objMap = value.Get<Value::Object>();
+      for (auto &it : objMap) {
+        json elementJson;
+        if (ValueToJson(it.second, &elementJson)) obj[it.first] = elementJson;
+      }
+      break;
+    }
+    case NULL_TYPE:
+    default:
+      return false;
+  }
+#endif
+  if (ret) *ret = std::move(obj);
+  return true;
+}
+
+static void SerializeValue(const std::string &key, const Value &value,
+                           json &obj) {
+  json ret;
+  if (ValueToJson(value, &ret)) {
+    JsonAddMember(obj, key.c_str(), std::move(ret));
+  }
+}
+
+static void SerializeGltfBufferData(const std::vector<unsigned char> &data,
+                                    json &o) {
+  std::string header = "data:application/octet-stream;base64,";
+  if (data.size() > 0) {
+    std::string encodedData =
+        base64_encode(&data[0], static_cast<unsigned int>(data.size()));
+    SerializeStringProperty("uri", header + encodedData, o);
+  } else {
+    // Issue #229
+    // size 0 is allowd. Just emit mime header.
+    SerializeStringProperty("uri", header, o);
+  }
+}
+
+static bool SerializeGltfBufferData(const std::vector<unsigned char> &data,
+                                    const std::string &binFilename) {
+#ifdef _WIN32
+#if defined(__GLIBCXX__)  // mingw
+  int file_descriptor =
+      _wopen(UTF8ToWchar(binFilename).c_str(), _O_CREAT | _O_WRONLY |
+                                               _O_TRUNC | _O_BINARY);
+  __gnu_cxx::stdio_filebuf<char> wfile_buf(file_descriptor,
+                                           std::ios_base::out |
+                                           std::ios_base::binary);
+  std::ostream output(&wfile_buf);
+  if (!wfile_buf.is_open()) return false;
+#elif defined(_MSC_VER)
+  std::ofstream output(UTF8ToWchar(binFilename).c_str(), std::ofstream::binary);
+  if (!output.is_open()) return false;
+#else
+  std::ofstream output(binFilename.c_str(), std::ofstream::binary);
+  if (!output.is_open()) return false;
+#endif
+#else
+  std::ofstream output(binFilename.c_str(), std::ofstream::binary);
+  if (!output.is_open()) return false;
+#endif
+  if (data.size() > 0) {
+    output.write(reinterpret_cast<const char *>(&data[0]),
+                 std::streamsize(data.size()));
+  } else {
+    // Issue #229
+    // size 0 will be still valid buffer data.
+    // write empty file.
+  }
+  return true;
+}
+
+#if 0  // FIXME(syoyo): not used. will be removed in the future release.
+static void SerializeParameterMap(ParameterMap &param, json &o) {
+  for (ParameterMap::iterator paramIt = param.begin(); paramIt != param.end();
+       ++paramIt) {
+    if (paramIt->second.number_array.size()) {
+      SerializeNumberArrayProperty<double>(paramIt->first,
+                                           paramIt->second.number_array, o);
+    } else if (paramIt->second.json_double_value.size()) {
+      json json_double_value;
+      for (std::map<std::string, double>::iterator it =
+               paramIt->second.json_double_value.begin();
+           it != paramIt->second.json_double_value.end(); ++it) {
+        if (it->first == "index") {
+          json_double_value[it->first] = paramIt->second.TextureIndex();
+        } else {
+          json_double_value[it->first] = it->second;
+        }
+      }
+
+      o[paramIt->first] = json_double_value;
+    } else if (!paramIt->second.string_value.empty()) {
+      SerializeStringProperty(paramIt->first, paramIt->second.string_value, o);
+    } else if (paramIt->second.has_number_value) {
+      o[paramIt->first] = paramIt->second.number_value;
+    } else {
+      o[paramIt->first] = paramIt->second.bool_value;
+    }
+  }
+}
+#endif
+
+static void SerializeExtensionMap(const ExtensionMap &extensions, json &o) {
+  if (!extensions.size()) return;
+
+  json extMap;
+  for (ExtensionMap::const_iterator extIt = extensions.begin();
+       extIt != extensions.end(); ++extIt) {
+    // Allow an empty object for extension(#97)
+    json ret;
+    bool isNull = true;
+    if (ValueToJson(extIt->second, &ret)) {
+      isNull = JsonIsNull(ret);
+      JsonAddMember(extMap, extIt->first.c_str(), std::move(ret));
+    }
+    if (isNull) {
+      if (!(extIt->first.empty())) {  // name should not be empty, but for sure
+        // create empty object so that an extension name is still included in
+        // json.
+        json empty;
+        JsonSetObject(empty);
+        JsonAddMember(extMap, extIt->first.c_str(), std::move(empty));
+      }
+    }
+  }
+  JsonAddMember(o, "extensions", std::move(extMap));
+}
+
+static void SerializeGltfAccessor(Accessor &accessor, json &o) {
+  if (accessor.bufferView >= 0)
+    SerializeNumberProperty<int>("bufferView", accessor.bufferView, o);
+
+  if (accessor.byteOffset != 0.0)
+    SerializeNumberProperty<int>("byteOffset", int(accessor.byteOffset), o);
+
+  SerializeNumberProperty<int>("componentType", accessor.componentType, o);
+  SerializeNumberProperty<size_t>("count", accessor.count, o);
+  SerializeNumberArrayProperty<double>("min", accessor.minValues, o);
+  SerializeNumberArrayProperty<double>("max", accessor.maxValues, o);
+  if (accessor.normalized)
+    SerializeValue("normalized", Value(accessor.normalized), o);
+  std::string type;
+  switch (accessor.type) {
+    case TINYGLTF_TYPE_SCALAR:
+      type = "SCALAR";
+      break;
+    case TINYGLTF_TYPE_VEC2:
+      type = "VEC2";
+      break;
+    case TINYGLTF_TYPE_VEC3:
+      type = "VEC3";
+      break;
+    case TINYGLTF_TYPE_VEC4:
+      type = "VEC4";
+      break;
+    case TINYGLTF_TYPE_MAT2:
+      type = "MAT2";
+      break;
+    case TINYGLTF_TYPE_MAT3:
+      type = "MAT3";
+      break;
+    case TINYGLTF_TYPE_MAT4:
+      type = "MAT4";
+      break;
+  }
+
+  SerializeStringProperty("type", type, o);
+  if (!accessor.name.empty()) SerializeStringProperty("name", accessor.name, o);
+
+  if (accessor.extras.Type() != NULL_TYPE) {
+    SerializeValue("extras", accessor.extras, o);
+  }
+}
+
+static void SerializeGltfAnimationChannel(AnimationChannel &channel, json &o) {
+  SerializeNumberProperty("sampler", channel.sampler, o);
+  {
+    json target;
+    SerializeNumberProperty("node", channel.target_node, target);
+    SerializeStringProperty("path", channel.target_path, target);
+
+	SerializeExtensionMap(channel.target_extensions, target);
+
+    JsonAddMember(o, "target", std::move(target));
+  }
+
+  if (channel.extras.Type() != NULL_TYPE) {
+    SerializeValue("extras", channel.extras, o);
+  }
+
+  SerializeExtensionMap(channel.extensions, o);
+}
+
+static void SerializeGltfAnimationSampler(AnimationSampler &sampler, json &o) {
+  SerializeNumberProperty("input", sampler.input, o);
+  SerializeNumberProperty("output", sampler.output, o);
+  SerializeStringProperty("interpolation", sampler.interpolation, o);
+
+  if (sampler.extras.Type() != NULL_TYPE) {
+    SerializeValue("extras", sampler.extras, o);
+  }
+}
+
+static void SerializeGltfAnimation(Animation &animation, json &o) {
+  if (!animation.name.empty())
+    SerializeStringProperty("name", animation.name, o);
+
+  {
+    json channels;
+    JsonReserveArray(channels, animation.channels.size());
+    for (unsigned int i = 0; i < animation.channels.size(); ++i) {
+      json channel;
+      AnimationChannel gltfChannel = animation.channels[i];
+      SerializeGltfAnimationChannel(gltfChannel, channel);
+      JsonPushBack(channels, std::move(channel));
+    }
+
+    JsonAddMember(o, "channels", std::move(channels));
+  }
+
+  {
+    json samplers;
+    JsonReserveArray(samplers, animation.samplers.size());
+    for (unsigned int i = 0; i < animation.samplers.size(); ++i) {
+      json sampler;
+      AnimationSampler gltfSampler = animation.samplers[i];
+      SerializeGltfAnimationSampler(gltfSampler, sampler);
+      JsonPushBack(samplers, std::move(sampler));
+    }
+    JsonAddMember(o, "samplers", std::move(samplers));
+  }
+
+  if (animation.extras.Type() != NULL_TYPE) {
+    SerializeValue("extras", animation.extras, o);
+  }
+
+  SerializeExtensionMap(animation.extensions, o);
+}
+
+static void SerializeGltfAsset(Asset &asset, json &o) {
+  if (!asset.generator.empty()) {
+    SerializeStringProperty("generator", asset.generator, o);
+  }
+
+  if (!asset.copyright.empty()) {
+    SerializeStringProperty("copyright", asset.copyright, o);
+  }
+
+  if (!asset.version.empty()) {
+    SerializeStringProperty("version", asset.version, o);
+  }
+
+  if (asset.extras.Keys().size()) {
+    SerializeValue("extras", asset.extras, o);
+  }
+
+  SerializeExtensionMap(asset.extensions, o);
+}
+
+static void SerializeGltfBufferBin(Buffer &buffer, json &o,
+                                   std::vector<unsigned char> &binBuffer) {
+  SerializeNumberProperty("byteLength", buffer.data.size(), o);
+  binBuffer = buffer.data;
+
+  if (buffer.name.size()) SerializeStringProperty("name", buffer.name, o);
+
+  if (buffer.extras.Type() != NULL_TYPE) {
+    SerializeValue("extras", buffer.extras, o);
+  }
+}
+
+static void SerializeGltfBuffer(Buffer &buffer, json &o) {
+  SerializeNumberProperty("byteLength", buffer.data.size(), o);
+  SerializeGltfBufferData(buffer.data, o);
+
+  if (buffer.name.size()) SerializeStringProperty("name", buffer.name, o);
+
+  if (buffer.extras.Type() != NULL_TYPE) {
+    SerializeValue("extras", buffer.extras, o);
+  }
+}
+
+static bool SerializeGltfBuffer(Buffer &buffer, json &o,
+                                const std::string &binFilename,
+                                const std::string &binBaseFilename) {
+  if (!SerializeGltfBufferData(buffer.data, binFilename)) return false;
+  SerializeNumberProperty("byteLength", buffer.data.size(), o);
+  SerializeStringProperty("uri", binBaseFilename, o);
+
+  if (buffer.name.size()) SerializeStringProperty("name", buffer.name, o);
+
+  if (buffer.extras.Type() != NULL_TYPE) {
+    SerializeValue("extras", buffer.extras, o);
+  }
+  return true;
+}
+
+static void SerializeGltfBufferView(BufferView &bufferView, json &o) {
+  SerializeNumberProperty("buffer", bufferView.buffer, o);
+  SerializeNumberProperty<size_t>("byteLength", bufferView.byteLength, o);
+
+  // byteStride is optional, minimum allowed is 4
+  if (bufferView.byteStride >= 4) {
+    SerializeNumberProperty<size_t>("byteStride", bufferView.byteStride, o);
+  }
+  // byteOffset is optional, default is 0
+  if (bufferView.byteOffset > 0) {
+    SerializeNumberProperty<size_t>("byteOffset", bufferView.byteOffset, o);
+  }
+  // Target is optional, check if it contains a valid value
+  if (bufferView.target == TINYGLTF_TARGET_ARRAY_BUFFER ||
+      bufferView.target == TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER) {
+    SerializeNumberProperty("target", bufferView.target, o);
+  }
+  if (bufferView.name.size()) {
+    SerializeStringProperty("name", bufferView.name, o);
+  }
+
+  if (bufferView.extras.Type() != NULL_TYPE) {
+    SerializeValue("extras", bufferView.extras, o);
+  }
+}
+
+static void SerializeGltfImage(Image &image, json &o) {
+  // if uri empty, the mimeType and bufferview should be set
+  if (image.uri.empty()) {
+    SerializeStringProperty("mimeType", image.mimeType, o);
+    SerializeNumberProperty<int>("bufferView", image.bufferView, o);
+  } else {
+    // TODO(syoyo): dlib::urilencode?
+    SerializeStringProperty("uri", image.uri, o);
+  }
+
+  if (image.name.size()) {
+    SerializeStringProperty("name", image.name, o);
+  }
+
+  if (image.extras.Type() != NULL_TYPE) {
+    SerializeValue("extras", image.extras, o);
+  }
+
+  SerializeExtensionMap(image.extensions, o);
+}
+
+static void SerializeGltfTextureInfo(TextureInfo &texinfo, json &o) {
+  SerializeNumberProperty("index", texinfo.index, o);
+
+  if (texinfo.texCoord != 0) {
+    SerializeNumberProperty("texCoord", texinfo.texCoord, o);
+  }
+
+  if (texinfo.extras.Type() != NULL_TYPE) {
+    SerializeValue("extras", texinfo.extras, o);
+  }
+
+  SerializeExtensionMap(texinfo.extensions, o);
+}
+
+static void SerializeGltfNormalTextureInfo(NormalTextureInfo &texinfo,
+                                           json &o) {
+  SerializeNumberProperty("index", texinfo.index, o);
+
+  if (texinfo.texCoord != 0) {
+    SerializeNumberProperty("texCoord", texinfo.texCoord, o);
+  }
+
+  if (!TINYGLTF_DOUBLE_EQUAL(texinfo.scale, 1.0)) {
+    SerializeNumberProperty("scale", texinfo.scale, o);
+  }
+
+  if (texinfo.extras.Type() != NULL_TYPE) {
+    SerializeValue("extras", texinfo.extras, o);
+  }
+
+  SerializeExtensionMap(texinfo.extensions, o);
+}
+
+static void SerializeGltfOcclusionTextureInfo(OcclusionTextureInfo &texinfo,
+                                              json &o) {
+  SerializeNumberProperty("index", texinfo.index, o);
+
+  if (texinfo.texCoord != 0) {
+    SerializeNumberProperty("texCoord", texinfo.texCoord, o);
+  }
+
+  if (!TINYGLTF_DOUBLE_EQUAL(texinfo.strength, 1.0)) {
+    SerializeNumberProperty("strength", texinfo.strength, o);
+  }
+
+  if (texinfo.extras.Type() != NULL_TYPE) {
+    SerializeValue("extras", texinfo.extras, o);
+  }
+
+  SerializeExtensionMap(texinfo.extensions, o);
+}
+
+static void SerializeGltfPbrMetallicRoughness(PbrMetallicRoughness &pbr,
+                                              json &o) {
+  std::vector<double> default_baseColorFactor = {1.0, 1.0, 1.0, 1.0};
+  if (!Equals(pbr.baseColorFactor, default_baseColorFactor)) {
+    SerializeNumberArrayProperty<double>("baseColorFactor", pbr.baseColorFactor,
+                                         o);
+  }
+
+  if (!TINYGLTF_DOUBLE_EQUAL(pbr.metallicFactor, 1.0)) {
+    SerializeNumberProperty("metallicFactor", pbr.metallicFactor, o);
+  }
+
+  if (!TINYGLTF_DOUBLE_EQUAL(pbr.roughnessFactor, 1.0)) {
+    SerializeNumberProperty("roughnessFactor", pbr.roughnessFactor, o);
+  }
+
+  if (pbr.baseColorTexture.index > -1) {
+    json texinfo;
+    SerializeGltfTextureInfo(pbr.baseColorTexture, texinfo);
+    JsonAddMember(o, "baseColorTexture", std::move(texinfo));
+  }
+
+  if (pbr.metallicRoughnessTexture.index > -1) {
+    json texinfo;
+    SerializeGltfTextureInfo(pbr.metallicRoughnessTexture, texinfo);
+    JsonAddMember(o, "metallicRoughnessTexture", std::move(texinfo));
+  }
+
+  SerializeExtensionMap(pbr.extensions, o);
+
+  if (pbr.extras.Type() != NULL_TYPE) {
+    SerializeValue("extras", pbr.extras, o);
+  }
+}
+
+static void SerializeGltfMaterial(Material &material, json &o) {
+  if (material.name.size()) {
+    SerializeStringProperty("name", material.name, o);
+  }
+
+  // QUESTION(syoyo): Write material parameters regardless of its default value?
+
+  if (!TINYGLTF_DOUBLE_EQUAL(material.alphaCutoff, 0.5)) {
+    SerializeNumberProperty("alphaCutoff", material.alphaCutoff, o);
+  }
+
+  if (material.alphaMode.compare("OPAQUE") != 0) {
+    SerializeStringProperty("alphaMode", material.alphaMode, o);
+  }
+
+  if (material.doubleSided != false)
+    JsonAddMember(o, "doubleSided", json(material.doubleSided));
+
+  if (material.normalTexture.index > -1) {
+    json texinfo;
+    SerializeGltfNormalTextureInfo(material.normalTexture, texinfo);
+    JsonAddMember(o, "normalTexture", std::move(texinfo));
+  }
+
+  if (material.occlusionTexture.index > -1) {
+    json texinfo;
+    SerializeGltfOcclusionTextureInfo(material.occlusionTexture, texinfo);
+    JsonAddMember(o, "occlusionTexture", std::move(texinfo));
+  }
+
+  if (material.emissiveTexture.index > -1) {
+    json texinfo;
+    SerializeGltfTextureInfo(material.emissiveTexture, texinfo);
+    JsonAddMember(o, "emissiveTexture", std::move(texinfo));
+  }
+
+  std::vector<double> default_emissiveFactor = {0.0, 0.0, 0.0};
+  if (!Equals(material.emissiveFactor, default_emissiveFactor)) {
+    SerializeNumberArrayProperty<double>("emissiveFactor",
+                                         material.emissiveFactor, o);
+  }
+
+  {
+    json pbrMetallicRoughness;
+    SerializeGltfPbrMetallicRoughness(material.pbrMetallicRoughness,
+                                      pbrMetallicRoughness);
+    // Issue 204
+    // Do not serialize `pbrMetallicRoughness` if pbrMetallicRoughness has all
+    // default values(json is null). Otherwise it will serialize to
+    // `pbrMetallicRoughness : null`, which cannot be read by other glTF
+    // importers(and validators).
+    //
+    if (!JsonIsNull(pbrMetallicRoughness)) {
+      JsonAddMember(o, "pbrMetallicRoughness", std::move(pbrMetallicRoughness));
+    }
+  }
+
+#if 0  // legacy way. just for the record.
+  if (material.values.size()) {
+    json pbrMetallicRoughness;
+    SerializeParameterMap(material.values, pbrMetallicRoughness);
+    JsonAddMember(o, "pbrMetallicRoughness", std::move(pbrMetallicRoughness));
+  }
+
+  SerializeParameterMap(material.additionalValues, o);
+#else
+
+#endif
+
+  SerializeExtensionMap(material.extensions, o);
+
+  if (material.extras.Type() != NULL_TYPE) {
+    SerializeValue("extras", material.extras, o);
+  }
+}
+
+static void SerializeGltfMesh(Mesh &mesh, json &o) {
+  json primitives;
+  JsonReserveArray(primitives, mesh.primitives.size());
+  for (unsigned int i = 0; i < mesh.primitives.size(); ++i) {
+    json primitive;
+    const Primitive &gltfPrimitive = mesh.primitives[i];  // don't make a copy
+    {
+      json attributes;
+      for (auto attrIt = gltfPrimitive.attributes.begin();
+           attrIt != gltfPrimitive.attributes.end(); ++attrIt) {
+        SerializeNumberProperty<int>(attrIt->first, attrIt->second, attributes);
+      }
+
+      JsonAddMember(primitive, "attributes", std::move(attributes));
+    }
+
+    // Indicies is optional
+    if (gltfPrimitive.indices > -1) {
+      SerializeNumberProperty<int>("indices", gltfPrimitive.indices, primitive);
+    }
+    // Material is optional
+    if (gltfPrimitive.material > -1) {
+      SerializeNumberProperty<int>("material", gltfPrimitive.material,
+                                   primitive);
+    }
+    SerializeNumberProperty<int>("mode", gltfPrimitive.mode, primitive);
+
+    // Morph targets
+    if (gltfPrimitive.targets.size()) {
+      json targets;
+      JsonReserveArray(targets, gltfPrimitive.targets.size());
+      for (unsigned int k = 0; k < gltfPrimitive.targets.size(); ++k) {
+        json targetAttributes;
+        std::map<std::string, int> targetData = gltfPrimitive.targets[k];
+        for (std::map<std::string, int>::iterator attrIt = targetData.begin();
+             attrIt != targetData.end(); ++attrIt) {
+          SerializeNumberProperty<int>(attrIt->first, attrIt->second,
+                                       targetAttributes);
+        }
+        JsonPushBack(targets, std::move(targetAttributes));
+      }
+      JsonAddMember(primitive, "targets", std::move(targets));
+    }
+
+    SerializeExtensionMap(gltfPrimitive.extensions, primitive);
+
+    if (gltfPrimitive.extras.Type() != NULL_TYPE) {
+      SerializeValue("extras", gltfPrimitive.extras, primitive);
+    }
+
+    JsonPushBack(primitives, std::move(primitive));
+  }
+
+  JsonAddMember(o, "primitives", std::move(primitives));
+
+  if (mesh.weights.size()) {
+    SerializeNumberArrayProperty<double>("weights", mesh.weights, o);
+  }
+
+  if (mesh.name.size()) {
+    SerializeStringProperty("name", mesh.name, o);
+  }
+
+  SerializeExtensionMap(mesh.extensions, o);
+  if (mesh.extras.Type() != NULL_TYPE) {
+    SerializeValue("extras", mesh.extras, o);
+  }
+}
+
+static void SerializeSpotLight(SpotLight &spot, json &o) {
+  SerializeNumberProperty("innerConeAngle", spot.innerConeAngle, o);
+  SerializeNumberProperty("outerConeAngle", spot.outerConeAngle, o);
+  SerializeExtensionMap(spot.extensions, o);
+  if (spot.extras.Type() != NULL_TYPE) {
+    SerializeValue("extras", spot.extras, o);
+  }
+}
+
+static void SerializeGltfLight(Light &light, json &o) {
+  if (!light.name.empty()) SerializeStringProperty("name", light.name, o);
+  SerializeNumberProperty("intensity", light.intensity, o);
+  SerializeNumberProperty("range", light.range, o);
+  SerializeNumberArrayProperty("color", light.color, o);
+  SerializeStringProperty("type", light.type, o);
+  if (light.type == "spot") {
+    json spot;
+    SerializeSpotLight(light.spot, spot);
+    JsonAddMember(o, "spot", std::move(spot));
+  }
+  SerializeExtensionMap(light.extensions, o);
+  if (light.extras.Type() != NULL_TYPE) {
+    SerializeValue("extras", light.extras, o);
+  }
+}
+
+static void SerializeGltfNode(Node &node, json &o) {
+  if (node.translation.size() > 0) {
+    SerializeNumberArrayProperty<double>("translation", node.translation, o);
+  }
+  if (node.rotation.size() > 0) {
+    SerializeNumberArrayProperty<double>("rotation", node.rotation, o);
+  }
+  if (node.scale.size() > 0) {
+    SerializeNumberArrayProperty<double>("scale", node.scale, o);
+  }
+  if (node.matrix.size() > 0) {
+    SerializeNumberArrayProperty<double>("matrix", node.matrix, o);
+  }
+  if (node.mesh != -1) {
+    SerializeNumberProperty<int>("mesh", node.mesh, o);
+  }
+
+  if (node.skin != -1) {
+    SerializeNumberProperty<int>("skin", node.skin, o);
+  }
+
+  if (node.camera != -1) {
+    SerializeNumberProperty<int>("camera", node.camera, o);
+  }
+
+  if (node.weights.size() > 0) {
+    SerializeNumberArrayProperty<double>("weights", node.weights, o);
+  }
+
+  if (node.extras.Type() != NULL_TYPE) {
+    SerializeValue("extras", node.extras, o);
+  }
+
+  SerializeExtensionMap(node.extensions, o);
+  if (!node.name.empty()) SerializeStringProperty("name", node.name, o);
+  SerializeNumberArrayProperty<int>("children", node.children, o);
+}
+
+static void SerializeGltfSampler(Sampler &sampler, json &o) {
+  if (sampler.magFilter != -1) {
+    SerializeNumberProperty("magFilter", sampler.magFilter, o);
+  }
+  if (sampler.minFilter != -1) {
+    SerializeNumberProperty("minFilter", sampler.minFilter, o);
+  }
+  SerializeNumberProperty("wrapR", sampler.wrapR, o);
+  SerializeNumberProperty("wrapS", sampler.wrapS, o);
+  SerializeNumberProperty("wrapT", sampler.wrapT, o);
+
+  if (sampler.extras.Type() != NULL_TYPE) {
+    SerializeValue("extras", sampler.extras, o);
+  }
+}
+
+static void SerializeGltfOrthographicCamera(const OrthographicCamera &camera,
+                                            json &o) {
+  SerializeNumberProperty("zfar", camera.zfar, o);
+  SerializeNumberProperty("znear", camera.znear, o);
+  SerializeNumberProperty("xmag", camera.xmag, o);
+  SerializeNumberProperty("ymag", camera.ymag, o);
+
+  if (camera.extras.Type() != NULL_TYPE) {
+    SerializeValue("extras", camera.extras, o);
+  }
+}
+
+static void SerializeGltfPerspectiveCamera(const PerspectiveCamera &camera,
+                                           json &o) {
+  SerializeNumberProperty("zfar", camera.zfar, o);
+  SerializeNumberProperty("znear", camera.znear, o);
+  if (camera.aspectRatio > 0) {
+    SerializeNumberProperty("aspectRatio", camera.aspectRatio, o);
+  }
+
+  if (camera.yfov > 0) {
+    SerializeNumberProperty("yfov", camera.yfov, o);
+  }
+
+  if (camera.extras.Type() != NULL_TYPE) {
+    SerializeValue("extras", camera.extras, o);
+  }
+}
+
+static void SerializeGltfCamera(const Camera &camera, json &o) {
+  SerializeStringProperty("type", camera.type, o);
+  if (!camera.name.empty()) {
+    SerializeStringProperty("name", camera.name, o);
+  }
+
+  if (camera.type.compare("orthographic") == 0) {
+    json orthographic;
+    SerializeGltfOrthographicCamera(camera.orthographic, orthographic);
+    JsonAddMember(o, "orthographic", std::move(orthographic));
+  } else if (camera.type.compare("perspective") == 0) {
+    json perspective;
+    SerializeGltfPerspectiveCamera(camera.perspective, perspective);
+    JsonAddMember(o, "perspective", std::move(perspective));
+  } else {
+    // ???
+  }
+}
+
+static void SerializeGltfScene(Scene &scene, json &o) {
+  SerializeNumberArrayProperty<int>("nodes", scene.nodes, o);
+
+  if (scene.name.size()) {
+    SerializeStringProperty("name", scene.name, o);
+  }
+  if (scene.extras.Type() != NULL_TYPE) {
+    SerializeValue("extras", scene.extras, o);
+  }
+  SerializeExtensionMap(scene.extensions, o);
+}
+
+static void SerializeGltfSkin(Skin &skin, json &o) {
+  if (skin.inverseBindMatrices != -1)
+    SerializeNumberProperty("inverseBindMatrices", skin.inverseBindMatrices, o);
+
+  SerializeNumberArrayProperty<int>("joints", skin.joints, o);
+  SerializeNumberProperty("skeleton", skin.skeleton, o);
+  if (skin.name.size()) {
+    SerializeStringProperty("name", skin.name, o);
+  }
+}
+
+static void SerializeGltfTexture(Texture &texture, json &o) {
+  if (texture.sampler > -1) {
+    SerializeNumberProperty("sampler", texture.sampler, o);
+  }
+  if (texture.source > -1) {
+    SerializeNumberProperty("source", texture.source, o);
+  }
+  if (texture.name.size()) {
+    SerializeStringProperty("name", texture.name, o);
+  }
+  if (texture.extras.Type() != NULL_TYPE) {
+    SerializeValue("extras", texture.extras, o);
+  }
+  SerializeExtensionMap(texture.extensions, o);
+}
+
+///
+/// Serialize all properties except buffers and images.
+///
+static void SerializeGltfModel(Model *model, json &o) {
+  // ACCESSORS
+  if (model->accessors.size()) {
+    json accessors;
+    JsonReserveArray(accessors, model->accessors.size());
+    for (unsigned int i = 0; i < model->accessors.size(); ++i) {
+      json accessor;
+      SerializeGltfAccessor(model->accessors[i], accessor);
+      JsonPushBack(accessors, std::move(accessor));
+    }
+    JsonAddMember(o, "accessors", std::move(accessors));
+  }
+
+  // ANIMATIONS
+  if (model->animations.size()) {
+    json animations;
+    JsonReserveArray(animations, model->animations.size());
+    for (unsigned int i = 0; i < model->animations.size(); ++i) {
+      if (model->animations[i].channels.size()) {
+        json animation;
+        SerializeGltfAnimation(model->animations[i], animation);
+        JsonPushBack(animations, std::move(animation));
+      }
+    }
+
+    JsonAddMember(o, "animations", std::move(animations));
+  }
+
+  // ASSET
+  json asset;
+  SerializeGltfAsset(model->asset, asset);
+  JsonAddMember(o, "asset", std::move(asset));
+
+  // BUFFERVIEWS
+  if(model->bufferViews.size()) {
+    json bufferViews;
+    JsonReserveArray(bufferViews, model->bufferViews.size());
+    for (unsigned int i = 0; i < model->bufferViews.size(); ++i) {
+      json bufferView;
+      SerializeGltfBufferView(model->bufferViews[i], bufferView);
+      JsonPushBack(bufferViews, std::move(bufferView));
+    }
+    JsonAddMember(o, "bufferViews", std::move(bufferViews));
+  }
+
+  // Extensions used
+  if (model->extensionsUsed.size()) {
+    SerializeStringArrayProperty("extensionsUsed", model->extensionsUsed, o);
+  }
+
+  // Extensions required
+  if (model->extensionsRequired.size()) {
+    SerializeStringArrayProperty("extensionsRequired",
+                                 model->extensionsRequired, o);
+  }
+
+  // MATERIALS
+  if (model->materials.size()) {
+    json materials;
+    JsonReserveArray(materials, model->materials.size());
+    for (unsigned int i = 0; i < model->materials.size(); ++i) {
+      json material;
+      SerializeGltfMaterial(model->materials[i], material);
+      JsonPushBack(materials, std::move(material));
+    }
+    JsonAddMember(o, "materials", std::move(materials));
+  }
+
+  // MESHES
+  if (model->meshes.size()) {
+    json meshes;
+    JsonReserveArray(meshes, model->meshes.size());
+    for (unsigned int i = 0; i < model->meshes.size(); ++i) {
+      json mesh;
+      SerializeGltfMesh(model->meshes[i], mesh);
+      JsonPushBack(meshes, std::move(mesh));
+    }
+    JsonAddMember(o, "meshes", std::move(meshes));
+  }
+
+  // NODES
+  if (model->nodes.size()) {
+    json nodes;
+    JsonReserveArray(nodes, model->nodes.size());
+    for (unsigned int i = 0; i < model->nodes.size(); ++i) {
+      json node;
+      SerializeGltfNode(model->nodes[i], node);
+      JsonPushBack(nodes, std::move(node));
+    }
+    JsonAddMember(o, "nodes", std::move(nodes));
+  }
+
+  // SCENE
+  if (model->defaultScene > -1) {
+    SerializeNumberProperty<int>("scene", model->defaultScene, o);
+  }
+
+  // SCENES
+  if (model->scenes.size()) {
+    json scenes;
+    JsonReserveArray(scenes, model->scenes.size());
+    for (unsigned int i = 0; i < model->scenes.size(); ++i) {
+      json currentScene;
+      SerializeGltfScene(model->scenes[i], currentScene);
+      JsonPushBack(scenes, std::move(currentScene));
+    }
+    JsonAddMember(o, "scenes", std::move(scenes));
+  }
+
+  // SKINS
+  if (model->skins.size()) {
+    json skins;
+    JsonReserveArray(skins, model->skins.size());
+    for (unsigned int i = 0; i < model->skins.size(); ++i) {
+      json skin;
+      SerializeGltfSkin(model->skins[i], skin);
+      JsonPushBack(skins, std::move(skin));
+    }
+    JsonAddMember(o, "skins", std::move(skins));
+  }
+
+  // TEXTURES
+  if (model->textures.size()) {
+    json textures;
+    JsonReserveArray(textures, model->textures.size());
+    for (unsigned int i = 0; i < model->textures.size(); ++i) {
+      json texture;
+      SerializeGltfTexture(model->textures[i], texture);
+      JsonPushBack(textures, std::move(texture));
+    }
+    JsonAddMember(o, "textures", std::move(textures));
+  }
+
+  // SAMPLERS
+  if (model->samplers.size()) {
+    json samplers;
+    JsonReserveArray(samplers, model->samplers.size());
+    for (unsigned int i = 0; i < model->samplers.size(); ++i) {
+      json sampler;
+      SerializeGltfSampler(model->samplers[i], sampler);
+      JsonPushBack(samplers, std::move(sampler));
+    }
+    JsonAddMember(o, "samplers", std::move(samplers));
+  }
+
+  // CAMERAS
+  if (model->cameras.size()) {
+    json cameras;
+    JsonReserveArray(cameras, model->cameras.size());
+    for (unsigned int i = 0; i < model->cameras.size(); ++i) {
+      json camera;
+      SerializeGltfCamera(model->cameras[i], camera);
+      JsonPushBack(cameras, std::move(camera));
+    }
+    JsonAddMember(o, "cameras", std::move(cameras));
+  }
+
+  // EXTENSIONS
+  SerializeExtensionMap(model->extensions, o);
+
+  // LIGHTS as KHR_lights_cmn
+  if (model->lights.size()) {
+    json lights;
+    JsonReserveArray(lights, model->lights.size());
+    for (unsigned int i = 0; i < model->lights.size(); ++i) {
+      json light;
+      SerializeGltfLight(model->lights[i], light);
+      JsonPushBack(lights, std::move(light));
+    }
+    json khr_lights_cmn;
+    JsonAddMember(khr_lights_cmn, "lights", std::move(lights));
+    json ext_j;
+
+    {
+      json_const_iterator it;
+      if (!FindMember(o, "extensions", it)) {
+        JsonAssign(ext_j, GetValue(it));
+      }
+    }
+
+    JsonAddMember(ext_j, "KHR_lights_punctual", std::move(khr_lights_cmn));
+
+    JsonAddMember(o, "extensions", std::move(ext_j));
+  }
+
+  // EXTRAS
+  if (model->extras.Type() != NULL_TYPE) {
+    SerializeValue("extras", model->extras, o);
+  }
+}
+
+static bool WriteGltfStream(std::ostream &stream, const std::string &content) {
+  stream << content << std::endl;
+  return true;
+}
+
+static bool WriteGltfFile(const std::string &output,
+                          const std::string &content) {
+#ifdef _WIN32
+#if defined(_MSC_VER)
+  std::ofstream gltfFile(UTF8ToWchar(output).c_str());
+#elif defined(__GLIBCXX__)
+  int file_descriptor =
+      _wopen(UTF8ToWchar(output).c_str(), _O_CREAT | _O_WRONLY |
+                                          _O_TRUNC | _O_BINARY);
+  __gnu_cxx::stdio_filebuf<char> wfile_buf(file_descriptor,
+                                           std::ios_base::out |
+                                           std::ios_base::binary);
+  std::ostream gltfFile(&wfile_buf);
+  if (!wfile_buf.is_open()) return false;
+#else
+  std::ofstream gltfFile(output.c_str());
+  if (!gltfFile.is_open()) return false;
+#endif
+#else
+  std::ofstream gltfFile(output.c_str());
+  if (!gltfFile.is_open()) return false;
+#endif
+  return WriteGltfStream(gltfFile, content);
+}
+
+static void WriteBinaryGltfStream(std::ostream &stream,
+                                  const std::string &content,
+                                  const std::vector<unsigned char> &binBuffer) {
+  const std::string header = "glTF";
+  const int version = 2;
+
+  // https://stackoverflow.com/questions/3407012/c-rounding-up-to-the-nearest-multiple-of-a-number
+  auto roundUp = [](uint32_t numToRound, uint32_t multiple) {
+    if (multiple == 0) return numToRound;
+
+    uint32_t remainder = numToRound % multiple;
+    if (remainder == 0) return numToRound;
+
+    return numToRound + multiple - remainder;
+  };
+
+  const uint32_t padding_size =
+      roundUp(uint32_t(content.size()), 4) - uint32_t(content.size());
+
+  // 12 bytes for header, JSON content length, 8 bytes for JSON chunk info.
+  // Chunk data must be located at 4-byte boundary.
+  const uint32_t length =
+      12 + 8 + roundUp(uint32_t(content.size()), 4) +
+      (binBuffer.size() ? (8 + roundUp(uint32_t(binBuffer.size()), 4)) : 0);
+
+  stream.write(header.c_str(), std::streamsize(header.size()));
+  stream.write(reinterpret_cast<const char *>(&version), sizeof(version));
+  stream.write(reinterpret_cast<const char *>(&length), sizeof(length));
+
+  // JSON chunk info, then JSON data
+  const uint32_t model_length = uint32_t(content.size()) + padding_size;
+  const uint32_t model_format = 0x4E4F534A;
+  stream.write(reinterpret_cast<const char *>(&model_length),
+               sizeof(model_length));
+  stream.write(reinterpret_cast<const char *>(&model_format),
+               sizeof(model_format));
+  stream.write(content.c_str(), std::streamsize(content.size()));
+
+  // Chunk must be multiplies of 4, so pad with spaces
+  if (padding_size > 0) {
+    const std::string padding = std::string(size_t(padding_size), ' ');
+    stream.write(padding.c_str(), std::streamsize(padding.size()));
+  }
+  if (binBuffer.size() > 0) {
+    const uint32_t bin_padding_size =
+        roundUp(uint32_t(binBuffer.size()), 4) - uint32_t(binBuffer.size());
+    // BIN chunk info, then BIN data
+    const uint32_t bin_length = uint32_t(binBuffer.size()) + bin_padding_size;
+    const uint32_t bin_format = 0x004e4942;
+    stream.write(reinterpret_cast<const char *>(&bin_length),
+                 sizeof(bin_length));
+    stream.write(reinterpret_cast<const char *>(&bin_format),
+                 sizeof(bin_format));
+    stream.write(reinterpret_cast<const char *>(binBuffer.data()),
+                 std::streamsize(binBuffer.size()));
+    // Chunksize must be multiplies of 4, so pad with zeroes
+    if (bin_padding_size > 0) {
+      const std::vector<unsigned char> padding =
+          std::vector<unsigned char>(size_t(bin_padding_size), 0);
+      stream.write(reinterpret_cast<const char *>(padding.data()),
+                   std::streamsize(padding.size()));
+    }
+  }
+}
+
+static void WriteBinaryGltfFile(const std::string &output,
+                                const std::string &content,
+                                const std::vector<unsigned char> &binBuffer) {
+#ifdef _WIN32
+#if defined(_MSC_VER)
+  std::ofstream gltfFile(UTF8ToWchar(output).c_str(), std::ios::binary);
+#elif defined(__GLIBCXX__)
+  int file_descriptor =
+      _wopen(UTF8ToWchar(output).c_str(), _O_CREAT | _O_WRONLY |
+                                          _O_TRUNC | _O_BINARY);
+  __gnu_cxx::stdio_filebuf<char> wfile_buf(file_descriptor,
+                                           std::ios_base::out |
+                                           std::ios_base::binary);
+  std::ostream gltfFile(&wfile_buf);
+#else
+  std::ofstream gltfFile(output.c_str(), std::ios::binary);
+#endif
+#else
+  std::ofstream gltfFile(output.c_str(), std::ios::binary);
+#endif
+  WriteBinaryGltfStream(gltfFile, content, binBuffer);
+}
+
+bool TinyGLTF::WriteGltfSceneToStream(Model *model, std::ostream &stream,
+                                      bool prettyPrint = true,
+                                      bool writeBinary = false) {
+  JsonDocument output;
+
+  /// Serialize all properties except buffers and images.
+  SerializeGltfModel(model, output);
+
+  // BUFFERS
+  std::vector<unsigned char> binBuffer;
+  if(model->buffers.size()) {
+    json buffers;
+    JsonReserveArray(buffers, model->buffers.size());
+    for (unsigned int i = 0; i < model->buffers.size(); ++i) {
+      json buffer;
+      if (writeBinary && i==0 && model->buffers[i].uri.empty()){
+        SerializeGltfBufferBin(model->buffers[i], buffer,binBuffer);
+      } else {
+        SerializeGltfBuffer(model->buffers[i], buffer);
+      }
+      JsonPushBack(buffers, std::move(buffer));
+    }
+    JsonAddMember(output, "buffers", std::move(buffers));
+  }
+
+  // IMAGES
+  if (model->images.size()) {
+    json images;
+    JsonReserveArray(images, model->images.size());
+    for (unsigned int i = 0; i < model->images.size(); ++i) {
+      json image;
+
+      std::string dummystring = "";
+	  // UpdateImageObject need baseDir but only uses it if embeddedImages is
+	  // enabled, since we won't write separate images when writing to a stream we
+	  UpdateImageObject(model->images[i], dummystring, int(i), false,
+		  &this->WriteImageData, this->write_image_user_data_);
+      SerializeGltfImage(model->images[i], image);
+      JsonPushBack(images, std::move(image));
+    }
+    JsonAddMember(output, "images", std::move(images));
+  }
+
+  if (writeBinary) {
+    WriteBinaryGltfStream(stream, JsonToString(output), binBuffer);
+  } else {
+    WriteGltfStream(stream, JsonToString(output, prettyPrint ? 2 : -1));
+  }
+
+  return true;
+}
+
+bool TinyGLTF::WriteGltfSceneToFile(Model *model, const std::string &filename,
+                                    bool embedImages = false,
+                                    bool embedBuffers = false,
+                                    bool prettyPrint = true,
+                                    bool writeBinary = false) {
+  JsonDocument output;
+  std::string defaultBinFilename = GetBaseFilename(filename);
+  std::string defaultBinFileExt = ".bin";
+  std::string::size_type pos =
+      defaultBinFilename.rfind('.', defaultBinFilename.length());
+
+  if (pos != std::string::npos) {
+    defaultBinFilename = defaultBinFilename.substr(0, pos);
+  }
+  std::string baseDir = GetBaseDir(filename);
+  if (baseDir.empty()) {
+    baseDir = "./";
+  }
+  /// Serialize all properties except buffers and images.
+  SerializeGltfModel(model, output);
+
+  // BUFFERS
+  std::vector<std::string> usedUris;
+  std::vector<unsigned char> binBuffer;
+  if (model->buffers.size()) {
+    json buffers;
+    JsonReserveArray(buffers, model->buffers.size());
+    for (unsigned int i = 0; i < model->buffers.size(); ++i) {
+      json buffer;
+      if (writeBinary && i==0 && model->buffers[i].uri.empty()){
+        SerializeGltfBufferBin(model->buffers[i], buffer,binBuffer);
+      } else if (embedBuffers) {
+        SerializeGltfBuffer(model->buffers[i], buffer);
+      } else {
+        std::string binSavePath;
+        std::string binUri;
+        if (!model->buffers[i].uri.empty() && !IsDataURI(model->buffers[i].uri)) {
+          binUri = model->buffers[i].uri;
+        } else {
+          binUri = defaultBinFilename + defaultBinFileExt;
+          bool inUse = true;
+          int numUsed = 0;
+          while (inUse) {
+            inUse = false;
+            for (const std::string &usedName : usedUris) {
+              if (binUri.compare(usedName) != 0) continue;
+              inUse = true;
+              binUri = defaultBinFilename + std::to_string(numUsed++) +
+                       defaultBinFileExt;
+              break;
+            }
+          }
+        }
+        usedUris.push_back(binUri);
+        binSavePath = JoinPath(baseDir, binUri);
+        if (!SerializeGltfBuffer(model->buffers[i], buffer, binSavePath,
+                                 binUri)) {
+          return false;
+        }
+      }
+      JsonPushBack(buffers, std::move(buffer));
+    }
+  JsonAddMember(output, "buffers", std::move(buffers));
+  }
+
+  // IMAGES
+  if (model->images.size()) {
+    json images;
+    JsonReserveArray(images, model->images.size());
+    for (unsigned int i = 0; i < model->images.size(); ++i) {
+      json image;
+
+      UpdateImageObject(model->images[i], baseDir, int(i), embedImages,
+                        &this->WriteImageData, this->write_image_user_data_);
+      SerializeGltfImage(model->images[i], image);
+      JsonPushBack(images, std::move(image));
+    }
+    JsonAddMember(output, "images", std::move(images));
+  }
+
+  if (writeBinary) {
+    WriteBinaryGltfFile(filename, JsonToString(output), binBuffer);
+  } else {
+    WriteGltfFile(filename, JsonToString(output, (prettyPrint ? 2 : -1)));
+  }
+
+  return true;
+}
+
+}  // namespace tinygltf
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+#endif  // TINYGLTF_IMPLEMENTATION
diff --git a/external/Vulkan/external/vk_video/vulkan_video_codec_h264std.h b/external/Vulkan/external/vk_video/vulkan_video_codec_h264std.h
new file mode 100644
index 0000000000000000000000000000000000000000..3338fe14219e667d4d1be789ba8c38df9ca539e7
--- /dev/null
+++ b/external/Vulkan/external/vk_video/vulkan_video_codec_h264std.h
@@ -0,0 +1,312 @@
+/*
+** Copyright (c) 2019-2021 The Khronos Group Inc.
+**
+** SPDX-License-Identifier: Apache-2.0
+*/
+
+#ifndef VULKAN_VIDEO_CODEC_H264STD_H_
+#define VULKAN_VIDEO_CODEC_H264STD_H_ 1
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "vk_video/vulkan_video_codecs_common.h"
+
+// Vulkan 0.9 provisional Vulkan video H.264 encode and decode std specification version number
+#define VK_STD_VULKAN_VIDEO_CODEC_H264_API_VERSION_0_9_5 VK_MAKE_VIDEO_STD_VERSION(0, 9, 5) // Patch version should always be set to 0
+
+// Format must be in the form XX.XX where the first two digits are the major and the second two, the minor.
+#define VK_STD_VULKAN_VIDEO_CODEC_H264_SPEC_VERSION   VK_STD_VULKAN_VIDEO_CODEC_H264_API_VERSION_0_9_5
+#define VK_STD_VULKAN_VIDEO_CODEC_H264_EXTENSION_NAME "VK_STD_vulkan_video_codec_h264"
+
+// *************************************************
+// Video H.264 common definitions:
+// *************************************************
+
+#define STD_VIDEO_H264_CPB_CNT_LIST_SIZE 32
+#define STD_VIDEO_H264_SCALING_LIST_4X4_NUM_LISTS 6
+#define STD_VIDEO_H264_SCALING_LIST_4X4_NUM_ELEMENTS 16
+#define STD_VIDEO_H264_SCALING_LIST_8X8_NUM_LISTS 2
+#define STD_VIDEO_H264_SCALING_LIST_8X8_NUM_ELEMENTS 64
+
+typedef enum StdVideoH264ChromaFormatIdc {
+    STD_VIDEO_H264_CHROMA_FORMAT_IDC_MONOCHROME  = 0,
+    STD_VIDEO_H264_CHROMA_FORMAT_IDC_420         = 1,
+    STD_VIDEO_H264_CHROMA_FORMAT_IDC_422         = 2,
+    STD_VIDEO_H264_CHROMA_FORMAT_IDC_444         = 3,
+    STD_VIDEO_H264_CHROMA_FORMAT_IDC_INVALID     = 0x7FFFFFFF
+} StdVideoH264ChromaFormatIdc;
+
+typedef enum StdVideoH264ProfileIdc {
+    STD_VIDEO_H264_PROFILE_IDC_BASELINE             = 66, /* Only constrained baseline is supported */
+    STD_VIDEO_H264_PROFILE_IDC_MAIN                 = 77,
+    STD_VIDEO_H264_PROFILE_IDC_HIGH                 = 100,
+    STD_VIDEO_H264_PROFILE_IDC_HIGH_444_PREDICTIVE  = 244,
+    STD_VIDEO_H264_PROFILE_IDC_INVALID              = 0x7FFFFFFF
+} StdVideoH264ProfileIdc;
+
+typedef enum StdVideoH264Level {
+    STD_VIDEO_H264_LEVEL_1_0 = 0,
+    STD_VIDEO_H264_LEVEL_1_1 = 1,
+    STD_VIDEO_H264_LEVEL_1_2 = 2,
+    STD_VIDEO_H264_LEVEL_1_3 = 3,
+    STD_VIDEO_H264_LEVEL_2_0 = 4,
+    STD_VIDEO_H264_LEVEL_2_1 = 5,
+    STD_VIDEO_H264_LEVEL_2_2 = 6,
+    STD_VIDEO_H264_LEVEL_3_0 = 7,
+    STD_VIDEO_H264_LEVEL_3_1 = 8,
+    STD_VIDEO_H264_LEVEL_3_2 = 9,
+    STD_VIDEO_H264_LEVEL_4_0 = 10,
+    STD_VIDEO_H264_LEVEL_4_1 = 11,
+    STD_VIDEO_H264_LEVEL_4_2 = 12,
+    STD_VIDEO_H264_LEVEL_5_0 = 13,
+    STD_VIDEO_H264_LEVEL_5_1 = 14,
+    STD_VIDEO_H264_LEVEL_5_2 = 15,
+    STD_VIDEO_H264_LEVEL_6_0 = 16,
+    STD_VIDEO_H264_LEVEL_6_1 = 17,
+    STD_VIDEO_H264_LEVEL_6_2 = 18,
+    STD_VIDEO_H264_LEVEL_INVALID = 0x7FFFFFFF
+} StdVideoH264Level;
+
+typedef enum StdVideoH264PocType {
+    STD_VIDEO_H264_POC_TYPE_0 = 0,
+    STD_VIDEO_H264_POC_TYPE_1 = 1,
+    STD_VIDEO_H264_POC_TYPE_2 = 2,
+    STD_VIDEO_H264_POC_TYPE_INVALID = 0x7FFFFFFF
+} StdVideoH264PocType;
+
+typedef enum StdVideoH264AspectRatioIdc {
+    STD_VIDEO_H264_ASPECT_RATIO_IDC_UNSPECIFIED = 0,
+    STD_VIDEO_H264_ASPECT_RATIO_IDC_SQUARE = 1,
+    STD_VIDEO_H264_ASPECT_RATIO_IDC_12_11 = 2,
+    STD_VIDEO_H264_ASPECT_RATIO_IDC_10_11 = 3,
+    STD_VIDEO_H264_ASPECT_RATIO_IDC_16_11 = 4,
+    STD_VIDEO_H264_ASPECT_RATIO_IDC_40_33 = 5,
+    STD_VIDEO_H264_ASPECT_RATIO_IDC_24_11 = 6,
+    STD_VIDEO_H264_ASPECT_RATIO_IDC_20_11 = 7,
+    STD_VIDEO_H264_ASPECT_RATIO_IDC_32_11 = 8,
+    STD_VIDEO_H264_ASPECT_RATIO_IDC_80_33 = 9,
+    STD_VIDEO_H264_ASPECT_RATIO_IDC_18_11 = 10,
+    STD_VIDEO_H264_ASPECT_RATIO_IDC_15_11 = 11,
+    STD_VIDEO_H264_ASPECT_RATIO_IDC_64_33 = 12,
+    STD_VIDEO_H264_ASPECT_RATIO_IDC_160_99 = 13,
+    STD_VIDEO_H264_ASPECT_RATIO_IDC_4_3 = 14,
+    STD_VIDEO_H264_ASPECT_RATIO_IDC_3_2 = 15,
+    STD_VIDEO_H264_ASPECT_RATIO_IDC_2_1 = 16,
+    STD_VIDEO_H264_ASPECT_RATIO_IDC_EXTENDED_SAR = 255,
+    STD_VIDEO_H264_ASPECT_RATIO_IDC_INVALID = 0x7FFFFFFF
+} StdVideoH264AspectRatioIdc;
+
+typedef enum StdVideoH264WeightedBipredIdc {
+    STD_VIDEO_H264_WEIGHTED_BIPRED_IDC_DEFAULT  = 0,
+    STD_VIDEO_H264_WEIGHTED_BIPRED_IDC_EXPLICIT = 1,
+    STD_VIDEO_H264_WEIGHTED_BIPRED_IDC_IMPLICIT = 2,
+    STD_VIDEO_H264_WEIGHTED_BIPRED_IDC_INVALID = 0x7FFFFFFF
+} StdVideoH264WeightedBipredIdc;
+
+typedef enum StdVideoH264ModificationOfPicNumsIdc {
+    STD_VIDEO_H264_MODIFICATION_OF_PIC_NUMS_IDC_SHORT_TERM_SUBTRACT = 0,
+    STD_VIDEO_H264_MODIFICATION_OF_PIC_NUMS_IDC_SHORT_TERM_ADD = 1,
+    STD_VIDEO_H264_MODIFICATION_OF_PIC_NUMS_IDC_LONG_TERM = 2,
+    STD_VIDEO_H264_MODIFICATION_OF_PIC_NUMS_IDC_END = 3,
+    STD_VIDEO_H264_MODIFICATION_OF_PIC_NUMS_IDC_INVALID = 0x7FFFFFFF
+} StdVideoH264ModificationOfPicNumsIdc;
+
+typedef enum StdVideoH264MemMgmtControlOp {
+    STD_VIDEO_H264_MEM_MGMT_CONTROL_OP_END = 0,
+    STD_VIDEO_H264_MEM_MGMT_CONTROL_OP_UNMARK_SHORT_TERM = 1,
+    STD_VIDEO_H264_MEM_MGMT_CONTROL_OP_UNMARK_LONG_TERM = 2,
+    STD_VIDEO_H264_MEM_MGMT_CONTROL_OP_MARK_LONG_TERM = 3,
+    STD_VIDEO_H264_MEM_MGMT_CONTROL_OP_SET_MAX_LONG_TERM_INDEX = 4,
+    STD_VIDEO_H264_MEM_MGMT_CONTROL_OP_UNMARK_ALL = 5,
+    STD_VIDEO_H264_MEM_MGMT_CONTROL_OP_MARK_CURRENT_AS_LONG_TERM = 6,
+    STD_VIDEO_H264_MEM_MGMT_CONTROL_OP_INVALID = 0x7FFFFFFF
+} StdVideoH264MemMgmtControlOp;
+
+typedef enum StdVideoH264CabacInitIdc {
+    STD_VIDEO_H264_CABAC_INIT_IDC_0 = 0,
+    STD_VIDEO_H264_CABAC_INIT_IDC_1 = 1,
+    STD_VIDEO_H264_CABAC_INIT_IDC_2 = 2,
+    STD_VIDEO_H264_CABAC_INIT_IDC_INVALID = 0x7FFFFFFF
+} StdVideoH264CabacInitIdc;
+
+typedef enum StdVideoH264DisableDeblockingFilterIdc {
+    STD_VIDEO_H264_DISABLE_DEBLOCKING_FILTER_IDC_DISABLED = 0,
+    STD_VIDEO_H264_DISABLE_DEBLOCKING_FILTER_IDC_ENABLED = 1,
+    STD_VIDEO_H264_DISABLE_DEBLOCKING_FILTER_IDC_PARTIAL = 2,
+    STD_VIDEO_H264_DISABLE_DEBLOCKING_FILTER_IDC_INVALID = 0x7FFFFFFF
+} StdVideoH264DisableDeblockingFilterIdc;
+
+typedef enum StdVideoH264SliceType {
+    STD_VIDEO_H264_SLICE_TYPE_P  = 0,
+    STD_VIDEO_H264_SLICE_TYPE_B  = 1,
+    STD_VIDEO_H264_SLICE_TYPE_I  = 2,
+    // reserved STD_VIDEO_H264_SLICE_TYPE_SP = 3,
+    // reserved STD_VIDEO_H264_SLICE_TYPE_SI = 4,
+    STD_VIDEO_H264_SLICE_TYPE_INVALID = 0x7FFFFFFF
+} StdVideoH264SliceType;
+
+typedef enum StdVideoH264PictureType {
+    STD_VIDEO_H264_PICTURE_TYPE_P   = 0,
+    STD_VIDEO_H264_PICTURE_TYPE_B   = 1,
+    STD_VIDEO_H264_PICTURE_TYPE_I   = 2,
+    // reserved STD_VIDEO_H264_PICTURE_TYPE_SP  = 3,
+    // reserved STD_VIDEO_H264_PICTURE_TYPE_SI  = 4,
+    STD_VIDEO_H264_PICTURE_TYPE_IDR = 5,
+    STD_VIDEO_H264_PICTURE_TYPE_INVALID = 0x7FFFFFFF
+} StdVideoH264PictureType;
+
+typedef enum StdVideoH264NonVclNaluType {
+    STD_VIDEO_H264_NON_VCL_NALU_TYPE_SPS = 0,
+    STD_VIDEO_H264_NON_VCL_NALU_TYPE_PPS = 1,
+    STD_VIDEO_H264_NON_VCL_NALU_TYPE_AUD = 2,
+    STD_VIDEO_H264_NON_VCL_NALU_TYPE_PREFIX = 3,
+    STD_VIDEO_H264_NON_VCL_NALU_TYPE_END_OF_SEQUENCE = 4,
+    STD_VIDEO_H264_NON_VCL_NALU_TYPE_END_OF_STREAM = 5,
+    STD_VIDEO_H264_NON_VCL_NALU_TYPE_PRECODED = 6,
+    STD_VIDEO_H264_NON_VCL_NALU_TYPE_INVALID = 0x7FFFFFFF
+} StdVideoH264NonVclNaluType;
+
+typedef struct StdVideoH264SpsVuiFlags {
+    uint32_t aspect_ratio_info_present_flag : 1;
+    uint32_t overscan_info_present_flag : 1;
+    uint32_t overscan_appropriate_flag : 1;
+    uint32_t video_signal_type_present_flag : 1;
+    uint32_t video_full_range_flag : 1;
+    uint32_t color_description_present_flag : 1;
+    uint32_t chroma_loc_info_present_flag : 1;
+    uint32_t timing_info_present_flag : 1;
+    uint32_t fixed_frame_rate_flag : 1;
+    uint32_t bitstream_restriction_flag : 1;
+    uint32_t nal_hrd_parameters_present_flag : 1;
+    uint32_t vcl_hrd_parameters_present_flag : 1;
+} StdVideoH264SpsVuiFlags;
+
+typedef struct StdVideoH264HrdParameters { // hrd_parameters
+    uint8_t                    cpb_cnt_minus1;
+    uint8_t                    bit_rate_scale;
+    uint8_t                    cpb_size_scale;
+    uint32_t                   bit_rate_value_minus1[STD_VIDEO_H264_CPB_CNT_LIST_SIZE]; // cpb_cnt_minus1 number of valid elements
+    uint32_t                   cpb_size_value_minus1[STD_VIDEO_H264_CPB_CNT_LIST_SIZE]; // cpb_cnt_minus1 number of valid elements
+    uint8_t                    cbr_flag[STD_VIDEO_H264_CPB_CNT_LIST_SIZE];              // cpb_cnt_minus1 number of valid elements
+    uint32_t                   initial_cpb_removal_delay_length_minus1;
+    uint32_t                   cpb_removal_delay_length_minus1;
+    uint32_t                   dpb_output_delay_length_minus1;
+    uint32_t                   time_offset_length;
+} StdVideoH264HrdParameters;
+
+typedef struct StdVideoH264SequenceParameterSetVui {
+    StdVideoH264AspectRatioIdc  aspect_ratio_idc;
+    uint16_t                    sar_width;
+    uint16_t                    sar_height;
+    uint8_t                     video_format;
+    uint8_t                     color_primaries;
+    uint8_t                     transfer_characteristics;
+    uint8_t                     matrix_coefficients;
+    uint32_t                    num_units_in_tick;
+    uint32_t                    time_scale;
+    StdVideoH264HrdParameters*  pHrdParameters;    // must be a valid ptr to hrd_parameters, if nal_hrd_parameters_present_flag or vcl_hrd_parameters_present_flag are set
+    uint8_t                     max_num_reorder_frames;
+    uint8_t                     max_dec_frame_buffering;
+    StdVideoH264SpsVuiFlags     flags;
+} StdVideoH264SequenceParameterSetVui;
+
+typedef struct StdVideoH264SpsFlags {
+    uint32_t constraint_set0_flag : 1;
+    uint32_t constraint_set1_flag : 1;
+    uint32_t constraint_set2_flag : 1;
+    uint32_t constraint_set3_flag : 1;
+    uint32_t constraint_set4_flag : 1;
+    uint32_t constraint_set5_flag : 1;
+    uint32_t direct_8x8_inference_flag : 1;
+    uint32_t mb_adaptive_frame_field_flag : 1;
+    uint32_t frame_mbs_only_flag : 1;
+    uint32_t delta_pic_order_always_zero_flag : 1;
+    uint32_t separate_colour_plane_flag : 1;
+    uint32_t gaps_in_frame_num_value_allowed_flag : 1;
+    uint32_t qpprime_y_zero_transform_bypass_flag : 1;
+    uint32_t frame_cropping_flag : 1;
+    uint32_t seq_scaling_matrix_present_flag : 1;
+    uint32_t vui_parameters_present_flag : 1;
+} StdVideoH264SpsFlags;
+
+typedef struct StdVideoH264ScalingLists
+{
+    // scaling_list_present_mask has one bit for each
+    // seq_scaling_list_present_flag[i] for SPS OR
+    // pic_scaling_list_present_flag[i] for PPS,
+    // bit 0 - 5 are for each entry of ScalingList4x4
+    // bit 6 - 7 are for each entry plus 6 for ScalingList8x8
+    uint8_t scaling_list_present_mask;
+    // use_default_scaling_matrix_mask has one bit for each
+    // UseDefaultScalingMatrix4x4Flag[ i ] and
+    // UseDefaultScalingMatrix8x8Flag[ i - 6 ] for SPS OR PPS
+    // bit 0 - 5 are for each entry of ScalingList4x4
+    // bit 6 - 7 are for each entry plus 6 for ScalingList8x8
+    uint8_t use_default_scaling_matrix_mask;
+    uint8_t ScalingList4x4[STD_VIDEO_H264_SCALING_LIST_4X4_NUM_LISTS][STD_VIDEO_H264_SCALING_LIST_4X4_NUM_ELEMENTS];
+    uint8_t ScalingList8x8[STD_VIDEO_H264_SCALING_LIST_8X8_NUM_LISTS][STD_VIDEO_H264_SCALING_LIST_8X8_NUM_ELEMENTS];
+} StdVideoH264ScalingLists;
+
+typedef struct StdVideoH264SequenceParameterSet
+{
+    StdVideoH264ProfileIdc               profile_idc;
+    StdVideoH264Level                    level_idc;
+    uint8_t                              seq_parameter_set_id;
+    StdVideoH264ChromaFormatIdc          chroma_format_idc;
+    uint8_t                              bit_depth_luma_minus8;
+    uint8_t                              bit_depth_chroma_minus8;
+    uint8_t                              log2_max_frame_num_minus4;
+    StdVideoH264PocType                  pic_order_cnt_type;
+    uint8_t                              log2_max_pic_order_cnt_lsb_minus4;
+    int32_t                              offset_for_non_ref_pic;
+    int32_t                              offset_for_top_to_bottom_field;
+    uint8_t                              num_ref_frames_in_pic_order_cnt_cycle;
+    uint8_t                              max_num_ref_frames;
+    uint32_t                             pic_width_in_mbs_minus1;
+    uint32_t                             pic_height_in_map_units_minus1;
+    uint32_t                             frame_crop_left_offset;
+    uint32_t                             frame_crop_right_offset;
+    uint32_t                             frame_crop_top_offset;
+    uint32_t                             frame_crop_bottom_offset;
+    StdVideoH264SpsFlags                 flags;
+    // pOffsetForRefFrame is a pointer representing the offset_for_ref_frame array with num_ref_frames_in_pic_order_cnt_cycle number of elements
+    // If pOffsetForRefFrame has nullptr value, then num_ref_frames_in_pic_order_cnt_cycle must also be "0".
+    int32_t*                             pOffsetForRefFrame;
+    StdVideoH264ScalingLists*            pScalingLists;             // Must be a valid pointer if seq_scaling_matrix_present_flag is set
+    StdVideoH264SequenceParameterSetVui* pSequenceParameterSetVui;  // Must be a valid pointer if StdVideoH264SpsFlags:vui_parameters_present_flag is set
+} StdVideoH264SequenceParameterSet;
+
+typedef struct StdVideoH264PpsFlags {
+    uint32_t transform_8x8_mode_flag : 1;
+    uint32_t redundant_pic_cnt_present_flag : 1;
+    uint32_t constrained_intra_pred_flag : 1;
+    uint32_t deblocking_filter_control_present_flag : 1;
+    uint32_t weighted_bipred_idc_flag : 1;
+    uint32_t weighted_pred_flag : 1;
+    uint32_t pic_order_present_flag : 1;
+    uint32_t entropy_coding_mode_flag : 1;
+    uint32_t pic_scaling_matrix_present_flag : 1;
+} StdVideoH264PpsFlags;
+
+typedef struct StdVideoH264PictureParameterSet
+{
+    uint8_t                       seq_parameter_set_id;
+    uint8_t                       pic_parameter_set_id;
+    uint8_t                       num_ref_idx_l0_default_active_minus1;
+    uint8_t                       num_ref_idx_l1_default_active_minus1;
+    StdVideoH264WeightedBipredIdc weighted_bipred_idc;
+    int8_t                        pic_init_qp_minus26;
+    int8_t                        pic_init_qs_minus26;
+    int8_t                        chroma_qp_index_offset;
+    int8_t                        second_chroma_qp_index_offset;
+    StdVideoH264PpsFlags          flags;
+    StdVideoH264ScalingLists*     pScalingLists; // Must be a valid pointer if  StdVideoH264PpsFlags::pic_scaling_matrix_present_flag is set.
+} StdVideoH264PictureParameterSet;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // VULKAN_VIDEO_CODEC_H264STD_H_
diff --git a/external/Vulkan/external/vk_video/vulkan_video_codec_h264std_decode.h b/external/Vulkan/external/vk_video/vulkan_video_codec_h264std_decode.h
new file mode 100644
index 0000000000000000000000000000000000000000..6f2d6d7e911fc353d18ad275faf6a538bbbc3928
--- /dev/null
+++ b/external/Vulkan/external/vk_video/vulkan_video_codec_h264std_decode.h
@@ -0,0 +1,96 @@
+/*
+** Copyright (c) 2019-2020 The Khronos Group Inc.
+**
+** SPDX-License-Identifier: Apache-2.0
+*/
+
+#ifndef VULKAN_VIDEO_CODEC_H264STD_DECODE_H_
+#define VULKAN_VIDEO_CODEC_H264STD_DECODE_H_ 1
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "vk_video/vulkan_video_codec_h264std.h"
+
+// *************************************************
+// Video H.264 Decode related parameters:
+// *************************************************
+
+#define STD_VIDEO_DECODE_H264_MVC_REF_LIST_SIZE 15
+
+typedef enum StdVideoDecodeH264FieldOrderCount {
+    STD_VIDEO_DECODE_H264_FIELD_ORDER_COUNT_TOP       = 0,
+    STD_VIDEO_DECODE_H264_FIELD_ORDER_COUNT_BOTTOM    = 1,
+    STD_VIDEO_DECODE_H264_FIELD_ORDER_COUNT_LIST_SIZE = 2,
+    STD_VIDEO_DECODE_H264_FIELD_ORDER_COUNT_INVALID   = 0x7FFFFFFF
+} StdVideoDecodeH264FieldOrderCnt;
+
+typedef struct StdVideoDecodeH264PictureInfoFlags {
+    uint32_t field_pic_flag : 1;             // Is field picture
+    uint32_t is_intra : 1;                   // Is intra picture
+    uint32_t bottom_field_flag : 1;          // bottom (true) or top (false) field if field_pic_flag is set.
+    uint32_t is_reference : 1;               // This only applies to picture info, and not to the DPB lists.
+    uint32_t complementary_field_pair : 1;   // complementary field pair, complementary non-reference field pair, complementary reference field pair
+} StdVideoDecodeH264PictureInfoFlags;
+
+typedef struct StdVideoDecodeH264PictureInfo {
+    uint8_t  seq_parameter_set_id;          // Selecting SPS from the Picture Parameters
+    uint8_t  pic_parameter_set_id;          // Selecting PPS from the Picture Parameters and the SPS
+    uint16_t reserved;                      // for structure members 32-bit packing/alignment
+    uint16_t frame_num;                     // 7.4.3 Slice header semantics
+    uint16_t idr_pic_id;                    // 7.4.3 Slice header semantics
+    // PicOrderCnt is based on TopFieldOrderCnt and BottomFieldOrderCnt. See 8.2.1 Decoding process for picture order count type 0 - 2
+    int32_t  PicOrderCnt[STD_VIDEO_DECODE_H264_FIELD_ORDER_COUNT_LIST_SIZE];  // TopFieldOrderCnt and BottomFieldOrderCnt fields.
+    StdVideoDecodeH264PictureInfoFlags flags;
+} StdVideoDecodeH264PictureInfo;
+
+typedef struct StdVideoDecodeH264ReferenceInfoFlags {
+    uint32_t top_field_flag : 1;             // Reference is used for top field reference.
+    uint32_t bottom_field_flag : 1;          // Reference is used for bottom field reference.
+    uint32_t is_long_term : 1;               // this is a long term reference
+    uint32_t is_non_existing : 1;            // Must be handled in accordance with 8.2.5.2: Decoding process for gaps in frame_num
+} StdVideoDecodeH264ReferenceInfoFlags;
+
+typedef struct StdVideoDecodeH264ReferenceInfo {
+    // FrameNum = is_long_term ?  long_term_frame_idx : frame_num
+    uint16_t FrameNum;                     // 7.4.3.3 Decoded reference picture marking semantics
+    uint16_t reserved;                     // for structure members 32-bit packing/alignment
+    int32_t  PicOrderCnt[2];               // TopFieldOrderCnt and BottomFieldOrderCnt fields.
+    StdVideoDecodeH264ReferenceInfoFlags flags;
+} StdVideoDecodeH264ReferenceInfo;
+
+typedef struct StdVideoDecodeH264MvcElementFlags {
+    uint32_t non_idr : 1;
+    uint32_t anchor_pic : 1;
+    uint32_t inter_view : 1;
+} StdVideoDecodeH264MvcElementFlags;
+
+typedef struct StdVideoDecodeH264MvcElement {
+    StdVideoDecodeH264MvcElementFlags flags;
+    uint16_t viewOrderIndex;
+    uint16_t viewId;
+    uint16_t temporalId; // move out?
+    uint16_t priorityId; // move out?
+    uint16_t numOfAnchorRefsInL0;
+    uint16_t viewIdOfAnchorRefsInL0[STD_VIDEO_DECODE_H264_MVC_REF_LIST_SIZE];
+    uint16_t numOfAnchorRefsInL1;
+    uint16_t viewIdOfAnchorRefsInL1[STD_VIDEO_DECODE_H264_MVC_REF_LIST_SIZE];
+    uint16_t numOfNonAnchorRefsInL0;
+    uint16_t viewIdOfNonAnchorRefsInL0[STD_VIDEO_DECODE_H264_MVC_REF_LIST_SIZE];
+    uint16_t numOfNonAnchorRefsInL1;
+    uint16_t viewIdOfNonAnchorRefsInL1[STD_VIDEO_DECODE_H264_MVC_REF_LIST_SIZE];
+} StdVideoDecodeH264MvcElement;
+
+typedef struct StdVideoDecodeH264Mvc {
+    uint32_t viewId0;
+    uint32_t mvcElementCount;
+    StdVideoDecodeH264MvcElement* pMvcElements;
+} StdVideoDecodeH264Mvc;
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // VULKAN_VIDEO_CODEC_H264STD_DECODE_H_
diff --git a/external/Vulkan/external/vk_video/vulkan_video_codec_h264std_encode.h b/external/Vulkan/external/vk_video/vulkan_video_codec_h264std_encode.h
new file mode 100644
index 0000000000000000000000000000000000000000..f3a0d3adae88b5eeaaac6e43a33ba5ebb279300b
--- /dev/null
+++ b/external/Vulkan/external/vk_video/vulkan_video_codec_h264std_encode.h
@@ -0,0 +1,94 @@
+/*
+** Copyright (c) 2019-2021 The Khronos Group Inc.
+**
+** SPDX-License-Identifier: Apache-2.0
+*/
+
+#ifndef VULKAN_VIDEO_CODEC_H264STD_ENCODE_H_
+#define VULKAN_VIDEO_CODEC_H264STD_ENCODE_H_ 1
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "vk_video/vulkan_video_codec_h264std.h"
+
+// *************************************************
+// Video H.264 Encode related parameters:
+// *************************************************
+
+typedef struct StdVideoEncodeH264SliceHeaderFlags {
+    uint32_t idr_flag : 1;
+    uint32_t is_reference_flag : 1;
+    uint32_t num_ref_idx_active_override_flag : 1;
+    uint32_t no_output_of_prior_pics_flag : 1;
+    uint32_t long_term_reference_flag : 1;
+    uint32_t adaptive_ref_pic_marking_mode_flag : 1;
+    uint32_t no_prior_references_available_flag : 1;
+} StdVideoEncodeH264SliceHeaderFlags;
+
+typedef struct StdVideoEncodeH264PictureInfoFlags {
+    uint32_t idr_flag : 1;
+    uint32_t is_reference_flag : 1;
+    uint32_t long_term_reference_flag : 1;
+} StdVideoEncodeH264PictureInfoFlags;
+
+typedef struct StdVideoEncodeH264RefMgmtFlags {
+    uint32_t ref_pic_list_modification_l0_flag : 1;
+    uint32_t ref_pic_list_modification_l1_flag : 1;
+} StdVideoEncodeH264RefMgmtFlags;
+
+typedef struct StdVideoEncodeH264RefListModEntry {
+    StdVideoH264ModificationOfPicNumsIdc modification_of_pic_nums_idc;
+    uint16_t                             abs_diff_pic_num_minus1;
+    uint16_t                             long_term_pic_num;
+} StdVideoEncodeH264RefListModEntry;
+
+typedef struct StdVideoEncodeH264RefPicMarkingEntry {
+    StdVideoH264MemMgmtControlOp  operation;
+    uint16_t                      difference_of_pic_nums_minus1;
+    uint16_t                      long_term_pic_num;
+    uint16_t                      long_term_frame_idx;
+    uint16_t                      max_long_term_frame_idx_plus1;
+} StdVideoEncodeH264RefPicMarkingEntry;
+
+typedef struct StdVideoEncodeH264RefMemMgmtCtrlOperations {
+    StdVideoEncodeH264RefMgmtFlags        flags;
+    uint8_t                               refList0ModOpCount;
+    StdVideoEncodeH264RefListModEntry*    pRefList0ModOperations;
+    uint8_t                               refList1ModOpCount;
+    StdVideoEncodeH264RefListModEntry*    pRefList1ModOperations;
+    uint8_t                               refPicMarkingOpCount;
+    StdVideoEncodeH264RefPicMarkingEntry* pRefPicMarkingOperations;
+} StdVideoEncodeH264RefMemMgmtCtrlOperations;
+
+typedef struct StdVideoEncodeH264PictureInfo {
+    StdVideoEncodeH264PictureInfoFlags   flags;
+    StdVideoH264PictureType              pictureType;
+    uint32_t                             frameNum;
+    uint32_t                             pictureOrderCount;
+    uint16_t                             long_term_pic_num;
+    uint16_t                             long_term_frame_idx;
+} StdVideoEncodeH264PictureInfo;
+
+typedef struct StdVideoEncodeH264SliceHeader {
+    StdVideoEncodeH264SliceHeaderFlags          flags;
+    StdVideoH264SliceType                       slice_type;
+    uint8_t                                     seq_parameter_set_id;
+    uint8_t                                     pic_parameter_set_id;
+    uint16_t                                    idr_pic_id;
+    uint8_t                                     num_ref_idx_l0_active_minus1;
+    uint8_t                                     num_ref_idx_l1_active_minus1;
+    StdVideoH264CabacInitIdc                    cabac_init_idc;
+    StdVideoH264DisableDeblockingFilterIdc      disable_deblocking_filter_idc;
+    int8_t                                      slice_alpha_c0_offset_div2;
+    int8_t                                      slice_beta_offset_div2;
+    StdVideoEncodeH264RefMemMgmtCtrlOperations* pMemMgmtCtrlOperations;
+} StdVideoEncodeH264SliceHeader;
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // VULKAN_VIDEO_CODEC_H264STD_ENCODE_H_
diff --git a/external/Vulkan/external/vk_video/vulkan_video_codec_h265std.h b/external/Vulkan/external/vk_video/vulkan_video_codec_h265std.h
new file mode 100644
index 0000000000000000000000000000000000000000..179c6b702136d5f6cb32e0132de3c4d35f6d1cc2
--- /dev/null
+++ b/external/Vulkan/external/vk_video/vulkan_video_codec_h265std.h
@@ -0,0 +1,371 @@
+/*
+** Copyright (c) 2019-2021 The Khronos Group Inc.
+**
+** SPDX-License-Identifier: Apache-2.0
+*/
+
+#ifndef VULKAN_VIDEO_CODEC_H265STD_H_
+#define VULKAN_VIDEO_CODEC_H265STD_H_ 1
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "vk_video/vulkan_video_codecs_common.h"
+
+// Vulkan 0.5 version number WIP
+#define VK_STD_VULKAN_VIDEO_CODEC_H265_API_VERSION_0_9_5 VK_MAKE_VIDEO_STD_VERSION(0, 9, 5) // Patch version should always be set to 0
+
+// Format must be in the form XX.XX where the first two digits are the major and the second two, the minor.
+#define VK_STD_VULKAN_VIDEO_CODEC_H265_SPEC_VERSION   VK_STD_VULKAN_VIDEO_CODEC_H265_API_VERSION_0_9_5
+#define VK_STD_VULKAN_VIDEO_CODEC_H265_EXTENSION_NAME "VK_STD_vulkan_video_codec_h265"
+
+#define STD_VIDEO_H265_CPB_CNT_LIST_SIZE 32
+#define STD_VIDEO_H265_SUBLAYERS_MINUS1_LIST_SIZE 7
+#define STD_VIDEO_H265_SCALING_LIST_4X4_NUM_LISTS 6
+#define STD_VIDEO_H265_SCALING_LIST_4X4_NUM_ELEMENTS 16
+#define STD_VIDEO_H265_SCALING_LIST_8X8_NUM_LISTS 6
+#define STD_VIDEO_H265_SCALING_LIST_8X8_NUM_ELEMENTS 64
+#define STD_VIDEO_H265_SCALING_LIST_16X16_NUM_LISTS 6
+#define STD_VIDEO_H265_SCALING_LIST_16X16_NUM_ELEMENTS 64
+#define STD_VIDEO_H265_SCALING_LIST_32X32_NUM_LISTS 2
+#define STD_VIDEO_H265_SCALING_LIST_32X32_NUM_ELEMENTS 64
+#define STD_VIDEO_H265_CHROMA_QP_OFFSET_LIST_SIZE 6
+#define STD_VIDEO_H265_CHROMA_QP_OFFSET_TILE_COLS_LIST_SIZE 19
+#define STD_VIDEO_H265_CHROMA_QP_OFFSET_TILE_ROWS_LIST_SIZE 21
+#define STD_VIDEO_H265_PREDICTOR_PALETTE_COMPONENTS_LIST_SIZE 3
+#define STD_VIDEO_H265_PREDICTOR_PALETTE_COMP_ENTRIES_LIST_SIZE 128
+
+typedef enum StdVideoH265ChromaFormatIdc {
+    STD_VIDEO_H265_CHROMA_FORMAT_IDC_MONOCHROME  = 0,
+    STD_VIDEO_H265_CHROMA_FORMAT_IDC_420         = 1,
+    STD_VIDEO_H265_CHROMA_FORMAT_IDC_422         = 2,
+    STD_VIDEO_H265_CHROMA_FORMAT_IDC_444         = 3,
+    STD_VIDEO_H265_CHROMA_FORMAT_IDC_INVALID     = 0x7FFFFFFF
+} StdVideoH265ChromaFormatIdc;
+
+typedef enum StdVideoH265ProfileIdc {
+    STD_VIDEO_H265_PROFILE_IDC_MAIN                     = 1,
+    STD_VIDEO_H265_PROFILE_IDC_MAIN_10                  = 2,
+    STD_VIDEO_H265_PROFILE_IDC_MAIN_STILL_PICTURE       = 3,
+    STD_VIDEO_H265_PROFILE_IDC_FORMAT_RANGE_EXTENSIONS  = 4,
+    STD_VIDEO_H265_PROFILE_IDC_SCC_EXTENSIONS           = 9,
+    STD_VIDEO_H265_PROFILE_IDC_INVALID                  = 0x7FFFFFFF
+} StdVideoH265ProfileIdc;
+
+typedef enum StdVideoH265Level {
+    STD_VIDEO_H265_LEVEL_1_0 = 0,
+    STD_VIDEO_H265_LEVEL_2_0 = 1,
+    STD_VIDEO_H265_LEVEL_2_1 = 2,
+    STD_VIDEO_H265_LEVEL_3_0 = 3,
+    STD_VIDEO_H265_LEVEL_3_1 = 4,
+    STD_VIDEO_H265_LEVEL_4_0 = 5,
+    STD_VIDEO_H265_LEVEL_4_1 = 6,
+    STD_VIDEO_H265_LEVEL_5_0 = 7,
+    STD_VIDEO_H265_LEVEL_5_1 = 8,
+    STD_VIDEO_H265_LEVEL_5_2 = 9,
+    STD_VIDEO_H265_LEVEL_6_0 = 10,
+    STD_VIDEO_H265_LEVEL_6_1 = 11,
+    STD_VIDEO_H265_LEVEL_6_2 = 12,
+    STD_VIDEO_H265_LEVEL_INVALID = 0x7FFFFFFF
+} StdVideoH265Level;
+
+typedef enum StdVideoH265SliceType {
+    STD_VIDEO_H265_SLICE_TYPE_B = 0,
+    STD_VIDEO_H265_SLICE_TYPE_P = 1,
+    STD_VIDEO_H265_SLICE_TYPE_I = 2,
+    STD_VIDEO_H265_SLICE_TYPE_INVALID = 0x7FFFFFFF
+} StdVideoH265SliceType;
+
+typedef enum StdVideoH265PictureType {
+    STD_VIDEO_H265_PICTURE_TYPE_P   = 0,
+    STD_VIDEO_H265_PICTURE_TYPE_B   = 1,
+    STD_VIDEO_H265_PICTURE_TYPE_I   = 2,
+    STD_VIDEO_H265_PICTURE_TYPE_IDR = 3,
+    STD_VIDEO_H265_PICTURE_TYPE_INVALID = 0x7FFFFFFF
+} StdVideoH265PictureType;
+
+typedef struct StdVideoH265DecPicBufMgr
+{
+    uint32_t max_latency_increase_plus1[STD_VIDEO_H265_SUBLAYERS_MINUS1_LIST_SIZE];
+    uint8_t  max_dec_pic_buffering_minus1[STD_VIDEO_H265_SUBLAYERS_MINUS1_LIST_SIZE];
+    uint8_t  max_num_reorder_pics[STD_VIDEO_H265_SUBLAYERS_MINUS1_LIST_SIZE];
+} StdVideoH265DecPicBufMgr;
+
+typedef struct StdVideoH265SubLayerHrdParameters { // sub_layer_hrd_parameters
+    uint32_t bit_rate_value_minus1[STD_VIDEO_H265_CPB_CNT_LIST_SIZE];
+    uint32_t cpb_size_value_minus1[STD_VIDEO_H265_CPB_CNT_LIST_SIZE];
+    uint32_t cpb_size_du_value_minus1[STD_VIDEO_H265_CPB_CNT_LIST_SIZE];
+    uint32_t bit_rate_du_value_minus1[STD_VIDEO_H265_CPB_CNT_LIST_SIZE];
+    uint32_t cbr_flag; // each bit represents a range of CpbCounts (bit 0 - cpb_cnt_minus1) per sub-layer
+} StdVideoH265SubLayerHrdParameters;
+
+typedef struct StdVideoH265HrdFlags {
+    uint32_t nal_hrd_parameters_present_flag : 1;
+    uint32_t vcl_hrd_parameters_present_flag : 1;
+    uint32_t sub_pic_hrd_params_present_flag : 1;
+    uint32_t sub_pic_cpb_params_in_pic_timing_sei_flag : 1;
+    uint32_t fixed_pic_rate_general_flag : 8;    // each bit represents a sublayer, bit 0 - vps_max_sub_layers_minus1
+    uint32_t fixed_pic_rate_within_cvs_flag : 8; // each bit represents a sublayer, bit 0 - vps_max_sub_layers_minus1
+    uint32_t low_delay_hrd_flag : 8;             // each bit represents a sublayer, bit 0 - vps_max_sub_layers_minus1
+} StdVideoH265HrdFlags;
+
+typedef struct StdVideoH265HrdParameters {
+    uint8_t                            tick_divisor_minus2;
+    uint8_t                            du_cpb_removal_delay_increment_length_minus1;
+    uint8_t                            dpb_output_delay_du_length_minus1;
+    uint8_t                            bit_rate_scale;
+    uint8_t                            cpb_size_scale;
+    uint8_t                            cpb_size_du_scale;
+    uint8_t                            initial_cpb_removal_delay_length_minus1;
+    uint8_t                            au_cpb_removal_delay_length_minus1;
+    uint8_t                            dpb_output_delay_length_minus1;
+    uint8_t                            cpb_cnt_minus1[STD_VIDEO_H265_SUBLAYERS_MINUS1_LIST_SIZE];
+    uint16_t                           elemental_duration_in_tc_minus1[STD_VIDEO_H265_SUBLAYERS_MINUS1_LIST_SIZE];
+    StdVideoH265SubLayerHrdParameters* pSubLayerHrdParametersNal[STD_VIDEO_H265_SUBLAYERS_MINUS1_LIST_SIZE]; // NAL per layer ptr to sub_layer_hrd_parameters
+    StdVideoH265SubLayerHrdParameters* pSubLayerHrdParametersVcl[STD_VIDEO_H265_SUBLAYERS_MINUS1_LIST_SIZE]; // VCL per layer ptr to sub_layer_hrd_parameters
+    StdVideoH265HrdFlags               flags;
+} StdVideoH265HrdParameters;
+
+typedef struct StdVideoH265VpsFlags {
+    uint32_t vps_temporal_id_nesting_flag : 1;
+    uint32_t vps_sub_layer_ordering_info_present_flag : 1;
+    uint32_t vps_timing_info_present_flag : 1;
+    uint32_t vps_poc_proportional_to_timing_flag : 1;
+} StdVideoH265VpsFlags;
+
+typedef struct StdVideoH265VideoParameterSet
+{
+    uint8_t                      vps_video_parameter_set_id;
+    uint8_t                      vps_max_sub_layers_minus1;
+    uint32_t                     vps_num_units_in_tick;
+    uint32_t                     vps_time_scale;
+    uint32_t                     vps_num_ticks_poc_diff_one_minus1;
+    StdVideoH265DecPicBufMgr*    pDecPicBufMgr;
+    StdVideoH265HrdParameters*   pHrdParameters;
+    StdVideoH265VpsFlags         flags;
+} StdVideoH265VideoParameterSet;
+
+typedef struct StdVideoH265ScalingLists
+{
+    uint8_t ScalingList4x4[STD_VIDEO_H265_SCALING_LIST_4X4_NUM_LISTS][STD_VIDEO_H265_SCALING_LIST_4X4_NUM_ELEMENTS];       // ScalingList[ 0 ][ MatrixID ][ i ] (sizeID = 0)
+    uint8_t ScalingList8x8[STD_VIDEO_H265_SCALING_LIST_8X8_NUM_LISTS][STD_VIDEO_H265_SCALING_LIST_8X8_NUM_ELEMENTS];       // ScalingList[ 1 ][ MatrixID ][ i ] (sizeID = 1)
+    uint8_t ScalingList16x16[STD_VIDEO_H265_SCALING_LIST_16X16_NUM_LISTS][STD_VIDEO_H265_SCALING_LIST_16X16_NUM_ELEMENTS];     // ScalingList[ 2 ][ MatrixID ][ i ] (sizeID = 2)
+    uint8_t ScalingList32x32[STD_VIDEO_H265_SCALING_LIST_32X32_NUM_LISTS][STD_VIDEO_H265_SCALING_LIST_32X32_NUM_ELEMENTS];     // ScalingList[ 3 ][ MatrixID ][ i ] (sizeID = 3)
+    uint8_t ScalingListDCCoef16x16[STD_VIDEO_H265_SCALING_LIST_16X16_NUM_LISTS];   // scaling_list_dc_coef_minus8[ sizeID - 2 ][ matrixID ] + 8, sizeID = 2
+    uint8_t ScalingListDCCoef32x32[STD_VIDEO_H265_SCALING_LIST_32X32_NUM_LISTS];   // scaling_list_dc_coef_minus8[ sizeID - 2 ][ matrixID ] + 8. sizeID = 3
+} StdVideoH265ScalingLists;
+
+typedef struct StdVideoH265SpsVuiFlags {
+    uint32_t aspect_ratio_info_present_flag : 1;
+    uint32_t overscan_info_present_flag : 1;
+    uint32_t overscan_appropriate_flag : 1;
+    uint32_t video_signal_type_present_flag : 1;
+    uint32_t video_full_range_flag : 1;
+    uint32_t colour_description_present_flag : 1;
+    uint32_t chroma_loc_info_present_flag : 1;
+    uint32_t neutral_chroma_indication_flag : 1;
+    uint32_t field_seq_flag : 1;
+    uint32_t frame_field_info_present_flag : 1;
+    uint32_t default_display_window_flag : 1;
+    uint32_t vui_timing_info_present_flag : 1;
+    uint32_t vui_poc_proportional_to_timing_flag : 1;
+    uint32_t vui_hrd_parameters_present_flag : 1;
+    uint32_t bitstream_restriction_flag : 1;
+    uint32_t tiles_fixed_structure_flag : 1;
+    uint32_t motion_vectors_over_pic_boundaries_flag : 1;
+    uint32_t restricted_ref_pic_lists_flag : 1;
+} StdVideoH265SpsVuiFlags;
+
+typedef struct StdVideoH265SequenceParameterSetVui {
+    uint8_t                     aspect_ratio_idc;
+    uint16_t                    sar_width;
+    uint16_t                    sar_height;
+    uint8_t                     video_format;
+    uint8_t                     colour_primaries;
+    uint8_t                     transfer_characteristics;
+    uint8_t                     matrix_coeffs;
+    uint8_t                     chroma_sample_loc_type_top_field;
+    uint8_t                     chroma_sample_loc_type_bottom_field;
+    uint16_t                    def_disp_win_left_offset;
+    uint16_t                    def_disp_win_right_offset;
+    uint16_t                    def_disp_win_top_offset;
+    uint16_t                    def_disp_win_bottom_offset;
+    uint32_t                    vui_num_units_in_tick;
+    uint32_t                    vui_time_scale;
+    uint32_t                    vui_num_ticks_poc_diff_one_minus1;
+    StdVideoH265HrdParameters*  pHrdParameters;
+    uint16_t                    min_spatial_segmentation_idc;
+    uint8_t                     max_bytes_per_pic_denom;
+    uint8_t                     max_bits_per_min_cu_denom;
+    uint8_t                     log2_max_mv_length_horizontal;
+    uint8_t                     log2_max_mv_length_vertical;
+    StdVideoH265SpsVuiFlags     flags;
+} StdVideoH265SequenceParameterSetVui;
+
+typedef struct StdVideoH265PredictorPaletteEntries
+{
+    uint16_t PredictorPaletteEntries[STD_VIDEO_H265_PREDICTOR_PALETTE_COMPONENTS_LIST_SIZE][STD_VIDEO_H265_PREDICTOR_PALETTE_COMP_ENTRIES_LIST_SIZE];
+} StdVideoH265PredictorPaletteEntries;
+
+typedef struct StdVideoH265SpsFlags {
+    uint32_t sps_temporal_id_nesting_flag : 1;
+    uint32_t separate_colour_plane_flag : 1;
+    uint32_t scaling_list_enabled_flag : 1;
+    uint32_t sps_scaling_list_data_present_flag : 1;
+    uint32_t amp_enabled_flag : 1;
+    uint32_t sample_adaptive_offset_enabled_flag : 1;
+    uint32_t pcm_enabled_flag : 1;
+    uint32_t pcm_loop_filter_disabled_flag : 1;
+    uint32_t long_term_ref_pics_present_flag : 1;
+    uint32_t sps_temporal_mvp_enabled_flag : 1;
+    uint32_t strong_intra_smoothing_enabled_flag : 1;
+    uint32_t vui_parameters_present_flag : 1;
+    uint32_t sps_extension_present_flag : 1;
+    uint32_t sps_range_extension_flag : 1;
+
+    // extension SPS flags, valid when STD_VIDEO_H265_PROFILE_IDC_FORMAT_RANGE_EXTENSIONS is set
+    uint32_t transform_skip_rotation_enabled_flag : 1;
+    uint32_t transform_skip_context_enabled_flag : 1;
+    uint32_t implicit_rdpcm_enabled_flag : 1;
+    uint32_t explicit_rdpcm_enabled_flag : 1;
+    uint32_t extended_precision_processing_flag : 1;
+    uint32_t intra_smoothing_disabled_flag : 1;
+    uint32_t high_precision_offsets_enabled_flag : 1;
+    uint32_t persistent_rice_adaptation_enabled_flag : 1;
+    uint32_t cabac_bypass_alignment_enabled_flag : 1;
+
+    // extension SPS flags, valid when STD_VIDEO_H265_PROFILE_IDC_SCC_EXTENSIONS is set
+    uint32_t sps_curr_pic_ref_enabled_flag : 1;
+    uint32_t palette_mode_enabled_flag : 1;
+    uint32_t sps_palette_predictor_initializer_present_flag : 1;
+    uint32_t intra_boundary_filtering_disabled_flag : 1;
+} StdVideoH265SpsFlags;
+
+typedef struct StdVideoH265SequenceParameterSet
+{
+    StdVideoH265ProfileIdc               profile_idc;
+    StdVideoH265Level                    level_idc;
+    uint32_t                             pic_width_in_luma_samples;
+    uint32_t                             pic_height_in_luma_samples;
+    uint8_t                              sps_video_parameter_set_id;
+    uint8_t                              sps_max_sub_layers_minus1;
+    uint8_t                              sps_seq_parameter_set_id;
+    uint8_t                              chroma_format_idc;
+    uint8_t                              bit_depth_luma_minus8;
+    uint8_t                              bit_depth_chroma_minus8;
+    uint8_t                              log2_max_pic_order_cnt_lsb_minus4;
+    uint8_t                              sps_max_dec_pic_buffering_minus1;
+    uint8_t                              log2_min_luma_coding_block_size_minus3;
+    uint8_t                              log2_diff_max_min_luma_coding_block_size;
+    uint8_t                              log2_min_luma_transform_block_size_minus2;
+    uint8_t                              log2_diff_max_min_luma_transform_block_size;
+    uint8_t                              max_transform_hierarchy_depth_inter;
+    uint8_t                              max_transform_hierarchy_depth_intra;
+    uint8_t                              num_short_term_ref_pic_sets;
+    uint8_t                              num_long_term_ref_pics_sps;
+    uint8_t                              pcm_sample_bit_depth_luma_minus1;
+    uint8_t                              pcm_sample_bit_depth_chroma_minus1;
+    uint8_t                              log2_min_pcm_luma_coding_block_size_minus3;
+    uint8_t                              log2_diff_max_min_pcm_luma_coding_block_size;
+    uint32_t                             conf_win_left_offset;
+    uint32_t                             conf_win_right_offset;
+    uint32_t                             conf_win_top_offset;
+    uint32_t                             conf_win_bottom_offset;
+    StdVideoH265DecPicBufMgr*            pDecPicBufMgr;
+    StdVideoH265SpsFlags                 flags;
+    StdVideoH265ScalingLists*            pScalingLists;             // Must be a valid pointer if sps_scaling_list_data_present_flag is set
+    StdVideoH265SequenceParameterSetVui* pSequenceParameterSetVui;  // Must be a valid pointer if StdVideoH265SpsFlags:vui_parameters_present_flag is set palette_max_size;
+
+    // extension SPS flags, valid when STD_VIDEO_H265_PROFILE_IDC_SCC_EXTENSIONS is set
+    uint8_t                              palette_max_size;
+    uint8_t                              delta_palette_max_predictor_size;
+    uint8_t                              motion_vector_resolution_control_idc;
+    uint8_t                              sps_num_palette_predictor_initializer_minus1;
+    StdVideoH265PredictorPaletteEntries* pPredictorPaletteEntries;  // Must be a valid pointer if sps_palette_predictor_initializer_present_flag is set
+} StdVideoH265SequenceParameterSet;
+
+
+typedef struct StdVideoH265PpsFlags {
+    uint32_t dependent_slice_segments_enabled_flag : 1;
+    uint32_t output_flag_present_flag : 1;
+    uint32_t sign_data_hiding_enabled_flag : 1;
+    uint32_t cabac_init_present_flag : 1;
+    uint32_t constrained_intra_pred_flag : 1;
+    uint32_t transform_skip_enabled_flag : 1;
+    uint32_t cu_qp_delta_enabled_flag : 1;
+    uint32_t pps_slice_chroma_qp_offsets_present_flag : 1;
+    uint32_t weighted_pred_flag : 1;
+    uint32_t weighted_bipred_flag : 1;
+    uint32_t transquant_bypass_enabled_flag : 1;
+    uint32_t tiles_enabled_flag : 1;
+    uint32_t entropy_coding_sync_enabled_flag : 1;
+    uint32_t uniform_spacing_flag : 1;
+    uint32_t loop_filter_across_tiles_enabled_flag : 1;
+    uint32_t pps_loop_filter_across_slices_enabled_flag : 1;
+    uint32_t deblocking_filter_control_present_flag : 1;
+    uint32_t deblocking_filter_override_enabled_flag : 1;
+    uint32_t pps_deblocking_filter_disabled_flag : 1;
+    uint32_t pps_scaling_list_data_present_flag : 1;
+    uint32_t lists_modification_present_flag : 1;
+    uint32_t slice_segment_header_extension_present_flag : 1;
+    uint32_t pps_extension_present_flag : 1;
+
+    // extension PPS flags, valid when STD_VIDEO_H265_PROFILE_IDC_FORMAT_RANGE_EXTENSIONS is set
+    uint32_t cross_component_prediction_enabled_flag : 1;
+    uint32_t chroma_qp_offset_list_enabled_flag : 1;
+
+    // extension PPS flags, valid when STD_VIDEO_H265_PROFILE_IDC_SCC_EXTENSIONS is set
+    uint32_t pps_curr_pic_ref_enabled_flag : 1;
+    uint32_t residual_adaptive_colour_transform_enabled_flag : 1;
+    uint32_t pps_slice_act_qp_offsets_present_flag : 1;
+    uint32_t pps_palette_predictor_initializer_present_flag : 1;
+    uint32_t monochrome_palette_flag : 1;
+    uint32_t pps_range_extension_flag : 1;
+} StdVideoH265PpsFlags;
+
+typedef struct StdVideoH265PictureParameterSet
+{
+    uint8_t                              pps_pic_parameter_set_id;
+    uint8_t                              pps_seq_parameter_set_id;
+    uint8_t                              num_extra_slice_header_bits;
+    uint8_t                              num_ref_idx_l0_default_active_minus1;
+    uint8_t                              num_ref_idx_l1_default_active_minus1;
+    int8_t                               init_qp_minus26;
+    uint8_t                              diff_cu_qp_delta_depth;
+    int8_t                               pps_cb_qp_offset;
+    int8_t                               pps_cr_qp_offset;
+    uint8_t                              num_tile_columns_minus1;
+    uint8_t                              num_tile_rows_minus1;
+    uint16_t                             column_width_minus1[STD_VIDEO_H265_CHROMA_QP_OFFSET_TILE_COLS_LIST_SIZE];
+    uint16_t                             row_height_minus1[STD_VIDEO_H265_CHROMA_QP_OFFSET_TILE_ROWS_LIST_SIZE];
+    int8_t                               pps_beta_offset_div2;
+    int8_t                               pps_tc_offset_div2;
+    uint8_t                              log2_parallel_merge_level_minus2;
+    StdVideoH265PpsFlags                 flags;
+    StdVideoH265ScalingLists*            pScalingLists; // Must be a valid pointer if pps_scaling_list_data_present_flag is set
+
+    // extension PPS, valid when STD_VIDEO_H265_PROFILE_IDC_FORMAT_RANGE_EXTENSIONS is set
+    uint8_t                              log2_max_transform_skip_block_size_minus2;
+    uint8_t                              diff_cu_chroma_qp_offset_depth;
+    uint8_t                              chroma_qp_offset_list_len_minus1;
+    int8_t                               cb_qp_offset_list[STD_VIDEO_H265_CHROMA_QP_OFFSET_LIST_SIZE];
+    int8_t                               cr_qp_offset_list[STD_VIDEO_H265_CHROMA_QP_OFFSET_LIST_SIZE];
+    uint8_t                              log2_sao_offset_scale_luma;
+    uint8_t                              log2_sao_offset_scale_chroma;
+
+    // extension PPS, valid when STD_VIDEO_H265_PROFILE_IDC_SCC_EXTENSIONS is set
+    int8_t                               pps_act_y_qp_offset_plus5;
+    int8_t                               pps_act_cb_qp_offset_plus5;
+    int8_t                               pps_act_cr_qp_offset_plus5;
+    uint8_t                              pps_num_palette_predictor_initializer;
+    uint8_t                              luma_bit_depth_entry_minus8;
+    uint8_t                              chroma_bit_depth_entry_minus8;
+    StdVideoH265PredictorPaletteEntries* pPredictorPaletteEntries;  // Must be a valid pointer if pps_palette_predictor_initializer_present_flag is set
+} StdVideoH265PictureParameterSet;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // VULKAN_VIDEO_CODEC_H265STD_H_
diff --git a/external/Vulkan/external/vk_video/vulkan_video_codec_h265std_decode.h b/external/Vulkan/external/vk_video/vulkan_video_codec_h265std_decode.h
new file mode 100644
index 0000000000000000000000000000000000000000..a1efa055998ae585b8ada44c0a47e8739e34f263
--- /dev/null
+++ b/external/Vulkan/external/vk_video/vulkan_video_codec_h265std_decode.h
@@ -0,0 +1,64 @@
+/*
+** Copyright (c) 2019-2021 The Khronos Group Inc.
+**
+** SPDX-License-Identifier: Apache-2.0
+*/
+
+#ifndef VULKAN_VIDEO_CODEC_H265STD_DECODE_H_
+#define VULKAN_VIDEO_CODEC_H265STD_DECODE_H_ 1
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "vk_video/vulkan_video_codec_h265std.h"
+
+// *************************************************
+// Video h265 Decode related parameters:
+// *************************************************
+
+#define STD_VIDEO_DECODE_H265_REF_PIC_SET_LIST_SIZE 8
+
+typedef struct StdVideoDecodeH265PictureInfoFlags {
+    uint32_t IrapPicFlag : 1;
+    uint32_t IdrPicFlag  : 1;
+    uint32_t IsReference : 1;
+    uint32_t short_term_ref_pic_set_sps_flag : 1;
+} StdVideoDecodeH265PictureInfoFlags;
+
+typedef struct StdVideoDecodeH265PictureInfo {
+    uint8_t                            vps_video_parameter_set_id;
+    uint8_t                            sps_seq_parameter_set_id;
+    uint8_t                            pps_pic_parameter_set_id;
+    uint8_t                            num_short_term_ref_pic_sets;
+    int32_t                            PicOrderCntVal;
+    uint16_t                           NumBitsForSTRefPicSetInSlice; // number of bits used in st_ref_pic_set()
+                                                                     //when short_term_ref_pic_set_sps_flag is 0; otherwise set to 0.
+    uint8_t                            NumDeltaPocsOfRefRpsIdx;      // NumDeltaPocs[ RefRpsIdx ] when short_term_ref_pic_set_sps_flag = 1, otherwise 0
+    uint8_t                            RefPicSetStCurrBefore[STD_VIDEO_DECODE_H265_REF_PIC_SET_LIST_SIZE]; // slotIndex as used in
+                                                                     // VkVideoReferenceSlotKHR structures representing
+                                                                     //pReferenceSlots in VkVideoDecodeInfoKHR, 0xff for invalid slotIndex
+    uint8_t                            RefPicSetStCurrAfter[STD_VIDEO_DECODE_H265_REF_PIC_SET_LIST_SIZE];   // slotIndex as used in
+                                                                     // VkVideoReferenceSlotKHR structures representing
+                                                                     //pReferenceSlots in VkVideoDecodeInfoKHR, 0xff for invalid slotIndex
+    uint8_t                            RefPicSetLtCurr[STD_VIDEO_DECODE_H265_REF_PIC_SET_LIST_SIZE]; // slotIndex as used in
+                                                                     // VkVideoReferenceSlotKHR structures representing
+                                                                     //pReferenceSlots in VkVideoDecodeInfoKHR, 0xff for invalid slotIndex
+    StdVideoDecodeH265PictureInfoFlags flags;
+} StdVideoDecodeH265PictureInfo;
+
+typedef struct StdVideoDecodeH265ReferenceInfoFlags {
+    uint32_t is_long_term : 1;
+    uint32_t is_non_existing : 1;
+} StdVideoDecodeH265ReferenceInfoFlags;
+
+typedef struct StdVideoDecodeH265ReferenceInfo {
+    int32_t                              PicOrderCntVal;
+    StdVideoDecodeH265ReferenceInfoFlags flags;
+} StdVideoDecodeH265ReferenceInfo;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // VULKAN_VIDEO_CODEC_H265STD_DECODE_H_
diff --git a/external/Vulkan/external/vk_video/vulkan_video_codec_h265std_encode.h b/external/Vulkan/external/vk_video/vulkan_video_codec_h265std_encode.h
new file mode 100644
index 0000000000000000000000000000000000000000..ffffef2062e36e95dc30bfea84224a7f365ce340
--- /dev/null
+++ b/external/Vulkan/external/vk_video/vulkan_video_codec_h265std_encode.h
@@ -0,0 +1,122 @@
+/*
+** Copyright (c) 2019-2021 The Khronos Group Inc.
+**
+** SPDX-License-Identifier: Apache-2.0
+*/
+
+#ifndef VULKAN_VIDEO_CODEC_H265STD_ENCODE_H_
+#define VULKAN_VIDEO_CODEC_H265STD_ENCODE_H_ 1
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "vk_video/vulkan_video_codec_h265std.h"
+
+// *************************************************
+// Video h265 Encode related parameters:
+// *************************************************
+
+#define STD_VIDEO_ENCODE_H265_LUMA_LIST_SIZE   15
+#define STD_VIDEO_ENCODE_H265_CHROMA_LIST_SIZE 15
+#define STD_VIDEO_ENCODE_H265_CHROMA_LISTS_NUM  2
+
+typedef struct StdVideoEncodeH265SliceHeaderFlags {
+    uint32_t first_slice_segment_in_pic_flag : 1;
+    uint32_t no_output_of_prior_pics_flag : 1;
+    uint32_t dependent_slice_segment_flag : 1;
+    uint32_t short_term_ref_pic_set_sps_flag : 1;
+    uint32_t slice_temporal_mvp_enable_flag : 1;
+    uint32_t slice_sao_luma_flag : 1;
+    uint32_t slice_sao_chroma_flag : 1;
+    uint32_t num_ref_idx_active_override_flag : 1;
+    uint32_t mvd_l1_zero_flag : 1;
+    uint32_t cabac_init_flag : 1;
+    uint32_t slice_deblocking_filter_disable_flag : 1;
+    uint32_t collocated_from_l0_flag : 1;
+    uint32_t slice_loop_filter_across_slices_enabled_flag : 1;
+    uint32_t bLastSliceInPic : 1;
+    uint32_t reservedBits : 18;
+    uint16_t luma_weight_l0_flag;   // bit 0 - num_ref_idx_l0_active_minus1
+    uint16_t chroma_weight_l0_flag; // bit 0 - num_ref_idx_l0_active_minus1
+    uint16_t luma_weight_l1_flag;   // bit 0 - num_ref_idx_l1_active_minus1
+    uint16_t chroma_weight_l1_flag; // bit 0 - num_ref_idx_l1_active_minus1
+} StdVideoEncodeH265SliceHeaderFlags;
+
+typedef struct StdVideoEncodeH265SliceHeader {
+    StdVideoH265SliceType              slice_type;
+    uint8_t                            slice_pic_parameter_set_id;
+    uint8_t                            num_short_term_ref_pic_sets;
+    uint32_t                           slice_segment_address;
+    uint8_t                            short_term_ref_pic_set_idx;
+    uint8_t                            num_long_term_sps;
+    uint8_t                            num_long_term_pics;
+    uint8_t                            collocated_ref_idx;
+    uint8_t                            num_ref_idx_l0_active_minus1; // [0, 14]
+    uint8_t                            num_ref_idx_l1_active_minus1; // [0, 14]
+    uint8_t                            luma_log2_weight_denom;       // [0, 7]
+    int8_t                             delta_chroma_log2_weight_denom;
+    int8_t                             delta_luma_weight_l0[STD_VIDEO_ENCODE_H265_LUMA_LIST_SIZE];
+    int8_t                             luma_offset_l0[STD_VIDEO_ENCODE_H265_LUMA_LIST_SIZE];
+    int8_t                             delta_chroma_weight_l0[STD_VIDEO_ENCODE_H265_CHROMA_LIST_SIZE][STD_VIDEO_ENCODE_H265_CHROMA_LISTS_NUM];
+    int8_t                             delta_chroma_offset_l0[STD_VIDEO_ENCODE_H265_CHROMA_LIST_SIZE][STD_VIDEO_ENCODE_H265_CHROMA_LISTS_NUM];
+    int8_t                             delta_luma_weight_l1[STD_VIDEO_ENCODE_H265_LUMA_LIST_SIZE];
+    int8_t                             luma_offset_l1[STD_VIDEO_ENCODE_H265_LUMA_LIST_SIZE];
+    int8_t                             delta_chroma_weight_l1[STD_VIDEO_ENCODE_H265_CHROMA_LIST_SIZE][STD_VIDEO_ENCODE_H265_CHROMA_LISTS_NUM];
+    int8_t                             delta_chroma_offset_l1[STD_VIDEO_ENCODE_H265_CHROMA_LIST_SIZE][STD_VIDEO_ENCODE_H265_CHROMA_LISTS_NUM];
+    uint8_t                            MaxNumMergeCand;
+    int8_t                             slice_qp_delta;
+    int8_t                             slice_cb_qp_offset;         // [-12, 12]
+    int8_t                             slice_cr_qp_offset;         // [-12, 12]
+    int8_t                             slice_beta_offset_div2;    // [-6, 6]
+    int8_t                             slice_tc_offset_div2;       // [-6, 6]
+    int8_t                             slice_act_y_qp_offset;
+    int8_t                             slice_act_cb_qp_offset;
+    int8_t                             slice_act_cr_qp_offset;
+    StdVideoEncodeH265SliceHeaderFlags flags;
+} StdVideoEncodeH265SliceHeader;
+
+typedef struct StdVideoEncodeH265ReferenceModificationFlags {
+    uint32_t ref_pic_list_modification_flag_l0 : 1;
+    uint32_t ref_pic_list_modification_flag_l1 : 1;
+} StdVideoEncodeH265ReferenceModificationFlags;
+
+typedef struct StdVideoEncodeH265ReferenceModifications {
+    StdVideoEncodeH265ReferenceModificationFlags flags;
+    uint8_t                                      referenceList0ModificationsCount; // num_ref_idx_l0_active_minus1
+    uint8_t*                                     pReferenceList0Modifications;     // list_entry_l0
+    uint8_t                                      referenceList1ModificationsCount; // num_ref_idx_l1_active_minus1
+    uint8_t*                                     pReferenceList1Modifications;     // list_entry_l1
+} StdVideoEncodeH265ReferenceModifications;
+
+typedef struct StdVideoEncodeH265PictureInfoFlags {
+    uint32_t is_reference_flag : 1;
+    uint32_t IrapPicFlag : 1;
+    uint32_t long_term_flag : 1;
+} StdVideoEncodeH265PictureInfoFlags;
+
+typedef struct StdVideoEncodeH265PictureInfo {
+    StdVideoH265PictureType            PictureType;
+    uint8_t                            sps_video_parameter_set_id;
+    uint8_t                            pps_seq_parameter_set_id;
+    int32_t                            PicOrderCntVal;
+    uint8_t                            TemporalId;
+    StdVideoEncodeH265PictureInfoFlags flags;
+} StdVideoEncodeH265PictureInfo;
+
+typedef struct StdVideoEncodeH265ReferenceInfoFlags {
+    uint32_t is_long_term : 1;
+    uint32_t isUsedFlag : 1;
+} StdVideoEncodeH265ReferenceInfoFlags;
+
+typedef struct StdVideoEncodeH265ReferenceInfo {
+    int32_t                              PicOrderCntVal;
+    uint8_t                              TemporalId;
+    StdVideoEncodeH265ReferenceInfoFlags flags;
+} StdVideoEncodeH265ReferenceInfo;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // VULKAN_VIDEO_CODEC_H265STD_ENCODE_H_
diff --git a/external/Vulkan/external/vk_video/vulkan_video_codecs_common.h b/external/Vulkan/external/vk_video/vulkan_video_codecs_common.h
new file mode 100644
index 0000000000000000000000000000000000000000..8cc227a63685b0a364c66cc4cffb39cb4b983e2e
--- /dev/null
+++ b/external/Vulkan/external/vk_video/vulkan_video_codecs_common.h
@@ -0,0 +1,21 @@
+/*
+** Copyright (c) 2019-2021 The Khronos Group Inc.
+**
+** SPDX-License-Identifier: Apache-2.0
+*/
+
+#ifndef VULKAN_VIDEO_CODEC_COMMON_H_
+#define VULKAN_VIDEO_CODEC_COMMON_H_ 1
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define VK_MAKE_VIDEO_STD_VERSION(major, minor, patch) \
+    ((((uint32_t)(major)) << 22) | (((uint32_t)(minor)) << 12) | ((uint32_t)(patch)))
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // VULKAN_VIDEO_CODEC_COMMON_H_
diff --git a/external/Vulkan/external/vulkan/vk_platform.h b/external/Vulkan/external/vulkan/vk_platform.h
new file mode 100644
index 0000000000000000000000000000000000000000..18b913abc6ad9ec592f3ff0cd62ef6f5e9b6d022
--- /dev/null
+++ b/external/Vulkan/external/vulkan/vk_platform.h
@@ -0,0 +1,84 @@
+//
+// File: vk_platform.h
+//
+/*
+** Copyright 2014-2021 The Khronos Group Inc.
+**
+** SPDX-License-Identifier: Apache-2.0
+*/
+
+
+#ifndef VK_PLATFORM_H_
+#define VK_PLATFORM_H_
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif // __cplusplus
+
+/*
+***************************************************************************************************
+*   Platform-specific directives and type declarations
+***************************************************************************************************
+*/
+
+/* Platform-specific calling convention macros.
+ *
+ * Platforms should define these so that Vulkan clients call Vulkan commands
+ * with the same calling conventions that the Vulkan implementation expects.
+ *
+ * VKAPI_ATTR - Placed before the return type in function declarations.
+ *              Useful for C++11 and GCC/Clang-style function attribute syntax.
+ * VKAPI_CALL - Placed after the return type in function declarations.
+ *              Useful for MSVC-style calling convention syntax.
+ * VKAPI_PTR  - Placed between the '(' and '*' in function pointer types.
+ *
+ * Function declaration:  VKAPI_ATTR void VKAPI_CALL vkCommand(void);
+ * Function pointer type: typedef void (VKAPI_PTR *PFN_vkCommand)(void);
+ */
+#if defined(_WIN32)
+    // On Windows, Vulkan commands use the stdcall convention
+    #define VKAPI_ATTR
+    #define VKAPI_CALL __stdcall
+    #define VKAPI_PTR  VKAPI_CALL
+#elif defined(__ANDROID__) && defined(__ARM_ARCH) && __ARM_ARCH < 7
+    #error "Vulkan isn't supported for the 'armeabi' NDK ABI"
+#elif defined(__ANDROID__) && defined(__ARM_ARCH) && __ARM_ARCH >= 7 && defined(__ARM_32BIT_STATE)
+    // On Android 32-bit ARM targets, Vulkan functions use the "hardfloat"
+    // calling convention, i.e. float parameters are passed in registers. This
+    // is true even if the rest of the application passes floats on the stack,
+    // as it does by default when compiling for the armeabi-v7a NDK ABI.
+    #define VKAPI_ATTR __attribute__((pcs("aapcs-vfp")))
+    #define VKAPI_CALL
+    #define VKAPI_PTR  VKAPI_ATTR
+#else
+    // On other platforms, use the default calling convention
+    #define VKAPI_ATTR
+    #define VKAPI_CALL
+    #define VKAPI_PTR
+#endif
+
+#if !defined(VK_NO_STDDEF_H)
+    #include <stddef.h>
+#endif // !defined(VK_NO_STDDEF_H)
+
+#if !defined(VK_NO_STDINT_H)
+    #if defined(_MSC_VER) && (_MSC_VER < 1600)
+        typedef signed   __int8  int8_t;
+        typedef unsigned __int8  uint8_t;
+        typedef signed   __int16 int16_t;
+        typedef unsigned __int16 uint16_t;
+        typedef signed   __int32 int32_t;
+        typedef unsigned __int32 uint32_t;
+        typedef signed   __int64 int64_t;
+        typedef unsigned __int64 uint64_t;
+    #else
+        #include <stdint.h>
+    #endif
+#endif // !defined(VK_NO_STDINT_H)
+
+#ifdef __cplusplus
+} // extern "C"
+#endif // __cplusplus
+
+#endif
diff --git a/external/Vulkan/external/vulkan/vulkan.h b/external/Vulkan/external/vulkan/vulkan.h
new file mode 100644
index 0000000000000000000000000000000000000000..3f7cdba584518b5610f4d65ab26eb789a0ea5ea7
--- /dev/null
+++ b/external/Vulkan/external/vulkan/vulkan.h
@@ -0,0 +1,92 @@
+#ifndef VULKAN_H_
+#define VULKAN_H_ 1
+
+/*
+** Copyright 2015-2021 The Khronos Group Inc.
+**
+** SPDX-License-Identifier: Apache-2.0
+*/
+
+#include "vk_platform.h"
+#include "vulkan_core.h"
+
+#ifdef VK_USE_PLATFORM_ANDROID_KHR
+#include "vulkan_android.h"
+#endif
+
+#ifdef VK_USE_PLATFORM_FUCHSIA
+#include <zircon/types.h>
+#include "vulkan_fuchsia.h"
+#endif
+
+#ifdef VK_USE_PLATFORM_IOS_MVK
+#include "vulkan_ios.h"
+#endif
+
+
+#ifdef VK_USE_PLATFORM_MACOS_MVK
+#include "vulkan_macos.h"
+#endif
+
+#ifdef VK_USE_PLATFORM_METAL_EXT
+#include "vulkan_metal.h"
+#endif
+
+#ifdef VK_USE_PLATFORM_VI_NN
+#include "vulkan_vi.h"
+#endif
+
+
+#ifdef VK_USE_PLATFORM_WAYLAND_KHR
+#include <wayland-client.h>
+#include "vulkan_wayland.h"
+#endif
+
+
+#ifdef VK_USE_PLATFORM_WIN32_KHR
+#include <windows.h>
+#include "vulkan_win32.h"
+#endif
+
+
+#ifdef VK_USE_PLATFORM_XCB_KHR
+#include <xcb/xcb.h>
+#include "vulkan_xcb.h"
+#endif
+
+
+#ifdef VK_USE_PLATFORM_XLIB_KHR
+#include <X11/Xlib.h>
+#include "vulkan_xlib.h"
+#endif
+
+
+#ifdef VK_USE_PLATFORM_DIRECTFB_EXT
+#include <directfb.h>
+#include "vulkan_directfb.h"
+#endif
+
+
+#ifdef VK_USE_PLATFORM_XLIB_XRANDR_EXT
+#include <X11/Xlib.h>
+#include <X11/extensions/Xrandr.h>
+#include "vulkan_xlib_xrandr.h"
+#endif
+
+
+#ifdef VK_USE_PLATFORM_GGP
+#include <ggp_c/vulkan_types.h>
+#include "vulkan_ggp.h"
+#endif
+
+
+#ifdef VK_USE_PLATFORM_SCREEN_QNX
+#include <screen/screen.h>
+#include "vulkan_screen.h"
+#endif
+
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+#include "vulkan_beta.h"
+#endif
+
+#endif // VULKAN_H_
diff --git a/external/Vulkan/external/vulkan/vulkan_android.h b/external/Vulkan/external/vulkan/vulkan_android.h
new file mode 100644
index 0000000000000000000000000000000000000000..a8a830673b2e612d6d03c86f47f9452fe93eae91
--- /dev/null
+++ b/external/Vulkan/external/vulkan/vulkan_android.h
@@ -0,0 +1,125 @@
+#ifndef VULKAN_ANDROID_H_
+#define VULKAN_ANDROID_H_ 1
+
+/*
+** Copyright 2015-2021 The Khronos Group Inc.
+**
+** SPDX-License-Identifier: Apache-2.0
+*/
+
+/*
+** This header is generated from the Khronos Vulkan XML API Registry.
+**
+*/
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+
+#define VK_KHR_android_surface 1
+struct ANativeWindow;
+#define VK_KHR_ANDROID_SURFACE_SPEC_VERSION 6
+#define VK_KHR_ANDROID_SURFACE_EXTENSION_NAME "VK_KHR_android_surface"
+typedef VkFlags VkAndroidSurfaceCreateFlagsKHR;
+typedef struct VkAndroidSurfaceCreateInfoKHR {
+    VkStructureType                   sType;
+    const void*                       pNext;
+    VkAndroidSurfaceCreateFlagsKHR    flags;
+    struct ANativeWindow*             window;
+} VkAndroidSurfaceCreateInfoKHR;
+
+typedef VkResult (VKAPI_PTR *PFN_vkCreateAndroidSurfaceKHR)(VkInstance instance, const VkAndroidSurfaceCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateAndroidSurfaceKHR(
+    VkInstance                                  instance,
+    const VkAndroidSurfaceCreateInfoKHR*        pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkSurfaceKHR*                               pSurface);
+#endif
+
+
+#define VK_ANDROID_external_memory_android_hardware_buffer 1
+struct AHardwareBuffer;
+#define VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_SPEC_VERSION 4
+#define VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME "VK_ANDROID_external_memory_android_hardware_buffer"
+typedef struct VkAndroidHardwareBufferUsageANDROID {
+    VkStructureType    sType;
+    void*              pNext;
+    uint64_t           androidHardwareBufferUsage;
+} VkAndroidHardwareBufferUsageANDROID;
+
+typedef struct VkAndroidHardwareBufferPropertiesANDROID {
+    VkStructureType    sType;
+    void*              pNext;
+    VkDeviceSize       allocationSize;
+    uint32_t           memoryTypeBits;
+} VkAndroidHardwareBufferPropertiesANDROID;
+
+typedef struct VkAndroidHardwareBufferFormatPropertiesANDROID {
+    VkStructureType                  sType;
+    void*                            pNext;
+    VkFormat                         format;
+    uint64_t                         externalFormat;
+    VkFormatFeatureFlags             formatFeatures;
+    VkComponentMapping               samplerYcbcrConversionComponents;
+    VkSamplerYcbcrModelConversion    suggestedYcbcrModel;
+    VkSamplerYcbcrRange              suggestedYcbcrRange;
+    VkChromaLocation                 suggestedXChromaOffset;
+    VkChromaLocation                 suggestedYChromaOffset;
+} VkAndroidHardwareBufferFormatPropertiesANDROID;
+
+typedef struct VkImportAndroidHardwareBufferInfoANDROID {
+    VkStructureType            sType;
+    const void*                pNext;
+    struct AHardwareBuffer*    buffer;
+} VkImportAndroidHardwareBufferInfoANDROID;
+
+typedef struct VkMemoryGetAndroidHardwareBufferInfoANDROID {
+    VkStructureType    sType;
+    const void*        pNext;
+    VkDeviceMemory     memory;
+} VkMemoryGetAndroidHardwareBufferInfoANDROID;
+
+typedef struct VkExternalFormatANDROID {
+    VkStructureType    sType;
+    void*              pNext;
+    uint64_t           externalFormat;
+} VkExternalFormatANDROID;
+
+typedef struct VkAndroidHardwareBufferFormatProperties2ANDROID {
+    VkStructureType                  sType;
+    void*                            pNext;
+    VkFormat                         format;
+    uint64_t                         externalFormat;
+    VkFormatFeatureFlags2KHR         formatFeatures;
+    VkComponentMapping               samplerYcbcrConversionComponents;
+    VkSamplerYcbcrModelConversion    suggestedYcbcrModel;
+    VkSamplerYcbcrRange              suggestedYcbcrRange;
+    VkChromaLocation                 suggestedXChromaOffset;
+    VkChromaLocation                 suggestedYChromaOffset;
+} VkAndroidHardwareBufferFormatProperties2ANDROID;
+
+typedef VkResult (VKAPI_PTR *PFN_vkGetAndroidHardwareBufferPropertiesANDROID)(VkDevice device, const struct AHardwareBuffer* buffer, VkAndroidHardwareBufferPropertiesANDROID* pProperties);
+typedef VkResult (VKAPI_PTR *PFN_vkGetMemoryAndroidHardwareBufferANDROID)(VkDevice device, const VkMemoryGetAndroidHardwareBufferInfoANDROID* pInfo, struct AHardwareBuffer** pBuffer);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkGetAndroidHardwareBufferPropertiesANDROID(
+    VkDevice                                    device,
+    const struct AHardwareBuffer*               buffer,
+    VkAndroidHardwareBufferPropertiesANDROID*   pProperties);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetMemoryAndroidHardwareBufferANDROID(
+    VkDevice                                    device,
+    const VkMemoryGetAndroidHardwareBufferInfoANDROID* pInfo,
+    struct AHardwareBuffer**                    pBuffer);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/external/Vulkan/external/vulkan/vulkan_beta.h b/external/Vulkan/external/vulkan/vulkan_beta.h
new file mode 100644
index 0000000000000000000000000000000000000000..d2f34d1cd002584e597b3500843b3cf4ee3ddaf5
--- /dev/null
+++ b/external/Vulkan/external/vulkan/vulkan_beta.h
@@ -0,0 +1,833 @@
+#ifndef VULKAN_BETA_H_
+#define VULKAN_BETA_H_ 1
+
+/*
+** Copyright 2015-2021 The Khronos Group Inc.
+**
+** SPDX-License-Identifier: Apache-2.0
+*/
+
+/*
+** This header is generated from the Khronos Vulkan XML API Registry.
+**
+*/
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+
+#define VK_KHR_video_queue 1
+VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkVideoSessionKHR)
+VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkVideoSessionParametersKHR)
+#define VK_KHR_VIDEO_QUEUE_SPEC_VERSION   2
+#define VK_KHR_VIDEO_QUEUE_EXTENSION_NAME "VK_KHR_video_queue"
+
+typedef enum VkQueryResultStatusKHR {
+    VK_QUERY_RESULT_STATUS_ERROR_KHR = -1,
+    VK_QUERY_RESULT_STATUS_NOT_READY_KHR = 0,
+    VK_QUERY_RESULT_STATUS_COMPLETE_KHR = 1,
+    VK_QUERY_RESULT_STATUS_MAX_ENUM_KHR = 0x7FFFFFFF
+} VkQueryResultStatusKHR;
+
+typedef enum VkVideoCodecOperationFlagBitsKHR {
+    VK_VIDEO_CODEC_OPERATION_INVALID_BIT_KHR = 0,
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_VIDEO_CODEC_OPERATION_ENCODE_H264_BIT_EXT = 0x00010000,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_VIDEO_CODEC_OPERATION_ENCODE_H265_BIT_EXT = 0x00020000,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_VIDEO_CODEC_OPERATION_DECODE_H264_BIT_EXT = 0x00000001,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_VIDEO_CODEC_OPERATION_DECODE_H265_BIT_EXT = 0x00000002,
+#endif
+    VK_VIDEO_CODEC_OPERATION_FLAG_BITS_MAX_ENUM_KHR = 0x7FFFFFFF
+} VkVideoCodecOperationFlagBitsKHR;
+typedef VkFlags VkVideoCodecOperationFlagsKHR;
+
+typedef enum VkVideoChromaSubsamplingFlagBitsKHR {
+    VK_VIDEO_CHROMA_SUBSAMPLING_INVALID_BIT_KHR = 0,
+    VK_VIDEO_CHROMA_SUBSAMPLING_MONOCHROME_BIT_KHR = 0x00000001,
+    VK_VIDEO_CHROMA_SUBSAMPLING_420_BIT_KHR = 0x00000002,
+    VK_VIDEO_CHROMA_SUBSAMPLING_422_BIT_KHR = 0x00000004,
+    VK_VIDEO_CHROMA_SUBSAMPLING_444_BIT_KHR = 0x00000008,
+    VK_VIDEO_CHROMA_SUBSAMPLING_FLAG_BITS_MAX_ENUM_KHR = 0x7FFFFFFF
+} VkVideoChromaSubsamplingFlagBitsKHR;
+typedef VkFlags VkVideoChromaSubsamplingFlagsKHR;
+
+typedef enum VkVideoComponentBitDepthFlagBitsKHR {
+    VK_VIDEO_COMPONENT_BIT_DEPTH_INVALID_KHR = 0,
+    VK_VIDEO_COMPONENT_BIT_DEPTH_8_BIT_KHR = 0x00000001,
+    VK_VIDEO_COMPONENT_BIT_DEPTH_10_BIT_KHR = 0x00000004,
+    VK_VIDEO_COMPONENT_BIT_DEPTH_12_BIT_KHR = 0x00000010,
+    VK_VIDEO_COMPONENT_BIT_DEPTH_FLAG_BITS_MAX_ENUM_KHR = 0x7FFFFFFF
+} VkVideoComponentBitDepthFlagBitsKHR;
+typedef VkFlags VkVideoComponentBitDepthFlagsKHR;
+
+typedef enum VkVideoCapabilityFlagBitsKHR {
+    VK_VIDEO_CAPABILITY_PROTECTED_CONTENT_BIT_KHR = 0x00000001,
+    VK_VIDEO_CAPABILITY_SEPARATE_REFERENCE_IMAGES_BIT_KHR = 0x00000002,
+    VK_VIDEO_CAPABILITY_FLAG_BITS_MAX_ENUM_KHR = 0x7FFFFFFF
+} VkVideoCapabilityFlagBitsKHR;
+typedef VkFlags VkVideoCapabilityFlagsKHR;
+
+typedef enum VkVideoSessionCreateFlagBitsKHR {
+    VK_VIDEO_SESSION_CREATE_DEFAULT_KHR = 0,
+    VK_VIDEO_SESSION_CREATE_PROTECTED_CONTENT_BIT_KHR = 0x00000001,
+    VK_VIDEO_SESSION_CREATE_FLAG_BITS_MAX_ENUM_KHR = 0x7FFFFFFF
+} VkVideoSessionCreateFlagBitsKHR;
+typedef VkFlags VkVideoSessionCreateFlagsKHR;
+typedef VkFlags VkVideoBeginCodingFlagsKHR;
+typedef VkFlags VkVideoEndCodingFlagsKHR;
+
+typedef enum VkVideoCodingControlFlagBitsKHR {
+    VK_VIDEO_CODING_CONTROL_DEFAULT_KHR = 0,
+    VK_VIDEO_CODING_CONTROL_RESET_BIT_KHR = 0x00000001,
+    VK_VIDEO_CODING_CONTROL_FLAG_BITS_MAX_ENUM_KHR = 0x7FFFFFFF
+} VkVideoCodingControlFlagBitsKHR;
+typedef VkFlags VkVideoCodingControlFlagsKHR;
+
+typedef enum VkVideoCodingQualityPresetFlagBitsKHR {
+    VK_VIDEO_CODING_QUALITY_PRESET_NORMAL_BIT_KHR = 0x00000001,
+    VK_VIDEO_CODING_QUALITY_PRESET_POWER_BIT_KHR = 0x00000002,
+    VK_VIDEO_CODING_QUALITY_PRESET_QUALITY_BIT_KHR = 0x00000004,
+    VK_VIDEO_CODING_QUALITY_PRESET_FLAG_BITS_MAX_ENUM_KHR = 0x7FFFFFFF
+} VkVideoCodingQualityPresetFlagBitsKHR;
+typedef VkFlags VkVideoCodingQualityPresetFlagsKHR;
+typedef struct VkVideoQueueFamilyProperties2KHR {
+    VkStructureType                  sType;
+    void*                            pNext;
+    VkVideoCodecOperationFlagsKHR    videoCodecOperations;
+} VkVideoQueueFamilyProperties2KHR;
+
+typedef struct VkVideoProfileKHR {
+    VkStructureType                     sType;
+    void*                               pNext;
+    VkVideoCodecOperationFlagBitsKHR    videoCodecOperation;
+    VkVideoChromaSubsamplingFlagsKHR    chromaSubsampling;
+    VkVideoComponentBitDepthFlagsKHR    lumaBitDepth;
+    VkVideoComponentBitDepthFlagsKHR    chromaBitDepth;
+} VkVideoProfileKHR;
+
+typedef struct VkVideoProfilesKHR {
+    VkStructureType             sType;
+    void*                       pNext;
+    uint32_t                    profileCount;
+    const VkVideoProfileKHR*    pProfiles;
+} VkVideoProfilesKHR;
+
+typedef struct VkVideoCapabilitiesKHR {
+    VkStructureType              sType;
+    void*                        pNext;
+    VkVideoCapabilityFlagsKHR    capabilityFlags;
+    VkDeviceSize                 minBitstreamBufferOffsetAlignment;
+    VkDeviceSize                 minBitstreamBufferSizeAlignment;
+    VkExtent2D                   videoPictureExtentGranularity;
+    VkExtent2D                   minExtent;
+    VkExtent2D                   maxExtent;
+    uint32_t                     maxReferencePicturesSlotsCount;
+    uint32_t                     maxReferencePicturesActiveCount;
+} VkVideoCapabilitiesKHR;
+
+typedef struct VkPhysicalDeviceVideoFormatInfoKHR {
+    VkStructureType              sType;
+    void*                        pNext;
+    VkImageUsageFlags            imageUsage;
+    const VkVideoProfilesKHR*    pVideoProfiles;
+} VkPhysicalDeviceVideoFormatInfoKHR;
+
+typedef struct VkVideoFormatPropertiesKHR {
+    VkStructureType    sType;
+    void*              pNext;
+    VkFormat           format;
+} VkVideoFormatPropertiesKHR;
+
+typedef struct VkVideoPictureResourceKHR {
+    VkStructureType    sType;
+    const void*        pNext;
+    VkOffset2D         codedOffset;
+    VkExtent2D         codedExtent;
+    uint32_t           baseArrayLayer;
+    VkImageView        imageViewBinding;
+} VkVideoPictureResourceKHR;
+
+typedef struct VkVideoReferenceSlotKHR {
+    VkStructureType                     sType;
+    const void*                         pNext;
+    int8_t                              slotIndex;
+    const VkVideoPictureResourceKHR*    pPictureResource;
+} VkVideoReferenceSlotKHR;
+
+typedef struct VkVideoGetMemoryPropertiesKHR {
+    VkStructureType           sType;
+    const void*               pNext;
+    uint32_t                  memoryBindIndex;
+    VkMemoryRequirements2*    pMemoryRequirements;
+} VkVideoGetMemoryPropertiesKHR;
+
+typedef struct VkVideoBindMemoryKHR {
+    VkStructureType    sType;
+    const void*        pNext;
+    uint32_t           memoryBindIndex;
+    VkDeviceMemory     memory;
+    VkDeviceSize       memoryOffset;
+    VkDeviceSize       memorySize;
+} VkVideoBindMemoryKHR;
+
+typedef struct VkVideoSessionCreateInfoKHR {
+    VkStructureType                 sType;
+    const void*                     pNext;
+    uint32_t                        queueFamilyIndex;
+    VkVideoSessionCreateFlagsKHR    flags;
+    const VkVideoProfileKHR*        pVideoProfile;
+    VkFormat                        pictureFormat;
+    VkExtent2D                      maxCodedExtent;
+    VkFormat                        referencePicturesFormat;
+    uint32_t                        maxReferencePicturesSlotsCount;
+    uint32_t                        maxReferencePicturesActiveCount;
+} VkVideoSessionCreateInfoKHR;
+
+typedef struct VkVideoSessionParametersCreateInfoKHR {
+    VkStructureType                sType;
+    const void*                    pNext;
+    VkVideoSessionParametersKHR    videoSessionParametersTemplate;
+    VkVideoSessionKHR              videoSession;
+} VkVideoSessionParametersCreateInfoKHR;
+
+typedef struct VkVideoSessionParametersUpdateInfoKHR {
+    VkStructureType    sType;
+    const void*        pNext;
+    uint32_t           updateSequenceCount;
+} VkVideoSessionParametersUpdateInfoKHR;
+
+typedef struct VkVideoBeginCodingInfoKHR {
+    VkStructureType                       sType;
+    const void*                           pNext;
+    VkVideoBeginCodingFlagsKHR            flags;
+    VkVideoCodingQualityPresetFlagsKHR    codecQualityPreset;
+    VkVideoSessionKHR                     videoSession;
+    VkVideoSessionParametersKHR           videoSessionParameters;
+    uint32_t                              referenceSlotCount;
+    const VkVideoReferenceSlotKHR*        pReferenceSlots;
+} VkVideoBeginCodingInfoKHR;
+
+typedef struct VkVideoEndCodingInfoKHR {
+    VkStructureType             sType;
+    const void*                 pNext;
+    VkVideoEndCodingFlagsKHR    flags;
+} VkVideoEndCodingInfoKHR;
+
+typedef struct VkVideoCodingControlInfoKHR {
+    VkStructureType                 sType;
+    const void*                     pNext;
+    VkVideoCodingControlFlagsKHR    flags;
+} VkVideoCodingControlInfoKHR;
+
+typedef VkResult (VKAPI_PTR *PFN_vkGetPhysicalDeviceVideoCapabilitiesKHR)(VkPhysicalDevice physicalDevice, const VkVideoProfileKHR* pVideoProfile, VkVideoCapabilitiesKHR* pCapabilities);
+typedef VkResult (VKAPI_PTR *PFN_vkGetPhysicalDeviceVideoFormatPropertiesKHR)(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceVideoFormatInfoKHR* pVideoFormatInfo, uint32_t* pVideoFormatPropertyCount, VkVideoFormatPropertiesKHR* pVideoFormatProperties);
+typedef VkResult (VKAPI_PTR *PFN_vkCreateVideoSessionKHR)(VkDevice device, const VkVideoSessionCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkVideoSessionKHR* pVideoSession);
+typedef void (VKAPI_PTR *PFN_vkDestroyVideoSessionKHR)(VkDevice device, VkVideoSessionKHR videoSession, const VkAllocationCallbacks* pAllocator);
+typedef VkResult (VKAPI_PTR *PFN_vkGetVideoSessionMemoryRequirementsKHR)(VkDevice device, VkVideoSessionKHR videoSession, uint32_t* pVideoSessionMemoryRequirementsCount, VkVideoGetMemoryPropertiesKHR* pVideoSessionMemoryRequirements);
+typedef VkResult (VKAPI_PTR *PFN_vkBindVideoSessionMemoryKHR)(VkDevice device, VkVideoSessionKHR videoSession, uint32_t videoSessionBindMemoryCount, const VkVideoBindMemoryKHR* pVideoSessionBindMemories);
+typedef VkResult (VKAPI_PTR *PFN_vkCreateVideoSessionParametersKHR)(VkDevice device, const VkVideoSessionParametersCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkVideoSessionParametersKHR* pVideoSessionParameters);
+typedef VkResult (VKAPI_PTR *PFN_vkUpdateVideoSessionParametersKHR)(VkDevice device, VkVideoSessionParametersKHR videoSessionParameters, const VkVideoSessionParametersUpdateInfoKHR* pUpdateInfo);
+typedef void (VKAPI_PTR *PFN_vkDestroyVideoSessionParametersKHR)(VkDevice device, VkVideoSessionParametersKHR videoSessionParameters, const VkAllocationCallbacks* pAllocator);
+typedef void (VKAPI_PTR *PFN_vkCmdBeginVideoCodingKHR)(VkCommandBuffer commandBuffer, const VkVideoBeginCodingInfoKHR* pBeginInfo);
+typedef void (VKAPI_PTR *PFN_vkCmdEndVideoCodingKHR)(VkCommandBuffer commandBuffer, const VkVideoEndCodingInfoKHR* pEndCodingInfo);
+typedef void (VKAPI_PTR *PFN_vkCmdControlVideoCodingKHR)(VkCommandBuffer commandBuffer, const VkVideoCodingControlInfoKHR* pCodingControlInfo);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceVideoCapabilitiesKHR(
+    VkPhysicalDevice                            physicalDevice,
+    const VkVideoProfileKHR*                    pVideoProfile,
+    VkVideoCapabilitiesKHR*                     pCapabilities);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceVideoFormatPropertiesKHR(
+    VkPhysicalDevice                            physicalDevice,
+    const VkPhysicalDeviceVideoFormatInfoKHR*   pVideoFormatInfo,
+    uint32_t*                                   pVideoFormatPropertyCount,
+    VkVideoFormatPropertiesKHR*                 pVideoFormatProperties);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateVideoSessionKHR(
+    VkDevice                                    device,
+    const VkVideoSessionCreateInfoKHR*          pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkVideoSessionKHR*                          pVideoSession);
+
+VKAPI_ATTR void VKAPI_CALL vkDestroyVideoSessionKHR(
+    VkDevice                                    device,
+    VkVideoSessionKHR                           videoSession,
+    const VkAllocationCallbacks*                pAllocator);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetVideoSessionMemoryRequirementsKHR(
+    VkDevice                                    device,
+    VkVideoSessionKHR                           videoSession,
+    uint32_t*                                   pVideoSessionMemoryRequirementsCount,
+    VkVideoGetMemoryPropertiesKHR*              pVideoSessionMemoryRequirements);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkBindVideoSessionMemoryKHR(
+    VkDevice                                    device,
+    VkVideoSessionKHR                           videoSession,
+    uint32_t                                    videoSessionBindMemoryCount,
+    const VkVideoBindMemoryKHR*                 pVideoSessionBindMemories);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateVideoSessionParametersKHR(
+    VkDevice                                    device,
+    const VkVideoSessionParametersCreateInfoKHR* pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkVideoSessionParametersKHR*                pVideoSessionParameters);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkUpdateVideoSessionParametersKHR(
+    VkDevice                                    device,
+    VkVideoSessionParametersKHR                 videoSessionParameters,
+    const VkVideoSessionParametersUpdateInfoKHR* pUpdateInfo);
+
+VKAPI_ATTR void VKAPI_CALL vkDestroyVideoSessionParametersKHR(
+    VkDevice                                    device,
+    VkVideoSessionParametersKHR                 videoSessionParameters,
+    const VkAllocationCallbacks*                pAllocator);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdBeginVideoCodingKHR(
+    VkCommandBuffer                             commandBuffer,
+    const VkVideoBeginCodingInfoKHR*            pBeginInfo);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdEndVideoCodingKHR(
+    VkCommandBuffer                             commandBuffer,
+    const VkVideoEndCodingInfoKHR*              pEndCodingInfo);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdControlVideoCodingKHR(
+    VkCommandBuffer                             commandBuffer,
+    const VkVideoCodingControlInfoKHR*          pCodingControlInfo);
+#endif
+
+
+#define VK_KHR_video_decode_queue 1
+#define VK_KHR_VIDEO_DECODE_QUEUE_SPEC_VERSION 2
+#define VK_KHR_VIDEO_DECODE_QUEUE_EXTENSION_NAME "VK_KHR_video_decode_queue"
+
+typedef enum VkVideoDecodeFlagBitsKHR {
+    VK_VIDEO_DECODE_DEFAULT_KHR = 0,
+    VK_VIDEO_DECODE_RESERVED_0_BIT_KHR = 0x00000001,
+    VK_VIDEO_DECODE_FLAG_BITS_MAX_ENUM_KHR = 0x7FFFFFFF
+} VkVideoDecodeFlagBitsKHR;
+typedef VkFlags VkVideoDecodeFlagsKHR;
+typedef struct VkVideoDecodeInfoKHR {
+    VkStructureType                   sType;
+    const void*                       pNext;
+    VkVideoDecodeFlagsKHR             flags;
+    VkOffset2D                        codedOffset;
+    VkExtent2D                        codedExtent;
+    VkBuffer                          srcBuffer;
+    VkDeviceSize                      srcBufferOffset;
+    VkDeviceSize                      srcBufferRange;
+    VkVideoPictureResourceKHR         dstPictureResource;
+    const VkVideoReferenceSlotKHR*    pSetupReferenceSlot;
+    uint32_t                          referenceSlotCount;
+    const VkVideoReferenceSlotKHR*    pReferenceSlots;
+} VkVideoDecodeInfoKHR;
+
+typedef void (VKAPI_PTR *PFN_vkCmdDecodeVideoKHR)(VkCommandBuffer commandBuffer, const VkVideoDecodeInfoKHR* pFrameInfo);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR void VKAPI_CALL vkCmdDecodeVideoKHR(
+    VkCommandBuffer                             commandBuffer,
+    const VkVideoDecodeInfoKHR*                 pFrameInfo);
+#endif
+
+
+#define VK_KHR_portability_subset 1
+#define VK_KHR_PORTABILITY_SUBSET_SPEC_VERSION 1
+#define VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME "VK_KHR_portability_subset"
+typedef struct VkPhysicalDevicePortabilitySubsetFeaturesKHR {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           constantAlphaColorBlendFactors;
+    VkBool32           events;
+    VkBool32           imageViewFormatReinterpretation;
+    VkBool32           imageViewFormatSwizzle;
+    VkBool32           imageView2DOn3DImage;
+    VkBool32           multisampleArrayImage;
+    VkBool32           mutableComparisonSamplers;
+    VkBool32           pointPolygons;
+    VkBool32           samplerMipLodBias;
+    VkBool32           separateStencilMaskRef;
+    VkBool32           shaderSampleRateInterpolationFunctions;
+    VkBool32           tessellationIsolines;
+    VkBool32           tessellationPointMode;
+    VkBool32           triangleFans;
+    VkBool32           vertexAttributeAccessBeyondStride;
+} VkPhysicalDevicePortabilitySubsetFeaturesKHR;
+
+typedef struct VkPhysicalDevicePortabilitySubsetPropertiesKHR {
+    VkStructureType    sType;
+    void*              pNext;
+    uint32_t           minVertexInputBindingStrideAlignment;
+} VkPhysicalDevicePortabilitySubsetPropertiesKHR;
+
+
+
+#define VK_KHR_video_encode_queue 1
+#define VK_KHR_VIDEO_ENCODE_QUEUE_SPEC_VERSION 3
+#define VK_KHR_VIDEO_ENCODE_QUEUE_EXTENSION_NAME "VK_KHR_video_encode_queue"
+
+typedef enum VkVideoEncodeFlagBitsKHR {
+    VK_VIDEO_ENCODE_DEFAULT_KHR = 0,
+    VK_VIDEO_ENCODE_RESERVED_0_BIT_KHR = 0x00000001,
+    VK_VIDEO_ENCODE_FLAG_BITS_MAX_ENUM_KHR = 0x7FFFFFFF
+} VkVideoEncodeFlagBitsKHR;
+typedef VkFlags VkVideoEncodeFlagsKHR;
+
+typedef enum VkVideoEncodeRateControlFlagBitsKHR {
+    VK_VIDEO_ENCODE_RATE_CONTROL_DEFAULT_KHR = 0,
+    VK_VIDEO_ENCODE_RATE_CONTROL_RESET_BIT_KHR = 0x00000001,
+    VK_VIDEO_ENCODE_RATE_CONTROL_FLAG_BITS_MAX_ENUM_KHR = 0x7FFFFFFF
+} VkVideoEncodeRateControlFlagBitsKHR;
+typedef VkFlags VkVideoEncodeRateControlFlagsKHR;
+
+typedef enum VkVideoEncodeRateControlModeFlagBitsKHR {
+    VK_VIDEO_ENCODE_RATE_CONTROL_MODE_NONE_BIT_KHR = 0,
+    VK_VIDEO_ENCODE_RATE_CONTROL_MODE_CBR_BIT_KHR = 1,
+    VK_VIDEO_ENCODE_RATE_CONTROL_MODE_VBR_BIT_KHR = 2,
+    VK_VIDEO_ENCODE_RATE_CONTROL_MODE_FLAG_BITS_MAX_ENUM_KHR = 0x7FFFFFFF
+} VkVideoEncodeRateControlModeFlagBitsKHR;
+typedef VkFlags VkVideoEncodeRateControlModeFlagsKHR;
+typedef struct VkVideoEncodeInfoKHR {
+    VkStructureType                   sType;
+    const void*                       pNext;
+    VkVideoEncodeFlagsKHR             flags;
+    uint32_t                          qualityLevel;
+    VkExtent2D                        codedExtent;
+    VkBuffer                          dstBitstreamBuffer;
+    VkDeviceSize                      dstBitstreamBufferOffset;
+    VkDeviceSize                      dstBitstreamBufferMaxRange;
+    VkVideoPictureResourceKHR         srcPictureResource;
+    const VkVideoReferenceSlotKHR*    pSetupReferenceSlot;
+    uint32_t                          referenceSlotCount;
+    const VkVideoReferenceSlotKHR*    pReferenceSlots;
+} VkVideoEncodeInfoKHR;
+
+typedef struct VkVideoEncodeRateControlInfoKHR {
+    VkStructureType                            sType;
+    const void*                                pNext;
+    VkVideoEncodeRateControlFlagsKHR           flags;
+    VkVideoEncodeRateControlModeFlagBitsKHR    rateControlMode;
+    uint32_t                                   averageBitrate;
+    uint16_t                                   peakToAverageBitrateRatio;
+    uint16_t                                   frameRateNumerator;
+    uint16_t                                   frameRateDenominator;
+    uint32_t                                   virtualBufferSizeInMs;
+} VkVideoEncodeRateControlInfoKHR;
+
+typedef void (VKAPI_PTR *PFN_vkCmdEncodeVideoKHR)(VkCommandBuffer commandBuffer, const VkVideoEncodeInfoKHR* pEncodeInfo);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR void VKAPI_CALL vkCmdEncodeVideoKHR(
+    VkCommandBuffer                             commandBuffer,
+    const VkVideoEncodeInfoKHR*                 pEncodeInfo);
+#endif
+
+
+#define VK_EXT_video_encode_h264 1
+#include "vk_video/vulkan_video_codec_h264std.h"
+#include "vk_video/vulkan_video_codec_h264std_encode.h"
+#define VK_EXT_VIDEO_ENCODE_H264_SPEC_VERSION 2
+#define VK_EXT_VIDEO_ENCODE_H264_EXTENSION_NAME "VK_EXT_video_encode_h264"
+
+typedef enum VkVideoEncodeH264CapabilityFlagBitsEXT {
+    VK_VIDEO_ENCODE_H264_CAPABILITY_CABAC_BIT_EXT = 0x00000001,
+    VK_VIDEO_ENCODE_H264_CAPABILITY_CAVLC_BIT_EXT = 0x00000002,
+    VK_VIDEO_ENCODE_H264_CAPABILITY_WEIGHTED_BI_PRED_IMPLICIT_BIT_EXT = 0x00000004,
+    VK_VIDEO_ENCODE_H264_CAPABILITY_TRANSFORM_8X8_BIT_EXT = 0x00000008,
+    VK_VIDEO_ENCODE_H264_CAPABILITY_CHROMA_QP_OFFSET_BIT_EXT = 0x00000010,
+    VK_VIDEO_ENCODE_H264_CAPABILITY_SECOND_CHROMA_QP_OFFSET_BIT_EXT = 0x00000020,
+    VK_VIDEO_ENCODE_H264_CAPABILITY_DEBLOCKING_FILTER_DISABLED_BIT_EXT = 0x00000040,
+    VK_VIDEO_ENCODE_H264_CAPABILITY_DEBLOCKING_FILTER_ENABLED_BIT_EXT = 0x00000080,
+    VK_VIDEO_ENCODE_H264_CAPABILITY_DEBLOCKING_FILTER_PARTIAL_BIT_EXT = 0x00000100,
+    VK_VIDEO_ENCODE_H264_CAPABILITY_MULTIPLE_SLICE_PER_FRAME_BIT_EXT = 0x00000200,
+    VK_VIDEO_ENCODE_H264_CAPABILITY_EVENLY_DISTRIBUTED_SLICE_SIZE_BIT_EXT = 0x00000400,
+    VK_VIDEO_ENCODE_H264_CAPABILITY_FLAG_BITS_MAX_ENUM_EXT = 0x7FFFFFFF
+} VkVideoEncodeH264CapabilityFlagBitsEXT;
+typedef VkFlags VkVideoEncodeH264CapabilityFlagsEXT;
+
+typedef enum VkVideoEncodeH264InputModeFlagBitsEXT {
+    VK_VIDEO_ENCODE_H264_INPUT_MODE_FRAME_BIT_EXT = 0x00000001,
+    VK_VIDEO_ENCODE_H264_INPUT_MODE_SLICE_BIT_EXT = 0x00000002,
+    VK_VIDEO_ENCODE_H264_INPUT_MODE_NON_VCL_BIT_EXT = 0x00000004,
+    VK_VIDEO_ENCODE_H264_INPUT_MODE_FLAG_BITS_MAX_ENUM_EXT = 0x7FFFFFFF
+} VkVideoEncodeH264InputModeFlagBitsEXT;
+typedef VkFlags VkVideoEncodeH264InputModeFlagsEXT;
+
+typedef enum VkVideoEncodeH264OutputModeFlagBitsEXT {
+    VK_VIDEO_ENCODE_H264_OUTPUT_MODE_FRAME_BIT_EXT = 0x00000001,
+    VK_VIDEO_ENCODE_H264_OUTPUT_MODE_SLICE_BIT_EXT = 0x00000002,
+    VK_VIDEO_ENCODE_H264_OUTPUT_MODE_NON_VCL_BIT_EXT = 0x00000004,
+    VK_VIDEO_ENCODE_H264_OUTPUT_MODE_FLAG_BITS_MAX_ENUM_EXT = 0x7FFFFFFF
+} VkVideoEncodeH264OutputModeFlagBitsEXT;
+typedef VkFlags VkVideoEncodeH264OutputModeFlagsEXT;
+
+typedef enum VkVideoEncodeH264CreateFlagBitsEXT {
+    VK_VIDEO_ENCODE_H264_CREATE_DEFAULT_EXT = 0,
+    VK_VIDEO_ENCODE_H264_CREATE_RESERVED_0_BIT_EXT = 0x00000001,
+    VK_VIDEO_ENCODE_H264_CREATE_FLAG_BITS_MAX_ENUM_EXT = 0x7FFFFFFF
+} VkVideoEncodeH264CreateFlagBitsEXT;
+typedef VkFlags VkVideoEncodeH264CreateFlagsEXT;
+typedef struct VkVideoEncodeH264CapabilitiesEXT {
+    VkStructureType                        sType;
+    const void*                            pNext;
+    VkVideoEncodeH264CapabilityFlagsEXT    flags;
+    VkVideoEncodeH264InputModeFlagsEXT     inputModeFlags;
+    VkVideoEncodeH264OutputModeFlagsEXT    outputModeFlags;
+    VkExtent2D                             minPictureSizeInMbs;
+    VkExtent2D                             maxPictureSizeInMbs;
+    VkExtent2D                             inputImageDataAlignment;
+    uint8_t                                maxNumL0ReferenceForP;
+    uint8_t                                maxNumL0ReferenceForB;
+    uint8_t                                maxNumL1Reference;
+    uint8_t                                qualityLevelCount;
+    VkExtensionProperties                  stdExtensionVersion;
+} VkVideoEncodeH264CapabilitiesEXT;
+
+typedef struct VkVideoEncodeH264SessionCreateInfoEXT {
+    VkStructureType                    sType;
+    const void*                        pNext;
+    VkVideoEncodeH264CreateFlagsEXT    flags;
+    VkExtent2D                         maxPictureSizeInMbs;
+    const VkExtensionProperties*       pStdExtensionVersion;
+} VkVideoEncodeH264SessionCreateInfoEXT;
+
+typedef struct VkVideoEncodeH264SessionParametersAddInfoEXT {
+    VkStructureType                            sType;
+    const void*                                pNext;
+    uint32_t                                   spsStdCount;
+    const StdVideoH264SequenceParameterSet*    pSpsStd;
+    uint32_t                                   ppsStdCount;
+    const StdVideoH264PictureParameterSet*     pPpsStd;
+} VkVideoEncodeH264SessionParametersAddInfoEXT;
+
+typedef struct VkVideoEncodeH264SessionParametersCreateInfoEXT {
+    VkStructureType                                        sType;
+    const void*                                            pNext;
+    uint32_t                                               maxSpsStdCount;
+    uint32_t                                               maxPpsStdCount;
+    const VkVideoEncodeH264SessionParametersAddInfoEXT*    pParametersAddInfo;
+} VkVideoEncodeH264SessionParametersCreateInfoEXT;
+
+typedef struct VkVideoEncodeH264DpbSlotInfoEXT {
+    VkStructureType                         sType;
+    const void*                             pNext;
+    int8_t                                  slotIndex;
+    const StdVideoEncodeH264PictureInfo*    pStdPictureInfo;
+} VkVideoEncodeH264DpbSlotInfoEXT;
+
+typedef struct VkVideoEncodeH264NaluSliceEXT {
+    VkStructureType                           sType;
+    const void*                               pNext;
+    const StdVideoEncodeH264SliceHeader*      pSliceHeaderStd;
+    uint32_t                                  mbCount;
+    uint8_t                                   refFinalList0EntryCount;
+    const VkVideoEncodeH264DpbSlotInfoEXT*    pRefFinalList0Entries;
+    uint8_t                                   refFinalList1EntryCount;
+    const VkVideoEncodeH264DpbSlotInfoEXT*    pRefFinalList1Entries;
+    uint32_t                                  precedingNaluBytes;
+    uint8_t                                   minQp;
+    uint8_t                                   maxQp;
+} VkVideoEncodeH264NaluSliceEXT;
+
+typedef struct VkVideoEncodeH264VclFrameInfoEXT {
+    VkStructureType                           sType;
+    const void*                               pNext;
+    uint8_t                                   refDefaultFinalList0EntryCount;
+    const VkVideoEncodeH264DpbSlotInfoEXT*    pRefDefaultFinalList0Entries;
+    uint8_t                                   refDefaultFinalList1EntryCount;
+    const VkVideoEncodeH264DpbSlotInfoEXT*    pRefDefaultFinalList1Entries;
+    uint32_t                                  naluSliceEntryCount;
+    const VkVideoEncodeH264NaluSliceEXT*      pNaluSliceEntries;
+    const VkVideoEncodeH264DpbSlotInfoEXT*    pCurrentPictureInfo;
+} VkVideoEncodeH264VclFrameInfoEXT;
+
+typedef struct VkVideoEncodeH264EmitPictureParametersEXT {
+    VkStructureType    sType;
+    const void*        pNext;
+    uint8_t            spsId;
+    VkBool32           emitSpsEnable;
+    uint32_t           ppsIdEntryCount;
+    const uint8_t*     ppsIdEntries;
+} VkVideoEncodeH264EmitPictureParametersEXT;
+
+typedef struct VkVideoEncodeH264ProfileEXT {
+    VkStructureType           sType;
+    const void*               pNext;
+    StdVideoH264ProfileIdc    stdProfileIdc;
+} VkVideoEncodeH264ProfileEXT;
+
+
+
+#define VK_EXT_video_encode_h265 1
+#include "vk_video/vulkan_video_codec_h265std.h"
+#include "vk_video/vulkan_video_codec_h265std_encode.h"
+#define VK_EXT_VIDEO_ENCODE_H265_SPEC_VERSION 2
+#define VK_EXT_VIDEO_ENCODE_H265_EXTENSION_NAME "VK_EXT_video_encode_h265"
+typedef VkFlags VkVideoEncodeH265CapabilityFlagsEXT;
+
+typedef enum VkVideoEncodeH265InputModeFlagBitsEXT {
+    VK_VIDEO_ENCODE_H265_INPUT_MODE_FRAME_BIT_EXT = 0x00000001,
+    VK_VIDEO_ENCODE_H265_INPUT_MODE_SLICE_BIT_EXT = 0x00000002,
+    VK_VIDEO_ENCODE_H265_INPUT_MODE_NON_VCL_BIT_EXT = 0x00000004,
+    VK_VIDEO_ENCODE_H265_INPUT_MODE_FLAG_BITS_MAX_ENUM_EXT = 0x7FFFFFFF
+} VkVideoEncodeH265InputModeFlagBitsEXT;
+typedef VkFlags VkVideoEncodeH265InputModeFlagsEXT;
+
+typedef enum VkVideoEncodeH265OutputModeFlagBitsEXT {
+    VK_VIDEO_ENCODE_H265_OUTPUT_MODE_FRAME_BIT_EXT = 0x00000001,
+    VK_VIDEO_ENCODE_H265_OUTPUT_MODE_SLICE_BIT_EXT = 0x00000002,
+    VK_VIDEO_ENCODE_H265_OUTPUT_MODE_NON_VCL_BIT_EXT = 0x00000004,
+    VK_VIDEO_ENCODE_H265_OUTPUT_MODE_FLAG_BITS_MAX_ENUM_EXT = 0x7FFFFFFF
+} VkVideoEncodeH265OutputModeFlagBitsEXT;
+typedef VkFlags VkVideoEncodeH265OutputModeFlagsEXT;
+typedef VkFlags VkVideoEncodeH265CreateFlagsEXT;
+
+typedef enum VkVideoEncodeH265CtbSizeFlagBitsEXT {
+    VK_VIDEO_ENCODE_H265_CTB_SIZE_8_BIT_EXT = 0x00000001,
+    VK_VIDEO_ENCODE_H265_CTB_SIZE_16_BIT_EXT = 0x00000002,
+    VK_VIDEO_ENCODE_H265_CTB_SIZE_32_BIT_EXT = 0x00000004,
+    VK_VIDEO_ENCODE_H265_CTB_SIZE_64_BIT_EXT = 0x00000008,
+    VK_VIDEO_ENCODE_H265_CTB_SIZE_FLAG_BITS_MAX_ENUM_EXT = 0x7FFFFFFF
+} VkVideoEncodeH265CtbSizeFlagBitsEXT;
+typedef VkFlags VkVideoEncodeH265CtbSizeFlagsEXT;
+typedef struct VkVideoEncodeH265CapabilitiesEXT {
+    VkStructureType                        sType;
+    const void*                            pNext;
+    VkVideoEncodeH265CapabilityFlagsEXT    flags;
+    VkVideoEncodeH265InputModeFlagsEXT     inputModeFlags;
+    VkVideoEncodeH265OutputModeFlagsEXT    outputModeFlags;
+    VkVideoEncodeH265CtbSizeFlagsEXT       ctbSizes;
+    VkExtent2D                             inputImageDataAlignment;
+    uint8_t                                maxNumL0ReferenceForP;
+    uint8_t                                maxNumL0ReferenceForB;
+    uint8_t                                maxNumL1Reference;
+    uint8_t                                maxNumSubLayers;
+    uint8_t                                qualityLevelCount;
+    VkExtensionProperties                  stdExtensionVersion;
+} VkVideoEncodeH265CapabilitiesEXT;
+
+typedef struct VkVideoEncodeH265SessionCreateInfoEXT {
+    VkStructureType                    sType;
+    const void*                        pNext;
+    VkVideoEncodeH265CreateFlagsEXT    flags;
+    const VkExtensionProperties*       pStdExtensionVersion;
+} VkVideoEncodeH265SessionCreateInfoEXT;
+
+typedef struct VkVideoEncodeH265SessionParametersAddInfoEXT {
+    VkStructureType                            sType;
+    const void*                                pNext;
+    uint32_t                                   vpsStdCount;
+    const StdVideoH265VideoParameterSet*       pVpsStd;
+    uint32_t                                   spsStdCount;
+    const StdVideoH265SequenceParameterSet*    pSpsStd;
+    uint32_t                                   ppsStdCount;
+    const StdVideoH265PictureParameterSet*     pPpsStd;
+} VkVideoEncodeH265SessionParametersAddInfoEXT;
+
+typedef struct VkVideoEncodeH265SessionParametersCreateInfoEXT {
+    VkStructureType                                        sType;
+    const void*                                            pNext;
+    uint32_t                                               maxVpsStdCount;
+    uint32_t                                               maxSpsStdCount;
+    uint32_t                                               maxPpsStdCount;
+    const VkVideoEncodeH265SessionParametersAddInfoEXT*    pParametersAddInfo;
+} VkVideoEncodeH265SessionParametersCreateInfoEXT;
+
+typedef struct VkVideoEncodeH265DpbSlotInfoEXT {
+    VkStructureType                           sType;
+    const void*                               pNext;
+    int8_t                                    slotIndex;
+    const StdVideoEncodeH265ReferenceInfo*    pStdReferenceInfo;
+} VkVideoEncodeH265DpbSlotInfoEXT;
+
+typedef struct VkVideoEncodeH265ReferenceListsEXT {
+    VkStructureType                                    sType;
+    const void*                                        pNext;
+    uint8_t                                            referenceList0EntryCount;
+    const VkVideoEncodeH265DpbSlotInfoEXT*             pReferenceList0Entries;
+    uint8_t                                            referenceList1EntryCount;
+    const VkVideoEncodeH265DpbSlotInfoEXT*             pReferenceList1Entries;
+    const StdVideoEncodeH265ReferenceModifications*    pReferenceModifications;
+} VkVideoEncodeH265ReferenceListsEXT;
+
+typedef struct VkVideoEncodeH265NaluSliceEXT {
+    VkStructureType                              sType;
+    const void*                                  pNext;
+    uint32_t                                     ctbCount;
+    const VkVideoEncodeH265ReferenceListsEXT*    pReferenceFinalLists;
+    const StdVideoEncodeH265SliceHeader*         pSliceHeaderStd;
+} VkVideoEncodeH265NaluSliceEXT;
+
+typedef struct VkVideoEncodeH265VclFrameInfoEXT {
+    VkStructureType                              sType;
+    const void*                                  pNext;
+    const VkVideoEncodeH265ReferenceListsEXT*    pReferenceFinalLists;
+    uint32_t                                     naluSliceEntryCount;
+    const VkVideoEncodeH265NaluSliceEXT*         pNaluSliceEntries;
+    const StdVideoEncodeH265PictureInfo*         pCurrentPictureInfo;
+} VkVideoEncodeH265VclFrameInfoEXT;
+
+typedef struct VkVideoEncodeH265EmitPictureParametersEXT {
+    VkStructureType    sType;
+    const void*        pNext;
+    uint8_t            vpsId;
+    uint8_t            spsId;
+    VkBool32           emitVpsEnable;
+    VkBool32           emitSpsEnable;
+    uint32_t           ppsIdEntryCount;
+    const uint8_t*     ppsIdEntries;
+} VkVideoEncodeH265EmitPictureParametersEXT;
+
+typedef struct VkVideoEncodeH265ProfileEXT {
+    VkStructureType           sType;
+    const void*               pNext;
+    StdVideoH265ProfileIdc    stdProfileIdc;
+} VkVideoEncodeH265ProfileEXT;
+
+
+
+#define VK_EXT_video_decode_h264 1
+#include "vk_video/vulkan_video_codec_h264std_decode.h"
+#define VK_EXT_VIDEO_DECODE_H264_SPEC_VERSION 3
+#define VK_EXT_VIDEO_DECODE_H264_EXTENSION_NAME "VK_EXT_video_decode_h264"
+
+typedef enum VkVideoDecodeH264PictureLayoutFlagBitsEXT {
+    VK_VIDEO_DECODE_H264_PICTURE_LAYOUT_PROGRESSIVE_EXT = 0,
+    VK_VIDEO_DECODE_H264_PICTURE_LAYOUT_INTERLACED_INTERLEAVED_LINES_BIT_EXT = 0x00000001,
+    VK_VIDEO_DECODE_H264_PICTURE_LAYOUT_INTERLACED_SEPARATE_PLANES_BIT_EXT = 0x00000002,
+    VK_VIDEO_DECODE_H264_PICTURE_LAYOUT_FLAG_BITS_MAX_ENUM_EXT = 0x7FFFFFFF
+} VkVideoDecodeH264PictureLayoutFlagBitsEXT;
+typedef VkFlags VkVideoDecodeH264PictureLayoutFlagsEXT;
+typedef VkFlags VkVideoDecodeH264CreateFlagsEXT;
+typedef struct VkVideoDecodeH264ProfileEXT {
+    VkStructureType                           sType;
+    const void*                               pNext;
+    StdVideoH264ProfileIdc                    stdProfileIdc;
+    VkVideoDecodeH264PictureLayoutFlagsEXT    pictureLayout;
+} VkVideoDecodeH264ProfileEXT;
+
+typedef struct VkVideoDecodeH264CapabilitiesEXT {
+    VkStructureType          sType;
+    void*                    pNext;
+    uint32_t                 maxLevel;
+    VkOffset2D               fieldOffsetGranularity;
+    VkExtensionProperties    stdExtensionVersion;
+} VkVideoDecodeH264CapabilitiesEXT;
+
+typedef struct VkVideoDecodeH264SessionCreateInfoEXT {
+    VkStructureType                    sType;
+    const void*                        pNext;
+    VkVideoDecodeH264CreateFlagsEXT    flags;
+    const VkExtensionProperties*       pStdExtensionVersion;
+} VkVideoDecodeH264SessionCreateInfoEXT;
+
+typedef struct VkVideoDecodeH264SessionParametersAddInfoEXT {
+    VkStructureType                            sType;
+    const void*                                pNext;
+    uint32_t                                   spsStdCount;
+    const StdVideoH264SequenceParameterSet*    pSpsStd;
+    uint32_t                                   ppsStdCount;
+    const StdVideoH264PictureParameterSet*     pPpsStd;
+} VkVideoDecodeH264SessionParametersAddInfoEXT;
+
+typedef struct VkVideoDecodeH264SessionParametersCreateInfoEXT {
+    VkStructureType                                        sType;
+    const void*                                            pNext;
+    uint32_t                                               maxSpsStdCount;
+    uint32_t                                               maxPpsStdCount;
+    const VkVideoDecodeH264SessionParametersAddInfoEXT*    pParametersAddInfo;
+} VkVideoDecodeH264SessionParametersCreateInfoEXT;
+
+typedef struct VkVideoDecodeH264PictureInfoEXT {
+    VkStructureType                         sType;
+    const void*                             pNext;
+    const StdVideoDecodeH264PictureInfo*    pStdPictureInfo;
+    uint32_t                                slicesCount;
+    const uint32_t*                         pSlicesDataOffsets;
+} VkVideoDecodeH264PictureInfoEXT;
+
+typedef struct VkVideoDecodeH264MvcEXT {
+    VkStructureType                 sType;
+    const void*                     pNext;
+    const StdVideoDecodeH264Mvc*    pStdMvc;
+} VkVideoDecodeH264MvcEXT;
+
+typedef struct VkVideoDecodeH264DpbSlotInfoEXT {
+    VkStructureType                           sType;
+    const void*                               pNext;
+    const StdVideoDecodeH264ReferenceInfo*    pStdReferenceInfo;
+} VkVideoDecodeH264DpbSlotInfoEXT;
+
+
+
+#define VK_EXT_video_decode_h265 1
+#include "vk_video/vulkan_video_codec_h265std_decode.h"
+#define VK_EXT_VIDEO_DECODE_H265_SPEC_VERSION 1
+#define VK_EXT_VIDEO_DECODE_H265_EXTENSION_NAME "VK_EXT_video_decode_h265"
+typedef VkFlags VkVideoDecodeH265CreateFlagsEXT;
+typedef struct VkVideoDecodeH265ProfileEXT {
+    VkStructureType           sType;
+    const void*               pNext;
+    StdVideoH265ProfileIdc    stdProfileIdc;
+} VkVideoDecodeH265ProfileEXT;
+
+typedef struct VkVideoDecodeH265CapabilitiesEXT {
+    VkStructureType          sType;
+    void*                    pNext;
+    uint32_t                 maxLevel;
+    VkExtensionProperties    stdExtensionVersion;
+} VkVideoDecodeH265CapabilitiesEXT;
+
+typedef struct VkVideoDecodeH265SessionCreateInfoEXT {
+    VkStructureType                    sType;
+    const void*                        pNext;
+    VkVideoDecodeH265CreateFlagsEXT    flags;
+    const VkExtensionProperties*       pStdExtensionVersion;
+} VkVideoDecodeH265SessionCreateInfoEXT;
+
+typedef struct VkVideoDecodeH265SessionParametersAddInfoEXT {
+    VkStructureType                            sType;
+    const void*                                pNext;
+    uint32_t                                   spsStdCount;
+    const StdVideoH265SequenceParameterSet*    pSpsStd;
+    uint32_t                                   ppsStdCount;
+    const StdVideoH265PictureParameterSet*     pPpsStd;
+} VkVideoDecodeH265SessionParametersAddInfoEXT;
+
+typedef struct VkVideoDecodeH265SessionParametersCreateInfoEXT {
+    VkStructureType                                        sType;
+    const void*                                            pNext;
+    uint32_t                                               maxSpsStdCount;
+    uint32_t                                               maxPpsStdCount;
+    const VkVideoDecodeH265SessionParametersAddInfoEXT*    pParametersAddInfo;
+} VkVideoDecodeH265SessionParametersCreateInfoEXT;
+
+typedef struct VkVideoDecodeH265PictureInfoEXT {
+    VkStructureType                   sType;
+    const void*                       pNext;
+    StdVideoDecodeH265PictureInfo*    pStdPictureInfo;
+    uint32_t                          slicesCount;
+    const uint32_t*                   pSlicesDataOffsets;
+} VkVideoDecodeH265PictureInfoEXT;
+
+typedef struct VkVideoDecodeH265DpbSlotInfoEXT {
+    VkStructureType                           sType;
+    const void*                               pNext;
+    const StdVideoDecodeH265ReferenceInfo*    pStdReferenceInfo;
+} VkVideoDecodeH265DpbSlotInfoEXT;
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/external/Vulkan/external/vulkan/vulkan_core.h b/external/Vulkan/external/vulkan/vulkan_core.h
new file mode 100644
index 0000000000000000000000000000000000000000..a2f4e7717cb8f23a9efdf78b6db886eed8f432f7
--- /dev/null
+++ b/external/Vulkan/external/vulkan/vulkan_core.h
@@ -0,0 +1,13540 @@
+#ifndef VULKAN_CORE_H_
+#define VULKAN_CORE_H_ 1
+
+/*
+** Copyright 2015-2021 The Khronos Group Inc.
+**
+** SPDX-License-Identifier: Apache-2.0
+*/
+
+/*
+** This header is generated from the Khronos Vulkan XML API Registry.
+**
+*/
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+
+#define VK_VERSION_1_0 1
+#include "vk_platform.h"
+
+#define VK_DEFINE_HANDLE(object) typedef struct object##_T* object;
+
+
+#ifndef VK_USE_64_BIT_PTR_DEFINES
+    #if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__) ) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(__powerpc64__)
+        #define VK_USE_64_BIT_PTR_DEFINES 1
+    #else
+        #define VK_USE_64_BIT_PTR_DEFINES 0
+    #endif
+#endif
+
+
+#ifndef VK_DEFINE_NON_DISPATCHABLE_HANDLE
+    #if (VK_USE_64_BIT_PTR_DEFINES==1)
+        #if (defined(__cplusplus) && (__cplusplus >= 201103L)) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201103L))
+            #define VK_NULL_HANDLE nullptr
+        #else
+            #define VK_NULL_HANDLE ((void*)0)
+        #endif
+    #else
+        #define VK_NULL_HANDLE 0ULL
+    #endif
+#endif
+#ifndef VK_NULL_HANDLE
+    #define VK_NULL_HANDLE 0
+#endif
+
+
+#ifndef VK_DEFINE_NON_DISPATCHABLE_HANDLE
+    #if (VK_USE_64_BIT_PTR_DEFINES==1)
+        #define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object) typedef struct object##_T *object;
+    #else
+        #define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object) typedef uint64_t object;
+    #endif
+#endif
+
+// DEPRECATED: This define is deprecated. VK_MAKE_API_VERSION should be used instead.
+#define VK_MAKE_VERSION(major, minor, patch) \
+    ((((uint32_t)(major)) << 22) | (((uint32_t)(minor)) << 12) | ((uint32_t)(patch)))
+
+// DEPRECATED: This define has been removed. Specific version defines (e.g. VK_API_VERSION_1_0), or the VK_MAKE_VERSION macro, should be used instead.
+//#define VK_API_VERSION VK_MAKE_VERSION(1, 0, 0) // Patch version should always be set to 0
+
+#define VK_MAKE_API_VERSION(variant, major, minor, patch) \
+    ((((uint32_t)(variant)) << 29) | (((uint32_t)(major)) << 22) | (((uint32_t)(minor)) << 12) | ((uint32_t)(patch)))
+
+// Vulkan 1.0 version number
+#define VK_API_VERSION_1_0 VK_MAKE_API_VERSION(0, 1, 0, 0)// Patch version should always be set to 0
+
+// Version of this file
+#define VK_HEADER_VERSION 197
+
+// Complete version of this file
+#define VK_HEADER_VERSION_COMPLETE VK_MAKE_API_VERSION(0, 1, 2, VK_HEADER_VERSION)
+
+// DEPRECATED: This define is deprecated. VK_API_VERSION_MAJOR should be used instead.
+#define VK_VERSION_MAJOR(version) ((uint32_t)(version) >> 22)
+
+// DEPRECATED: This define is deprecated. VK_API_VERSION_MINOR should be used instead.
+#define VK_VERSION_MINOR(version) (((uint32_t)(version) >> 12) & 0x3FFU)
+
+// DEPRECATED: This define is deprecated. VK_API_VERSION_PATCH should be used instead.
+#define VK_VERSION_PATCH(version) ((uint32_t)(version) & 0xFFFU)
+
+#define VK_API_VERSION_VARIANT(version) ((uint32_t)(version) >> 29)
+#define VK_API_VERSION_MAJOR(version) (((uint32_t)(version) >> 22) & 0x7FU)
+#define VK_API_VERSION_MINOR(version) (((uint32_t)(version) >> 12) & 0x3FFU)
+#define VK_API_VERSION_PATCH(version) ((uint32_t)(version) & 0xFFFU)
+typedef uint32_t VkBool32;
+typedef uint64_t VkDeviceAddress;
+typedef uint64_t VkDeviceSize;
+typedef uint32_t VkFlags;
+typedef uint32_t VkSampleMask;
+VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkBuffer)
+VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkImage)
+VK_DEFINE_HANDLE(VkInstance)
+VK_DEFINE_HANDLE(VkPhysicalDevice)
+VK_DEFINE_HANDLE(VkDevice)
+VK_DEFINE_HANDLE(VkQueue)
+VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkSemaphore)
+VK_DEFINE_HANDLE(VkCommandBuffer)
+VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkFence)
+VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkDeviceMemory)
+VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkEvent)
+VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkQueryPool)
+VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkBufferView)
+VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkImageView)
+VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkShaderModule)
+VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkPipelineCache)
+VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkPipelineLayout)
+VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkPipeline)
+VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkRenderPass)
+VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkDescriptorSetLayout)
+VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkSampler)
+VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkDescriptorSet)
+VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkDescriptorPool)
+VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkFramebuffer)
+VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkCommandPool)
+#define VK_UUID_SIZE                      16U
+#define VK_ATTACHMENT_UNUSED              (~0U)
+#define VK_FALSE                          0U
+#define VK_LOD_CLAMP_NONE                 1000.0F
+#define VK_QUEUE_FAMILY_IGNORED           (~0U)
+#define VK_REMAINING_ARRAY_LAYERS         (~0U)
+#define VK_REMAINING_MIP_LEVELS           (~0U)
+#define VK_SUBPASS_EXTERNAL               (~0U)
+#define VK_TRUE                           1U
+#define VK_WHOLE_SIZE                     (~0ULL)
+#define VK_MAX_MEMORY_TYPES               32U
+#define VK_MAX_MEMORY_HEAPS               16U
+#define VK_MAX_PHYSICAL_DEVICE_NAME_SIZE  256U
+#define VK_MAX_EXTENSION_NAME_SIZE        256U
+#define VK_MAX_DESCRIPTION_SIZE           256U
+
+typedef enum VkResult {
+    VK_SUCCESS = 0,
+    VK_NOT_READY = 1,
+    VK_TIMEOUT = 2,
+    VK_EVENT_SET = 3,
+    VK_EVENT_RESET = 4,
+    VK_INCOMPLETE = 5,
+    VK_ERROR_OUT_OF_HOST_MEMORY = -1,
+    VK_ERROR_OUT_OF_DEVICE_MEMORY = -2,
+    VK_ERROR_INITIALIZATION_FAILED = -3,
+    VK_ERROR_DEVICE_LOST = -4,
+    VK_ERROR_MEMORY_MAP_FAILED = -5,
+    VK_ERROR_LAYER_NOT_PRESENT = -6,
+    VK_ERROR_EXTENSION_NOT_PRESENT = -7,
+    VK_ERROR_FEATURE_NOT_PRESENT = -8,
+    VK_ERROR_INCOMPATIBLE_DRIVER = -9,
+    VK_ERROR_TOO_MANY_OBJECTS = -10,
+    VK_ERROR_FORMAT_NOT_SUPPORTED = -11,
+    VK_ERROR_FRAGMENTED_POOL = -12,
+    VK_ERROR_UNKNOWN = -13,
+    VK_ERROR_OUT_OF_POOL_MEMORY = -1000069000,
+    VK_ERROR_INVALID_EXTERNAL_HANDLE = -1000072003,
+    VK_ERROR_FRAGMENTATION = -1000161000,
+    VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS = -1000257000,
+    VK_ERROR_SURFACE_LOST_KHR = -1000000000,
+    VK_ERROR_NATIVE_WINDOW_IN_USE_KHR = -1000000001,
+    VK_SUBOPTIMAL_KHR = 1000001003,
+    VK_ERROR_OUT_OF_DATE_KHR = -1000001004,
+    VK_ERROR_INCOMPATIBLE_DISPLAY_KHR = -1000003001,
+    VK_ERROR_VALIDATION_FAILED_EXT = -1000011001,
+    VK_ERROR_INVALID_SHADER_NV = -1000012000,
+    VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT = -1000158000,
+    VK_ERROR_NOT_PERMITTED_EXT = -1000174001,
+    VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT = -1000255000,
+    VK_THREAD_IDLE_KHR = 1000268000,
+    VK_THREAD_DONE_KHR = 1000268001,
+    VK_OPERATION_DEFERRED_KHR = 1000268002,
+    VK_OPERATION_NOT_DEFERRED_KHR = 1000268003,
+    VK_PIPELINE_COMPILE_REQUIRED_EXT = 1000297000,
+    VK_ERROR_OUT_OF_POOL_MEMORY_KHR = VK_ERROR_OUT_OF_POOL_MEMORY,
+    VK_ERROR_INVALID_EXTERNAL_HANDLE_KHR = VK_ERROR_INVALID_EXTERNAL_HANDLE,
+    VK_ERROR_FRAGMENTATION_EXT = VK_ERROR_FRAGMENTATION,
+    VK_ERROR_INVALID_DEVICE_ADDRESS_EXT = VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS,
+    VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS_KHR = VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS,
+    VK_ERROR_PIPELINE_COMPILE_REQUIRED_EXT = VK_PIPELINE_COMPILE_REQUIRED_EXT,
+    VK_RESULT_MAX_ENUM = 0x7FFFFFFF
+} VkResult;
+
+typedef enum VkStructureType {
+    VK_STRUCTURE_TYPE_APPLICATION_INFO = 0,
+    VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO = 1,
+    VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO = 2,
+    VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO = 3,
+    VK_STRUCTURE_TYPE_SUBMIT_INFO = 4,
+    VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO = 5,
+    VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE = 6,
+    VK_STRUCTURE_TYPE_BIND_SPARSE_INFO = 7,
+    VK_STRUCTURE_TYPE_FENCE_CREATE_INFO = 8,
+    VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO = 9,
+    VK_STRUCTURE_TYPE_EVENT_CREATE_INFO = 10,
+    VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO = 11,
+    VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO = 12,
+    VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO = 13,
+    VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO = 14,
+    VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO = 15,
+    VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO = 16,
+    VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO = 17,
+    VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO = 18,
+    VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO = 19,
+    VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO = 20,
+    VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO = 21,
+    VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO = 22,
+    VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO = 23,
+    VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO = 24,
+    VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO = 25,
+    VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO = 26,
+    VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO = 27,
+    VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO = 28,
+    VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO = 29,
+    VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO = 30,
+    VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO = 31,
+    VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO = 32,
+    VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO = 33,
+    VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO = 34,
+    VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET = 35,
+    VK_STRUCTURE_TYPE_COPY_DESCRIPTOR_SET = 36,
+    VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO = 37,
+    VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO = 38,
+    VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO = 39,
+    VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO = 40,
+    VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO = 41,
+    VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO = 42,
+    VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO = 43,
+    VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER = 44,
+    VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER = 45,
+    VK_STRUCTURE_TYPE_MEMORY_BARRIER = 46,
+    VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO = 47,
+    VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO = 48,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_PROPERTIES = 1000094000,
+    VK_STRUCTURE_TYPE_BIND_BUFFER_MEMORY_INFO = 1000157000,
+    VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_INFO = 1000157001,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES = 1000083000,
+    VK_STRUCTURE_TYPE_MEMORY_DEDICATED_REQUIREMENTS = 1000127000,
+    VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO = 1000127001,
+    VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO = 1000060000,
+    VK_STRUCTURE_TYPE_DEVICE_GROUP_RENDER_PASS_BEGIN_INFO = 1000060003,
+    VK_STRUCTURE_TYPE_DEVICE_GROUP_COMMAND_BUFFER_BEGIN_INFO = 1000060004,
+    VK_STRUCTURE_TYPE_DEVICE_GROUP_SUBMIT_INFO = 1000060005,
+    VK_STRUCTURE_TYPE_DEVICE_GROUP_BIND_SPARSE_INFO = 1000060006,
+    VK_STRUCTURE_TYPE_BIND_BUFFER_MEMORY_DEVICE_GROUP_INFO = 1000060013,
+    VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_DEVICE_GROUP_INFO = 1000060014,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_GROUP_PROPERTIES = 1000070000,
+    VK_STRUCTURE_TYPE_DEVICE_GROUP_DEVICE_CREATE_INFO = 1000070001,
+    VK_STRUCTURE_TYPE_BUFFER_MEMORY_REQUIREMENTS_INFO_2 = 1000146000,
+    VK_STRUCTURE_TYPE_IMAGE_MEMORY_REQUIREMENTS_INFO_2 = 1000146001,
+    VK_STRUCTURE_TYPE_IMAGE_SPARSE_MEMORY_REQUIREMENTS_INFO_2 = 1000146002,
+    VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2 = 1000146003,
+    VK_STRUCTURE_TYPE_SPARSE_IMAGE_MEMORY_REQUIREMENTS_2 = 1000146004,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2 = 1000059000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2 = 1000059001,
+    VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2 = 1000059002,
+    VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2 = 1000059003,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2 = 1000059004,
+    VK_STRUCTURE_TYPE_QUEUE_FAMILY_PROPERTIES_2 = 1000059005,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PROPERTIES_2 = 1000059006,
+    VK_STRUCTURE_TYPE_SPARSE_IMAGE_FORMAT_PROPERTIES_2 = 1000059007,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SPARSE_IMAGE_FORMAT_INFO_2 = 1000059008,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_POINT_CLIPPING_PROPERTIES = 1000117000,
+    VK_STRUCTURE_TYPE_RENDER_PASS_INPUT_ATTACHMENT_ASPECT_CREATE_INFO = 1000117001,
+    VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO = 1000117002,
+    VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_DOMAIN_ORIGIN_STATE_CREATE_INFO = 1000117003,
+    VK_STRUCTURE_TYPE_RENDER_PASS_MULTIVIEW_CREATE_INFO = 1000053000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES = 1000053001,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_PROPERTIES = 1000053002,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VARIABLE_POINTERS_FEATURES = 1000120000,
+    VK_STRUCTURE_TYPE_PROTECTED_SUBMIT_INFO = 1000145000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_FEATURES = 1000145001,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_PROPERTIES = 1000145002,
+    VK_STRUCTURE_TYPE_DEVICE_QUEUE_INFO_2 = 1000145003,
+    VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_CREATE_INFO = 1000156000,
+    VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO = 1000156001,
+    VK_STRUCTURE_TYPE_BIND_IMAGE_PLANE_MEMORY_INFO = 1000156002,
+    VK_STRUCTURE_TYPE_IMAGE_PLANE_MEMORY_REQUIREMENTS_INFO = 1000156003,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES = 1000156004,
+    VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_IMAGE_FORMAT_PROPERTIES = 1000156005,
+    VK_STRUCTURE_TYPE_DESCRIPTOR_UPDATE_TEMPLATE_CREATE_INFO = 1000085000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_IMAGE_FORMAT_INFO = 1000071000,
+    VK_STRUCTURE_TYPE_EXTERNAL_IMAGE_FORMAT_PROPERTIES = 1000071001,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_BUFFER_INFO = 1000071002,
+    VK_STRUCTURE_TYPE_EXTERNAL_BUFFER_PROPERTIES = 1000071003,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ID_PROPERTIES = 1000071004,
+    VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO = 1000072000,
+    VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO = 1000072001,
+    VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO = 1000072002,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_FENCE_INFO = 1000112000,
+    VK_STRUCTURE_TYPE_EXTERNAL_FENCE_PROPERTIES = 1000112001,
+    VK_STRUCTURE_TYPE_EXPORT_FENCE_CREATE_INFO = 1000113000,
+    VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO = 1000077000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_SEMAPHORE_INFO = 1000076000,
+    VK_STRUCTURE_TYPE_EXTERNAL_SEMAPHORE_PROPERTIES = 1000076001,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_3_PROPERTIES = 1000168000,
+    VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_SUPPORT = 1000168001,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_DRAW_PARAMETERS_FEATURES = 1000063000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES = 49,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_PROPERTIES = 50,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES = 51,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_PROPERTIES = 52,
+    VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO = 1000147000,
+    VK_STRUCTURE_TYPE_ATTACHMENT_DESCRIPTION_2 = 1000109000,
+    VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2 = 1000109001,
+    VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_2 = 1000109002,
+    VK_STRUCTURE_TYPE_SUBPASS_DEPENDENCY_2 = 1000109003,
+    VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO_2 = 1000109004,
+    VK_STRUCTURE_TYPE_SUBPASS_BEGIN_INFO = 1000109005,
+    VK_STRUCTURE_TYPE_SUBPASS_END_INFO = 1000109006,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_8BIT_STORAGE_FEATURES = 1000177000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES = 1000196000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_ATOMIC_INT64_FEATURES = 1000180000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_FLOAT16_INT8_FEATURES = 1000082000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FLOAT_CONTROLS_PROPERTIES = 1000197000,
+    VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO = 1000161000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES = 1000161001,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_PROPERTIES = 1000161002,
+    VK_STRUCTURE_TYPE_DESCRIPTOR_SET_VARIABLE_DESCRIPTOR_COUNT_ALLOCATE_INFO = 1000161003,
+    VK_STRUCTURE_TYPE_DESCRIPTOR_SET_VARIABLE_DESCRIPTOR_COUNT_LAYOUT_SUPPORT = 1000161004,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_STENCIL_RESOLVE_PROPERTIES = 1000199000,
+    VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_DEPTH_STENCIL_RESOLVE = 1000199001,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SCALAR_BLOCK_LAYOUT_FEATURES = 1000221000,
+    VK_STRUCTURE_TYPE_IMAGE_STENCIL_USAGE_CREATE_INFO = 1000246000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_FILTER_MINMAX_PROPERTIES = 1000130000,
+    VK_STRUCTURE_TYPE_SAMPLER_REDUCTION_MODE_CREATE_INFO = 1000130001,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_MEMORY_MODEL_FEATURES = 1000211000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGELESS_FRAMEBUFFER_FEATURES = 1000108000,
+    VK_STRUCTURE_TYPE_FRAMEBUFFER_ATTACHMENTS_CREATE_INFO = 1000108001,
+    VK_STRUCTURE_TYPE_FRAMEBUFFER_ATTACHMENT_IMAGE_INFO = 1000108002,
+    VK_STRUCTURE_TYPE_RENDER_PASS_ATTACHMENT_BEGIN_INFO = 1000108003,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_UNIFORM_BUFFER_STANDARD_LAYOUT_FEATURES = 1000253000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_SUBGROUP_EXTENDED_TYPES_FEATURES = 1000175000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SEPARATE_DEPTH_STENCIL_LAYOUTS_FEATURES = 1000241000,
+    VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_STENCIL_LAYOUT = 1000241001,
+    VK_STRUCTURE_TYPE_ATTACHMENT_DESCRIPTION_STENCIL_LAYOUT = 1000241002,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_HOST_QUERY_RESET_FEATURES = 1000261000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES = 1000207000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_PROPERTIES = 1000207001,
+    VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO = 1000207002,
+    VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO = 1000207003,
+    VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO = 1000207004,
+    VK_STRUCTURE_TYPE_SEMAPHORE_SIGNAL_INFO = 1000207005,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES = 1000257000,
+    VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO = 1000244001,
+    VK_STRUCTURE_TYPE_BUFFER_OPAQUE_CAPTURE_ADDRESS_CREATE_INFO = 1000257002,
+    VK_STRUCTURE_TYPE_MEMORY_OPAQUE_CAPTURE_ADDRESS_ALLOCATE_INFO = 1000257003,
+    VK_STRUCTURE_TYPE_DEVICE_MEMORY_OPAQUE_CAPTURE_ADDRESS_INFO = 1000257004,
+    VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR = 1000001000,
+    VK_STRUCTURE_TYPE_PRESENT_INFO_KHR = 1000001001,
+    VK_STRUCTURE_TYPE_DEVICE_GROUP_PRESENT_CAPABILITIES_KHR = 1000060007,
+    VK_STRUCTURE_TYPE_IMAGE_SWAPCHAIN_CREATE_INFO_KHR = 1000060008,
+    VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_SWAPCHAIN_INFO_KHR = 1000060009,
+    VK_STRUCTURE_TYPE_ACQUIRE_NEXT_IMAGE_INFO_KHR = 1000060010,
+    VK_STRUCTURE_TYPE_DEVICE_GROUP_PRESENT_INFO_KHR = 1000060011,
+    VK_STRUCTURE_TYPE_DEVICE_GROUP_SWAPCHAIN_CREATE_INFO_KHR = 1000060012,
+    VK_STRUCTURE_TYPE_DISPLAY_MODE_CREATE_INFO_KHR = 1000002000,
+    VK_STRUCTURE_TYPE_DISPLAY_SURFACE_CREATE_INFO_KHR = 1000002001,
+    VK_STRUCTURE_TYPE_DISPLAY_PRESENT_INFO_KHR = 1000003000,
+    VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR = 1000004000,
+    VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR = 1000005000,
+    VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR = 1000006000,
+    VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR = 1000008000,
+    VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR = 1000009000,
+    VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT = 1000011000,
+    VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_RASTERIZATION_ORDER_AMD = 1000018000,
+    VK_STRUCTURE_TYPE_DEBUG_MARKER_OBJECT_NAME_INFO_EXT = 1000022000,
+    VK_STRUCTURE_TYPE_DEBUG_MARKER_OBJECT_TAG_INFO_EXT = 1000022001,
+    VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT = 1000022002,
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_VIDEO_PROFILE_KHR = 1000023000,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_VIDEO_CAPABILITIES_KHR = 1000023001,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_VIDEO_PICTURE_RESOURCE_KHR = 1000023002,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_VIDEO_GET_MEMORY_PROPERTIES_KHR = 1000023003,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_VIDEO_BIND_MEMORY_KHR = 1000023004,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_VIDEO_SESSION_CREATE_INFO_KHR = 1000023005,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_VIDEO_SESSION_PARAMETERS_CREATE_INFO_KHR = 1000023006,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_VIDEO_SESSION_PARAMETERS_UPDATE_INFO_KHR = 1000023007,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_VIDEO_BEGIN_CODING_INFO_KHR = 1000023008,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_VIDEO_END_CODING_INFO_KHR = 1000023009,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_VIDEO_CODING_CONTROL_INFO_KHR = 1000023010,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_VIDEO_REFERENCE_SLOT_KHR = 1000023011,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_VIDEO_QUEUE_FAMILY_PROPERTIES_2_KHR = 1000023012,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_VIDEO_PROFILES_KHR = 1000023013,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VIDEO_FORMAT_INFO_KHR = 1000023014,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_VIDEO_FORMAT_PROPERTIES_KHR = 1000023015,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_VIDEO_DECODE_INFO_KHR = 1000024000,
+#endif
+    VK_STRUCTURE_TYPE_DEDICATED_ALLOCATION_IMAGE_CREATE_INFO_NV = 1000026000,
+    VK_STRUCTURE_TYPE_DEDICATED_ALLOCATION_BUFFER_CREATE_INFO_NV = 1000026001,
+    VK_STRUCTURE_TYPE_DEDICATED_ALLOCATION_MEMORY_ALLOCATE_INFO_NV = 1000026002,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TRANSFORM_FEEDBACK_FEATURES_EXT = 1000028000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TRANSFORM_FEEDBACK_PROPERTIES_EXT = 1000028001,
+    VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_STREAM_CREATE_INFO_EXT = 1000028002,
+    VK_STRUCTURE_TYPE_CU_MODULE_CREATE_INFO_NVX = 1000029000,
+    VK_STRUCTURE_TYPE_CU_FUNCTION_CREATE_INFO_NVX = 1000029001,
+    VK_STRUCTURE_TYPE_CU_LAUNCH_INFO_NVX = 1000029002,
+    VK_STRUCTURE_TYPE_IMAGE_VIEW_HANDLE_INFO_NVX = 1000030000,
+    VK_STRUCTURE_TYPE_IMAGE_VIEW_ADDRESS_PROPERTIES_NVX = 1000030001,
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_VIDEO_ENCODE_H264_CAPABILITIES_EXT = 1000038000,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_VIDEO_ENCODE_H264_SESSION_CREATE_INFO_EXT = 1000038001,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_VIDEO_ENCODE_H264_SESSION_PARAMETERS_CREATE_INFO_EXT = 1000038002,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_VIDEO_ENCODE_H264_SESSION_PARAMETERS_ADD_INFO_EXT = 1000038003,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_VIDEO_ENCODE_H264_VCL_FRAME_INFO_EXT = 1000038004,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_VIDEO_ENCODE_H264_DPB_SLOT_INFO_EXT = 1000038005,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_VIDEO_ENCODE_H264_NALU_SLICE_EXT = 1000038006,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_VIDEO_ENCODE_H264_EMIT_PICTURE_PARAMETERS_EXT = 1000038007,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_VIDEO_ENCODE_H264_PROFILE_EXT = 1000038008,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_VIDEO_ENCODE_H265_CAPABILITIES_EXT = 1000039000,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_VIDEO_ENCODE_H265_SESSION_CREATE_INFO_EXT = 1000039001,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_VIDEO_ENCODE_H265_SESSION_PARAMETERS_CREATE_INFO_EXT = 1000039002,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_VIDEO_ENCODE_H265_SESSION_PARAMETERS_ADD_INFO_EXT = 1000039003,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_VIDEO_ENCODE_H265_VCL_FRAME_INFO_EXT = 1000039004,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_VIDEO_ENCODE_H265_DPB_SLOT_INFO_EXT = 1000039005,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_VIDEO_ENCODE_H265_NALU_SLICE_EXT = 1000039006,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_VIDEO_ENCODE_H265_EMIT_PICTURE_PARAMETERS_EXT = 1000039007,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_VIDEO_ENCODE_H265_PROFILE_EXT = 1000039008,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_VIDEO_ENCODE_H265_REFERENCE_LISTS_EXT = 1000039009,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_VIDEO_DECODE_H264_CAPABILITIES_EXT = 1000040000,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_VIDEO_DECODE_H264_SESSION_CREATE_INFO_EXT = 1000040001,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_VIDEO_DECODE_H264_PICTURE_INFO_EXT = 1000040002,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_VIDEO_DECODE_H264_MVC_EXT = 1000040003,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_VIDEO_DECODE_H264_PROFILE_EXT = 1000040004,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_VIDEO_DECODE_H264_SESSION_PARAMETERS_CREATE_INFO_EXT = 1000040005,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_VIDEO_DECODE_H264_SESSION_PARAMETERS_ADD_INFO_EXT = 1000040006,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_VIDEO_DECODE_H264_DPB_SLOT_INFO_EXT = 1000040007,
+#endif
+    VK_STRUCTURE_TYPE_TEXTURE_LOD_GATHER_FORMAT_PROPERTIES_AMD = 1000041000,
+    VK_STRUCTURE_TYPE_RENDERING_INFO_KHR = 1000044000,
+    VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR = 1000044001,
+    VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR = 1000044002,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES_KHR = 1000044003,
+    VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_RENDERING_INFO_KHR = 1000044004,
+    VK_STRUCTURE_TYPE_RENDERING_FRAGMENT_SHADING_RATE_ATTACHMENT_INFO_KHR = 1000044006,
+    VK_STRUCTURE_TYPE_RENDERING_FRAGMENT_DENSITY_MAP_ATTACHMENT_INFO_EXT = 1000044007,
+    VK_STRUCTURE_TYPE_ATTACHMENT_SAMPLE_COUNT_INFO_AMD = 1000044008,
+    VK_STRUCTURE_TYPE_MULTIVIEW_PER_VIEW_ATTRIBUTES_INFO_NVX = 1000044009,
+    VK_STRUCTURE_TYPE_STREAM_DESCRIPTOR_SURFACE_CREATE_INFO_GGP = 1000049000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CORNER_SAMPLED_IMAGE_FEATURES_NV = 1000050000,
+    VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO_NV = 1000056000,
+    VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO_NV = 1000056001,
+    VK_STRUCTURE_TYPE_IMPORT_MEMORY_WIN32_HANDLE_INFO_NV = 1000057000,
+    VK_STRUCTURE_TYPE_EXPORT_MEMORY_WIN32_HANDLE_INFO_NV = 1000057001,
+    VK_STRUCTURE_TYPE_WIN32_KEYED_MUTEX_ACQUIRE_RELEASE_INFO_NV = 1000058000,
+    VK_STRUCTURE_TYPE_VALIDATION_FLAGS_EXT = 1000061000,
+    VK_STRUCTURE_TYPE_VI_SURFACE_CREATE_INFO_NN = 1000062000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TEXTURE_COMPRESSION_ASTC_HDR_FEATURES_EXT = 1000066000,
+    VK_STRUCTURE_TYPE_IMAGE_VIEW_ASTC_DECODE_MODE_EXT = 1000067000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ASTC_DECODE_FEATURES_EXT = 1000067001,
+    VK_STRUCTURE_TYPE_IMPORT_MEMORY_WIN32_HANDLE_INFO_KHR = 1000073000,
+    VK_STRUCTURE_TYPE_EXPORT_MEMORY_WIN32_HANDLE_INFO_KHR = 1000073001,
+    VK_STRUCTURE_TYPE_MEMORY_WIN32_HANDLE_PROPERTIES_KHR = 1000073002,
+    VK_STRUCTURE_TYPE_MEMORY_GET_WIN32_HANDLE_INFO_KHR = 1000073003,
+    VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR = 1000074000,
+    VK_STRUCTURE_TYPE_MEMORY_FD_PROPERTIES_KHR = 1000074001,
+    VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR = 1000074002,
+    VK_STRUCTURE_TYPE_WIN32_KEYED_MUTEX_ACQUIRE_RELEASE_INFO_KHR = 1000075000,
+    VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_WIN32_HANDLE_INFO_KHR = 1000078000,
+    VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_WIN32_HANDLE_INFO_KHR = 1000078001,
+    VK_STRUCTURE_TYPE_D3D12_FENCE_SUBMIT_INFO_KHR = 1000078002,
+    VK_STRUCTURE_TYPE_SEMAPHORE_GET_WIN32_HANDLE_INFO_KHR = 1000078003,
+    VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_FD_INFO_KHR = 1000079000,
+    VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR = 1000079001,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PUSH_DESCRIPTOR_PROPERTIES_KHR = 1000080000,
+    VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_CONDITIONAL_RENDERING_INFO_EXT = 1000081000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CONDITIONAL_RENDERING_FEATURES_EXT = 1000081001,
+    VK_STRUCTURE_TYPE_CONDITIONAL_RENDERING_BEGIN_INFO_EXT = 1000081002,
+    VK_STRUCTURE_TYPE_PRESENT_REGIONS_KHR = 1000084000,
+    VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_W_SCALING_STATE_CREATE_INFO_NV = 1000087000,
+    VK_STRUCTURE_TYPE_SURFACE_CAPABILITIES_2_EXT = 1000090000,
+    VK_STRUCTURE_TYPE_DISPLAY_POWER_INFO_EXT = 1000091000,
+    VK_STRUCTURE_TYPE_DEVICE_EVENT_INFO_EXT = 1000091001,
+    VK_STRUCTURE_TYPE_DISPLAY_EVENT_INFO_EXT = 1000091002,
+    VK_STRUCTURE_TYPE_SWAPCHAIN_COUNTER_CREATE_INFO_EXT = 1000091003,
+    VK_STRUCTURE_TYPE_PRESENT_TIMES_INFO_GOOGLE = 1000092000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_PER_VIEW_ATTRIBUTES_PROPERTIES_NVX = 1000097000,
+    VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_SWIZZLE_STATE_CREATE_INFO_NV = 1000098000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DISCARD_RECTANGLE_PROPERTIES_EXT = 1000099000,
+    VK_STRUCTURE_TYPE_PIPELINE_DISCARD_RECTANGLE_STATE_CREATE_INFO_EXT = 1000099001,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CONSERVATIVE_RASTERIZATION_PROPERTIES_EXT = 1000101000,
+    VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_CONSERVATIVE_STATE_CREATE_INFO_EXT = 1000101001,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_CLIP_ENABLE_FEATURES_EXT = 1000102000,
+    VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_DEPTH_CLIP_STATE_CREATE_INFO_EXT = 1000102001,
+    VK_STRUCTURE_TYPE_HDR_METADATA_EXT = 1000105000,
+    VK_STRUCTURE_TYPE_SHARED_PRESENT_SURFACE_CAPABILITIES_KHR = 1000111000,
+    VK_STRUCTURE_TYPE_IMPORT_FENCE_WIN32_HANDLE_INFO_KHR = 1000114000,
+    VK_STRUCTURE_TYPE_EXPORT_FENCE_WIN32_HANDLE_INFO_KHR = 1000114001,
+    VK_STRUCTURE_TYPE_FENCE_GET_WIN32_HANDLE_INFO_KHR = 1000114002,
+    VK_STRUCTURE_TYPE_IMPORT_FENCE_FD_INFO_KHR = 1000115000,
+    VK_STRUCTURE_TYPE_FENCE_GET_FD_INFO_KHR = 1000115001,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PERFORMANCE_QUERY_FEATURES_KHR = 1000116000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PERFORMANCE_QUERY_PROPERTIES_KHR = 1000116001,
+    VK_STRUCTURE_TYPE_QUERY_POOL_PERFORMANCE_CREATE_INFO_KHR = 1000116002,
+    VK_STRUCTURE_TYPE_PERFORMANCE_QUERY_SUBMIT_INFO_KHR = 1000116003,
+    VK_STRUCTURE_TYPE_ACQUIRE_PROFILING_LOCK_INFO_KHR = 1000116004,
+    VK_STRUCTURE_TYPE_PERFORMANCE_COUNTER_KHR = 1000116005,
+    VK_STRUCTURE_TYPE_PERFORMANCE_COUNTER_DESCRIPTION_KHR = 1000116006,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SURFACE_INFO_2_KHR = 1000119000,
+    VK_STRUCTURE_TYPE_SURFACE_CAPABILITIES_2_KHR = 1000119001,
+    VK_STRUCTURE_TYPE_SURFACE_FORMAT_2_KHR = 1000119002,
+    VK_STRUCTURE_TYPE_DISPLAY_PROPERTIES_2_KHR = 1000121000,
+    VK_STRUCTURE_TYPE_DISPLAY_PLANE_PROPERTIES_2_KHR = 1000121001,
+    VK_STRUCTURE_TYPE_DISPLAY_MODE_PROPERTIES_2_KHR = 1000121002,
+    VK_STRUCTURE_TYPE_DISPLAY_PLANE_INFO_2_KHR = 1000121003,
+    VK_STRUCTURE_TYPE_DISPLAY_PLANE_CAPABILITIES_2_KHR = 1000121004,
+    VK_STRUCTURE_TYPE_IOS_SURFACE_CREATE_INFO_MVK = 1000122000,
+    VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK = 1000123000,
+    VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT = 1000128000,
+    VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_TAG_INFO_EXT = 1000128001,
+    VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT = 1000128002,
+    VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CALLBACK_DATA_EXT = 1000128003,
+    VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT = 1000128004,
+    VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_USAGE_ANDROID = 1000129000,
+    VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_PROPERTIES_ANDROID = 1000129001,
+    VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_FORMAT_PROPERTIES_ANDROID = 1000129002,
+    VK_STRUCTURE_TYPE_IMPORT_ANDROID_HARDWARE_BUFFER_INFO_ANDROID = 1000129003,
+    VK_STRUCTURE_TYPE_MEMORY_GET_ANDROID_HARDWARE_BUFFER_INFO_ANDROID = 1000129004,
+    VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID = 1000129005,
+    VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_FORMAT_PROPERTIES_2_ANDROID = 1000129006,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INLINE_UNIFORM_BLOCK_FEATURES_EXT = 1000138000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INLINE_UNIFORM_BLOCK_PROPERTIES_EXT = 1000138001,
+    VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_INLINE_UNIFORM_BLOCK_EXT = 1000138002,
+    VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_INLINE_UNIFORM_BLOCK_CREATE_INFO_EXT = 1000138003,
+    VK_STRUCTURE_TYPE_SAMPLE_LOCATIONS_INFO_EXT = 1000143000,
+    VK_STRUCTURE_TYPE_RENDER_PASS_SAMPLE_LOCATIONS_BEGIN_INFO_EXT = 1000143001,
+    VK_STRUCTURE_TYPE_PIPELINE_SAMPLE_LOCATIONS_STATE_CREATE_INFO_EXT = 1000143002,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLE_LOCATIONS_PROPERTIES_EXT = 1000143003,
+    VK_STRUCTURE_TYPE_MULTISAMPLE_PROPERTIES_EXT = 1000143004,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BLEND_OPERATION_ADVANCED_FEATURES_EXT = 1000148000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BLEND_OPERATION_ADVANCED_PROPERTIES_EXT = 1000148001,
+    VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_ADVANCED_STATE_CREATE_INFO_EXT = 1000148002,
+    VK_STRUCTURE_TYPE_PIPELINE_COVERAGE_TO_COLOR_STATE_CREATE_INFO_NV = 1000149000,
+    VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR = 1000150007,
+    VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR = 1000150000,
+    VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_DEVICE_ADDRESS_INFO_KHR = 1000150002,
+    VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_AABBS_DATA_KHR = 1000150003,
+    VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_INSTANCES_DATA_KHR = 1000150004,
+    VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR = 1000150005,
+    VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR = 1000150006,
+    VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_VERSION_INFO_KHR = 1000150009,
+    VK_STRUCTURE_TYPE_COPY_ACCELERATION_STRUCTURE_INFO_KHR = 1000150010,
+    VK_STRUCTURE_TYPE_COPY_ACCELERATION_STRUCTURE_TO_MEMORY_INFO_KHR = 1000150011,
+    VK_STRUCTURE_TYPE_COPY_MEMORY_TO_ACCELERATION_STRUCTURE_INFO_KHR = 1000150012,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_FEATURES_KHR = 1000150013,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_PROPERTIES_KHR = 1000150014,
+    VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_CREATE_INFO_KHR = 1000150017,
+    VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_SIZES_INFO_KHR = 1000150020,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_FEATURES_KHR = 1000347000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_PROPERTIES_KHR = 1000347001,
+    VK_STRUCTURE_TYPE_RAY_TRACING_PIPELINE_CREATE_INFO_KHR = 1000150015,
+    VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR = 1000150016,
+    VK_STRUCTURE_TYPE_RAY_TRACING_PIPELINE_INTERFACE_CREATE_INFO_KHR = 1000150018,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_QUERY_FEATURES_KHR = 1000348013,
+    VK_STRUCTURE_TYPE_PIPELINE_COVERAGE_MODULATION_STATE_CREATE_INFO_NV = 1000152000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_SM_BUILTINS_FEATURES_NV = 1000154000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_SM_BUILTINS_PROPERTIES_NV = 1000154001,
+    VK_STRUCTURE_TYPE_DRM_FORMAT_MODIFIER_PROPERTIES_LIST_EXT = 1000158000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_DRM_FORMAT_MODIFIER_INFO_EXT = 1000158002,
+    VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_LIST_CREATE_INFO_EXT = 1000158003,
+    VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_EXPLICIT_CREATE_INFO_EXT = 1000158004,
+    VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_PROPERTIES_EXT = 1000158005,
+    VK_STRUCTURE_TYPE_DRM_FORMAT_MODIFIER_PROPERTIES_LIST_2_EXT = 1000158006,
+    VK_STRUCTURE_TYPE_VALIDATION_CACHE_CREATE_INFO_EXT = 1000160000,
+    VK_STRUCTURE_TYPE_SHADER_MODULE_VALIDATION_CACHE_CREATE_INFO_EXT = 1000160001,
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PORTABILITY_SUBSET_FEATURES_KHR = 1000163000,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PORTABILITY_SUBSET_PROPERTIES_KHR = 1000163001,
+#endif
+    VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_SHADING_RATE_IMAGE_STATE_CREATE_INFO_NV = 1000164000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADING_RATE_IMAGE_FEATURES_NV = 1000164001,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADING_RATE_IMAGE_PROPERTIES_NV = 1000164002,
+    VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_COARSE_SAMPLE_ORDER_STATE_CREATE_INFO_NV = 1000164005,
+    VK_STRUCTURE_TYPE_RAY_TRACING_PIPELINE_CREATE_INFO_NV = 1000165000,
+    VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_CREATE_INFO_NV = 1000165001,
+    VK_STRUCTURE_TYPE_GEOMETRY_NV = 1000165003,
+    VK_STRUCTURE_TYPE_GEOMETRY_TRIANGLES_NV = 1000165004,
+    VK_STRUCTURE_TYPE_GEOMETRY_AABB_NV = 1000165005,
+    VK_STRUCTURE_TYPE_BIND_ACCELERATION_STRUCTURE_MEMORY_INFO_NV = 1000165006,
+    VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_NV = 1000165007,
+    VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_MEMORY_REQUIREMENTS_INFO_NV = 1000165008,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PROPERTIES_NV = 1000165009,
+    VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_NV = 1000165011,
+    VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_INFO_NV = 1000165012,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_REPRESENTATIVE_FRAGMENT_TEST_FEATURES_NV = 1000166000,
+    VK_STRUCTURE_TYPE_PIPELINE_REPRESENTATIVE_FRAGMENT_TEST_STATE_CREATE_INFO_NV = 1000166001,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_VIEW_IMAGE_FORMAT_INFO_EXT = 1000170000,
+    VK_STRUCTURE_TYPE_FILTER_CUBIC_IMAGE_VIEW_IMAGE_FORMAT_PROPERTIES_EXT = 1000170001,
+    VK_STRUCTURE_TYPE_DEVICE_QUEUE_GLOBAL_PRIORITY_CREATE_INFO_EXT = 1000174000,
+    VK_STRUCTURE_TYPE_IMPORT_MEMORY_HOST_POINTER_INFO_EXT = 1000178000,
+    VK_STRUCTURE_TYPE_MEMORY_HOST_POINTER_PROPERTIES_EXT = 1000178001,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_MEMORY_HOST_PROPERTIES_EXT = 1000178002,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_CLOCK_FEATURES_KHR = 1000181000,
+    VK_STRUCTURE_TYPE_PIPELINE_COMPILER_CONTROL_CREATE_INFO_AMD = 1000183000,
+    VK_STRUCTURE_TYPE_CALIBRATED_TIMESTAMP_INFO_EXT = 1000184000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_CORE_PROPERTIES_AMD = 1000185000,
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_VIDEO_DECODE_H265_CAPABILITIES_EXT = 1000187000,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_VIDEO_DECODE_H265_SESSION_CREATE_INFO_EXT = 1000187001,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_VIDEO_DECODE_H265_SESSION_PARAMETERS_CREATE_INFO_EXT = 1000187002,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_VIDEO_DECODE_H265_SESSION_PARAMETERS_ADD_INFO_EXT = 1000187003,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_VIDEO_DECODE_H265_PROFILE_EXT = 1000187004,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_VIDEO_DECODE_H265_PICTURE_INFO_EXT = 1000187005,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_VIDEO_DECODE_H265_DPB_SLOT_INFO_EXT = 1000187006,
+#endif
+    VK_STRUCTURE_TYPE_DEVICE_MEMORY_OVERALLOCATION_CREATE_INFO_AMD = 1000189000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VERTEX_ATTRIBUTE_DIVISOR_PROPERTIES_EXT = 1000190000,
+    VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_DIVISOR_STATE_CREATE_INFO_EXT = 1000190001,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VERTEX_ATTRIBUTE_DIVISOR_FEATURES_EXT = 1000190002,
+    VK_STRUCTURE_TYPE_PRESENT_FRAME_TOKEN_GGP = 1000191000,
+    VK_STRUCTURE_TYPE_PIPELINE_CREATION_FEEDBACK_CREATE_INFO_EXT = 1000192000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COMPUTE_SHADER_DERIVATIVES_FEATURES_NV = 1000201000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MESH_SHADER_FEATURES_NV = 1000202000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MESH_SHADER_PROPERTIES_NV = 1000202001,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADER_BARYCENTRIC_FEATURES_NV = 1000203000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_IMAGE_FOOTPRINT_FEATURES_NV = 1000204000,
+    VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_EXCLUSIVE_SCISSOR_STATE_CREATE_INFO_NV = 1000205000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXCLUSIVE_SCISSOR_FEATURES_NV = 1000205002,
+    VK_STRUCTURE_TYPE_CHECKPOINT_DATA_NV = 1000206000,
+    VK_STRUCTURE_TYPE_QUEUE_FAMILY_CHECKPOINT_PROPERTIES_NV = 1000206001,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_INTEGER_FUNCTIONS_2_FEATURES_INTEL = 1000209000,
+    VK_STRUCTURE_TYPE_QUERY_POOL_PERFORMANCE_QUERY_CREATE_INFO_INTEL = 1000210000,
+    VK_STRUCTURE_TYPE_INITIALIZE_PERFORMANCE_API_INFO_INTEL = 1000210001,
+    VK_STRUCTURE_TYPE_PERFORMANCE_MARKER_INFO_INTEL = 1000210002,
+    VK_STRUCTURE_TYPE_PERFORMANCE_STREAM_MARKER_INFO_INTEL = 1000210003,
+    VK_STRUCTURE_TYPE_PERFORMANCE_OVERRIDE_INFO_INTEL = 1000210004,
+    VK_STRUCTURE_TYPE_PERFORMANCE_CONFIGURATION_ACQUIRE_INFO_INTEL = 1000210005,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PCI_BUS_INFO_PROPERTIES_EXT = 1000212000,
+    VK_STRUCTURE_TYPE_DISPLAY_NATIVE_HDR_SURFACE_CAPABILITIES_AMD = 1000213000,
+    VK_STRUCTURE_TYPE_SWAPCHAIN_DISPLAY_NATIVE_HDR_CREATE_INFO_AMD = 1000213001,
+    VK_STRUCTURE_TYPE_IMAGEPIPE_SURFACE_CREATE_INFO_FUCHSIA = 1000214000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_TERMINATE_INVOCATION_FEATURES_KHR = 1000215000,
+    VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT = 1000217000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_DENSITY_MAP_FEATURES_EXT = 1000218000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_DENSITY_MAP_PROPERTIES_EXT = 1000218001,
+    VK_STRUCTURE_TYPE_RENDER_PASS_FRAGMENT_DENSITY_MAP_CREATE_INFO_EXT = 1000218002,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_SIZE_CONTROL_PROPERTIES_EXT = 1000225000,
+    VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_REQUIRED_SUBGROUP_SIZE_CREATE_INFO_EXT = 1000225001,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_SIZE_CONTROL_FEATURES_EXT = 1000225002,
+    VK_STRUCTURE_TYPE_FRAGMENT_SHADING_RATE_ATTACHMENT_INFO_KHR = 1000226000,
+    VK_STRUCTURE_TYPE_PIPELINE_FRAGMENT_SHADING_RATE_STATE_CREATE_INFO_KHR = 1000226001,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_PROPERTIES_KHR = 1000226002,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_FEATURES_KHR = 1000226003,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_KHR = 1000226004,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_CORE_PROPERTIES_2_AMD = 1000227000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COHERENT_MEMORY_FEATURES_AMD = 1000229000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_IMAGE_ATOMIC_INT64_FEATURES_EXT = 1000234000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_BUDGET_PROPERTIES_EXT = 1000237000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PRIORITY_FEATURES_EXT = 1000238000,
+    VK_STRUCTURE_TYPE_MEMORY_PRIORITY_ALLOCATE_INFO_EXT = 1000238001,
+    VK_STRUCTURE_TYPE_SURFACE_PROTECTED_CAPABILITIES_KHR = 1000239000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEDICATED_ALLOCATION_IMAGE_ALIASING_FEATURES_NV = 1000240000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES_EXT = 1000244000,
+    VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_CREATE_INFO_EXT = 1000244002,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TOOL_PROPERTIES_EXT = 1000245000,
+    VK_STRUCTURE_TYPE_VALIDATION_FEATURES_EXT = 1000247000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_WAIT_FEATURES_KHR = 1000248000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COOPERATIVE_MATRIX_FEATURES_NV = 1000249000,
+    VK_STRUCTURE_TYPE_COOPERATIVE_MATRIX_PROPERTIES_NV = 1000249001,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COOPERATIVE_MATRIX_PROPERTIES_NV = 1000249002,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COVERAGE_REDUCTION_MODE_FEATURES_NV = 1000250000,
+    VK_STRUCTURE_TYPE_PIPELINE_COVERAGE_REDUCTION_STATE_CREATE_INFO_NV = 1000250001,
+    VK_STRUCTURE_TYPE_FRAMEBUFFER_MIXED_SAMPLES_COMBINATION_NV = 1000250002,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADER_INTERLOCK_FEATURES_EXT = 1000251000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_YCBCR_IMAGE_ARRAYS_FEATURES_EXT = 1000252000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROVOKING_VERTEX_FEATURES_EXT = 1000254000,
+    VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_PROVOKING_VERTEX_STATE_CREATE_INFO_EXT = 1000254001,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROVOKING_VERTEX_PROPERTIES_EXT = 1000254002,
+    VK_STRUCTURE_TYPE_SURFACE_FULL_SCREEN_EXCLUSIVE_INFO_EXT = 1000255000,
+    VK_STRUCTURE_TYPE_SURFACE_CAPABILITIES_FULL_SCREEN_EXCLUSIVE_EXT = 1000255002,
+    VK_STRUCTURE_TYPE_SURFACE_FULL_SCREEN_EXCLUSIVE_WIN32_INFO_EXT = 1000255001,
+    VK_STRUCTURE_TYPE_HEADLESS_SURFACE_CREATE_INFO_EXT = 1000256000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_LINE_RASTERIZATION_FEATURES_EXT = 1000259000,
+    VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_LINE_STATE_CREATE_INFO_EXT = 1000259001,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_LINE_RASTERIZATION_PROPERTIES_EXT = 1000259002,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_ATOMIC_FLOAT_FEATURES_EXT = 1000260000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INDEX_TYPE_UINT8_FEATURES_EXT = 1000265000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_FEATURES_EXT = 1000267000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PIPELINE_EXECUTABLE_PROPERTIES_FEATURES_KHR = 1000269000,
+    VK_STRUCTURE_TYPE_PIPELINE_INFO_KHR = 1000269001,
+    VK_STRUCTURE_TYPE_PIPELINE_EXECUTABLE_PROPERTIES_KHR = 1000269002,
+    VK_STRUCTURE_TYPE_PIPELINE_EXECUTABLE_INFO_KHR = 1000269003,
+    VK_STRUCTURE_TYPE_PIPELINE_EXECUTABLE_STATISTIC_KHR = 1000269004,
+    VK_STRUCTURE_TYPE_PIPELINE_EXECUTABLE_INTERNAL_REPRESENTATION_KHR = 1000269005,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_ATOMIC_FLOAT_2_FEATURES_EXT = 1000273000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_DEMOTE_TO_HELPER_INVOCATION_FEATURES_EXT = 1000276000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEVICE_GENERATED_COMMANDS_PROPERTIES_NV = 1000277000,
+    VK_STRUCTURE_TYPE_GRAPHICS_SHADER_GROUP_CREATE_INFO_NV = 1000277001,
+    VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_SHADER_GROUPS_CREATE_INFO_NV = 1000277002,
+    VK_STRUCTURE_TYPE_INDIRECT_COMMANDS_LAYOUT_TOKEN_NV = 1000277003,
+    VK_STRUCTURE_TYPE_INDIRECT_COMMANDS_LAYOUT_CREATE_INFO_NV = 1000277004,
+    VK_STRUCTURE_TYPE_GENERATED_COMMANDS_INFO_NV = 1000277005,
+    VK_STRUCTURE_TYPE_GENERATED_COMMANDS_MEMORY_REQUIREMENTS_INFO_NV = 1000277006,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEVICE_GENERATED_COMMANDS_FEATURES_NV = 1000277007,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INHERITED_VIEWPORT_SCISSOR_FEATURES_NV = 1000278000,
+    VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_VIEWPORT_SCISSOR_INFO_NV = 1000278001,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_INTEGER_DOT_PRODUCT_FEATURES_KHR = 1000280000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_INTEGER_DOT_PRODUCT_PROPERTIES_KHR = 1000280001,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TEXEL_BUFFER_ALIGNMENT_FEATURES_EXT = 1000281000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TEXEL_BUFFER_ALIGNMENT_PROPERTIES_EXT = 1000281001,
+    VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_RENDER_PASS_TRANSFORM_INFO_QCOM = 1000282000,
+    VK_STRUCTURE_TYPE_RENDER_PASS_TRANSFORM_BEGIN_INFO_QCOM = 1000282001,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEVICE_MEMORY_REPORT_FEATURES_EXT = 1000284000,
+    VK_STRUCTURE_TYPE_DEVICE_DEVICE_MEMORY_REPORT_CREATE_INFO_EXT = 1000284001,
+    VK_STRUCTURE_TYPE_DEVICE_MEMORY_REPORT_CALLBACK_DATA_EXT = 1000284002,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ROBUSTNESS_2_FEATURES_EXT = 1000286000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ROBUSTNESS_2_PROPERTIES_EXT = 1000286001,
+    VK_STRUCTURE_TYPE_SAMPLER_CUSTOM_BORDER_COLOR_CREATE_INFO_EXT = 1000287000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CUSTOM_BORDER_COLOR_PROPERTIES_EXT = 1000287001,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CUSTOM_BORDER_COLOR_FEATURES_EXT = 1000287002,
+    VK_STRUCTURE_TYPE_PIPELINE_LIBRARY_CREATE_INFO_KHR = 1000290000,
+    VK_STRUCTURE_TYPE_PRESENT_ID_KHR = 1000294000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_ID_FEATURES_KHR = 1000294001,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRIVATE_DATA_FEATURES_EXT = 1000295000,
+    VK_STRUCTURE_TYPE_DEVICE_PRIVATE_DATA_CREATE_INFO_EXT = 1000295001,
+    VK_STRUCTURE_TYPE_PRIVATE_DATA_SLOT_CREATE_INFO_EXT = 1000295002,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PIPELINE_CREATION_CACHE_CONTROL_FEATURES_EXT = 1000297000,
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_VIDEO_ENCODE_INFO_KHR = 1000299000,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_STRUCTURE_TYPE_VIDEO_ENCODE_RATE_CONTROL_INFO_KHR = 1000299001,
+#endif
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DIAGNOSTICS_CONFIG_FEATURES_NV = 1000300000,
+    VK_STRUCTURE_TYPE_DEVICE_DIAGNOSTICS_CONFIG_CREATE_INFO_NV = 1000300001,
+    VK_STRUCTURE_TYPE_MEMORY_BARRIER_2_KHR = 1000314000,
+    VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2_KHR = 1000314001,
+    VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2_KHR = 1000314002,
+    VK_STRUCTURE_TYPE_DEPENDENCY_INFO_KHR = 1000314003,
+    VK_STRUCTURE_TYPE_SUBMIT_INFO_2_KHR = 1000314004,
+    VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO_KHR = 1000314005,
+    VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO_KHR = 1000314006,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SYNCHRONIZATION_2_FEATURES_KHR = 1000314007,
+    VK_STRUCTURE_TYPE_QUEUE_FAMILY_CHECKPOINT_PROPERTIES_2_NV = 1000314008,
+    VK_STRUCTURE_TYPE_CHECKPOINT_DATA_2_NV = 1000314009,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_SUBGROUP_UNIFORM_CONTROL_FLOW_FEATURES_KHR = 1000323000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ZERO_INITIALIZE_WORKGROUP_MEMORY_FEATURES_KHR = 1000325000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_ENUMS_PROPERTIES_NV = 1000326000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_ENUMS_FEATURES_NV = 1000326001,
+    VK_STRUCTURE_TYPE_PIPELINE_FRAGMENT_SHADING_RATE_ENUM_STATE_CREATE_INFO_NV = 1000326002,
+    VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_MOTION_TRIANGLES_DATA_NV = 1000327000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_MOTION_BLUR_FEATURES_NV = 1000327001,
+    VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_MOTION_INFO_NV = 1000327002,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_YCBCR_2_PLANE_444_FORMATS_FEATURES_EXT = 1000330000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_DENSITY_MAP_2_FEATURES_EXT = 1000332000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_DENSITY_MAP_2_PROPERTIES_EXT = 1000332001,
+    VK_STRUCTURE_TYPE_COPY_COMMAND_TRANSFORM_INFO_QCOM = 1000333000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_ROBUSTNESS_FEATURES_EXT = 1000335000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_WORKGROUP_MEMORY_EXPLICIT_LAYOUT_FEATURES_KHR = 1000336000,
+    VK_STRUCTURE_TYPE_COPY_BUFFER_INFO_2_KHR = 1000337000,
+    VK_STRUCTURE_TYPE_COPY_IMAGE_INFO_2_KHR = 1000337001,
+    VK_STRUCTURE_TYPE_COPY_BUFFER_TO_IMAGE_INFO_2_KHR = 1000337002,
+    VK_STRUCTURE_TYPE_COPY_IMAGE_TO_BUFFER_INFO_2_KHR = 1000337003,
+    VK_STRUCTURE_TYPE_BLIT_IMAGE_INFO_2_KHR = 1000337004,
+    VK_STRUCTURE_TYPE_RESOLVE_IMAGE_INFO_2_KHR = 1000337005,
+    VK_STRUCTURE_TYPE_BUFFER_COPY_2_KHR = 1000337006,
+    VK_STRUCTURE_TYPE_IMAGE_COPY_2_KHR = 1000337007,
+    VK_STRUCTURE_TYPE_IMAGE_BLIT_2_KHR = 1000337008,
+    VK_STRUCTURE_TYPE_BUFFER_IMAGE_COPY_2_KHR = 1000337009,
+    VK_STRUCTURE_TYPE_IMAGE_RESOLVE_2_KHR = 1000337010,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_4444_FORMATS_FEATURES_EXT = 1000340000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RGBA10X6_FORMATS_FEATURES_EXT = 1000344000,
+    VK_STRUCTURE_TYPE_DIRECTFB_SURFACE_CREATE_INFO_EXT = 1000346000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MUTABLE_DESCRIPTOR_TYPE_FEATURES_VALVE = 1000351000,
+    VK_STRUCTURE_TYPE_MUTABLE_DESCRIPTOR_TYPE_CREATE_INFO_VALVE = 1000351002,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VERTEX_INPUT_DYNAMIC_STATE_FEATURES_EXT = 1000352000,
+    VK_STRUCTURE_TYPE_VERTEX_INPUT_BINDING_DESCRIPTION_2_EXT = 1000352001,
+    VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT = 1000352002,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRM_PROPERTIES_EXT = 1000353000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRIMITIVE_TOPOLOGY_LIST_RESTART_FEATURES_EXT = 1000356000,
+    VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_3_KHR = 1000360000,
+    VK_STRUCTURE_TYPE_IMPORT_MEMORY_ZIRCON_HANDLE_INFO_FUCHSIA = 1000364000,
+    VK_STRUCTURE_TYPE_MEMORY_ZIRCON_HANDLE_PROPERTIES_FUCHSIA = 1000364001,
+    VK_STRUCTURE_TYPE_MEMORY_GET_ZIRCON_HANDLE_INFO_FUCHSIA = 1000364002,
+    VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_ZIRCON_HANDLE_INFO_FUCHSIA = 1000365000,
+    VK_STRUCTURE_TYPE_SEMAPHORE_GET_ZIRCON_HANDLE_INFO_FUCHSIA = 1000365001,
+    VK_STRUCTURE_TYPE_BUFFER_COLLECTION_CREATE_INFO_FUCHSIA = 1000366000,
+    VK_STRUCTURE_TYPE_IMPORT_MEMORY_BUFFER_COLLECTION_FUCHSIA = 1000366001,
+    VK_STRUCTURE_TYPE_BUFFER_COLLECTION_IMAGE_CREATE_INFO_FUCHSIA = 1000366002,
+    VK_STRUCTURE_TYPE_BUFFER_COLLECTION_PROPERTIES_FUCHSIA = 1000366003,
+    VK_STRUCTURE_TYPE_BUFFER_CONSTRAINTS_INFO_FUCHSIA = 1000366004,
+    VK_STRUCTURE_TYPE_BUFFER_COLLECTION_BUFFER_CREATE_INFO_FUCHSIA = 1000366005,
+    VK_STRUCTURE_TYPE_IMAGE_CONSTRAINTS_INFO_FUCHSIA = 1000366006,
+    VK_STRUCTURE_TYPE_IMAGE_FORMAT_CONSTRAINTS_INFO_FUCHSIA = 1000366007,
+    VK_STRUCTURE_TYPE_SYSMEM_COLOR_SPACE_FUCHSIA = 1000366008,
+    VK_STRUCTURE_TYPE_BUFFER_COLLECTION_CONSTRAINTS_INFO_FUCHSIA = 1000366009,
+    VK_STRUCTURE_TYPE_SUBPASS_SHADING_PIPELINE_CREATE_INFO_HUAWEI = 1000369000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBPASS_SHADING_FEATURES_HUAWEI = 1000369001,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBPASS_SHADING_PROPERTIES_HUAWEI = 1000369002,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INVOCATION_MASK_FEATURES_HUAWEI = 1000370000,
+    VK_STRUCTURE_TYPE_MEMORY_GET_REMOTE_ADDRESS_INFO_NV = 1000371000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_MEMORY_RDMA_FEATURES_NV = 1000371001,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_2_FEATURES_EXT = 1000377000,
+    VK_STRUCTURE_TYPE_SCREEN_SURFACE_CREATE_INFO_QNX = 1000378000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COLOR_WRITE_ENABLE_FEATURES_EXT = 1000381000,
+    VK_STRUCTURE_TYPE_PIPELINE_COLOR_WRITE_CREATE_INFO_EXT = 1000381001,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_GLOBAL_PRIORITY_QUERY_FEATURES_EXT = 1000388000,
+    VK_STRUCTURE_TYPE_QUEUE_FAMILY_GLOBAL_PRIORITY_PROPERTIES_EXT = 1000388001,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTI_DRAW_FEATURES_EXT = 1000392000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTI_DRAW_PROPERTIES_EXT = 1000392001,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BORDER_COLOR_SWIZZLE_FEATURES_EXT = 1000411000,
+    VK_STRUCTURE_TYPE_SAMPLER_BORDER_COLOR_COMPONENT_MAPPING_CREATE_INFO_EXT = 1000411001,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PAGEABLE_DEVICE_LOCAL_MEMORY_FEATURES_EXT = 1000412000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_4_FEATURES_KHR = 1000413000,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_4_PROPERTIES_KHR = 1000413001,
+    VK_STRUCTURE_TYPE_DEVICE_BUFFER_MEMORY_REQUIREMENTS_KHR = 1000413002,
+    VK_STRUCTURE_TYPE_DEVICE_IMAGE_MEMORY_REQUIREMENTS_KHR = 1000413003,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VARIABLE_POINTER_FEATURES = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VARIABLE_POINTERS_FEATURES,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_DRAW_PARAMETER_FEATURES = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_DRAW_PARAMETERS_FEATURES,
+    VK_STRUCTURE_TYPE_DEBUG_REPORT_CREATE_INFO_EXT = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT,
+    VK_STRUCTURE_TYPE_ATTACHMENT_SAMPLE_COUNT_INFO_NV = VK_STRUCTURE_TYPE_ATTACHMENT_SAMPLE_COUNT_INFO_AMD,
+    VK_STRUCTURE_TYPE_RENDER_PASS_MULTIVIEW_CREATE_INFO_KHR = VK_STRUCTURE_TYPE_RENDER_PASS_MULTIVIEW_CREATE_INFO,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES_KHR = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_PROPERTIES_KHR = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_PROPERTIES,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2,
+    VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2_KHR = VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2,
+    VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2_KHR = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2_KHR = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2,
+    VK_STRUCTURE_TYPE_QUEUE_FAMILY_PROPERTIES_2_KHR = VK_STRUCTURE_TYPE_QUEUE_FAMILY_PROPERTIES_2,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PROPERTIES_2_KHR = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PROPERTIES_2,
+    VK_STRUCTURE_TYPE_SPARSE_IMAGE_FORMAT_PROPERTIES_2_KHR = VK_STRUCTURE_TYPE_SPARSE_IMAGE_FORMAT_PROPERTIES_2,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SPARSE_IMAGE_FORMAT_INFO_2_KHR = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SPARSE_IMAGE_FORMAT_INFO_2,
+    VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO_KHR = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO,
+    VK_STRUCTURE_TYPE_DEVICE_GROUP_RENDER_PASS_BEGIN_INFO_KHR = VK_STRUCTURE_TYPE_DEVICE_GROUP_RENDER_PASS_BEGIN_INFO,
+    VK_STRUCTURE_TYPE_DEVICE_GROUP_COMMAND_BUFFER_BEGIN_INFO_KHR = VK_STRUCTURE_TYPE_DEVICE_GROUP_COMMAND_BUFFER_BEGIN_INFO,
+    VK_STRUCTURE_TYPE_DEVICE_GROUP_SUBMIT_INFO_KHR = VK_STRUCTURE_TYPE_DEVICE_GROUP_SUBMIT_INFO,
+    VK_STRUCTURE_TYPE_DEVICE_GROUP_BIND_SPARSE_INFO_KHR = VK_STRUCTURE_TYPE_DEVICE_GROUP_BIND_SPARSE_INFO,
+    VK_STRUCTURE_TYPE_BIND_BUFFER_MEMORY_DEVICE_GROUP_INFO_KHR = VK_STRUCTURE_TYPE_BIND_BUFFER_MEMORY_DEVICE_GROUP_INFO,
+    VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_DEVICE_GROUP_INFO_KHR = VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_DEVICE_GROUP_INFO,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_GROUP_PROPERTIES_KHR = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_GROUP_PROPERTIES,
+    VK_STRUCTURE_TYPE_DEVICE_GROUP_DEVICE_CREATE_INFO_KHR = VK_STRUCTURE_TYPE_DEVICE_GROUP_DEVICE_CREATE_INFO,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_IMAGE_FORMAT_INFO_KHR = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_IMAGE_FORMAT_INFO,
+    VK_STRUCTURE_TYPE_EXTERNAL_IMAGE_FORMAT_PROPERTIES_KHR = VK_STRUCTURE_TYPE_EXTERNAL_IMAGE_FORMAT_PROPERTIES,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_BUFFER_INFO_KHR = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_BUFFER_INFO,
+    VK_STRUCTURE_TYPE_EXTERNAL_BUFFER_PROPERTIES_KHR = VK_STRUCTURE_TYPE_EXTERNAL_BUFFER_PROPERTIES,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ID_PROPERTIES_KHR = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ID_PROPERTIES,
+    VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO_KHR = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO,
+    VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO_KHR = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO,
+    VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO_KHR = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_SEMAPHORE_INFO_KHR = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_SEMAPHORE_INFO,
+    VK_STRUCTURE_TYPE_EXTERNAL_SEMAPHORE_PROPERTIES_KHR = VK_STRUCTURE_TYPE_EXTERNAL_SEMAPHORE_PROPERTIES,
+    VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO_KHR = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_FLOAT16_INT8_FEATURES_KHR = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_FLOAT16_INT8_FEATURES,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FLOAT16_INT8_FEATURES_KHR = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_FLOAT16_INT8_FEATURES,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES_KHR = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES,
+    VK_STRUCTURE_TYPE_DESCRIPTOR_UPDATE_TEMPLATE_CREATE_INFO_KHR = VK_STRUCTURE_TYPE_DESCRIPTOR_UPDATE_TEMPLATE_CREATE_INFO,
+    VK_STRUCTURE_TYPE_SURFACE_CAPABILITIES2_EXT = VK_STRUCTURE_TYPE_SURFACE_CAPABILITIES_2_EXT,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGELESS_FRAMEBUFFER_FEATURES_KHR = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGELESS_FRAMEBUFFER_FEATURES,
+    VK_STRUCTURE_TYPE_FRAMEBUFFER_ATTACHMENTS_CREATE_INFO_KHR = VK_STRUCTURE_TYPE_FRAMEBUFFER_ATTACHMENTS_CREATE_INFO,
+    VK_STRUCTURE_TYPE_FRAMEBUFFER_ATTACHMENT_IMAGE_INFO_KHR = VK_STRUCTURE_TYPE_FRAMEBUFFER_ATTACHMENT_IMAGE_INFO,
+    VK_STRUCTURE_TYPE_RENDER_PASS_ATTACHMENT_BEGIN_INFO_KHR = VK_STRUCTURE_TYPE_RENDER_PASS_ATTACHMENT_BEGIN_INFO,
+    VK_STRUCTURE_TYPE_ATTACHMENT_DESCRIPTION_2_KHR = VK_STRUCTURE_TYPE_ATTACHMENT_DESCRIPTION_2,
+    VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2_KHR = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2,
+    VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_2_KHR = VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_2,
+    VK_STRUCTURE_TYPE_SUBPASS_DEPENDENCY_2_KHR = VK_STRUCTURE_TYPE_SUBPASS_DEPENDENCY_2,
+    VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO_2_KHR = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO_2,
+    VK_STRUCTURE_TYPE_SUBPASS_BEGIN_INFO_KHR = VK_STRUCTURE_TYPE_SUBPASS_BEGIN_INFO,
+    VK_STRUCTURE_TYPE_SUBPASS_END_INFO_KHR = VK_STRUCTURE_TYPE_SUBPASS_END_INFO,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_FENCE_INFO_KHR = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_FENCE_INFO,
+    VK_STRUCTURE_TYPE_EXTERNAL_FENCE_PROPERTIES_KHR = VK_STRUCTURE_TYPE_EXTERNAL_FENCE_PROPERTIES,
+    VK_STRUCTURE_TYPE_EXPORT_FENCE_CREATE_INFO_KHR = VK_STRUCTURE_TYPE_EXPORT_FENCE_CREATE_INFO,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_POINT_CLIPPING_PROPERTIES_KHR = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_POINT_CLIPPING_PROPERTIES,
+    VK_STRUCTURE_TYPE_RENDER_PASS_INPUT_ATTACHMENT_ASPECT_CREATE_INFO_KHR = VK_STRUCTURE_TYPE_RENDER_PASS_INPUT_ATTACHMENT_ASPECT_CREATE_INFO,
+    VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO_KHR = VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO,
+    VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_DOMAIN_ORIGIN_STATE_CREATE_INFO_KHR = VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_DOMAIN_ORIGIN_STATE_CREATE_INFO,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VARIABLE_POINTERS_FEATURES_KHR = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VARIABLE_POINTERS_FEATURES,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VARIABLE_POINTER_FEATURES_KHR = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VARIABLE_POINTERS_FEATURES_KHR,
+    VK_STRUCTURE_TYPE_MEMORY_DEDICATED_REQUIREMENTS_KHR = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_REQUIREMENTS,
+    VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO_KHR = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_FILTER_MINMAX_PROPERTIES_EXT = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_FILTER_MINMAX_PROPERTIES,
+    VK_STRUCTURE_TYPE_SAMPLER_REDUCTION_MODE_CREATE_INFO_EXT = VK_STRUCTURE_TYPE_SAMPLER_REDUCTION_MODE_CREATE_INFO,
+    VK_STRUCTURE_TYPE_BUFFER_MEMORY_REQUIREMENTS_INFO_2_KHR = VK_STRUCTURE_TYPE_BUFFER_MEMORY_REQUIREMENTS_INFO_2,
+    VK_STRUCTURE_TYPE_IMAGE_MEMORY_REQUIREMENTS_INFO_2_KHR = VK_STRUCTURE_TYPE_IMAGE_MEMORY_REQUIREMENTS_INFO_2,
+    VK_STRUCTURE_TYPE_IMAGE_SPARSE_MEMORY_REQUIREMENTS_INFO_2_KHR = VK_STRUCTURE_TYPE_IMAGE_SPARSE_MEMORY_REQUIREMENTS_INFO_2,
+    VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2_KHR = VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2,
+    VK_STRUCTURE_TYPE_SPARSE_IMAGE_MEMORY_REQUIREMENTS_2_KHR = VK_STRUCTURE_TYPE_SPARSE_IMAGE_MEMORY_REQUIREMENTS_2,
+    VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO_KHR = VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO,
+    VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_CREATE_INFO_KHR = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_CREATE_INFO,
+    VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO_KHR = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO,
+    VK_STRUCTURE_TYPE_BIND_IMAGE_PLANE_MEMORY_INFO_KHR = VK_STRUCTURE_TYPE_BIND_IMAGE_PLANE_MEMORY_INFO,
+    VK_STRUCTURE_TYPE_IMAGE_PLANE_MEMORY_REQUIREMENTS_INFO_KHR = VK_STRUCTURE_TYPE_IMAGE_PLANE_MEMORY_REQUIREMENTS_INFO,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES_KHR = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES,
+    VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_IMAGE_FORMAT_PROPERTIES_KHR = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_IMAGE_FORMAT_PROPERTIES,
+    VK_STRUCTURE_TYPE_BIND_BUFFER_MEMORY_INFO_KHR = VK_STRUCTURE_TYPE_BIND_BUFFER_MEMORY_INFO,
+    VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_INFO_KHR = VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_INFO,
+    VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO_EXT = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES_EXT = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_PROPERTIES_EXT = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_PROPERTIES,
+    VK_STRUCTURE_TYPE_DESCRIPTOR_SET_VARIABLE_DESCRIPTOR_COUNT_ALLOCATE_INFO_EXT = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_VARIABLE_DESCRIPTOR_COUNT_ALLOCATE_INFO,
+    VK_STRUCTURE_TYPE_DESCRIPTOR_SET_VARIABLE_DESCRIPTOR_COUNT_LAYOUT_SUPPORT_EXT = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_VARIABLE_DESCRIPTOR_COUNT_LAYOUT_SUPPORT,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_3_PROPERTIES_KHR = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_3_PROPERTIES,
+    VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_SUPPORT_KHR = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_SUPPORT,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_SUBGROUP_EXTENDED_TYPES_FEATURES_KHR = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_SUBGROUP_EXTENDED_TYPES_FEATURES,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_8BIT_STORAGE_FEATURES_KHR = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_8BIT_STORAGE_FEATURES,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_ATOMIC_INT64_FEATURES_KHR = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_ATOMIC_INT64_FEATURES,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES_KHR = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FLOAT_CONTROLS_PROPERTIES_KHR = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FLOAT_CONTROLS_PROPERTIES,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_STENCIL_RESOLVE_PROPERTIES_KHR = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_STENCIL_RESOLVE_PROPERTIES,
+    VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_DEPTH_STENCIL_RESOLVE_KHR = VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_DEPTH_STENCIL_RESOLVE,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES_KHR = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_PROPERTIES_KHR = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_PROPERTIES,
+    VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO_KHR = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO,
+    VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO_KHR = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO,
+    VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO_KHR = VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO,
+    VK_STRUCTURE_TYPE_SEMAPHORE_SIGNAL_INFO_KHR = VK_STRUCTURE_TYPE_SEMAPHORE_SIGNAL_INFO,
+    VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO_INTEL = VK_STRUCTURE_TYPE_QUERY_POOL_PERFORMANCE_QUERY_CREATE_INFO_INTEL,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_MEMORY_MODEL_FEATURES_KHR = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_MEMORY_MODEL_FEATURES,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SCALAR_BLOCK_LAYOUT_FEATURES_EXT = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SCALAR_BLOCK_LAYOUT_FEATURES,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SEPARATE_DEPTH_STENCIL_LAYOUTS_FEATURES_KHR = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SEPARATE_DEPTH_STENCIL_LAYOUTS_FEATURES,
+    VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_STENCIL_LAYOUT_KHR = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_STENCIL_LAYOUT,
+    VK_STRUCTURE_TYPE_ATTACHMENT_DESCRIPTION_STENCIL_LAYOUT_KHR = VK_STRUCTURE_TYPE_ATTACHMENT_DESCRIPTION_STENCIL_LAYOUT,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_ADDRESS_FEATURES_EXT = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES_EXT,
+    VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO_EXT = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO,
+    VK_STRUCTURE_TYPE_IMAGE_STENCIL_USAGE_CREATE_INFO_EXT = VK_STRUCTURE_TYPE_IMAGE_STENCIL_USAGE_CREATE_INFO,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_UNIFORM_BUFFER_STANDARD_LAYOUT_FEATURES_KHR = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_UNIFORM_BUFFER_STANDARD_LAYOUT_FEATURES,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES_KHR = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES,
+    VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO_KHR = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO,
+    VK_STRUCTURE_TYPE_BUFFER_OPAQUE_CAPTURE_ADDRESS_CREATE_INFO_KHR = VK_STRUCTURE_TYPE_BUFFER_OPAQUE_CAPTURE_ADDRESS_CREATE_INFO,
+    VK_STRUCTURE_TYPE_MEMORY_OPAQUE_CAPTURE_ADDRESS_ALLOCATE_INFO_KHR = VK_STRUCTURE_TYPE_MEMORY_OPAQUE_CAPTURE_ADDRESS_ALLOCATE_INFO,
+    VK_STRUCTURE_TYPE_DEVICE_MEMORY_OPAQUE_CAPTURE_ADDRESS_INFO_KHR = VK_STRUCTURE_TYPE_DEVICE_MEMORY_OPAQUE_CAPTURE_ADDRESS_INFO,
+    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_HOST_QUERY_RESET_FEATURES_EXT = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_HOST_QUERY_RESET_FEATURES,
+    VK_STRUCTURE_TYPE_MAX_ENUM = 0x7FFFFFFF
+} VkStructureType;
+
+typedef enum VkImageLayout {
+    VK_IMAGE_LAYOUT_UNDEFINED = 0,
+    VK_IMAGE_LAYOUT_GENERAL = 1,
+    VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL = 2,
+    VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL = 3,
+    VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL = 4,
+    VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL = 5,
+    VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL = 6,
+    VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL = 7,
+    VK_IMAGE_LAYOUT_PREINITIALIZED = 8,
+    VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL = 1000117000,
+    VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_OPTIMAL = 1000117001,
+    VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL = 1000241000,
+    VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_OPTIMAL = 1000241001,
+    VK_IMAGE_LAYOUT_STENCIL_ATTACHMENT_OPTIMAL = 1000241002,
+    VK_IMAGE_LAYOUT_STENCIL_READ_ONLY_OPTIMAL = 1000241003,
+    VK_IMAGE_LAYOUT_PRESENT_SRC_KHR = 1000001002,
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_IMAGE_LAYOUT_VIDEO_DECODE_DST_KHR = 1000024000,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_IMAGE_LAYOUT_VIDEO_DECODE_SRC_KHR = 1000024001,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_IMAGE_LAYOUT_VIDEO_DECODE_DPB_KHR = 1000024002,
+#endif
+    VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR = 1000111000,
+    VK_IMAGE_LAYOUT_FRAGMENT_DENSITY_MAP_OPTIMAL_EXT = 1000218000,
+    VK_IMAGE_LAYOUT_FRAGMENT_SHADING_RATE_ATTACHMENT_OPTIMAL_KHR = 1000164003,
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_IMAGE_LAYOUT_VIDEO_ENCODE_DST_KHR = 1000299000,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_IMAGE_LAYOUT_VIDEO_ENCODE_SRC_KHR = 1000299001,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_IMAGE_LAYOUT_VIDEO_ENCODE_DPB_KHR = 1000299002,
+#endif
+    VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL_KHR = 1000314000,
+    VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL_KHR = 1000314001,
+    VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL_KHR = VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL,
+    VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_OPTIMAL_KHR = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_OPTIMAL,
+    VK_IMAGE_LAYOUT_SHADING_RATE_OPTIMAL_NV = VK_IMAGE_LAYOUT_FRAGMENT_SHADING_RATE_ATTACHMENT_OPTIMAL_KHR,
+    VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL_KHR = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL,
+    VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_OPTIMAL_KHR = VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_OPTIMAL,
+    VK_IMAGE_LAYOUT_STENCIL_ATTACHMENT_OPTIMAL_KHR = VK_IMAGE_LAYOUT_STENCIL_ATTACHMENT_OPTIMAL,
+    VK_IMAGE_LAYOUT_STENCIL_READ_ONLY_OPTIMAL_KHR = VK_IMAGE_LAYOUT_STENCIL_READ_ONLY_OPTIMAL,
+    VK_IMAGE_LAYOUT_MAX_ENUM = 0x7FFFFFFF
+} VkImageLayout;
+
+typedef enum VkObjectType {
+    VK_OBJECT_TYPE_UNKNOWN = 0,
+    VK_OBJECT_TYPE_INSTANCE = 1,
+    VK_OBJECT_TYPE_PHYSICAL_DEVICE = 2,
+    VK_OBJECT_TYPE_DEVICE = 3,
+    VK_OBJECT_TYPE_QUEUE = 4,
+    VK_OBJECT_TYPE_SEMAPHORE = 5,
+    VK_OBJECT_TYPE_COMMAND_BUFFER = 6,
+    VK_OBJECT_TYPE_FENCE = 7,
+    VK_OBJECT_TYPE_DEVICE_MEMORY = 8,
+    VK_OBJECT_TYPE_BUFFER = 9,
+    VK_OBJECT_TYPE_IMAGE = 10,
+    VK_OBJECT_TYPE_EVENT = 11,
+    VK_OBJECT_TYPE_QUERY_POOL = 12,
+    VK_OBJECT_TYPE_BUFFER_VIEW = 13,
+    VK_OBJECT_TYPE_IMAGE_VIEW = 14,
+    VK_OBJECT_TYPE_SHADER_MODULE = 15,
+    VK_OBJECT_TYPE_PIPELINE_CACHE = 16,
+    VK_OBJECT_TYPE_PIPELINE_LAYOUT = 17,
+    VK_OBJECT_TYPE_RENDER_PASS = 18,
+    VK_OBJECT_TYPE_PIPELINE = 19,
+    VK_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT = 20,
+    VK_OBJECT_TYPE_SAMPLER = 21,
+    VK_OBJECT_TYPE_DESCRIPTOR_POOL = 22,
+    VK_OBJECT_TYPE_DESCRIPTOR_SET = 23,
+    VK_OBJECT_TYPE_FRAMEBUFFER = 24,
+    VK_OBJECT_TYPE_COMMAND_POOL = 25,
+    VK_OBJECT_TYPE_SAMPLER_YCBCR_CONVERSION = 1000156000,
+    VK_OBJECT_TYPE_DESCRIPTOR_UPDATE_TEMPLATE = 1000085000,
+    VK_OBJECT_TYPE_SURFACE_KHR = 1000000000,
+    VK_OBJECT_TYPE_SWAPCHAIN_KHR = 1000001000,
+    VK_OBJECT_TYPE_DISPLAY_KHR = 1000002000,
+    VK_OBJECT_TYPE_DISPLAY_MODE_KHR = 1000002001,
+    VK_OBJECT_TYPE_DEBUG_REPORT_CALLBACK_EXT = 1000011000,
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_OBJECT_TYPE_VIDEO_SESSION_KHR = 1000023000,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_OBJECT_TYPE_VIDEO_SESSION_PARAMETERS_KHR = 1000023001,
+#endif
+    VK_OBJECT_TYPE_CU_MODULE_NVX = 1000029000,
+    VK_OBJECT_TYPE_CU_FUNCTION_NVX = 1000029001,
+    VK_OBJECT_TYPE_DEBUG_UTILS_MESSENGER_EXT = 1000128000,
+    VK_OBJECT_TYPE_ACCELERATION_STRUCTURE_KHR = 1000150000,
+    VK_OBJECT_TYPE_VALIDATION_CACHE_EXT = 1000160000,
+    VK_OBJECT_TYPE_ACCELERATION_STRUCTURE_NV = 1000165000,
+    VK_OBJECT_TYPE_PERFORMANCE_CONFIGURATION_INTEL = 1000210000,
+    VK_OBJECT_TYPE_DEFERRED_OPERATION_KHR = 1000268000,
+    VK_OBJECT_TYPE_INDIRECT_COMMANDS_LAYOUT_NV = 1000277000,
+    VK_OBJECT_TYPE_PRIVATE_DATA_SLOT_EXT = 1000295000,
+    VK_OBJECT_TYPE_BUFFER_COLLECTION_FUCHSIA = 1000366000,
+    VK_OBJECT_TYPE_DESCRIPTOR_UPDATE_TEMPLATE_KHR = VK_OBJECT_TYPE_DESCRIPTOR_UPDATE_TEMPLATE,
+    VK_OBJECT_TYPE_SAMPLER_YCBCR_CONVERSION_KHR = VK_OBJECT_TYPE_SAMPLER_YCBCR_CONVERSION,
+    VK_OBJECT_TYPE_MAX_ENUM = 0x7FFFFFFF
+} VkObjectType;
+
+typedef enum VkPipelineCacheHeaderVersion {
+    VK_PIPELINE_CACHE_HEADER_VERSION_ONE = 1,
+    VK_PIPELINE_CACHE_HEADER_VERSION_MAX_ENUM = 0x7FFFFFFF
+} VkPipelineCacheHeaderVersion;
+
+typedef enum VkVendorId {
+    VK_VENDOR_ID_VIV = 0x10001,
+    VK_VENDOR_ID_VSI = 0x10002,
+    VK_VENDOR_ID_KAZAN = 0x10003,
+    VK_VENDOR_ID_CODEPLAY = 0x10004,
+    VK_VENDOR_ID_MESA = 0x10005,
+    VK_VENDOR_ID_POCL = 0x10006,
+    VK_VENDOR_ID_MAX_ENUM = 0x7FFFFFFF
+} VkVendorId;
+
+typedef enum VkSystemAllocationScope {
+    VK_SYSTEM_ALLOCATION_SCOPE_COMMAND = 0,
+    VK_SYSTEM_ALLOCATION_SCOPE_OBJECT = 1,
+    VK_SYSTEM_ALLOCATION_SCOPE_CACHE = 2,
+    VK_SYSTEM_ALLOCATION_SCOPE_DEVICE = 3,
+    VK_SYSTEM_ALLOCATION_SCOPE_INSTANCE = 4,
+    VK_SYSTEM_ALLOCATION_SCOPE_MAX_ENUM = 0x7FFFFFFF
+} VkSystemAllocationScope;
+
+typedef enum VkInternalAllocationType {
+    VK_INTERNAL_ALLOCATION_TYPE_EXECUTABLE = 0,
+    VK_INTERNAL_ALLOCATION_TYPE_MAX_ENUM = 0x7FFFFFFF
+} VkInternalAllocationType;
+
+typedef enum VkFormat {
+    VK_FORMAT_UNDEFINED = 0,
+    VK_FORMAT_R4G4_UNORM_PACK8 = 1,
+    VK_FORMAT_R4G4B4A4_UNORM_PACK16 = 2,
+    VK_FORMAT_B4G4R4A4_UNORM_PACK16 = 3,
+    VK_FORMAT_R5G6B5_UNORM_PACK16 = 4,
+    VK_FORMAT_B5G6R5_UNORM_PACK16 = 5,
+    VK_FORMAT_R5G5B5A1_UNORM_PACK16 = 6,
+    VK_FORMAT_B5G5R5A1_UNORM_PACK16 = 7,
+    VK_FORMAT_A1R5G5B5_UNORM_PACK16 = 8,
+    VK_FORMAT_R8_UNORM = 9,
+    VK_FORMAT_R8_SNORM = 10,
+    VK_FORMAT_R8_USCALED = 11,
+    VK_FORMAT_R8_SSCALED = 12,
+    VK_FORMAT_R8_UINT = 13,
+    VK_FORMAT_R8_SINT = 14,
+    VK_FORMAT_R8_SRGB = 15,
+    VK_FORMAT_R8G8_UNORM = 16,
+    VK_FORMAT_R8G8_SNORM = 17,
+    VK_FORMAT_R8G8_USCALED = 18,
+    VK_FORMAT_R8G8_SSCALED = 19,
+    VK_FORMAT_R8G8_UINT = 20,
+    VK_FORMAT_R8G8_SINT = 21,
+    VK_FORMAT_R8G8_SRGB = 22,
+    VK_FORMAT_R8G8B8_UNORM = 23,
+    VK_FORMAT_R8G8B8_SNORM = 24,
+    VK_FORMAT_R8G8B8_USCALED = 25,
+    VK_FORMAT_R8G8B8_SSCALED = 26,
+    VK_FORMAT_R8G8B8_UINT = 27,
+    VK_FORMAT_R8G8B8_SINT = 28,
+    VK_FORMAT_R8G8B8_SRGB = 29,
+    VK_FORMAT_B8G8R8_UNORM = 30,
+    VK_FORMAT_B8G8R8_SNORM = 31,
+    VK_FORMAT_B8G8R8_USCALED = 32,
+    VK_FORMAT_B8G8R8_SSCALED = 33,
+    VK_FORMAT_B8G8R8_UINT = 34,
+    VK_FORMAT_B8G8R8_SINT = 35,
+    VK_FORMAT_B8G8R8_SRGB = 36,
+    VK_FORMAT_R8G8B8A8_UNORM = 37,
+    VK_FORMAT_R8G8B8A8_SNORM = 38,
+    VK_FORMAT_R8G8B8A8_USCALED = 39,
+    VK_FORMAT_R8G8B8A8_SSCALED = 40,
+    VK_FORMAT_R8G8B8A8_UINT = 41,
+    VK_FORMAT_R8G8B8A8_SINT = 42,
+    VK_FORMAT_R8G8B8A8_SRGB = 43,
+    VK_FORMAT_B8G8R8A8_UNORM = 44,
+    VK_FORMAT_B8G8R8A8_SNORM = 45,
+    VK_FORMAT_B8G8R8A8_USCALED = 46,
+    VK_FORMAT_B8G8R8A8_SSCALED = 47,
+    VK_FORMAT_B8G8R8A8_UINT = 48,
+    VK_FORMAT_B8G8R8A8_SINT = 49,
+    VK_FORMAT_B8G8R8A8_SRGB = 50,
+    VK_FORMAT_A8B8G8R8_UNORM_PACK32 = 51,
+    VK_FORMAT_A8B8G8R8_SNORM_PACK32 = 52,
+    VK_FORMAT_A8B8G8R8_USCALED_PACK32 = 53,
+    VK_FORMAT_A8B8G8R8_SSCALED_PACK32 = 54,
+    VK_FORMAT_A8B8G8R8_UINT_PACK32 = 55,
+    VK_FORMAT_A8B8G8R8_SINT_PACK32 = 56,
+    VK_FORMAT_A8B8G8R8_SRGB_PACK32 = 57,
+    VK_FORMAT_A2R10G10B10_UNORM_PACK32 = 58,
+    VK_FORMAT_A2R10G10B10_SNORM_PACK32 = 59,
+    VK_FORMAT_A2R10G10B10_USCALED_PACK32 = 60,
+    VK_FORMAT_A2R10G10B10_SSCALED_PACK32 = 61,
+    VK_FORMAT_A2R10G10B10_UINT_PACK32 = 62,
+    VK_FORMAT_A2R10G10B10_SINT_PACK32 = 63,
+    VK_FORMAT_A2B10G10R10_UNORM_PACK32 = 64,
+    VK_FORMAT_A2B10G10R10_SNORM_PACK32 = 65,
+    VK_FORMAT_A2B10G10R10_USCALED_PACK32 = 66,
+    VK_FORMAT_A2B10G10R10_SSCALED_PACK32 = 67,
+    VK_FORMAT_A2B10G10R10_UINT_PACK32 = 68,
+    VK_FORMAT_A2B10G10R10_SINT_PACK32 = 69,
+    VK_FORMAT_R16_UNORM = 70,
+    VK_FORMAT_R16_SNORM = 71,
+    VK_FORMAT_R16_USCALED = 72,
+    VK_FORMAT_R16_SSCALED = 73,
+    VK_FORMAT_R16_UINT = 74,
+    VK_FORMAT_R16_SINT = 75,
+    VK_FORMAT_R16_SFLOAT = 76,
+    VK_FORMAT_R16G16_UNORM = 77,
+    VK_FORMAT_R16G16_SNORM = 78,
+    VK_FORMAT_R16G16_USCALED = 79,
+    VK_FORMAT_R16G16_SSCALED = 80,
+    VK_FORMAT_R16G16_UINT = 81,
+    VK_FORMAT_R16G16_SINT = 82,
+    VK_FORMAT_R16G16_SFLOAT = 83,
+    VK_FORMAT_R16G16B16_UNORM = 84,
+    VK_FORMAT_R16G16B16_SNORM = 85,
+    VK_FORMAT_R16G16B16_USCALED = 86,
+    VK_FORMAT_R16G16B16_SSCALED = 87,
+    VK_FORMAT_R16G16B16_UINT = 88,
+    VK_FORMAT_R16G16B16_SINT = 89,
+    VK_FORMAT_R16G16B16_SFLOAT = 90,
+    VK_FORMAT_R16G16B16A16_UNORM = 91,
+    VK_FORMAT_R16G16B16A16_SNORM = 92,
+    VK_FORMAT_R16G16B16A16_USCALED = 93,
+    VK_FORMAT_R16G16B16A16_SSCALED = 94,
+    VK_FORMAT_R16G16B16A16_UINT = 95,
+    VK_FORMAT_R16G16B16A16_SINT = 96,
+    VK_FORMAT_R16G16B16A16_SFLOAT = 97,
+    VK_FORMAT_R32_UINT = 98,
+    VK_FORMAT_R32_SINT = 99,
+    VK_FORMAT_R32_SFLOAT = 100,
+    VK_FORMAT_R32G32_UINT = 101,
+    VK_FORMAT_R32G32_SINT = 102,
+    VK_FORMAT_R32G32_SFLOAT = 103,
+    VK_FORMAT_R32G32B32_UINT = 104,
+    VK_FORMAT_R32G32B32_SINT = 105,
+    VK_FORMAT_R32G32B32_SFLOAT = 106,
+    VK_FORMAT_R32G32B32A32_UINT = 107,
+    VK_FORMAT_R32G32B32A32_SINT = 108,
+    VK_FORMAT_R32G32B32A32_SFLOAT = 109,
+    VK_FORMAT_R64_UINT = 110,
+    VK_FORMAT_R64_SINT = 111,
+    VK_FORMAT_R64_SFLOAT = 112,
+    VK_FORMAT_R64G64_UINT = 113,
+    VK_FORMAT_R64G64_SINT = 114,
+    VK_FORMAT_R64G64_SFLOAT = 115,
+    VK_FORMAT_R64G64B64_UINT = 116,
+    VK_FORMAT_R64G64B64_SINT = 117,
+    VK_FORMAT_R64G64B64_SFLOAT = 118,
+    VK_FORMAT_R64G64B64A64_UINT = 119,
+    VK_FORMAT_R64G64B64A64_SINT = 120,
+    VK_FORMAT_R64G64B64A64_SFLOAT = 121,
+    VK_FORMAT_B10G11R11_UFLOAT_PACK32 = 122,
+    VK_FORMAT_E5B9G9R9_UFLOAT_PACK32 = 123,
+    VK_FORMAT_D16_UNORM = 124,
+    VK_FORMAT_X8_D24_UNORM_PACK32 = 125,
+    VK_FORMAT_D32_SFLOAT = 126,
+    VK_FORMAT_S8_UINT = 127,
+    VK_FORMAT_D16_UNORM_S8_UINT = 128,
+    VK_FORMAT_D24_UNORM_S8_UINT = 129,
+    VK_FORMAT_D32_SFLOAT_S8_UINT = 130,
+    VK_FORMAT_BC1_RGB_UNORM_BLOCK = 131,
+    VK_FORMAT_BC1_RGB_SRGB_BLOCK = 132,
+    VK_FORMAT_BC1_RGBA_UNORM_BLOCK = 133,
+    VK_FORMAT_BC1_RGBA_SRGB_BLOCK = 134,
+    VK_FORMAT_BC2_UNORM_BLOCK = 135,
+    VK_FORMAT_BC2_SRGB_BLOCK = 136,
+    VK_FORMAT_BC3_UNORM_BLOCK = 137,
+    VK_FORMAT_BC3_SRGB_BLOCK = 138,
+    VK_FORMAT_BC4_UNORM_BLOCK = 139,
+    VK_FORMAT_BC4_SNORM_BLOCK = 140,
+    VK_FORMAT_BC5_UNORM_BLOCK = 141,
+    VK_FORMAT_BC5_SNORM_BLOCK = 142,
+    VK_FORMAT_BC6H_UFLOAT_BLOCK = 143,
+    VK_FORMAT_BC6H_SFLOAT_BLOCK = 144,
+    VK_FORMAT_BC7_UNORM_BLOCK = 145,
+    VK_FORMAT_BC7_SRGB_BLOCK = 146,
+    VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK = 147,
+    VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK = 148,
+    VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK = 149,
+    VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK = 150,
+    VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK = 151,
+    VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK = 152,
+    VK_FORMAT_EAC_R11_UNORM_BLOCK = 153,
+    VK_FORMAT_EAC_R11_SNORM_BLOCK = 154,
+    VK_FORMAT_EAC_R11G11_UNORM_BLOCK = 155,
+    VK_FORMAT_EAC_R11G11_SNORM_BLOCK = 156,
+    VK_FORMAT_ASTC_4x4_UNORM_BLOCK = 157,
+    VK_FORMAT_ASTC_4x4_SRGB_BLOCK = 158,
+    VK_FORMAT_ASTC_5x4_UNORM_BLOCK = 159,
+    VK_FORMAT_ASTC_5x4_SRGB_BLOCK = 160,
+    VK_FORMAT_ASTC_5x5_UNORM_BLOCK = 161,
+    VK_FORMAT_ASTC_5x5_SRGB_BLOCK = 162,
+    VK_FORMAT_ASTC_6x5_UNORM_BLOCK = 163,
+    VK_FORMAT_ASTC_6x5_SRGB_BLOCK = 164,
+    VK_FORMAT_ASTC_6x6_UNORM_BLOCK = 165,
+    VK_FORMAT_ASTC_6x6_SRGB_BLOCK = 166,
+    VK_FORMAT_ASTC_8x5_UNORM_BLOCK = 167,
+    VK_FORMAT_ASTC_8x5_SRGB_BLOCK = 168,
+    VK_FORMAT_ASTC_8x6_UNORM_BLOCK = 169,
+    VK_FORMAT_ASTC_8x6_SRGB_BLOCK = 170,
+    VK_FORMAT_ASTC_8x8_UNORM_BLOCK = 171,
+    VK_FORMAT_ASTC_8x8_SRGB_BLOCK = 172,
+    VK_FORMAT_ASTC_10x5_UNORM_BLOCK = 173,
+    VK_FORMAT_ASTC_10x5_SRGB_BLOCK = 174,
+    VK_FORMAT_ASTC_10x6_UNORM_BLOCK = 175,
+    VK_FORMAT_ASTC_10x6_SRGB_BLOCK = 176,
+    VK_FORMAT_ASTC_10x8_UNORM_BLOCK = 177,
+    VK_FORMAT_ASTC_10x8_SRGB_BLOCK = 178,
+    VK_FORMAT_ASTC_10x10_UNORM_BLOCK = 179,
+    VK_FORMAT_ASTC_10x10_SRGB_BLOCK = 180,
+    VK_FORMAT_ASTC_12x10_UNORM_BLOCK = 181,
+    VK_FORMAT_ASTC_12x10_SRGB_BLOCK = 182,
+    VK_FORMAT_ASTC_12x12_UNORM_BLOCK = 183,
+    VK_FORMAT_ASTC_12x12_SRGB_BLOCK = 184,
+    VK_FORMAT_G8B8G8R8_422_UNORM = 1000156000,
+    VK_FORMAT_B8G8R8G8_422_UNORM = 1000156001,
+    VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM = 1000156002,
+    VK_FORMAT_G8_B8R8_2PLANE_420_UNORM = 1000156003,
+    VK_FORMAT_G8_B8_R8_3PLANE_422_UNORM = 1000156004,
+    VK_FORMAT_G8_B8R8_2PLANE_422_UNORM = 1000156005,
+    VK_FORMAT_G8_B8_R8_3PLANE_444_UNORM = 1000156006,
+    VK_FORMAT_R10X6_UNORM_PACK16 = 1000156007,
+    VK_FORMAT_R10X6G10X6_UNORM_2PACK16 = 1000156008,
+    VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16 = 1000156009,
+    VK_FORMAT_G10X6B10X6G10X6R10X6_422_UNORM_4PACK16 = 1000156010,
+    VK_FORMAT_B10X6G10X6R10X6G10X6_422_UNORM_4PACK16 = 1000156011,
+    VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16 = 1000156012,
+    VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16 = 1000156013,
+    VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_422_UNORM_3PACK16 = 1000156014,
+    VK_FORMAT_G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16 = 1000156015,
+    VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16 = 1000156016,
+    VK_FORMAT_R12X4_UNORM_PACK16 = 1000156017,
+    VK_FORMAT_R12X4G12X4_UNORM_2PACK16 = 1000156018,
+    VK_FORMAT_R12X4G12X4B12X4A12X4_UNORM_4PACK16 = 1000156019,
+    VK_FORMAT_G12X4B12X4G12X4R12X4_422_UNORM_4PACK16 = 1000156020,
+    VK_FORMAT_B12X4G12X4R12X4G12X4_422_UNORM_4PACK16 = 1000156021,
+    VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_420_UNORM_3PACK16 = 1000156022,
+    VK_FORMAT_G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16 = 1000156023,
+    VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_422_UNORM_3PACK16 = 1000156024,
+    VK_FORMAT_G12X4_B12X4R12X4_2PLANE_422_UNORM_3PACK16 = 1000156025,
+    VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_444_UNORM_3PACK16 = 1000156026,
+    VK_FORMAT_G16B16G16R16_422_UNORM = 1000156027,
+    VK_FORMAT_B16G16R16G16_422_UNORM = 1000156028,
+    VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM = 1000156029,
+    VK_FORMAT_G16_B16R16_2PLANE_420_UNORM = 1000156030,
+    VK_FORMAT_G16_B16_R16_3PLANE_422_UNORM = 1000156031,
+    VK_FORMAT_G16_B16R16_2PLANE_422_UNORM = 1000156032,
+    VK_FORMAT_G16_B16_R16_3PLANE_444_UNORM = 1000156033,
+    VK_FORMAT_PVRTC1_2BPP_UNORM_BLOCK_IMG = 1000054000,
+    VK_FORMAT_PVRTC1_4BPP_UNORM_BLOCK_IMG = 1000054001,
+    VK_FORMAT_PVRTC2_2BPP_UNORM_BLOCK_IMG = 1000054002,
+    VK_FORMAT_PVRTC2_4BPP_UNORM_BLOCK_IMG = 1000054003,
+    VK_FORMAT_PVRTC1_2BPP_SRGB_BLOCK_IMG = 1000054004,
+    VK_FORMAT_PVRTC1_4BPP_SRGB_BLOCK_IMG = 1000054005,
+    VK_FORMAT_PVRTC2_2BPP_SRGB_BLOCK_IMG = 1000054006,
+    VK_FORMAT_PVRTC2_4BPP_SRGB_BLOCK_IMG = 1000054007,
+    VK_FORMAT_ASTC_4x4_SFLOAT_BLOCK_EXT = 1000066000,
+    VK_FORMAT_ASTC_5x4_SFLOAT_BLOCK_EXT = 1000066001,
+    VK_FORMAT_ASTC_5x5_SFLOAT_BLOCK_EXT = 1000066002,
+    VK_FORMAT_ASTC_6x5_SFLOAT_BLOCK_EXT = 1000066003,
+    VK_FORMAT_ASTC_6x6_SFLOAT_BLOCK_EXT = 1000066004,
+    VK_FORMAT_ASTC_8x5_SFLOAT_BLOCK_EXT = 1000066005,
+    VK_FORMAT_ASTC_8x6_SFLOAT_BLOCK_EXT = 1000066006,
+    VK_FORMAT_ASTC_8x8_SFLOAT_BLOCK_EXT = 1000066007,
+    VK_FORMAT_ASTC_10x5_SFLOAT_BLOCK_EXT = 1000066008,
+    VK_FORMAT_ASTC_10x6_SFLOAT_BLOCK_EXT = 1000066009,
+    VK_FORMAT_ASTC_10x8_SFLOAT_BLOCK_EXT = 1000066010,
+    VK_FORMAT_ASTC_10x10_SFLOAT_BLOCK_EXT = 1000066011,
+    VK_FORMAT_ASTC_12x10_SFLOAT_BLOCK_EXT = 1000066012,
+    VK_FORMAT_ASTC_12x12_SFLOAT_BLOCK_EXT = 1000066013,
+    VK_FORMAT_G8_B8R8_2PLANE_444_UNORM_EXT = 1000330000,
+    VK_FORMAT_G10X6_B10X6R10X6_2PLANE_444_UNORM_3PACK16_EXT = 1000330001,
+    VK_FORMAT_G12X4_B12X4R12X4_2PLANE_444_UNORM_3PACK16_EXT = 1000330002,
+    VK_FORMAT_G16_B16R16_2PLANE_444_UNORM_EXT = 1000330003,
+    VK_FORMAT_A4R4G4B4_UNORM_PACK16_EXT = 1000340000,
+    VK_FORMAT_A4B4G4R4_UNORM_PACK16_EXT = 1000340001,
+    VK_FORMAT_G8B8G8R8_422_UNORM_KHR = VK_FORMAT_G8B8G8R8_422_UNORM,
+    VK_FORMAT_B8G8R8G8_422_UNORM_KHR = VK_FORMAT_B8G8R8G8_422_UNORM,
+    VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM_KHR = VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM,
+    VK_FORMAT_G8_B8R8_2PLANE_420_UNORM_KHR = VK_FORMAT_G8_B8R8_2PLANE_420_UNORM,
+    VK_FORMAT_G8_B8_R8_3PLANE_422_UNORM_KHR = VK_FORMAT_G8_B8_R8_3PLANE_422_UNORM,
+    VK_FORMAT_G8_B8R8_2PLANE_422_UNORM_KHR = VK_FORMAT_G8_B8R8_2PLANE_422_UNORM,
+    VK_FORMAT_G8_B8_R8_3PLANE_444_UNORM_KHR = VK_FORMAT_G8_B8_R8_3PLANE_444_UNORM,
+    VK_FORMAT_R10X6_UNORM_PACK16_KHR = VK_FORMAT_R10X6_UNORM_PACK16,
+    VK_FORMAT_R10X6G10X6_UNORM_2PACK16_KHR = VK_FORMAT_R10X6G10X6_UNORM_2PACK16,
+    VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16_KHR = VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16,
+    VK_FORMAT_G10X6B10X6G10X6R10X6_422_UNORM_4PACK16_KHR = VK_FORMAT_G10X6B10X6G10X6R10X6_422_UNORM_4PACK16,
+    VK_FORMAT_B10X6G10X6R10X6G10X6_422_UNORM_4PACK16_KHR = VK_FORMAT_B10X6G10X6R10X6G10X6_422_UNORM_4PACK16,
+    VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16_KHR = VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16,
+    VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16_KHR = VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16,
+    VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_422_UNORM_3PACK16_KHR = VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_422_UNORM_3PACK16,
+    VK_FORMAT_G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16_KHR = VK_FORMAT_G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16,
+    VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16_KHR = VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16,
+    VK_FORMAT_R12X4_UNORM_PACK16_KHR = VK_FORMAT_R12X4_UNORM_PACK16,
+    VK_FORMAT_R12X4G12X4_UNORM_2PACK16_KHR = VK_FORMAT_R12X4G12X4_UNORM_2PACK16,
+    VK_FORMAT_R12X4G12X4B12X4A12X4_UNORM_4PACK16_KHR = VK_FORMAT_R12X4G12X4B12X4A12X4_UNORM_4PACK16,
+    VK_FORMAT_G12X4B12X4G12X4R12X4_422_UNORM_4PACK16_KHR = VK_FORMAT_G12X4B12X4G12X4R12X4_422_UNORM_4PACK16,
+    VK_FORMAT_B12X4G12X4R12X4G12X4_422_UNORM_4PACK16_KHR = VK_FORMAT_B12X4G12X4R12X4G12X4_422_UNORM_4PACK16,
+    VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_420_UNORM_3PACK16_KHR = VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_420_UNORM_3PACK16,
+    VK_FORMAT_G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16_KHR = VK_FORMAT_G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16,
+    VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_422_UNORM_3PACK16_KHR = VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_422_UNORM_3PACK16,
+    VK_FORMAT_G12X4_B12X4R12X4_2PLANE_422_UNORM_3PACK16_KHR = VK_FORMAT_G12X4_B12X4R12X4_2PLANE_422_UNORM_3PACK16,
+    VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_444_UNORM_3PACK16_KHR = VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_444_UNORM_3PACK16,
+    VK_FORMAT_G16B16G16R16_422_UNORM_KHR = VK_FORMAT_G16B16G16R16_422_UNORM,
+    VK_FORMAT_B16G16R16G16_422_UNORM_KHR = VK_FORMAT_B16G16R16G16_422_UNORM,
+    VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM_KHR = VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM,
+    VK_FORMAT_G16_B16R16_2PLANE_420_UNORM_KHR = VK_FORMAT_G16_B16R16_2PLANE_420_UNORM,
+    VK_FORMAT_G16_B16_R16_3PLANE_422_UNORM_KHR = VK_FORMAT_G16_B16_R16_3PLANE_422_UNORM,
+    VK_FORMAT_G16_B16R16_2PLANE_422_UNORM_KHR = VK_FORMAT_G16_B16R16_2PLANE_422_UNORM,
+    VK_FORMAT_G16_B16_R16_3PLANE_444_UNORM_KHR = VK_FORMAT_G16_B16_R16_3PLANE_444_UNORM,
+    VK_FORMAT_MAX_ENUM = 0x7FFFFFFF
+} VkFormat;
+
+typedef enum VkImageTiling {
+    VK_IMAGE_TILING_OPTIMAL = 0,
+    VK_IMAGE_TILING_LINEAR = 1,
+    VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT = 1000158000,
+    VK_IMAGE_TILING_MAX_ENUM = 0x7FFFFFFF
+} VkImageTiling;
+
+typedef enum VkImageType {
+    VK_IMAGE_TYPE_1D = 0,
+    VK_IMAGE_TYPE_2D = 1,
+    VK_IMAGE_TYPE_3D = 2,
+    VK_IMAGE_TYPE_MAX_ENUM = 0x7FFFFFFF
+} VkImageType;
+
+typedef enum VkPhysicalDeviceType {
+    VK_PHYSICAL_DEVICE_TYPE_OTHER = 0,
+    VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU = 1,
+    VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU = 2,
+    VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU = 3,
+    VK_PHYSICAL_DEVICE_TYPE_CPU = 4,
+    VK_PHYSICAL_DEVICE_TYPE_MAX_ENUM = 0x7FFFFFFF
+} VkPhysicalDeviceType;
+
+typedef enum VkQueryType {
+    VK_QUERY_TYPE_OCCLUSION = 0,
+    VK_QUERY_TYPE_PIPELINE_STATISTICS = 1,
+    VK_QUERY_TYPE_TIMESTAMP = 2,
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_QUERY_TYPE_RESULT_STATUS_ONLY_KHR = 1000023000,
+#endif
+    VK_QUERY_TYPE_TRANSFORM_FEEDBACK_STREAM_EXT = 1000028004,
+    VK_QUERY_TYPE_PERFORMANCE_QUERY_KHR = 1000116000,
+    VK_QUERY_TYPE_ACCELERATION_STRUCTURE_COMPACTED_SIZE_KHR = 1000150000,
+    VK_QUERY_TYPE_ACCELERATION_STRUCTURE_SERIALIZATION_SIZE_KHR = 1000150001,
+    VK_QUERY_TYPE_ACCELERATION_STRUCTURE_COMPACTED_SIZE_NV = 1000165000,
+    VK_QUERY_TYPE_PERFORMANCE_QUERY_INTEL = 1000210000,
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_QUERY_TYPE_VIDEO_ENCODE_BITSTREAM_BUFFER_RANGE_KHR = 1000299000,
+#endif
+    VK_QUERY_TYPE_MAX_ENUM = 0x7FFFFFFF
+} VkQueryType;
+
+typedef enum VkSharingMode {
+    VK_SHARING_MODE_EXCLUSIVE = 0,
+    VK_SHARING_MODE_CONCURRENT = 1,
+    VK_SHARING_MODE_MAX_ENUM = 0x7FFFFFFF
+} VkSharingMode;
+
+typedef enum VkComponentSwizzle {
+    VK_COMPONENT_SWIZZLE_IDENTITY = 0,
+    VK_COMPONENT_SWIZZLE_ZERO = 1,
+    VK_COMPONENT_SWIZZLE_ONE = 2,
+    VK_COMPONENT_SWIZZLE_R = 3,
+    VK_COMPONENT_SWIZZLE_G = 4,
+    VK_COMPONENT_SWIZZLE_B = 5,
+    VK_COMPONENT_SWIZZLE_A = 6,
+    VK_COMPONENT_SWIZZLE_MAX_ENUM = 0x7FFFFFFF
+} VkComponentSwizzle;
+
+typedef enum VkImageViewType {
+    VK_IMAGE_VIEW_TYPE_1D = 0,
+    VK_IMAGE_VIEW_TYPE_2D = 1,
+    VK_IMAGE_VIEW_TYPE_3D = 2,
+    VK_IMAGE_VIEW_TYPE_CUBE = 3,
+    VK_IMAGE_VIEW_TYPE_1D_ARRAY = 4,
+    VK_IMAGE_VIEW_TYPE_2D_ARRAY = 5,
+    VK_IMAGE_VIEW_TYPE_CUBE_ARRAY = 6,
+    VK_IMAGE_VIEW_TYPE_MAX_ENUM = 0x7FFFFFFF
+} VkImageViewType;
+
+typedef enum VkBlendFactor {
+    VK_BLEND_FACTOR_ZERO = 0,
+    VK_BLEND_FACTOR_ONE = 1,
+    VK_BLEND_FACTOR_SRC_COLOR = 2,
+    VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR = 3,
+    VK_BLEND_FACTOR_DST_COLOR = 4,
+    VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR = 5,
+    VK_BLEND_FACTOR_SRC_ALPHA = 6,
+    VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA = 7,
+    VK_BLEND_FACTOR_DST_ALPHA = 8,
+    VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA = 9,
+    VK_BLEND_FACTOR_CONSTANT_COLOR = 10,
+    VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR = 11,
+    VK_BLEND_FACTOR_CONSTANT_ALPHA = 12,
+    VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA = 13,
+    VK_BLEND_FACTOR_SRC_ALPHA_SATURATE = 14,
+    VK_BLEND_FACTOR_SRC1_COLOR = 15,
+    VK_BLEND_FACTOR_ONE_MINUS_SRC1_COLOR = 16,
+    VK_BLEND_FACTOR_SRC1_ALPHA = 17,
+    VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA = 18,
+    VK_BLEND_FACTOR_MAX_ENUM = 0x7FFFFFFF
+} VkBlendFactor;
+
+typedef enum VkBlendOp {
+    VK_BLEND_OP_ADD = 0,
+    VK_BLEND_OP_SUBTRACT = 1,
+    VK_BLEND_OP_REVERSE_SUBTRACT = 2,
+    VK_BLEND_OP_MIN = 3,
+    VK_BLEND_OP_MAX = 4,
+    VK_BLEND_OP_ZERO_EXT = 1000148000,
+    VK_BLEND_OP_SRC_EXT = 1000148001,
+    VK_BLEND_OP_DST_EXT = 1000148002,
+    VK_BLEND_OP_SRC_OVER_EXT = 1000148003,
+    VK_BLEND_OP_DST_OVER_EXT = 1000148004,
+    VK_BLEND_OP_SRC_IN_EXT = 1000148005,
+    VK_BLEND_OP_DST_IN_EXT = 1000148006,
+    VK_BLEND_OP_SRC_OUT_EXT = 1000148007,
+    VK_BLEND_OP_DST_OUT_EXT = 1000148008,
+    VK_BLEND_OP_SRC_ATOP_EXT = 1000148009,
+    VK_BLEND_OP_DST_ATOP_EXT = 1000148010,
+    VK_BLEND_OP_XOR_EXT = 1000148011,
+    VK_BLEND_OP_MULTIPLY_EXT = 1000148012,
+    VK_BLEND_OP_SCREEN_EXT = 1000148013,
+    VK_BLEND_OP_OVERLAY_EXT = 1000148014,
+    VK_BLEND_OP_DARKEN_EXT = 1000148015,
+    VK_BLEND_OP_LIGHTEN_EXT = 1000148016,
+    VK_BLEND_OP_COLORDODGE_EXT = 1000148017,
+    VK_BLEND_OP_COLORBURN_EXT = 1000148018,
+    VK_BLEND_OP_HARDLIGHT_EXT = 1000148019,
+    VK_BLEND_OP_SOFTLIGHT_EXT = 1000148020,
+    VK_BLEND_OP_DIFFERENCE_EXT = 1000148021,
+    VK_BLEND_OP_EXCLUSION_EXT = 1000148022,
+    VK_BLEND_OP_INVERT_EXT = 1000148023,
+    VK_BLEND_OP_INVERT_RGB_EXT = 1000148024,
+    VK_BLEND_OP_LINEARDODGE_EXT = 1000148025,
+    VK_BLEND_OP_LINEARBURN_EXT = 1000148026,
+    VK_BLEND_OP_VIVIDLIGHT_EXT = 1000148027,
+    VK_BLEND_OP_LINEARLIGHT_EXT = 1000148028,
+    VK_BLEND_OP_PINLIGHT_EXT = 1000148029,
+    VK_BLEND_OP_HARDMIX_EXT = 1000148030,
+    VK_BLEND_OP_HSL_HUE_EXT = 1000148031,
+    VK_BLEND_OP_HSL_SATURATION_EXT = 1000148032,
+    VK_BLEND_OP_HSL_COLOR_EXT = 1000148033,
+    VK_BLEND_OP_HSL_LUMINOSITY_EXT = 1000148034,
+    VK_BLEND_OP_PLUS_EXT = 1000148035,
+    VK_BLEND_OP_PLUS_CLAMPED_EXT = 1000148036,
+    VK_BLEND_OP_PLUS_CLAMPED_ALPHA_EXT = 1000148037,
+    VK_BLEND_OP_PLUS_DARKER_EXT = 1000148038,
+    VK_BLEND_OP_MINUS_EXT = 1000148039,
+    VK_BLEND_OP_MINUS_CLAMPED_EXT = 1000148040,
+    VK_BLEND_OP_CONTRAST_EXT = 1000148041,
+    VK_BLEND_OP_INVERT_OVG_EXT = 1000148042,
+    VK_BLEND_OP_RED_EXT = 1000148043,
+    VK_BLEND_OP_GREEN_EXT = 1000148044,
+    VK_BLEND_OP_BLUE_EXT = 1000148045,
+    VK_BLEND_OP_MAX_ENUM = 0x7FFFFFFF
+} VkBlendOp;
+
+typedef enum VkCompareOp {
+    VK_COMPARE_OP_NEVER = 0,
+    VK_COMPARE_OP_LESS = 1,
+    VK_COMPARE_OP_EQUAL = 2,
+    VK_COMPARE_OP_LESS_OR_EQUAL = 3,
+    VK_COMPARE_OP_GREATER = 4,
+    VK_COMPARE_OP_NOT_EQUAL = 5,
+    VK_COMPARE_OP_GREATER_OR_EQUAL = 6,
+    VK_COMPARE_OP_ALWAYS = 7,
+    VK_COMPARE_OP_MAX_ENUM = 0x7FFFFFFF
+} VkCompareOp;
+
+typedef enum VkDynamicState {
+    VK_DYNAMIC_STATE_VIEWPORT = 0,
+    VK_DYNAMIC_STATE_SCISSOR = 1,
+    VK_DYNAMIC_STATE_LINE_WIDTH = 2,
+    VK_DYNAMIC_STATE_DEPTH_BIAS = 3,
+    VK_DYNAMIC_STATE_BLEND_CONSTANTS = 4,
+    VK_DYNAMIC_STATE_DEPTH_BOUNDS = 5,
+    VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK = 6,
+    VK_DYNAMIC_STATE_STENCIL_WRITE_MASK = 7,
+    VK_DYNAMIC_STATE_STENCIL_REFERENCE = 8,
+    VK_DYNAMIC_STATE_VIEWPORT_W_SCALING_NV = 1000087000,
+    VK_DYNAMIC_STATE_DISCARD_RECTANGLE_EXT = 1000099000,
+    VK_DYNAMIC_STATE_SAMPLE_LOCATIONS_EXT = 1000143000,
+    VK_DYNAMIC_STATE_RAY_TRACING_PIPELINE_STACK_SIZE_KHR = 1000347000,
+    VK_DYNAMIC_STATE_VIEWPORT_SHADING_RATE_PALETTE_NV = 1000164004,
+    VK_DYNAMIC_STATE_VIEWPORT_COARSE_SAMPLE_ORDER_NV = 1000164006,
+    VK_DYNAMIC_STATE_EXCLUSIVE_SCISSOR_NV = 1000205001,
+    VK_DYNAMIC_STATE_FRAGMENT_SHADING_RATE_KHR = 1000226000,
+    VK_DYNAMIC_STATE_LINE_STIPPLE_EXT = 1000259000,
+    VK_DYNAMIC_STATE_CULL_MODE_EXT = 1000267000,
+    VK_DYNAMIC_STATE_FRONT_FACE_EXT = 1000267001,
+    VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY_EXT = 1000267002,
+    VK_DYNAMIC_STATE_VIEWPORT_WITH_COUNT_EXT = 1000267003,
+    VK_DYNAMIC_STATE_SCISSOR_WITH_COUNT_EXT = 1000267004,
+    VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE_EXT = 1000267005,
+    VK_DYNAMIC_STATE_DEPTH_TEST_ENABLE_EXT = 1000267006,
+    VK_DYNAMIC_STATE_DEPTH_WRITE_ENABLE_EXT = 1000267007,
+    VK_DYNAMIC_STATE_DEPTH_COMPARE_OP_EXT = 1000267008,
+    VK_DYNAMIC_STATE_DEPTH_BOUNDS_TEST_ENABLE_EXT = 1000267009,
+    VK_DYNAMIC_STATE_STENCIL_TEST_ENABLE_EXT = 1000267010,
+    VK_DYNAMIC_STATE_STENCIL_OP_EXT = 1000267011,
+    VK_DYNAMIC_STATE_VERTEX_INPUT_EXT = 1000352000,
+    VK_DYNAMIC_STATE_PATCH_CONTROL_POINTS_EXT = 1000377000,
+    VK_DYNAMIC_STATE_RASTERIZER_DISCARD_ENABLE_EXT = 1000377001,
+    VK_DYNAMIC_STATE_DEPTH_BIAS_ENABLE_EXT = 1000377002,
+    VK_DYNAMIC_STATE_LOGIC_OP_EXT = 1000377003,
+    VK_DYNAMIC_STATE_PRIMITIVE_RESTART_ENABLE_EXT = 1000377004,
+    VK_DYNAMIC_STATE_COLOR_WRITE_ENABLE_EXT = 1000381000,
+    VK_DYNAMIC_STATE_MAX_ENUM = 0x7FFFFFFF
+} VkDynamicState;
+
+typedef enum VkFrontFace {
+    VK_FRONT_FACE_COUNTER_CLOCKWISE = 0,
+    VK_FRONT_FACE_CLOCKWISE = 1,
+    VK_FRONT_FACE_MAX_ENUM = 0x7FFFFFFF
+} VkFrontFace;
+
+typedef enum VkVertexInputRate {
+    VK_VERTEX_INPUT_RATE_VERTEX = 0,
+    VK_VERTEX_INPUT_RATE_INSTANCE = 1,
+    VK_VERTEX_INPUT_RATE_MAX_ENUM = 0x7FFFFFFF
+} VkVertexInputRate;
+
+typedef enum VkPrimitiveTopology {
+    VK_PRIMITIVE_TOPOLOGY_POINT_LIST = 0,
+    VK_PRIMITIVE_TOPOLOGY_LINE_LIST = 1,
+    VK_PRIMITIVE_TOPOLOGY_LINE_STRIP = 2,
+    VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST = 3,
+    VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP = 4,
+    VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN = 5,
+    VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY = 6,
+    VK_PRIMITIVE_TOPOLOGY_LINE_STRIP_WITH_ADJACENCY = 7,
+    VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY = 8,
+    VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP_WITH_ADJACENCY = 9,
+    VK_PRIMITIVE_TOPOLOGY_PATCH_LIST = 10,
+    VK_PRIMITIVE_TOPOLOGY_MAX_ENUM = 0x7FFFFFFF
+} VkPrimitiveTopology;
+
+typedef enum VkPolygonMode {
+    VK_POLYGON_MODE_FILL = 0,
+    VK_POLYGON_MODE_LINE = 1,
+    VK_POLYGON_MODE_POINT = 2,
+    VK_POLYGON_MODE_FILL_RECTANGLE_NV = 1000153000,
+    VK_POLYGON_MODE_MAX_ENUM = 0x7FFFFFFF
+} VkPolygonMode;
+
+typedef enum VkStencilOp {
+    VK_STENCIL_OP_KEEP = 0,
+    VK_STENCIL_OP_ZERO = 1,
+    VK_STENCIL_OP_REPLACE = 2,
+    VK_STENCIL_OP_INCREMENT_AND_CLAMP = 3,
+    VK_STENCIL_OP_DECREMENT_AND_CLAMP = 4,
+    VK_STENCIL_OP_INVERT = 5,
+    VK_STENCIL_OP_INCREMENT_AND_WRAP = 6,
+    VK_STENCIL_OP_DECREMENT_AND_WRAP = 7,
+    VK_STENCIL_OP_MAX_ENUM = 0x7FFFFFFF
+} VkStencilOp;
+
+typedef enum VkLogicOp {
+    VK_LOGIC_OP_CLEAR = 0,
+    VK_LOGIC_OP_AND = 1,
+    VK_LOGIC_OP_AND_REVERSE = 2,
+    VK_LOGIC_OP_COPY = 3,
+    VK_LOGIC_OP_AND_INVERTED = 4,
+    VK_LOGIC_OP_NO_OP = 5,
+    VK_LOGIC_OP_XOR = 6,
+    VK_LOGIC_OP_OR = 7,
+    VK_LOGIC_OP_NOR = 8,
+    VK_LOGIC_OP_EQUIVALENT = 9,
+    VK_LOGIC_OP_INVERT = 10,
+    VK_LOGIC_OP_OR_REVERSE = 11,
+    VK_LOGIC_OP_COPY_INVERTED = 12,
+    VK_LOGIC_OP_OR_INVERTED = 13,
+    VK_LOGIC_OP_NAND = 14,
+    VK_LOGIC_OP_SET = 15,
+    VK_LOGIC_OP_MAX_ENUM = 0x7FFFFFFF
+} VkLogicOp;
+
+typedef enum VkBorderColor {
+    VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK = 0,
+    VK_BORDER_COLOR_INT_TRANSPARENT_BLACK = 1,
+    VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK = 2,
+    VK_BORDER_COLOR_INT_OPAQUE_BLACK = 3,
+    VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE = 4,
+    VK_BORDER_COLOR_INT_OPAQUE_WHITE = 5,
+    VK_BORDER_COLOR_FLOAT_CUSTOM_EXT = 1000287003,
+    VK_BORDER_COLOR_INT_CUSTOM_EXT = 1000287004,
+    VK_BORDER_COLOR_MAX_ENUM = 0x7FFFFFFF
+} VkBorderColor;
+
+typedef enum VkFilter {
+    VK_FILTER_NEAREST = 0,
+    VK_FILTER_LINEAR = 1,
+    VK_FILTER_CUBIC_IMG = 1000015000,
+    VK_FILTER_CUBIC_EXT = VK_FILTER_CUBIC_IMG,
+    VK_FILTER_MAX_ENUM = 0x7FFFFFFF
+} VkFilter;
+
+typedef enum VkSamplerAddressMode {
+    VK_SAMPLER_ADDRESS_MODE_REPEAT = 0,
+    VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT = 1,
+    VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE = 2,
+    VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER = 3,
+    VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE = 4,
+    VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE_KHR = VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE,
+    VK_SAMPLER_ADDRESS_MODE_MAX_ENUM = 0x7FFFFFFF
+} VkSamplerAddressMode;
+
+typedef enum VkSamplerMipmapMode {
+    VK_SAMPLER_MIPMAP_MODE_NEAREST = 0,
+    VK_SAMPLER_MIPMAP_MODE_LINEAR = 1,
+    VK_SAMPLER_MIPMAP_MODE_MAX_ENUM = 0x7FFFFFFF
+} VkSamplerMipmapMode;
+
+typedef enum VkDescriptorType {
+    VK_DESCRIPTOR_TYPE_SAMPLER = 0,
+    VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER = 1,
+    VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE = 2,
+    VK_DESCRIPTOR_TYPE_STORAGE_IMAGE = 3,
+    VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER = 4,
+    VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER = 5,
+    VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER = 6,
+    VK_DESCRIPTOR_TYPE_STORAGE_BUFFER = 7,
+    VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC = 8,
+    VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC = 9,
+    VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT = 10,
+    VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT = 1000138000,
+    VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR = 1000150000,
+    VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_NV = 1000165000,
+    VK_DESCRIPTOR_TYPE_MUTABLE_VALVE = 1000351000,
+    VK_DESCRIPTOR_TYPE_MAX_ENUM = 0x7FFFFFFF
+} VkDescriptorType;
+
+typedef enum VkAttachmentLoadOp {
+    VK_ATTACHMENT_LOAD_OP_LOAD = 0,
+    VK_ATTACHMENT_LOAD_OP_CLEAR = 1,
+    VK_ATTACHMENT_LOAD_OP_DONT_CARE = 2,
+    VK_ATTACHMENT_LOAD_OP_NONE_EXT = 1000400000,
+    VK_ATTACHMENT_LOAD_OP_MAX_ENUM = 0x7FFFFFFF
+} VkAttachmentLoadOp;
+
+typedef enum VkAttachmentStoreOp {
+    VK_ATTACHMENT_STORE_OP_STORE = 0,
+    VK_ATTACHMENT_STORE_OP_DONT_CARE = 1,
+    VK_ATTACHMENT_STORE_OP_NONE_KHR = 1000301000,
+    VK_ATTACHMENT_STORE_OP_NONE_QCOM = VK_ATTACHMENT_STORE_OP_NONE_KHR,
+    VK_ATTACHMENT_STORE_OP_NONE_EXT = VK_ATTACHMENT_STORE_OP_NONE_KHR,
+    VK_ATTACHMENT_STORE_OP_MAX_ENUM = 0x7FFFFFFF
+} VkAttachmentStoreOp;
+
+typedef enum VkPipelineBindPoint {
+    VK_PIPELINE_BIND_POINT_GRAPHICS = 0,
+    VK_PIPELINE_BIND_POINT_COMPUTE = 1,
+    VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR = 1000165000,
+    VK_PIPELINE_BIND_POINT_SUBPASS_SHADING_HUAWEI = 1000369003,
+    VK_PIPELINE_BIND_POINT_RAY_TRACING_NV = VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR,
+    VK_PIPELINE_BIND_POINT_MAX_ENUM = 0x7FFFFFFF
+} VkPipelineBindPoint;
+
+typedef enum VkCommandBufferLevel {
+    VK_COMMAND_BUFFER_LEVEL_PRIMARY = 0,
+    VK_COMMAND_BUFFER_LEVEL_SECONDARY = 1,
+    VK_COMMAND_BUFFER_LEVEL_MAX_ENUM = 0x7FFFFFFF
+} VkCommandBufferLevel;
+
+typedef enum VkIndexType {
+    VK_INDEX_TYPE_UINT16 = 0,
+    VK_INDEX_TYPE_UINT32 = 1,
+    VK_INDEX_TYPE_NONE_KHR = 1000165000,
+    VK_INDEX_TYPE_UINT8_EXT = 1000265000,
+    VK_INDEX_TYPE_NONE_NV = VK_INDEX_TYPE_NONE_KHR,
+    VK_INDEX_TYPE_MAX_ENUM = 0x7FFFFFFF
+} VkIndexType;
+
+typedef enum VkSubpassContents {
+    VK_SUBPASS_CONTENTS_INLINE = 0,
+    VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS = 1,
+    VK_SUBPASS_CONTENTS_MAX_ENUM = 0x7FFFFFFF
+} VkSubpassContents;
+
+typedef enum VkAccessFlagBits {
+    VK_ACCESS_INDIRECT_COMMAND_READ_BIT = 0x00000001,
+    VK_ACCESS_INDEX_READ_BIT = 0x00000002,
+    VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT = 0x00000004,
+    VK_ACCESS_UNIFORM_READ_BIT = 0x00000008,
+    VK_ACCESS_INPUT_ATTACHMENT_READ_BIT = 0x00000010,
+    VK_ACCESS_SHADER_READ_BIT = 0x00000020,
+    VK_ACCESS_SHADER_WRITE_BIT = 0x00000040,
+    VK_ACCESS_COLOR_ATTACHMENT_READ_BIT = 0x00000080,
+    VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT = 0x00000100,
+    VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT = 0x00000200,
+    VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT = 0x00000400,
+    VK_ACCESS_TRANSFER_READ_BIT = 0x00000800,
+    VK_ACCESS_TRANSFER_WRITE_BIT = 0x00001000,
+    VK_ACCESS_HOST_READ_BIT = 0x00002000,
+    VK_ACCESS_HOST_WRITE_BIT = 0x00004000,
+    VK_ACCESS_MEMORY_READ_BIT = 0x00008000,
+    VK_ACCESS_MEMORY_WRITE_BIT = 0x00010000,
+    VK_ACCESS_TRANSFORM_FEEDBACK_WRITE_BIT_EXT = 0x02000000,
+    VK_ACCESS_TRANSFORM_FEEDBACK_COUNTER_READ_BIT_EXT = 0x04000000,
+    VK_ACCESS_TRANSFORM_FEEDBACK_COUNTER_WRITE_BIT_EXT = 0x08000000,
+    VK_ACCESS_CONDITIONAL_RENDERING_READ_BIT_EXT = 0x00100000,
+    VK_ACCESS_COLOR_ATTACHMENT_READ_NONCOHERENT_BIT_EXT = 0x00080000,
+    VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR = 0x00200000,
+    VK_ACCESS_ACCELERATION_STRUCTURE_WRITE_BIT_KHR = 0x00400000,
+    VK_ACCESS_FRAGMENT_DENSITY_MAP_READ_BIT_EXT = 0x01000000,
+    VK_ACCESS_FRAGMENT_SHADING_RATE_ATTACHMENT_READ_BIT_KHR = 0x00800000,
+    VK_ACCESS_COMMAND_PREPROCESS_READ_BIT_NV = 0x00020000,
+    VK_ACCESS_COMMAND_PREPROCESS_WRITE_BIT_NV = 0x00040000,
+    VK_ACCESS_NONE_KHR = 0,
+    VK_ACCESS_SHADING_RATE_IMAGE_READ_BIT_NV = VK_ACCESS_FRAGMENT_SHADING_RATE_ATTACHMENT_READ_BIT_KHR,
+    VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_NV = VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR,
+    VK_ACCESS_ACCELERATION_STRUCTURE_WRITE_BIT_NV = VK_ACCESS_ACCELERATION_STRUCTURE_WRITE_BIT_KHR,
+    VK_ACCESS_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkAccessFlagBits;
+typedef VkFlags VkAccessFlags;
+
+typedef enum VkImageAspectFlagBits {
+    VK_IMAGE_ASPECT_COLOR_BIT = 0x00000001,
+    VK_IMAGE_ASPECT_DEPTH_BIT = 0x00000002,
+    VK_IMAGE_ASPECT_STENCIL_BIT = 0x00000004,
+    VK_IMAGE_ASPECT_METADATA_BIT = 0x00000008,
+    VK_IMAGE_ASPECT_PLANE_0_BIT = 0x00000010,
+    VK_IMAGE_ASPECT_PLANE_1_BIT = 0x00000020,
+    VK_IMAGE_ASPECT_PLANE_2_BIT = 0x00000040,
+    VK_IMAGE_ASPECT_MEMORY_PLANE_0_BIT_EXT = 0x00000080,
+    VK_IMAGE_ASPECT_MEMORY_PLANE_1_BIT_EXT = 0x00000100,
+    VK_IMAGE_ASPECT_MEMORY_PLANE_2_BIT_EXT = 0x00000200,
+    VK_IMAGE_ASPECT_MEMORY_PLANE_3_BIT_EXT = 0x00000400,
+    VK_IMAGE_ASPECT_PLANE_0_BIT_KHR = VK_IMAGE_ASPECT_PLANE_0_BIT,
+    VK_IMAGE_ASPECT_PLANE_1_BIT_KHR = VK_IMAGE_ASPECT_PLANE_1_BIT,
+    VK_IMAGE_ASPECT_PLANE_2_BIT_KHR = VK_IMAGE_ASPECT_PLANE_2_BIT,
+    VK_IMAGE_ASPECT_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkImageAspectFlagBits;
+typedef VkFlags VkImageAspectFlags;
+
+typedef enum VkFormatFeatureFlagBits {
+    VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT = 0x00000001,
+    VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT = 0x00000002,
+    VK_FORMAT_FEATURE_STORAGE_IMAGE_ATOMIC_BIT = 0x00000004,
+    VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT = 0x00000008,
+    VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT = 0x00000010,
+    VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_ATOMIC_BIT = 0x00000020,
+    VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT = 0x00000040,
+    VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT = 0x00000080,
+    VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT = 0x00000100,
+    VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT = 0x00000200,
+    VK_FORMAT_FEATURE_BLIT_SRC_BIT = 0x00000400,
+    VK_FORMAT_FEATURE_BLIT_DST_BIT = 0x00000800,
+    VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT = 0x00001000,
+    VK_FORMAT_FEATURE_TRANSFER_SRC_BIT = 0x00004000,
+    VK_FORMAT_FEATURE_TRANSFER_DST_BIT = 0x00008000,
+    VK_FORMAT_FEATURE_MIDPOINT_CHROMA_SAMPLES_BIT = 0x00020000,
+    VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_LINEAR_FILTER_BIT = 0x00040000,
+    VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_SEPARATE_RECONSTRUCTION_FILTER_BIT = 0x00080000,
+    VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_CHROMA_RECONSTRUCTION_EXPLICIT_BIT = 0x00100000,
+    VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_CHROMA_RECONSTRUCTION_EXPLICIT_FORCEABLE_BIT = 0x00200000,
+    VK_FORMAT_FEATURE_DISJOINT_BIT = 0x00400000,
+    VK_FORMAT_FEATURE_COSITED_CHROMA_SAMPLES_BIT = 0x00800000,
+    VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_MINMAX_BIT = 0x00010000,
+    VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_CUBIC_BIT_IMG = 0x00002000,
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_FORMAT_FEATURE_VIDEO_DECODE_OUTPUT_BIT_KHR = 0x02000000,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_FORMAT_FEATURE_VIDEO_DECODE_DPB_BIT_KHR = 0x04000000,
+#endif
+    VK_FORMAT_FEATURE_ACCELERATION_STRUCTURE_VERTEX_BUFFER_BIT_KHR = 0x20000000,
+    VK_FORMAT_FEATURE_FRAGMENT_DENSITY_MAP_BIT_EXT = 0x01000000,
+    VK_FORMAT_FEATURE_FRAGMENT_SHADING_RATE_ATTACHMENT_BIT_KHR = 0x40000000,
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_FORMAT_FEATURE_VIDEO_ENCODE_INPUT_BIT_KHR = 0x08000000,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_FORMAT_FEATURE_VIDEO_ENCODE_DPB_BIT_KHR = 0x10000000,
+#endif
+    VK_FORMAT_FEATURE_TRANSFER_SRC_BIT_KHR = VK_FORMAT_FEATURE_TRANSFER_SRC_BIT,
+    VK_FORMAT_FEATURE_TRANSFER_DST_BIT_KHR = VK_FORMAT_FEATURE_TRANSFER_DST_BIT,
+    VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_MINMAX_BIT_EXT = VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_MINMAX_BIT,
+    VK_FORMAT_FEATURE_MIDPOINT_CHROMA_SAMPLES_BIT_KHR = VK_FORMAT_FEATURE_MIDPOINT_CHROMA_SAMPLES_BIT,
+    VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_LINEAR_FILTER_BIT_KHR = VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_LINEAR_FILTER_BIT,
+    VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_SEPARATE_RECONSTRUCTION_FILTER_BIT_KHR = VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_SEPARATE_RECONSTRUCTION_FILTER_BIT,
+    VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_CHROMA_RECONSTRUCTION_EXPLICIT_BIT_KHR = VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_CHROMA_RECONSTRUCTION_EXPLICIT_BIT,
+    VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_CHROMA_RECONSTRUCTION_EXPLICIT_FORCEABLE_BIT_KHR = VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_CHROMA_RECONSTRUCTION_EXPLICIT_FORCEABLE_BIT,
+    VK_FORMAT_FEATURE_DISJOINT_BIT_KHR = VK_FORMAT_FEATURE_DISJOINT_BIT,
+    VK_FORMAT_FEATURE_COSITED_CHROMA_SAMPLES_BIT_KHR = VK_FORMAT_FEATURE_COSITED_CHROMA_SAMPLES_BIT,
+    VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_CUBIC_BIT_EXT = VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_CUBIC_BIT_IMG,
+    VK_FORMAT_FEATURE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkFormatFeatureFlagBits;
+typedef VkFlags VkFormatFeatureFlags;
+
+typedef enum VkImageCreateFlagBits {
+    VK_IMAGE_CREATE_SPARSE_BINDING_BIT = 0x00000001,
+    VK_IMAGE_CREATE_SPARSE_RESIDENCY_BIT = 0x00000002,
+    VK_IMAGE_CREATE_SPARSE_ALIASED_BIT = 0x00000004,
+    VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT = 0x00000008,
+    VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT = 0x00000010,
+    VK_IMAGE_CREATE_ALIAS_BIT = 0x00000400,
+    VK_IMAGE_CREATE_SPLIT_INSTANCE_BIND_REGIONS_BIT = 0x00000040,
+    VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT = 0x00000020,
+    VK_IMAGE_CREATE_BLOCK_TEXEL_VIEW_COMPATIBLE_BIT = 0x00000080,
+    VK_IMAGE_CREATE_EXTENDED_USAGE_BIT = 0x00000100,
+    VK_IMAGE_CREATE_PROTECTED_BIT = 0x00000800,
+    VK_IMAGE_CREATE_DISJOINT_BIT = 0x00000200,
+    VK_IMAGE_CREATE_CORNER_SAMPLED_BIT_NV = 0x00002000,
+    VK_IMAGE_CREATE_SAMPLE_LOCATIONS_COMPATIBLE_DEPTH_BIT_EXT = 0x00001000,
+    VK_IMAGE_CREATE_SUBSAMPLED_BIT_EXT = 0x00004000,
+    VK_IMAGE_CREATE_SPLIT_INSTANCE_BIND_REGIONS_BIT_KHR = VK_IMAGE_CREATE_SPLIT_INSTANCE_BIND_REGIONS_BIT,
+    VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT_KHR = VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT,
+    VK_IMAGE_CREATE_BLOCK_TEXEL_VIEW_COMPATIBLE_BIT_KHR = VK_IMAGE_CREATE_BLOCK_TEXEL_VIEW_COMPATIBLE_BIT,
+    VK_IMAGE_CREATE_EXTENDED_USAGE_BIT_KHR = VK_IMAGE_CREATE_EXTENDED_USAGE_BIT,
+    VK_IMAGE_CREATE_DISJOINT_BIT_KHR = VK_IMAGE_CREATE_DISJOINT_BIT,
+    VK_IMAGE_CREATE_ALIAS_BIT_KHR = VK_IMAGE_CREATE_ALIAS_BIT,
+    VK_IMAGE_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkImageCreateFlagBits;
+typedef VkFlags VkImageCreateFlags;
+
+typedef enum VkSampleCountFlagBits {
+    VK_SAMPLE_COUNT_1_BIT = 0x00000001,
+    VK_SAMPLE_COUNT_2_BIT = 0x00000002,
+    VK_SAMPLE_COUNT_4_BIT = 0x00000004,
+    VK_SAMPLE_COUNT_8_BIT = 0x00000008,
+    VK_SAMPLE_COUNT_16_BIT = 0x00000010,
+    VK_SAMPLE_COUNT_32_BIT = 0x00000020,
+    VK_SAMPLE_COUNT_64_BIT = 0x00000040,
+    VK_SAMPLE_COUNT_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkSampleCountFlagBits;
+typedef VkFlags VkSampleCountFlags;
+
+typedef enum VkImageUsageFlagBits {
+    VK_IMAGE_USAGE_TRANSFER_SRC_BIT = 0x00000001,
+    VK_IMAGE_USAGE_TRANSFER_DST_BIT = 0x00000002,
+    VK_IMAGE_USAGE_SAMPLED_BIT = 0x00000004,
+    VK_IMAGE_USAGE_STORAGE_BIT = 0x00000008,
+    VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT = 0x00000010,
+    VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT = 0x00000020,
+    VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT = 0x00000040,
+    VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT = 0x00000080,
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_IMAGE_USAGE_VIDEO_DECODE_DST_BIT_KHR = 0x00000400,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_IMAGE_USAGE_VIDEO_DECODE_SRC_BIT_KHR = 0x00000800,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_IMAGE_USAGE_VIDEO_DECODE_DPB_BIT_KHR = 0x00001000,
+#endif
+    VK_IMAGE_USAGE_FRAGMENT_DENSITY_MAP_BIT_EXT = 0x00000200,
+    VK_IMAGE_USAGE_FRAGMENT_SHADING_RATE_ATTACHMENT_BIT_KHR = 0x00000100,
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_IMAGE_USAGE_VIDEO_ENCODE_DST_BIT_KHR = 0x00002000,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_IMAGE_USAGE_VIDEO_ENCODE_SRC_BIT_KHR = 0x00004000,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_IMAGE_USAGE_VIDEO_ENCODE_DPB_BIT_KHR = 0x00008000,
+#endif
+    VK_IMAGE_USAGE_INVOCATION_MASK_BIT_HUAWEI = 0x00040000,
+    VK_IMAGE_USAGE_SHADING_RATE_IMAGE_BIT_NV = VK_IMAGE_USAGE_FRAGMENT_SHADING_RATE_ATTACHMENT_BIT_KHR,
+    VK_IMAGE_USAGE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkImageUsageFlagBits;
+typedef VkFlags VkImageUsageFlags;
+typedef VkFlags VkInstanceCreateFlags;
+
+typedef enum VkMemoryHeapFlagBits {
+    VK_MEMORY_HEAP_DEVICE_LOCAL_BIT = 0x00000001,
+    VK_MEMORY_HEAP_MULTI_INSTANCE_BIT = 0x00000002,
+    VK_MEMORY_HEAP_MULTI_INSTANCE_BIT_KHR = VK_MEMORY_HEAP_MULTI_INSTANCE_BIT,
+    VK_MEMORY_HEAP_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkMemoryHeapFlagBits;
+typedef VkFlags VkMemoryHeapFlags;
+
+typedef enum VkMemoryPropertyFlagBits {
+    VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT = 0x00000001,
+    VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT = 0x00000002,
+    VK_MEMORY_PROPERTY_HOST_COHERENT_BIT = 0x00000004,
+    VK_MEMORY_PROPERTY_HOST_CACHED_BIT = 0x00000008,
+    VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT = 0x00000010,
+    VK_MEMORY_PROPERTY_PROTECTED_BIT = 0x00000020,
+    VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD = 0x00000040,
+    VK_MEMORY_PROPERTY_DEVICE_UNCACHED_BIT_AMD = 0x00000080,
+    VK_MEMORY_PROPERTY_RDMA_CAPABLE_BIT_NV = 0x00000100,
+    VK_MEMORY_PROPERTY_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkMemoryPropertyFlagBits;
+typedef VkFlags VkMemoryPropertyFlags;
+
+typedef enum VkQueueFlagBits {
+    VK_QUEUE_GRAPHICS_BIT = 0x00000001,
+    VK_QUEUE_COMPUTE_BIT = 0x00000002,
+    VK_QUEUE_TRANSFER_BIT = 0x00000004,
+    VK_QUEUE_SPARSE_BINDING_BIT = 0x00000008,
+    VK_QUEUE_PROTECTED_BIT = 0x00000010,
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_QUEUE_VIDEO_DECODE_BIT_KHR = 0x00000020,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_QUEUE_VIDEO_ENCODE_BIT_KHR = 0x00000040,
+#endif
+    VK_QUEUE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkQueueFlagBits;
+typedef VkFlags VkQueueFlags;
+typedef VkFlags VkDeviceCreateFlags;
+
+typedef enum VkDeviceQueueCreateFlagBits {
+    VK_DEVICE_QUEUE_CREATE_PROTECTED_BIT = 0x00000001,
+    VK_DEVICE_QUEUE_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkDeviceQueueCreateFlagBits;
+typedef VkFlags VkDeviceQueueCreateFlags;
+
+typedef enum VkPipelineStageFlagBits {
+    VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT = 0x00000001,
+    VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT = 0x00000002,
+    VK_PIPELINE_STAGE_VERTEX_INPUT_BIT = 0x00000004,
+    VK_PIPELINE_STAGE_VERTEX_SHADER_BIT = 0x00000008,
+    VK_PIPELINE_STAGE_TESSELLATION_CONTROL_SHADER_BIT = 0x00000010,
+    VK_PIPELINE_STAGE_TESSELLATION_EVALUATION_SHADER_BIT = 0x00000020,
+    VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT = 0x00000040,
+    VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT = 0x00000080,
+    VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT = 0x00000100,
+    VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT = 0x00000200,
+    VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT = 0x00000400,
+    VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT = 0x00000800,
+    VK_PIPELINE_STAGE_TRANSFER_BIT = 0x00001000,
+    VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT = 0x00002000,
+    VK_PIPELINE_STAGE_HOST_BIT = 0x00004000,
+    VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT = 0x00008000,
+    VK_PIPELINE_STAGE_ALL_COMMANDS_BIT = 0x00010000,
+    VK_PIPELINE_STAGE_TRANSFORM_FEEDBACK_BIT_EXT = 0x01000000,
+    VK_PIPELINE_STAGE_CONDITIONAL_RENDERING_BIT_EXT = 0x00040000,
+    VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR = 0x02000000,
+    VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR = 0x00200000,
+    VK_PIPELINE_STAGE_TASK_SHADER_BIT_NV = 0x00080000,
+    VK_PIPELINE_STAGE_MESH_SHADER_BIT_NV = 0x00100000,
+    VK_PIPELINE_STAGE_FRAGMENT_DENSITY_PROCESS_BIT_EXT = 0x00800000,
+    VK_PIPELINE_STAGE_FRAGMENT_SHADING_RATE_ATTACHMENT_BIT_KHR = 0x00400000,
+    VK_PIPELINE_STAGE_COMMAND_PREPROCESS_BIT_NV = 0x00020000,
+    VK_PIPELINE_STAGE_NONE_KHR = 0,
+    VK_PIPELINE_STAGE_SHADING_RATE_IMAGE_BIT_NV = VK_PIPELINE_STAGE_FRAGMENT_SHADING_RATE_ATTACHMENT_BIT_KHR,
+    VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_NV = VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR,
+    VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_NV = VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR,
+    VK_PIPELINE_STAGE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkPipelineStageFlagBits;
+typedef VkFlags VkPipelineStageFlags;
+typedef VkFlags VkMemoryMapFlags;
+
+typedef enum VkSparseMemoryBindFlagBits {
+    VK_SPARSE_MEMORY_BIND_METADATA_BIT = 0x00000001,
+    VK_SPARSE_MEMORY_BIND_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkSparseMemoryBindFlagBits;
+typedef VkFlags VkSparseMemoryBindFlags;
+
+typedef enum VkSparseImageFormatFlagBits {
+    VK_SPARSE_IMAGE_FORMAT_SINGLE_MIPTAIL_BIT = 0x00000001,
+    VK_SPARSE_IMAGE_FORMAT_ALIGNED_MIP_SIZE_BIT = 0x00000002,
+    VK_SPARSE_IMAGE_FORMAT_NONSTANDARD_BLOCK_SIZE_BIT = 0x00000004,
+    VK_SPARSE_IMAGE_FORMAT_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkSparseImageFormatFlagBits;
+typedef VkFlags VkSparseImageFormatFlags;
+
+typedef enum VkFenceCreateFlagBits {
+    VK_FENCE_CREATE_SIGNALED_BIT = 0x00000001,
+    VK_FENCE_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkFenceCreateFlagBits;
+typedef VkFlags VkFenceCreateFlags;
+typedef VkFlags VkSemaphoreCreateFlags;
+
+typedef enum VkEventCreateFlagBits {
+    VK_EVENT_CREATE_DEVICE_ONLY_BIT_KHR = 0x00000001,
+    VK_EVENT_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkEventCreateFlagBits;
+typedef VkFlags VkEventCreateFlags;
+
+typedef enum VkQueryPipelineStatisticFlagBits {
+    VK_QUERY_PIPELINE_STATISTIC_INPUT_ASSEMBLY_VERTICES_BIT = 0x00000001,
+    VK_QUERY_PIPELINE_STATISTIC_INPUT_ASSEMBLY_PRIMITIVES_BIT = 0x00000002,
+    VK_QUERY_PIPELINE_STATISTIC_VERTEX_SHADER_INVOCATIONS_BIT = 0x00000004,
+    VK_QUERY_PIPELINE_STATISTIC_GEOMETRY_SHADER_INVOCATIONS_BIT = 0x00000008,
+    VK_QUERY_PIPELINE_STATISTIC_GEOMETRY_SHADER_PRIMITIVES_BIT = 0x00000010,
+    VK_QUERY_PIPELINE_STATISTIC_CLIPPING_INVOCATIONS_BIT = 0x00000020,
+    VK_QUERY_PIPELINE_STATISTIC_CLIPPING_PRIMITIVES_BIT = 0x00000040,
+    VK_QUERY_PIPELINE_STATISTIC_FRAGMENT_SHADER_INVOCATIONS_BIT = 0x00000080,
+    VK_QUERY_PIPELINE_STATISTIC_TESSELLATION_CONTROL_SHADER_PATCHES_BIT = 0x00000100,
+    VK_QUERY_PIPELINE_STATISTIC_TESSELLATION_EVALUATION_SHADER_INVOCATIONS_BIT = 0x00000200,
+    VK_QUERY_PIPELINE_STATISTIC_COMPUTE_SHADER_INVOCATIONS_BIT = 0x00000400,
+    VK_QUERY_PIPELINE_STATISTIC_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkQueryPipelineStatisticFlagBits;
+typedef VkFlags VkQueryPipelineStatisticFlags;
+typedef VkFlags VkQueryPoolCreateFlags;
+
+typedef enum VkQueryResultFlagBits {
+    VK_QUERY_RESULT_64_BIT = 0x00000001,
+    VK_QUERY_RESULT_WAIT_BIT = 0x00000002,
+    VK_QUERY_RESULT_WITH_AVAILABILITY_BIT = 0x00000004,
+    VK_QUERY_RESULT_PARTIAL_BIT = 0x00000008,
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_QUERY_RESULT_WITH_STATUS_BIT_KHR = 0x00000010,
+#endif
+    VK_QUERY_RESULT_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkQueryResultFlagBits;
+typedef VkFlags VkQueryResultFlags;
+
+typedef enum VkBufferCreateFlagBits {
+    VK_BUFFER_CREATE_SPARSE_BINDING_BIT = 0x00000001,
+    VK_BUFFER_CREATE_SPARSE_RESIDENCY_BIT = 0x00000002,
+    VK_BUFFER_CREATE_SPARSE_ALIASED_BIT = 0x00000004,
+    VK_BUFFER_CREATE_PROTECTED_BIT = 0x00000008,
+    VK_BUFFER_CREATE_DEVICE_ADDRESS_CAPTURE_REPLAY_BIT = 0x00000010,
+    VK_BUFFER_CREATE_DEVICE_ADDRESS_CAPTURE_REPLAY_BIT_EXT = VK_BUFFER_CREATE_DEVICE_ADDRESS_CAPTURE_REPLAY_BIT,
+    VK_BUFFER_CREATE_DEVICE_ADDRESS_CAPTURE_REPLAY_BIT_KHR = VK_BUFFER_CREATE_DEVICE_ADDRESS_CAPTURE_REPLAY_BIT,
+    VK_BUFFER_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkBufferCreateFlagBits;
+typedef VkFlags VkBufferCreateFlags;
+
+typedef enum VkBufferUsageFlagBits {
+    VK_BUFFER_USAGE_TRANSFER_SRC_BIT = 0x00000001,
+    VK_BUFFER_USAGE_TRANSFER_DST_BIT = 0x00000002,
+    VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT = 0x00000004,
+    VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT = 0x00000008,
+    VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT = 0x00000010,
+    VK_BUFFER_USAGE_STORAGE_BUFFER_BIT = 0x00000020,
+    VK_BUFFER_USAGE_INDEX_BUFFER_BIT = 0x00000040,
+    VK_BUFFER_USAGE_VERTEX_BUFFER_BIT = 0x00000080,
+    VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT = 0x00000100,
+    VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT = 0x00020000,
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_BUFFER_USAGE_VIDEO_DECODE_SRC_BIT_KHR = 0x00002000,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_BUFFER_USAGE_VIDEO_DECODE_DST_BIT_KHR = 0x00004000,
+#endif
+    VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_BUFFER_BIT_EXT = 0x00000800,
+    VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_COUNTER_BUFFER_BIT_EXT = 0x00001000,
+    VK_BUFFER_USAGE_CONDITIONAL_RENDERING_BIT_EXT = 0x00000200,
+    VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR = 0x00080000,
+    VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR = 0x00100000,
+    VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR = 0x00000400,
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_BUFFER_USAGE_VIDEO_ENCODE_DST_BIT_KHR = 0x00008000,
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+    VK_BUFFER_USAGE_VIDEO_ENCODE_SRC_BIT_KHR = 0x00010000,
+#endif
+    VK_BUFFER_USAGE_RAY_TRACING_BIT_NV = VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR,
+    VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT_EXT = VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
+    VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT_KHR = VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
+    VK_BUFFER_USAGE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkBufferUsageFlagBits;
+typedef VkFlags VkBufferUsageFlags;
+typedef VkFlags VkBufferViewCreateFlags;
+
+typedef enum VkImageViewCreateFlagBits {
+    VK_IMAGE_VIEW_CREATE_FRAGMENT_DENSITY_MAP_DYNAMIC_BIT_EXT = 0x00000001,
+    VK_IMAGE_VIEW_CREATE_FRAGMENT_DENSITY_MAP_DEFERRED_BIT_EXT = 0x00000002,
+    VK_IMAGE_VIEW_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkImageViewCreateFlagBits;
+typedef VkFlags VkImageViewCreateFlags;
+typedef VkFlags VkShaderModuleCreateFlags;
+
+typedef enum VkPipelineCacheCreateFlagBits {
+    VK_PIPELINE_CACHE_CREATE_EXTERNALLY_SYNCHRONIZED_BIT_EXT = 0x00000001,
+    VK_PIPELINE_CACHE_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkPipelineCacheCreateFlagBits;
+typedef VkFlags VkPipelineCacheCreateFlags;
+
+typedef enum VkColorComponentFlagBits {
+    VK_COLOR_COMPONENT_R_BIT = 0x00000001,
+    VK_COLOR_COMPONENT_G_BIT = 0x00000002,
+    VK_COLOR_COMPONENT_B_BIT = 0x00000004,
+    VK_COLOR_COMPONENT_A_BIT = 0x00000008,
+    VK_COLOR_COMPONENT_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkColorComponentFlagBits;
+typedef VkFlags VkColorComponentFlags;
+
+typedef enum VkPipelineCreateFlagBits {
+    VK_PIPELINE_CREATE_DISABLE_OPTIMIZATION_BIT = 0x00000001,
+    VK_PIPELINE_CREATE_ALLOW_DERIVATIVES_BIT = 0x00000002,
+    VK_PIPELINE_CREATE_DERIVATIVE_BIT = 0x00000004,
+    VK_PIPELINE_CREATE_VIEW_INDEX_FROM_DEVICE_INDEX_BIT = 0x00000008,
+    VK_PIPELINE_CREATE_DISPATCH_BASE_BIT = 0x00000010,
+    VK_PIPELINE_RASTERIZATION_STATE_CREATE_FRAGMENT_SHADING_RATE_ATTACHMENT_BIT_KHR = 0x00200000,
+    VK_PIPELINE_RASTERIZATION_STATE_CREATE_FRAGMENT_DENSITY_MAP_ATTACHMENT_BIT_EXT = 0x00400000,
+    VK_PIPELINE_CREATE_RAY_TRACING_NO_NULL_ANY_HIT_SHADERS_BIT_KHR = 0x00004000,
+    VK_PIPELINE_CREATE_RAY_TRACING_NO_NULL_CLOSEST_HIT_SHADERS_BIT_KHR = 0x00008000,
+    VK_PIPELINE_CREATE_RAY_TRACING_NO_NULL_MISS_SHADERS_BIT_KHR = 0x00010000,
+    VK_PIPELINE_CREATE_RAY_TRACING_NO_NULL_INTERSECTION_SHADERS_BIT_KHR = 0x00020000,
+    VK_PIPELINE_CREATE_RAY_TRACING_SKIP_TRIANGLES_BIT_KHR = 0x00001000,
+    VK_PIPELINE_CREATE_RAY_TRACING_SKIP_AABBS_BIT_KHR = 0x00002000,
+    VK_PIPELINE_CREATE_RAY_TRACING_SHADER_GROUP_HANDLE_CAPTURE_REPLAY_BIT_KHR = 0x00080000,
+    VK_PIPELINE_CREATE_DEFER_COMPILE_BIT_NV = 0x00000020,
+    VK_PIPELINE_CREATE_CAPTURE_STATISTICS_BIT_KHR = 0x00000040,
+    VK_PIPELINE_CREATE_CAPTURE_INTERNAL_REPRESENTATIONS_BIT_KHR = 0x00000080,
+    VK_PIPELINE_CREATE_INDIRECT_BINDABLE_BIT_NV = 0x00040000,
+    VK_PIPELINE_CREATE_LIBRARY_BIT_KHR = 0x00000800,
+    VK_PIPELINE_CREATE_FAIL_ON_PIPELINE_COMPILE_REQUIRED_BIT_EXT = 0x00000100,
+    VK_PIPELINE_CREATE_EARLY_RETURN_ON_FAILURE_BIT_EXT = 0x00000200,
+    VK_PIPELINE_CREATE_RAY_TRACING_ALLOW_MOTION_BIT_NV = 0x00100000,
+    VK_PIPELINE_CREATE_DISPATCH_BASE = VK_PIPELINE_CREATE_DISPATCH_BASE_BIT,
+    VK_PIPELINE_CREATE_VIEW_INDEX_FROM_DEVICE_INDEX_BIT_KHR = VK_PIPELINE_CREATE_VIEW_INDEX_FROM_DEVICE_INDEX_BIT,
+    VK_PIPELINE_CREATE_DISPATCH_BASE_KHR = VK_PIPELINE_CREATE_DISPATCH_BASE,
+    VK_PIPELINE_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkPipelineCreateFlagBits;
+typedef VkFlags VkPipelineCreateFlags;
+
+typedef enum VkPipelineShaderStageCreateFlagBits {
+    VK_PIPELINE_SHADER_STAGE_CREATE_ALLOW_VARYING_SUBGROUP_SIZE_BIT_EXT = 0x00000001,
+    VK_PIPELINE_SHADER_STAGE_CREATE_REQUIRE_FULL_SUBGROUPS_BIT_EXT = 0x00000002,
+    VK_PIPELINE_SHADER_STAGE_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkPipelineShaderStageCreateFlagBits;
+typedef VkFlags VkPipelineShaderStageCreateFlags;
+
+typedef enum VkShaderStageFlagBits {
+    VK_SHADER_STAGE_VERTEX_BIT = 0x00000001,
+    VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT = 0x00000002,
+    VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT = 0x00000004,
+    VK_SHADER_STAGE_GEOMETRY_BIT = 0x00000008,
+    VK_SHADER_STAGE_FRAGMENT_BIT = 0x00000010,
+    VK_SHADER_STAGE_COMPUTE_BIT = 0x00000020,
+    VK_SHADER_STAGE_ALL_GRAPHICS = 0x0000001F,
+    VK_SHADER_STAGE_ALL = 0x7FFFFFFF,
+    VK_SHADER_STAGE_RAYGEN_BIT_KHR = 0x00000100,
+    VK_SHADER_STAGE_ANY_HIT_BIT_KHR = 0x00000200,
+    VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR = 0x00000400,
+    VK_SHADER_STAGE_MISS_BIT_KHR = 0x00000800,
+    VK_SHADER_STAGE_INTERSECTION_BIT_KHR = 0x00001000,
+    VK_SHADER_STAGE_CALLABLE_BIT_KHR = 0x00002000,
+    VK_SHADER_STAGE_TASK_BIT_NV = 0x00000040,
+    VK_SHADER_STAGE_MESH_BIT_NV = 0x00000080,
+    VK_SHADER_STAGE_SUBPASS_SHADING_BIT_HUAWEI = 0x00004000,
+    VK_SHADER_STAGE_RAYGEN_BIT_NV = VK_SHADER_STAGE_RAYGEN_BIT_KHR,
+    VK_SHADER_STAGE_ANY_HIT_BIT_NV = VK_SHADER_STAGE_ANY_HIT_BIT_KHR,
+    VK_SHADER_STAGE_CLOSEST_HIT_BIT_NV = VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR,
+    VK_SHADER_STAGE_MISS_BIT_NV = VK_SHADER_STAGE_MISS_BIT_KHR,
+    VK_SHADER_STAGE_INTERSECTION_BIT_NV = VK_SHADER_STAGE_INTERSECTION_BIT_KHR,
+    VK_SHADER_STAGE_CALLABLE_BIT_NV = VK_SHADER_STAGE_CALLABLE_BIT_KHR,
+    VK_SHADER_STAGE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkShaderStageFlagBits;
+
+typedef enum VkCullModeFlagBits {
+    VK_CULL_MODE_NONE = 0,
+    VK_CULL_MODE_FRONT_BIT = 0x00000001,
+    VK_CULL_MODE_BACK_BIT = 0x00000002,
+    VK_CULL_MODE_FRONT_AND_BACK = 0x00000003,
+    VK_CULL_MODE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkCullModeFlagBits;
+typedef VkFlags VkCullModeFlags;
+typedef VkFlags VkPipelineVertexInputStateCreateFlags;
+typedef VkFlags VkPipelineInputAssemblyStateCreateFlags;
+typedef VkFlags VkPipelineTessellationStateCreateFlags;
+typedef VkFlags VkPipelineViewportStateCreateFlags;
+typedef VkFlags VkPipelineRasterizationStateCreateFlags;
+typedef VkFlags VkPipelineMultisampleStateCreateFlags;
+typedef VkFlags VkPipelineDepthStencilStateCreateFlags;
+typedef VkFlags VkPipelineColorBlendStateCreateFlags;
+typedef VkFlags VkPipelineDynamicStateCreateFlags;
+typedef VkFlags VkPipelineLayoutCreateFlags;
+typedef VkFlags VkShaderStageFlags;
+
+typedef enum VkSamplerCreateFlagBits {
+    VK_SAMPLER_CREATE_SUBSAMPLED_BIT_EXT = 0x00000001,
+    VK_SAMPLER_CREATE_SUBSAMPLED_COARSE_RECONSTRUCTION_BIT_EXT = 0x00000002,
+    VK_SAMPLER_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkSamplerCreateFlagBits;
+typedef VkFlags VkSamplerCreateFlags;
+
+typedef enum VkDescriptorPoolCreateFlagBits {
+    VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT = 0x00000001,
+    VK_DESCRIPTOR_POOL_CREATE_UPDATE_AFTER_BIND_BIT = 0x00000002,
+    VK_DESCRIPTOR_POOL_CREATE_HOST_ONLY_BIT_VALVE = 0x00000004,
+    VK_DESCRIPTOR_POOL_CREATE_UPDATE_AFTER_BIND_BIT_EXT = VK_DESCRIPTOR_POOL_CREATE_UPDATE_AFTER_BIND_BIT,
+    VK_DESCRIPTOR_POOL_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkDescriptorPoolCreateFlagBits;
+typedef VkFlags VkDescriptorPoolCreateFlags;
+typedef VkFlags VkDescriptorPoolResetFlags;
+
+typedef enum VkDescriptorSetLayoutCreateFlagBits {
+    VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT = 0x00000002,
+    VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT_KHR = 0x00000001,
+    VK_DESCRIPTOR_SET_LAYOUT_CREATE_HOST_ONLY_POOL_BIT_VALVE = 0x00000004,
+    VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT_EXT = VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT,
+    VK_DESCRIPTOR_SET_LAYOUT_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkDescriptorSetLayoutCreateFlagBits;
+typedef VkFlags VkDescriptorSetLayoutCreateFlags;
+
+typedef enum VkAttachmentDescriptionFlagBits {
+    VK_ATTACHMENT_DESCRIPTION_MAY_ALIAS_BIT = 0x00000001,
+    VK_ATTACHMENT_DESCRIPTION_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkAttachmentDescriptionFlagBits;
+typedef VkFlags VkAttachmentDescriptionFlags;
+
+typedef enum VkDependencyFlagBits {
+    VK_DEPENDENCY_BY_REGION_BIT = 0x00000001,
+    VK_DEPENDENCY_DEVICE_GROUP_BIT = 0x00000004,
+    VK_DEPENDENCY_VIEW_LOCAL_BIT = 0x00000002,
+    VK_DEPENDENCY_VIEW_LOCAL_BIT_KHR = VK_DEPENDENCY_VIEW_LOCAL_BIT,
+    VK_DEPENDENCY_DEVICE_GROUP_BIT_KHR = VK_DEPENDENCY_DEVICE_GROUP_BIT,
+    VK_DEPENDENCY_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkDependencyFlagBits;
+typedef VkFlags VkDependencyFlags;
+
+typedef enum VkFramebufferCreateFlagBits {
+    VK_FRAMEBUFFER_CREATE_IMAGELESS_BIT = 0x00000001,
+    VK_FRAMEBUFFER_CREATE_IMAGELESS_BIT_KHR = VK_FRAMEBUFFER_CREATE_IMAGELESS_BIT,
+    VK_FRAMEBUFFER_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkFramebufferCreateFlagBits;
+typedef VkFlags VkFramebufferCreateFlags;
+
+typedef enum VkRenderPassCreateFlagBits {
+    VK_RENDER_PASS_CREATE_TRANSFORM_BIT_QCOM = 0x00000002,
+    VK_RENDER_PASS_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkRenderPassCreateFlagBits;
+typedef VkFlags VkRenderPassCreateFlags;
+
+typedef enum VkSubpassDescriptionFlagBits {
+    VK_SUBPASS_DESCRIPTION_PER_VIEW_ATTRIBUTES_BIT_NVX = 0x00000001,
+    VK_SUBPASS_DESCRIPTION_PER_VIEW_POSITION_X_ONLY_BIT_NVX = 0x00000002,
+    VK_SUBPASS_DESCRIPTION_FRAGMENT_REGION_BIT_QCOM = 0x00000004,
+    VK_SUBPASS_DESCRIPTION_SHADER_RESOLVE_BIT_QCOM = 0x00000008,
+    VK_SUBPASS_DESCRIPTION_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkSubpassDescriptionFlagBits;
+typedef VkFlags VkSubpassDescriptionFlags;
+
+typedef enum VkCommandPoolCreateFlagBits {
+    VK_COMMAND_POOL_CREATE_TRANSIENT_BIT = 0x00000001,
+    VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT = 0x00000002,
+    VK_COMMAND_POOL_CREATE_PROTECTED_BIT = 0x00000004,
+    VK_COMMAND_POOL_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkCommandPoolCreateFlagBits;
+typedef VkFlags VkCommandPoolCreateFlags;
+
+typedef enum VkCommandPoolResetFlagBits {
+    VK_COMMAND_POOL_RESET_RELEASE_RESOURCES_BIT = 0x00000001,
+    VK_COMMAND_POOL_RESET_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkCommandPoolResetFlagBits;
+typedef VkFlags VkCommandPoolResetFlags;
+
+typedef enum VkCommandBufferUsageFlagBits {
+    VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT = 0x00000001,
+    VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT = 0x00000002,
+    VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT = 0x00000004,
+    VK_COMMAND_BUFFER_USAGE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkCommandBufferUsageFlagBits;
+typedef VkFlags VkCommandBufferUsageFlags;
+
+typedef enum VkQueryControlFlagBits {
+    VK_QUERY_CONTROL_PRECISE_BIT = 0x00000001,
+    VK_QUERY_CONTROL_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkQueryControlFlagBits;
+typedef VkFlags VkQueryControlFlags;
+
+typedef enum VkCommandBufferResetFlagBits {
+    VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BIT = 0x00000001,
+    VK_COMMAND_BUFFER_RESET_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkCommandBufferResetFlagBits;
+typedef VkFlags VkCommandBufferResetFlags;
+
+typedef enum VkStencilFaceFlagBits {
+    VK_STENCIL_FACE_FRONT_BIT = 0x00000001,
+    VK_STENCIL_FACE_BACK_BIT = 0x00000002,
+    VK_STENCIL_FACE_FRONT_AND_BACK = 0x00000003,
+    VK_STENCIL_FRONT_AND_BACK = VK_STENCIL_FACE_FRONT_AND_BACK,
+    VK_STENCIL_FACE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkStencilFaceFlagBits;
+typedef VkFlags VkStencilFaceFlags;
+typedef struct VkExtent2D {
+    uint32_t    width;
+    uint32_t    height;
+} VkExtent2D;
+
+typedef struct VkExtent3D {
+    uint32_t    width;
+    uint32_t    height;
+    uint32_t    depth;
+} VkExtent3D;
+
+typedef struct VkOffset2D {
+    int32_t    x;
+    int32_t    y;
+} VkOffset2D;
+
+typedef struct VkOffset3D {
+    int32_t    x;
+    int32_t    y;
+    int32_t    z;
+} VkOffset3D;
+
+typedef struct VkRect2D {
+    VkOffset2D    offset;
+    VkExtent2D    extent;
+} VkRect2D;
+
+typedef struct VkBaseInStructure {
+    VkStructureType                    sType;
+    const struct VkBaseInStructure*    pNext;
+} VkBaseInStructure;
+
+typedef struct VkBaseOutStructure {
+    VkStructureType               sType;
+    struct VkBaseOutStructure*    pNext;
+} VkBaseOutStructure;
+
+typedef struct VkBufferMemoryBarrier {
+    VkStructureType    sType;
+    const void*        pNext;
+    VkAccessFlags      srcAccessMask;
+    VkAccessFlags      dstAccessMask;
+    uint32_t           srcQueueFamilyIndex;
+    uint32_t           dstQueueFamilyIndex;
+    VkBuffer           buffer;
+    VkDeviceSize       offset;
+    VkDeviceSize       size;
+} VkBufferMemoryBarrier;
+
+typedef struct VkDispatchIndirectCommand {
+    uint32_t    x;
+    uint32_t    y;
+    uint32_t    z;
+} VkDispatchIndirectCommand;
+
+typedef struct VkDrawIndexedIndirectCommand {
+    uint32_t    indexCount;
+    uint32_t    instanceCount;
+    uint32_t    firstIndex;
+    int32_t     vertexOffset;
+    uint32_t    firstInstance;
+} VkDrawIndexedIndirectCommand;
+
+typedef struct VkDrawIndirectCommand {
+    uint32_t    vertexCount;
+    uint32_t    instanceCount;
+    uint32_t    firstVertex;
+    uint32_t    firstInstance;
+} VkDrawIndirectCommand;
+
+typedef struct VkImageSubresourceRange {
+    VkImageAspectFlags    aspectMask;
+    uint32_t              baseMipLevel;
+    uint32_t              levelCount;
+    uint32_t              baseArrayLayer;
+    uint32_t              layerCount;
+} VkImageSubresourceRange;
+
+typedef struct VkImageMemoryBarrier {
+    VkStructureType            sType;
+    const void*                pNext;
+    VkAccessFlags              srcAccessMask;
+    VkAccessFlags              dstAccessMask;
+    VkImageLayout              oldLayout;
+    VkImageLayout              newLayout;
+    uint32_t                   srcQueueFamilyIndex;
+    uint32_t                   dstQueueFamilyIndex;
+    VkImage                    image;
+    VkImageSubresourceRange    subresourceRange;
+} VkImageMemoryBarrier;
+
+typedef struct VkMemoryBarrier {
+    VkStructureType    sType;
+    const void*        pNext;
+    VkAccessFlags      srcAccessMask;
+    VkAccessFlags      dstAccessMask;
+} VkMemoryBarrier;
+
+typedef struct VkPipelineCacheHeaderVersionOne {
+    uint32_t                        headerSize;
+    VkPipelineCacheHeaderVersion    headerVersion;
+    uint32_t                        vendorID;
+    uint32_t                        deviceID;
+    uint8_t                         pipelineCacheUUID[VK_UUID_SIZE];
+} VkPipelineCacheHeaderVersionOne;
+
+typedef void* (VKAPI_PTR *PFN_vkAllocationFunction)(
+    void*                                       pUserData,
+    size_t                                      size,
+    size_t                                      alignment,
+    VkSystemAllocationScope                     allocationScope);
+
+typedef void (VKAPI_PTR *PFN_vkFreeFunction)(
+    void*                                       pUserData,
+    void*                                       pMemory);
+
+typedef void (VKAPI_PTR *PFN_vkInternalAllocationNotification)(
+    void*                                       pUserData,
+    size_t                                      size,
+    VkInternalAllocationType                    allocationType,
+    VkSystemAllocationScope                     allocationScope);
+
+typedef void (VKAPI_PTR *PFN_vkInternalFreeNotification)(
+    void*                                       pUserData,
+    size_t                                      size,
+    VkInternalAllocationType                    allocationType,
+    VkSystemAllocationScope                     allocationScope);
+
+typedef void* (VKAPI_PTR *PFN_vkReallocationFunction)(
+    void*                                       pUserData,
+    void*                                       pOriginal,
+    size_t                                      size,
+    size_t                                      alignment,
+    VkSystemAllocationScope                     allocationScope);
+
+typedef void (VKAPI_PTR *PFN_vkVoidFunction)(void);
+typedef struct VkAllocationCallbacks {
+    void*                                   pUserData;
+    PFN_vkAllocationFunction                pfnAllocation;
+    PFN_vkReallocationFunction              pfnReallocation;
+    PFN_vkFreeFunction                      pfnFree;
+    PFN_vkInternalAllocationNotification    pfnInternalAllocation;
+    PFN_vkInternalFreeNotification          pfnInternalFree;
+} VkAllocationCallbacks;
+
+typedef struct VkApplicationInfo {
+    VkStructureType    sType;
+    const void*        pNext;
+    const char*        pApplicationName;
+    uint32_t           applicationVersion;
+    const char*        pEngineName;
+    uint32_t           engineVersion;
+    uint32_t           apiVersion;
+} VkApplicationInfo;
+
+typedef struct VkFormatProperties {
+    VkFormatFeatureFlags    linearTilingFeatures;
+    VkFormatFeatureFlags    optimalTilingFeatures;
+    VkFormatFeatureFlags    bufferFeatures;
+} VkFormatProperties;
+
+typedef struct VkImageFormatProperties {
+    VkExtent3D            maxExtent;
+    uint32_t              maxMipLevels;
+    uint32_t              maxArrayLayers;
+    VkSampleCountFlags    sampleCounts;
+    VkDeviceSize          maxResourceSize;
+} VkImageFormatProperties;
+
+typedef struct VkInstanceCreateInfo {
+    VkStructureType             sType;
+    const void*                 pNext;
+    VkInstanceCreateFlags       flags;
+    const VkApplicationInfo*    pApplicationInfo;
+    uint32_t                    enabledLayerCount;
+    const char* const*          ppEnabledLayerNames;
+    uint32_t                    enabledExtensionCount;
+    const char* const*          ppEnabledExtensionNames;
+} VkInstanceCreateInfo;
+
+typedef struct VkMemoryHeap {
+    VkDeviceSize         size;
+    VkMemoryHeapFlags    flags;
+} VkMemoryHeap;
+
+typedef struct VkMemoryType {
+    VkMemoryPropertyFlags    propertyFlags;
+    uint32_t                 heapIndex;
+} VkMemoryType;
+
+typedef struct VkPhysicalDeviceFeatures {
+    VkBool32    robustBufferAccess;
+    VkBool32    fullDrawIndexUint32;
+    VkBool32    imageCubeArray;
+    VkBool32    independentBlend;
+    VkBool32    geometryShader;
+    VkBool32    tessellationShader;
+    VkBool32    sampleRateShading;
+    VkBool32    dualSrcBlend;
+    VkBool32    logicOp;
+    VkBool32    multiDrawIndirect;
+    VkBool32    drawIndirectFirstInstance;
+    VkBool32    depthClamp;
+    VkBool32    depthBiasClamp;
+    VkBool32    fillModeNonSolid;
+    VkBool32    depthBounds;
+    VkBool32    wideLines;
+    VkBool32    largePoints;
+    VkBool32    alphaToOne;
+    VkBool32    multiViewport;
+    VkBool32    samplerAnisotropy;
+    VkBool32    textureCompressionETC2;
+    VkBool32    textureCompressionASTC_LDR;
+    VkBool32    textureCompressionBC;
+    VkBool32    occlusionQueryPrecise;
+    VkBool32    pipelineStatisticsQuery;
+    VkBool32    vertexPipelineStoresAndAtomics;
+    VkBool32    fragmentStoresAndAtomics;
+    VkBool32    shaderTessellationAndGeometryPointSize;
+    VkBool32    shaderImageGatherExtended;
+    VkBool32    shaderStorageImageExtendedFormats;
+    VkBool32    shaderStorageImageMultisample;
+    VkBool32    shaderStorageImageReadWithoutFormat;
+    VkBool32    shaderStorageImageWriteWithoutFormat;
+    VkBool32    shaderUniformBufferArrayDynamicIndexing;
+    VkBool32    shaderSampledImageArrayDynamicIndexing;
+    VkBool32    shaderStorageBufferArrayDynamicIndexing;
+    VkBool32    shaderStorageImageArrayDynamicIndexing;
+    VkBool32    shaderClipDistance;
+    VkBool32    shaderCullDistance;
+    VkBool32    shaderFloat64;
+    VkBool32    shaderInt64;
+    VkBool32    shaderInt16;
+    VkBool32    shaderResourceResidency;
+    VkBool32    shaderResourceMinLod;
+    VkBool32    sparseBinding;
+    VkBool32    sparseResidencyBuffer;
+    VkBool32    sparseResidencyImage2D;
+    VkBool32    sparseResidencyImage3D;
+    VkBool32    sparseResidency2Samples;
+    VkBool32    sparseResidency4Samples;
+    VkBool32    sparseResidency8Samples;
+    VkBool32    sparseResidency16Samples;
+    VkBool32    sparseResidencyAliased;
+    VkBool32    variableMultisampleRate;
+    VkBool32    inheritedQueries;
+} VkPhysicalDeviceFeatures;
+
+typedef struct VkPhysicalDeviceLimits {
+    uint32_t              maxImageDimension1D;
+    uint32_t              maxImageDimension2D;
+    uint32_t              maxImageDimension3D;
+    uint32_t              maxImageDimensionCube;
+    uint32_t              maxImageArrayLayers;
+    uint32_t              maxTexelBufferElements;
+    uint32_t              maxUniformBufferRange;
+    uint32_t              maxStorageBufferRange;
+    uint32_t              maxPushConstantsSize;
+    uint32_t              maxMemoryAllocationCount;
+    uint32_t              maxSamplerAllocationCount;
+    VkDeviceSize          bufferImageGranularity;
+    VkDeviceSize          sparseAddressSpaceSize;
+    uint32_t              maxBoundDescriptorSets;
+    uint32_t              maxPerStageDescriptorSamplers;
+    uint32_t              maxPerStageDescriptorUniformBuffers;
+    uint32_t              maxPerStageDescriptorStorageBuffers;
+    uint32_t              maxPerStageDescriptorSampledImages;
+    uint32_t              maxPerStageDescriptorStorageImages;
+    uint32_t              maxPerStageDescriptorInputAttachments;
+    uint32_t              maxPerStageResources;
+    uint32_t              maxDescriptorSetSamplers;
+    uint32_t              maxDescriptorSetUniformBuffers;
+    uint32_t              maxDescriptorSetUniformBuffersDynamic;
+    uint32_t              maxDescriptorSetStorageBuffers;
+    uint32_t              maxDescriptorSetStorageBuffersDynamic;
+    uint32_t              maxDescriptorSetSampledImages;
+    uint32_t              maxDescriptorSetStorageImages;
+    uint32_t              maxDescriptorSetInputAttachments;
+    uint32_t              maxVertexInputAttributes;
+    uint32_t              maxVertexInputBindings;
+    uint32_t              maxVertexInputAttributeOffset;
+    uint32_t              maxVertexInputBindingStride;
+    uint32_t              maxVertexOutputComponents;
+    uint32_t              maxTessellationGenerationLevel;
+    uint32_t              maxTessellationPatchSize;
+    uint32_t              maxTessellationControlPerVertexInputComponents;
+    uint32_t              maxTessellationControlPerVertexOutputComponents;
+    uint32_t              maxTessellationControlPerPatchOutputComponents;
+    uint32_t              maxTessellationControlTotalOutputComponents;
+    uint32_t              maxTessellationEvaluationInputComponents;
+    uint32_t              maxTessellationEvaluationOutputComponents;
+    uint32_t              maxGeometryShaderInvocations;
+    uint32_t              maxGeometryInputComponents;
+    uint32_t              maxGeometryOutputComponents;
+    uint32_t              maxGeometryOutputVertices;
+    uint32_t              maxGeometryTotalOutputComponents;
+    uint32_t              maxFragmentInputComponents;
+    uint32_t              maxFragmentOutputAttachments;
+    uint32_t              maxFragmentDualSrcAttachments;
+    uint32_t              maxFragmentCombinedOutputResources;
+    uint32_t              maxComputeSharedMemorySize;
+    uint32_t              maxComputeWorkGroupCount[3];
+    uint32_t              maxComputeWorkGroupInvocations;
+    uint32_t              maxComputeWorkGroupSize[3];
+    uint32_t              subPixelPrecisionBits;
+    uint32_t              subTexelPrecisionBits;
+    uint32_t              mipmapPrecisionBits;
+    uint32_t              maxDrawIndexedIndexValue;
+    uint32_t              maxDrawIndirectCount;
+    float                 maxSamplerLodBias;
+    float                 maxSamplerAnisotropy;
+    uint32_t              maxViewports;
+    uint32_t              maxViewportDimensions[2];
+    float                 viewportBoundsRange[2];
+    uint32_t              viewportSubPixelBits;
+    size_t                minMemoryMapAlignment;
+    VkDeviceSize          minTexelBufferOffsetAlignment;
+    VkDeviceSize          minUniformBufferOffsetAlignment;
+    VkDeviceSize          minStorageBufferOffsetAlignment;
+    int32_t               minTexelOffset;
+    uint32_t              maxTexelOffset;
+    int32_t               minTexelGatherOffset;
+    uint32_t              maxTexelGatherOffset;
+    float                 minInterpolationOffset;
+    float                 maxInterpolationOffset;
+    uint32_t              subPixelInterpolationOffsetBits;
+    uint32_t              maxFramebufferWidth;
+    uint32_t              maxFramebufferHeight;
+    uint32_t              maxFramebufferLayers;
+    VkSampleCountFlags    framebufferColorSampleCounts;
+    VkSampleCountFlags    framebufferDepthSampleCounts;
+    VkSampleCountFlags    framebufferStencilSampleCounts;
+    VkSampleCountFlags    framebufferNoAttachmentsSampleCounts;
+    uint32_t              maxColorAttachments;
+    VkSampleCountFlags    sampledImageColorSampleCounts;
+    VkSampleCountFlags    sampledImageIntegerSampleCounts;
+    VkSampleCountFlags    sampledImageDepthSampleCounts;
+    VkSampleCountFlags    sampledImageStencilSampleCounts;
+    VkSampleCountFlags    storageImageSampleCounts;
+    uint32_t              maxSampleMaskWords;
+    VkBool32              timestampComputeAndGraphics;
+    float                 timestampPeriod;
+    uint32_t              maxClipDistances;
+    uint32_t              maxCullDistances;
+    uint32_t              maxCombinedClipAndCullDistances;
+    uint32_t              discreteQueuePriorities;
+    float                 pointSizeRange[2];
+    float                 lineWidthRange[2];
+    float                 pointSizeGranularity;
+    float                 lineWidthGranularity;
+    VkBool32              strictLines;
+    VkBool32              standardSampleLocations;
+    VkDeviceSize          optimalBufferCopyOffsetAlignment;
+    VkDeviceSize          optimalBufferCopyRowPitchAlignment;
+    VkDeviceSize          nonCoherentAtomSize;
+} VkPhysicalDeviceLimits;
+
+typedef struct VkPhysicalDeviceMemoryProperties {
+    uint32_t        memoryTypeCount;
+    VkMemoryType    memoryTypes[VK_MAX_MEMORY_TYPES];
+    uint32_t        memoryHeapCount;
+    VkMemoryHeap    memoryHeaps[VK_MAX_MEMORY_HEAPS];
+} VkPhysicalDeviceMemoryProperties;
+
+typedef struct VkPhysicalDeviceSparseProperties {
+    VkBool32    residencyStandard2DBlockShape;
+    VkBool32    residencyStandard2DMultisampleBlockShape;
+    VkBool32    residencyStandard3DBlockShape;
+    VkBool32    residencyAlignedMipSize;
+    VkBool32    residencyNonResidentStrict;
+} VkPhysicalDeviceSparseProperties;
+
+typedef struct VkPhysicalDeviceProperties {
+    uint32_t                            apiVersion;
+    uint32_t                            driverVersion;
+    uint32_t                            vendorID;
+    uint32_t                            deviceID;
+    VkPhysicalDeviceType                deviceType;
+    char                                deviceName[VK_MAX_PHYSICAL_DEVICE_NAME_SIZE];
+    uint8_t                             pipelineCacheUUID[VK_UUID_SIZE];
+    VkPhysicalDeviceLimits              limits;
+    VkPhysicalDeviceSparseProperties    sparseProperties;
+} VkPhysicalDeviceProperties;
+
+typedef struct VkQueueFamilyProperties {
+    VkQueueFlags    queueFlags;
+    uint32_t        queueCount;
+    uint32_t        timestampValidBits;
+    VkExtent3D      minImageTransferGranularity;
+} VkQueueFamilyProperties;
+
+typedef struct VkDeviceQueueCreateInfo {
+    VkStructureType             sType;
+    const void*                 pNext;
+    VkDeviceQueueCreateFlags    flags;
+    uint32_t                    queueFamilyIndex;
+    uint32_t                    queueCount;
+    const float*                pQueuePriorities;
+} VkDeviceQueueCreateInfo;
+
+typedef struct VkDeviceCreateInfo {
+    VkStructureType                    sType;
+    const void*                        pNext;
+    VkDeviceCreateFlags                flags;
+    uint32_t                           queueCreateInfoCount;
+    const VkDeviceQueueCreateInfo*     pQueueCreateInfos;
+    uint32_t                           enabledLayerCount;
+    const char* const*                 ppEnabledLayerNames;
+    uint32_t                           enabledExtensionCount;
+    const char* const*                 ppEnabledExtensionNames;
+    const VkPhysicalDeviceFeatures*    pEnabledFeatures;
+} VkDeviceCreateInfo;
+
+typedef struct VkExtensionProperties {
+    char        extensionName[VK_MAX_EXTENSION_NAME_SIZE];
+    uint32_t    specVersion;
+} VkExtensionProperties;
+
+typedef struct VkLayerProperties {
+    char        layerName[VK_MAX_EXTENSION_NAME_SIZE];
+    uint32_t    specVersion;
+    uint32_t    implementationVersion;
+    char        description[VK_MAX_DESCRIPTION_SIZE];
+} VkLayerProperties;
+
+typedef struct VkSubmitInfo {
+    VkStructureType                sType;
+    const void*                    pNext;
+    uint32_t                       waitSemaphoreCount;
+    const VkSemaphore*             pWaitSemaphores;
+    const VkPipelineStageFlags*    pWaitDstStageMask;
+    uint32_t                       commandBufferCount;
+    const VkCommandBuffer*         pCommandBuffers;
+    uint32_t                       signalSemaphoreCount;
+    const VkSemaphore*             pSignalSemaphores;
+} VkSubmitInfo;
+
+typedef struct VkMappedMemoryRange {
+    VkStructureType    sType;
+    const void*        pNext;
+    VkDeviceMemory     memory;
+    VkDeviceSize       offset;
+    VkDeviceSize       size;
+} VkMappedMemoryRange;
+
+typedef struct VkMemoryAllocateInfo {
+    VkStructureType    sType;
+    const void*        pNext;
+    VkDeviceSize       allocationSize;
+    uint32_t           memoryTypeIndex;
+} VkMemoryAllocateInfo;
+
+typedef struct VkMemoryRequirements {
+    VkDeviceSize    size;
+    VkDeviceSize    alignment;
+    uint32_t        memoryTypeBits;
+} VkMemoryRequirements;
+
+typedef struct VkSparseMemoryBind {
+    VkDeviceSize               resourceOffset;
+    VkDeviceSize               size;
+    VkDeviceMemory             memory;
+    VkDeviceSize               memoryOffset;
+    VkSparseMemoryBindFlags    flags;
+} VkSparseMemoryBind;
+
+typedef struct VkSparseBufferMemoryBindInfo {
+    VkBuffer                     buffer;
+    uint32_t                     bindCount;
+    const VkSparseMemoryBind*    pBinds;
+} VkSparseBufferMemoryBindInfo;
+
+typedef struct VkSparseImageOpaqueMemoryBindInfo {
+    VkImage                      image;
+    uint32_t                     bindCount;
+    const VkSparseMemoryBind*    pBinds;
+} VkSparseImageOpaqueMemoryBindInfo;
+
+typedef struct VkImageSubresource {
+    VkImageAspectFlags    aspectMask;
+    uint32_t              mipLevel;
+    uint32_t              arrayLayer;
+} VkImageSubresource;
+
+typedef struct VkSparseImageMemoryBind {
+    VkImageSubresource         subresource;
+    VkOffset3D                 offset;
+    VkExtent3D                 extent;
+    VkDeviceMemory             memory;
+    VkDeviceSize               memoryOffset;
+    VkSparseMemoryBindFlags    flags;
+} VkSparseImageMemoryBind;
+
+typedef struct VkSparseImageMemoryBindInfo {
+    VkImage                           image;
+    uint32_t                          bindCount;
+    const VkSparseImageMemoryBind*    pBinds;
+} VkSparseImageMemoryBindInfo;
+
+typedef struct VkBindSparseInfo {
+    VkStructureType                             sType;
+    const void*                                 pNext;
+    uint32_t                                    waitSemaphoreCount;
+    const VkSemaphore*                          pWaitSemaphores;
+    uint32_t                                    bufferBindCount;
+    const VkSparseBufferMemoryBindInfo*         pBufferBinds;
+    uint32_t                                    imageOpaqueBindCount;
+    const VkSparseImageOpaqueMemoryBindInfo*    pImageOpaqueBinds;
+    uint32_t                                    imageBindCount;
+    const VkSparseImageMemoryBindInfo*          pImageBinds;
+    uint32_t                                    signalSemaphoreCount;
+    const VkSemaphore*                          pSignalSemaphores;
+} VkBindSparseInfo;
+
+typedef struct VkSparseImageFormatProperties {
+    VkImageAspectFlags          aspectMask;
+    VkExtent3D                  imageGranularity;
+    VkSparseImageFormatFlags    flags;
+} VkSparseImageFormatProperties;
+
+typedef struct VkSparseImageMemoryRequirements {
+    VkSparseImageFormatProperties    formatProperties;
+    uint32_t                         imageMipTailFirstLod;
+    VkDeviceSize                     imageMipTailSize;
+    VkDeviceSize                     imageMipTailOffset;
+    VkDeviceSize                     imageMipTailStride;
+} VkSparseImageMemoryRequirements;
+
+typedef struct VkFenceCreateInfo {
+    VkStructureType       sType;
+    const void*           pNext;
+    VkFenceCreateFlags    flags;
+} VkFenceCreateInfo;
+
+typedef struct VkSemaphoreCreateInfo {
+    VkStructureType           sType;
+    const void*               pNext;
+    VkSemaphoreCreateFlags    flags;
+} VkSemaphoreCreateInfo;
+
+typedef struct VkEventCreateInfo {
+    VkStructureType       sType;
+    const void*           pNext;
+    VkEventCreateFlags    flags;
+} VkEventCreateInfo;
+
+typedef struct VkQueryPoolCreateInfo {
+    VkStructureType                  sType;
+    const void*                      pNext;
+    VkQueryPoolCreateFlags           flags;
+    VkQueryType                      queryType;
+    uint32_t                         queryCount;
+    VkQueryPipelineStatisticFlags    pipelineStatistics;
+} VkQueryPoolCreateInfo;
+
+typedef struct VkBufferCreateInfo {
+    VkStructureType        sType;
+    const void*            pNext;
+    VkBufferCreateFlags    flags;
+    VkDeviceSize           size;
+    VkBufferUsageFlags     usage;
+    VkSharingMode          sharingMode;
+    uint32_t               queueFamilyIndexCount;
+    const uint32_t*        pQueueFamilyIndices;
+} VkBufferCreateInfo;
+
+typedef struct VkBufferViewCreateInfo {
+    VkStructureType            sType;
+    const void*                pNext;
+    VkBufferViewCreateFlags    flags;
+    VkBuffer                   buffer;
+    VkFormat                   format;
+    VkDeviceSize               offset;
+    VkDeviceSize               range;
+} VkBufferViewCreateInfo;
+
+typedef struct VkImageCreateInfo {
+    VkStructureType          sType;
+    const void*              pNext;
+    VkImageCreateFlags       flags;
+    VkImageType              imageType;
+    VkFormat                 format;
+    VkExtent3D               extent;
+    uint32_t                 mipLevels;
+    uint32_t                 arrayLayers;
+    VkSampleCountFlagBits    samples;
+    VkImageTiling            tiling;
+    VkImageUsageFlags        usage;
+    VkSharingMode            sharingMode;
+    uint32_t                 queueFamilyIndexCount;
+    const uint32_t*          pQueueFamilyIndices;
+    VkImageLayout            initialLayout;
+} VkImageCreateInfo;
+
+typedef struct VkSubresourceLayout {
+    VkDeviceSize    offset;
+    VkDeviceSize    size;
+    VkDeviceSize    rowPitch;
+    VkDeviceSize    arrayPitch;
+    VkDeviceSize    depthPitch;
+} VkSubresourceLayout;
+
+typedef struct VkComponentMapping {
+    VkComponentSwizzle    r;
+    VkComponentSwizzle    g;
+    VkComponentSwizzle    b;
+    VkComponentSwizzle    a;
+} VkComponentMapping;
+
+typedef struct VkImageViewCreateInfo {
+    VkStructureType            sType;
+    const void*                pNext;
+    VkImageViewCreateFlags     flags;
+    VkImage                    image;
+    VkImageViewType            viewType;
+    VkFormat                   format;
+    VkComponentMapping         components;
+    VkImageSubresourceRange    subresourceRange;
+} VkImageViewCreateInfo;
+
+typedef struct VkShaderModuleCreateInfo {
+    VkStructureType              sType;
+    const void*                  pNext;
+    VkShaderModuleCreateFlags    flags;
+    size_t                       codeSize;
+    const uint32_t*              pCode;
+} VkShaderModuleCreateInfo;
+
+typedef struct VkPipelineCacheCreateInfo {
+    VkStructureType               sType;
+    const void*                   pNext;
+    VkPipelineCacheCreateFlags    flags;
+    size_t                        initialDataSize;
+    const void*                   pInitialData;
+} VkPipelineCacheCreateInfo;
+
+typedef struct VkSpecializationMapEntry {
+    uint32_t    constantID;
+    uint32_t    offset;
+    size_t      size;
+} VkSpecializationMapEntry;
+
+typedef struct VkSpecializationInfo {
+    uint32_t                           mapEntryCount;
+    const VkSpecializationMapEntry*    pMapEntries;
+    size_t                             dataSize;
+    const void*                        pData;
+} VkSpecializationInfo;
+
+typedef struct VkPipelineShaderStageCreateInfo {
+    VkStructureType                     sType;
+    const void*                         pNext;
+    VkPipelineShaderStageCreateFlags    flags;
+    VkShaderStageFlagBits               stage;
+    VkShaderModule                      module;
+    const char*                         pName;
+    const VkSpecializationInfo*         pSpecializationInfo;
+} VkPipelineShaderStageCreateInfo;
+
+typedef struct VkComputePipelineCreateInfo {
+    VkStructureType                    sType;
+    const void*                        pNext;
+    VkPipelineCreateFlags              flags;
+    VkPipelineShaderStageCreateInfo    stage;
+    VkPipelineLayout                   layout;
+    VkPipeline                         basePipelineHandle;
+    int32_t                            basePipelineIndex;
+} VkComputePipelineCreateInfo;
+
+typedef struct VkVertexInputBindingDescription {
+    uint32_t             binding;
+    uint32_t             stride;
+    VkVertexInputRate    inputRate;
+} VkVertexInputBindingDescription;
+
+typedef struct VkVertexInputAttributeDescription {
+    uint32_t    location;
+    uint32_t    binding;
+    VkFormat    format;
+    uint32_t    offset;
+} VkVertexInputAttributeDescription;
+
+typedef struct VkPipelineVertexInputStateCreateInfo {
+    VkStructureType                             sType;
+    const void*                                 pNext;
+    VkPipelineVertexInputStateCreateFlags       flags;
+    uint32_t                                    vertexBindingDescriptionCount;
+    const VkVertexInputBindingDescription*      pVertexBindingDescriptions;
+    uint32_t                                    vertexAttributeDescriptionCount;
+    const VkVertexInputAttributeDescription*    pVertexAttributeDescriptions;
+} VkPipelineVertexInputStateCreateInfo;
+
+typedef struct VkPipelineInputAssemblyStateCreateInfo {
+    VkStructureType                            sType;
+    const void*                                pNext;
+    VkPipelineInputAssemblyStateCreateFlags    flags;
+    VkPrimitiveTopology                        topology;
+    VkBool32                                   primitiveRestartEnable;
+} VkPipelineInputAssemblyStateCreateInfo;
+
+typedef struct VkPipelineTessellationStateCreateInfo {
+    VkStructureType                           sType;
+    const void*                               pNext;
+    VkPipelineTessellationStateCreateFlags    flags;
+    uint32_t                                  patchControlPoints;
+} VkPipelineTessellationStateCreateInfo;
+
+typedef struct VkViewport {
+    float    x;
+    float    y;
+    float    width;
+    float    height;
+    float    minDepth;
+    float    maxDepth;
+} VkViewport;
+
+typedef struct VkPipelineViewportStateCreateInfo {
+    VkStructureType                       sType;
+    const void*                           pNext;
+    VkPipelineViewportStateCreateFlags    flags;
+    uint32_t                              viewportCount;
+    const VkViewport*                     pViewports;
+    uint32_t                              scissorCount;
+    const VkRect2D*                       pScissors;
+} VkPipelineViewportStateCreateInfo;
+
+typedef struct VkPipelineRasterizationStateCreateInfo {
+    VkStructureType                            sType;
+    const void*                                pNext;
+    VkPipelineRasterizationStateCreateFlags    flags;
+    VkBool32                                   depthClampEnable;
+    VkBool32                                   rasterizerDiscardEnable;
+    VkPolygonMode                              polygonMode;
+    VkCullModeFlags                            cullMode;
+    VkFrontFace                                frontFace;
+    VkBool32                                   depthBiasEnable;
+    float                                      depthBiasConstantFactor;
+    float                                      depthBiasClamp;
+    float                                      depthBiasSlopeFactor;
+    float                                      lineWidth;
+} VkPipelineRasterizationStateCreateInfo;
+
+typedef struct VkPipelineMultisampleStateCreateInfo {
+    VkStructureType                          sType;
+    const void*                              pNext;
+    VkPipelineMultisampleStateCreateFlags    flags;
+    VkSampleCountFlagBits                    rasterizationSamples;
+    VkBool32                                 sampleShadingEnable;
+    float                                    minSampleShading;
+    const VkSampleMask*                      pSampleMask;
+    VkBool32                                 alphaToCoverageEnable;
+    VkBool32                                 alphaToOneEnable;
+} VkPipelineMultisampleStateCreateInfo;
+
+typedef struct VkStencilOpState {
+    VkStencilOp    failOp;
+    VkStencilOp    passOp;
+    VkStencilOp    depthFailOp;
+    VkCompareOp    compareOp;
+    uint32_t       compareMask;
+    uint32_t       writeMask;
+    uint32_t       reference;
+} VkStencilOpState;
+
+typedef struct VkPipelineDepthStencilStateCreateInfo {
+    VkStructureType                           sType;
+    const void*                               pNext;
+    VkPipelineDepthStencilStateCreateFlags    flags;
+    VkBool32                                  depthTestEnable;
+    VkBool32                                  depthWriteEnable;
+    VkCompareOp                               depthCompareOp;
+    VkBool32                                  depthBoundsTestEnable;
+    VkBool32                                  stencilTestEnable;
+    VkStencilOpState                          front;
+    VkStencilOpState                          back;
+    float                                     minDepthBounds;
+    float                                     maxDepthBounds;
+} VkPipelineDepthStencilStateCreateInfo;
+
+typedef struct VkPipelineColorBlendAttachmentState {
+    VkBool32                 blendEnable;
+    VkBlendFactor            srcColorBlendFactor;
+    VkBlendFactor            dstColorBlendFactor;
+    VkBlendOp                colorBlendOp;
+    VkBlendFactor            srcAlphaBlendFactor;
+    VkBlendFactor            dstAlphaBlendFactor;
+    VkBlendOp                alphaBlendOp;
+    VkColorComponentFlags    colorWriteMask;
+} VkPipelineColorBlendAttachmentState;
+
+typedef struct VkPipelineColorBlendStateCreateInfo {
+    VkStructureType                               sType;
+    const void*                                   pNext;
+    VkPipelineColorBlendStateCreateFlags          flags;
+    VkBool32                                      logicOpEnable;
+    VkLogicOp                                     logicOp;
+    uint32_t                                      attachmentCount;
+    const VkPipelineColorBlendAttachmentState*    pAttachments;
+    float                                         blendConstants[4];
+} VkPipelineColorBlendStateCreateInfo;
+
+typedef struct VkPipelineDynamicStateCreateInfo {
+    VkStructureType                      sType;
+    const void*                          pNext;
+    VkPipelineDynamicStateCreateFlags    flags;
+    uint32_t                             dynamicStateCount;
+    const VkDynamicState*                pDynamicStates;
+} VkPipelineDynamicStateCreateInfo;
+
+typedef struct VkGraphicsPipelineCreateInfo {
+    VkStructureType                                  sType;
+    const void*                                      pNext;
+    VkPipelineCreateFlags                            flags;
+    uint32_t                                         stageCount;
+    const VkPipelineShaderStageCreateInfo*           pStages;
+    const VkPipelineVertexInputStateCreateInfo*      pVertexInputState;
+    const VkPipelineInputAssemblyStateCreateInfo*    pInputAssemblyState;
+    const VkPipelineTessellationStateCreateInfo*     pTessellationState;
+    const VkPipelineViewportStateCreateInfo*         pViewportState;
+    const VkPipelineRasterizationStateCreateInfo*    pRasterizationState;
+    const VkPipelineMultisampleStateCreateInfo*      pMultisampleState;
+    const VkPipelineDepthStencilStateCreateInfo*     pDepthStencilState;
+    const VkPipelineColorBlendStateCreateInfo*       pColorBlendState;
+    const VkPipelineDynamicStateCreateInfo*          pDynamicState;
+    VkPipelineLayout                                 layout;
+    VkRenderPass                                     renderPass;
+    uint32_t                                         subpass;
+    VkPipeline                                       basePipelineHandle;
+    int32_t                                          basePipelineIndex;
+} VkGraphicsPipelineCreateInfo;
+
+typedef struct VkPushConstantRange {
+    VkShaderStageFlags    stageFlags;
+    uint32_t              offset;
+    uint32_t              size;
+} VkPushConstantRange;
+
+typedef struct VkPipelineLayoutCreateInfo {
+    VkStructureType                 sType;
+    const void*                     pNext;
+    VkPipelineLayoutCreateFlags     flags;
+    uint32_t                        setLayoutCount;
+    const VkDescriptorSetLayout*    pSetLayouts;
+    uint32_t                        pushConstantRangeCount;
+    const VkPushConstantRange*      pPushConstantRanges;
+} VkPipelineLayoutCreateInfo;
+
+typedef struct VkSamplerCreateInfo {
+    VkStructureType         sType;
+    const void*             pNext;
+    VkSamplerCreateFlags    flags;
+    VkFilter                magFilter;
+    VkFilter                minFilter;
+    VkSamplerMipmapMode     mipmapMode;
+    VkSamplerAddressMode    addressModeU;
+    VkSamplerAddressMode    addressModeV;
+    VkSamplerAddressMode    addressModeW;
+    float                   mipLodBias;
+    VkBool32                anisotropyEnable;
+    float                   maxAnisotropy;
+    VkBool32                compareEnable;
+    VkCompareOp             compareOp;
+    float                   minLod;
+    float                   maxLod;
+    VkBorderColor           borderColor;
+    VkBool32                unnormalizedCoordinates;
+} VkSamplerCreateInfo;
+
+typedef struct VkCopyDescriptorSet {
+    VkStructureType    sType;
+    const void*        pNext;
+    VkDescriptorSet    srcSet;
+    uint32_t           srcBinding;
+    uint32_t           srcArrayElement;
+    VkDescriptorSet    dstSet;
+    uint32_t           dstBinding;
+    uint32_t           dstArrayElement;
+    uint32_t           descriptorCount;
+} VkCopyDescriptorSet;
+
+typedef struct VkDescriptorBufferInfo {
+    VkBuffer        buffer;
+    VkDeviceSize    offset;
+    VkDeviceSize    range;
+} VkDescriptorBufferInfo;
+
+typedef struct VkDescriptorImageInfo {
+    VkSampler        sampler;
+    VkImageView      imageView;
+    VkImageLayout    imageLayout;
+} VkDescriptorImageInfo;
+
+typedef struct VkDescriptorPoolSize {
+    VkDescriptorType    type;
+    uint32_t            descriptorCount;
+} VkDescriptorPoolSize;
+
+typedef struct VkDescriptorPoolCreateInfo {
+    VkStructureType                sType;
+    const void*                    pNext;
+    VkDescriptorPoolCreateFlags    flags;
+    uint32_t                       maxSets;
+    uint32_t                       poolSizeCount;
+    const VkDescriptorPoolSize*    pPoolSizes;
+} VkDescriptorPoolCreateInfo;
+
+typedef struct VkDescriptorSetAllocateInfo {
+    VkStructureType                 sType;
+    const void*                     pNext;
+    VkDescriptorPool                descriptorPool;
+    uint32_t                        descriptorSetCount;
+    const VkDescriptorSetLayout*    pSetLayouts;
+} VkDescriptorSetAllocateInfo;
+
+typedef struct VkDescriptorSetLayoutBinding {
+    uint32_t              binding;
+    VkDescriptorType      descriptorType;
+    uint32_t              descriptorCount;
+    VkShaderStageFlags    stageFlags;
+    const VkSampler*      pImmutableSamplers;
+} VkDescriptorSetLayoutBinding;
+
+typedef struct VkDescriptorSetLayoutCreateInfo {
+    VkStructureType                        sType;
+    const void*                            pNext;
+    VkDescriptorSetLayoutCreateFlags       flags;
+    uint32_t                               bindingCount;
+    const VkDescriptorSetLayoutBinding*    pBindings;
+} VkDescriptorSetLayoutCreateInfo;
+
+typedef struct VkWriteDescriptorSet {
+    VkStructureType                  sType;
+    const void*                      pNext;
+    VkDescriptorSet                  dstSet;
+    uint32_t                         dstBinding;
+    uint32_t                         dstArrayElement;
+    uint32_t                         descriptorCount;
+    VkDescriptorType                 descriptorType;
+    const VkDescriptorImageInfo*     pImageInfo;
+    const VkDescriptorBufferInfo*    pBufferInfo;
+    const VkBufferView*              pTexelBufferView;
+} VkWriteDescriptorSet;
+
+typedef struct VkAttachmentDescription {
+    VkAttachmentDescriptionFlags    flags;
+    VkFormat                        format;
+    VkSampleCountFlagBits           samples;
+    VkAttachmentLoadOp              loadOp;
+    VkAttachmentStoreOp             storeOp;
+    VkAttachmentLoadOp              stencilLoadOp;
+    VkAttachmentStoreOp             stencilStoreOp;
+    VkImageLayout                   initialLayout;
+    VkImageLayout                   finalLayout;
+} VkAttachmentDescription;
+
+typedef struct VkAttachmentReference {
+    uint32_t         attachment;
+    VkImageLayout    layout;
+} VkAttachmentReference;
+
+typedef struct VkFramebufferCreateInfo {
+    VkStructureType             sType;
+    const void*                 pNext;
+    VkFramebufferCreateFlags    flags;
+    VkRenderPass                renderPass;
+    uint32_t                    attachmentCount;
+    const VkImageView*          pAttachments;
+    uint32_t                    width;
+    uint32_t                    height;
+    uint32_t                    layers;
+} VkFramebufferCreateInfo;
+
+typedef struct VkSubpassDescription {
+    VkSubpassDescriptionFlags       flags;
+    VkPipelineBindPoint             pipelineBindPoint;
+    uint32_t                        inputAttachmentCount;
+    const VkAttachmentReference*    pInputAttachments;
+    uint32_t                        colorAttachmentCount;
+    const VkAttachmentReference*    pColorAttachments;
+    const VkAttachmentReference*    pResolveAttachments;
+    const VkAttachmentReference*    pDepthStencilAttachment;
+    uint32_t                        preserveAttachmentCount;
+    const uint32_t*                 pPreserveAttachments;
+} VkSubpassDescription;
+
+typedef struct VkSubpassDependency {
+    uint32_t                srcSubpass;
+    uint32_t                dstSubpass;
+    VkPipelineStageFlags    srcStageMask;
+    VkPipelineStageFlags    dstStageMask;
+    VkAccessFlags           srcAccessMask;
+    VkAccessFlags           dstAccessMask;
+    VkDependencyFlags       dependencyFlags;
+} VkSubpassDependency;
+
+typedef struct VkRenderPassCreateInfo {
+    VkStructureType                   sType;
+    const void*                       pNext;
+    VkRenderPassCreateFlags           flags;
+    uint32_t                          attachmentCount;
+    const VkAttachmentDescription*    pAttachments;
+    uint32_t                          subpassCount;
+    const VkSubpassDescription*       pSubpasses;
+    uint32_t                          dependencyCount;
+    const VkSubpassDependency*        pDependencies;
+} VkRenderPassCreateInfo;
+
+typedef struct VkCommandPoolCreateInfo {
+    VkStructureType             sType;
+    const void*                 pNext;
+    VkCommandPoolCreateFlags    flags;
+    uint32_t                    queueFamilyIndex;
+} VkCommandPoolCreateInfo;
+
+typedef struct VkCommandBufferAllocateInfo {
+    VkStructureType         sType;
+    const void*             pNext;
+    VkCommandPool           commandPool;
+    VkCommandBufferLevel    level;
+    uint32_t                commandBufferCount;
+} VkCommandBufferAllocateInfo;
+
+typedef struct VkCommandBufferInheritanceInfo {
+    VkStructureType                  sType;
+    const void*                      pNext;
+    VkRenderPass                     renderPass;
+    uint32_t                         subpass;
+    VkFramebuffer                    framebuffer;
+    VkBool32                         occlusionQueryEnable;
+    VkQueryControlFlags              queryFlags;
+    VkQueryPipelineStatisticFlags    pipelineStatistics;
+} VkCommandBufferInheritanceInfo;
+
+typedef struct VkCommandBufferBeginInfo {
+    VkStructureType                          sType;
+    const void*                              pNext;
+    VkCommandBufferUsageFlags                flags;
+    const VkCommandBufferInheritanceInfo*    pInheritanceInfo;
+} VkCommandBufferBeginInfo;
+
+typedef struct VkBufferCopy {
+    VkDeviceSize    srcOffset;
+    VkDeviceSize    dstOffset;
+    VkDeviceSize    size;
+} VkBufferCopy;
+
+typedef struct VkImageSubresourceLayers {
+    VkImageAspectFlags    aspectMask;
+    uint32_t              mipLevel;
+    uint32_t              baseArrayLayer;
+    uint32_t              layerCount;
+} VkImageSubresourceLayers;
+
+typedef struct VkBufferImageCopy {
+    VkDeviceSize                bufferOffset;
+    uint32_t                    bufferRowLength;
+    uint32_t                    bufferImageHeight;
+    VkImageSubresourceLayers    imageSubresource;
+    VkOffset3D                  imageOffset;
+    VkExtent3D                  imageExtent;
+} VkBufferImageCopy;
+
+typedef union VkClearColorValue {
+    float       float32[4];
+    int32_t     int32[4];
+    uint32_t    uint32[4];
+} VkClearColorValue;
+
+typedef struct VkClearDepthStencilValue {
+    float       depth;
+    uint32_t    stencil;
+} VkClearDepthStencilValue;
+
+typedef union VkClearValue {
+    VkClearColorValue           color;
+    VkClearDepthStencilValue    depthStencil;
+} VkClearValue;
+
+typedef struct VkClearAttachment {
+    VkImageAspectFlags    aspectMask;
+    uint32_t              colorAttachment;
+    VkClearValue          clearValue;
+} VkClearAttachment;
+
+typedef struct VkClearRect {
+    VkRect2D    rect;
+    uint32_t    baseArrayLayer;
+    uint32_t    layerCount;
+} VkClearRect;
+
+typedef struct VkImageBlit {
+    VkImageSubresourceLayers    srcSubresource;
+    VkOffset3D                  srcOffsets[2];
+    VkImageSubresourceLayers    dstSubresource;
+    VkOffset3D                  dstOffsets[2];
+} VkImageBlit;
+
+typedef struct VkImageCopy {
+    VkImageSubresourceLayers    srcSubresource;
+    VkOffset3D                  srcOffset;
+    VkImageSubresourceLayers    dstSubresource;
+    VkOffset3D                  dstOffset;
+    VkExtent3D                  extent;
+} VkImageCopy;
+
+typedef struct VkImageResolve {
+    VkImageSubresourceLayers    srcSubresource;
+    VkOffset3D                  srcOffset;
+    VkImageSubresourceLayers    dstSubresource;
+    VkOffset3D                  dstOffset;
+    VkExtent3D                  extent;
+} VkImageResolve;
+
+typedef struct VkRenderPassBeginInfo {
+    VkStructureType        sType;
+    const void*            pNext;
+    VkRenderPass           renderPass;
+    VkFramebuffer          framebuffer;
+    VkRect2D               renderArea;
+    uint32_t               clearValueCount;
+    const VkClearValue*    pClearValues;
+} VkRenderPassBeginInfo;
+
+typedef VkResult (VKAPI_PTR *PFN_vkCreateInstance)(const VkInstanceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkInstance* pInstance);
+typedef void (VKAPI_PTR *PFN_vkDestroyInstance)(VkInstance instance, const VkAllocationCallbacks* pAllocator);
+typedef VkResult (VKAPI_PTR *PFN_vkEnumeratePhysicalDevices)(VkInstance instance, uint32_t* pPhysicalDeviceCount, VkPhysicalDevice* pPhysicalDevices);
+typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceFeatures)(VkPhysicalDevice physicalDevice, VkPhysicalDeviceFeatures* pFeatures);
+typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceFormatProperties)(VkPhysicalDevice physicalDevice, VkFormat format, VkFormatProperties* pFormatProperties);
+typedef VkResult (VKAPI_PTR *PFN_vkGetPhysicalDeviceImageFormatProperties)(VkPhysicalDevice physicalDevice, VkFormat format, VkImageType type, VkImageTiling tiling, VkImageUsageFlags usage, VkImageCreateFlags flags, VkImageFormatProperties* pImageFormatProperties);
+typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceProperties)(VkPhysicalDevice physicalDevice, VkPhysicalDeviceProperties* pProperties);
+typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceQueueFamilyProperties)(VkPhysicalDevice physicalDevice, uint32_t* pQueueFamilyPropertyCount, VkQueueFamilyProperties* pQueueFamilyProperties);
+typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceMemoryProperties)(VkPhysicalDevice physicalDevice, VkPhysicalDeviceMemoryProperties* pMemoryProperties);
+typedef PFN_vkVoidFunction (VKAPI_PTR *PFN_vkGetInstanceProcAddr)(VkInstance instance, const char* pName);
+typedef PFN_vkVoidFunction (VKAPI_PTR *PFN_vkGetDeviceProcAddr)(VkDevice device, const char* pName);
+typedef VkResult (VKAPI_PTR *PFN_vkCreateDevice)(VkPhysicalDevice physicalDevice, const VkDeviceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDevice* pDevice);
+typedef void (VKAPI_PTR *PFN_vkDestroyDevice)(VkDevice device, const VkAllocationCallbacks* pAllocator);
+typedef VkResult (VKAPI_PTR *PFN_vkEnumerateInstanceExtensionProperties)(const char* pLayerName, uint32_t* pPropertyCount, VkExtensionProperties* pProperties);
+typedef VkResult (VKAPI_PTR *PFN_vkEnumerateDeviceExtensionProperties)(VkPhysicalDevice physicalDevice, const char* pLayerName, uint32_t* pPropertyCount, VkExtensionProperties* pProperties);
+typedef VkResult (VKAPI_PTR *PFN_vkEnumerateInstanceLayerProperties)(uint32_t* pPropertyCount, VkLayerProperties* pProperties);
+typedef VkResult (VKAPI_PTR *PFN_vkEnumerateDeviceLayerProperties)(VkPhysicalDevice physicalDevice, uint32_t* pPropertyCount, VkLayerProperties* pProperties);
+typedef void (VKAPI_PTR *PFN_vkGetDeviceQueue)(VkDevice device, uint32_t queueFamilyIndex, uint32_t queueIndex, VkQueue* pQueue);
+typedef VkResult (VKAPI_PTR *PFN_vkQueueSubmit)(VkQueue queue, uint32_t submitCount, const VkSubmitInfo* pSubmits, VkFence fence);
+typedef VkResult (VKAPI_PTR *PFN_vkQueueWaitIdle)(VkQueue queue);
+typedef VkResult (VKAPI_PTR *PFN_vkDeviceWaitIdle)(VkDevice device);
+typedef VkResult (VKAPI_PTR *PFN_vkAllocateMemory)(VkDevice device, const VkMemoryAllocateInfo* pAllocateInfo, const VkAllocationCallbacks* pAllocator, VkDeviceMemory* pMemory);
+typedef void (VKAPI_PTR *PFN_vkFreeMemory)(VkDevice device, VkDeviceMemory memory, const VkAllocationCallbacks* pAllocator);
+typedef VkResult (VKAPI_PTR *PFN_vkMapMemory)(VkDevice device, VkDeviceMemory memory, VkDeviceSize offset, VkDeviceSize size, VkMemoryMapFlags flags, void** ppData);
+typedef void (VKAPI_PTR *PFN_vkUnmapMemory)(VkDevice device, VkDeviceMemory memory);
+typedef VkResult (VKAPI_PTR *PFN_vkFlushMappedMemoryRanges)(VkDevice device, uint32_t memoryRangeCount, const VkMappedMemoryRange* pMemoryRanges);
+typedef VkResult (VKAPI_PTR *PFN_vkInvalidateMappedMemoryRanges)(VkDevice device, uint32_t memoryRangeCount, const VkMappedMemoryRange* pMemoryRanges);
+typedef void (VKAPI_PTR *PFN_vkGetDeviceMemoryCommitment)(VkDevice device, VkDeviceMemory memory, VkDeviceSize* pCommittedMemoryInBytes);
+typedef VkResult (VKAPI_PTR *PFN_vkBindBufferMemory)(VkDevice device, VkBuffer buffer, VkDeviceMemory memory, VkDeviceSize memoryOffset);
+typedef VkResult (VKAPI_PTR *PFN_vkBindImageMemory)(VkDevice device, VkImage image, VkDeviceMemory memory, VkDeviceSize memoryOffset);
+typedef void (VKAPI_PTR *PFN_vkGetBufferMemoryRequirements)(VkDevice device, VkBuffer buffer, VkMemoryRequirements* pMemoryRequirements);
+typedef void (VKAPI_PTR *PFN_vkGetImageMemoryRequirements)(VkDevice device, VkImage image, VkMemoryRequirements* pMemoryRequirements);
+typedef void (VKAPI_PTR *PFN_vkGetImageSparseMemoryRequirements)(VkDevice device, VkImage image, uint32_t* pSparseMemoryRequirementCount, VkSparseImageMemoryRequirements* pSparseMemoryRequirements);
+typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceSparseImageFormatProperties)(VkPhysicalDevice physicalDevice, VkFormat format, VkImageType type, VkSampleCountFlagBits samples, VkImageUsageFlags usage, VkImageTiling tiling, uint32_t* pPropertyCount, VkSparseImageFormatProperties* pProperties);
+typedef VkResult (VKAPI_PTR *PFN_vkQueueBindSparse)(VkQueue queue, uint32_t bindInfoCount, const VkBindSparseInfo* pBindInfo, VkFence fence);
+typedef VkResult (VKAPI_PTR *PFN_vkCreateFence)(VkDevice device, const VkFenceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkFence* pFence);
+typedef void (VKAPI_PTR *PFN_vkDestroyFence)(VkDevice device, VkFence fence, const VkAllocationCallbacks* pAllocator);
+typedef VkResult (VKAPI_PTR *PFN_vkResetFences)(VkDevice device, uint32_t fenceCount, const VkFence* pFences);
+typedef VkResult (VKAPI_PTR *PFN_vkGetFenceStatus)(VkDevice device, VkFence fence);
+typedef VkResult (VKAPI_PTR *PFN_vkWaitForFences)(VkDevice device, uint32_t fenceCount, const VkFence* pFences, VkBool32 waitAll, uint64_t timeout);
+typedef VkResult (VKAPI_PTR *PFN_vkCreateSemaphore)(VkDevice device, const VkSemaphoreCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSemaphore* pSemaphore);
+typedef void (VKAPI_PTR *PFN_vkDestroySemaphore)(VkDevice device, VkSemaphore semaphore, const VkAllocationCallbacks* pAllocator);
+typedef VkResult (VKAPI_PTR *PFN_vkCreateEvent)(VkDevice device, const VkEventCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkEvent* pEvent);
+typedef void (VKAPI_PTR *PFN_vkDestroyEvent)(VkDevice device, VkEvent event, const VkAllocationCallbacks* pAllocator);
+typedef VkResult (VKAPI_PTR *PFN_vkGetEventStatus)(VkDevice device, VkEvent event);
+typedef VkResult (VKAPI_PTR *PFN_vkSetEvent)(VkDevice device, VkEvent event);
+typedef VkResult (VKAPI_PTR *PFN_vkResetEvent)(VkDevice device, VkEvent event);
+typedef VkResult (VKAPI_PTR *PFN_vkCreateQueryPool)(VkDevice device, const VkQueryPoolCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkQueryPool* pQueryPool);
+typedef void (VKAPI_PTR *PFN_vkDestroyQueryPool)(VkDevice device, VkQueryPool queryPool, const VkAllocationCallbacks* pAllocator);
+typedef VkResult (VKAPI_PTR *PFN_vkGetQueryPoolResults)(VkDevice device, VkQueryPool queryPool, uint32_t firstQuery, uint32_t queryCount, size_t dataSize, void* pData, VkDeviceSize stride, VkQueryResultFlags flags);
+typedef VkResult (VKAPI_PTR *PFN_vkCreateBuffer)(VkDevice device, const VkBufferCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkBuffer* pBuffer);
+typedef void (VKAPI_PTR *PFN_vkDestroyBuffer)(VkDevice device, VkBuffer buffer, const VkAllocationCallbacks* pAllocator);
+typedef VkResult (VKAPI_PTR *PFN_vkCreateBufferView)(VkDevice device, const VkBufferViewCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkBufferView* pView);
+typedef void (VKAPI_PTR *PFN_vkDestroyBufferView)(VkDevice device, VkBufferView bufferView, const VkAllocationCallbacks* pAllocator);
+typedef VkResult (VKAPI_PTR *PFN_vkCreateImage)(VkDevice device, const VkImageCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkImage* pImage);
+typedef void (VKAPI_PTR *PFN_vkDestroyImage)(VkDevice device, VkImage image, const VkAllocationCallbacks* pAllocator);
+typedef void (VKAPI_PTR *PFN_vkGetImageSubresourceLayout)(VkDevice device, VkImage image, const VkImageSubresource* pSubresource, VkSubresourceLayout* pLayout);
+typedef VkResult (VKAPI_PTR *PFN_vkCreateImageView)(VkDevice device, const VkImageViewCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkImageView* pView);
+typedef void (VKAPI_PTR *PFN_vkDestroyImageView)(VkDevice device, VkImageView imageView, const VkAllocationCallbacks* pAllocator);
+typedef VkResult (VKAPI_PTR *PFN_vkCreateShaderModule)(VkDevice device, const VkShaderModuleCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkShaderModule* pShaderModule);
+typedef void (VKAPI_PTR *PFN_vkDestroyShaderModule)(VkDevice device, VkShaderModule shaderModule, const VkAllocationCallbacks* pAllocator);
+typedef VkResult (VKAPI_PTR *PFN_vkCreatePipelineCache)(VkDevice device, const VkPipelineCacheCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkPipelineCache* pPipelineCache);
+typedef void (VKAPI_PTR *PFN_vkDestroyPipelineCache)(VkDevice device, VkPipelineCache pipelineCache, const VkAllocationCallbacks* pAllocator);
+typedef VkResult (VKAPI_PTR *PFN_vkGetPipelineCacheData)(VkDevice device, VkPipelineCache pipelineCache, size_t* pDataSize, void* pData);
+typedef VkResult (VKAPI_PTR *PFN_vkMergePipelineCaches)(VkDevice device, VkPipelineCache dstCache, uint32_t srcCacheCount, const VkPipelineCache* pSrcCaches);
+typedef VkResult (VKAPI_PTR *PFN_vkCreateGraphicsPipelines)(VkDevice device, VkPipelineCache pipelineCache, uint32_t createInfoCount, const VkGraphicsPipelineCreateInfo* pCreateInfos, const VkAllocationCallbacks* pAllocator, VkPipeline* pPipelines);
+typedef VkResult (VKAPI_PTR *PFN_vkCreateComputePipelines)(VkDevice device, VkPipelineCache pipelineCache, uint32_t createInfoCount, const VkComputePipelineCreateInfo* pCreateInfos, const VkAllocationCallbacks* pAllocator, VkPipeline* pPipelines);
+typedef void (VKAPI_PTR *PFN_vkDestroyPipeline)(VkDevice device, VkPipeline pipeline, const VkAllocationCallbacks* pAllocator);
+typedef VkResult (VKAPI_PTR *PFN_vkCreatePipelineLayout)(VkDevice device, const VkPipelineLayoutCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkPipelineLayout* pPipelineLayout);
+typedef void (VKAPI_PTR *PFN_vkDestroyPipelineLayout)(VkDevice device, VkPipelineLayout pipelineLayout, const VkAllocationCallbacks* pAllocator);
+typedef VkResult (VKAPI_PTR *PFN_vkCreateSampler)(VkDevice device, const VkSamplerCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSampler* pSampler);
+typedef void (VKAPI_PTR *PFN_vkDestroySampler)(VkDevice device, VkSampler sampler, const VkAllocationCallbacks* pAllocator);
+typedef VkResult (VKAPI_PTR *PFN_vkCreateDescriptorSetLayout)(VkDevice device, const VkDescriptorSetLayoutCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDescriptorSetLayout* pSetLayout);
+typedef void (VKAPI_PTR *PFN_vkDestroyDescriptorSetLayout)(VkDevice device, VkDescriptorSetLayout descriptorSetLayout, const VkAllocationCallbacks* pAllocator);
+typedef VkResult (VKAPI_PTR *PFN_vkCreateDescriptorPool)(VkDevice device, const VkDescriptorPoolCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDescriptorPool* pDescriptorPool);
+typedef void (VKAPI_PTR *PFN_vkDestroyDescriptorPool)(VkDevice device, VkDescriptorPool descriptorPool, const VkAllocationCallbacks* pAllocator);
+typedef VkResult (VKAPI_PTR *PFN_vkResetDescriptorPool)(VkDevice device, VkDescriptorPool descriptorPool, VkDescriptorPoolResetFlags flags);
+typedef VkResult (VKAPI_PTR *PFN_vkAllocateDescriptorSets)(VkDevice device, const VkDescriptorSetAllocateInfo* pAllocateInfo, VkDescriptorSet* pDescriptorSets);
+typedef VkResult (VKAPI_PTR *PFN_vkFreeDescriptorSets)(VkDevice device, VkDescriptorPool descriptorPool, uint32_t descriptorSetCount, const VkDescriptorSet* pDescriptorSets);
+typedef void (VKAPI_PTR *PFN_vkUpdateDescriptorSets)(VkDevice device, uint32_t descriptorWriteCount, const VkWriteDescriptorSet* pDescriptorWrites, uint32_t descriptorCopyCount, const VkCopyDescriptorSet* pDescriptorCopies);
+typedef VkResult (VKAPI_PTR *PFN_vkCreateFramebuffer)(VkDevice device, const VkFramebufferCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkFramebuffer* pFramebuffer);
+typedef void (VKAPI_PTR *PFN_vkDestroyFramebuffer)(VkDevice device, VkFramebuffer framebuffer, const VkAllocationCallbacks* pAllocator);
+typedef VkResult (VKAPI_PTR *PFN_vkCreateRenderPass)(VkDevice device, const VkRenderPassCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkRenderPass* pRenderPass);
+typedef void (VKAPI_PTR *PFN_vkDestroyRenderPass)(VkDevice device, VkRenderPass renderPass, const VkAllocationCallbacks* pAllocator);
+typedef void (VKAPI_PTR *PFN_vkGetRenderAreaGranularity)(VkDevice device, VkRenderPass renderPass, VkExtent2D* pGranularity);
+typedef VkResult (VKAPI_PTR *PFN_vkCreateCommandPool)(VkDevice device, const VkCommandPoolCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkCommandPool* pCommandPool);
+typedef void (VKAPI_PTR *PFN_vkDestroyCommandPool)(VkDevice device, VkCommandPool commandPool, const VkAllocationCallbacks* pAllocator);
+typedef VkResult (VKAPI_PTR *PFN_vkResetCommandPool)(VkDevice device, VkCommandPool commandPool, VkCommandPoolResetFlags flags);
+typedef VkResult (VKAPI_PTR *PFN_vkAllocateCommandBuffers)(VkDevice device, const VkCommandBufferAllocateInfo* pAllocateInfo, VkCommandBuffer* pCommandBuffers);
+typedef void (VKAPI_PTR *PFN_vkFreeCommandBuffers)(VkDevice device, VkCommandPool commandPool, uint32_t commandBufferCount, const VkCommandBuffer* pCommandBuffers);
+typedef VkResult (VKAPI_PTR *PFN_vkBeginCommandBuffer)(VkCommandBuffer commandBuffer, const VkCommandBufferBeginInfo* pBeginInfo);
+typedef VkResult (VKAPI_PTR *PFN_vkEndCommandBuffer)(VkCommandBuffer commandBuffer);
+typedef VkResult (VKAPI_PTR *PFN_vkResetCommandBuffer)(VkCommandBuffer commandBuffer, VkCommandBufferResetFlags flags);
+typedef void (VKAPI_PTR *PFN_vkCmdBindPipeline)(VkCommandBuffer commandBuffer, VkPipelineBindPoint pipelineBindPoint, VkPipeline pipeline);
+typedef void (VKAPI_PTR *PFN_vkCmdSetViewport)(VkCommandBuffer commandBuffer, uint32_t firstViewport, uint32_t viewportCount, const VkViewport* pViewports);
+typedef void (VKAPI_PTR *PFN_vkCmdSetScissor)(VkCommandBuffer commandBuffer, uint32_t firstScissor, uint32_t scissorCount, const VkRect2D* pScissors);
+typedef void (VKAPI_PTR *PFN_vkCmdSetLineWidth)(VkCommandBuffer commandBuffer, float lineWidth);
+typedef void (VKAPI_PTR *PFN_vkCmdSetDepthBias)(VkCommandBuffer commandBuffer, float depthBiasConstantFactor, float depthBiasClamp, float depthBiasSlopeFactor);
+typedef void (VKAPI_PTR *PFN_vkCmdSetBlendConstants)(VkCommandBuffer commandBuffer, const float blendConstants[4]);
+typedef void (VKAPI_PTR *PFN_vkCmdSetDepthBounds)(VkCommandBuffer commandBuffer, float minDepthBounds, float maxDepthBounds);
+typedef void (VKAPI_PTR *PFN_vkCmdSetStencilCompareMask)(VkCommandBuffer commandBuffer, VkStencilFaceFlags faceMask, uint32_t compareMask);
+typedef void (VKAPI_PTR *PFN_vkCmdSetStencilWriteMask)(VkCommandBuffer commandBuffer, VkStencilFaceFlags faceMask, uint32_t writeMask);
+typedef void (VKAPI_PTR *PFN_vkCmdSetStencilReference)(VkCommandBuffer commandBuffer, VkStencilFaceFlags faceMask, uint32_t reference);
+typedef void (VKAPI_PTR *PFN_vkCmdBindDescriptorSets)(VkCommandBuffer commandBuffer, VkPipelineBindPoint pipelineBindPoint, VkPipelineLayout layout, uint32_t firstSet, uint32_t descriptorSetCount, const VkDescriptorSet* pDescriptorSets, uint32_t dynamicOffsetCount, const uint32_t* pDynamicOffsets);
+typedef void (VKAPI_PTR *PFN_vkCmdBindIndexBuffer)(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset, VkIndexType indexType);
+typedef void (VKAPI_PTR *PFN_vkCmdBindVertexBuffers)(VkCommandBuffer commandBuffer, uint32_t firstBinding, uint32_t bindingCount, const VkBuffer* pBuffers, const VkDeviceSize* pOffsets);
+typedef void (VKAPI_PTR *PFN_vkCmdDraw)(VkCommandBuffer commandBuffer, uint32_t vertexCount, uint32_t instanceCount, uint32_t firstVertex, uint32_t firstInstance);
+typedef void (VKAPI_PTR *PFN_vkCmdDrawIndexed)(VkCommandBuffer commandBuffer, uint32_t indexCount, uint32_t instanceCount, uint32_t firstIndex, int32_t vertexOffset, uint32_t firstInstance);
+typedef void (VKAPI_PTR *PFN_vkCmdDrawIndirect)(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset, uint32_t drawCount, uint32_t stride);
+typedef void (VKAPI_PTR *PFN_vkCmdDrawIndexedIndirect)(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset, uint32_t drawCount, uint32_t stride);
+typedef void (VKAPI_PTR *PFN_vkCmdDispatch)(VkCommandBuffer commandBuffer, uint32_t groupCountX, uint32_t groupCountY, uint32_t groupCountZ);
+typedef void (VKAPI_PTR *PFN_vkCmdDispatchIndirect)(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset);
+typedef void (VKAPI_PTR *PFN_vkCmdCopyBuffer)(VkCommandBuffer commandBuffer, VkBuffer srcBuffer, VkBuffer dstBuffer, uint32_t regionCount, const VkBufferCopy* pRegions);
+typedef void (VKAPI_PTR *PFN_vkCmdCopyImage)(VkCommandBuffer commandBuffer, VkImage srcImage, VkImageLayout srcImageLayout, VkImage dstImage, VkImageLayout dstImageLayout, uint32_t regionCount, const VkImageCopy* pRegions);
+typedef void (VKAPI_PTR *PFN_vkCmdBlitImage)(VkCommandBuffer commandBuffer, VkImage srcImage, VkImageLayout srcImageLayout, VkImage dstImage, VkImageLayout dstImageLayout, uint32_t regionCount, const VkImageBlit* pRegions, VkFilter filter);
+typedef void (VKAPI_PTR *PFN_vkCmdCopyBufferToImage)(VkCommandBuffer commandBuffer, VkBuffer srcBuffer, VkImage dstImage, VkImageLayout dstImageLayout, uint32_t regionCount, const VkBufferImageCopy* pRegions);
+typedef void (VKAPI_PTR *PFN_vkCmdCopyImageToBuffer)(VkCommandBuffer commandBuffer, VkImage srcImage, VkImageLayout srcImageLayout, VkBuffer dstBuffer, uint32_t regionCount, const VkBufferImageCopy* pRegions);
+typedef void (VKAPI_PTR *PFN_vkCmdUpdateBuffer)(VkCommandBuffer commandBuffer, VkBuffer dstBuffer, VkDeviceSize dstOffset, VkDeviceSize dataSize, const void* pData);
+typedef void (VKAPI_PTR *PFN_vkCmdFillBuffer)(VkCommandBuffer commandBuffer, VkBuffer dstBuffer, VkDeviceSize dstOffset, VkDeviceSize size, uint32_t data);
+typedef void (VKAPI_PTR *PFN_vkCmdClearColorImage)(VkCommandBuffer commandBuffer, VkImage image, VkImageLayout imageLayout, const VkClearColorValue* pColor, uint32_t rangeCount, const VkImageSubresourceRange* pRanges);
+typedef void (VKAPI_PTR *PFN_vkCmdClearDepthStencilImage)(VkCommandBuffer commandBuffer, VkImage image, VkImageLayout imageLayout, const VkClearDepthStencilValue* pDepthStencil, uint32_t rangeCount, const VkImageSubresourceRange* pRanges);
+typedef void (VKAPI_PTR *PFN_vkCmdClearAttachments)(VkCommandBuffer commandBuffer, uint32_t attachmentCount, const VkClearAttachment* pAttachments, uint32_t rectCount, const VkClearRect* pRects);
+typedef void (VKAPI_PTR *PFN_vkCmdResolveImage)(VkCommandBuffer commandBuffer, VkImage srcImage, VkImageLayout srcImageLayout, VkImage dstImage, VkImageLayout dstImageLayout, uint32_t regionCount, const VkImageResolve* pRegions);
+typedef void (VKAPI_PTR *PFN_vkCmdSetEvent)(VkCommandBuffer commandBuffer, VkEvent event, VkPipelineStageFlags stageMask);
+typedef void (VKAPI_PTR *PFN_vkCmdResetEvent)(VkCommandBuffer commandBuffer, VkEvent event, VkPipelineStageFlags stageMask);
+typedef void (VKAPI_PTR *PFN_vkCmdWaitEvents)(VkCommandBuffer commandBuffer, uint32_t eventCount, const VkEvent* pEvents, VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask, uint32_t memoryBarrierCount, const VkMemoryBarrier* pMemoryBarriers, uint32_t bufferMemoryBarrierCount, const VkBufferMemoryBarrier* pBufferMemoryBarriers, uint32_t imageMemoryBarrierCount, const VkImageMemoryBarrier* pImageMemoryBarriers);
+typedef void (VKAPI_PTR *PFN_vkCmdPipelineBarrier)(VkCommandBuffer commandBuffer, VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask, VkDependencyFlags dependencyFlags, uint32_t memoryBarrierCount, const VkMemoryBarrier* pMemoryBarriers, uint32_t bufferMemoryBarrierCount, const VkBufferMemoryBarrier* pBufferMemoryBarriers, uint32_t imageMemoryBarrierCount, const VkImageMemoryBarrier* pImageMemoryBarriers);
+typedef void (VKAPI_PTR *PFN_vkCmdBeginQuery)(VkCommandBuffer commandBuffer, VkQueryPool queryPool, uint32_t query, VkQueryControlFlags flags);
+typedef void (VKAPI_PTR *PFN_vkCmdEndQuery)(VkCommandBuffer commandBuffer, VkQueryPool queryPool, uint32_t query);
+typedef void (VKAPI_PTR *PFN_vkCmdResetQueryPool)(VkCommandBuffer commandBuffer, VkQueryPool queryPool, uint32_t firstQuery, uint32_t queryCount);
+typedef void (VKAPI_PTR *PFN_vkCmdWriteTimestamp)(VkCommandBuffer commandBuffer, VkPipelineStageFlagBits pipelineStage, VkQueryPool queryPool, uint32_t query);
+typedef void (VKAPI_PTR *PFN_vkCmdCopyQueryPoolResults)(VkCommandBuffer commandBuffer, VkQueryPool queryPool, uint32_t firstQuery, uint32_t queryCount, VkBuffer dstBuffer, VkDeviceSize dstOffset, VkDeviceSize stride, VkQueryResultFlags flags);
+typedef void (VKAPI_PTR *PFN_vkCmdPushConstants)(VkCommandBuffer commandBuffer, VkPipelineLayout layout, VkShaderStageFlags stageFlags, uint32_t offset, uint32_t size, const void* pValues);
+typedef void (VKAPI_PTR *PFN_vkCmdBeginRenderPass)(VkCommandBuffer commandBuffer, const VkRenderPassBeginInfo* pRenderPassBegin, VkSubpassContents contents);
+typedef void (VKAPI_PTR *PFN_vkCmdNextSubpass)(VkCommandBuffer commandBuffer, VkSubpassContents contents);
+typedef void (VKAPI_PTR *PFN_vkCmdEndRenderPass)(VkCommandBuffer commandBuffer);
+typedef void (VKAPI_PTR *PFN_vkCmdExecuteCommands)(VkCommandBuffer commandBuffer, uint32_t commandBufferCount, const VkCommandBuffer* pCommandBuffers);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateInstance(
+    const VkInstanceCreateInfo*                 pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkInstance*                                 pInstance);
+
+VKAPI_ATTR void VKAPI_CALL vkDestroyInstance(
+    VkInstance                                  instance,
+    const VkAllocationCallbacks*                pAllocator);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkEnumeratePhysicalDevices(
+    VkInstance                                  instance,
+    uint32_t*                                   pPhysicalDeviceCount,
+    VkPhysicalDevice*                           pPhysicalDevices);
+
+VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceFeatures(
+    VkPhysicalDevice                            physicalDevice,
+    VkPhysicalDeviceFeatures*                   pFeatures);
+
+VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceFormatProperties(
+    VkPhysicalDevice                            physicalDevice,
+    VkFormat                                    format,
+    VkFormatProperties*                         pFormatProperties);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceImageFormatProperties(
+    VkPhysicalDevice                            physicalDevice,
+    VkFormat                                    format,
+    VkImageType                                 type,
+    VkImageTiling                               tiling,
+    VkImageUsageFlags                           usage,
+    VkImageCreateFlags                          flags,
+    VkImageFormatProperties*                    pImageFormatProperties);
+
+VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceProperties(
+    VkPhysicalDevice                            physicalDevice,
+    VkPhysicalDeviceProperties*                 pProperties);
+
+VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceQueueFamilyProperties(
+    VkPhysicalDevice                            physicalDevice,
+    uint32_t*                                   pQueueFamilyPropertyCount,
+    VkQueueFamilyProperties*                    pQueueFamilyProperties);
+
+VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceMemoryProperties(
+    VkPhysicalDevice                            physicalDevice,
+    VkPhysicalDeviceMemoryProperties*           pMemoryProperties);
+
+VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetInstanceProcAddr(
+    VkInstance                                  instance,
+    const char*                                 pName);
+
+VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vkGetDeviceProcAddr(
+    VkDevice                                    device,
+    const char*                                 pName);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateDevice(
+    VkPhysicalDevice                            physicalDevice,
+    const VkDeviceCreateInfo*                   pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkDevice*                                   pDevice);
+
+VKAPI_ATTR void VKAPI_CALL vkDestroyDevice(
+    VkDevice                                    device,
+    const VkAllocationCallbacks*                pAllocator);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateInstanceExtensionProperties(
+    const char*                                 pLayerName,
+    uint32_t*                                   pPropertyCount,
+    VkExtensionProperties*                      pProperties);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateDeviceExtensionProperties(
+    VkPhysicalDevice                            physicalDevice,
+    const char*                                 pLayerName,
+    uint32_t*                                   pPropertyCount,
+    VkExtensionProperties*                      pProperties);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateInstanceLayerProperties(
+    uint32_t*                                   pPropertyCount,
+    VkLayerProperties*                          pProperties);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateDeviceLayerProperties(
+    VkPhysicalDevice                            physicalDevice,
+    uint32_t*                                   pPropertyCount,
+    VkLayerProperties*                          pProperties);
+
+VKAPI_ATTR void VKAPI_CALL vkGetDeviceQueue(
+    VkDevice                                    device,
+    uint32_t                                    queueFamilyIndex,
+    uint32_t                                    queueIndex,
+    VkQueue*                                    pQueue);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkQueueSubmit(
+    VkQueue                                     queue,
+    uint32_t                                    submitCount,
+    const VkSubmitInfo*                         pSubmits,
+    VkFence                                     fence);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkQueueWaitIdle(
+    VkQueue                                     queue);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkDeviceWaitIdle(
+    VkDevice                                    device);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkAllocateMemory(
+    VkDevice                                    device,
+    const VkMemoryAllocateInfo*                 pAllocateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkDeviceMemory*                             pMemory);
+
+VKAPI_ATTR void VKAPI_CALL vkFreeMemory(
+    VkDevice                                    device,
+    VkDeviceMemory                              memory,
+    const VkAllocationCallbacks*                pAllocator);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkMapMemory(
+    VkDevice                                    device,
+    VkDeviceMemory                              memory,
+    VkDeviceSize                                offset,
+    VkDeviceSize                                size,
+    VkMemoryMapFlags                            flags,
+    void**                                      ppData);
+
+VKAPI_ATTR void VKAPI_CALL vkUnmapMemory(
+    VkDevice                                    device,
+    VkDeviceMemory                              memory);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkFlushMappedMemoryRanges(
+    VkDevice                                    device,
+    uint32_t                                    memoryRangeCount,
+    const VkMappedMemoryRange*                  pMemoryRanges);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkInvalidateMappedMemoryRanges(
+    VkDevice                                    device,
+    uint32_t                                    memoryRangeCount,
+    const VkMappedMemoryRange*                  pMemoryRanges);
+
+VKAPI_ATTR void VKAPI_CALL vkGetDeviceMemoryCommitment(
+    VkDevice                                    device,
+    VkDeviceMemory                              memory,
+    VkDeviceSize*                               pCommittedMemoryInBytes);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkBindBufferMemory(
+    VkDevice                                    device,
+    VkBuffer                                    buffer,
+    VkDeviceMemory                              memory,
+    VkDeviceSize                                memoryOffset);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkBindImageMemory(
+    VkDevice                                    device,
+    VkImage                                     image,
+    VkDeviceMemory                              memory,
+    VkDeviceSize                                memoryOffset);
+
+VKAPI_ATTR void VKAPI_CALL vkGetBufferMemoryRequirements(
+    VkDevice                                    device,
+    VkBuffer                                    buffer,
+    VkMemoryRequirements*                       pMemoryRequirements);
+
+VKAPI_ATTR void VKAPI_CALL vkGetImageMemoryRequirements(
+    VkDevice                                    device,
+    VkImage                                     image,
+    VkMemoryRequirements*                       pMemoryRequirements);
+
+VKAPI_ATTR void VKAPI_CALL vkGetImageSparseMemoryRequirements(
+    VkDevice                                    device,
+    VkImage                                     image,
+    uint32_t*                                   pSparseMemoryRequirementCount,
+    VkSparseImageMemoryRequirements*            pSparseMemoryRequirements);
+
+VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceSparseImageFormatProperties(
+    VkPhysicalDevice                            physicalDevice,
+    VkFormat                                    format,
+    VkImageType                                 type,
+    VkSampleCountFlagBits                       samples,
+    VkImageUsageFlags                           usage,
+    VkImageTiling                               tiling,
+    uint32_t*                                   pPropertyCount,
+    VkSparseImageFormatProperties*              pProperties);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkQueueBindSparse(
+    VkQueue                                     queue,
+    uint32_t                                    bindInfoCount,
+    const VkBindSparseInfo*                     pBindInfo,
+    VkFence                                     fence);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateFence(
+    VkDevice                                    device,
+    const VkFenceCreateInfo*                    pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkFence*                                    pFence);
+
+VKAPI_ATTR void VKAPI_CALL vkDestroyFence(
+    VkDevice                                    device,
+    VkFence                                     fence,
+    const VkAllocationCallbacks*                pAllocator);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkResetFences(
+    VkDevice                                    device,
+    uint32_t                                    fenceCount,
+    const VkFence*                              pFences);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetFenceStatus(
+    VkDevice                                    device,
+    VkFence                                     fence);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkWaitForFences(
+    VkDevice                                    device,
+    uint32_t                                    fenceCount,
+    const VkFence*                              pFences,
+    VkBool32                                    waitAll,
+    uint64_t                                    timeout);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateSemaphore(
+    VkDevice                                    device,
+    const VkSemaphoreCreateInfo*                pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkSemaphore*                                pSemaphore);
+
+VKAPI_ATTR void VKAPI_CALL vkDestroySemaphore(
+    VkDevice                                    device,
+    VkSemaphore                                 semaphore,
+    const VkAllocationCallbacks*                pAllocator);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateEvent(
+    VkDevice                                    device,
+    const VkEventCreateInfo*                    pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkEvent*                                    pEvent);
+
+VKAPI_ATTR void VKAPI_CALL vkDestroyEvent(
+    VkDevice                                    device,
+    VkEvent                                     event,
+    const VkAllocationCallbacks*                pAllocator);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetEventStatus(
+    VkDevice                                    device,
+    VkEvent                                     event);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkSetEvent(
+    VkDevice                                    device,
+    VkEvent                                     event);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkResetEvent(
+    VkDevice                                    device,
+    VkEvent                                     event);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateQueryPool(
+    VkDevice                                    device,
+    const VkQueryPoolCreateInfo*                pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkQueryPool*                                pQueryPool);
+
+VKAPI_ATTR void VKAPI_CALL vkDestroyQueryPool(
+    VkDevice                                    device,
+    VkQueryPool                                 queryPool,
+    const VkAllocationCallbacks*                pAllocator);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetQueryPoolResults(
+    VkDevice                                    device,
+    VkQueryPool                                 queryPool,
+    uint32_t                                    firstQuery,
+    uint32_t                                    queryCount,
+    size_t                                      dataSize,
+    void*                                       pData,
+    VkDeviceSize                                stride,
+    VkQueryResultFlags                          flags);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateBuffer(
+    VkDevice                                    device,
+    const VkBufferCreateInfo*                   pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkBuffer*                                   pBuffer);
+
+VKAPI_ATTR void VKAPI_CALL vkDestroyBuffer(
+    VkDevice                                    device,
+    VkBuffer                                    buffer,
+    const VkAllocationCallbacks*                pAllocator);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateBufferView(
+    VkDevice                                    device,
+    const VkBufferViewCreateInfo*               pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkBufferView*                               pView);
+
+VKAPI_ATTR void VKAPI_CALL vkDestroyBufferView(
+    VkDevice                                    device,
+    VkBufferView                                bufferView,
+    const VkAllocationCallbacks*                pAllocator);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateImage(
+    VkDevice                                    device,
+    const VkImageCreateInfo*                    pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkImage*                                    pImage);
+
+VKAPI_ATTR void VKAPI_CALL vkDestroyImage(
+    VkDevice                                    device,
+    VkImage                                     image,
+    const VkAllocationCallbacks*                pAllocator);
+
+VKAPI_ATTR void VKAPI_CALL vkGetImageSubresourceLayout(
+    VkDevice                                    device,
+    VkImage                                     image,
+    const VkImageSubresource*                   pSubresource,
+    VkSubresourceLayout*                        pLayout);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateImageView(
+    VkDevice                                    device,
+    const VkImageViewCreateInfo*                pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkImageView*                                pView);
+
+VKAPI_ATTR void VKAPI_CALL vkDestroyImageView(
+    VkDevice                                    device,
+    VkImageView                                 imageView,
+    const VkAllocationCallbacks*                pAllocator);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateShaderModule(
+    VkDevice                                    device,
+    const VkShaderModuleCreateInfo*             pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkShaderModule*                             pShaderModule);
+
+VKAPI_ATTR void VKAPI_CALL vkDestroyShaderModule(
+    VkDevice                                    device,
+    VkShaderModule                              shaderModule,
+    const VkAllocationCallbacks*                pAllocator);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkCreatePipelineCache(
+    VkDevice                                    device,
+    const VkPipelineCacheCreateInfo*            pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkPipelineCache*                            pPipelineCache);
+
+VKAPI_ATTR void VKAPI_CALL vkDestroyPipelineCache(
+    VkDevice                                    device,
+    VkPipelineCache                             pipelineCache,
+    const VkAllocationCallbacks*                pAllocator);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetPipelineCacheData(
+    VkDevice                                    device,
+    VkPipelineCache                             pipelineCache,
+    size_t*                                     pDataSize,
+    void*                                       pData);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkMergePipelineCaches(
+    VkDevice                                    device,
+    VkPipelineCache                             dstCache,
+    uint32_t                                    srcCacheCount,
+    const VkPipelineCache*                      pSrcCaches);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateGraphicsPipelines(
+    VkDevice                                    device,
+    VkPipelineCache                             pipelineCache,
+    uint32_t                                    createInfoCount,
+    const VkGraphicsPipelineCreateInfo*         pCreateInfos,
+    const VkAllocationCallbacks*                pAllocator,
+    VkPipeline*                                 pPipelines);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateComputePipelines(
+    VkDevice                                    device,
+    VkPipelineCache                             pipelineCache,
+    uint32_t                                    createInfoCount,
+    const VkComputePipelineCreateInfo*          pCreateInfos,
+    const VkAllocationCallbacks*                pAllocator,
+    VkPipeline*                                 pPipelines);
+
+VKAPI_ATTR void VKAPI_CALL vkDestroyPipeline(
+    VkDevice                                    device,
+    VkPipeline                                  pipeline,
+    const VkAllocationCallbacks*                pAllocator);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkCreatePipelineLayout(
+    VkDevice                                    device,
+    const VkPipelineLayoutCreateInfo*           pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkPipelineLayout*                           pPipelineLayout);
+
+VKAPI_ATTR void VKAPI_CALL vkDestroyPipelineLayout(
+    VkDevice                                    device,
+    VkPipelineLayout                            pipelineLayout,
+    const VkAllocationCallbacks*                pAllocator);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateSampler(
+    VkDevice                                    device,
+    const VkSamplerCreateInfo*                  pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkSampler*                                  pSampler);
+
+VKAPI_ATTR void VKAPI_CALL vkDestroySampler(
+    VkDevice                                    device,
+    VkSampler                                   sampler,
+    const VkAllocationCallbacks*                pAllocator);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateDescriptorSetLayout(
+    VkDevice                                    device,
+    const VkDescriptorSetLayoutCreateInfo*      pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkDescriptorSetLayout*                      pSetLayout);
+
+VKAPI_ATTR void VKAPI_CALL vkDestroyDescriptorSetLayout(
+    VkDevice                                    device,
+    VkDescriptorSetLayout                       descriptorSetLayout,
+    const VkAllocationCallbacks*                pAllocator);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateDescriptorPool(
+    VkDevice                                    device,
+    const VkDescriptorPoolCreateInfo*           pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkDescriptorPool*                           pDescriptorPool);
+
+VKAPI_ATTR void VKAPI_CALL vkDestroyDescriptorPool(
+    VkDevice                                    device,
+    VkDescriptorPool                            descriptorPool,
+    const VkAllocationCallbacks*                pAllocator);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkResetDescriptorPool(
+    VkDevice                                    device,
+    VkDescriptorPool                            descriptorPool,
+    VkDescriptorPoolResetFlags                  flags);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkAllocateDescriptorSets(
+    VkDevice                                    device,
+    const VkDescriptorSetAllocateInfo*          pAllocateInfo,
+    VkDescriptorSet*                            pDescriptorSets);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkFreeDescriptorSets(
+    VkDevice                                    device,
+    VkDescriptorPool                            descriptorPool,
+    uint32_t                                    descriptorSetCount,
+    const VkDescriptorSet*                      pDescriptorSets);
+
+VKAPI_ATTR void VKAPI_CALL vkUpdateDescriptorSets(
+    VkDevice                                    device,
+    uint32_t                                    descriptorWriteCount,
+    const VkWriteDescriptorSet*                 pDescriptorWrites,
+    uint32_t                                    descriptorCopyCount,
+    const VkCopyDescriptorSet*                  pDescriptorCopies);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateFramebuffer(
+    VkDevice                                    device,
+    const VkFramebufferCreateInfo*              pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkFramebuffer*                              pFramebuffer);
+
+VKAPI_ATTR void VKAPI_CALL vkDestroyFramebuffer(
+    VkDevice                                    device,
+    VkFramebuffer                               framebuffer,
+    const VkAllocationCallbacks*                pAllocator);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateRenderPass(
+    VkDevice                                    device,
+    const VkRenderPassCreateInfo*               pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkRenderPass*                               pRenderPass);
+
+VKAPI_ATTR void VKAPI_CALL vkDestroyRenderPass(
+    VkDevice                                    device,
+    VkRenderPass                                renderPass,
+    const VkAllocationCallbacks*                pAllocator);
+
+VKAPI_ATTR void VKAPI_CALL vkGetRenderAreaGranularity(
+    VkDevice                                    device,
+    VkRenderPass                                renderPass,
+    VkExtent2D*                                 pGranularity);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateCommandPool(
+    VkDevice                                    device,
+    const VkCommandPoolCreateInfo*              pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkCommandPool*                              pCommandPool);
+
+VKAPI_ATTR void VKAPI_CALL vkDestroyCommandPool(
+    VkDevice                                    device,
+    VkCommandPool                               commandPool,
+    const VkAllocationCallbacks*                pAllocator);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkResetCommandPool(
+    VkDevice                                    device,
+    VkCommandPool                               commandPool,
+    VkCommandPoolResetFlags                     flags);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkAllocateCommandBuffers(
+    VkDevice                                    device,
+    const VkCommandBufferAllocateInfo*          pAllocateInfo,
+    VkCommandBuffer*                            pCommandBuffers);
+
+VKAPI_ATTR void VKAPI_CALL vkFreeCommandBuffers(
+    VkDevice                                    device,
+    VkCommandPool                               commandPool,
+    uint32_t                                    commandBufferCount,
+    const VkCommandBuffer*                      pCommandBuffers);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkBeginCommandBuffer(
+    VkCommandBuffer                             commandBuffer,
+    const VkCommandBufferBeginInfo*             pBeginInfo);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkEndCommandBuffer(
+    VkCommandBuffer                             commandBuffer);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkResetCommandBuffer(
+    VkCommandBuffer                             commandBuffer,
+    VkCommandBufferResetFlags                   flags);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdBindPipeline(
+    VkCommandBuffer                             commandBuffer,
+    VkPipelineBindPoint                         pipelineBindPoint,
+    VkPipeline                                  pipeline);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdSetViewport(
+    VkCommandBuffer                             commandBuffer,
+    uint32_t                                    firstViewport,
+    uint32_t                                    viewportCount,
+    const VkViewport*                           pViewports);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdSetScissor(
+    VkCommandBuffer                             commandBuffer,
+    uint32_t                                    firstScissor,
+    uint32_t                                    scissorCount,
+    const VkRect2D*                             pScissors);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdSetLineWidth(
+    VkCommandBuffer                             commandBuffer,
+    float                                       lineWidth);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdSetDepthBias(
+    VkCommandBuffer                             commandBuffer,
+    float                                       depthBiasConstantFactor,
+    float                                       depthBiasClamp,
+    float                                       depthBiasSlopeFactor);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdSetBlendConstants(
+    VkCommandBuffer                             commandBuffer,
+    const float                                 blendConstants[4]);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdSetDepthBounds(
+    VkCommandBuffer                             commandBuffer,
+    float                                       minDepthBounds,
+    float                                       maxDepthBounds);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdSetStencilCompareMask(
+    VkCommandBuffer                             commandBuffer,
+    VkStencilFaceFlags                          faceMask,
+    uint32_t                                    compareMask);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdSetStencilWriteMask(
+    VkCommandBuffer                             commandBuffer,
+    VkStencilFaceFlags                          faceMask,
+    uint32_t                                    writeMask);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdSetStencilReference(
+    VkCommandBuffer                             commandBuffer,
+    VkStencilFaceFlags                          faceMask,
+    uint32_t                                    reference);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdBindDescriptorSets(
+    VkCommandBuffer                             commandBuffer,
+    VkPipelineBindPoint                         pipelineBindPoint,
+    VkPipelineLayout                            layout,
+    uint32_t                                    firstSet,
+    uint32_t                                    descriptorSetCount,
+    const VkDescriptorSet*                      pDescriptorSets,
+    uint32_t                                    dynamicOffsetCount,
+    const uint32_t*                             pDynamicOffsets);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdBindIndexBuffer(
+    VkCommandBuffer                             commandBuffer,
+    VkBuffer                                    buffer,
+    VkDeviceSize                                offset,
+    VkIndexType                                 indexType);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdBindVertexBuffers(
+    VkCommandBuffer                             commandBuffer,
+    uint32_t                                    firstBinding,
+    uint32_t                                    bindingCount,
+    const VkBuffer*                             pBuffers,
+    const VkDeviceSize*                         pOffsets);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdDraw(
+    VkCommandBuffer                             commandBuffer,
+    uint32_t                                    vertexCount,
+    uint32_t                                    instanceCount,
+    uint32_t                                    firstVertex,
+    uint32_t                                    firstInstance);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdDrawIndexed(
+    VkCommandBuffer                             commandBuffer,
+    uint32_t                                    indexCount,
+    uint32_t                                    instanceCount,
+    uint32_t                                    firstIndex,
+    int32_t                                     vertexOffset,
+    uint32_t                                    firstInstance);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdDrawIndirect(
+    VkCommandBuffer                             commandBuffer,
+    VkBuffer                                    buffer,
+    VkDeviceSize                                offset,
+    uint32_t                                    drawCount,
+    uint32_t                                    stride);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdDrawIndexedIndirect(
+    VkCommandBuffer                             commandBuffer,
+    VkBuffer                                    buffer,
+    VkDeviceSize                                offset,
+    uint32_t                                    drawCount,
+    uint32_t                                    stride);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdDispatch(
+    VkCommandBuffer                             commandBuffer,
+    uint32_t                                    groupCountX,
+    uint32_t                                    groupCountY,
+    uint32_t                                    groupCountZ);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdDispatchIndirect(
+    VkCommandBuffer                             commandBuffer,
+    VkBuffer                                    buffer,
+    VkDeviceSize                                offset);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdCopyBuffer(
+    VkCommandBuffer                             commandBuffer,
+    VkBuffer                                    srcBuffer,
+    VkBuffer                                    dstBuffer,
+    uint32_t                                    regionCount,
+    const VkBufferCopy*                         pRegions);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdCopyImage(
+    VkCommandBuffer                             commandBuffer,
+    VkImage                                     srcImage,
+    VkImageLayout                               srcImageLayout,
+    VkImage                                     dstImage,
+    VkImageLayout                               dstImageLayout,
+    uint32_t                                    regionCount,
+    const VkImageCopy*                          pRegions);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdBlitImage(
+    VkCommandBuffer                             commandBuffer,
+    VkImage                                     srcImage,
+    VkImageLayout                               srcImageLayout,
+    VkImage                                     dstImage,
+    VkImageLayout                               dstImageLayout,
+    uint32_t                                    regionCount,
+    const VkImageBlit*                          pRegions,
+    VkFilter                                    filter);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdCopyBufferToImage(
+    VkCommandBuffer                             commandBuffer,
+    VkBuffer                                    srcBuffer,
+    VkImage                                     dstImage,
+    VkImageLayout                               dstImageLayout,
+    uint32_t                                    regionCount,
+    const VkBufferImageCopy*                    pRegions);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdCopyImageToBuffer(
+    VkCommandBuffer                             commandBuffer,
+    VkImage                                     srcImage,
+    VkImageLayout                               srcImageLayout,
+    VkBuffer                                    dstBuffer,
+    uint32_t                                    regionCount,
+    const VkBufferImageCopy*                    pRegions);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdUpdateBuffer(
+    VkCommandBuffer                             commandBuffer,
+    VkBuffer                                    dstBuffer,
+    VkDeviceSize                                dstOffset,
+    VkDeviceSize                                dataSize,
+    const void*                                 pData);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdFillBuffer(
+    VkCommandBuffer                             commandBuffer,
+    VkBuffer                                    dstBuffer,
+    VkDeviceSize                                dstOffset,
+    VkDeviceSize                                size,
+    uint32_t                                    data);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdClearColorImage(
+    VkCommandBuffer                             commandBuffer,
+    VkImage                                     image,
+    VkImageLayout                               imageLayout,
+    const VkClearColorValue*                    pColor,
+    uint32_t                                    rangeCount,
+    const VkImageSubresourceRange*              pRanges);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdClearDepthStencilImage(
+    VkCommandBuffer                             commandBuffer,
+    VkImage                                     image,
+    VkImageLayout                               imageLayout,
+    const VkClearDepthStencilValue*             pDepthStencil,
+    uint32_t                                    rangeCount,
+    const VkImageSubresourceRange*              pRanges);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdClearAttachments(
+    VkCommandBuffer                             commandBuffer,
+    uint32_t                                    attachmentCount,
+    const VkClearAttachment*                    pAttachments,
+    uint32_t                                    rectCount,
+    const VkClearRect*                          pRects);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdResolveImage(
+    VkCommandBuffer                             commandBuffer,
+    VkImage                                     srcImage,
+    VkImageLayout                               srcImageLayout,
+    VkImage                                     dstImage,
+    VkImageLayout                               dstImageLayout,
+    uint32_t                                    regionCount,
+    const VkImageResolve*                       pRegions);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdSetEvent(
+    VkCommandBuffer                             commandBuffer,
+    VkEvent                                     event,
+    VkPipelineStageFlags                        stageMask);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdResetEvent(
+    VkCommandBuffer                             commandBuffer,
+    VkEvent                                     event,
+    VkPipelineStageFlags                        stageMask);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdWaitEvents(
+    VkCommandBuffer                             commandBuffer,
+    uint32_t                                    eventCount,
+    const VkEvent*                              pEvents,
+    VkPipelineStageFlags                        srcStageMask,
+    VkPipelineStageFlags                        dstStageMask,
+    uint32_t                                    memoryBarrierCount,
+    const VkMemoryBarrier*                      pMemoryBarriers,
+    uint32_t                                    bufferMemoryBarrierCount,
+    const VkBufferMemoryBarrier*                pBufferMemoryBarriers,
+    uint32_t                                    imageMemoryBarrierCount,
+    const VkImageMemoryBarrier*                 pImageMemoryBarriers);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdPipelineBarrier(
+    VkCommandBuffer                             commandBuffer,
+    VkPipelineStageFlags                        srcStageMask,
+    VkPipelineStageFlags                        dstStageMask,
+    VkDependencyFlags                           dependencyFlags,
+    uint32_t                                    memoryBarrierCount,
+    const VkMemoryBarrier*                      pMemoryBarriers,
+    uint32_t                                    bufferMemoryBarrierCount,
+    const VkBufferMemoryBarrier*                pBufferMemoryBarriers,
+    uint32_t                                    imageMemoryBarrierCount,
+    const VkImageMemoryBarrier*                 pImageMemoryBarriers);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdBeginQuery(
+    VkCommandBuffer                             commandBuffer,
+    VkQueryPool                                 queryPool,
+    uint32_t                                    query,
+    VkQueryControlFlags                         flags);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdEndQuery(
+    VkCommandBuffer                             commandBuffer,
+    VkQueryPool                                 queryPool,
+    uint32_t                                    query);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdResetQueryPool(
+    VkCommandBuffer                             commandBuffer,
+    VkQueryPool                                 queryPool,
+    uint32_t                                    firstQuery,
+    uint32_t                                    queryCount);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdWriteTimestamp(
+    VkCommandBuffer                             commandBuffer,
+    VkPipelineStageFlagBits                     pipelineStage,
+    VkQueryPool                                 queryPool,
+    uint32_t                                    query);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdCopyQueryPoolResults(
+    VkCommandBuffer                             commandBuffer,
+    VkQueryPool                                 queryPool,
+    uint32_t                                    firstQuery,
+    uint32_t                                    queryCount,
+    VkBuffer                                    dstBuffer,
+    VkDeviceSize                                dstOffset,
+    VkDeviceSize                                stride,
+    VkQueryResultFlags                          flags);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdPushConstants(
+    VkCommandBuffer                             commandBuffer,
+    VkPipelineLayout                            layout,
+    VkShaderStageFlags                          stageFlags,
+    uint32_t                                    offset,
+    uint32_t                                    size,
+    const void*                                 pValues);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdBeginRenderPass(
+    VkCommandBuffer                             commandBuffer,
+    const VkRenderPassBeginInfo*                pRenderPassBegin,
+    VkSubpassContents                           contents);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdNextSubpass(
+    VkCommandBuffer                             commandBuffer,
+    VkSubpassContents                           contents);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdEndRenderPass(
+    VkCommandBuffer                             commandBuffer);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdExecuteCommands(
+    VkCommandBuffer                             commandBuffer,
+    uint32_t                                    commandBufferCount,
+    const VkCommandBuffer*                      pCommandBuffers);
+#endif
+
+
+#define VK_VERSION_1_1 1
+// Vulkan 1.1 version number
+#define VK_API_VERSION_1_1 VK_MAKE_API_VERSION(0, 1, 1, 0)// Patch version should always be set to 0
+
+VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkSamplerYcbcrConversion)
+VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkDescriptorUpdateTemplate)
+#define VK_MAX_DEVICE_GROUP_SIZE          32U
+#define VK_LUID_SIZE                      8U
+#define VK_QUEUE_FAMILY_EXTERNAL          (~1U)
+
+typedef enum VkPointClippingBehavior {
+    VK_POINT_CLIPPING_BEHAVIOR_ALL_CLIP_PLANES = 0,
+    VK_POINT_CLIPPING_BEHAVIOR_USER_CLIP_PLANES_ONLY = 1,
+    VK_POINT_CLIPPING_BEHAVIOR_ALL_CLIP_PLANES_KHR = VK_POINT_CLIPPING_BEHAVIOR_ALL_CLIP_PLANES,
+    VK_POINT_CLIPPING_BEHAVIOR_USER_CLIP_PLANES_ONLY_KHR = VK_POINT_CLIPPING_BEHAVIOR_USER_CLIP_PLANES_ONLY,
+    VK_POINT_CLIPPING_BEHAVIOR_MAX_ENUM = 0x7FFFFFFF
+} VkPointClippingBehavior;
+
+typedef enum VkTessellationDomainOrigin {
+    VK_TESSELLATION_DOMAIN_ORIGIN_UPPER_LEFT = 0,
+    VK_TESSELLATION_DOMAIN_ORIGIN_LOWER_LEFT = 1,
+    VK_TESSELLATION_DOMAIN_ORIGIN_UPPER_LEFT_KHR = VK_TESSELLATION_DOMAIN_ORIGIN_UPPER_LEFT,
+    VK_TESSELLATION_DOMAIN_ORIGIN_LOWER_LEFT_KHR = VK_TESSELLATION_DOMAIN_ORIGIN_LOWER_LEFT,
+    VK_TESSELLATION_DOMAIN_ORIGIN_MAX_ENUM = 0x7FFFFFFF
+} VkTessellationDomainOrigin;
+
+typedef enum VkSamplerYcbcrModelConversion {
+    VK_SAMPLER_YCBCR_MODEL_CONVERSION_RGB_IDENTITY = 0,
+    VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_IDENTITY = 1,
+    VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_709 = 2,
+    VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_601 = 3,
+    VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_2020 = 4,
+    VK_SAMPLER_YCBCR_MODEL_CONVERSION_RGB_IDENTITY_KHR = VK_SAMPLER_YCBCR_MODEL_CONVERSION_RGB_IDENTITY,
+    VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_IDENTITY_KHR = VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_IDENTITY,
+    VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_709_KHR = VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_709,
+    VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_601_KHR = VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_601,
+    VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_2020_KHR = VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_2020,
+    VK_SAMPLER_YCBCR_MODEL_CONVERSION_MAX_ENUM = 0x7FFFFFFF
+} VkSamplerYcbcrModelConversion;
+
+typedef enum VkSamplerYcbcrRange {
+    VK_SAMPLER_YCBCR_RANGE_ITU_FULL = 0,
+    VK_SAMPLER_YCBCR_RANGE_ITU_NARROW = 1,
+    VK_SAMPLER_YCBCR_RANGE_ITU_FULL_KHR = VK_SAMPLER_YCBCR_RANGE_ITU_FULL,
+    VK_SAMPLER_YCBCR_RANGE_ITU_NARROW_KHR = VK_SAMPLER_YCBCR_RANGE_ITU_NARROW,
+    VK_SAMPLER_YCBCR_RANGE_MAX_ENUM = 0x7FFFFFFF
+} VkSamplerYcbcrRange;
+
+typedef enum VkChromaLocation {
+    VK_CHROMA_LOCATION_COSITED_EVEN = 0,
+    VK_CHROMA_LOCATION_MIDPOINT = 1,
+    VK_CHROMA_LOCATION_COSITED_EVEN_KHR = VK_CHROMA_LOCATION_COSITED_EVEN,
+    VK_CHROMA_LOCATION_MIDPOINT_KHR = VK_CHROMA_LOCATION_MIDPOINT,
+    VK_CHROMA_LOCATION_MAX_ENUM = 0x7FFFFFFF
+} VkChromaLocation;
+
+typedef enum VkDescriptorUpdateTemplateType {
+    VK_DESCRIPTOR_UPDATE_TEMPLATE_TYPE_DESCRIPTOR_SET = 0,
+    VK_DESCRIPTOR_UPDATE_TEMPLATE_TYPE_PUSH_DESCRIPTORS_KHR = 1,
+    VK_DESCRIPTOR_UPDATE_TEMPLATE_TYPE_DESCRIPTOR_SET_KHR = VK_DESCRIPTOR_UPDATE_TEMPLATE_TYPE_DESCRIPTOR_SET,
+    VK_DESCRIPTOR_UPDATE_TEMPLATE_TYPE_MAX_ENUM = 0x7FFFFFFF
+} VkDescriptorUpdateTemplateType;
+
+typedef enum VkSubgroupFeatureFlagBits {
+    VK_SUBGROUP_FEATURE_BASIC_BIT = 0x00000001,
+    VK_SUBGROUP_FEATURE_VOTE_BIT = 0x00000002,
+    VK_SUBGROUP_FEATURE_ARITHMETIC_BIT = 0x00000004,
+    VK_SUBGROUP_FEATURE_BALLOT_BIT = 0x00000008,
+    VK_SUBGROUP_FEATURE_SHUFFLE_BIT = 0x00000010,
+    VK_SUBGROUP_FEATURE_SHUFFLE_RELATIVE_BIT = 0x00000020,
+    VK_SUBGROUP_FEATURE_CLUSTERED_BIT = 0x00000040,
+    VK_SUBGROUP_FEATURE_QUAD_BIT = 0x00000080,
+    VK_SUBGROUP_FEATURE_PARTITIONED_BIT_NV = 0x00000100,
+    VK_SUBGROUP_FEATURE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkSubgroupFeatureFlagBits;
+typedef VkFlags VkSubgroupFeatureFlags;
+
+typedef enum VkPeerMemoryFeatureFlagBits {
+    VK_PEER_MEMORY_FEATURE_COPY_SRC_BIT = 0x00000001,
+    VK_PEER_MEMORY_FEATURE_COPY_DST_BIT = 0x00000002,
+    VK_PEER_MEMORY_FEATURE_GENERIC_SRC_BIT = 0x00000004,
+    VK_PEER_MEMORY_FEATURE_GENERIC_DST_BIT = 0x00000008,
+    VK_PEER_MEMORY_FEATURE_COPY_SRC_BIT_KHR = VK_PEER_MEMORY_FEATURE_COPY_SRC_BIT,
+    VK_PEER_MEMORY_FEATURE_COPY_DST_BIT_KHR = VK_PEER_MEMORY_FEATURE_COPY_DST_BIT,
+    VK_PEER_MEMORY_FEATURE_GENERIC_SRC_BIT_KHR = VK_PEER_MEMORY_FEATURE_GENERIC_SRC_BIT,
+    VK_PEER_MEMORY_FEATURE_GENERIC_DST_BIT_KHR = VK_PEER_MEMORY_FEATURE_GENERIC_DST_BIT,
+    VK_PEER_MEMORY_FEATURE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkPeerMemoryFeatureFlagBits;
+typedef VkFlags VkPeerMemoryFeatureFlags;
+
+typedef enum VkMemoryAllocateFlagBits {
+    VK_MEMORY_ALLOCATE_DEVICE_MASK_BIT = 0x00000001,
+    VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT = 0x00000002,
+    VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_CAPTURE_REPLAY_BIT = 0x00000004,
+    VK_MEMORY_ALLOCATE_DEVICE_MASK_BIT_KHR = VK_MEMORY_ALLOCATE_DEVICE_MASK_BIT,
+    VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT_KHR = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT,
+    VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_CAPTURE_REPLAY_BIT_KHR = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_CAPTURE_REPLAY_BIT,
+    VK_MEMORY_ALLOCATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkMemoryAllocateFlagBits;
+typedef VkFlags VkMemoryAllocateFlags;
+typedef VkFlags VkCommandPoolTrimFlags;
+typedef VkFlags VkDescriptorUpdateTemplateCreateFlags;
+
+typedef enum VkExternalMemoryHandleTypeFlagBits {
+    VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT = 0x00000001,
+    VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT = 0x00000002,
+    VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_KMT_BIT = 0x00000004,
+    VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D11_TEXTURE_BIT = 0x00000008,
+    VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D11_TEXTURE_KMT_BIT = 0x00000010,
+    VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D12_HEAP_BIT = 0x00000020,
+    VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D12_RESOURCE_BIT = 0x00000040,
+    VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT = 0x00000200,
+    VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID = 0x00000400,
+    VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT = 0x00000080,
+    VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_MAPPED_FOREIGN_MEMORY_BIT_EXT = 0x00000100,
+    VK_EXTERNAL_MEMORY_HANDLE_TYPE_ZIRCON_VMO_BIT_FUCHSIA = 0x00000800,
+    VK_EXTERNAL_MEMORY_HANDLE_TYPE_RDMA_ADDRESS_BIT_NV = 0x00001000,
+    VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT,
+    VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT_KHR = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT,
+    VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_KMT_BIT_KHR = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_KMT_BIT,
+    VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D11_TEXTURE_BIT_KHR = VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D11_TEXTURE_BIT,
+    VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D11_TEXTURE_KMT_BIT_KHR = VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D11_TEXTURE_KMT_BIT,
+    VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D12_HEAP_BIT_KHR = VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D12_HEAP_BIT,
+    VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D12_RESOURCE_BIT_KHR = VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D12_RESOURCE_BIT,
+    VK_EXTERNAL_MEMORY_HANDLE_TYPE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkExternalMemoryHandleTypeFlagBits;
+typedef VkFlags VkExternalMemoryHandleTypeFlags;
+
+typedef enum VkExternalMemoryFeatureFlagBits {
+    VK_EXTERNAL_MEMORY_FEATURE_DEDICATED_ONLY_BIT = 0x00000001,
+    VK_EXTERNAL_MEMORY_FEATURE_EXPORTABLE_BIT = 0x00000002,
+    VK_EXTERNAL_MEMORY_FEATURE_IMPORTABLE_BIT = 0x00000004,
+    VK_EXTERNAL_MEMORY_FEATURE_DEDICATED_ONLY_BIT_KHR = VK_EXTERNAL_MEMORY_FEATURE_DEDICATED_ONLY_BIT,
+    VK_EXTERNAL_MEMORY_FEATURE_EXPORTABLE_BIT_KHR = VK_EXTERNAL_MEMORY_FEATURE_EXPORTABLE_BIT,
+    VK_EXTERNAL_MEMORY_FEATURE_IMPORTABLE_BIT_KHR = VK_EXTERNAL_MEMORY_FEATURE_IMPORTABLE_BIT,
+    VK_EXTERNAL_MEMORY_FEATURE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkExternalMemoryFeatureFlagBits;
+typedef VkFlags VkExternalMemoryFeatureFlags;
+
+typedef enum VkExternalFenceHandleTypeFlagBits {
+    VK_EXTERNAL_FENCE_HANDLE_TYPE_OPAQUE_FD_BIT = 0x00000001,
+    VK_EXTERNAL_FENCE_HANDLE_TYPE_OPAQUE_WIN32_BIT = 0x00000002,
+    VK_EXTERNAL_FENCE_HANDLE_TYPE_OPAQUE_WIN32_KMT_BIT = 0x00000004,
+    VK_EXTERNAL_FENCE_HANDLE_TYPE_SYNC_FD_BIT = 0x00000008,
+    VK_EXTERNAL_FENCE_HANDLE_TYPE_OPAQUE_FD_BIT_KHR = VK_EXTERNAL_FENCE_HANDLE_TYPE_OPAQUE_FD_BIT,
+    VK_EXTERNAL_FENCE_HANDLE_TYPE_OPAQUE_WIN32_BIT_KHR = VK_EXTERNAL_FENCE_HANDLE_TYPE_OPAQUE_WIN32_BIT,
+    VK_EXTERNAL_FENCE_HANDLE_TYPE_OPAQUE_WIN32_KMT_BIT_KHR = VK_EXTERNAL_FENCE_HANDLE_TYPE_OPAQUE_WIN32_KMT_BIT,
+    VK_EXTERNAL_FENCE_HANDLE_TYPE_SYNC_FD_BIT_KHR = VK_EXTERNAL_FENCE_HANDLE_TYPE_SYNC_FD_BIT,
+    VK_EXTERNAL_FENCE_HANDLE_TYPE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkExternalFenceHandleTypeFlagBits;
+typedef VkFlags VkExternalFenceHandleTypeFlags;
+
+typedef enum VkExternalFenceFeatureFlagBits {
+    VK_EXTERNAL_FENCE_FEATURE_EXPORTABLE_BIT = 0x00000001,
+    VK_EXTERNAL_FENCE_FEATURE_IMPORTABLE_BIT = 0x00000002,
+    VK_EXTERNAL_FENCE_FEATURE_EXPORTABLE_BIT_KHR = VK_EXTERNAL_FENCE_FEATURE_EXPORTABLE_BIT,
+    VK_EXTERNAL_FENCE_FEATURE_IMPORTABLE_BIT_KHR = VK_EXTERNAL_FENCE_FEATURE_IMPORTABLE_BIT,
+    VK_EXTERNAL_FENCE_FEATURE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkExternalFenceFeatureFlagBits;
+typedef VkFlags VkExternalFenceFeatureFlags;
+
+typedef enum VkFenceImportFlagBits {
+    VK_FENCE_IMPORT_TEMPORARY_BIT = 0x00000001,
+    VK_FENCE_IMPORT_TEMPORARY_BIT_KHR = VK_FENCE_IMPORT_TEMPORARY_BIT,
+    VK_FENCE_IMPORT_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkFenceImportFlagBits;
+typedef VkFlags VkFenceImportFlags;
+
+typedef enum VkSemaphoreImportFlagBits {
+    VK_SEMAPHORE_IMPORT_TEMPORARY_BIT = 0x00000001,
+    VK_SEMAPHORE_IMPORT_TEMPORARY_BIT_KHR = VK_SEMAPHORE_IMPORT_TEMPORARY_BIT,
+    VK_SEMAPHORE_IMPORT_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkSemaphoreImportFlagBits;
+typedef VkFlags VkSemaphoreImportFlags;
+
+typedef enum VkExternalSemaphoreHandleTypeFlagBits {
+    VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT = 0x00000001,
+    VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_WIN32_BIT = 0x00000002,
+    VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_WIN32_KMT_BIT = 0x00000004,
+    VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_D3D12_FENCE_BIT = 0x00000008,
+    VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT = 0x00000010,
+    VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_ZIRCON_EVENT_BIT_FUCHSIA = 0x00000080,
+    VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_D3D11_FENCE_BIT = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_D3D12_FENCE_BIT,
+    VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT_KHR = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT,
+    VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_WIN32_BIT_KHR = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_WIN32_BIT,
+    VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_WIN32_KMT_BIT_KHR = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_WIN32_KMT_BIT,
+    VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_D3D12_FENCE_BIT_KHR = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_D3D12_FENCE_BIT,
+    VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT_KHR = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
+    VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkExternalSemaphoreHandleTypeFlagBits;
+typedef VkFlags VkExternalSemaphoreHandleTypeFlags;
+
+typedef enum VkExternalSemaphoreFeatureFlagBits {
+    VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT = 0x00000001,
+    VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT = 0x00000002,
+    VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT_KHR = VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT,
+    VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT_KHR = VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT,
+    VK_EXTERNAL_SEMAPHORE_FEATURE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkExternalSemaphoreFeatureFlagBits;
+typedef VkFlags VkExternalSemaphoreFeatureFlags;
+typedef struct VkPhysicalDeviceSubgroupProperties {
+    VkStructureType           sType;
+    void*                     pNext;
+    uint32_t                  subgroupSize;
+    VkShaderStageFlags        supportedStages;
+    VkSubgroupFeatureFlags    supportedOperations;
+    VkBool32                  quadOperationsInAllStages;
+} VkPhysicalDeviceSubgroupProperties;
+
+typedef struct VkBindBufferMemoryInfo {
+    VkStructureType    sType;
+    const void*        pNext;
+    VkBuffer           buffer;
+    VkDeviceMemory     memory;
+    VkDeviceSize       memoryOffset;
+} VkBindBufferMemoryInfo;
+
+typedef struct VkBindImageMemoryInfo {
+    VkStructureType    sType;
+    const void*        pNext;
+    VkImage            image;
+    VkDeviceMemory     memory;
+    VkDeviceSize       memoryOffset;
+} VkBindImageMemoryInfo;
+
+typedef struct VkPhysicalDevice16BitStorageFeatures {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           storageBuffer16BitAccess;
+    VkBool32           uniformAndStorageBuffer16BitAccess;
+    VkBool32           storagePushConstant16;
+    VkBool32           storageInputOutput16;
+} VkPhysicalDevice16BitStorageFeatures;
+
+typedef struct VkMemoryDedicatedRequirements {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           prefersDedicatedAllocation;
+    VkBool32           requiresDedicatedAllocation;
+} VkMemoryDedicatedRequirements;
+
+typedef struct VkMemoryDedicatedAllocateInfo {
+    VkStructureType    sType;
+    const void*        pNext;
+    VkImage            image;
+    VkBuffer           buffer;
+} VkMemoryDedicatedAllocateInfo;
+
+typedef struct VkMemoryAllocateFlagsInfo {
+    VkStructureType          sType;
+    const void*              pNext;
+    VkMemoryAllocateFlags    flags;
+    uint32_t                 deviceMask;
+} VkMemoryAllocateFlagsInfo;
+
+typedef struct VkDeviceGroupRenderPassBeginInfo {
+    VkStructureType    sType;
+    const void*        pNext;
+    uint32_t           deviceMask;
+    uint32_t           deviceRenderAreaCount;
+    const VkRect2D*    pDeviceRenderAreas;
+} VkDeviceGroupRenderPassBeginInfo;
+
+typedef struct VkDeviceGroupCommandBufferBeginInfo {
+    VkStructureType    sType;
+    const void*        pNext;
+    uint32_t           deviceMask;
+} VkDeviceGroupCommandBufferBeginInfo;
+
+typedef struct VkDeviceGroupSubmitInfo {
+    VkStructureType    sType;
+    const void*        pNext;
+    uint32_t           waitSemaphoreCount;
+    const uint32_t*    pWaitSemaphoreDeviceIndices;
+    uint32_t           commandBufferCount;
+    const uint32_t*    pCommandBufferDeviceMasks;
+    uint32_t           signalSemaphoreCount;
+    const uint32_t*    pSignalSemaphoreDeviceIndices;
+} VkDeviceGroupSubmitInfo;
+
+typedef struct VkDeviceGroupBindSparseInfo {
+    VkStructureType    sType;
+    const void*        pNext;
+    uint32_t           resourceDeviceIndex;
+    uint32_t           memoryDeviceIndex;
+} VkDeviceGroupBindSparseInfo;
+
+typedef struct VkBindBufferMemoryDeviceGroupInfo {
+    VkStructureType    sType;
+    const void*        pNext;
+    uint32_t           deviceIndexCount;
+    const uint32_t*    pDeviceIndices;
+} VkBindBufferMemoryDeviceGroupInfo;
+
+typedef struct VkBindImageMemoryDeviceGroupInfo {
+    VkStructureType    sType;
+    const void*        pNext;
+    uint32_t           deviceIndexCount;
+    const uint32_t*    pDeviceIndices;
+    uint32_t           splitInstanceBindRegionCount;
+    const VkRect2D*    pSplitInstanceBindRegions;
+} VkBindImageMemoryDeviceGroupInfo;
+
+typedef struct VkPhysicalDeviceGroupProperties {
+    VkStructureType     sType;
+    void*               pNext;
+    uint32_t            physicalDeviceCount;
+    VkPhysicalDevice    physicalDevices[VK_MAX_DEVICE_GROUP_SIZE];
+    VkBool32            subsetAllocation;
+} VkPhysicalDeviceGroupProperties;
+
+typedef struct VkDeviceGroupDeviceCreateInfo {
+    VkStructureType            sType;
+    const void*                pNext;
+    uint32_t                   physicalDeviceCount;
+    const VkPhysicalDevice*    pPhysicalDevices;
+} VkDeviceGroupDeviceCreateInfo;
+
+typedef struct VkBufferMemoryRequirementsInfo2 {
+    VkStructureType    sType;
+    const void*        pNext;
+    VkBuffer           buffer;
+} VkBufferMemoryRequirementsInfo2;
+
+typedef struct VkImageMemoryRequirementsInfo2 {
+    VkStructureType    sType;
+    const void*        pNext;
+    VkImage            image;
+} VkImageMemoryRequirementsInfo2;
+
+typedef struct VkImageSparseMemoryRequirementsInfo2 {
+    VkStructureType    sType;
+    const void*        pNext;
+    VkImage            image;
+} VkImageSparseMemoryRequirementsInfo2;
+
+typedef struct VkMemoryRequirements2 {
+    VkStructureType         sType;
+    void*                   pNext;
+    VkMemoryRequirements    memoryRequirements;
+} VkMemoryRequirements2;
+
+typedef struct VkSparseImageMemoryRequirements2 {
+    VkStructureType                    sType;
+    void*                              pNext;
+    VkSparseImageMemoryRequirements    memoryRequirements;
+} VkSparseImageMemoryRequirements2;
+
+typedef struct VkPhysicalDeviceFeatures2 {
+    VkStructureType             sType;
+    void*                       pNext;
+    VkPhysicalDeviceFeatures    features;
+} VkPhysicalDeviceFeatures2;
+
+typedef struct VkPhysicalDeviceProperties2 {
+    VkStructureType               sType;
+    void*                         pNext;
+    VkPhysicalDeviceProperties    properties;
+} VkPhysicalDeviceProperties2;
+
+typedef struct VkFormatProperties2 {
+    VkStructureType       sType;
+    void*                 pNext;
+    VkFormatProperties    formatProperties;
+} VkFormatProperties2;
+
+typedef struct VkImageFormatProperties2 {
+    VkStructureType            sType;
+    void*                      pNext;
+    VkImageFormatProperties    imageFormatProperties;
+} VkImageFormatProperties2;
+
+typedef struct VkPhysicalDeviceImageFormatInfo2 {
+    VkStructureType       sType;
+    const void*           pNext;
+    VkFormat              format;
+    VkImageType           type;
+    VkImageTiling         tiling;
+    VkImageUsageFlags     usage;
+    VkImageCreateFlags    flags;
+} VkPhysicalDeviceImageFormatInfo2;
+
+typedef struct VkQueueFamilyProperties2 {
+    VkStructureType            sType;
+    void*                      pNext;
+    VkQueueFamilyProperties    queueFamilyProperties;
+} VkQueueFamilyProperties2;
+
+typedef struct VkPhysicalDeviceMemoryProperties2 {
+    VkStructureType                     sType;
+    void*                               pNext;
+    VkPhysicalDeviceMemoryProperties    memoryProperties;
+} VkPhysicalDeviceMemoryProperties2;
+
+typedef struct VkSparseImageFormatProperties2 {
+    VkStructureType                  sType;
+    void*                            pNext;
+    VkSparseImageFormatProperties    properties;
+} VkSparseImageFormatProperties2;
+
+typedef struct VkPhysicalDeviceSparseImageFormatInfo2 {
+    VkStructureType          sType;
+    const void*              pNext;
+    VkFormat                 format;
+    VkImageType              type;
+    VkSampleCountFlagBits    samples;
+    VkImageUsageFlags        usage;
+    VkImageTiling            tiling;
+} VkPhysicalDeviceSparseImageFormatInfo2;
+
+typedef struct VkPhysicalDevicePointClippingProperties {
+    VkStructureType            sType;
+    void*                      pNext;
+    VkPointClippingBehavior    pointClippingBehavior;
+} VkPhysicalDevicePointClippingProperties;
+
+typedef struct VkInputAttachmentAspectReference {
+    uint32_t              subpass;
+    uint32_t              inputAttachmentIndex;
+    VkImageAspectFlags    aspectMask;
+} VkInputAttachmentAspectReference;
+
+typedef struct VkRenderPassInputAttachmentAspectCreateInfo {
+    VkStructureType                            sType;
+    const void*                                pNext;
+    uint32_t                                   aspectReferenceCount;
+    const VkInputAttachmentAspectReference*    pAspectReferences;
+} VkRenderPassInputAttachmentAspectCreateInfo;
+
+typedef struct VkImageViewUsageCreateInfo {
+    VkStructureType      sType;
+    const void*          pNext;
+    VkImageUsageFlags    usage;
+} VkImageViewUsageCreateInfo;
+
+typedef struct VkPipelineTessellationDomainOriginStateCreateInfo {
+    VkStructureType               sType;
+    const void*                   pNext;
+    VkTessellationDomainOrigin    domainOrigin;
+} VkPipelineTessellationDomainOriginStateCreateInfo;
+
+typedef struct VkRenderPassMultiviewCreateInfo {
+    VkStructureType    sType;
+    const void*        pNext;
+    uint32_t           subpassCount;
+    const uint32_t*    pViewMasks;
+    uint32_t           dependencyCount;
+    const int32_t*     pViewOffsets;
+    uint32_t           correlationMaskCount;
+    const uint32_t*    pCorrelationMasks;
+} VkRenderPassMultiviewCreateInfo;
+
+typedef struct VkPhysicalDeviceMultiviewFeatures {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           multiview;
+    VkBool32           multiviewGeometryShader;
+    VkBool32           multiviewTessellationShader;
+} VkPhysicalDeviceMultiviewFeatures;
+
+typedef struct VkPhysicalDeviceMultiviewProperties {
+    VkStructureType    sType;
+    void*              pNext;
+    uint32_t           maxMultiviewViewCount;
+    uint32_t           maxMultiviewInstanceIndex;
+} VkPhysicalDeviceMultiviewProperties;
+
+typedef struct VkPhysicalDeviceVariablePointersFeatures {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           variablePointersStorageBuffer;
+    VkBool32           variablePointers;
+} VkPhysicalDeviceVariablePointersFeatures;
+
+typedef VkPhysicalDeviceVariablePointersFeatures VkPhysicalDeviceVariablePointerFeatures;
+
+typedef struct VkPhysicalDeviceProtectedMemoryFeatures {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           protectedMemory;
+} VkPhysicalDeviceProtectedMemoryFeatures;
+
+typedef struct VkPhysicalDeviceProtectedMemoryProperties {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           protectedNoFault;
+} VkPhysicalDeviceProtectedMemoryProperties;
+
+typedef struct VkDeviceQueueInfo2 {
+    VkStructureType             sType;
+    const void*                 pNext;
+    VkDeviceQueueCreateFlags    flags;
+    uint32_t                    queueFamilyIndex;
+    uint32_t                    queueIndex;
+} VkDeviceQueueInfo2;
+
+typedef struct VkProtectedSubmitInfo {
+    VkStructureType    sType;
+    const void*        pNext;
+    VkBool32           protectedSubmit;
+} VkProtectedSubmitInfo;
+
+typedef struct VkSamplerYcbcrConversionCreateInfo {
+    VkStructureType                  sType;
+    const void*                      pNext;
+    VkFormat                         format;
+    VkSamplerYcbcrModelConversion    ycbcrModel;
+    VkSamplerYcbcrRange              ycbcrRange;
+    VkComponentMapping               components;
+    VkChromaLocation                 xChromaOffset;
+    VkChromaLocation                 yChromaOffset;
+    VkFilter                         chromaFilter;
+    VkBool32                         forceExplicitReconstruction;
+} VkSamplerYcbcrConversionCreateInfo;
+
+typedef struct VkSamplerYcbcrConversionInfo {
+    VkStructureType             sType;
+    const void*                 pNext;
+    VkSamplerYcbcrConversion    conversion;
+} VkSamplerYcbcrConversionInfo;
+
+typedef struct VkBindImagePlaneMemoryInfo {
+    VkStructureType          sType;
+    const void*              pNext;
+    VkImageAspectFlagBits    planeAspect;
+} VkBindImagePlaneMemoryInfo;
+
+typedef struct VkImagePlaneMemoryRequirementsInfo {
+    VkStructureType          sType;
+    const void*              pNext;
+    VkImageAspectFlagBits    planeAspect;
+} VkImagePlaneMemoryRequirementsInfo;
+
+typedef struct VkPhysicalDeviceSamplerYcbcrConversionFeatures {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           samplerYcbcrConversion;
+} VkPhysicalDeviceSamplerYcbcrConversionFeatures;
+
+typedef struct VkSamplerYcbcrConversionImageFormatProperties {
+    VkStructureType    sType;
+    void*              pNext;
+    uint32_t           combinedImageSamplerDescriptorCount;
+} VkSamplerYcbcrConversionImageFormatProperties;
+
+typedef struct VkDescriptorUpdateTemplateEntry {
+    uint32_t            dstBinding;
+    uint32_t            dstArrayElement;
+    uint32_t            descriptorCount;
+    VkDescriptorType    descriptorType;
+    size_t              offset;
+    size_t              stride;
+} VkDescriptorUpdateTemplateEntry;
+
+typedef struct VkDescriptorUpdateTemplateCreateInfo {
+    VkStructureType                           sType;
+    const void*                               pNext;
+    VkDescriptorUpdateTemplateCreateFlags     flags;
+    uint32_t                                  descriptorUpdateEntryCount;
+    const VkDescriptorUpdateTemplateEntry*    pDescriptorUpdateEntries;
+    VkDescriptorUpdateTemplateType            templateType;
+    VkDescriptorSetLayout                     descriptorSetLayout;
+    VkPipelineBindPoint                       pipelineBindPoint;
+    VkPipelineLayout                          pipelineLayout;
+    uint32_t                                  set;
+} VkDescriptorUpdateTemplateCreateInfo;
+
+typedef struct VkExternalMemoryProperties {
+    VkExternalMemoryFeatureFlags       externalMemoryFeatures;
+    VkExternalMemoryHandleTypeFlags    exportFromImportedHandleTypes;
+    VkExternalMemoryHandleTypeFlags    compatibleHandleTypes;
+} VkExternalMemoryProperties;
+
+typedef struct VkPhysicalDeviceExternalImageFormatInfo {
+    VkStructureType                       sType;
+    const void*                           pNext;
+    VkExternalMemoryHandleTypeFlagBits    handleType;
+} VkPhysicalDeviceExternalImageFormatInfo;
+
+typedef struct VkExternalImageFormatProperties {
+    VkStructureType               sType;
+    void*                         pNext;
+    VkExternalMemoryProperties    externalMemoryProperties;
+} VkExternalImageFormatProperties;
+
+typedef struct VkPhysicalDeviceExternalBufferInfo {
+    VkStructureType                       sType;
+    const void*                           pNext;
+    VkBufferCreateFlags                   flags;
+    VkBufferUsageFlags                    usage;
+    VkExternalMemoryHandleTypeFlagBits    handleType;
+} VkPhysicalDeviceExternalBufferInfo;
+
+typedef struct VkExternalBufferProperties {
+    VkStructureType               sType;
+    void*                         pNext;
+    VkExternalMemoryProperties    externalMemoryProperties;
+} VkExternalBufferProperties;
+
+typedef struct VkPhysicalDeviceIDProperties {
+    VkStructureType    sType;
+    void*              pNext;
+    uint8_t            deviceUUID[VK_UUID_SIZE];
+    uint8_t            driverUUID[VK_UUID_SIZE];
+    uint8_t            deviceLUID[VK_LUID_SIZE];
+    uint32_t           deviceNodeMask;
+    VkBool32           deviceLUIDValid;
+} VkPhysicalDeviceIDProperties;
+
+typedef struct VkExternalMemoryImageCreateInfo {
+    VkStructureType                    sType;
+    const void*                        pNext;
+    VkExternalMemoryHandleTypeFlags    handleTypes;
+} VkExternalMemoryImageCreateInfo;
+
+typedef struct VkExternalMemoryBufferCreateInfo {
+    VkStructureType                    sType;
+    const void*                        pNext;
+    VkExternalMemoryHandleTypeFlags    handleTypes;
+} VkExternalMemoryBufferCreateInfo;
+
+typedef struct VkExportMemoryAllocateInfo {
+    VkStructureType                    sType;
+    const void*                        pNext;
+    VkExternalMemoryHandleTypeFlags    handleTypes;
+} VkExportMemoryAllocateInfo;
+
+typedef struct VkPhysicalDeviceExternalFenceInfo {
+    VkStructureType                      sType;
+    const void*                          pNext;
+    VkExternalFenceHandleTypeFlagBits    handleType;
+} VkPhysicalDeviceExternalFenceInfo;
+
+typedef struct VkExternalFenceProperties {
+    VkStructureType                   sType;
+    void*                             pNext;
+    VkExternalFenceHandleTypeFlags    exportFromImportedHandleTypes;
+    VkExternalFenceHandleTypeFlags    compatibleHandleTypes;
+    VkExternalFenceFeatureFlags       externalFenceFeatures;
+} VkExternalFenceProperties;
+
+typedef struct VkExportFenceCreateInfo {
+    VkStructureType                   sType;
+    const void*                       pNext;
+    VkExternalFenceHandleTypeFlags    handleTypes;
+} VkExportFenceCreateInfo;
+
+typedef struct VkExportSemaphoreCreateInfo {
+    VkStructureType                       sType;
+    const void*                           pNext;
+    VkExternalSemaphoreHandleTypeFlags    handleTypes;
+} VkExportSemaphoreCreateInfo;
+
+typedef struct VkPhysicalDeviceExternalSemaphoreInfo {
+    VkStructureType                          sType;
+    const void*                              pNext;
+    VkExternalSemaphoreHandleTypeFlagBits    handleType;
+} VkPhysicalDeviceExternalSemaphoreInfo;
+
+typedef struct VkExternalSemaphoreProperties {
+    VkStructureType                       sType;
+    void*                                 pNext;
+    VkExternalSemaphoreHandleTypeFlags    exportFromImportedHandleTypes;
+    VkExternalSemaphoreHandleTypeFlags    compatibleHandleTypes;
+    VkExternalSemaphoreFeatureFlags       externalSemaphoreFeatures;
+} VkExternalSemaphoreProperties;
+
+typedef struct VkPhysicalDeviceMaintenance3Properties {
+    VkStructureType    sType;
+    void*              pNext;
+    uint32_t           maxPerSetDescriptors;
+    VkDeviceSize       maxMemoryAllocationSize;
+} VkPhysicalDeviceMaintenance3Properties;
+
+typedef struct VkDescriptorSetLayoutSupport {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           supported;
+} VkDescriptorSetLayoutSupport;
+
+typedef struct VkPhysicalDeviceShaderDrawParametersFeatures {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           shaderDrawParameters;
+} VkPhysicalDeviceShaderDrawParametersFeatures;
+
+typedef VkPhysicalDeviceShaderDrawParametersFeatures VkPhysicalDeviceShaderDrawParameterFeatures;
+
+typedef VkResult (VKAPI_PTR *PFN_vkEnumerateInstanceVersion)(uint32_t* pApiVersion);
+typedef VkResult (VKAPI_PTR *PFN_vkBindBufferMemory2)(VkDevice device, uint32_t bindInfoCount, const VkBindBufferMemoryInfo* pBindInfos);
+typedef VkResult (VKAPI_PTR *PFN_vkBindImageMemory2)(VkDevice device, uint32_t bindInfoCount, const VkBindImageMemoryInfo* pBindInfos);
+typedef void (VKAPI_PTR *PFN_vkGetDeviceGroupPeerMemoryFeatures)(VkDevice device, uint32_t heapIndex, uint32_t localDeviceIndex, uint32_t remoteDeviceIndex, VkPeerMemoryFeatureFlags* pPeerMemoryFeatures);
+typedef void (VKAPI_PTR *PFN_vkCmdSetDeviceMask)(VkCommandBuffer commandBuffer, uint32_t deviceMask);
+typedef void (VKAPI_PTR *PFN_vkCmdDispatchBase)(VkCommandBuffer commandBuffer, uint32_t baseGroupX, uint32_t baseGroupY, uint32_t baseGroupZ, uint32_t groupCountX, uint32_t groupCountY, uint32_t groupCountZ);
+typedef VkResult (VKAPI_PTR *PFN_vkEnumeratePhysicalDeviceGroups)(VkInstance instance, uint32_t* pPhysicalDeviceGroupCount, VkPhysicalDeviceGroupProperties* pPhysicalDeviceGroupProperties);
+typedef void (VKAPI_PTR *PFN_vkGetImageMemoryRequirements2)(VkDevice device, const VkImageMemoryRequirementsInfo2* pInfo, VkMemoryRequirements2* pMemoryRequirements);
+typedef void (VKAPI_PTR *PFN_vkGetBufferMemoryRequirements2)(VkDevice device, const VkBufferMemoryRequirementsInfo2* pInfo, VkMemoryRequirements2* pMemoryRequirements);
+typedef void (VKAPI_PTR *PFN_vkGetImageSparseMemoryRequirements2)(VkDevice device, const VkImageSparseMemoryRequirementsInfo2* pInfo, uint32_t* pSparseMemoryRequirementCount, VkSparseImageMemoryRequirements2* pSparseMemoryRequirements);
+typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceFeatures2)(VkPhysicalDevice physicalDevice, VkPhysicalDeviceFeatures2* pFeatures);
+typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceProperties2)(VkPhysicalDevice physicalDevice, VkPhysicalDeviceProperties2* pProperties);
+typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceFormatProperties2)(VkPhysicalDevice physicalDevice, VkFormat format, VkFormatProperties2* pFormatProperties);
+typedef VkResult (VKAPI_PTR *PFN_vkGetPhysicalDeviceImageFormatProperties2)(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceImageFormatInfo2* pImageFormatInfo, VkImageFormatProperties2* pImageFormatProperties);
+typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceQueueFamilyProperties2)(VkPhysicalDevice physicalDevice, uint32_t* pQueueFamilyPropertyCount, VkQueueFamilyProperties2* pQueueFamilyProperties);
+typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceMemoryProperties2)(VkPhysicalDevice physicalDevice, VkPhysicalDeviceMemoryProperties2* pMemoryProperties);
+typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceSparseImageFormatProperties2)(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceSparseImageFormatInfo2* pFormatInfo, uint32_t* pPropertyCount, VkSparseImageFormatProperties2* pProperties);
+typedef void (VKAPI_PTR *PFN_vkTrimCommandPool)(VkDevice device, VkCommandPool commandPool, VkCommandPoolTrimFlags flags);
+typedef void (VKAPI_PTR *PFN_vkGetDeviceQueue2)(VkDevice device, const VkDeviceQueueInfo2* pQueueInfo, VkQueue* pQueue);
+typedef VkResult (VKAPI_PTR *PFN_vkCreateSamplerYcbcrConversion)(VkDevice device, const VkSamplerYcbcrConversionCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSamplerYcbcrConversion* pYcbcrConversion);
+typedef void (VKAPI_PTR *PFN_vkDestroySamplerYcbcrConversion)(VkDevice device, VkSamplerYcbcrConversion ycbcrConversion, const VkAllocationCallbacks* pAllocator);
+typedef VkResult (VKAPI_PTR *PFN_vkCreateDescriptorUpdateTemplate)(VkDevice device, const VkDescriptorUpdateTemplateCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDescriptorUpdateTemplate* pDescriptorUpdateTemplate);
+typedef void (VKAPI_PTR *PFN_vkDestroyDescriptorUpdateTemplate)(VkDevice device, VkDescriptorUpdateTemplate descriptorUpdateTemplate, const VkAllocationCallbacks* pAllocator);
+typedef void (VKAPI_PTR *PFN_vkUpdateDescriptorSetWithTemplate)(VkDevice device, VkDescriptorSet descriptorSet, VkDescriptorUpdateTemplate descriptorUpdateTemplate, const void* pData);
+typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceExternalBufferProperties)(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceExternalBufferInfo* pExternalBufferInfo, VkExternalBufferProperties* pExternalBufferProperties);
+typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceExternalFenceProperties)(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceExternalFenceInfo* pExternalFenceInfo, VkExternalFenceProperties* pExternalFenceProperties);
+typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceExternalSemaphoreProperties)(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceExternalSemaphoreInfo* pExternalSemaphoreInfo, VkExternalSemaphoreProperties* pExternalSemaphoreProperties);
+typedef void (VKAPI_PTR *PFN_vkGetDescriptorSetLayoutSupport)(VkDevice device, const VkDescriptorSetLayoutCreateInfo* pCreateInfo, VkDescriptorSetLayoutSupport* pSupport);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateInstanceVersion(
+    uint32_t*                                   pApiVersion);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkBindBufferMemory2(
+    VkDevice                                    device,
+    uint32_t                                    bindInfoCount,
+    const VkBindBufferMemoryInfo*               pBindInfos);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkBindImageMemory2(
+    VkDevice                                    device,
+    uint32_t                                    bindInfoCount,
+    const VkBindImageMemoryInfo*                pBindInfos);
+
+VKAPI_ATTR void VKAPI_CALL vkGetDeviceGroupPeerMemoryFeatures(
+    VkDevice                                    device,
+    uint32_t                                    heapIndex,
+    uint32_t                                    localDeviceIndex,
+    uint32_t                                    remoteDeviceIndex,
+    VkPeerMemoryFeatureFlags*                   pPeerMemoryFeatures);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdSetDeviceMask(
+    VkCommandBuffer                             commandBuffer,
+    uint32_t                                    deviceMask);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdDispatchBase(
+    VkCommandBuffer                             commandBuffer,
+    uint32_t                                    baseGroupX,
+    uint32_t                                    baseGroupY,
+    uint32_t                                    baseGroupZ,
+    uint32_t                                    groupCountX,
+    uint32_t                                    groupCountY,
+    uint32_t                                    groupCountZ);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkEnumeratePhysicalDeviceGroups(
+    VkInstance                                  instance,
+    uint32_t*                                   pPhysicalDeviceGroupCount,
+    VkPhysicalDeviceGroupProperties*            pPhysicalDeviceGroupProperties);
+
+VKAPI_ATTR void VKAPI_CALL vkGetImageMemoryRequirements2(
+    VkDevice                                    device,
+    const VkImageMemoryRequirementsInfo2*       pInfo,
+    VkMemoryRequirements2*                      pMemoryRequirements);
+
+VKAPI_ATTR void VKAPI_CALL vkGetBufferMemoryRequirements2(
+    VkDevice                                    device,
+    const VkBufferMemoryRequirementsInfo2*      pInfo,
+    VkMemoryRequirements2*                      pMemoryRequirements);
+
+VKAPI_ATTR void VKAPI_CALL vkGetImageSparseMemoryRequirements2(
+    VkDevice                                    device,
+    const VkImageSparseMemoryRequirementsInfo2* pInfo,
+    uint32_t*                                   pSparseMemoryRequirementCount,
+    VkSparseImageMemoryRequirements2*           pSparseMemoryRequirements);
+
+VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceFeatures2(
+    VkPhysicalDevice                            physicalDevice,
+    VkPhysicalDeviceFeatures2*                  pFeatures);
+
+VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceProperties2(
+    VkPhysicalDevice                            physicalDevice,
+    VkPhysicalDeviceProperties2*                pProperties);
+
+VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceFormatProperties2(
+    VkPhysicalDevice                            physicalDevice,
+    VkFormat                                    format,
+    VkFormatProperties2*                        pFormatProperties);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceImageFormatProperties2(
+    VkPhysicalDevice                            physicalDevice,
+    const VkPhysicalDeviceImageFormatInfo2*     pImageFormatInfo,
+    VkImageFormatProperties2*                   pImageFormatProperties);
+
+VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceQueueFamilyProperties2(
+    VkPhysicalDevice                            physicalDevice,
+    uint32_t*                                   pQueueFamilyPropertyCount,
+    VkQueueFamilyProperties2*                   pQueueFamilyProperties);
+
+VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceMemoryProperties2(
+    VkPhysicalDevice                            physicalDevice,
+    VkPhysicalDeviceMemoryProperties2*          pMemoryProperties);
+
+VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceSparseImageFormatProperties2(
+    VkPhysicalDevice                            physicalDevice,
+    const VkPhysicalDeviceSparseImageFormatInfo2* pFormatInfo,
+    uint32_t*                                   pPropertyCount,
+    VkSparseImageFormatProperties2*             pProperties);
+
+VKAPI_ATTR void VKAPI_CALL vkTrimCommandPool(
+    VkDevice                                    device,
+    VkCommandPool                               commandPool,
+    VkCommandPoolTrimFlags                      flags);
+
+VKAPI_ATTR void VKAPI_CALL vkGetDeviceQueue2(
+    VkDevice                                    device,
+    const VkDeviceQueueInfo2*                   pQueueInfo,
+    VkQueue*                                    pQueue);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateSamplerYcbcrConversion(
+    VkDevice                                    device,
+    const VkSamplerYcbcrConversionCreateInfo*   pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkSamplerYcbcrConversion*                   pYcbcrConversion);
+
+VKAPI_ATTR void VKAPI_CALL vkDestroySamplerYcbcrConversion(
+    VkDevice                                    device,
+    VkSamplerYcbcrConversion                    ycbcrConversion,
+    const VkAllocationCallbacks*                pAllocator);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateDescriptorUpdateTemplate(
+    VkDevice                                    device,
+    const VkDescriptorUpdateTemplateCreateInfo* pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkDescriptorUpdateTemplate*                 pDescriptorUpdateTemplate);
+
+VKAPI_ATTR void VKAPI_CALL vkDestroyDescriptorUpdateTemplate(
+    VkDevice                                    device,
+    VkDescriptorUpdateTemplate                  descriptorUpdateTemplate,
+    const VkAllocationCallbacks*                pAllocator);
+
+VKAPI_ATTR void VKAPI_CALL vkUpdateDescriptorSetWithTemplate(
+    VkDevice                                    device,
+    VkDescriptorSet                             descriptorSet,
+    VkDescriptorUpdateTemplate                  descriptorUpdateTemplate,
+    const void*                                 pData);
+
+VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceExternalBufferProperties(
+    VkPhysicalDevice                            physicalDevice,
+    const VkPhysicalDeviceExternalBufferInfo*   pExternalBufferInfo,
+    VkExternalBufferProperties*                 pExternalBufferProperties);
+
+VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceExternalFenceProperties(
+    VkPhysicalDevice                            physicalDevice,
+    const VkPhysicalDeviceExternalFenceInfo*    pExternalFenceInfo,
+    VkExternalFenceProperties*                  pExternalFenceProperties);
+
+VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceExternalSemaphoreProperties(
+    VkPhysicalDevice                            physicalDevice,
+    const VkPhysicalDeviceExternalSemaphoreInfo* pExternalSemaphoreInfo,
+    VkExternalSemaphoreProperties*              pExternalSemaphoreProperties);
+
+VKAPI_ATTR void VKAPI_CALL vkGetDescriptorSetLayoutSupport(
+    VkDevice                                    device,
+    const VkDescriptorSetLayoutCreateInfo*      pCreateInfo,
+    VkDescriptorSetLayoutSupport*               pSupport);
+#endif
+
+
+#define VK_VERSION_1_2 1
+// Vulkan 1.2 version number
+#define VK_API_VERSION_1_2 VK_MAKE_API_VERSION(0, 1, 2, 0)// Patch version should always be set to 0
+
+#define VK_MAX_DRIVER_NAME_SIZE           256U
+#define VK_MAX_DRIVER_INFO_SIZE           256U
+
+typedef enum VkDriverId {
+    VK_DRIVER_ID_AMD_PROPRIETARY = 1,
+    VK_DRIVER_ID_AMD_OPEN_SOURCE = 2,
+    VK_DRIVER_ID_MESA_RADV = 3,
+    VK_DRIVER_ID_NVIDIA_PROPRIETARY = 4,
+    VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS = 5,
+    VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA = 6,
+    VK_DRIVER_ID_IMAGINATION_PROPRIETARY = 7,
+    VK_DRIVER_ID_QUALCOMM_PROPRIETARY = 8,
+    VK_DRIVER_ID_ARM_PROPRIETARY = 9,
+    VK_DRIVER_ID_GOOGLE_SWIFTSHADER = 10,
+    VK_DRIVER_ID_GGP_PROPRIETARY = 11,
+    VK_DRIVER_ID_BROADCOM_PROPRIETARY = 12,
+    VK_DRIVER_ID_MESA_LLVMPIPE = 13,
+    VK_DRIVER_ID_MOLTENVK = 14,
+    VK_DRIVER_ID_COREAVI_PROPRIETARY = 15,
+    VK_DRIVER_ID_JUICE_PROPRIETARY = 16,
+    VK_DRIVER_ID_VERISILICON_PROPRIETARY = 17,
+    VK_DRIVER_ID_MESA_TURNIP = 18,
+    VK_DRIVER_ID_MESA_V3DV = 19,
+    VK_DRIVER_ID_MESA_PANVK = 20,
+    VK_DRIVER_ID_AMD_PROPRIETARY_KHR = VK_DRIVER_ID_AMD_PROPRIETARY,
+    VK_DRIVER_ID_AMD_OPEN_SOURCE_KHR = VK_DRIVER_ID_AMD_OPEN_SOURCE,
+    VK_DRIVER_ID_MESA_RADV_KHR = VK_DRIVER_ID_MESA_RADV,
+    VK_DRIVER_ID_NVIDIA_PROPRIETARY_KHR = VK_DRIVER_ID_NVIDIA_PROPRIETARY,
+    VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS_KHR = VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS,
+    VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA_KHR = VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA,
+    VK_DRIVER_ID_IMAGINATION_PROPRIETARY_KHR = VK_DRIVER_ID_IMAGINATION_PROPRIETARY,
+    VK_DRIVER_ID_QUALCOMM_PROPRIETARY_KHR = VK_DRIVER_ID_QUALCOMM_PROPRIETARY,
+    VK_DRIVER_ID_ARM_PROPRIETARY_KHR = VK_DRIVER_ID_ARM_PROPRIETARY,
+    VK_DRIVER_ID_GOOGLE_SWIFTSHADER_KHR = VK_DRIVER_ID_GOOGLE_SWIFTSHADER,
+    VK_DRIVER_ID_GGP_PROPRIETARY_KHR = VK_DRIVER_ID_GGP_PROPRIETARY,
+    VK_DRIVER_ID_BROADCOM_PROPRIETARY_KHR = VK_DRIVER_ID_BROADCOM_PROPRIETARY,
+    VK_DRIVER_ID_MAX_ENUM = 0x7FFFFFFF
+} VkDriverId;
+
+typedef enum VkShaderFloatControlsIndependence {
+    VK_SHADER_FLOAT_CONTROLS_INDEPENDENCE_32_BIT_ONLY = 0,
+    VK_SHADER_FLOAT_CONTROLS_INDEPENDENCE_ALL = 1,
+    VK_SHADER_FLOAT_CONTROLS_INDEPENDENCE_NONE = 2,
+    VK_SHADER_FLOAT_CONTROLS_INDEPENDENCE_32_BIT_ONLY_KHR = VK_SHADER_FLOAT_CONTROLS_INDEPENDENCE_32_BIT_ONLY,
+    VK_SHADER_FLOAT_CONTROLS_INDEPENDENCE_ALL_KHR = VK_SHADER_FLOAT_CONTROLS_INDEPENDENCE_ALL,
+    VK_SHADER_FLOAT_CONTROLS_INDEPENDENCE_NONE_KHR = VK_SHADER_FLOAT_CONTROLS_INDEPENDENCE_NONE,
+    VK_SHADER_FLOAT_CONTROLS_INDEPENDENCE_MAX_ENUM = 0x7FFFFFFF
+} VkShaderFloatControlsIndependence;
+
+typedef enum VkSamplerReductionMode {
+    VK_SAMPLER_REDUCTION_MODE_WEIGHTED_AVERAGE = 0,
+    VK_SAMPLER_REDUCTION_MODE_MIN = 1,
+    VK_SAMPLER_REDUCTION_MODE_MAX = 2,
+    VK_SAMPLER_REDUCTION_MODE_WEIGHTED_AVERAGE_EXT = VK_SAMPLER_REDUCTION_MODE_WEIGHTED_AVERAGE,
+    VK_SAMPLER_REDUCTION_MODE_MIN_EXT = VK_SAMPLER_REDUCTION_MODE_MIN,
+    VK_SAMPLER_REDUCTION_MODE_MAX_EXT = VK_SAMPLER_REDUCTION_MODE_MAX,
+    VK_SAMPLER_REDUCTION_MODE_MAX_ENUM = 0x7FFFFFFF
+} VkSamplerReductionMode;
+
+typedef enum VkSemaphoreType {
+    VK_SEMAPHORE_TYPE_BINARY = 0,
+    VK_SEMAPHORE_TYPE_TIMELINE = 1,
+    VK_SEMAPHORE_TYPE_BINARY_KHR = VK_SEMAPHORE_TYPE_BINARY,
+    VK_SEMAPHORE_TYPE_TIMELINE_KHR = VK_SEMAPHORE_TYPE_TIMELINE,
+    VK_SEMAPHORE_TYPE_MAX_ENUM = 0x7FFFFFFF
+} VkSemaphoreType;
+
+typedef enum VkResolveModeFlagBits {
+    VK_RESOLVE_MODE_NONE = 0,
+    VK_RESOLVE_MODE_SAMPLE_ZERO_BIT = 0x00000001,
+    VK_RESOLVE_MODE_AVERAGE_BIT = 0x00000002,
+    VK_RESOLVE_MODE_MIN_BIT = 0x00000004,
+    VK_RESOLVE_MODE_MAX_BIT = 0x00000008,
+    VK_RESOLVE_MODE_NONE_KHR = VK_RESOLVE_MODE_NONE,
+    VK_RESOLVE_MODE_SAMPLE_ZERO_BIT_KHR = VK_RESOLVE_MODE_SAMPLE_ZERO_BIT,
+    VK_RESOLVE_MODE_AVERAGE_BIT_KHR = VK_RESOLVE_MODE_AVERAGE_BIT,
+    VK_RESOLVE_MODE_MIN_BIT_KHR = VK_RESOLVE_MODE_MIN_BIT,
+    VK_RESOLVE_MODE_MAX_BIT_KHR = VK_RESOLVE_MODE_MAX_BIT,
+    VK_RESOLVE_MODE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkResolveModeFlagBits;
+typedef VkFlags VkResolveModeFlags;
+
+typedef enum VkDescriptorBindingFlagBits {
+    VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT = 0x00000001,
+    VK_DESCRIPTOR_BINDING_UPDATE_UNUSED_WHILE_PENDING_BIT = 0x00000002,
+    VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT = 0x00000004,
+    VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT = 0x00000008,
+    VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT_EXT = VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT,
+    VK_DESCRIPTOR_BINDING_UPDATE_UNUSED_WHILE_PENDING_BIT_EXT = VK_DESCRIPTOR_BINDING_UPDATE_UNUSED_WHILE_PENDING_BIT,
+    VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT_EXT = VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT,
+    VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT_EXT = VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT,
+    VK_DESCRIPTOR_BINDING_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkDescriptorBindingFlagBits;
+typedef VkFlags VkDescriptorBindingFlags;
+
+typedef enum VkSemaphoreWaitFlagBits {
+    VK_SEMAPHORE_WAIT_ANY_BIT = 0x00000001,
+    VK_SEMAPHORE_WAIT_ANY_BIT_KHR = VK_SEMAPHORE_WAIT_ANY_BIT,
+    VK_SEMAPHORE_WAIT_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
+} VkSemaphoreWaitFlagBits;
+typedef VkFlags VkSemaphoreWaitFlags;
+typedef struct VkPhysicalDeviceVulkan11Features {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           storageBuffer16BitAccess;
+    VkBool32           uniformAndStorageBuffer16BitAccess;
+    VkBool32           storagePushConstant16;
+    VkBool32           storageInputOutput16;
+    VkBool32           multiview;
+    VkBool32           multiviewGeometryShader;
+    VkBool32           multiviewTessellationShader;
+    VkBool32           variablePointersStorageBuffer;
+    VkBool32           variablePointers;
+    VkBool32           protectedMemory;
+    VkBool32           samplerYcbcrConversion;
+    VkBool32           shaderDrawParameters;
+} VkPhysicalDeviceVulkan11Features;
+
+typedef struct VkPhysicalDeviceVulkan11Properties {
+    VkStructureType            sType;
+    void*                      pNext;
+    uint8_t                    deviceUUID[VK_UUID_SIZE];
+    uint8_t                    driverUUID[VK_UUID_SIZE];
+    uint8_t                    deviceLUID[VK_LUID_SIZE];
+    uint32_t                   deviceNodeMask;
+    VkBool32                   deviceLUIDValid;
+    uint32_t                   subgroupSize;
+    VkShaderStageFlags         subgroupSupportedStages;
+    VkSubgroupFeatureFlags     subgroupSupportedOperations;
+    VkBool32                   subgroupQuadOperationsInAllStages;
+    VkPointClippingBehavior    pointClippingBehavior;
+    uint32_t                   maxMultiviewViewCount;
+    uint32_t                   maxMultiviewInstanceIndex;
+    VkBool32                   protectedNoFault;
+    uint32_t                   maxPerSetDescriptors;
+    VkDeviceSize               maxMemoryAllocationSize;
+} VkPhysicalDeviceVulkan11Properties;
+
+typedef struct VkPhysicalDeviceVulkan12Features {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           samplerMirrorClampToEdge;
+    VkBool32           drawIndirectCount;
+    VkBool32           storageBuffer8BitAccess;
+    VkBool32           uniformAndStorageBuffer8BitAccess;
+    VkBool32           storagePushConstant8;
+    VkBool32           shaderBufferInt64Atomics;
+    VkBool32           shaderSharedInt64Atomics;
+    VkBool32           shaderFloat16;
+    VkBool32           shaderInt8;
+    VkBool32           descriptorIndexing;
+    VkBool32           shaderInputAttachmentArrayDynamicIndexing;
+    VkBool32           shaderUniformTexelBufferArrayDynamicIndexing;
+    VkBool32           shaderStorageTexelBufferArrayDynamicIndexing;
+    VkBool32           shaderUniformBufferArrayNonUniformIndexing;
+    VkBool32           shaderSampledImageArrayNonUniformIndexing;
+    VkBool32           shaderStorageBufferArrayNonUniformIndexing;
+    VkBool32           shaderStorageImageArrayNonUniformIndexing;
+    VkBool32           shaderInputAttachmentArrayNonUniformIndexing;
+    VkBool32           shaderUniformTexelBufferArrayNonUniformIndexing;
+    VkBool32           shaderStorageTexelBufferArrayNonUniformIndexing;
+    VkBool32           descriptorBindingUniformBufferUpdateAfterBind;
+    VkBool32           descriptorBindingSampledImageUpdateAfterBind;
+    VkBool32           descriptorBindingStorageImageUpdateAfterBind;
+    VkBool32           descriptorBindingStorageBufferUpdateAfterBind;
+    VkBool32           descriptorBindingUniformTexelBufferUpdateAfterBind;
+    VkBool32           descriptorBindingStorageTexelBufferUpdateAfterBind;
+    VkBool32           descriptorBindingUpdateUnusedWhilePending;
+    VkBool32           descriptorBindingPartiallyBound;
+    VkBool32           descriptorBindingVariableDescriptorCount;
+    VkBool32           runtimeDescriptorArray;
+    VkBool32           samplerFilterMinmax;
+    VkBool32           scalarBlockLayout;
+    VkBool32           imagelessFramebuffer;
+    VkBool32           uniformBufferStandardLayout;
+    VkBool32           shaderSubgroupExtendedTypes;
+    VkBool32           separateDepthStencilLayouts;
+    VkBool32           hostQueryReset;
+    VkBool32           timelineSemaphore;
+    VkBool32           bufferDeviceAddress;
+    VkBool32           bufferDeviceAddressCaptureReplay;
+    VkBool32           bufferDeviceAddressMultiDevice;
+    VkBool32           vulkanMemoryModel;
+    VkBool32           vulkanMemoryModelDeviceScope;
+    VkBool32           vulkanMemoryModelAvailabilityVisibilityChains;
+    VkBool32           shaderOutputViewportIndex;
+    VkBool32           shaderOutputLayer;
+    VkBool32           subgroupBroadcastDynamicId;
+} VkPhysicalDeviceVulkan12Features;
+
+typedef struct VkConformanceVersion {
+    uint8_t    major;
+    uint8_t    minor;
+    uint8_t    subminor;
+    uint8_t    patch;
+} VkConformanceVersion;
+
+typedef struct VkPhysicalDeviceVulkan12Properties {
+    VkStructureType                      sType;
+    void*                                pNext;
+    VkDriverId                           driverID;
+    char                                 driverName[VK_MAX_DRIVER_NAME_SIZE];
+    char                                 driverInfo[VK_MAX_DRIVER_INFO_SIZE];
+    VkConformanceVersion                 conformanceVersion;
+    VkShaderFloatControlsIndependence    denormBehaviorIndependence;
+    VkShaderFloatControlsIndependence    roundingModeIndependence;
+    VkBool32                             shaderSignedZeroInfNanPreserveFloat16;
+    VkBool32                             shaderSignedZeroInfNanPreserveFloat32;
+    VkBool32                             shaderSignedZeroInfNanPreserveFloat64;
+    VkBool32                             shaderDenormPreserveFloat16;
+    VkBool32                             shaderDenormPreserveFloat32;
+    VkBool32                             shaderDenormPreserveFloat64;
+    VkBool32                             shaderDenormFlushToZeroFloat16;
+    VkBool32                             shaderDenormFlushToZeroFloat32;
+    VkBool32                             shaderDenormFlushToZeroFloat64;
+    VkBool32                             shaderRoundingModeRTEFloat16;
+    VkBool32                             shaderRoundingModeRTEFloat32;
+    VkBool32                             shaderRoundingModeRTEFloat64;
+    VkBool32                             shaderRoundingModeRTZFloat16;
+    VkBool32                             shaderRoundingModeRTZFloat32;
+    VkBool32                             shaderRoundingModeRTZFloat64;
+    uint32_t                             maxUpdateAfterBindDescriptorsInAllPools;
+    VkBool32                             shaderUniformBufferArrayNonUniformIndexingNative;
+    VkBool32                             shaderSampledImageArrayNonUniformIndexingNative;
+    VkBool32                             shaderStorageBufferArrayNonUniformIndexingNative;
+    VkBool32                             shaderStorageImageArrayNonUniformIndexingNative;
+    VkBool32                             shaderInputAttachmentArrayNonUniformIndexingNative;
+    VkBool32                             robustBufferAccessUpdateAfterBind;
+    VkBool32                             quadDivergentImplicitLod;
+    uint32_t                             maxPerStageDescriptorUpdateAfterBindSamplers;
+    uint32_t                             maxPerStageDescriptorUpdateAfterBindUniformBuffers;
+    uint32_t                             maxPerStageDescriptorUpdateAfterBindStorageBuffers;
+    uint32_t                             maxPerStageDescriptorUpdateAfterBindSampledImages;
+    uint32_t                             maxPerStageDescriptorUpdateAfterBindStorageImages;
+    uint32_t                             maxPerStageDescriptorUpdateAfterBindInputAttachments;
+    uint32_t                             maxPerStageUpdateAfterBindResources;
+    uint32_t                             maxDescriptorSetUpdateAfterBindSamplers;
+    uint32_t                             maxDescriptorSetUpdateAfterBindUniformBuffers;
+    uint32_t                             maxDescriptorSetUpdateAfterBindUniformBuffersDynamic;
+    uint32_t                             maxDescriptorSetUpdateAfterBindStorageBuffers;
+    uint32_t                             maxDescriptorSetUpdateAfterBindStorageBuffersDynamic;
+    uint32_t                             maxDescriptorSetUpdateAfterBindSampledImages;
+    uint32_t                             maxDescriptorSetUpdateAfterBindStorageImages;
+    uint32_t                             maxDescriptorSetUpdateAfterBindInputAttachments;
+    VkResolveModeFlags                   supportedDepthResolveModes;
+    VkResolveModeFlags                   supportedStencilResolveModes;
+    VkBool32                             independentResolveNone;
+    VkBool32                             independentResolve;
+    VkBool32                             filterMinmaxSingleComponentFormats;
+    VkBool32                             filterMinmaxImageComponentMapping;
+    uint64_t                             maxTimelineSemaphoreValueDifference;
+    VkSampleCountFlags                   framebufferIntegerColorSampleCounts;
+} VkPhysicalDeviceVulkan12Properties;
+
+typedef struct VkImageFormatListCreateInfo {
+    VkStructureType    sType;
+    const void*        pNext;
+    uint32_t           viewFormatCount;
+    const VkFormat*    pViewFormats;
+} VkImageFormatListCreateInfo;
+
+typedef struct VkAttachmentDescription2 {
+    VkStructureType                 sType;
+    const void*                     pNext;
+    VkAttachmentDescriptionFlags    flags;
+    VkFormat                        format;
+    VkSampleCountFlagBits           samples;
+    VkAttachmentLoadOp              loadOp;
+    VkAttachmentStoreOp             storeOp;
+    VkAttachmentLoadOp              stencilLoadOp;
+    VkAttachmentStoreOp             stencilStoreOp;
+    VkImageLayout                   initialLayout;
+    VkImageLayout                   finalLayout;
+} VkAttachmentDescription2;
+
+typedef struct VkAttachmentReference2 {
+    VkStructureType       sType;
+    const void*           pNext;
+    uint32_t              attachment;
+    VkImageLayout         layout;
+    VkImageAspectFlags    aspectMask;
+} VkAttachmentReference2;
+
+typedef struct VkSubpassDescription2 {
+    VkStructureType                  sType;
+    const void*                      pNext;
+    VkSubpassDescriptionFlags        flags;
+    VkPipelineBindPoint              pipelineBindPoint;
+    uint32_t                         viewMask;
+    uint32_t                         inputAttachmentCount;
+    const VkAttachmentReference2*    pInputAttachments;
+    uint32_t                         colorAttachmentCount;
+    const VkAttachmentReference2*    pColorAttachments;
+    const VkAttachmentReference2*    pResolveAttachments;
+    const VkAttachmentReference2*    pDepthStencilAttachment;
+    uint32_t                         preserveAttachmentCount;
+    const uint32_t*                  pPreserveAttachments;
+} VkSubpassDescription2;
+
+typedef struct VkSubpassDependency2 {
+    VkStructureType         sType;
+    const void*             pNext;
+    uint32_t                srcSubpass;
+    uint32_t                dstSubpass;
+    VkPipelineStageFlags    srcStageMask;
+    VkPipelineStageFlags    dstStageMask;
+    VkAccessFlags           srcAccessMask;
+    VkAccessFlags           dstAccessMask;
+    VkDependencyFlags       dependencyFlags;
+    int32_t                 viewOffset;
+} VkSubpassDependency2;
+
+typedef struct VkRenderPassCreateInfo2 {
+    VkStructureType                    sType;
+    const void*                        pNext;
+    VkRenderPassCreateFlags            flags;
+    uint32_t                           attachmentCount;
+    const VkAttachmentDescription2*    pAttachments;
+    uint32_t                           subpassCount;
+    const VkSubpassDescription2*       pSubpasses;
+    uint32_t                           dependencyCount;
+    const VkSubpassDependency2*        pDependencies;
+    uint32_t                           correlatedViewMaskCount;
+    const uint32_t*                    pCorrelatedViewMasks;
+} VkRenderPassCreateInfo2;
+
+typedef struct VkSubpassBeginInfo {
+    VkStructureType      sType;
+    const void*          pNext;
+    VkSubpassContents    contents;
+} VkSubpassBeginInfo;
+
+typedef struct VkSubpassEndInfo {
+    VkStructureType    sType;
+    const void*        pNext;
+} VkSubpassEndInfo;
+
+typedef struct VkPhysicalDevice8BitStorageFeatures {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           storageBuffer8BitAccess;
+    VkBool32           uniformAndStorageBuffer8BitAccess;
+    VkBool32           storagePushConstant8;
+} VkPhysicalDevice8BitStorageFeatures;
+
+typedef struct VkPhysicalDeviceDriverProperties {
+    VkStructureType         sType;
+    void*                   pNext;
+    VkDriverId              driverID;
+    char                    driverName[VK_MAX_DRIVER_NAME_SIZE];
+    char                    driverInfo[VK_MAX_DRIVER_INFO_SIZE];
+    VkConformanceVersion    conformanceVersion;
+} VkPhysicalDeviceDriverProperties;
+
+typedef struct VkPhysicalDeviceShaderAtomicInt64Features {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           shaderBufferInt64Atomics;
+    VkBool32           shaderSharedInt64Atomics;
+} VkPhysicalDeviceShaderAtomicInt64Features;
+
+typedef struct VkPhysicalDeviceShaderFloat16Int8Features {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           shaderFloat16;
+    VkBool32           shaderInt8;
+} VkPhysicalDeviceShaderFloat16Int8Features;
+
+typedef struct VkPhysicalDeviceFloatControlsProperties {
+    VkStructureType                      sType;
+    void*                                pNext;
+    VkShaderFloatControlsIndependence    denormBehaviorIndependence;
+    VkShaderFloatControlsIndependence    roundingModeIndependence;
+    VkBool32                             shaderSignedZeroInfNanPreserveFloat16;
+    VkBool32                             shaderSignedZeroInfNanPreserveFloat32;
+    VkBool32                             shaderSignedZeroInfNanPreserveFloat64;
+    VkBool32                             shaderDenormPreserveFloat16;
+    VkBool32                             shaderDenormPreserveFloat32;
+    VkBool32                             shaderDenormPreserveFloat64;
+    VkBool32                             shaderDenormFlushToZeroFloat16;
+    VkBool32                             shaderDenormFlushToZeroFloat32;
+    VkBool32                             shaderDenormFlushToZeroFloat64;
+    VkBool32                             shaderRoundingModeRTEFloat16;
+    VkBool32                             shaderRoundingModeRTEFloat32;
+    VkBool32                             shaderRoundingModeRTEFloat64;
+    VkBool32                             shaderRoundingModeRTZFloat16;
+    VkBool32                             shaderRoundingModeRTZFloat32;
+    VkBool32                             shaderRoundingModeRTZFloat64;
+} VkPhysicalDeviceFloatControlsProperties;
+
+typedef struct VkDescriptorSetLayoutBindingFlagsCreateInfo {
+    VkStructureType                    sType;
+    const void*                        pNext;
+    uint32_t                           bindingCount;
+    const VkDescriptorBindingFlags*    pBindingFlags;
+} VkDescriptorSetLayoutBindingFlagsCreateInfo;
+
+typedef struct VkPhysicalDeviceDescriptorIndexingFeatures {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           shaderInputAttachmentArrayDynamicIndexing;
+    VkBool32           shaderUniformTexelBufferArrayDynamicIndexing;
+    VkBool32           shaderStorageTexelBufferArrayDynamicIndexing;
+    VkBool32           shaderUniformBufferArrayNonUniformIndexing;
+    VkBool32           shaderSampledImageArrayNonUniformIndexing;
+    VkBool32           shaderStorageBufferArrayNonUniformIndexing;
+    VkBool32           shaderStorageImageArrayNonUniformIndexing;
+    VkBool32           shaderInputAttachmentArrayNonUniformIndexing;
+    VkBool32           shaderUniformTexelBufferArrayNonUniformIndexing;
+    VkBool32           shaderStorageTexelBufferArrayNonUniformIndexing;
+    VkBool32           descriptorBindingUniformBufferUpdateAfterBind;
+    VkBool32           descriptorBindingSampledImageUpdateAfterBind;
+    VkBool32           descriptorBindingStorageImageUpdateAfterBind;
+    VkBool32           descriptorBindingStorageBufferUpdateAfterBind;
+    VkBool32           descriptorBindingUniformTexelBufferUpdateAfterBind;
+    VkBool32           descriptorBindingStorageTexelBufferUpdateAfterBind;
+    VkBool32           descriptorBindingUpdateUnusedWhilePending;
+    VkBool32           descriptorBindingPartiallyBound;
+    VkBool32           descriptorBindingVariableDescriptorCount;
+    VkBool32           runtimeDescriptorArray;
+} VkPhysicalDeviceDescriptorIndexingFeatures;
+
+typedef struct VkPhysicalDeviceDescriptorIndexingProperties {
+    VkStructureType    sType;
+    void*              pNext;
+    uint32_t           maxUpdateAfterBindDescriptorsInAllPools;
+    VkBool32           shaderUniformBufferArrayNonUniformIndexingNative;
+    VkBool32           shaderSampledImageArrayNonUniformIndexingNative;
+    VkBool32           shaderStorageBufferArrayNonUniformIndexingNative;
+    VkBool32           shaderStorageImageArrayNonUniformIndexingNative;
+    VkBool32           shaderInputAttachmentArrayNonUniformIndexingNative;
+    VkBool32           robustBufferAccessUpdateAfterBind;
+    VkBool32           quadDivergentImplicitLod;
+    uint32_t           maxPerStageDescriptorUpdateAfterBindSamplers;
+    uint32_t           maxPerStageDescriptorUpdateAfterBindUniformBuffers;
+    uint32_t           maxPerStageDescriptorUpdateAfterBindStorageBuffers;
+    uint32_t           maxPerStageDescriptorUpdateAfterBindSampledImages;
+    uint32_t           maxPerStageDescriptorUpdateAfterBindStorageImages;
+    uint32_t           maxPerStageDescriptorUpdateAfterBindInputAttachments;
+    uint32_t           maxPerStageUpdateAfterBindResources;
+    uint32_t           maxDescriptorSetUpdateAfterBindSamplers;
+    uint32_t           maxDescriptorSetUpdateAfterBindUniformBuffers;
+    uint32_t           maxDescriptorSetUpdateAfterBindUniformBuffersDynamic;
+    uint32_t           maxDescriptorSetUpdateAfterBindStorageBuffers;
+    uint32_t           maxDescriptorSetUpdateAfterBindStorageBuffersDynamic;
+    uint32_t           maxDescriptorSetUpdateAfterBindSampledImages;
+    uint32_t           maxDescriptorSetUpdateAfterBindStorageImages;
+    uint32_t           maxDescriptorSetUpdateAfterBindInputAttachments;
+} VkPhysicalDeviceDescriptorIndexingProperties;
+
+typedef struct VkDescriptorSetVariableDescriptorCountAllocateInfo {
+    VkStructureType    sType;
+    const void*        pNext;
+    uint32_t           descriptorSetCount;
+    const uint32_t*    pDescriptorCounts;
+} VkDescriptorSetVariableDescriptorCountAllocateInfo;
+
+typedef struct VkDescriptorSetVariableDescriptorCountLayoutSupport {
+    VkStructureType    sType;
+    void*              pNext;
+    uint32_t           maxVariableDescriptorCount;
+} VkDescriptorSetVariableDescriptorCountLayoutSupport;
+
+typedef struct VkSubpassDescriptionDepthStencilResolve {
+    VkStructureType                  sType;
+    const void*                      pNext;
+    VkResolveModeFlagBits            depthResolveMode;
+    VkResolveModeFlagBits            stencilResolveMode;
+    const VkAttachmentReference2*    pDepthStencilResolveAttachment;
+} VkSubpassDescriptionDepthStencilResolve;
+
+typedef struct VkPhysicalDeviceDepthStencilResolveProperties {
+    VkStructureType       sType;
+    void*                 pNext;
+    VkResolveModeFlags    supportedDepthResolveModes;
+    VkResolveModeFlags    supportedStencilResolveModes;
+    VkBool32              independentResolveNone;
+    VkBool32              independentResolve;
+} VkPhysicalDeviceDepthStencilResolveProperties;
+
+typedef struct VkPhysicalDeviceScalarBlockLayoutFeatures {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           scalarBlockLayout;
+} VkPhysicalDeviceScalarBlockLayoutFeatures;
+
+typedef struct VkImageStencilUsageCreateInfo {
+    VkStructureType      sType;
+    const void*          pNext;
+    VkImageUsageFlags    stencilUsage;
+} VkImageStencilUsageCreateInfo;
+
+typedef struct VkSamplerReductionModeCreateInfo {
+    VkStructureType           sType;
+    const void*               pNext;
+    VkSamplerReductionMode    reductionMode;
+} VkSamplerReductionModeCreateInfo;
+
+typedef struct VkPhysicalDeviceSamplerFilterMinmaxProperties {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           filterMinmaxSingleComponentFormats;
+    VkBool32           filterMinmaxImageComponentMapping;
+} VkPhysicalDeviceSamplerFilterMinmaxProperties;
+
+typedef struct VkPhysicalDeviceVulkanMemoryModelFeatures {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           vulkanMemoryModel;
+    VkBool32           vulkanMemoryModelDeviceScope;
+    VkBool32           vulkanMemoryModelAvailabilityVisibilityChains;
+} VkPhysicalDeviceVulkanMemoryModelFeatures;
+
+typedef struct VkPhysicalDeviceImagelessFramebufferFeatures {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           imagelessFramebuffer;
+} VkPhysicalDeviceImagelessFramebufferFeatures;
+
+typedef struct VkFramebufferAttachmentImageInfo {
+    VkStructureType       sType;
+    const void*           pNext;
+    VkImageCreateFlags    flags;
+    VkImageUsageFlags     usage;
+    uint32_t              width;
+    uint32_t              height;
+    uint32_t              layerCount;
+    uint32_t              viewFormatCount;
+    const VkFormat*       pViewFormats;
+} VkFramebufferAttachmentImageInfo;
+
+typedef struct VkFramebufferAttachmentsCreateInfo {
+    VkStructureType                            sType;
+    const void*                                pNext;
+    uint32_t                                   attachmentImageInfoCount;
+    const VkFramebufferAttachmentImageInfo*    pAttachmentImageInfos;
+} VkFramebufferAttachmentsCreateInfo;
+
+typedef struct VkRenderPassAttachmentBeginInfo {
+    VkStructureType       sType;
+    const void*           pNext;
+    uint32_t              attachmentCount;
+    const VkImageView*    pAttachments;
+} VkRenderPassAttachmentBeginInfo;
+
+typedef struct VkPhysicalDeviceUniformBufferStandardLayoutFeatures {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           uniformBufferStandardLayout;
+} VkPhysicalDeviceUniformBufferStandardLayoutFeatures;
+
+typedef struct VkPhysicalDeviceShaderSubgroupExtendedTypesFeatures {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           shaderSubgroupExtendedTypes;
+} VkPhysicalDeviceShaderSubgroupExtendedTypesFeatures;
+
+typedef struct VkPhysicalDeviceSeparateDepthStencilLayoutsFeatures {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           separateDepthStencilLayouts;
+} VkPhysicalDeviceSeparateDepthStencilLayoutsFeatures;
+
+typedef struct VkAttachmentReferenceStencilLayout {
+    VkStructureType    sType;
+    void*              pNext;
+    VkImageLayout      stencilLayout;
+} VkAttachmentReferenceStencilLayout;
+
+typedef struct VkAttachmentDescriptionStencilLayout {
+    VkStructureType    sType;
+    void*              pNext;
+    VkImageLayout      stencilInitialLayout;
+    VkImageLayout      stencilFinalLayout;
+} VkAttachmentDescriptionStencilLayout;
+
+typedef struct VkPhysicalDeviceHostQueryResetFeatures {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           hostQueryReset;
+} VkPhysicalDeviceHostQueryResetFeatures;
+
+typedef struct VkPhysicalDeviceTimelineSemaphoreFeatures {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           timelineSemaphore;
+} VkPhysicalDeviceTimelineSemaphoreFeatures;
+
+typedef struct VkPhysicalDeviceTimelineSemaphoreProperties {
+    VkStructureType    sType;
+    void*              pNext;
+    uint64_t           maxTimelineSemaphoreValueDifference;
+} VkPhysicalDeviceTimelineSemaphoreProperties;
+
+typedef struct VkSemaphoreTypeCreateInfo {
+    VkStructureType    sType;
+    const void*        pNext;
+    VkSemaphoreType    semaphoreType;
+    uint64_t           initialValue;
+} VkSemaphoreTypeCreateInfo;
+
+typedef struct VkTimelineSemaphoreSubmitInfo {
+    VkStructureType    sType;
+    const void*        pNext;
+    uint32_t           waitSemaphoreValueCount;
+    const uint64_t*    pWaitSemaphoreValues;
+    uint32_t           signalSemaphoreValueCount;
+    const uint64_t*    pSignalSemaphoreValues;
+} VkTimelineSemaphoreSubmitInfo;
+
+typedef struct VkSemaphoreWaitInfo {
+    VkStructureType         sType;
+    const void*             pNext;
+    VkSemaphoreWaitFlags    flags;
+    uint32_t                semaphoreCount;
+    const VkSemaphore*      pSemaphores;
+    const uint64_t*         pValues;
+} VkSemaphoreWaitInfo;
+
+typedef struct VkSemaphoreSignalInfo {
+    VkStructureType    sType;
+    const void*        pNext;
+    VkSemaphore        semaphore;
+    uint64_t           value;
+} VkSemaphoreSignalInfo;
+
+typedef struct VkPhysicalDeviceBufferDeviceAddressFeatures {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           bufferDeviceAddress;
+    VkBool32           bufferDeviceAddressCaptureReplay;
+    VkBool32           bufferDeviceAddressMultiDevice;
+} VkPhysicalDeviceBufferDeviceAddressFeatures;
+
+typedef struct VkBufferDeviceAddressInfo {
+    VkStructureType    sType;
+    const void*        pNext;
+    VkBuffer           buffer;
+} VkBufferDeviceAddressInfo;
+
+typedef struct VkBufferOpaqueCaptureAddressCreateInfo {
+    VkStructureType    sType;
+    const void*        pNext;
+    uint64_t           opaqueCaptureAddress;
+} VkBufferOpaqueCaptureAddressCreateInfo;
+
+typedef struct VkMemoryOpaqueCaptureAddressAllocateInfo {
+    VkStructureType    sType;
+    const void*        pNext;
+    uint64_t           opaqueCaptureAddress;
+} VkMemoryOpaqueCaptureAddressAllocateInfo;
+
+typedef struct VkDeviceMemoryOpaqueCaptureAddressInfo {
+    VkStructureType    sType;
+    const void*        pNext;
+    VkDeviceMemory     memory;
+} VkDeviceMemoryOpaqueCaptureAddressInfo;
+
+typedef void (VKAPI_PTR *PFN_vkCmdDrawIndirectCount)(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset, VkBuffer countBuffer, VkDeviceSize countBufferOffset, uint32_t maxDrawCount, uint32_t stride);
+typedef void (VKAPI_PTR *PFN_vkCmdDrawIndexedIndirectCount)(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset, VkBuffer countBuffer, VkDeviceSize countBufferOffset, uint32_t maxDrawCount, uint32_t stride);
+typedef VkResult (VKAPI_PTR *PFN_vkCreateRenderPass2)(VkDevice device, const VkRenderPassCreateInfo2* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkRenderPass* pRenderPass);
+typedef void (VKAPI_PTR *PFN_vkCmdBeginRenderPass2)(VkCommandBuffer commandBuffer, const VkRenderPassBeginInfo*      pRenderPassBegin, const VkSubpassBeginInfo*      pSubpassBeginInfo);
+typedef void (VKAPI_PTR *PFN_vkCmdNextSubpass2)(VkCommandBuffer commandBuffer, const VkSubpassBeginInfo*      pSubpassBeginInfo, const VkSubpassEndInfo*        pSubpassEndInfo);
+typedef void (VKAPI_PTR *PFN_vkCmdEndRenderPass2)(VkCommandBuffer commandBuffer, const VkSubpassEndInfo*        pSubpassEndInfo);
+typedef void (VKAPI_PTR *PFN_vkResetQueryPool)(VkDevice device, VkQueryPool queryPool, uint32_t firstQuery, uint32_t queryCount);
+typedef VkResult (VKAPI_PTR *PFN_vkGetSemaphoreCounterValue)(VkDevice device, VkSemaphore semaphore, uint64_t* pValue);
+typedef VkResult (VKAPI_PTR *PFN_vkWaitSemaphores)(VkDevice device, const VkSemaphoreWaitInfo* pWaitInfo, uint64_t timeout);
+typedef VkResult (VKAPI_PTR *PFN_vkSignalSemaphore)(VkDevice device, const VkSemaphoreSignalInfo* pSignalInfo);
+typedef VkDeviceAddress (VKAPI_PTR *PFN_vkGetBufferDeviceAddress)(VkDevice device, const VkBufferDeviceAddressInfo* pInfo);
+typedef uint64_t (VKAPI_PTR *PFN_vkGetBufferOpaqueCaptureAddress)(VkDevice device, const VkBufferDeviceAddressInfo* pInfo);
+typedef uint64_t (VKAPI_PTR *PFN_vkGetDeviceMemoryOpaqueCaptureAddress)(VkDevice device, const VkDeviceMemoryOpaqueCaptureAddressInfo* pInfo);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR void VKAPI_CALL vkCmdDrawIndirectCount(
+    VkCommandBuffer                             commandBuffer,
+    VkBuffer                                    buffer,
+    VkDeviceSize                                offset,
+    VkBuffer                                    countBuffer,
+    VkDeviceSize                                countBufferOffset,
+    uint32_t                                    maxDrawCount,
+    uint32_t                                    stride);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdDrawIndexedIndirectCount(
+    VkCommandBuffer                             commandBuffer,
+    VkBuffer                                    buffer,
+    VkDeviceSize                                offset,
+    VkBuffer                                    countBuffer,
+    VkDeviceSize                                countBufferOffset,
+    uint32_t                                    maxDrawCount,
+    uint32_t                                    stride);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateRenderPass2(
+    VkDevice                                    device,
+    const VkRenderPassCreateInfo2*              pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkRenderPass*                               pRenderPass);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdBeginRenderPass2(
+    VkCommandBuffer                             commandBuffer,
+    const VkRenderPassBeginInfo*                pRenderPassBegin,
+    const VkSubpassBeginInfo*                   pSubpassBeginInfo);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdNextSubpass2(
+    VkCommandBuffer                             commandBuffer,
+    const VkSubpassBeginInfo*                   pSubpassBeginInfo,
+    const VkSubpassEndInfo*                     pSubpassEndInfo);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdEndRenderPass2(
+    VkCommandBuffer                             commandBuffer,
+    const VkSubpassEndInfo*                     pSubpassEndInfo);
+
+VKAPI_ATTR void VKAPI_CALL vkResetQueryPool(
+    VkDevice                                    device,
+    VkQueryPool                                 queryPool,
+    uint32_t                                    firstQuery,
+    uint32_t                                    queryCount);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetSemaphoreCounterValue(
+    VkDevice                                    device,
+    VkSemaphore                                 semaphore,
+    uint64_t*                                   pValue);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkWaitSemaphores(
+    VkDevice                                    device,
+    const VkSemaphoreWaitInfo*                  pWaitInfo,
+    uint64_t                                    timeout);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkSignalSemaphore(
+    VkDevice                                    device,
+    const VkSemaphoreSignalInfo*                pSignalInfo);
+
+VKAPI_ATTR VkDeviceAddress VKAPI_CALL vkGetBufferDeviceAddress(
+    VkDevice                                    device,
+    const VkBufferDeviceAddressInfo*            pInfo);
+
+VKAPI_ATTR uint64_t VKAPI_CALL vkGetBufferOpaqueCaptureAddress(
+    VkDevice                                    device,
+    const VkBufferDeviceAddressInfo*            pInfo);
+
+VKAPI_ATTR uint64_t VKAPI_CALL vkGetDeviceMemoryOpaqueCaptureAddress(
+    VkDevice                                    device,
+    const VkDeviceMemoryOpaqueCaptureAddressInfo* pInfo);
+#endif
+
+
+#define VK_KHR_surface 1
+VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkSurfaceKHR)
+#define VK_KHR_SURFACE_SPEC_VERSION       25
+#define VK_KHR_SURFACE_EXTENSION_NAME     "VK_KHR_surface"
+
+typedef enum VkPresentModeKHR {
+    VK_PRESENT_MODE_IMMEDIATE_KHR = 0,
+    VK_PRESENT_MODE_MAILBOX_KHR = 1,
+    VK_PRESENT_MODE_FIFO_KHR = 2,
+    VK_PRESENT_MODE_FIFO_RELAXED_KHR = 3,
+    VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR = 1000111000,
+    VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR = 1000111001,
+    VK_PRESENT_MODE_MAX_ENUM_KHR = 0x7FFFFFFF
+} VkPresentModeKHR;
+
+typedef enum VkColorSpaceKHR {
+    VK_COLOR_SPACE_SRGB_NONLINEAR_KHR = 0,
+    VK_COLOR_SPACE_DISPLAY_P3_NONLINEAR_EXT = 1000104001,
+    VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT = 1000104002,
+    VK_COLOR_SPACE_DISPLAY_P3_LINEAR_EXT = 1000104003,
+    VK_COLOR_SPACE_DCI_P3_NONLINEAR_EXT = 1000104004,
+    VK_COLOR_SPACE_BT709_LINEAR_EXT = 1000104005,
+    VK_COLOR_SPACE_BT709_NONLINEAR_EXT = 1000104006,
+    VK_COLOR_SPACE_BT2020_LINEAR_EXT = 1000104007,
+    VK_COLOR_SPACE_HDR10_ST2084_EXT = 1000104008,
+    VK_COLOR_SPACE_DOLBYVISION_EXT = 1000104009,
+    VK_COLOR_SPACE_HDR10_HLG_EXT = 1000104010,
+    VK_COLOR_SPACE_ADOBERGB_LINEAR_EXT = 1000104011,
+    VK_COLOR_SPACE_ADOBERGB_NONLINEAR_EXT = 1000104012,
+    VK_COLOR_SPACE_PASS_THROUGH_EXT = 1000104013,
+    VK_COLOR_SPACE_EXTENDED_SRGB_NONLINEAR_EXT = 1000104014,
+    VK_COLOR_SPACE_DISPLAY_NATIVE_AMD = 1000213000,
+    VK_COLORSPACE_SRGB_NONLINEAR_KHR = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR,
+    VK_COLOR_SPACE_DCI_P3_LINEAR_EXT = VK_COLOR_SPACE_DISPLAY_P3_LINEAR_EXT,
+    VK_COLOR_SPACE_MAX_ENUM_KHR = 0x7FFFFFFF
+} VkColorSpaceKHR;
+
+typedef enum VkSurfaceTransformFlagBitsKHR {
+    VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR = 0x00000001,
+    VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR = 0x00000002,
+    VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR = 0x00000004,
+    VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR = 0x00000008,
+    VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_BIT_KHR = 0x00000010,
+    VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_ROTATE_90_BIT_KHR = 0x00000020,
+    VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_ROTATE_180_BIT_KHR = 0x00000040,
+    VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_ROTATE_270_BIT_KHR = 0x00000080,
+    VK_SURFACE_TRANSFORM_INHERIT_BIT_KHR = 0x00000100,
+    VK_SURFACE_TRANSFORM_FLAG_BITS_MAX_ENUM_KHR = 0x7FFFFFFF
+} VkSurfaceTransformFlagBitsKHR;
+
+typedef enum VkCompositeAlphaFlagBitsKHR {
+    VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR = 0x00000001,
+    VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR = 0x00000002,
+    VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR = 0x00000004,
+    VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR = 0x00000008,
+    VK_COMPOSITE_ALPHA_FLAG_BITS_MAX_ENUM_KHR = 0x7FFFFFFF
+} VkCompositeAlphaFlagBitsKHR;
+typedef VkFlags VkCompositeAlphaFlagsKHR;
+typedef VkFlags VkSurfaceTransformFlagsKHR;
+typedef struct VkSurfaceCapabilitiesKHR {
+    uint32_t                         minImageCount;
+    uint32_t                         maxImageCount;
+    VkExtent2D                       currentExtent;
+    VkExtent2D                       minImageExtent;
+    VkExtent2D                       maxImageExtent;
+    uint32_t                         maxImageArrayLayers;
+    VkSurfaceTransformFlagsKHR       supportedTransforms;
+    VkSurfaceTransformFlagBitsKHR    currentTransform;
+    VkCompositeAlphaFlagsKHR         supportedCompositeAlpha;
+    VkImageUsageFlags                supportedUsageFlags;
+} VkSurfaceCapabilitiesKHR;
+
+typedef struct VkSurfaceFormatKHR {
+    VkFormat           format;
+    VkColorSpaceKHR    colorSpace;
+} VkSurfaceFormatKHR;
+
+typedef void (VKAPI_PTR *PFN_vkDestroySurfaceKHR)(VkInstance instance, VkSurfaceKHR surface, const VkAllocationCallbacks* pAllocator);
+typedef VkResult (VKAPI_PTR *PFN_vkGetPhysicalDeviceSurfaceSupportKHR)(VkPhysicalDevice physicalDevice, uint32_t queueFamilyIndex, VkSurfaceKHR surface, VkBool32* pSupported);
+typedef VkResult (VKAPI_PTR *PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR)(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, VkSurfaceCapabilitiesKHR* pSurfaceCapabilities);
+typedef VkResult (VKAPI_PTR *PFN_vkGetPhysicalDeviceSurfaceFormatsKHR)(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, uint32_t* pSurfaceFormatCount, VkSurfaceFormatKHR* pSurfaceFormats);
+typedef VkResult (VKAPI_PTR *PFN_vkGetPhysicalDeviceSurfacePresentModesKHR)(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, uint32_t* pPresentModeCount, VkPresentModeKHR* pPresentModes);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR void VKAPI_CALL vkDestroySurfaceKHR(
+    VkInstance                                  instance,
+    VkSurfaceKHR                                surface,
+    const VkAllocationCallbacks*                pAllocator);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceSurfaceSupportKHR(
+    VkPhysicalDevice                            physicalDevice,
+    uint32_t                                    queueFamilyIndex,
+    VkSurfaceKHR                                surface,
+    VkBool32*                                   pSupported);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceSurfaceCapabilitiesKHR(
+    VkPhysicalDevice                            physicalDevice,
+    VkSurfaceKHR                                surface,
+    VkSurfaceCapabilitiesKHR*                   pSurfaceCapabilities);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceSurfaceFormatsKHR(
+    VkPhysicalDevice                            physicalDevice,
+    VkSurfaceKHR                                surface,
+    uint32_t*                                   pSurfaceFormatCount,
+    VkSurfaceFormatKHR*                         pSurfaceFormats);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceSurfacePresentModesKHR(
+    VkPhysicalDevice                            physicalDevice,
+    VkSurfaceKHR                                surface,
+    uint32_t*                                   pPresentModeCount,
+    VkPresentModeKHR*                           pPresentModes);
+#endif
+
+
+#define VK_KHR_swapchain 1
+VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkSwapchainKHR)
+#define VK_KHR_SWAPCHAIN_SPEC_VERSION     70
+#define VK_KHR_SWAPCHAIN_EXTENSION_NAME   "VK_KHR_swapchain"
+
+typedef enum VkSwapchainCreateFlagBitsKHR {
+    VK_SWAPCHAIN_CREATE_SPLIT_INSTANCE_BIND_REGIONS_BIT_KHR = 0x00000001,
+    VK_SWAPCHAIN_CREATE_PROTECTED_BIT_KHR = 0x00000002,
+    VK_SWAPCHAIN_CREATE_MUTABLE_FORMAT_BIT_KHR = 0x00000004,
+    VK_SWAPCHAIN_CREATE_FLAG_BITS_MAX_ENUM_KHR = 0x7FFFFFFF
+} VkSwapchainCreateFlagBitsKHR;
+typedef VkFlags VkSwapchainCreateFlagsKHR;
+
+typedef enum VkDeviceGroupPresentModeFlagBitsKHR {
+    VK_DEVICE_GROUP_PRESENT_MODE_LOCAL_BIT_KHR = 0x00000001,
+    VK_DEVICE_GROUP_PRESENT_MODE_REMOTE_BIT_KHR = 0x00000002,
+    VK_DEVICE_GROUP_PRESENT_MODE_SUM_BIT_KHR = 0x00000004,
+    VK_DEVICE_GROUP_PRESENT_MODE_LOCAL_MULTI_DEVICE_BIT_KHR = 0x00000008,
+    VK_DEVICE_GROUP_PRESENT_MODE_FLAG_BITS_MAX_ENUM_KHR = 0x7FFFFFFF
+} VkDeviceGroupPresentModeFlagBitsKHR;
+typedef VkFlags VkDeviceGroupPresentModeFlagsKHR;
+typedef struct VkSwapchainCreateInfoKHR {
+    VkStructureType                  sType;
+    const void*                      pNext;
+    VkSwapchainCreateFlagsKHR        flags;
+    VkSurfaceKHR                     surface;
+    uint32_t                         minImageCount;
+    VkFormat                         imageFormat;
+    VkColorSpaceKHR                  imageColorSpace;
+    VkExtent2D                       imageExtent;
+    uint32_t                         imageArrayLayers;
+    VkImageUsageFlags                imageUsage;
+    VkSharingMode                    imageSharingMode;
+    uint32_t                         queueFamilyIndexCount;
+    const uint32_t*                  pQueueFamilyIndices;
+    VkSurfaceTransformFlagBitsKHR    preTransform;
+    VkCompositeAlphaFlagBitsKHR      compositeAlpha;
+    VkPresentModeKHR                 presentMode;
+    VkBool32                         clipped;
+    VkSwapchainKHR                   oldSwapchain;
+} VkSwapchainCreateInfoKHR;
+
+typedef struct VkPresentInfoKHR {
+    VkStructureType          sType;
+    const void*              pNext;
+    uint32_t                 waitSemaphoreCount;
+    const VkSemaphore*       pWaitSemaphores;
+    uint32_t                 swapchainCount;
+    const VkSwapchainKHR*    pSwapchains;
+    const uint32_t*          pImageIndices;
+    VkResult*                pResults;
+} VkPresentInfoKHR;
+
+typedef struct VkImageSwapchainCreateInfoKHR {
+    VkStructureType    sType;
+    const void*        pNext;
+    VkSwapchainKHR     swapchain;
+} VkImageSwapchainCreateInfoKHR;
+
+typedef struct VkBindImageMemorySwapchainInfoKHR {
+    VkStructureType    sType;
+    const void*        pNext;
+    VkSwapchainKHR     swapchain;
+    uint32_t           imageIndex;
+} VkBindImageMemorySwapchainInfoKHR;
+
+typedef struct VkAcquireNextImageInfoKHR {
+    VkStructureType    sType;
+    const void*        pNext;
+    VkSwapchainKHR     swapchain;
+    uint64_t           timeout;
+    VkSemaphore        semaphore;
+    VkFence            fence;
+    uint32_t           deviceMask;
+} VkAcquireNextImageInfoKHR;
+
+typedef struct VkDeviceGroupPresentCapabilitiesKHR {
+    VkStructureType                     sType;
+    void*                               pNext;
+    uint32_t                            presentMask[VK_MAX_DEVICE_GROUP_SIZE];
+    VkDeviceGroupPresentModeFlagsKHR    modes;
+} VkDeviceGroupPresentCapabilitiesKHR;
+
+typedef struct VkDeviceGroupPresentInfoKHR {
+    VkStructureType                        sType;
+    const void*                            pNext;
+    uint32_t                               swapchainCount;
+    const uint32_t*                        pDeviceMasks;
+    VkDeviceGroupPresentModeFlagBitsKHR    mode;
+} VkDeviceGroupPresentInfoKHR;
+
+typedef struct VkDeviceGroupSwapchainCreateInfoKHR {
+    VkStructureType                     sType;
+    const void*                         pNext;
+    VkDeviceGroupPresentModeFlagsKHR    modes;
+} VkDeviceGroupSwapchainCreateInfoKHR;
+
+typedef VkResult (VKAPI_PTR *PFN_vkCreateSwapchainKHR)(VkDevice device, const VkSwapchainCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSwapchainKHR* pSwapchain);
+typedef void (VKAPI_PTR *PFN_vkDestroySwapchainKHR)(VkDevice device, VkSwapchainKHR swapchain, const VkAllocationCallbacks* pAllocator);
+typedef VkResult (VKAPI_PTR *PFN_vkGetSwapchainImagesKHR)(VkDevice device, VkSwapchainKHR swapchain, uint32_t* pSwapchainImageCount, VkImage* pSwapchainImages);
+typedef VkResult (VKAPI_PTR *PFN_vkAcquireNextImageKHR)(VkDevice device, VkSwapchainKHR swapchain, uint64_t timeout, VkSemaphore semaphore, VkFence fence, uint32_t* pImageIndex);
+typedef VkResult (VKAPI_PTR *PFN_vkQueuePresentKHR)(VkQueue queue, const VkPresentInfoKHR* pPresentInfo);
+typedef VkResult (VKAPI_PTR *PFN_vkGetDeviceGroupPresentCapabilitiesKHR)(VkDevice device, VkDeviceGroupPresentCapabilitiesKHR* pDeviceGroupPresentCapabilities);
+typedef VkResult (VKAPI_PTR *PFN_vkGetDeviceGroupSurfacePresentModesKHR)(VkDevice device, VkSurfaceKHR surface, VkDeviceGroupPresentModeFlagsKHR* pModes);
+typedef VkResult (VKAPI_PTR *PFN_vkGetPhysicalDevicePresentRectanglesKHR)(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, uint32_t* pRectCount, VkRect2D* pRects);
+typedef VkResult (VKAPI_PTR *PFN_vkAcquireNextImage2KHR)(VkDevice device, const VkAcquireNextImageInfoKHR* pAcquireInfo, uint32_t* pImageIndex);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateSwapchainKHR(
+    VkDevice                                    device,
+    const VkSwapchainCreateInfoKHR*             pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkSwapchainKHR*                             pSwapchain);
+
+VKAPI_ATTR void VKAPI_CALL vkDestroySwapchainKHR(
+    VkDevice                                    device,
+    VkSwapchainKHR                              swapchain,
+    const VkAllocationCallbacks*                pAllocator);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetSwapchainImagesKHR(
+    VkDevice                                    device,
+    VkSwapchainKHR                              swapchain,
+    uint32_t*                                   pSwapchainImageCount,
+    VkImage*                                    pSwapchainImages);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkAcquireNextImageKHR(
+    VkDevice                                    device,
+    VkSwapchainKHR                              swapchain,
+    uint64_t                                    timeout,
+    VkSemaphore                                 semaphore,
+    VkFence                                     fence,
+    uint32_t*                                   pImageIndex);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkQueuePresentKHR(
+    VkQueue                                     queue,
+    const VkPresentInfoKHR*                     pPresentInfo);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetDeviceGroupPresentCapabilitiesKHR(
+    VkDevice                                    device,
+    VkDeviceGroupPresentCapabilitiesKHR*        pDeviceGroupPresentCapabilities);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetDeviceGroupSurfacePresentModesKHR(
+    VkDevice                                    device,
+    VkSurfaceKHR                                surface,
+    VkDeviceGroupPresentModeFlagsKHR*           pModes);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDevicePresentRectanglesKHR(
+    VkPhysicalDevice                            physicalDevice,
+    VkSurfaceKHR                                surface,
+    uint32_t*                                   pRectCount,
+    VkRect2D*                                   pRects);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkAcquireNextImage2KHR(
+    VkDevice                                    device,
+    const VkAcquireNextImageInfoKHR*            pAcquireInfo,
+    uint32_t*                                   pImageIndex);
+#endif
+
+
+#define VK_KHR_display 1
+VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkDisplayKHR)
+VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkDisplayModeKHR)
+#define VK_KHR_DISPLAY_SPEC_VERSION       23
+#define VK_KHR_DISPLAY_EXTENSION_NAME     "VK_KHR_display"
+typedef VkFlags VkDisplayModeCreateFlagsKHR;
+
+typedef enum VkDisplayPlaneAlphaFlagBitsKHR {
+    VK_DISPLAY_PLANE_ALPHA_OPAQUE_BIT_KHR = 0x00000001,
+    VK_DISPLAY_PLANE_ALPHA_GLOBAL_BIT_KHR = 0x00000002,
+    VK_DISPLAY_PLANE_ALPHA_PER_PIXEL_BIT_KHR = 0x00000004,
+    VK_DISPLAY_PLANE_ALPHA_PER_PIXEL_PREMULTIPLIED_BIT_KHR = 0x00000008,
+    VK_DISPLAY_PLANE_ALPHA_FLAG_BITS_MAX_ENUM_KHR = 0x7FFFFFFF
+} VkDisplayPlaneAlphaFlagBitsKHR;
+typedef VkFlags VkDisplayPlaneAlphaFlagsKHR;
+typedef VkFlags VkDisplaySurfaceCreateFlagsKHR;
+typedef struct VkDisplayModeParametersKHR {
+    VkExtent2D    visibleRegion;
+    uint32_t      refreshRate;
+} VkDisplayModeParametersKHR;
+
+typedef struct VkDisplayModeCreateInfoKHR {
+    VkStructureType                sType;
+    const void*                    pNext;
+    VkDisplayModeCreateFlagsKHR    flags;
+    VkDisplayModeParametersKHR     parameters;
+} VkDisplayModeCreateInfoKHR;
+
+typedef struct VkDisplayModePropertiesKHR {
+    VkDisplayModeKHR              displayMode;
+    VkDisplayModeParametersKHR    parameters;
+} VkDisplayModePropertiesKHR;
+
+typedef struct VkDisplayPlaneCapabilitiesKHR {
+    VkDisplayPlaneAlphaFlagsKHR    supportedAlpha;
+    VkOffset2D                     minSrcPosition;
+    VkOffset2D                     maxSrcPosition;
+    VkExtent2D                     minSrcExtent;
+    VkExtent2D                     maxSrcExtent;
+    VkOffset2D                     minDstPosition;
+    VkOffset2D                     maxDstPosition;
+    VkExtent2D                     minDstExtent;
+    VkExtent2D                     maxDstExtent;
+} VkDisplayPlaneCapabilitiesKHR;
+
+typedef struct VkDisplayPlanePropertiesKHR {
+    VkDisplayKHR    currentDisplay;
+    uint32_t        currentStackIndex;
+} VkDisplayPlanePropertiesKHR;
+
+typedef struct VkDisplayPropertiesKHR {
+    VkDisplayKHR                  display;
+    const char*                   displayName;
+    VkExtent2D                    physicalDimensions;
+    VkExtent2D                    physicalResolution;
+    VkSurfaceTransformFlagsKHR    supportedTransforms;
+    VkBool32                      planeReorderPossible;
+    VkBool32                      persistentContent;
+} VkDisplayPropertiesKHR;
+
+typedef struct VkDisplaySurfaceCreateInfoKHR {
+    VkStructureType                   sType;
+    const void*                       pNext;
+    VkDisplaySurfaceCreateFlagsKHR    flags;
+    VkDisplayModeKHR                  displayMode;
+    uint32_t                          planeIndex;
+    uint32_t                          planeStackIndex;
+    VkSurfaceTransformFlagBitsKHR     transform;
+    float                             globalAlpha;
+    VkDisplayPlaneAlphaFlagBitsKHR    alphaMode;
+    VkExtent2D                        imageExtent;
+} VkDisplaySurfaceCreateInfoKHR;
+
+typedef VkResult (VKAPI_PTR *PFN_vkGetPhysicalDeviceDisplayPropertiesKHR)(VkPhysicalDevice physicalDevice, uint32_t* pPropertyCount, VkDisplayPropertiesKHR* pProperties);
+typedef VkResult (VKAPI_PTR *PFN_vkGetPhysicalDeviceDisplayPlanePropertiesKHR)(VkPhysicalDevice physicalDevice, uint32_t* pPropertyCount, VkDisplayPlanePropertiesKHR* pProperties);
+typedef VkResult (VKAPI_PTR *PFN_vkGetDisplayPlaneSupportedDisplaysKHR)(VkPhysicalDevice physicalDevice, uint32_t planeIndex, uint32_t* pDisplayCount, VkDisplayKHR* pDisplays);
+typedef VkResult (VKAPI_PTR *PFN_vkGetDisplayModePropertiesKHR)(VkPhysicalDevice physicalDevice, VkDisplayKHR display, uint32_t* pPropertyCount, VkDisplayModePropertiesKHR* pProperties);
+typedef VkResult (VKAPI_PTR *PFN_vkCreateDisplayModeKHR)(VkPhysicalDevice physicalDevice, VkDisplayKHR display, const VkDisplayModeCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDisplayModeKHR* pMode);
+typedef VkResult (VKAPI_PTR *PFN_vkGetDisplayPlaneCapabilitiesKHR)(VkPhysicalDevice physicalDevice, VkDisplayModeKHR mode, uint32_t planeIndex, VkDisplayPlaneCapabilitiesKHR* pCapabilities);
+typedef VkResult (VKAPI_PTR *PFN_vkCreateDisplayPlaneSurfaceKHR)(VkInstance instance, const VkDisplaySurfaceCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceDisplayPropertiesKHR(
+    VkPhysicalDevice                            physicalDevice,
+    uint32_t*                                   pPropertyCount,
+    VkDisplayPropertiesKHR*                     pProperties);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceDisplayPlanePropertiesKHR(
+    VkPhysicalDevice                            physicalDevice,
+    uint32_t*                                   pPropertyCount,
+    VkDisplayPlanePropertiesKHR*                pProperties);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetDisplayPlaneSupportedDisplaysKHR(
+    VkPhysicalDevice                            physicalDevice,
+    uint32_t                                    planeIndex,
+    uint32_t*                                   pDisplayCount,
+    VkDisplayKHR*                               pDisplays);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetDisplayModePropertiesKHR(
+    VkPhysicalDevice                            physicalDevice,
+    VkDisplayKHR                                display,
+    uint32_t*                                   pPropertyCount,
+    VkDisplayModePropertiesKHR*                 pProperties);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateDisplayModeKHR(
+    VkPhysicalDevice                            physicalDevice,
+    VkDisplayKHR                                display,
+    const VkDisplayModeCreateInfoKHR*           pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkDisplayModeKHR*                           pMode);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetDisplayPlaneCapabilitiesKHR(
+    VkPhysicalDevice                            physicalDevice,
+    VkDisplayModeKHR                            mode,
+    uint32_t                                    planeIndex,
+    VkDisplayPlaneCapabilitiesKHR*              pCapabilities);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateDisplayPlaneSurfaceKHR(
+    VkInstance                                  instance,
+    const VkDisplaySurfaceCreateInfoKHR*        pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkSurfaceKHR*                               pSurface);
+#endif
+
+
+#define VK_KHR_display_swapchain 1
+#define VK_KHR_DISPLAY_SWAPCHAIN_SPEC_VERSION 10
+#define VK_KHR_DISPLAY_SWAPCHAIN_EXTENSION_NAME "VK_KHR_display_swapchain"
+typedef struct VkDisplayPresentInfoKHR {
+    VkStructureType    sType;
+    const void*        pNext;
+    VkRect2D           srcRect;
+    VkRect2D           dstRect;
+    VkBool32           persistent;
+} VkDisplayPresentInfoKHR;
+
+typedef VkResult (VKAPI_PTR *PFN_vkCreateSharedSwapchainsKHR)(VkDevice device, uint32_t swapchainCount, const VkSwapchainCreateInfoKHR* pCreateInfos, const VkAllocationCallbacks* pAllocator, VkSwapchainKHR* pSwapchains);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateSharedSwapchainsKHR(
+    VkDevice                                    device,
+    uint32_t                                    swapchainCount,
+    const VkSwapchainCreateInfoKHR*             pCreateInfos,
+    const VkAllocationCallbacks*                pAllocator,
+    VkSwapchainKHR*                             pSwapchains);
+#endif
+
+
+#define VK_KHR_sampler_mirror_clamp_to_edge 1
+#define VK_KHR_SAMPLER_MIRROR_CLAMP_TO_EDGE_SPEC_VERSION 3
+#define VK_KHR_SAMPLER_MIRROR_CLAMP_TO_EDGE_EXTENSION_NAME "VK_KHR_sampler_mirror_clamp_to_edge"
+
+
+#define VK_KHR_dynamic_rendering 1
+#define VK_KHR_DYNAMIC_RENDERING_SPEC_VERSION 1
+#define VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME "VK_KHR_dynamic_rendering"
+
+typedef enum VkRenderingFlagBitsKHR {
+    VK_RENDERING_CONTENTS_SECONDARY_COMMAND_BUFFERS_BIT_KHR = 0x00000001,
+    VK_RENDERING_SUSPENDING_BIT_KHR = 0x00000002,
+    VK_RENDERING_RESUMING_BIT_KHR = 0x00000004,
+    VK_RENDERING_FLAG_BITS_MAX_ENUM_KHR = 0x7FFFFFFF
+} VkRenderingFlagBitsKHR;
+typedef VkFlags VkRenderingFlagsKHR;
+typedef struct VkRenderingAttachmentInfoKHR {
+    VkStructureType          sType;
+    const void*              pNext;
+    VkImageView              imageView;
+    VkImageLayout            imageLayout;
+    VkResolveModeFlagBits    resolveMode;
+    VkImageView              resolveImageView;
+    VkImageLayout            resolveImageLayout;
+    VkAttachmentLoadOp       loadOp;
+    VkAttachmentStoreOp      storeOp;
+    VkClearValue             clearValue;
+} VkRenderingAttachmentInfoKHR;
+
+typedef struct VkRenderingInfoKHR {
+    VkStructureType                        sType;
+    const void*                            pNext;
+    VkRenderingFlagsKHR                    flags;
+    VkRect2D                               renderArea;
+    uint32_t                               layerCount;
+    uint32_t                               viewMask;
+    uint32_t                               colorAttachmentCount;
+    const VkRenderingAttachmentInfoKHR*    pColorAttachments;
+    const VkRenderingAttachmentInfoKHR*    pDepthAttachment;
+    const VkRenderingAttachmentInfoKHR*    pStencilAttachment;
+} VkRenderingInfoKHR;
+
+typedef struct VkPipelineRenderingCreateInfoKHR {
+    VkStructureType    sType;
+    const void*        pNext;
+    uint32_t           viewMask;
+    uint32_t           colorAttachmentCount;
+    const VkFormat*    pColorAttachmentFormats;
+    VkFormat           depthAttachmentFormat;
+    VkFormat           stencilAttachmentFormat;
+} VkPipelineRenderingCreateInfoKHR;
+
+typedef struct VkPhysicalDeviceDynamicRenderingFeaturesKHR {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           dynamicRendering;
+} VkPhysicalDeviceDynamicRenderingFeaturesKHR;
+
+typedef struct VkCommandBufferInheritanceRenderingInfoKHR {
+    VkStructureType          sType;
+    const void*              pNext;
+    VkRenderingFlagsKHR      flags;
+    uint32_t                 viewMask;
+    uint32_t                 colorAttachmentCount;
+    const VkFormat*          pColorAttachmentFormats;
+    VkFormat                 depthAttachmentFormat;
+    VkFormat                 stencilAttachmentFormat;
+    VkSampleCountFlagBits    rasterizationSamples;
+} VkCommandBufferInheritanceRenderingInfoKHR;
+
+typedef struct VkRenderingFragmentShadingRateAttachmentInfoKHR {
+    VkStructureType    sType;
+    const void*        pNext;
+    VkImageView        imageView;
+    VkImageLayout      imageLayout;
+    VkExtent2D         shadingRateAttachmentTexelSize;
+} VkRenderingFragmentShadingRateAttachmentInfoKHR;
+
+typedef struct VkRenderingFragmentDensityMapAttachmentInfoEXT {
+    VkStructureType    sType;
+    const void*        pNext;
+    VkImageView        imageView;
+    VkImageLayout      imageLayout;
+} VkRenderingFragmentDensityMapAttachmentInfoEXT;
+
+typedef struct VkAttachmentSampleCountInfoAMD {
+    VkStructureType                 sType;
+    const void*                     pNext;
+    uint32_t                        colorAttachmentCount;
+    const VkSampleCountFlagBits*    pColorAttachmentSamples;
+    VkSampleCountFlagBits           depthStencilAttachmentSamples;
+} VkAttachmentSampleCountInfoAMD;
+
+typedef VkAttachmentSampleCountInfoAMD VkAttachmentSampleCountInfoNV;
+
+typedef struct VkMultiviewPerViewAttributesInfoNVX {
+    VkStructureType    sType;
+    const void*        pNext;
+    VkBool32           perViewAttributes;
+    VkBool32           perViewAttributesPositionXOnly;
+} VkMultiviewPerViewAttributesInfoNVX;
+
+typedef void (VKAPI_PTR *PFN_vkCmdBeginRenderingKHR)(VkCommandBuffer                   commandBuffer, const VkRenderingInfoKHR*                           pRenderingInfo);
+typedef void (VKAPI_PTR *PFN_vkCmdEndRenderingKHR)(VkCommandBuffer                   commandBuffer);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR void VKAPI_CALL vkCmdBeginRenderingKHR(
+    VkCommandBuffer                             commandBuffer,
+    const VkRenderingInfoKHR*                   pRenderingInfo);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdEndRenderingKHR(
+    VkCommandBuffer                             commandBuffer);
+#endif
+
+
+#define VK_KHR_multiview 1
+#define VK_KHR_MULTIVIEW_SPEC_VERSION     1
+#define VK_KHR_MULTIVIEW_EXTENSION_NAME   "VK_KHR_multiview"
+typedef VkRenderPassMultiviewCreateInfo VkRenderPassMultiviewCreateInfoKHR;
+
+typedef VkPhysicalDeviceMultiviewFeatures VkPhysicalDeviceMultiviewFeaturesKHR;
+
+typedef VkPhysicalDeviceMultiviewProperties VkPhysicalDeviceMultiviewPropertiesKHR;
+
+
+
+#define VK_KHR_get_physical_device_properties2 1
+#define VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_SPEC_VERSION 2
+#define VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME "VK_KHR_get_physical_device_properties2"
+typedef VkPhysicalDeviceFeatures2 VkPhysicalDeviceFeatures2KHR;
+
+typedef VkPhysicalDeviceProperties2 VkPhysicalDeviceProperties2KHR;
+
+typedef VkFormatProperties2 VkFormatProperties2KHR;
+
+typedef VkImageFormatProperties2 VkImageFormatProperties2KHR;
+
+typedef VkPhysicalDeviceImageFormatInfo2 VkPhysicalDeviceImageFormatInfo2KHR;
+
+typedef VkQueueFamilyProperties2 VkQueueFamilyProperties2KHR;
+
+typedef VkPhysicalDeviceMemoryProperties2 VkPhysicalDeviceMemoryProperties2KHR;
+
+typedef VkSparseImageFormatProperties2 VkSparseImageFormatProperties2KHR;
+
+typedef VkPhysicalDeviceSparseImageFormatInfo2 VkPhysicalDeviceSparseImageFormatInfo2KHR;
+
+typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceFeatures2KHR)(VkPhysicalDevice physicalDevice, VkPhysicalDeviceFeatures2* pFeatures);
+typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceProperties2KHR)(VkPhysicalDevice physicalDevice, VkPhysicalDeviceProperties2* pProperties);
+typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceFormatProperties2KHR)(VkPhysicalDevice physicalDevice, VkFormat format, VkFormatProperties2* pFormatProperties);
+typedef VkResult (VKAPI_PTR *PFN_vkGetPhysicalDeviceImageFormatProperties2KHR)(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceImageFormatInfo2* pImageFormatInfo, VkImageFormatProperties2* pImageFormatProperties);
+typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceQueueFamilyProperties2KHR)(VkPhysicalDevice physicalDevice, uint32_t* pQueueFamilyPropertyCount, VkQueueFamilyProperties2* pQueueFamilyProperties);
+typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceMemoryProperties2KHR)(VkPhysicalDevice physicalDevice, VkPhysicalDeviceMemoryProperties2* pMemoryProperties);
+typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceSparseImageFormatProperties2KHR)(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceSparseImageFormatInfo2* pFormatInfo, uint32_t* pPropertyCount, VkSparseImageFormatProperties2* pProperties);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceFeatures2KHR(
+    VkPhysicalDevice                            physicalDevice,
+    VkPhysicalDeviceFeatures2*                  pFeatures);
+
+VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceProperties2KHR(
+    VkPhysicalDevice                            physicalDevice,
+    VkPhysicalDeviceProperties2*                pProperties);
+
+VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceFormatProperties2KHR(
+    VkPhysicalDevice                            physicalDevice,
+    VkFormat                                    format,
+    VkFormatProperties2*                        pFormatProperties);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceImageFormatProperties2KHR(
+    VkPhysicalDevice                            physicalDevice,
+    const VkPhysicalDeviceImageFormatInfo2*     pImageFormatInfo,
+    VkImageFormatProperties2*                   pImageFormatProperties);
+
+VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceQueueFamilyProperties2KHR(
+    VkPhysicalDevice                            physicalDevice,
+    uint32_t*                                   pQueueFamilyPropertyCount,
+    VkQueueFamilyProperties2*                   pQueueFamilyProperties);
+
+VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceMemoryProperties2KHR(
+    VkPhysicalDevice                            physicalDevice,
+    VkPhysicalDeviceMemoryProperties2*          pMemoryProperties);
+
+VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceSparseImageFormatProperties2KHR(
+    VkPhysicalDevice                            physicalDevice,
+    const VkPhysicalDeviceSparseImageFormatInfo2* pFormatInfo,
+    uint32_t*                                   pPropertyCount,
+    VkSparseImageFormatProperties2*             pProperties);
+#endif
+
+
+#define VK_KHR_device_group 1
+#define VK_KHR_DEVICE_GROUP_SPEC_VERSION  4
+#define VK_KHR_DEVICE_GROUP_EXTENSION_NAME "VK_KHR_device_group"
+typedef VkPeerMemoryFeatureFlags VkPeerMemoryFeatureFlagsKHR;
+
+typedef VkPeerMemoryFeatureFlagBits VkPeerMemoryFeatureFlagBitsKHR;
+
+typedef VkMemoryAllocateFlags VkMemoryAllocateFlagsKHR;
+
+typedef VkMemoryAllocateFlagBits VkMemoryAllocateFlagBitsKHR;
+
+typedef VkMemoryAllocateFlagsInfo VkMemoryAllocateFlagsInfoKHR;
+
+typedef VkDeviceGroupRenderPassBeginInfo VkDeviceGroupRenderPassBeginInfoKHR;
+
+typedef VkDeviceGroupCommandBufferBeginInfo VkDeviceGroupCommandBufferBeginInfoKHR;
+
+typedef VkDeviceGroupSubmitInfo VkDeviceGroupSubmitInfoKHR;
+
+typedef VkDeviceGroupBindSparseInfo VkDeviceGroupBindSparseInfoKHR;
+
+typedef VkBindBufferMemoryDeviceGroupInfo VkBindBufferMemoryDeviceGroupInfoKHR;
+
+typedef VkBindImageMemoryDeviceGroupInfo VkBindImageMemoryDeviceGroupInfoKHR;
+
+typedef void (VKAPI_PTR *PFN_vkGetDeviceGroupPeerMemoryFeaturesKHR)(VkDevice device, uint32_t heapIndex, uint32_t localDeviceIndex, uint32_t remoteDeviceIndex, VkPeerMemoryFeatureFlags* pPeerMemoryFeatures);
+typedef void (VKAPI_PTR *PFN_vkCmdSetDeviceMaskKHR)(VkCommandBuffer commandBuffer, uint32_t deviceMask);
+typedef void (VKAPI_PTR *PFN_vkCmdDispatchBaseKHR)(VkCommandBuffer commandBuffer, uint32_t baseGroupX, uint32_t baseGroupY, uint32_t baseGroupZ, uint32_t groupCountX, uint32_t groupCountY, uint32_t groupCountZ);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR void VKAPI_CALL vkGetDeviceGroupPeerMemoryFeaturesKHR(
+    VkDevice                                    device,
+    uint32_t                                    heapIndex,
+    uint32_t                                    localDeviceIndex,
+    uint32_t                                    remoteDeviceIndex,
+    VkPeerMemoryFeatureFlags*                   pPeerMemoryFeatures);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdSetDeviceMaskKHR(
+    VkCommandBuffer                             commandBuffer,
+    uint32_t                                    deviceMask);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdDispatchBaseKHR(
+    VkCommandBuffer                             commandBuffer,
+    uint32_t                                    baseGroupX,
+    uint32_t                                    baseGroupY,
+    uint32_t                                    baseGroupZ,
+    uint32_t                                    groupCountX,
+    uint32_t                                    groupCountY,
+    uint32_t                                    groupCountZ);
+#endif
+
+
+#define VK_KHR_shader_draw_parameters 1
+#define VK_KHR_SHADER_DRAW_PARAMETERS_SPEC_VERSION 1
+#define VK_KHR_SHADER_DRAW_PARAMETERS_EXTENSION_NAME "VK_KHR_shader_draw_parameters"
+
+
+#define VK_KHR_maintenance1 1
+#define VK_KHR_MAINTENANCE_1_SPEC_VERSION 2
+#define VK_KHR_MAINTENANCE_1_EXTENSION_NAME "VK_KHR_maintenance1"
+#define VK_KHR_MAINTENANCE1_SPEC_VERSION  VK_KHR_MAINTENANCE_1_SPEC_VERSION
+#define VK_KHR_MAINTENANCE1_EXTENSION_NAME VK_KHR_MAINTENANCE_1_EXTENSION_NAME
+typedef VkCommandPoolTrimFlags VkCommandPoolTrimFlagsKHR;
+
+typedef void (VKAPI_PTR *PFN_vkTrimCommandPoolKHR)(VkDevice device, VkCommandPool commandPool, VkCommandPoolTrimFlags flags);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR void VKAPI_CALL vkTrimCommandPoolKHR(
+    VkDevice                                    device,
+    VkCommandPool                               commandPool,
+    VkCommandPoolTrimFlags                      flags);
+#endif
+
+
+#define VK_KHR_device_group_creation 1
+#define VK_KHR_DEVICE_GROUP_CREATION_SPEC_VERSION 1
+#define VK_KHR_DEVICE_GROUP_CREATION_EXTENSION_NAME "VK_KHR_device_group_creation"
+#define VK_MAX_DEVICE_GROUP_SIZE_KHR      VK_MAX_DEVICE_GROUP_SIZE
+typedef VkPhysicalDeviceGroupProperties VkPhysicalDeviceGroupPropertiesKHR;
+
+typedef VkDeviceGroupDeviceCreateInfo VkDeviceGroupDeviceCreateInfoKHR;
+
+typedef VkResult (VKAPI_PTR *PFN_vkEnumeratePhysicalDeviceGroupsKHR)(VkInstance instance, uint32_t* pPhysicalDeviceGroupCount, VkPhysicalDeviceGroupProperties* pPhysicalDeviceGroupProperties);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkEnumeratePhysicalDeviceGroupsKHR(
+    VkInstance                                  instance,
+    uint32_t*                                   pPhysicalDeviceGroupCount,
+    VkPhysicalDeviceGroupProperties*            pPhysicalDeviceGroupProperties);
+#endif
+
+
+#define VK_KHR_external_memory_capabilities 1
+#define VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_SPEC_VERSION 1
+#define VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME "VK_KHR_external_memory_capabilities"
+#define VK_LUID_SIZE_KHR                  VK_LUID_SIZE
+typedef VkExternalMemoryHandleTypeFlags VkExternalMemoryHandleTypeFlagsKHR;
+
+typedef VkExternalMemoryHandleTypeFlagBits VkExternalMemoryHandleTypeFlagBitsKHR;
+
+typedef VkExternalMemoryFeatureFlags VkExternalMemoryFeatureFlagsKHR;
+
+typedef VkExternalMemoryFeatureFlagBits VkExternalMemoryFeatureFlagBitsKHR;
+
+typedef VkExternalMemoryProperties VkExternalMemoryPropertiesKHR;
+
+typedef VkPhysicalDeviceExternalImageFormatInfo VkPhysicalDeviceExternalImageFormatInfoKHR;
+
+typedef VkExternalImageFormatProperties VkExternalImageFormatPropertiesKHR;
+
+typedef VkPhysicalDeviceExternalBufferInfo VkPhysicalDeviceExternalBufferInfoKHR;
+
+typedef VkExternalBufferProperties VkExternalBufferPropertiesKHR;
+
+typedef VkPhysicalDeviceIDProperties VkPhysicalDeviceIDPropertiesKHR;
+
+typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceExternalBufferPropertiesKHR)(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceExternalBufferInfo* pExternalBufferInfo, VkExternalBufferProperties* pExternalBufferProperties);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceExternalBufferPropertiesKHR(
+    VkPhysicalDevice                            physicalDevice,
+    const VkPhysicalDeviceExternalBufferInfo*   pExternalBufferInfo,
+    VkExternalBufferProperties*                 pExternalBufferProperties);
+#endif
+
+
+#define VK_KHR_external_memory 1
+#define VK_KHR_EXTERNAL_MEMORY_SPEC_VERSION 1
+#define VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME "VK_KHR_external_memory"
+#define VK_QUEUE_FAMILY_EXTERNAL_KHR      VK_QUEUE_FAMILY_EXTERNAL
+typedef VkExternalMemoryImageCreateInfo VkExternalMemoryImageCreateInfoKHR;
+
+typedef VkExternalMemoryBufferCreateInfo VkExternalMemoryBufferCreateInfoKHR;
+
+typedef VkExportMemoryAllocateInfo VkExportMemoryAllocateInfoKHR;
+
+
+
+#define VK_KHR_external_memory_fd 1
+#define VK_KHR_EXTERNAL_MEMORY_FD_SPEC_VERSION 1
+#define VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME "VK_KHR_external_memory_fd"
+typedef struct VkImportMemoryFdInfoKHR {
+    VkStructureType                       sType;
+    const void*                           pNext;
+    VkExternalMemoryHandleTypeFlagBits    handleType;
+    int                                   fd;
+} VkImportMemoryFdInfoKHR;
+
+typedef struct VkMemoryFdPropertiesKHR {
+    VkStructureType    sType;
+    void*              pNext;
+    uint32_t           memoryTypeBits;
+} VkMemoryFdPropertiesKHR;
+
+typedef struct VkMemoryGetFdInfoKHR {
+    VkStructureType                       sType;
+    const void*                           pNext;
+    VkDeviceMemory                        memory;
+    VkExternalMemoryHandleTypeFlagBits    handleType;
+} VkMemoryGetFdInfoKHR;
+
+typedef VkResult (VKAPI_PTR *PFN_vkGetMemoryFdKHR)(VkDevice device, const VkMemoryGetFdInfoKHR* pGetFdInfo, int* pFd);
+typedef VkResult (VKAPI_PTR *PFN_vkGetMemoryFdPropertiesKHR)(VkDevice device, VkExternalMemoryHandleTypeFlagBits handleType, int fd, VkMemoryFdPropertiesKHR* pMemoryFdProperties);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkGetMemoryFdKHR(
+    VkDevice                                    device,
+    const VkMemoryGetFdInfoKHR*                 pGetFdInfo,
+    int*                                        pFd);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetMemoryFdPropertiesKHR(
+    VkDevice                                    device,
+    VkExternalMemoryHandleTypeFlagBits          handleType,
+    int                                         fd,
+    VkMemoryFdPropertiesKHR*                    pMemoryFdProperties);
+#endif
+
+
+#define VK_KHR_external_semaphore_capabilities 1
+#define VK_KHR_EXTERNAL_SEMAPHORE_CAPABILITIES_SPEC_VERSION 1
+#define VK_KHR_EXTERNAL_SEMAPHORE_CAPABILITIES_EXTENSION_NAME "VK_KHR_external_semaphore_capabilities"
+typedef VkExternalSemaphoreHandleTypeFlags VkExternalSemaphoreHandleTypeFlagsKHR;
+
+typedef VkExternalSemaphoreHandleTypeFlagBits VkExternalSemaphoreHandleTypeFlagBitsKHR;
+
+typedef VkExternalSemaphoreFeatureFlags VkExternalSemaphoreFeatureFlagsKHR;
+
+typedef VkExternalSemaphoreFeatureFlagBits VkExternalSemaphoreFeatureFlagBitsKHR;
+
+typedef VkPhysicalDeviceExternalSemaphoreInfo VkPhysicalDeviceExternalSemaphoreInfoKHR;
+
+typedef VkExternalSemaphoreProperties VkExternalSemaphorePropertiesKHR;
+
+typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceExternalSemaphorePropertiesKHR)(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceExternalSemaphoreInfo* pExternalSemaphoreInfo, VkExternalSemaphoreProperties* pExternalSemaphoreProperties);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceExternalSemaphorePropertiesKHR(
+    VkPhysicalDevice                            physicalDevice,
+    const VkPhysicalDeviceExternalSemaphoreInfo* pExternalSemaphoreInfo,
+    VkExternalSemaphoreProperties*              pExternalSemaphoreProperties);
+#endif
+
+
+#define VK_KHR_external_semaphore 1
+#define VK_KHR_EXTERNAL_SEMAPHORE_SPEC_VERSION 1
+#define VK_KHR_EXTERNAL_SEMAPHORE_EXTENSION_NAME "VK_KHR_external_semaphore"
+typedef VkSemaphoreImportFlags VkSemaphoreImportFlagsKHR;
+
+typedef VkSemaphoreImportFlagBits VkSemaphoreImportFlagBitsKHR;
+
+typedef VkExportSemaphoreCreateInfo VkExportSemaphoreCreateInfoKHR;
+
+
+
+#define VK_KHR_external_semaphore_fd 1
+#define VK_KHR_EXTERNAL_SEMAPHORE_FD_SPEC_VERSION 1
+#define VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME "VK_KHR_external_semaphore_fd"
+typedef struct VkImportSemaphoreFdInfoKHR {
+    VkStructureType                          sType;
+    const void*                              pNext;
+    VkSemaphore                              semaphore;
+    VkSemaphoreImportFlags                   flags;
+    VkExternalSemaphoreHandleTypeFlagBits    handleType;
+    int                                      fd;
+} VkImportSemaphoreFdInfoKHR;
+
+typedef struct VkSemaphoreGetFdInfoKHR {
+    VkStructureType                          sType;
+    const void*                              pNext;
+    VkSemaphore                              semaphore;
+    VkExternalSemaphoreHandleTypeFlagBits    handleType;
+} VkSemaphoreGetFdInfoKHR;
+
+typedef VkResult (VKAPI_PTR *PFN_vkImportSemaphoreFdKHR)(VkDevice device, const VkImportSemaphoreFdInfoKHR* pImportSemaphoreFdInfo);
+typedef VkResult (VKAPI_PTR *PFN_vkGetSemaphoreFdKHR)(VkDevice device, const VkSemaphoreGetFdInfoKHR* pGetFdInfo, int* pFd);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkImportSemaphoreFdKHR(
+    VkDevice                                    device,
+    const VkImportSemaphoreFdInfoKHR*           pImportSemaphoreFdInfo);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetSemaphoreFdKHR(
+    VkDevice                                    device,
+    const VkSemaphoreGetFdInfoKHR*              pGetFdInfo,
+    int*                                        pFd);
+#endif
+
+
+#define VK_KHR_push_descriptor 1
+#define VK_KHR_PUSH_DESCRIPTOR_SPEC_VERSION 2
+#define VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME "VK_KHR_push_descriptor"
+typedef struct VkPhysicalDevicePushDescriptorPropertiesKHR {
+    VkStructureType    sType;
+    void*              pNext;
+    uint32_t           maxPushDescriptors;
+} VkPhysicalDevicePushDescriptorPropertiesKHR;
+
+typedef void (VKAPI_PTR *PFN_vkCmdPushDescriptorSetKHR)(VkCommandBuffer commandBuffer, VkPipelineBindPoint pipelineBindPoint, VkPipelineLayout layout, uint32_t set, uint32_t descriptorWriteCount, const VkWriteDescriptorSet* pDescriptorWrites);
+typedef void (VKAPI_PTR *PFN_vkCmdPushDescriptorSetWithTemplateKHR)(VkCommandBuffer commandBuffer, VkDescriptorUpdateTemplate descriptorUpdateTemplate, VkPipelineLayout layout, uint32_t set, const void* pData);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR void VKAPI_CALL vkCmdPushDescriptorSetKHR(
+    VkCommandBuffer                             commandBuffer,
+    VkPipelineBindPoint                         pipelineBindPoint,
+    VkPipelineLayout                            layout,
+    uint32_t                                    set,
+    uint32_t                                    descriptorWriteCount,
+    const VkWriteDescriptorSet*                 pDescriptorWrites);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdPushDescriptorSetWithTemplateKHR(
+    VkCommandBuffer                             commandBuffer,
+    VkDescriptorUpdateTemplate                  descriptorUpdateTemplate,
+    VkPipelineLayout                            layout,
+    uint32_t                                    set,
+    const void*                                 pData);
+#endif
+
+
+#define VK_KHR_shader_float16_int8 1
+#define VK_KHR_SHADER_FLOAT16_INT8_SPEC_VERSION 1
+#define VK_KHR_SHADER_FLOAT16_INT8_EXTENSION_NAME "VK_KHR_shader_float16_int8"
+typedef VkPhysicalDeviceShaderFloat16Int8Features VkPhysicalDeviceShaderFloat16Int8FeaturesKHR;
+
+typedef VkPhysicalDeviceShaderFloat16Int8Features VkPhysicalDeviceFloat16Int8FeaturesKHR;
+
+
+
+#define VK_KHR_16bit_storage 1
+#define VK_KHR_16BIT_STORAGE_SPEC_VERSION 1
+#define VK_KHR_16BIT_STORAGE_EXTENSION_NAME "VK_KHR_16bit_storage"
+typedef VkPhysicalDevice16BitStorageFeatures VkPhysicalDevice16BitStorageFeaturesKHR;
+
+
+
+#define VK_KHR_incremental_present 1
+#define VK_KHR_INCREMENTAL_PRESENT_SPEC_VERSION 2
+#define VK_KHR_INCREMENTAL_PRESENT_EXTENSION_NAME "VK_KHR_incremental_present"
+typedef struct VkRectLayerKHR {
+    VkOffset2D    offset;
+    VkExtent2D    extent;
+    uint32_t      layer;
+} VkRectLayerKHR;
+
+typedef struct VkPresentRegionKHR {
+    uint32_t                 rectangleCount;
+    const VkRectLayerKHR*    pRectangles;
+} VkPresentRegionKHR;
+
+typedef struct VkPresentRegionsKHR {
+    VkStructureType              sType;
+    const void*                  pNext;
+    uint32_t                     swapchainCount;
+    const VkPresentRegionKHR*    pRegions;
+} VkPresentRegionsKHR;
+
+
+
+#define VK_KHR_descriptor_update_template 1
+typedef VkDescriptorUpdateTemplate VkDescriptorUpdateTemplateKHR;
+
+#define VK_KHR_DESCRIPTOR_UPDATE_TEMPLATE_SPEC_VERSION 1
+#define VK_KHR_DESCRIPTOR_UPDATE_TEMPLATE_EXTENSION_NAME "VK_KHR_descriptor_update_template"
+typedef VkDescriptorUpdateTemplateType VkDescriptorUpdateTemplateTypeKHR;
+
+typedef VkDescriptorUpdateTemplateCreateFlags VkDescriptorUpdateTemplateCreateFlagsKHR;
+
+typedef VkDescriptorUpdateTemplateEntry VkDescriptorUpdateTemplateEntryKHR;
+
+typedef VkDescriptorUpdateTemplateCreateInfo VkDescriptorUpdateTemplateCreateInfoKHR;
+
+typedef VkResult (VKAPI_PTR *PFN_vkCreateDescriptorUpdateTemplateKHR)(VkDevice device, const VkDescriptorUpdateTemplateCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDescriptorUpdateTemplate* pDescriptorUpdateTemplate);
+typedef void (VKAPI_PTR *PFN_vkDestroyDescriptorUpdateTemplateKHR)(VkDevice device, VkDescriptorUpdateTemplate descriptorUpdateTemplate, const VkAllocationCallbacks* pAllocator);
+typedef void (VKAPI_PTR *PFN_vkUpdateDescriptorSetWithTemplateKHR)(VkDevice device, VkDescriptorSet descriptorSet, VkDescriptorUpdateTemplate descriptorUpdateTemplate, const void* pData);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateDescriptorUpdateTemplateKHR(
+    VkDevice                                    device,
+    const VkDescriptorUpdateTemplateCreateInfo* pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkDescriptorUpdateTemplate*                 pDescriptorUpdateTemplate);
+
+VKAPI_ATTR void VKAPI_CALL vkDestroyDescriptorUpdateTemplateKHR(
+    VkDevice                                    device,
+    VkDescriptorUpdateTemplate                  descriptorUpdateTemplate,
+    const VkAllocationCallbacks*                pAllocator);
+
+VKAPI_ATTR void VKAPI_CALL vkUpdateDescriptorSetWithTemplateKHR(
+    VkDevice                                    device,
+    VkDescriptorSet                             descriptorSet,
+    VkDescriptorUpdateTemplate                  descriptorUpdateTemplate,
+    const void*                                 pData);
+#endif
+
+
+#define VK_KHR_imageless_framebuffer 1
+#define VK_KHR_IMAGELESS_FRAMEBUFFER_SPEC_VERSION 1
+#define VK_KHR_IMAGELESS_FRAMEBUFFER_EXTENSION_NAME "VK_KHR_imageless_framebuffer"
+typedef VkPhysicalDeviceImagelessFramebufferFeatures VkPhysicalDeviceImagelessFramebufferFeaturesKHR;
+
+typedef VkFramebufferAttachmentsCreateInfo VkFramebufferAttachmentsCreateInfoKHR;
+
+typedef VkFramebufferAttachmentImageInfo VkFramebufferAttachmentImageInfoKHR;
+
+typedef VkRenderPassAttachmentBeginInfo VkRenderPassAttachmentBeginInfoKHR;
+
+
+
+#define VK_KHR_create_renderpass2 1
+#define VK_KHR_CREATE_RENDERPASS_2_SPEC_VERSION 1
+#define VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME "VK_KHR_create_renderpass2"
+typedef VkRenderPassCreateInfo2 VkRenderPassCreateInfo2KHR;
+
+typedef VkAttachmentDescription2 VkAttachmentDescription2KHR;
+
+typedef VkAttachmentReference2 VkAttachmentReference2KHR;
+
+typedef VkSubpassDescription2 VkSubpassDescription2KHR;
+
+typedef VkSubpassDependency2 VkSubpassDependency2KHR;
+
+typedef VkSubpassBeginInfo VkSubpassBeginInfoKHR;
+
+typedef VkSubpassEndInfo VkSubpassEndInfoKHR;
+
+typedef VkResult (VKAPI_PTR *PFN_vkCreateRenderPass2KHR)(VkDevice device, const VkRenderPassCreateInfo2* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkRenderPass* pRenderPass);
+typedef void (VKAPI_PTR *PFN_vkCmdBeginRenderPass2KHR)(VkCommandBuffer commandBuffer, const VkRenderPassBeginInfo*      pRenderPassBegin, const VkSubpassBeginInfo*      pSubpassBeginInfo);
+typedef void (VKAPI_PTR *PFN_vkCmdNextSubpass2KHR)(VkCommandBuffer commandBuffer, const VkSubpassBeginInfo*      pSubpassBeginInfo, const VkSubpassEndInfo*        pSubpassEndInfo);
+typedef void (VKAPI_PTR *PFN_vkCmdEndRenderPass2KHR)(VkCommandBuffer commandBuffer, const VkSubpassEndInfo*        pSubpassEndInfo);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateRenderPass2KHR(
+    VkDevice                                    device,
+    const VkRenderPassCreateInfo2*              pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkRenderPass*                               pRenderPass);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdBeginRenderPass2KHR(
+    VkCommandBuffer                             commandBuffer,
+    const VkRenderPassBeginInfo*                pRenderPassBegin,
+    const VkSubpassBeginInfo*                   pSubpassBeginInfo);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdNextSubpass2KHR(
+    VkCommandBuffer                             commandBuffer,
+    const VkSubpassBeginInfo*                   pSubpassBeginInfo,
+    const VkSubpassEndInfo*                     pSubpassEndInfo);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdEndRenderPass2KHR(
+    VkCommandBuffer                             commandBuffer,
+    const VkSubpassEndInfo*                     pSubpassEndInfo);
+#endif
+
+
+#define VK_KHR_shared_presentable_image 1
+#define VK_KHR_SHARED_PRESENTABLE_IMAGE_SPEC_VERSION 1
+#define VK_KHR_SHARED_PRESENTABLE_IMAGE_EXTENSION_NAME "VK_KHR_shared_presentable_image"
+typedef struct VkSharedPresentSurfaceCapabilitiesKHR {
+    VkStructureType      sType;
+    void*                pNext;
+    VkImageUsageFlags    sharedPresentSupportedUsageFlags;
+} VkSharedPresentSurfaceCapabilitiesKHR;
+
+typedef VkResult (VKAPI_PTR *PFN_vkGetSwapchainStatusKHR)(VkDevice device, VkSwapchainKHR swapchain);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkGetSwapchainStatusKHR(
+    VkDevice                                    device,
+    VkSwapchainKHR                              swapchain);
+#endif
+
+
+#define VK_KHR_external_fence_capabilities 1
+#define VK_KHR_EXTERNAL_FENCE_CAPABILITIES_SPEC_VERSION 1
+#define VK_KHR_EXTERNAL_FENCE_CAPABILITIES_EXTENSION_NAME "VK_KHR_external_fence_capabilities"
+typedef VkExternalFenceHandleTypeFlags VkExternalFenceHandleTypeFlagsKHR;
+
+typedef VkExternalFenceHandleTypeFlagBits VkExternalFenceHandleTypeFlagBitsKHR;
+
+typedef VkExternalFenceFeatureFlags VkExternalFenceFeatureFlagsKHR;
+
+typedef VkExternalFenceFeatureFlagBits VkExternalFenceFeatureFlagBitsKHR;
+
+typedef VkPhysicalDeviceExternalFenceInfo VkPhysicalDeviceExternalFenceInfoKHR;
+
+typedef VkExternalFenceProperties VkExternalFencePropertiesKHR;
+
+typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceExternalFencePropertiesKHR)(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceExternalFenceInfo* pExternalFenceInfo, VkExternalFenceProperties* pExternalFenceProperties);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceExternalFencePropertiesKHR(
+    VkPhysicalDevice                            physicalDevice,
+    const VkPhysicalDeviceExternalFenceInfo*    pExternalFenceInfo,
+    VkExternalFenceProperties*                  pExternalFenceProperties);
+#endif
+
+
+#define VK_KHR_external_fence 1
+#define VK_KHR_EXTERNAL_FENCE_SPEC_VERSION 1
+#define VK_KHR_EXTERNAL_FENCE_EXTENSION_NAME "VK_KHR_external_fence"
+typedef VkFenceImportFlags VkFenceImportFlagsKHR;
+
+typedef VkFenceImportFlagBits VkFenceImportFlagBitsKHR;
+
+typedef VkExportFenceCreateInfo VkExportFenceCreateInfoKHR;
+
+
+
+#define VK_KHR_external_fence_fd 1
+#define VK_KHR_EXTERNAL_FENCE_FD_SPEC_VERSION 1
+#define VK_KHR_EXTERNAL_FENCE_FD_EXTENSION_NAME "VK_KHR_external_fence_fd"
+typedef struct VkImportFenceFdInfoKHR {
+    VkStructureType                      sType;
+    const void*                          pNext;
+    VkFence                              fence;
+    VkFenceImportFlags                   flags;
+    VkExternalFenceHandleTypeFlagBits    handleType;
+    int                                  fd;
+} VkImportFenceFdInfoKHR;
+
+typedef struct VkFenceGetFdInfoKHR {
+    VkStructureType                      sType;
+    const void*                          pNext;
+    VkFence                              fence;
+    VkExternalFenceHandleTypeFlagBits    handleType;
+} VkFenceGetFdInfoKHR;
+
+typedef VkResult (VKAPI_PTR *PFN_vkImportFenceFdKHR)(VkDevice device, const VkImportFenceFdInfoKHR* pImportFenceFdInfo);
+typedef VkResult (VKAPI_PTR *PFN_vkGetFenceFdKHR)(VkDevice device, const VkFenceGetFdInfoKHR* pGetFdInfo, int* pFd);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkImportFenceFdKHR(
+    VkDevice                                    device,
+    const VkImportFenceFdInfoKHR*               pImportFenceFdInfo);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetFenceFdKHR(
+    VkDevice                                    device,
+    const VkFenceGetFdInfoKHR*                  pGetFdInfo,
+    int*                                        pFd);
+#endif
+
+
+#define VK_KHR_performance_query 1
+#define VK_KHR_PERFORMANCE_QUERY_SPEC_VERSION 1
+#define VK_KHR_PERFORMANCE_QUERY_EXTENSION_NAME "VK_KHR_performance_query"
+
+typedef enum VkPerformanceCounterUnitKHR {
+    VK_PERFORMANCE_COUNTER_UNIT_GENERIC_KHR = 0,
+    VK_PERFORMANCE_COUNTER_UNIT_PERCENTAGE_KHR = 1,
+    VK_PERFORMANCE_COUNTER_UNIT_NANOSECONDS_KHR = 2,
+    VK_PERFORMANCE_COUNTER_UNIT_BYTES_KHR = 3,
+    VK_PERFORMANCE_COUNTER_UNIT_BYTES_PER_SECOND_KHR = 4,
+    VK_PERFORMANCE_COUNTER_UNIT_KELVIN_KHR = 5,
+    VK_PERFORMANCE_COUNTER_UNIT_WATTS_KHR = 6,
+    VK_PERFORMANCE_COUNTER_UNIT_VOLTS_KHR = 7,
+    VK_PERFORMANCE_COUNTER_UNIT_AMPS_KHR = 8,
+    VK_PERFORMANCE_COUNTER_UNIT_HERTZ_KHR = 9,
+    VK_PERFORMANCE_COUNTER_UNIT_CYCLES_KHR = 10,
+    VK_PERFORMANCE_COUNTER_UNIT_MAX_ENUM_KHR = 0x7FFFFFFF
+} VkPerformanceCounterUnitKHR;
+
+typedef enum VkPerformanceCounterScopeKHR {
+    VK_PERFORMANCE_COUNTER_SCOPE_COMMAND_BUFFER_KHR = 0,
+    VK_PERFORMANCE_COUNTER_SCOPE_RENDER_PASS_KHR = 1,
+    VK_PERFORMANCE_COUNTER_SCOPE_COMMAND_KHR = 2,
+    VK_QUERY_SCOPE_COMMAND_BUFFER_KHR = VK_PERFORMANCE_COUNTER_SCOPE_COMMAND_BUFFER_KHR,
+    VK_QUERY_SCOPE_RENDER_PASS_KHR = VK_PERFORMANCE_COUNTER_SCOPE_RENDER_PASS_KHR,
+    VK_QUERY_SCOPE_COMMAND_KHR = VK_PERFORMANCE_COUNTER_SCOPE_COMMAND_KHR,
+    VK_PERFORMANCE_COUNTER_SCOPE_MAX_ENUM_KHR = 0x7FFFFFFF
+} VkPerformanceCounterScopeKHR;
+
+typedef enum VkPerformanceCounterStorageKHR {
+    VK_PERFORMANCE_COUNTER_STORAGE_INT32_KHR = 0,
+    VK_PERFORMANCE_COUNTER_STORAGE_INT64_KHR = 1,
+    VK_PERFORMANCE_COUNTER_STORAGE_UINT32_KHR = 2,
+    VK_PERFORMANCE_COUNTER_STORAGE_UINT64_KHR = 3,
+    VK_PERFORMANCE_COUNTER_STORAGE_FLOAT32_KHR = 4,
+    VK_PERFORMANCE_COUNTER_STORAGE_FLOAT64_KHR = 5,
+    VK_PERFORMANCE_COUNTER_STORAGE_MAX_ENUM_KHR = 0x7FFFFFFF
+} VkPerformanceCounterStorageKHR;
+
+typedef enum VkPerformanceCounterDescriptionFlagBitsKHR {
+    VK_PERFORMANCE_COUNTER_DESCRIPTION_PERFORMANCE_IMPACTING_BIT_KHR = 0x00000001,
+    VK_PERFORMANCE_COUNTER_DESCRIPTION_CONCURRENTLY_IMPACTED_BIT_KHR = 0x00000002,
+    VK_PERFORMANCE_COUNTER_DESCRIPTION_PERFORMANCE_IMPACTING_KHR = VK_PERFORMANCE_COUNTER_DESCRIPTION_PERFORMANCE_IMPACTING_BIT_KHR,
+    VK_PERFORMANCE_COUNTER_DESCRIPTION_CONCURRENTLY_IMPACTED_KHR = VK_PERFORMANCE_COUNTER_DESCRIPTION_CONCURRENTLY_IMPACTED_BIT_KHR,
+    VK_PERFORMANCE_COUNTER_DESCRIPTION_FLAG_BITS_MAX_ENUM_KHR = 0x7FFFFFFF
+} VkPerformanceCounterDescriptionFlagBitsKHR;
+typedef VkFlags VkPerformanceCounterDescriptionFlagsKHR;
+
+typedef enum VkAcquireProfilingLockFlagBitsKHR {
+    VK_ACQUIRE_PROFILING_LOCK_FLAG_BITS_MAX_ENUM_KHR = 0x7FFFFFFF
+} VkAcquireProfilingLockFlagBitsKHR;
+typedef VkFlags VkAcquireProfilingLockFlagsKHR;
+typedef struct VkPhysicalDevicePerformanceQueryFeaturesKHR {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           performanceCounterQueryPools;
+    VkBool32           performanceCounterMultipleQueryPools;
+} VkPhysicalDevicePerformanceQueryFeaturesKHR;
+
+typedef struct VkPhysicalDevicePerformanceQueryPropertiesKHR {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           allowCommandBufferQueryCopies;
+} VkPhysicalDevicePerformanceQueryPropertiesKHR;
+
+typedef struct VkPerformanceCounterKHR {
+    VkStructureType                   sType;
+    void*                             pNext;
+    VkPerformanceCounterUnitKHR       unit;
+    VkPerformanceCounterScopeKHR      scope;
+    VkPerformanceCounterStorageKHR    storage;
+    uint8_t                           uuid[VK_UUID_SIZE];
+} VkPerformanceCounterKHR;
+
+typedef struct VkPerformanceCounterDescriptionKHR {
+    VkStructureType                            sType;
+    void*                                      pNext;
+    VkPerformanceCounterDescriptionFlagsKHR    flags;
+    char                                       name[VK_MAX_DESCRIPTION_SIZE];
+    char                                       category[VK_MAX_DESCRIPTION_SIZE];
+    char                                       description[VK_MAX_DESCRIPTION_SIZE];
+} VkPerformanceCounterDescriptionKHR;
+
+typedef struct VkQueryPoolPerformanceCreateInfoKHR {
+    VkStructureType    sType;
+    const void*        pNext;
+    uint32_t           queueFamilyIndex;
+    uint32_t           counterIndexCount;
+    const uint32_t*    pCounterIndices;
+} VkQueryPoolPerformanceCreateInfoKHR;
+
+typedef union VkPerformanceCounterResultKHR {
+    int32_t     int32;
+    int64_t     int64;
+    uint32_t    uint32;
+    uint64_t    uint64;
+    float       float32;
+    double      float64;
+} VkPerformanceCounterResultKHR;
+
+typedef struct VkAcquireProfilingLockInfoKHR {
+    VkStructureType                   sType;
+    const void*                       pNext;
+    VkAcquireProfilingLockFlagsKHR    flags;
+    uint64_t                          timeout;
+} VkAcquireProfilingLockInfoKHR;
+
+typedef struct VkPerformanceQuerySubmitInfoKHR {
+    VkStructureType    sType;
+    const void*        pNext;
+    uint32_t           counterPassIndex;
+} VkPerformanceQuerySubmitInfoKHR;
+
+typedef VkResult (VKAPI_PTR *PFN_vkEnumeratePhysicalDeviceQueueFamilyPerformanceQueryCountersKHR)(VkPhysicalDevice physicalDevice, uint32_t queueFamilyIndex, uint32_t* pCounterCount, VkPerformanceCounterKHR* pCounters, VkPerformanceCounterDescriptionKHR* pCounterDescriptions);
+typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceQueueFamilyPerformanceQueryPassesKHR)(VkPhysicalDevice physicalDevice, const VkQueryPoolPerformanceCreateInfoKHR* pPerformanceQueryCreateInfo, uint32_t* pNumPasses);
+typedef VkResult (VKAPI_PTR *PFN_vkAcquireProfilingLockKHR)(VkDevice device, const VkAcquireProfilingLockInfoKHR* pInfo);
+typedef void (VKAPI_PTR *PFN_vkReleaseProfilingLockKHR)(VkDevice device);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkEnumeratePhysicalDeviceQueueFamilyPerformanceQueryCountersKHR(
+    VkPhysicalDevice                            physicalDevice,
+    uint32_t                                    queueFamilyIndex,
+    uint32_t*                                   pCounterCount,
+    VkPerformanceCounterKHR*                    pCounters,
+    VkPerformanceCounterDescriptionKHR*         pCounterDescriptions);
+
+VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceQueueFamilyPerformanceQueryPassesKHR(
+    VkPhysicalDevice                            physicalDevice,
+    const VkQueryPoolPerformanceCreateInfoKHR*  pPerformanceQueryCreateInfo,
+    uint32_t*                                   pNumPasses);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkAcquireProfilingLockKHR(
+    VkDevice                                    device,
+    const VkAcquireProfilingLockInfoKHR*        pInfo);
+
+VKAPI_ATTR void VKAPI_CALL vkReleaseProfilingLockKHR(
+    VkDevice                                    device);
+#endif
+
+
+#define VK_KHR_maintenance2 1
+#define VK_KHR_MAINTENANCE_2_SPEC_VERSION 1
+#define VK_KHR_MAINTENANCE_2_EXTENSION_NAME "VK_KHR_maintenance2"
+#define VK_KHR_MAINTENANCE2_SPEC_VERSION  VK_KHR_MAINTENANCE_2_SPEC_VERSION
+#define VK_KHR_MAINTENANCE2_EXTENSION_NAME VK_KHR_MAINTENANCE_2_EXTENSION_NAME
+typedef VkPointClippingBehavior VkPointClippingBehaviorKHR;
+
+typedef VkTessellationDomainOrigin VkTessellationDomainOriginKHR;
+
+typedef VkPhysicalDevicePointClippingProperties VkPhysicalDevicePointClippingPropertiesKHR;
+
+typedef VkRenderPassInputAttachmentAspectCreateInfo VkRenderPassInputAttachmentAspectCreateInfoKHR;
+
+typedef VkInputAttachmentAspectReference VkInputAttachmentAspectReferenceKHR;
+
+typedef VkImageViewUsageCreateInfo VkImageViewUsageCreateInfoKHR;
+
+typedef VkPipelineTessellationDomainOriginStateCreateInfo VkPipelineTessellationDomainOriginStateCreateInfoKHR;
+
+
+
+#define VK_KHR_get_surface_capabilities2 1
+#define VK_KHR_GET_SURFACE_CAPABILITIES_2_SPEC_VERSION 1
+#define VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME "VK_KHR_get_surface_capabilities2"
+typedef struct VkPhysicalDeviceSurfaceInfo2KHR {
+    VkStructureType    sType;
+    const void*        pNext;
+    VkSurfaceKHR       surface;
+} VkPhysicalDeviceSurfaceInfo2KHR;
+
+typedef struct VkSurfaceCapabilities2KHR {
+    VkStructureType             sType;
+    void*                       pNext;
+    VkSurfaceCapabilitiesKHR    surfaceCapabilities;
+} VkSurfaceCapabilities2KHR;
+
+typedef struct VkSurfaceFormat2KHR {
+    VkStructureType       sType;
+    void*                 pNext;
+    VkSurfaceFormatKHR    surfaceFormat;
+} VkSurfaceFormat2KHR;
+
+typedef VkResult (VKAPI_PTR *PFN_vkGetPhysicalDeviceSurfaceCapabilities2KHR)(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceSurfaceInfo2KHR* pSurfaceInfo, VkSurfaceCapabilities2KHR* pSurfaceCapabilities);
+typedef VkResult (VKAPI_PTR *PFN_vkGetPhysicalDeviceSurfaceFormats2KHR)(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceSurfaceInfo2KHR* pSurfaceInfo, uint32_t* pSurfaceFormatCount, VkSurfaceFormat2KHR* pSurfaceFormats);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceSurfaceCapabilities2KHR(
+    VkPhysicalDevice                            physicalDevice,
+    const VkPhysicalDeviceSurfaceInfo2KHR*      pSurfaceInfo,
+    VkSurfaceCapabilities2KHR*                  pSurfaceCapabilities);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceSurfaceFormats2KHR(
+    VkPhysicalDevice                            physicalDevice,
+    const VkPhysicalDeviceSurfaceInfo2KHR*      pSurfaceInfo,
+    uint32_t*                                   pSurfaceFormatCount,
+    VkSurfaceFormat2KHR*                        pSurfaceFormats);
+#endif
+
+
+#define VK_KHR_variable_pointers 1
+#define VK_KHR_VARIABLE_POINTERS_SPEC_VERSION 1
+#define VK_KHR_VARIABLE_POINTERS_EXTENSION_NAME "VK_KHR_variable_pointers"
+typedef VkPhysicalDeviceVariablePointersFeatures VkPhysicalDeviceVariablePointerFeaturesKHR;
+
+typedef VkPhysicalDeviceVariablePointersFeatures VkPhysicalDeviceVariablePointersFeaturesKHR;
+
+
+
+#define VK_KHR_get_display_properties2 1
+#define VK_KHR_GET_DISPLAY_PROPERTIES_2_SPEC_VERSION 1
+#define VK_KHR_GET_DISPLAY_PROPERTIES_2_EXTENSION_NAME "VK_KHR_get_display_properties2"
+typedef struct VkDisplayProperties2KHR {
+    VkStructureType           sType;
+    void*                     pNext;
+    VkDisplayPropertiesKHR    displayProperties;
+} VkDisplayProperties2KHR;
+
+typedef struct VkDisplayPlaneProperties2KHR {
+    VkStructureType                sType;
+    void*                          pNext;
+    VkDisplayPlanePropertiesKHR    displayPlaneProperties;
+} VkDisplayPlaneProperties2KHR;
+
+typedef struct VkDisplayModeProperties2KHR {
+    VkStructureType               sType;
+    void*                         pNext;
+    VkDisplayModePropertiesKHR    displayModeProperties;
+} VkDisplayModeProperties2KHR;
+
+typedef struct VkDisplayPlaneInfo2KHR {
+    VkStructureType     sType;
+    const void*         pNext;
+    VkDisplayModeKHR    mode;
+    uint32_t            planeIndex;
+} VkDisplayPlaneInfo2KHR;
+
+typedef struct VkDisplayPlaneCapabilities2KHR {
+    VkStructureType                  sType;
+    void*                            pNext;
+    VkDisplayPlaneCapabilitiesKHR    capabilities;
+} VkDisplayPlaneCapabilities2KHR;
+
+typedef VkResult (VKAPI_PTR *PFN_vkGetPhysicalDeviceDisplayProperties2KHR)(VkPhysicalDevice physicalDevice, uint32_t* pPropertyCount, VkDisplayProperties2KHR* pProperties);
+typedef VkResult (VKAPI_PTR *PFN_vkGetPhysicalDeviceDisplayPlaneProperties2KHR)(VkPhysicalDevice physicalDevice, uint32_t* pPropertyCount, VkDisplayPlaneProperties2KHR* pProperties);
+typedef VkResult (VKAPI_PTR *PFN_vkGetDisplayModeProperties2KHR)(VkPhysicalDevice physicalDevice, VkDisplayKHR display, uint32_t* pPropertyCount, VkDisplayModeProperties2KHR* pProperties);
+typedef VkResult (VKAPI_PTR *PFN_vkGetDisplayPlaneCapabilities2KHR)(VkPhysicalDevice physicalDevice, const VkDisplayPlaneInfo2KHR* pDisplayPlaneInfo, VkDisplayPlaneCapabilities2KHR* pCapabilities);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceDisplayProperties2KHR(
+    VkPhysicalDevice                            physicalDevice,
+    uint32_t*                                   pPropertyCount,
+    VkDisplayProperties2KHR*                    pProperties);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceDisplayPlaneProperties2KHR(
+    VkPhysicalDevice                            physicalDevice,
+    uint32_t*                                   pPropertyCount,
+    VkDisplayPlaneProperties2KHR*               pProperties);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetDisplayModeProperties2KHR(
+    VkPhysicalDevice                            physicalDevice,
+    VkDisplayKHR                                display,
+    uint32_t*                                   pPropertyCount,
+    VkDisplayModeProperties2KHR*                pProperties);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetDisplayPlaneCapabilities2KHR(
+    VkPhysicalDevice                            physicalDevice,
+    const VkDisplayPlaneInfo2KHR*               pDisplayPlaneInfo,
+    VkDisplayPlaneCapabilities2KHR*             pCapabilities);
+#endif
+
+
+#define VK_KHR_dedicated_allocation 1
+#define VK_KHR_DEDICATED_ALLOCATION_SPEC_VERSION 3
+#define VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME "VK_KHR_dedicated_allocation"
+typedef VkMemoryDedicatedRequirements VkMemoryDedicatedRequirementsKHR;
+
+typedef VkMemoryDedicatedAllocateInfo VkMemoryDedicatedAllocateInfoKHR;
+
+
+
+#define VK_KHR_storage_buffer_storage_class 1
+#define VK_KHR_STORAGE_BUFFER_STORAGE_CLASS_SPEC_VERSION 1
+#define VK_KHR_STORAGE_BUFFER_STORAGE_CLASS_EXTENSION_NAME "VK_KHR_storage_buffer_storage_class"
+
+
+#define VK_KHR_relaxed_block_layout 1
+#define VK_KHR_RELAXED_BLOCK_LAYOUT_SPEC_VERSION 1
+#define VK_KHR_RELAXED_BLOCK_LAYOUT_EXTENSION_NAME "VK_KHR_relaxed_block_layout"
+
+
+#define VK_KHR_get_memory_requirements2 1
+#define VK_KHR_GET_MEMORY_REQUIREMENTS_2_SPEC_VERSION 1
+#define VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME "VK_KHR_get_memory_requirements2"
+typedef VkBufferMemoryRequirementsInfo2 VkBufferMemoryRequirementsInfo2KHR;
+
+typedef VkImageMemoryRequirementsInfo2 VkImageMemoryRequirementsInfo2KHR;
+
+typedef VkImageSparseMemoryRequirementsInfo2 VkImageSparseMemoryRequirementsInfo2KHR;
+
+typedef VkMemoryRequirements2 VkMemoryRequirements2KHR;
+
+typedef VkSparseImageMemoryRequirements2 VkSparseImageMemoryRequirements2KHR;
+
+typedef void (VKAPI_PTR *PFN_vkGetImageMemoryRequirements2KHR)(VkDevice device, const VkImageMemoryRequirementsInfo2* pInfo, VkMemoryRequirements2* pMemoryRequirements);
+typedef void (VKAPI_PTR *PFN_vkGetBufferMemoryRequirements2KHR)(VkDevice device, const VkBufferMemoryRequirementsInfo2* pInfo, VkMemoryRequirements2* pMemoryRequirements);
+typedef void (VKAPI_PTR *PFN_vkGetImageSparseMemoryRequirements2KHR)(VkDevice device, const VkImageSparseMemoryRequirementsInfo2* pInfo, uint32_t* pSparseMemoryRequirementCount, VkSparseImageMemoryRequirements2* pSparseMemoryRequirements);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR void VKAPI_CALL vkGetImageMemoryRequirements2KHR(
+    VkDevice                                    device,
+    const VkImageMemoryRequirementsInfo2*       pInfo,
+    VkMemoryRequirements2*                      pMemoryRequirements);
+
+VKAPI_ATTR void VKAPI_CALL vkGetBufferMemoryRequirements2KHR(
+    VkDevice                                    device,
+    const VkBufferMemoryRequirementsInfo2*      pInfo,
+    VkMemoryRequirements2*                      pMemoryRequirements);
+
+VKAPI_ATTR void VKAPI_CALL vkGetImageSparseMemoryRequirements2KHR(
+    VkDevice                                    device,
+    const VkImageSparseMemoryRequirementsInfo2* pInfo,
+    uint32_t*                                   pSparseMemoryRequirementCount,
+    VkSparseImageMemoryRequirements2*           pSparseMemoryRequirements);
+#endif
+
+
+#define VK_KHR_image_format_list 1
+#define VK_KHR_IMAGE_FORMAT_LIST_SPEC_VERSION 1
+#define VK_KHR_IMAGE_FORMAT_LIST_EXTENSION_NAME "VK_KHR_image_format_list"
+typedef VkImageFormatListCreateInfo VkImageFormatListCreateInfoKHR;
+
+
+
+#define VK_KHR_sampler_ycbcr_conversion 1
+typedef VkSamplerYcbcrConversion VkSamplerYcbcrConversionKHR;
+
+#define VK_KHR_SAMPLER_YCBCR_CONVERSION_SPEC_VERSION 14
+#define VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME "VK_KHR_sampler_ycbcr_conversion"
+typedef VkSamplerYcbcrModelConversion VkSamplerYcbcrModelConversionKHR;
+
+typedef VkSamplerYcbcrRange VkSamplerYcbcrRangeKHR;
+
+typedef VkChromaLocation VkChromaLocationKHR;
+
+typedef VkSamplerYcbcrConversionCreateInfo VkSamplerYcbcrConversionCreateInfoKHR;
+
+typedef VkSamplerYcbcrConversionInfo VkSamplerYcbcrConversionInfoKHR;
+
+typedef VkBindImagePlaneMemoryInfo VkBindImagePlaneMemoryInfoKHR;
+
+typedef VkImagePlaneMemoryRequirementsInfo VkImagePlaneMemoryRequirementsInfoKHR;
+
+typedef VkPhysicalDeviceSamplerYcbcrConversionFeatures VkPhysicalDeviceSamplerYcbcrConversionFeaturesKHR;
+
+typedef VkSamplerYcbcrConversionImageFormatProperties VkSamplerYcbcrConversionImageFormatPropertiesKHR;
+
+typedef VkResult (VKAPI_PTR *PFN_vkCreateSamplerYcbcrConversionKHR)(VkDevice device, const VkSamplerYcbcrConversionCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSamplerYcbcrConversion* pYcbcrConversion);
+typedef void (VKAPI_PTR *PFN_vkDestroySamplerYcbcrConversionKHR)(VkDevice device, VkSamplerYcbcrConversion ycbcrConversion, const VkAllocationCallbacks* pAllocator);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateSamplerYcbcrConversionKHR(
+    VkDevice                                    device,
+    const VkSamplerYcbcrConversionCreateInfo*   pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkSamplerYcbcrConversion*                   pYcbcrConversion);
+
+VKAPI_ATTR void VKAPI_CALL vkDestroySamplerYcbcrConversionKHR(
+    VkDevice                                    device,
+    VkSamplerYcbcrConversion                    ycbcrConversion,
+    const VkAllocationCallbacks*                pAllocator);
+#endif
+
+
+#define VK_KHR_bind_memory2 1
+#define VK_KHR_BIND_MEMORY_2_SPEC_VERSION 1
+#define VK_KHR_BIND_MEMORY_2_EXTENSION_NAME "VK_KHR_bind_memory2"
+typedef VkBindBufferMemoryInfo VkBindBufferMemoryInfoKHR;
+
+typedef VkBindImageMemoryInfo VkBindImageMemoryInfoKHR;
+
+typedef VkResult (VKAPI_PTR *PFN_vkBindBufferMemory2KHR)(VkDevice device, uint32_t bindInfoCount, const VkBindBufferMemoryInfo* pBindInfos);
+typedef VkResult (VKAPI_PTR *PFN_vkBindImageMemory2KHR)(VkDevice device, uint32_t bindInfoCount, const VkBindImageMemoryInfo* pBindInfos);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkBindBufferMemory2KHR(
+    VkDevice                                    device,
+    uint32_t                                    bindInfoCount,
+    const VkBindBufferMemoryInfo*               pBindInfos);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkBindImageMemory2KHR(
+    VkDevice                                    device,
+    uint32_t                                    bindInfoCount,
+    const VkBindImageMemoryInfo*                pBindInfos);
+#endif
+
+
+#define VK_KHR_maintenance3 1
+#define VK_KHR_MAINTENANCE_3_SPEC_VERSION 1
+#define VK_KHR_MAINTENANCE_3_EXTENSION_NAME "VK_KHR_maintenance3"
+#define VK_KHR_MAINTENANCE3_SPEC_VERSION  VK_KHR_MAINTENANCE_3_SPEC_VERSION
+#define VK_KHR_MAINTENANCE3_EXTENSION_NAME VK_KHR_MAINTENANCE_3_EXTENSION_NAME
+typedef VkPhysicalDeviceMaintenance3Properties VkPhysicalDeviceMaintenance3PropertiesKHR;
+
+typedef VkDescriptorSetLayoutSupport VkDescriptorSetLayoutSupportKHR;
+
+typedef void (VKAPI_PTR *PFN_vkGetDescriptorSetLayoutSupportKHR)(VkDevice device, const VkDescriptorSetLayoutCreateInfo* pCreateInfo, VkDescriptorSetLayoutSupport* pSupport);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR void VKAPI_CALL vkGetDescriptorSetLayoutSupportKHR(
+    VkDevice                                    device,
+    const VkDescriptorSetLayoutCreateInfo*      pCreateInfo,
+    VkDescriptorSetLayoutSupport*               pSupport);
+#endif
+
+
+#define VK_KHR_draw_indirect_count 1
+#define VK_KHR_DRAW_INDIRECT_COUNT_SPEC_VERSION 1
+#define VK_KHR_DRAW_INDIRECT_COUNT_EXTENSION_NAME "VK_KHR_draw_indirect_count"
+typedef void (VKAPI_PTR *PFN_vkCmdDrawIndirectCountKHR)(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset, VkBuffer countBuffer, VkDeviceSize countBufferOffset, uint32_t maxDrawCount, uint32_t stride);
+typedef void (VKAPI_PTR *PFN_vkCmdDrawIndexedIndirectCountKHR)(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset, VkBuffer countBuffer, VkDeviceSize countBufferOffset, uint32_t maxDrawCount, uint32_t stride);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR void VKAPI_CALL vkCmdDrawIndirectCountKHR(
+    VkCommandBuffer                             commandBuffer,
+    VkBuffer                                    buffer,
+    VkDeviceSize                                offset,
+    VkBuffer                                    countBuffer,
+    VkDeviceSize                                countBufferOffset,
+    uint32_t                                    maxDrawCount,
+    uint32_t                                    stride);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdDrawIndexedIndirectCountKHR(
+    VkCommandBuffer                             commandBuffer,
+    VkBuffer                                    buffer,
+    VkDeviceSize                                offset,
+    VkBuffer                                    countBuffer,
+    VkDeviceSize                                countBufferOffset,
+    uint32_t                                    maxDrawCount,
+    uint32_t                                    stride);
+#endif
+
+
+#define VK_KHR_shader_subgroup_extended_types 1
+#define VK_KHR_SHADER_SUBGROUP_EXTENDED_TYPES_SPEC_VERSION 1
+#define VK_KHR_SHADER_SUBGROUP_EXTENDED_TYPES_EXTENSION_NAME "VK_KHR_shader_subgroup_extended_types"
+typedef VkPhysicalDeviceShaderSubgroupExtendedTypesFeatures VkPhysicalDeviceShaderSubgroupExtendedTypesFeaturesKHR;
+
+
+
+#define VK_KHR_8bit_storage 1
+#define VK_KHR_8BIT_STORAGE_SPEC_VERSION  1
+#define VK_KHR_8BIT_STORAGE_EXTENSION_NAME "VK_KHR_8bit_storage"
+typedef VkPhysicalDevice8BitStorageFeatures VkPhysicalDevice8BitStorageFeaturesKHR;
+
+
+
+#define VK_KHR_shader_atomic_int64 1
+#define VK_KHR_SHADER_ATOMIC_INT64_SPEC_VERSION 1
+#define VK_KHR_SHADER_ATOMIC_INT64_EXTENSION_NAME "VK_KHR_shader_atomic_int64"
+typedef VkPhysicalDeviceShaderAtomicInt64Features VkPhysicalDeviceShaderAtomicInt64FeaturesKHR;
+
+
+
+#define VK_KHR_shader_clock 1
+#define VK_KHR_SHADER_CLOCK_SPEC_VERSION  1
+#define VK_KHR_SHADER_CLOCK_EXTENSION_NAME "VK_KHR_shader_clock"
+typedef struct VkPhysicalDeviceShaderClockFeaturesKHR {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           shaderSubgroupClock;
+    VkBool32           shaderDeviceClock;
+} VkPhysicalDeviceShaderClockFeaturesKHR;
+
+
+
+#define VK_KHR_driver_properties 1
+#define VK_KHR_DRIVER_PROPERTIES_SPEC_VERSION 1
+#define VK_KHR_DRIVER_PROPERTIES_EXTENSION_NAME "VK_KHR_driver_properties"
+#define VK_MAX_DRIVER_NAME_SIZE_KHR       VK_MAX_DRIVER_NAME_SIZE
+#define VK_MAX_DRIVER_INFO_SIZE_KHR       VK_MAX_DRIVER_INFO_SIZE
+typedef VkDriverId VkDriverIdKHR;
+
+typedef VkConformanceVersion VkConformanceVersionKHR;
+
+typedef VkPhysicalDeviceDriverProperties VkPhysicalDeviceDriverPropertiesKHR;
+
+
+
+#define VK_KHR_shader_float_controls 1
+#define VK_KHR_SHADER_FLOAT_CONTROLS_SPEC_VERSION 4
+#define VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME "VK_KHR_shader_float_controls"
+typedef VkShaderFloatControlsIndependence VkShaderFloatControlsIndependenceKHR;
+
+typedef VkPhysicalDeviceFloatControlsProperties VkPhysicalDeviceFloatControlsPropertiesKHR;
+
+
+
+#define VK_KHR_depth_stencil_resolve 1
+#define VK_KHR_DEPTH_STENCIL_RESOLVE_SPEC_VERSION 1
+#define VK_KHR_DEPTH_STENCIL_RESOLVE_EXTENSION_NAME "VK_KHR_depth_stencil_resolve"
+typedef VkResolveModeFlagBits VkResolveModeFlagBitsKHR;
+
+typedef VkResolveModeFlags VkResolveModeFlagsKHR;
+
+typedef VkSubpassDescriptionDepthStencilResolve VkSubpassDescriptionDepthStencilResolveKHR;
+
+typedef VkPhysicalDeviceDepthStencilResolveProperties VkPhysicalDeviceDepthStencilResolvePropertiesKHR;
+
+
+
+#define VK_KHR_swapchain_mutable_format 1
+#define VK_KHR_SWAPCHAIN_MUTABLE_FORMAT_SPEC_VERSION 1
+#define VK_KHR_SWAPCHAIN_MUTABLE_FORMAT_EXTENSION_NAME "VK_KHR_swapchain_mutable_format"
+
+
+#define VK_KHR_timeline_semaphore 1
+#define VK_KHR_TIMELINE_SEMAPHORE_SPEC_VERSION 2
+#define VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME "VK_KHR_timeline_semaphore"
+typedef VkSemaphoreType VkSemaphoreTypeKHR;
+
+typedef VkSemaphoreWaitFlagBits VkSemaphoreWaitFlagBitsKHR;
+
+typedef VkSemaphoreWaitFlags VkSemaphoreWaitFlagsKHR;
+
+typedef VkPhysicalDeviceTimelineSemaphoreFeatures VkPhysicalDeviceTimelineSemaphoreFeaturesKHR;
+
+typedef VkPhysicalDeviceTimelineSemaphoreProperties VkPhysicalDeviceTimelineSemaphorePropertiesKHR;
+
+typedef VkSemaphoreTypeCreateInfo VkSemaphoreTypeCreateInfoKHR;
+
+typedef VkTimelineSemaphoreSubmitInfo VkTimelineSemaphoreSubmitInfoKHR;
+
+typedef VkSemaphoreWaitInfo VkSemaphoreWaitInfoKHR;
+
+typedef VkSemaphoreSignalInfo VkSemaphoreSignalInfoKHR;
+
+typedef VkResult (VKAPI_PTR *PFN_vkGetSemaphoreCounterValueKHR)(VkDevice device, VkSemaphore semaphore, uint64_t* pValue);
+typedef VkResult (VKAPI_PTR *PFN_vkWaitSemaphoresKHR)(VkDevice device, const VkSemaphoreWaitInfo* pWaitInfo, uint64_t timeout);
+typedef VkResult (VKAPI_PTR *PFN_vkSignalSemaphoreKHR)(VkDevice device, const VkSemaphoreSignalInfo* pSignalInfo);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkGetSemaphoreCounterValueKHR(
+    VkDevice                                    device,
+    VkSemaphore                                 semaphore,
+    uint64_t*                                   pValue);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkWaitSemaphoresKHR(
+    VkDevice                                    device,
+    const VkSemaphoreWaitInfo*                  pWaitInfo,
+    uint64_t                                    timeout);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkSignalSemaphoreKHR(
+    VkDevice                                    device,
+    const VkSemaphoreSignalInfo*                pSignalInfo);
+#endif
+
+
+#define VK_KHR_vulkan_memory_model 1
+#define VK_KHR_VULKAN_MEMORY_MODEL_SPEC_VERSION 3
+#define VK_KHR_VULKAN_MEMORY_MODEL_EXTENSION_NAME "VK_KHR_vulkan_memory_model"
+typedef VkPhysicalDeviceVulkanMemoryModelFeatures VkPhysicalDeviceVulkanMemoryModelFeaturesKHR;
+
+
+
+#define VK_KHR_shader_terminate_invocation 1
+#define VK_KHR_SHADER_TERMINATE_INVOCATION_SPEC_VERSION 1
+#define VK_KHR_SHADER_TERMINATE_INVOCATION_EXTENSION_NAME "VK_KHR_shader_terminate_invocation"
+typedef struct VkPhysicalDeviceShaderTerminateInvocationFeaturesKHR {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           shaderTerminateInvocation;
+} VkPhysicalDeviceShaderTerminateInvocationFeaturesKHR;
+
+
+
+#define VK_KHR_fragment_shading_rate 1
+#define VK_KHR_FRAGMENT_SHADING_RATE_SPEC_VERSION 2
+#define VK_KHR_FRAGMENT_SHADING_RATE_EXTENSION_NAME "VK_KHR_fragment_shading_rate"
+
+typedef enum VkFragmentShadingRateCombinerOpKHR {
+    VK_FRAGMENT_SHADING_RATE_COMBINER_OP_KEEP_KHR = 0,
+    VK_FRAGMENT_SHADING_RATE_COMBINER_OP_REPLACE_KHR = 1,
+    VK_FRAGMENT_SHADING_RATE_COMBINER_OP_MIN_KHR = 2,
+    VK_FRAGMENT_SHADING_RATE_COMBINER_OP_MAX_KHR = 3,
+    VK_FRAGMENT_SHADING_RATE_COMBINER_OP_MUL_KHR = 4,
+    VK_FRAGMENT_SHADING_RATE_COMBINER_OP_MAX_ENUM_KHR = 0x7FFFFFFF
+} VkFragmentShadingRateCombinerOpKHR;
+typedef struct VkFragmentShadingRateAttachmentInfoKHR {
+    VkStructureType                  sType;
+    const void*                      pNext;
+    const VkAttachmentReference2*    pFragmentShadingRateAttachment;
+    VkExtent2D                       shadingRateAttachmentTexelSize;
+} VkFragmentShadingRateAttachmentInfoKHR;
+
+typedef struct VkPipelineFragmentShadingRateStateCreateInfoKHR {
+    VkStructureType                       sType;
+    const void*                           pNext;
+    VkExtent2D                            fragmentSize;
+    VkFragmentShadingRateCombinerOpKHR    combinerOps[2];
+} VkPipelineFragmentShadingRateStateCreateInfoKHR;
+
+typedef struct VkPhysicalDeviceFragmentShadingRateFeaturesKHR {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           pipelineFragmentShadingRate;
+    VkBool32           primitiveFragmentShadingRate;
+    VkBool32           attachmentFragmentShadingRate;
+} VkPhysicalDeviceFragmentShadingRateFeaturesKHR;
+
+typedef struct VkPhysicalDeviceFragmentShadingRatePropertiesKHR {
+    VkStructureType          sType;
+    void*                    pNext;
+    VkExtent2D               minFragmentShadingRateAttachmentTexelSize;
+    VkExtent2D               maxFragmentShadingRateAttachmentTexelSize;
+    uint32_t                 maxFragmentShadingRateAttachmentTexelSizeAspectRatio;
+    VkBool32                 primitiveFragmentShadingRateWithMultipleViewports;
+    VkBool32                 layeredShadingRateAttachments;
+    VkBool32                 fragmentShadingRateNonTrivialCombinerOps;
+    VkExtent2D               maxFragmentSize;
+    uint32_t                 maxFragmentSizeAspectRatio;
+    uint32_t                 maxFragmentShadingRateCoverageSamples;
+    VkSampleCountFlagBits    maxFragmentShadingRateRasterizationSamples;
+    VkBool32                 fragmentShadingRateWithShaderDepthStencilWrites;
+    VkBool32                 fragmentShadingRateWithSampleMask;
+    VkBool32                 fragmentShadingRateWithShaderSampleMask;
+    VkBool32                 fragmentShadingRateWithConservativeRasterization;
+    VkBool32                 fragmentShadingRateWithFragmentShaderInterlock;
+    VkBool32                 fragmentShadingRateWithCustomSampleLocations;
+    VkBool32                 fragmentShadingRateStrictMultiplyCombiner;
+} VkPhysicalDeviceFragmentShadingRatePropertiesKHR;
+
+typedef struct VkPhysicalDeviceFragmentShadingRateKHR {
+    VkStructureType       sType;
+    void*                 pNext;
+    VkSampleCountFlags    sampleCounts;
+    VkExtent2D            fragmentSize;
+} VkPhysicalDeviceFragmentShadingRateKHR;
+
+typedef VkResult (VKAPI_PTR *PFN_vkGetPhysicalDeviceFragmentShadingRatesKHR)(VkPhysicalDevice physicalDevice, uint32_t* pFragmentShadingRateCount, VkPhysicalDeviceFragmentShadingRateKHR* pFragmentShadingRates);
+typedef void (VKAPI_PTR *PFN_vkCmdSetFragmentShadingRateKHR)(VkCommandBuffer           commandBuffer, const VkExtent2D*                           pFragmentSize, const VkFragmentShadingRateCombinerOpKHR    combinerOps[2]);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceFragmentShadingRatesKHR(
+    VkPhysicalDevice                            physicalDevice,
+    uint32_t*                                   pFragmentShadingRateCount,
+    VkPhysicalDeviceFragmentShadingRateKHR*     pFragmentShadingRates);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdSetFragmentShadingRateKHR(
+    VkCommandBuffer                             commandBuffer,
+    const VkExtent2D*                           pFragmentSize,
+    const VkFragmentShadingRateCombinerOpKHR    combinerOps[2]);
+#endif
+
+
+#define VK_KHR_spirv_1_4 1
+#define VK_KHR_SPIRV_1_4_SPEC_VERSION     1
+#define VK_KHR_SPIRV_1_4_EXTENSION_NAME   "VK_KHR_spirv_1_4"
+
+
+#define VK_KHR_surface_protected_capabilities 1
+#define VK_KHR_SURFACE_PROTECTED_CAPABILITIES_SPEC_VERSION 1
+#define VK_KHR_SURFACE_PROTECTED_CAPABILITIES_EXTENSION_NAME "VK_KHR_surface_protected_capabilities"
+typedef struct VkSurfaceProtectedCapabilitiesKHR {
+    VkStructureType    sType;
+    const void*        pNext;
+    VkBool32           supportsProtected;
+} VkSurfaceProtectedCapabilitiesKHR;
+
+
+
+#define VK_KHR_separate_depth_stencil_layouts 1
+#define VK_KHR_SEPARATE_DEPTH_STENCIL_LAYOUTS_SPEC_VERSION 1
+#define VK_KHR_SEPARATE_DEPTH_STENCIL_LAYOUTS_EXTENSION_NAME "VK_KHR_separate_depth_stencil_layouts"
+typedef VkPhysicalDeviceSeparateDepthStencilLayoutsFeatures VkPhysicalDeviceSeparateDepthStencilLayoutsFeaturesKHR;
+
+typedef VkAttachmentReferenceStencilLayout VkAttachmentReferenceStencilLayoutKHR;
+
+typedef VkAttachmentDescriptionStencilLayout VkAttachmentDescriptionStencilLayoutKHR;
+
+
+
+#define VK_KHR_present_wait 1
+#define VK_KHR_PRESENT_WAIT_SPEC_VERSION  1
+#define VK_KHR_PRESENT_WAIT_EXTENSION_NAME "VK_KHR_present_wait"
+typedef struct VkPhysicalDevicePresentWaitFeaturesKHR {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           presentWait;
+} VkPhysicalDevicePresentWaitFeaturesKHR;
+
+typedef VkResult (VKAPI_PTR *PFN_vkWaitForPresentKHR)(VkDevice device, VkSwapchainKHR swapchain, uint64_t presentId, uint64_t timeout);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkWaitForPresentKHR(
+    VkDevice                                    device,
+    VkSwapchainKHR                              swapchain,
+    uint64_t                                    presentId,
+    uint64_t                                    timeout);
+#endif
+
+
+#define VK_KHR_uniform_buffer_standard_layout 1
+#define VK_KHR_UNIFORM_BUFFER_STANDARD_LAYOUT_SPEC_VERSION 1
+#define VK_KHR_UNIFORM_BUFFER_STANDARD_LAYOUT_EXTENSION_NAME "VK_KHR_uniform_buffer_standard_layout"
+typedef VkPhysicalDeviceUniformBufferStandardLayoutFeatures VkPhysicalDeviceUniformBufferStandardLayoutFeaturesKHR;
+
+
+
+#define VK_KHR_buffer_device_address 1
+#define VK_KHR_BUFFER_DEVICE_ADDRESS_SPEC_VERSION 1
+#define VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME "VK_KHR_buffer_device_address"
+typedef VkPhysicalDeviceBufferDeviceAddressFeatures VkPhysicalDeviceBufferDeviceAddressFeaturesKHR;
+
+typedef VkBufferDeviceAddressInfo VkBufferDeviceAddressInfoKHR;
+
+typedef VkBufferOpaqueCaptureAddressCreateInfo VkBufferOpaqueCaptureAddressCreateInfoKHR;
+
+typedef VkMemoryOpaqueCaptureAddressAllocateInfo VkMemoryOpaqueCaptureAddressAllocateInfoKHR;
+
+typedef VkDeviceMemoryOpaqueCaptureAddressInfo VkDeviceMemoryOpaqueCaptureAddressInfoKHR;
+
+typedef VkDeviceAddress (VKAPI_PTR *PFN_vkGetBufferDeviceAddressKHR)(VkDevice device, const VkBufferDeviceAddressInfo* pInfo);
+typedef uint64_t (VKAPI_PTR *PFN_vkGetBufferOpaqueCaptureAddressKHR)(VkDevice device, const VkBufferDeviceAddressInfo* pInfo);
+typedef uint64_t (VKAPI_PTR *PFN_vkGetDeviceMemoryOpaqueCaptureAddressKHR)(VkDevice device, const VkDeviceMemoryOpaqueCaptureAddressInfo* pInfo);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkDeviceAddress VKAPI_CALL vkGetBufferDeviceAddressKHR(
+    VkDevice                                    device,
+    const VkBufferDeviceAddressInfo*            pInfo);
+
+VKAPI_ATTR uint64_t VKAPI_CALL vkGetBufferOpaqueCaptureAddressKHR(
+    VkDevice                                    device,
+    const VkBufferDeviceAddressInfo*            pInfo);
+
+VKAPI_ATTR uint64_t VKAPI_CALL vkGetDeviceMemoryOpaqueCaptureAddressKHR(
+    VkDevice                                    device,
+    const VkDeviceMemoryOpaqueCaptureAddressInfo* pInfo);
+#endif
+
+
+#define VK_KHR_deferred_host_operations 1
+VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkDeferredOperationKHR)
+#define VK_KHR_DEFERRED_HOST_OPERATIONS_SPEC_VERSION 4
+#define VK_KHR_DEFERRED_HOST_OPERATIONS_EXTENSION_NAME "VK_KHR_deferred_host_operations"
+typedef VkResult (VKAPI_PTR *PFN_vkCreateDeferredOperationKHR)(VkDevice device, const VkAllocationCallbacks* pAllocator, VkDeferredOperationKHR* pDeferredOperation);
+typedef void (VKAPI_PTR *PFN_vkDestroyDeferredOperationKHR)(VkDevice device, VkDeferredOperationKHR operation, const VkAllocationCallbacks* pAllocator);
+typedef uint32_t (VKAPI_PTR *PFN_vkGetDeferredOperationMaxConcurrencyKHR)(VkDevice device, VkDeferredOperationKHR operation);
+typedef VkResult (VKAPI_PTR *PFN_vkGetDeferredOperationResultKHR)(VkDevice device, VkDeferredOperationKHR operation);
+typedef VkResult (VKAPI_PTR *PFN_vkDeferredOperationJoinKHR)(VkDevice device, VkDeferredOperationKHR operation);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateDeferredOperationKHR(
+    VkDevice                                    device,
+    const VkAllocationCallbacks*                pAllocator,
+    VkDeferredOperationKHR*                     pDeferredOperation);
+
+VKAPI_ATTR void VKAPI_CALL vkDestroyDeferredOperationKHR(
+    VkDevice                                    device,
+    VkDeferredOperationKHR                      operation,
+    const VkAllocationCallbacks*                pAllocator);
+
+VKAPI_ATTR uint32_t VKAPI_CALL vkGetDeferredOperationMaxConcurrencyKHR(
+    VkDevice                                    device,
+    VkDeferredOperationKHR                      operation);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetDeferredOperationResultKHR(
+    VkDevice                                    device,
+    VkDeferredOperationKHR                      operation);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkDeferredOperationJoinKHR(
+    VkDevice                                    device,
+    VkDeferredOperationKHR                      operation);
+#endif
+
+
+#define VK_KHR_pipeline_executable_properties 1
+#define VK_KHR_PIPELINE_EXECUTABLE_PROPERTIES_SPEC_VERSION 1
+#define VK_KHR_PIPELINE_EXECUTABLE_PROPERTIES_EXTENSION_NAME "VK_KHR_pipeline_executable_properties"
+
+typedef enum VkPipelineExecutableStatisticFormatKHR {
+    VK_PIPELINE_EXECUTABLE_STATISTIC_FORMAT_BOOL32_KHR = 0,
+    VK_PIPELINE_EXECUTABLE_STATISTIC_FORMAT_INT64_KHR = 1,
+    VK_PIPELINE_EXECUTABLE_STATISTIC_FORMAT_UINT64_KHR = 2,
+    VK_PIPELINE_EXECUTABLE_STATISTIC_FORMAT_FLOAT64_KHR = 3,
+    VK_PIPELINE_EXECUTABLE_STATISTIC_FORMAT_MAX_ENUM_KHR = 0x7FFFFFFF
+} VkPipelineExecutableStatisticFormatKHR;
+typedef struct VkPhysicalDevicePipelineExecutablePropertiesFeaturesKHR {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           pipelineExecutableInfo;
+} VkPhysicalDevicePipelineExecutablePropertiesFeaturesKHR;
+
+typedef struct VkPipelineInfoKHR {
+    VkStructureType    sType;
+    const void*        pNext;
+    VkPipeline         pipeline;
+} VkPipelineInfoKHR;
+
+typedef struct VkPipelineExecutablePropertiesKHR {
+    VkStructureType       sType;
+    void*                 pNext;
+    VkShaderStageFlags    stages;
+    char                  name[VK_MAX_DESCRIPTION_SIZE];
+    char                  description[VK_MAX_DESCRIPTION_SIZE];
+    uint32_t              subgroupSize;
+} VkPipelineExecutablePropertiesKHR;
+
+typedef struct VkPipelineExecutableInfoKHR {
+    VkStructureType    sType;
+    const void*        pNext;
+    VkPipeline         pipeline;
+    uint32_t           executableIndex;
+} VkPipelineExecutableInfoKHR;
+
+typedef union VkPipelineExecutableStatisticValueKHR {
+    VkBool32    b32;
+    int64_t     i64;
+    uint64_t    u64;
+    double      f64;
+} VkPipelineExecutableStatisticValueKHR;
+
+typedef struct VkPipelineExecutableStatisticKHR {
+    VkStructureType                           sType;
+    void*                                     pNext;
+    char                                      name[VK_MAX_DESCRIPTION_SIZE];
+    char                                      description[VK_MAX_DESCRIPTION_SIZE];
+    VkPipelineExecutableStatisticFormatKHR    format;
+    VkPipelineExecutableStatisticValueKHR     value;
+} VkPipelineExecutableStatisticKHR;
+
+typedef struct VkPipelineExecutableInternalRepresentationKHR {
+    VkStructureType    sType;
+    void*              pNext;
+    char               name[VK_MAX_DESCRIPTION_SIZE];
+    char               description[VK_MAX_DESCRIPTION_SIZE];
+    VkBool32           isText;
+    size_t             dataSize;
+    void*              pData;
+} VkPipelineExecutableInternalRepresentationKHR;
+
+typedef VkResult (VKAPI_PTR *PFN_vkGetPipelineExecutablePropertiesKHR)(VkDevice                        device, const VkPipelineInfoKHR*        pPipelineInfo, uint32_t* pExecutableCount, VkPipelineExecutablePropertiesKHR* pProperties);
+typedef VkResult (VKAPI_PTR *PFN_vkGetPipelineExecutableStatisticsKHR)(VkDevice                        device, const VkPipelineExecutableInfoKHR*  pExecutableInfo, uint32_t* pStatisticCount, VkPipelineExecutableStatisticKHR* pStatistics);
+typedef VkResult (VKAPI_PTR *PFN_vkGetPipelineExecutableInternalRepresentationsKHR)(VkDevice                        device, const VkPipelineExecutableInfoKHR*  pExecutableInfo, uint32_t* pInternalRepresentationCount, VkPipelineExecutableInternalRepresentationKHR* pInternalRepresentations);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkGetPipelineExecutablePropertiesKHR(
+    VkDevice                                    device,
+    const VkPipelineInfoKHR*                    pPipelineInfo,
+    uint32_t*                                   pExecutableCount,
+    VkPipelineExecutablePropertiesKHR*          pProperties);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetPipelineExecutableStatisticsKHR(
+    VkDevice                                    device,
+    const VkPipelineExecutableInfoKHR*          pExecutableInfo,
+    uint32_t*                                   pStatisticCount,
+    VkPipelineExecutableStatisticKHR*           pStatistics);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetPipelineExecutableInternalRepresentationsKHR(
+    VkDevice                                    device,
+    const VkPipelineExecutableInfoKHR*          pExecutableInfo,
+    uint32_t*                                   pInternalRepresentationCount,
+    VkPipelineExecutableInternalRepresentationKHR* pInternalRepresentations);
+#endif
+
+
+#define VK_KHR_shader_integer_dot_product 1
+#define VK_KHR_SHADER_INTEGER_DOT_PRODUCT_SPEC_VERSION 1
+#define VK_KHR_SHADER_INTEGER_DOT_PRODUCT_EXTENSION_NAME "VK_KHR_shader_integer_dot_product"
+typedef struct VkPhysicalDeviceShaderIntegerDotProductFeaturesKHR {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           shaderIntegerDotProduct;
+} VkPhysicalDeviceShaderIntegerDotProductFeaturesKHR;
+
+typedef struct VkPhysicalDeviceShaderIntegerDotProductPropertiesKHR {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           integerDotProduct8BitUnsignedAccelerated;
+    VkBool32           integerDotProduct8BitSignedAccelerated;
+    VkBool32           integerDotProduct8BitMixedSignednessAccelerated;
+    VkBool32           integerDotProduct4x8BitPackedUnsignedAccelerated;
+    VkBool32           integerDotProduct4x8BitPackedSignedAccelerated;
+    VkBool32           integerDotProduct4x8BitPackedMixedSignednessAccelerated;
+    VkBool32           integerDotProduct16BitUnsignedAccelerated;
+    VkBool32           integerDotProduct16BitSignedAccelerated;
+    VkBool32           integerDotProduct16BitMixedSignednessAccelerated;
+    VkBool32           integerDotProduct32BitUnsignedAccelerated;
+    VkBool32           integerDotProduct32BitSignedAccelerated;
+    VkBool32           integerDotProduct32BitMixedSignednessAccelerated;
+    VkBool32           integerDotProduct64BitUnsignedAccelerated;
+    VkBool32           integerDotProduct64BitSignedAccelerated;
+    VkBool32           integerDotProduct64BitMixedSignednessAccelerated;
+    VkBool32           integerDotProductAccumulatingSaturating8BitUnsignedAccelerated;
+    VkBool32           integerDotProductAccumulatingSaturating8BitSignedAccelerated;
+    VkBool32           integerDotProductAccumulatingSaturating8BitMixedSignednessAccelerated;
+    VkBool32           integerDotProductAccumulatingSaturating4x8BitPackedUnsignedAccelerated;
+    VkBool32           integerDotProductAccumulatingSaturating4x8BitPackedSignedAccelerated;
+    VkBool32           integerDotProductAccumulatingSaturating4x8BitPackedMixedSignednessAccelerated;
+    VkBool32           integerDotProductAccumulatingSaturating16BitUnsignedAccelerated;
+    VkBool32           integerDotProductAccumulatingSaturating16BitSignedAccelerated;
+    VkBool32           integerDotProductAccumulatingSaturating16BitMixedSignednessAccelerated;
+    VkBool32           integerDotProductAccumulatingSaturating32BitUnsignedAccelerated;
+    VkBool32           integerDotProductAccumulatingSaturating32BitSignedAccelerated;
+    VkBool32           integerDotProductAccumulatingSaturating32BitMixedSignednessAccelerated;
+    VkBool32           integerDotProductAccumulatingSaturating64BitUnsignedAccelerated;
+    VkBool32           integerDotProductAccumulatingSaturating64BitSignedAccelerated;
+    VkBool32           integerDotProductAccumulatingSaturating64BitMixedSignednessAccelerated;
+} VkPhysicalDeviceShaderIntegerDotProductPropertiesKHR;
+
+
+
+#define VK_KHR_pipeline_library 1
+#define VK_KHR_PIPELINE_LIBRARY_SPEC_VERSION 1
+#define VK_KHR_PIPELINE_LIBRARY_EXTENSION_NAME "VK_KHR_pipeline_library"
+typedef struct VkPipelineLibraryCreateInfoKHR {
+    VkStructureType      sType;
+    const void*          pNext;
+    uint32_t             libraryCount;
+    const VkPipeline*    pLibraries;
+} VkPipelineLibraryCreateInfoKHR;
+
+
+
+#define VK_KHR_shader_non_semantic_info 1
+#define VK_KHR_SHADER_NON_SEMANTIC_INFO_SPEC_VERSION 1
+#define VK_KHR_SHADER_NON_SEMANTIC_INFO_EXTENSION_NAME "VK_KHR_shader_non_semantic_info"
+
+
+#define VK_KHR_present_id 1
+#define VK_KHR_PRESENT_ID_SPEC_VERSION    1
+#define VK_KHR_PRESENT_ID_EXTENSION_NAME  "VK_KHR_present_id"
+typedef struct VkPresentIdKHR {
+    VkStructureType    sType;
+    const void*        pNext;
+    uint32_t           swapchainCount;
+    const uint64_t*    pPresentIds;
+} VkPresentIdKHR;
+
+typedef struct VkPhysicalDevicePresentIdFeaturesKHR {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           presentId;
+} VkPhysicalDevicePresentIdFeaturesKHR;
+
+
+
+#define VK_KHR_synchronization2 1
+typedef uint64_t VkFlags64;
+#define VK_KHR_SYNCHRONIZATION_2_SPEC_VERSION 1
+#define VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME "VK_KHR_synchronization2"
+typedef VkFlags64 VkPipelineStageFlags2KHR;
+
+// Flag bits for VkPipelineStageFlagBits2KHR
+typedef VkFlags64 VkPipelineStageFlagBits2KHR;
+static const VkPipelineStageFlagBits2KHR VK_PIPELINE_STAGE_2_NONE_KHR = 0ULL;
+static const VkPipelineStageFlagBits2KHR VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT_KHR = 0x00000001ULL;
+static const VkPipelineStageFlagBits2KHR VK_PIPELINE_STAGE_2_DRAW_INDIRECT_BIT_KHR = 0x00000002ULL;
+static const VkPipelineStageFlagBits2KHR VK_PIPELINE_STAGE_2_VERTEX_INPUT_BIT_KHR = 0x00000004ULL;
+static const VkPipelineStageFlagBits2KHR VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT_KHR = 0x00000008ULL;
+static const VkPipelineStageFlagBits2KHR VK_PIPELINE_STAGE_2_TESSELLATION_CONTROL_SHADER_BIT_KHR = 0x00000010ULL;
+static const VkPipelineStageFlagBits2KHR VK_PIPELINE_STAGE_2_TESSELLATION_EVALUATION_SHADER_BIT_KHR = 0x00000020ULL;
+static const VkPipelineStageFlagBits2KHR VK_PIPELINE_STAGE_2_GEOMETRY_SHADER_BIT_KHR = 0x00000040ULL;
+static const VkPipelineStageFlagBits2KHR VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT_KHR = 0x00000080ULL;
+static const VkPipelineStageFlagBits2KHR VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT_KHR = 0x00000100ULL;
+static const VkPipelineStageFlagBits2KHR VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT_KHR = 0x00000200ULL;
+static const VkPipelineStageFlagBits2KHR VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT_KHR = 0x00000400ULL;
+static const VkPipelineStageFlagBits2KHR VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT_KHR = 0x00000800ULL;
+static const VkPipelineStageFlagBits2KHR VK_PIPELINE_STAGE_2_ALL_TRANSFER_BIT_KHR = 0x00001000ULL;
+static const VkPipelineStageFlagBits2KHR VK_PIPELINE_STAGE_2_TRANSFER_BIT_KHR = 0x00001000ULL;
+static const VkPipelineStageFlagBits2KHR VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT_KHR = 0x00002000ULL;
+static const VkPipelineStageFlagBits2KHR VK_PIPELINE_STAGE_2_HOST_BIT_KHR = 0x00004000ULL;
+static const VkPipelineStageFlagBits2KHR VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT_KHR = 0x00008000ULL;
+static const VkPipelineStageFlagBits2KHR VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT_KHR = 0x00010000ULL;
+static const VkPipelineStageFlagBits2KHR VK_PIPELINE_STAGE_2_COPY_BIT_KHR = 0x100000000ULL;
+static const VkPipelineStageFlagBits2KHR VK_PIPELINE_STAGE_2_RESOLVE_BIT_KHR = 0x200000000ULL;
+static const VkPipelineStageFlagBits2KHR VK_PIPELINE_STAGE_2_BLIT_BIT_KHR = 0x400000000ULL;
+static const VkPipelineStageFlagBits2KHR VK_PIPELINE_STAGE_2_CLEAR_BIT_KHR = 0x800000000ULL;
+static const VkPipelineStageFlagBits2KHR VK_PIPELINE_STAGE_2_INDEX_INPUT_BIT_KHR = 0x1000000000ULL;
+static const VkPipelineStageFlagBits2KHR VK_PIPELINE_STAGE_2_VERTEX_ATTRIBUTE_INPUT_BIT_KHR = 0x2000000000ULL;
+static const VkPipelineStageFlagBits2KHR VK_PIPELINE_STAGE_2_PRE_RASTERIZATION_SHADERS_BIT_KHR = 0x4000000000ULL;
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+static const VkPipelineStageFlagBits2KHR VK_PIPELINE_STAGE_2_VIDEO_DECODE_BIT_KHR = 0x04000000ULL;
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+static const VkPipelineStageFlagBits2KHR VK_PIPELINE_STAGE_2_VIDEO_ENCODE_BIT_KHR = 0x08000000ULL;
+#endif
+static const VkPipelineStageFlagBits2KHR VK_PIPELINE_STAGE_2_TRANSFORM_FEEDBACK_BIT_EXT = 0x01000000ULL;
+static const VkPipelineStageFlagBits2KHR VK_PIPELINE_STAGE_2_CONDITIONAL_RENDERING_BIT_EXT = 0x00040000ULL;
+static const VkPipelineStageFlagBits2KHR VK_PIPELINE_STAGE_2_COMMAND_PREPROCESS_BIT_NV = 0x00020000ULL;
+static const VkPipelineStageFlagBits2KHR VK_PIPELINE_STAGE_2_FRAGMENT_SHADING_RATE_ATTACHMENT_BIT_KHR = 0x00400000ULL;
+static const VkPipelineStageFlagBits2KHR VK_PIPELINE_STAGE_2_SHADING_RATE_IMAGE_BIT_NV = 0x00400000ULL;
+static const VkPipelineStageFlagBits2KHR VK_PIPELINE_STAGE_2_ACCELERATION_STRUCTURE_BUILD_BIT_KHR = 0x02000000ULL;
+static const VkPipelineStageFlagBits2KHR VK_PIPELINE_STAGE_2_RAY_TRACING_SHADER_BIT_KHR = 0x00200000ULL;
+static const VkPipelineStageFlagBits2KHR VK_PIPELINE_STAGE_2_RAY_TRACING_SHADER_BIT_NV = 0x00200000ULL;
+static const VkPipelineStageFlagBits2KHR VK_PIPELINE_STAGE_2_ACCELERATION_STRUCTURE_BUILD_BIT_NV = 0x02000000ULL;
+static const VkPipelineStageFlagBits2KHR VK_PIPELINE_STAGE_2_FRAGMENT_DENSITY_PROCESS_BIT_EXT = 0x00800000ULL;
+static const VkPipelineStageFlagBits2KHR VK_PIPELINE_STAGE_2_TASK_SHADER_BIT_NV = 0x00080000ULL;
+static const VkPipelineStageFlagBits2KHR VK_PIPELINE_STAGE_2_MESH_SHADER_BIT_NV = 0x00100000ULL;
+static const VkPipelineStageFlagBits2KHR VK_PIPELINE_STAGE_2_SUBPASS_SHADING_BIT_HUAWEI = 0x8000000000ULL;
+static const VkPipelineStageFlagBits2KHR VK_PIPELINE_STAGE_2_INVOCATION_MASK_BIT_HUAWEI = 0x10000000000ULL;
+
+typedef VkFlags64 VkAccessFlags2KHR;
+
+// Flag bits for VkAccessFlagBits2KHR
+typedef VkFlags64 VkAccessFlagBits2KHR;
+static const VkAccessFlagBits2KHR VK_ACCESS_2_NONE_KHR = 0ULL;
+static const VkAccessFlagBits2KHR VK_ACCESS_2_INDIRECT_COMMAND_READ_BIT_KHR = 0x00000001ULL;
+static const VkAccessFlagBits2KHR VK_ACCESS_2_INDEX_READ_BIT_KHR = 0x00000002ULL;
+static const VkAccessFlagBits2KHR VK_ACCESS_2_VERTEX_ATTRIBUTE_READ_BIT_KHR = 0x00000004ULL;
+static const VkAccessFlagBits2KHR VK_ACCESS_2_UNIFORM_READ_BIT_KHR = 0x00000008ULL;
+static const VkAccessFlagBits2KHR VK_ACCESS_2_INPUT_ATTACHMENT_READ_BIT_KHR = 0x00000010ULL;
+static const VkAccessFlagBits2KHR VK_ACCESS_2_SHADER_READ_BIT_KHR = 0x00000020ULL;
+static const VkAccessFlagBits2KHR VK_ACCESS_2_SHADER_WRITE_BIT_KHR = 0x00000040ULL;
+static const VkAccessFlagBits2KHR VK_ACCESS_2_COLOR_ATTACHMENT_READ_BIT_KHR = 0x00000080ULL;
+static const VkAccessFlagBits2KHR VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT_KHR = 0x00000100ULL;
+static const VkAccessFlagBits2KHR VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_READ_BIT_KHR = 0x00000200ULL;
+static const VkAccessFlagBits2KHR VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT_KHR = 0x00000400ULL;
+static const VkAccessFlagBits2KHR VK_ACCESS_2_TRANSFER_READ_BIT_KHR = 0x00000800ULL;
+static const VkAccessFlagBits2KHR VK_ACCESS_2_TRANSFER_WRITE_BIT_KHR = 0x00001000ULL;
+static const VkAccessFlagBits2KHR VK_ACCESS_2_HOST_READ_BIT_KHR = 0x00002000ULL;
+static const VkAccessFlagBits2KHR VK_ACCESS_2_HOST_WRITE_BIT_KHR = 0x00004000ULL;
+static const VkAccessFlagBits2KHR VK_ACCESS_2_MEMORY_READ_BIT_KHR = 0x00008000ULL;
+static const VkAccessFlagBits2KHR VK_ACCESS_2_MEMORY_WRITE_BIT_KHR = 0x00010000ULL;
+static const VkAccessFlagBits2KHR VK_ACCESS_2_SHADER_SAMPLED_READ_BIT_KHR = 0x100000000ULL;
+static const VkAccessFlagBits2KHR VK_ACCESS_2_SHADER_STORAGE_READ_BIT_KHR = 0x200000000ULL;
+static const VkAccessFlagBits2KHR VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT_KHR = 0x400000000ULL;
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+static const VkAccessFlagBits2KHR VK_ACCESS_2_VIDEO_DECODE_READ_BIT_KHR = 0x800000000ULL;
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+static const VkAccessFlagBits2KHR VK_ACCESS_2_VIDEO_DECODE_WRITE_BIT_KHR = 0x1000000000ULL;
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+static const VkAccessFlagBits2KHR VK_ACCESS_2_VIDEO_ENCODE_READ_BIT_KHR = 0x2000000000ULL;
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+static const VkAccessFlagBits2KHR VK_ACCESS_2_VIDEO_ENCODE_WRITE_BIT_KHR = 0x4000000000ULL;
+#endif
+static const VkAccessFlagBits2KHR VK_ACCESS_2_TRANSFORM_FEEDBACK_WRITE_BIT_EXT = 0x02000000ULL;
+static const VkAccessFlagBits2KHR VK_ACCESS_2_TRANSFORM_FEEDBACK_COUNTER_READ_BIT_EXT = 0x04000000ULL;
+static const VkAccessFlagBits2KHR VK_ACCESS_2_TRANSFORM_FEEDBACK_COUNTER_WRITE_BIT_EXT = 0x08000000ULL;
+static const VkAccessFlagBits2KHR VK_ACCESS_2_CONDITIONAL_RENDERING_READ_BIT_EXT = 0x00100000ULL;
+static const VkAccessFlagBits2KHR VK_ACCESS_2_COMMAND_PREPROCESS_READ_BIT_NV = 0x00020000ULL;
+static const VkAccessFlagBits2KHR VK_ACCESS_2_COMMAND_PREPROCESS_WRITE_BIT_NV = 0x00040000ULL;
+static const VkAccessFlagBits2KHR VK_ACCESS_2_FRAGMENT_SHADING_RATE_ATTACHMENT_READ_BIT_KHR = 0x00800000ULL;
+static const VkAccessFlagBits2KHR VK_ACCESS_2_SHADING_RATE_IMAGE_READ_BIT_NV = 0x00800000ULL;
+static const VkAccessFlagBits2KHR VK_ACCESS_2_ACCELERATION_STRUCTURE_READ_BIT_KHR = 0x00200000ULL;
+static const VkAccessFlagBits2KHR VK_ACCESS_2_ACCELERATION_STRUCTURE_WRITE_BIT_KHR = 0x00400000ULL;
+static const VkAccessFlagBits2KHR VK_ACCESS_2_ACCELERATION_STRUCTURE_READ_BIT_NV = 0x00200000ULL;
+static const VkAccessFlagBits2KHR VK_ACCESS_2_ACCELERATION_STRUCTURE_WRITE_BIT_NV = 0x00400000ULL;
+static const VkAccessFlagBits2KHR VK_ACCESS_2_FRAGMENT_DENSITY_MAP_READ_BIT_EXT = 0x01000000ULL;
+static const VkAccessFlagBits2KHR VK_ACCESS_2_COLOR_ATTACHMENT_READ_NONCOHERENT_BIT_EXT = 0x00080000ULL;
+static const VkAccessFlagBits2KHR VK_ACCESS_2_INVOCATION_MASK_READ_BIT_HUAWEI = 0x8000000000ULL;
+
+
+typedef enum VkSubmitFlagBitsKHR {
+    VK_SUBMIT_PROTECTED_BIT_KHR = 0x00000001,
+    VK_SUBMIT_FLAG_BITS_MAX_ENUM_KHR = 0x7FFFFFFF
+} VkSubmitFlagBitsKHR;
+typedef VkFlags VkSubmitFlagsKHR;
+typedef struct VkMemoryBarrier2KHR {
+    VkStructureType             sType;
+    const void*                 pNext;
+    VkPipelineStageFlags2KHR    srcStageMask;
+    VkAccessFlags2KHR           srcAccessMask;
+    VkPipelineStageFlags2KHR    dstStageMask;
+    VkAccessFlags2KHR           dstAccessMask;
+} VkMemoryBarrier2KHR;
+
+typedef struct VkBufferMemoryBarrier2KHR {
+    VkStructureType             sType;
+    const void*                 pNext;
+    VkPipelineStageFlags2KHR    srcStageMask;
+    VkAccessFlags2KHR           srcAccessMask;
+    VkPipelineStageFlags2KHR    dstStageMask;
+    VkAccessFlags2KHR           dstAccessMask;
+    uint32_t                    srcQueueFamilyIndex;
+    uint32_t                    dstQueueFamilyIndex;
+    VkBuffer                    buffer;
+    VkDeviceSize                offset;
+    VkDeviceSize                size;
+} VkBufferMemoryBarrier2KHR;
+
+typedef struct VkImageMemoryBarrier2KHR {
+    VkStructureType             sType;
+    const void*                 pNext;
+    VkPipelineStageFlags2KHR    srcStageMask;
+    VkAccessFlags2KHR           srcAccessMask;
+    VkPipelineStageFlags2KHR    dstStageMask;
+    VkAccessFlags2KHR           dstAccessMask;
+    VkImageLayout               oldLayout;
+    VkImageLayout               newLayout;
+    uint32_t                    srcQueueFamilyIndex;
+    uint32_t                    dstQueueFamilyIndex;
+    VkImage                     image;
+    VkImageSubresourceRange     subresourceRange;
+} VkImageMemoryBarrier2KHR;
+
+typedef struct VkDependencyInfoKHR {
+    VkStructureType                     sType;
+    const void*                         pNext;
+    VkDependencyFlags                   dependencyFlags;
+    uint32_t                            memoryBarrierCount;
+    const VkMemoryBarrier2KHR*          pMemoryBarriers;
+    uint32_t                            bufferMemoryBarrierCount;
+    const VkBufferMemoryBarrier2KHR*    pBufferMemoryBarriers;
+    uint32_t                            imageMemoryBarrierCount;
+    const VkImageMemoryBarrier2KHR*     pImageMemoryBarriers;
+} VkDependencyInfoKHR;
+
+typedef struct VkSemaphoreSubmitInfoKHR {
+    VkStructureType             sType;
+    const void*                 pNext;
+    VkSemaphore                 semaphore;
+    uint64_t                    value;
+    VkPipelineStageFlags2KHR    stageMask;
+    uint32_t                    deviceIndex;
+} VkSemaphoreSubmitInfoKHR;
+
+typedef struct VkCommandBufferSubmitInfoKHR {
+    VkStructureType    sType;
+    const void*        pNext;
+    VkCommandBuffer    commandBuffer;
+    uint32_t           deviceMask;
+} VkCommandBufferSubmitInfoKHR;
+
+typedef struct VkSubmitInfo2KHR {
+    VkStructureType                        sType;
+    const void*                            pNext;
+    VkSubmitFlagsKHR                       flags;
+    uint32_t                               waitSemaphoreInfoCount;
+    const VkSemaphoreSubmitInfoKHR*        pWaitSemaphoreInfos;
+    uint32_t                               commandBufferInfoCount;
+    const VkCommandBufferSubmitInfoKHR*    pCommandBufferInfos;
+    uint32_t                               signalSemaphoreInfoCount;
+    const VkSemaphoreSubmitInfoKHR*        pSignalSemaphoreInfos;
+} VkSubmitInfo2KHR;
+
+typedef struct VkPhysicalDeviceSynchronization2FeaturesKHR {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           synchronization2;
+} VkPhysicalDeviceSynchronization2FeaturesKHR;
+
+typedef struct VkQueueFamilyCheckpointProperties2NV {
+    VkStructureType             sType;
+    void*                       pNext;
+    VkPipelineStageFlags2KHR    checkpointExecutionStageMask;
+} VkQueueFamilyCheckpointProperties2NV;
+
+typedef struct VkCheckpointData2NV {
+    VkStructureType             sType;
+    void*                       pNext;
+    VkPipelineStageFlags2KHR    stage;
+    void*                       pCheckpointMarker;
+} VkCheckpointData2NV;
+
+typedef void (VKAPI_PTR *PFN_vkCmdSetEvent2KHR)(VkCommandBuffer                   commandBuffer, VkEvent                                             event, const VkDependencyInfoKHR*                          pDependencyInfo);
+typedef void (VKAPI_PTR *PFN_vkCmdResetEvent2KHR)(VkCommandBuffer                   commandBuffer, VkEvent                                             event, VkPipelineStageFlags2KHR                            stageMask);
+typedef void (VKAPI_PTR *PFN_vkCmdWaitEvents2KHR)(VkCommandBuffer                   commandBuffer, uint32_t                                            eventCount, const VkEvent*                     pEvents, const VkDependencyInfoKHR*         pDependencyInfos);
+typedef void (VKAPI_PTR *PFN_vkCmdPipelineBarrier2KHR)(VkCommandBuffer                   commandBuffer, const VkDependencyInfoKHR*                                pDependencyInfo);
+typedef void (VKAPI_PTR *PFN_vkCmdWriteTimestamp2KHR)(VkCommandBuffer                   commandBuffer, VkPipelineStageFlags2KHR                            stage, VkQueryPool                                         queryPool, uint32_t                                            query);
+typedef VkResult (VKAPI_PTR *PFN_vkQueueSubmit2KHR)(VkQueue                           queue, uint32_t                            submitCount, const VkSubmitInfo2KHR*           pSubmits, VkFence           fence);
+typedef void (VKAPI_PTR *PFN_vkCmdWriteBufferMarker2AMD)(VkCommandBuffer                   commandBuffer, VkPipelineStageFlags2KHR                            stage, VkBuffer                                            dstBuffer, VkDeviceSize                                        dstOffset, uint32_t                                            marker);
+typedef void (VKAPI_PTR *PFN_vkGetQueueCheckpointData2NV)(VkQueue queue, uint32_t* pCheckpointDataCount, VkCheckpointData2NV* pCheckpointData);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR void VKAPI_CALL vkCmdSetEvent2KHR(
+    VkCommandBuffer                             commandBuffer,
+    VkEvent                                     event,
+    const VkDependencyInfoKHR*                  pDependencyInfo);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdResetEvent2KHR(
+    VkCommandBuffer                             commandBuffer,
+    VkEvent                                     event,
+    VkPipelineStageFlags2KHR                    stageMask);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdWaitEvents2KHR(
+    VkCommandBuffer                             commandBuffer,
+    uint32_t                                    eventCount,
+    const VkEvent*                              pEvents,
+    const VkDependencyInfoKHR*                  pDependencyInfos);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdPipelineBarrier2KHR(
+    VkCommandBuffer                             commandBuffer,
+    const VkDependencyInfoKHR*                  pDependencyInfo);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdWriteTimestamp2KHR(
+    VkCommandBuffer                             commandBuffer,
+    VkPipelineStageFlags2KHR                    stage,
+    VkQueryPool                                 queryPool,
+    uint32_t                                    query);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkQueueSubmit2KHR(
+    VkQueue                                     queue,
+    uint32_t                                    submitCount,
+    const VkSubmitInfo2KHR*                     pSubmits,
+    VkFence                                     fence);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdWriteBufferMarker2AMD(
+    VkCommandBuffer                             commandBuffer,
+    VkPipelineStageFlags2KHR                    stage,
+    VkBuffer                                    dstBuffer,
+    VkDeviceSize                                dstOffset,
+    uint32_t                                    marker);
+
+VKAPI_ATTR void VKAPI_CALL vkGetQueueCheckpointData2NV(
+    VkQueue                                     queue,
+    uint32_t*                                   pCheckpointDataCount,
+    VkCheckpointData2NV*                        pCheckpointData);
+#endif
+
+
+#define VK_KHR_shader_subgroup_uniform_control_flow 1
+#define VK_KHR_SHADER_SUBGROUP_UNIFORM_CONTROL_FLOW_SPEC_VERSION 1
+#define VK_KHR_SHADER_SUBGROUP_UNIFORM_CONTROL_FLOW_EXTENSION_NAME "VK_KHR_shader_subgroup_uniform_control_flow"
+typedef struct VkPhysicalDeviceShaderSubgroupUniformControlFlowFeaturesKHR {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           shaderSubgroupUniformControlFlow;
+} VkPhysicalDeviceShaderSubgroupUniformControlFlowFeaturesKHR;
+
+
+
+#define VK_KHR_zero_initialize_workgroup_memory 1
+#define VK_KHR_ZERO_INITIALIZE_WORKGROUP_MEMORY_SPEC_VERSION 1
+#define VK_KHR_ZERO_INITIALIZE_WORKGROUP_MEMORY_EXTENSION_NAME "VK_KHR_zero_initialize_workgroup_memory"
+typedef struct VkPhysicalDeviceZeroInitializeWorkgroupMemoryFeaturesKHR {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           shaderZeroInitializeWorkgroupMemory;
+} VkPhysicalDeviceZeroInitializeWorkgroupMemoryFeaturesKHR;
+
+
+
+#define VK_KHR_workgroup_memory_explicit_layout 1
+#define VK_KHR_WORKGROUP_MEMORY_EXPLICIT_LAYOUT_SPEC_VERSION 1
+#define VK_KHR_WORKGROUP_MEMORY_EXPLICIT_LAYOUT_EXTENSION_NAME "VK_KHR_workgroup_memory_explicit_layout"
+typedef struct VkPhysicalDeviceWorkgroupMemoryExplicitLayoutFeaturesKHR {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           workgroupMemoryExplicitLayout;
+    VkBool32           workgroupMemoryExplicitLayoutScalarBlockLayout;
+    VkBool32           workgroupMemoryExplicitLayout8BitAccess;
+    VkBool32           workgroupMemoryExplicitLayout16BitAccess;
+} VkPhysicalDeviceWorkgroupMemoryExplicitLayoutFeaturesKHR;
+
+
+
+#define VK_KHR_copy_commands2 1
+#define VK_KHR_COPY_COMMANDS_2_SPEC_VERSION 1
+#define VK_KHR_COPY_COMMANDS_2_EXTENSION_NAME "VK_KHR_copy_commands2"
+typedef struct VkBufferCopy2KHR {
+    VkStructureType    sType;
+    const void*        pNext;
+    VkDeviceSize       srcOffset;
+    VkDeviceSize       dstOffset;
+    VkDeviceSize       size;
+} VkBufferCopy2KHR;
+
+typedef struct VkCopyBufferInfo2KHR {
+    VkStructureType            sType;
+    const void*                pNext;
+    VkBuffer                   srcBuffer;
+    VkBuffer                   dstBuffer;
+    uint32_t                   regionCount;
+    const VkBufferCopy2KHR*    pRegions;
+} VkCopyBufferInfo2KHR;
+
+typedef struct VkImageCopy2KHR {
+    VkStructureType             sType;
+    const void*                 pNext;
+    VkImageSubresourceLayers    srcSubresource;
+    VkOffset3D                  srcOffset;
+    VkImageSubresourceLayers    dstSubresource;
+    VkOffset3D                  dstOffset;
+    VkExtent3D                  extent;
+} VkImageCopy2KHR;
+
+typedef struct VkCopyImageInfo2KHR {
+    VkStructureType           sType;
+    const void*               pNext;
+    VkImage                   srcImage;
+    VkImageLayout             srcImageLayout;
+    VkImage                   dstImage;
+    VkImageLayout             dstImageLayout;
+    uint32_t                  regionCount;
+    const VkImageCopy2KHR*    pRegions;
+} VkCopyImageInfo2KHR;
+
+typedef struct VkBufferImageCopy2KHR {
+    VkStructureType             sType;
+    const void*                 pNext;
+    VkDeviceSize                bufferOffset;
+    uint32_t                    bufferRowLength;
+    uint32_t                    bufferImageHeight;
+    VkImageSubresourceLayers    imageSubresource;
+    VkOffset3D                  imageOffset;
+    VkExtent3D                  imageExtent;
+} VkBufferImageCopy2KHR;
+
+typedef struct VkCopyBufferToImageInfo2KHR {
+    VkStructureType                 sType;
+    const void*                     pNext;
+    VkBuffer                        srcBuffer;
+    VkImage                         dstImage;
+    VkImageLayout                   dstImageLayout;
+    uint32_t                        regionCount;
+    const VkBufferImageCopy2KHR*    pRegions;
+} VkCopyBufferToImageInfo2KHR;
+
+typedef struct VkCopyImageToBufferInfo2KHR {
+    VkStructureType                 sType;
+    const void*                     pNext;
+    VkImage                         srcImage;
+    VkImageLayout                   srcImageLayout;
+    VkBuffer                        dstBuffer;
+    uint32_t                        regionCount;
+    const VkBufferImageCopy2KHR*    pRegions;
+} VkCopyImageToBufferInfo2KHR;
+
+typedef struct VkImageBlit2KHR {
+    VkStructureType             sType;
+    const void*                 pNext;
+    VkImageSubresourceLayers    srcSubresource;
+    VkOffset3D                  srcOffsets[2];
+    VkImageSubresourceLayers    dstSubresource;
+    VkOffset3D                  dstOffsets[2];
+} VkImageBlit2KHR;
+
+typedef struct VkBlitImageInfo2KHR {
+    VkStructureType           sType;
+    const void*               pNext;
+    VkImage                   srcImage;
+    VkImageLayout             srcImageLayout;
+    VkImage                   dstImage;
+    VkImageLayout             dstImageLayout;
+    uint32_t                  regionCount;
+    const VkImageBlit2KHR*    pRegions;
+    VkFilter                  filter;
+} VkBlitImageInfo2KHR;
+
+typedef struct VkImageResolve2KHR {
+    VkStructureType             sType;
+    const void*                 pNext;
+    VkImageSubresourceLayers    srcSubresource;
+    VkOffset3D                  srcOffset;
+    VkImageSubresourceLayers    dstSubresource;
+    VkOffset3D                  dstOffset;
+    VkExtent3D                  extent;
+} VkImageResolve2KHR;
+
+typedef struct VkResolveImageInfo2KHR {
+    VkStructureType              sType;
+    const void*                  pNext;
+    VkImage                      srcImage;
+    VkImageLayout                srcImageLayout;
+    VkImage                      dstImage;
+    VkImageLayout                dstImageLayout;
+    uint32_t                     regionCount;
+    const VkImageResolve2KHR*    pRegions;
+} VkResolveImageInfo2KHR;
+
+typedef void (VKAPI_PTR *PFN_vkCmdCopyBuffer2KHR)(VkCommandBuffer commandBuffer, const VkCopyBufferInfo2KHR* pCopyBufferInfo);
+typedef void (VKAPI_PTR *PFN_vkCmdCopyImage2KHR)(VkCommandBuffer commandBuffer, const VkCopyImageInfo2KHR* pCopyImageInfo);
+typedef void (VKAPI_PTR *PFN_vkCmdCopyBufferToImage2KHR)(VkCommandBuffer commandBuffer, const VkCopyBufferToImageInfo2KHR* pCopyBufferToImageInfo);
+typedef void (VKAPI_PTR *PFN_vkCmdCopyImageToBuffer2KHR)(VkCommandBuffer commandBuffer, const VkCopyImageToBufferInfo2KHR* pCopyImageToBufferInfo);
+typedef void (VKAPI_PTR *PFN_vkCmdBlitImage2KHR)(VkCommandBuffer commandBuffer, const VkBlitImageInfo2KHR* pBlitImageInfo);
+typedef void (VKAPI_PTR *PFN_vkCmdResolveImage2KHR)(VkCommandBuffer commandBuffer, const VkResolveImageInfo2KHR* pResolveImageInfo);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR void VKAPI_CALL vkCmdCopyBuffer2KHR(
+    VkCommandBuffer                             commandBuffer,
+    const VkCopyBufferInfo2KHR*                 pCopyBufferInfo);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdCopyImage2KHR(
+    VkCommandBuffer                             commandBuffer,
+    const VkCopyImageInfo2KHR*                  pCopyImageInfo);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdCopyBufferToImage2KHR(
+    VkCommandBuffer                             commandBuffer,
+    const VkCopyBufferToImageInfo2KHR*          pCopyBufferToImageInfo);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdCopyImageToBuffer2KHR(
+    VkCommandBuffer                             commandBuffer,
+    const VkCopyImageToBufferInfo2KHR*          pCopyImageToBufferInfo);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdBlitImage2KHR(
+    VkCommandBuffer                             commandBuffer,
+    const VkBlitImageInfo2KHR*                  pBlitImageInfo);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdResolveImage2KHR(
+    VkCommandBuffer                             commandBuffer,
+    const VkResolveImageInfo2KHR*               pResolveImageInfo);
+#endif
+
+
+#define VK_KHR_format_feature_flags2 1
+#define VK_KHR_FORMAT_FEATURE_FLAGS_2_SPEC_VERSION 1
+#define VK_KHR_FORMAT_FEATURE_FLAGS_2_EXTENSION_NAME "VK_KHR_format_feature_flags2"
+typedef VkFlags64 VkFormatFeatureFlags2KHR;
+
+// Flag bits for VkFormatFeatureFlagBits2KHR
+typedef VkFlags64 VkFormatFeatureFlagBits2KHR;
+static const VkFormatFeatureFlagBits2KHR VK_FORMAT_FEATURE_2_SAMPLED_IMAGE_BIT_KHR = 0x00000001ULL;
+static const VkFormatFeatureFlagBits2KHR VK_FORMAT_FEATURE_2_STORAGE_IMAGE_BIT_KHR = 0x00000002ULL;
+static const VkFormatFeatureFlagBits2KHR VK_FORMAT_FEATURE_2_STORAGE_IMAGE_ATOMIC_BIT_KHR = 0x00000004ULL;
+static const VkFormatFeatureFlagBits2KHR VK_FORMAT_FEATURE_2_UNIFORM_TEXEL_BUFFER_BIT_KHR = 0x00000008ULL;
+static const VkFormatFeatureFlagBits2KHR VK_FORMAT_FEATURE_2_STORAGE_TEXEL_BUFFER_BIT_KHR = 0x00000010ULL;
+static const VkFormatFeatureFlagBits2KHR VK_FORMAT_FEATURE_2_STORAGE_TEXEL_BUFFER_ATOMIC_BIT_KHR = 0x00000020ULL;
+static const VkFormatFeatureFlagBits2KHR VK_FORMAT_FEATURE_2_VERTEX_BUFFER_BIT_KHR = 0x00000040ULL;
+static const VkFormatFeatureFlagBits2KHR VK_FORMAT_FEATURE_2_COLOR_ATTACHMENT_BIT_KHR = 0x00000080ULL;
+static const VkFormatFeatureFlagBits2KHR VK_FORMAT_FEATURE_2_COLOR_ATTACHMENT_BLEND_BIT_KHR = 0x00000100ULL;
+static const VkFormatFeatureFlagBits2KHR VK_FORMAT_FEATURE_2_DEPTH_STENCIL_ATTACHMENT_BIT_KHR = 0x00000200ULL;
+static const VkFormatFeatureFlagBits2KHR VK_FORMAT_FEATURE_2_BLIT_SRC_BIT_KHR = 0x00000400ULL;
+static const VkFormatFeatureFlagBits2KHR VK_FORMAT_FEATURE_2_BLIT_DST_BIT_KHR = 0x00000800ULL;
+static const VkFormatFeatureFlagBits2KHR VK_FORMAT_FEATURE_2_SAMPLED_IMAGE_FILTER_LINEAR_BIT_KHR = 0x00001000ULL;
+static const VkFormatFeatureFlagBits2KHR VK_FORMAT_FEATURE_2_SAMPLED_IMAGE_FILTER_CUBIC_BIT_EXT = 0x00002000ULL;
+static const VkFormatFeatureFlagBits2KHR VK_FORMAT_FEATURE_2_TRANSFER_SRC_BIT_KHR = 0x00004000ULL;
+static const VkFormatFeatureFlagBits2KHR VK_FORMAT_FEATURE_2_TRANSFER_DST_BIT_KHR = 0x00008000ULL;
+static const VkFormatFeatureFlagBits2KHR VK_FORMAT_FEATURE_2_SAMPLED_IMAGE_FILTER_MINMAX_BIT_KHR = 0x00010000ULL;
+static const VkFormatFeatureFlagBits2KHR VK_FORMAT_FEATURE_2_MIDPOINT_CHROMA_SAMPLES_BIT_KHR = 0x00020000ULL;
+static const VkFormatFeatureFlagBits2KHR VK_FORMAT_FEATURE_2_SAMPLED_IMAGE_YCBCR_CONVERSION_LINEAR_FILTER_BIT_KHR = 0x00040000ULL;
+static const VkFormatFeatureFlagBits2KHR VK_FORMAT_FEATURE_2_SAMPLED_IMAGE_YCBCR_CONVERSION_SEPARATE_RECONSTRUCTION_FILTER_BIT_KHR = 0x00080000ULL;
+static const VkFormatFeatureFlagBits2KHR VK_FORMAT_FEATURE_2_SAMPLED_IMAGE_YCBCR_CONVERSION_CHROMA_RECONSTRUCTION_EXPLICIT_BIT_KHR = 0x00100000ULL;
+static const VkFormatFeatureFlagBits2KHR VK_FORMAT_FEATURE_2_SAMPLED_IMAGE_YCBCR_CONVERSION_CHROMA_RECONSTRUCTION_EXPLICIT_FORCEABLE_BIT_KHR = 0x00200000ULL;
+static const VkFormatFeatureFlagBits2KHR VK_FORMAT_FEATURE_2_DISJOINT_BIT_KHR = 0x00400000ULL;
+static const VkFormatFeatureFlagBits2KHR VK_FORMAT_FEATURE_2_COSITED_CHROMA_SAMPLES_BIT_KHR = 0x00800000ULL;
+static const VkFormatFeatureFlagBits2KHR VK_FORMAT_FEATURE_2_STORAGE_READ_WITHOUT_FORMAT_BIT_KHR = 0x80000000ULL;
+static const VkFormatFeatureFlagBits2KHR VK_FORMAT_FEATURE_2_STORAGE_WRITE_WITHOUT_FORMAT_BIT_KHR = 0x100000000ULL;
+static const VkFormatFeatureFlagBits2KHR VK_FORMAT_FEATURE_2_SAMPLED_IMAGE_DEPTH_COMPARISON_BIT_KHR = 0x200000000ULL;
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+static const VkFormatFeatureFlagBits2KHR VK_FORMAT_FEATURE_2_VIDEO_DECODE_OUTPUT_BIT_KHR = 0x02000000ULL;
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+static const VkFormatFeatureFlagBits2KHR VK_FORMAT_FEATURE_2_VIDEO_DECODE_DPB_BIT_KHR = 0x04000000ULL;
+#endif
+static const VkFormatFeatureFlagBits2KHR VK_FORMAT_FEATURE_2_ACCELERATION_STRUCTURE_VERTEX_BUFFER_BIT_KHR = 0x20000000ULL;
+static const VkFormatFeatureFlagBits2KHR VK_FORMAT_FEATURE_2_FRAGMENT_DENSITY_MAP_BIT_EXT = 0x01000000ULL;
+static const VkFormatFeatureFlagBits2KHR VK_FORMAT_FEATURE_2_FRAGMENT_SHADING_RATE_ATTACHMENT_BIT_KHR = 0x40000000ULL;
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+static const VkFormatFeatureFlagBits2KHR VK_FORMAT_FEATURE_2_VIDEO_ENCODE_INPUT_BIT_KHR = 0x08000000ULL;
+#endif
+#ifdef VK_ENABLE_BETA_EXTENSIONS
+static const VkFormatFeatureFlagBits2KHR VK_FORMAT_FEATURE_2_VIDEO_ENCODE_DPB_BIT_KHR = 0x10000000ULL;
+#endif
+
+typedef struct VkFormatProperties3KHR {
+    VkStructureType             sType;
+    void*                       pNext;
+    VkFormatFeatureFlags2KHR    linearTilingFeatures;
+    VkFormatFeatureFlags2KHR    optimalTilingFeatures;
+    VkFormatFeatureFlags2KHR    bufferFeatures;
+} VkFormatProperties3KHR;
+
+
+
+#define VK_KHR_maintenance4 1
+#define VK_KHR_MAINTENANCE_4_SPEC_VERSION 1
+#define VK_KHR_MAINTENANCE_4_EXTENSION_NAME "VK_KHR_maintenance4"
+typedef struct VkPhysicalDeviceMaintenance4FeaturesKHR {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           maintenance4;
+} VkPhysicalDeviceMaintenance4FeaturesKHR;
+
+typedef struct VkPhysicalDeviceMaintenance4PropertiesKHR {
+    VkStructureType    sType;
+    void*              pNext;
+    VkDeviceSize       maxBufferSize;
+} VkPhysicalDeviceMaintenance4PropertiesKHR;
+
+typedef struct VkDeviceBufferMemoryRequirementsKHR {
+    VkStructureType              sType;
+    const void*                  pNext;
+    const VkBufferCreateInfo*    pCreateInfo;
+} VkDeviceBufferMemoryRequirementsKHR;
+
+typedef struct VkDeviceImageMemoryRequirementsKHR {
+    VkStructureType             sType;
+    const void*                 pNext;
+    const VkImageCreateInfo*    pCreateInfo;
+    VkImageAspectFlagBits       planeAspect;
+} VkDeviceImageMemoryRequirementsKHR;
+
+typedef void (VKAPI_PTR *PFN_vkGetDeviceBufferMemoryRequirementsKHR)(VkDevice device, const VkDeviceBufferMemoryRequirementsKHR* pInfo, VkMemoryRequirements2* pMemoryRequirements);
+typedef void (VKAPI_PTR *PFN_vkGetDeviceImageMemoryRequirementsKHR)(VkDevice device, const VkDeviceImageMemoryRequirementsKHR* pInfo, VkMemoryRequirements2* pMemoryRequirements);
+typedef void (VKAPI_PTR *PFN_vkGetDeviceImageSparseMemoryRequirementsKHR)(VkDevice device, const VkDeviceImageMemoryRequirementsKHR* pInfo, uint32_t* pSparseMemoryRequirementCount, VkSparseImageMemoryRequirements2* pSparseMemoryRequirements);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR void VKAPI_CALL vkGetDeviceBufferMemoryRequirementsKHR(
+    VkDevice                                    device,
+    const VkDeviceBufferMemoryRequirementsKHR*  pInfo,
+    VkMemoryRequirements2*                      pMemoryRequirements);
+
+VKAPI_ATTR void VKAPI_CALL vkGetDeviceImageMemoryRequirementsKHR(
+    VkDevice                                    device,
+    const VkDeviceImageMemoryRequirementsKHR*   pInfo,
+    VkMemoryRequirements2*                      pMemoryRequirements);
+
+VKAPI_ATTR void VKAPI_CALL vkGetDeviceImageSparseMemoryRequirementsKHR(
+    VkDevice                                    device,
+    const VkDeviceImageMemoryRequirementsKHR*   pInfo,
+    uint32_t*                                   pSparseMemoryRequirementCount,
+    VkSparseImageMemoryRequirements2*           pSparseMemoryRequirements);
+#endif
+
+
+#define VK_EXT_debug_report 1
+VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkDebugReportCallbackEXT)
+#define VK_EXT_DEBUG_REPORT_SPEC_VERSION  10
+#define VK_EXT_DEBUG_REPORT_EXTENSION_NAME "VK_EXT_debug_report"
+
+typedef enum VkDebugReportObjectTypeEXT {
+    VK_DEBUG_REPORT_OBJECT_TYPE_UNKNOWN_EXT = 0,
+    VK_DEBUG_REPORT_OBJECT_TYPE_INSTANCE_EXT = 1,
+    VK_DEBUG_REPORT_OBJECT_TYPE_PHYSICAL_DEVICE_EXT = 2,
+    VK_DEBUG_REPORT_OBJECT_TYPE_DEVICE_EXT = 3,
+    VK_DEBUG_REPORT_OBJECT_TYPE_QUEUE_EXT = 4,
+    VK_DEBUG_REPORT_OBJECT_TYPE_SEMAPHORE_EXT = 5,
+    VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT = 6,
+    VK_DEBUG_REPORT_OBJECT_TYPE_FENCE_EXT = 7,
+    VK_DEBUG_REPORT_OBJECT_TYPE_DEVICE_MEMORY_EXT = 8,
+    VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_EXT = 9,
+    VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_EXT = 10,
+    VK_DEBUG_REPORT_OBJECT_TYPE_EVENT_EXT = 11,
+    VK_DEBUG_REPORT_OBJECT_TYPE_QUERY_POOL_EXT = 12,
+    VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_VIEW_EXT = 13,
+    VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_VIEW_EXT = 14,
+    VK_DEBUG_REPORT_OBJECT_TYPE_SHADER_MODULE_EXT = 15,
+    VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_CACHE_EXT = 16,
+    VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_LAYOUT_EXT = 17,
+    VK_DEBUG_REPORT_OBJECT_TYPE_RENDER_PASS_EXT = 18,
+    VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_EXT = 19,
+    VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT_EXT = 20,
+    VK_DEBUG_REPORT_OBJECT_TYPE_SAMPLER_EXT = 21,
+    VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_POOL_EXT = 22,
+    VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_SET_EXT = 23,
+    VK_DEBUG_REPORT_OBJECT_TYPE_FRAMEBUFFER_EXT = 24,
+    VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_POOL_EXT = 25,
+    VK_DEBUG_REPORT_OBJECT_TYPE_SURFACE_KHR_EXT = 26,
+    VK_DEBUG_REPORT_OBJECT_TYPE_SWAPCHAIN_KHR_EXT = 27,
+    VK_DEBUG_REPORT_OBJECT_TYPE_DEBUG_REPORT_CALLBACK_EXT_EXT = 28,
+    VK_DEBUG_REPORT_OBJECT_TYPE_DISPLAY_KHR_EXT = 29,
+    VK_DEBUG_REPORT_OBJECT_TYPE_DISPLAY_MODE_KHR_EXT = 30,
+    VK_DEBUG_REPORT_OBJECT_TYPE_VALIDATION_CACHE_EXT_EXT = 33,
+    VK_DEBUG_REPORT_OBJECT_TYPE_SAMPLER_YCBCR_CONVERSION_EXT = 1000156000,
+    VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_UPDATE_TEMPLATE_EXT = 1000085000,
+    VK_DEBUG_REPORT_OBJECT_TYPE_CU_MODULE_NVX_EXT = 1000029000,
+    VK_DEBUG_REPORT_OBJECT_TYPE_CU_FUNCTION_NVX_EXT = 1000029001,
+    VK_DEBUG_REPORT_OBJECT_TYPE_ACCELERATION_STRUCTURE_KHR_EXT = 1000150000,
+    VK_DEBUG_REPORT_OBJECT_TYPE_ACCELERATION_STRUCTURE_NV_EXT = 1000165000,
+    VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_COLLECTION_FUCHSIA_EXT = 1000366000,
+    VK_DEBUG_REPORT_OBJECT_TYPE_DEBUG_REPORT_EXT = VK_DEBUG_REPORT_OBJECT_TYPE_DEBUG_REPORT_CALLBACK_EXT_EXT,
+    VK_DEBUG_REPORT_OBJECT_TYPE_VALIDATION_CACHE_EXT = VK_DEBUG_REPORT_OBJECT_TYPE_VALIDATION_CACHE_EXT_EXT,
+    VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_UPDATE_TEMPLATE_KHR_EXT = VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_UPDATE_TEMPLATE_EXT,
+    VK_DEBUG_REPORT_OBJECT_TYPE_SAMPLER_YCBCR_CONVERSION_KHR_EXT = VK_DEBUG_REPORT_OBJECT_TYPE_SAMPLER_YCBCR_CONVERSION_EXT,
+    VK_DEBUG_REPORT_OBJECT_TYPE_MAX_ENUM_EXT = 0x7FFFFFFF
+} VkDebugReportObjectTypeEXT;
+
+typedef enum VkDebugReportFlagBitsEXT {
+    VK_DEBUG_REPORT_INFORMATION_BIT_EXT = 0x00000001,
+    VK_DEBUG_REPORT_WARNING_BIT_EXT = 0x00000002,
+    VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT = 0x00000004,
+    VK_DEBUG_REPORT_ERROR_BIT_EXT = 0x00000008,
+    VK_DEBUG_REPORT_DEBUG_BIT_EXT = 0x00000010,
+    VK_DEBUG_REPORT_FLAG_BITS_MAX_ENUM_EXT = 0x7FFFFFFF
+} VkDebugReportFlagBitsEXT;
+typedef VkFlags VkDebugReportFlagsEXT;
+typedef VkBool32 (VKAPI_PTR *PFN_vkDebugReportCallbackEXT)(
+    VkDebugReportFlagsEXT                       flags,
+    VkDebugReportObjectTypeEXT                  objectType,
+    uint64_t                                    object,
+    size_t                                      location,
+    int32_t                                     messageCode,
+    const char*                                 pLayerPrefix,
+    const char*                                 pMessage,
+    void*                                       pUserData);
+
+typedef struct VkDebugReportCallbackCreateInfoEXT {
+    VkStructureType                 sType;
+    const void*                     pNext;
+    VkDebugReportFlagsEXT           flags;
+    PFN_vkDebugReportCallbackEXT    pfnCallback;
+    void*                           pUserData;
+} VkDebugReportCallbackCreateInfoEXT;
+
+typedef VkResult (VKAPI_PTR *PFN_vkCreateDebugReportCallbackEXT)(VkInstance instance, const VkDebugReportCallbackCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugReportCallbackEXT* pCallback);
+typedef void (VKAPI_PTR *PFN_vkDestroyDebugReportCallbackEXT)(VkInstance instance, VkDebugReportCallbackEXT callback, const VkAllocationCallbacks* pAllocator);
+typedef void (VKAPI_PTR *PFN_vkDebugReportMessageEXT)(VkInstance instance, VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objectType, uint64_t object, size_t location, int32_t messageCode, const char* pLayerPrefix, const char* pMessage);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateDebugReportCallbackEXT(
+    VkInstance                                  instance,
+    const VkDebugReportCallbackCreateInfoEXT*   pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkDebugReportCallbackEXT*                   pCallback);
+
+VKAPI_ATTR void VKAPI_CALL vkDestroyDebugReportCallbackEXT(
+    VkInstance                                  instance,
+    VkDebugReportCallbackEXT                    callback,
+    const VkAllocationCallbacks*                pAllocator);
+
+VKAPI_ATTR void VKAPI_CALL vkDebugReportMessageEXT(
+    VkInstance                                  instance,
+    VkDebugReportFlagsEXT                       flags,
+    VkDebugReportObjectTypeEXT                  objectType,
+    uint64_t                                    object,
+    size_t                                      location,
+    int32_t                                     messageCode,
+    const char*                                 pLayerPrefix,
+    const char*                                 pMessage);
+#endif
+
+
+#define VK_NV_glsl_shader 1
+#define VK_NV_GLSL_SHADER_SPEC_VERSION    1
+#define VK_NV_GLSL_SHADER_EXTENSION_NAME  "VK_NV_glsl_shader"
+
+
+#define VK_EXT_depth_range_unrestricted 1
+#define VK_EXT_DEPTH_RANGE_UNRESTRICTED_SPEC_VERSION 1
+#define VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME "VK_EXT_depth_range_unrestricted"
+
+
+#define VK_IMG_filter_cubic 1
+#define VK_IMG_FILTER_CUBIC_SPEC_VERSION  1
+#define VK_IMG_FILTER_CUBIC_EXTENSION_NAME "VK_IMG_filter_cubic"
+
+
+#define VK_AMD_rasterization_order 1
+#define VK_AMD_RASTERIZATION_ORDER_SPEC_VERSION 1
+#define VK_AMD_RASTERIZATION_ORDER_EXTENSION_NAME "VK_AMD_rasterization_order"
+
+typedef enum VkRasterizationOrderAMD {
+    VK_RASTERIZATION_ORDER_STRICT_AMD = 0,
+    VK_RASTERIZATION_ORDER_RELAXED_AMD = 1,
+    VK_RASTERIZATION_ORDER_MAX_ENUM_AMD = 0x7FFFFFFF
+} VkRasterizationOrderAMD;
+typedef struct VkPipelineRasterizationStateRasterizationOrderAMD {
+    VkStructureType            sType;
+    const void*                pNext;
+    VkRasterizationOrderAMD    rasterizationOrder;
+} VkPipelineRasterizationStateRasterizationOrderAMD;
+
+
+
+#define VK_AMD_shader_trinary_minmax 1
+#define VK_AMD_SHADER_TRINARY_MINMAX_SPEC_VERSION 1
+#define VK_AMD_SHADER_TRINARY_MINMAX_EXTENSION_NAME "VK_AMD_shader_trinary_minmax"
+
+
+#define VK_AMD_shader_explicit_vertex_parameter 1
+#define VK_AMD_SHADER_EXPLICIT_VERTEX_PARAMETER_SPEC_VERSION 1
+#define VK_AMD_SHADER_EXPLICIT_VERTEX_PARAMETER_EXTENSION_NAME "VK_AMD_shader_explicit_vertex_parameter"
+
+
+#define VK_EXT_debug_marker 1
+#define VK_EXT_DEBUG_MARKER_SPEC_VERSION  4
+#define VK_EXT_DEBUG_MARKER_EXTENSION_NAME "VK_EXT_debug_marker"
+typedef struct VkDebugMarkerObjectNameInfoEXT {
+    VkStructureType               sType;
+    const void*                   pNext;
+    VkDebugReportObjectTypeEXT    objectType;
+    uint64_t                      object;
+    const char*                   pObjectName;
+} VkDebugMarkerObjectNameInfoEXT;
+
+typedef struct VkDebugMarkerObjectTagInfoEXT {
+    VkStructureType               sType;
+    const void*                   pNext;
+    VkDebugReportObjectTypeEXT    objectType;
+    uint64_t                      object;
+    uint64_t                      tagName;
+    size_t                        tagSize;
+    const void*                   pTag;
+} VkDebugMarkerObjectTagInfoEXT;
+
+typedef struct VkDebugMarkerMarkerInfoEXT {
+    VkStructureType    sType;
+    const void*        pNext;
+    const char*        pMarkerName;
+    float              color[4];
+} VkDebugMarkerMarkerInfoEXT;
+
+typedef VkResult (VKAPI_PTR *PFN_vkDebugMarkerSetObjectTagEXT)(VkDevice device, const VkDebugMarkerObjectTagInfoEXT* pTagInfo);
+typedef VkResult (VKAPI_PTR *PFN_vkDebugMarkerSetObjectNameEXT)(VkDevice device, const VkDebugMarkerObjectNameInfoEXT* pNameInfo);
+typedef void (VKAPI_PTR *PFN_vkCmdDebugMarkerBeginEXT)(VkCommandBuffer commandBuffer, const VkDebugMarkerMarkerInfoEXT* pMarkerInfo);
+typedef void (VKAPI_PTR *PFN_vkCmdDebugMarkerEndEXT)(VkCommandBuffer commandBuffer);
+typedef void (VKAPI_PTR *PFN_vkCmdDebugMarkerInsertEXT)(VkCommandBuffer commandBuffer, const VkDebugMarkerMarkerInfoEXT* pMarkerInfo);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkDebugMarkerSetObjectTagEXT(
+    VkDevice                                    device,
+    const VkDebugMarkerObjectTagInfoEXT*        pTagInfo);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkDebugMarkerSetObjectNameEXT(
+    VkDevice                                    device,
+    const VkDebugMarkerObjectNameInfoEXT*       pNameInfo);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdDebugMarkerBeginEXT(
+    VkCommandBuffer                             commandBuffer,
+    const VkDebugMarkerMarkerInfoEXT*           pMarkerInfo);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdDebugMarkerEndEXT(
+    VkCommandBuffer                             commandBuffer);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdDebugMarkerInsertEXT(
+    VkCommandBuffer                             commandBuffer,
+    const VkDebugMarkerMarkerInfoEXT*           pMarkerInfo);
+#endif
+
+
+#define VK_AMD_gcn_shader 1
+#define VK_AMD_GCN_SHADER_SPEC_VERSION    1
+#define VK_AMD_GCN_SHADER_EXTENSION_NAME  "VK_AMD_gcn_shader"
+
+
+#define VK_NV_dedicated_allocation 1
+#define VK_NV_DEDICATED_ALLOCATION_SPEC_VERSION 1
+#define VK_NV_DEDICATED_ALLOCATION_EXTENSION_NAME "VK_NV_dedicated_allocation"
+typedef struct VkDedicatedAllocationImageCreateInfoNV {
+    VkStructureType    sType;
+    const void*        pNext;
+    VkBool32           dedicatedAllocation;
+} VkDedicatedAllocationImageCreateInfoNV;
+
+typedef struct VkDedicatedAllocationBufferCreateInfoNV {
+    VkStructureType    sType;
+    const void*        pNext;
+    VkBool32           dedicatedAllocation;
+} VkDedicatedAllocationBufferCreateInfoNV;
+
+typedef struct VkDedicatedAllocationMemoryAllocateInfoNV {
+    VkStructureType    sType;
+    const void*        pNext;
+    VkImage            image;
+    VkBuffer           buffer;
+} VkDedicatedAllocationMemoryAllocateInfoNV;
+
+
+
+#define VK_EXT_transform_feedback 1
+#define VK_EXT_TRANSFORM_FEEDBACK_SPEC_VERSION 1
+#define VK_EXT_TRANSFORM_FEEDBACK_EXTENSION_NAME "VK_EXT_transform_feedback"
+typedef VkFlags VkPipelineRasterizationStateStreamCreateFlagsEXT;
+typedef struct VkPhysicalDeviceTransformFeedbackFeaturesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           transformFeedback;
+    VkBool32           geometryStreams;
+} VkPhysicalDeviceTransformFeedbackFeaturesEXT;
+
+typedef struct VkPhysicalDeviceTransformFeedbackPropertiesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    uint32_t           maxTransformFeedbackStreams;
+    uint32_t           maxTransformFeedbackBuffers;
+    VkDeviceSize       maxTransformFeedbackBufferSize;
+    uint32_t           maxTransformFeedbackStreamDataSize;
+    uint32_t           maxTransformFeedbackBufferDataSize;
+    uint32_t           maxTransformFeedbackBufferDataStride;
+    VkBool32           transformFeedbackQueries;
+    VkBool32           transformFeedbackStreamsLinesTriangles;
+    VkBool32           transformFeedbackRasterizationStreamSelect;
+    VkBool32           transformFeedbackDraw;
+} VkPhysicalDeviceTransformFeedbackPropertiesEXT;
+
+typedef struct VkPipelineRasterizationStateStreamCreateInfoEXT {
+    VkStructureType                                     sType;
+    const void*                                         pNext;
+    VkPipelineRasterizationStateStreamCreateFlagsEXT    flags;
+    uint32_t                                            rasterizationStream;
+} VkPipelineRasterizationStateStreamCreateInfoEXT;
+
+typedef void (VKAPI_PTR *PFN_vkCmdBindTransformFeedbackBuffersEXT)(VkCommandBuffer commandBuffer, uint32_t firstBinding, uint32_t bindingCount, const VkBuffer* pBuffers, const VkDeviceSize* pOffsets, const VkDeviceSize* pSizes);
+typedef void (VKAPI_PTR *PFN_vkCmdBeginTransformFeedbackEXT)(VkCommandBuffer commandBuffer, uint32_t firstCounterBuffer, uint32_t counterBufferCount, const VkBuffer* pCounterBuffers, const VkDeviceSize* pCounterBufferOffsets);
+typedef void (VKAPI_PTR *PFN_vkCmdEndTransformFeedbackEXT)(VkCommandBuffer commandBuffer, uint32_t firstCounterBuffer, uint32_t counterBufferCount, const VkBuffer* pCounterBuffers, const VkDeviceSize* pCounterBufferOffsets);
+typedef void (VKAPI_PTR *PFN_vkCmdBeginQueryIndexedEXT)(VkCommandBuffer commandBuffer, VkQueryPool queryPool, uint32_t query, VkQueryControlFlags flags, uint32_t index);
+typedef void (VKAPI_PTR *PFN_vkCmdEndQueryIndexedEXT)(VkCommandBuffer commandBuffer, VkQueryPool queryPool, uint32_t query, uint32_t index);
+typedef void (VKAPI_PTR *PFN_vkCmdDrawIndirectByteCountEXT)(VkCommandBuffer commandBuffer, uint32_t instanceCount, uint32_t firstInstance, VkBuffer counterBuffer, VkDeviceSize counterBufferOffset, uint32_t counterOffset, uint32_t vertexStride);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR void VKAPI_CALL vkCmdBindTransformFeedbackBuffersEXT(
+    VkCommandBuffer                             commandBuffer,
+    uint32_t                                    firstBinding,
+    uint32_t                                    bindingCount,
+    const VkBuffer*                             pBuffers,
+    const VkDeviceSize*                         pOffsets,
+    const VkDeviceSize*                         pSizes);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdBeginTransformFeedbackEXT(
+    VkCommandBuffer                             commandBuffer,
+    uint32_t                                    firstCounterBuffer,
+    uint32_t                                    counterBufferCount,
+    const VkBuffer*                             pCounterBuffers,
+    const VkDeviceSize*                         pCounterBufferOffsets);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdEndTransformFeedbackEXT(
+    VkCommandBuffer                             commandBuffer,
+    uint32_t                                    firstCounterBuffer,
+    uint32_t                                    counterBufferCount,
+    const VkBuffer*                             pCounterBuffers,
+    const VkDeviceSize*                         pCounterBufferOffsets);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdBeginQueryIndexedEXT(
+    VkCommandBuffer                             commandBuffer,
+    VkQueryPool                                 queryPool,
+    uint32_t                                    query,
+    VkQueryControlFlags                         flags,
+    uint32_t                                    index);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdEndQueryIndexedEXT(
+    VkCommandBuffer                             commandBuffer,
+    VkQueryPool                                 queryPool,
+    uint32_t                                    query,
+    uint32_t                                    index);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdDrawIndirectByteCountEXT(
+    VkCommandBuffer                             commandBuffer,
+    uint32_t                                    instanceCount,
+    uint32_t                                    firstInstance,
+    VkBuffer                                    counterBuffer,
+    VkDeviceSize                                counterBufferOffset,
+    uint32_t                                    counterOffset,
+    uint32_t                                    vertexStride);
+#endif
+
+
+#define VK_NVX_binary_import 1
+VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkCuModuleNVX)
+VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkCuFunctionNVX)
+#define VK_NVX_BINARY_IMPORT_SPEC_VERSION 1
+#define VK_NVX_BINARY_IMPORT_EXTENSION_NAME "VK_NVX_binary_import"
+typedef struct VkCuModuleCreateInfoNVX {
+    VkStructureType    sType;
+    const void*        pNext;
+    size_t             dataSize;
+    const void*        pData;
+} VkCuModuleCreateInfoNVX;
+
+typedef struct VkCuFunctionCreateInfoNVX {
+    VkStructureType    sType;
+    const void*        pNext;
+    VkCuModuleNVX      module;
+    const char*        pName;
+} VkCuFunctionCreateInfoNVX;
+
+typedef struct VkCuLaunchInfoNVX {
+    VkStructureType        sType;
+    const void*            pNext;
+    VkCuFunctionNVX        function;
+    uint32_t               gridDimX;
+    uint32_t               gridDimY;
+    uint32_t               gridDimZ;
+    uint32_t               blockDimX;
+    uint32_t               blockDimY;
+    uint32_t               blockDimZ;
+    uint32_t               sharedMemBytes;
+    size_t                 paramCount;
+    const void* const *    pParams;
+    size_t                 extraCount;
+    const void* const *    pExtras;
+} VkCuLaunchInfoNVX;
+
+typedef VkResult (VKAPI_PTR *PFN_vkCreateCuModuleNVX)(VkDevice device, const VkCuModuleCreateInfoNVX* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkCuModuleNVX* pModule);
+typedef VkResult (VKAPI_PTR *PFN_vkCreateCuFunctionNVX)(VkDevice device, const VkCuFunctionCreateInfoNVX* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkCuFunctionNVX* pFunction);
+typedef void (VKAPI_PTR *PFN_vkDestroyCuModuleNVX)(VkDevice device, VkCuModuleNVX module, const VkAllocationCallbacks* pAllocator);
+typedef void (VKAPI_PTR *PFN_vkDestroyCuFunctionNVX)(VkDevice device, VkCuFunctionNVX function, const VkAllocationCallbacks* pAllocator);
+typedef void (VKAPI_PTR *PFN_vkCmdCuLaunchKernelNVX)(VkCommandBuffer commandBuffer, const VkCuLaunchInfoNVX* pLaunchInfo);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateCuModuleNVX(
+    VkDevice                                    device,
+    const VkCuModuleCreateInfoNVX*              pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkCuModuleNVX*                              pModule);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateCuFunctionNVX(
+    VkDevice                                    device,
+    const VkCuFunctionCreateInfoNVX*            pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkCuFunctionNVX*                            pFunction);
+
+VKAPI_ATTR void VKAPI_CALL vkDestroyCuModuleNVX(
+    VkDevice                                    device,
+    VkCuModuleNVX                               module,
+    const VkAllocationCallbacks*                pAllocator);
+
+VKAPI_ATTR void VKAPI_CALL vkDestroyCuFunctionNVX(
+    VkDevice                                    device,
+    VkCuFunctionNVX                             function,
+    const VkAllocationCallbacks*                pAllocator);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdCuLaunchKernelNVX(
+    VkCommandBuffer                             commandBuffer,
+    const VkCuLaunchInfoNVX*                    pLaunchInfo);
+#endif
+
+
+#define VK_NVX_image_view_handle 1
+#define VK_NVX_IMAGE_VIEW_HANDLE_SPEC_VERSION 2
+#define VK_NVX_IMAGE_VIEW_HANDLE_EXTENSION_NAME "VK_NVX_image_view_handle"
+typedef struct VkImageViewHandleInfoNVX {
+    VkStructureType     sType;
+    const void*         pNext;
+    VkImageView         imageView;
+    VkDescriptorType    descriptorType;
+    VkSampler           sampler;
+} VkImageViewHandleInfoNVX;
+
+typedef struct VkImageViewAddressPropertiesNVX {
+    VkStructureType    sType;
+    void*              pNext;
+    VkDeviceAddress    deviceAddress;
+    VkDeviceSize       size;
+} VkImageViewAddressPropertiesNVX;
+
+typedef uint32_t (VKAPI_PTR *PFN_vkGetImageViewHandleNVX)(VkDevice device, const VkImageViewHandleInfoNVX* pInfo);
+typedef VkResult (VKAPI_PTR *PFN_vkGetImageViewAddressNVX)(VkDevice device, VkImageView imageView, VkImageViewAddressPropertiesNVX* pProperties);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR uint32_t VKAPI_CALL vkGetImageViewHandleNVX(
+    VkDevice                                    device,
+    const VkImageViewHandleInfoNVX*             pInfo);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetImageViewAddressNVX(
+    VkDevice                                    device,
+    VkImageView                                 imageView,
+    VkImageViewAddressPropertiesNVX*            pProperties);
+#endif
+
+
+#define VK_AMD_draw_indirect_count 1
+#define VK_AMD_DRAW_INDIRECT_COUNT_SPEC_VERSION 2
+#define VK_AMD_DRAW_INDIRECT_COUNT_EXTENSION_NAME "VK_AMD_draw_indirect_count"
+typedef void (VKAPI_PTR *PFN_vkCmdDrawIndirectCountAMD)(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset, VkBuffer countBuffer, VkDeviceSize countBufferOffset, uint32_t maxDrawCount, uint32_t stride);
+typedef void (VKAPI_PTR *PFN_vkCmdDrawIndexedIndirectCountAMD)(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset, VkBuffer countBuffer, VkDeviceSize countBufferOffset, uint32_t maxDrawCount, uint32_t stride);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR void VKAPI_CALL vkCmdDrawIndirectCountAMD(
+    VkCommandBuffer                             commandBuffer,
+    VkBuffer                                    buffer,
+    VkDeviceSize                                offset,
+    VkBuffer                                    countBuffer,
+    VkDeviceSize                                countBufferOffset,
+    uint32_t                                    maxDrawCount,
+    uint32_t                                    stride);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdDrawIndexedIndirectCountAMD(
+    VkCommandBuffer                             commandBuffer,
+    VkBuffer                                    buffer,
+    VkDeviceSize                                offset,
+    VkBuffer                                    countBuffer,
+    VkDeviceSize                                countBufferOffset,
+    uint32_t                                    maxDrawCount,
+    uint32_t                                    stride);
+#endif
+
+
+#define VK_AMD_negative_viewport_height 1
+#define VK_AMD_NEGATIVE_VIEWPORT_HEIGHT_SPEC_VERSION 1
+#define VK_AMD_NEGATIVE_VIEWPORT_HEIGHT_EXTENSION_NAME "VK_AMD_negative_viewport_height"
+
+
+#define VK_AMD_gpu_shader_half_float 1
+#define VK_AMD_GPU_SHADER_HALF_FLOAT_SPEC_VERSION 2
+#define VK_AMD_GPU_SHADER_HALF_FLOAT_EXTENSION_NAME "VK_AMD_gpu_shader_half_float"
+
+
+#define VK_AMD_shader_ballot 1
+#define VK_AMD_SHADER_BALLOT_SPEC_VERSION 1
+#define VK_AMD_SHADER_BALLOT_EXTENSION_NAME "VK_AMD_shader_ballot"
+
+
+#define VK_AMD_texture_gather_bias_lod 1
+#define VK_AMD_TEXTURE_GATHER_BIAS_LOD_SPEC_VERSION 1
+#define VK_AMD_TEXTURE_GATHER_BIAS_LOD_EXTENSION_NAME "VK_AMD_texture_gather_bias_lod"
+typedef struct VkTextureLODGatherFormatPropertiesAMD {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           supportsTextureGatherLODBiasAMD;
+} VkTextureLODGatherFormatPropertiesAMD;
+
+
+
+#define VK_AMD_shader_info 1
+#define VK_AMD_SHADER_INFO_SPEC_VERSION   1
+#define VK_AMD_SHADER_INFO_EXTENSION_NAME "VK_AMD_shader_info"
+
+typedef enum VkShaderInfoTypeAMD {
+    VK_SHADER_INFO_TYPE_STATISTICS_AMD = 0,
+    VK_SHADER_INFO_TYPE_BINARY_AMD = 1,
+    VK_SHADER_INFO_TYPE_DISASSEMBLY_AMD = 2,
+    VK_SHADER_INFO_TYPE_MAX_ENUM_AMD = 0x7FFFFFFF
+} VkShaderInfoTypeAMD;
+typedef struct VkShaderResourceUsageAMD {
+    uint32_t    numUsedVgprs;
+    uint32_t    numUsedSgprs;
+    uint32_t    ldsSizePerLocalWorkGroup;
+    size_t      ldsUsageSizeInBytes;
+    size_t      scratchMemUsageInBytes;
+} VkShaderResourceUsageAMD;
+
+typedef struct VkShaderStatisticsInfoAMD {
+    VkShaderStageFlags          shaderStageMask;
+    VkShaderResourceUsageAMD    resourceUsage;
+    uint32_t                    numPhysicalVgprs;
+    uint32_t                    numPhysicalSgprs;
+    uint32_t                    numAvailableVgprs;
+    uint32_t                    numAvailableSgprs;
+    uint32_t                    computeWorkGroupSize[3];
+} VkShaderStatisticsInfoAMD;
+
+typedef VkResult (VKAPI_PTR *PFN_vkGetShaderInfoAMD)(VkDevice device, VkPipeline pipeline, VkShaderStageFlagBits shaderStage, VkShaderInfoTypeAMD infoType, size_t* pInfoSize, void* pInfo);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkGetShaderInfoAMD(
+    VkDevice                                    device,
+    VkPipeline                                  pipeline,
+    VkShaderStageFlagBits                       shaderStage,
+    VkShaderInfoTypeAMD                         infoType,
+    size_t*                                     pInfoSize,
+    void*                                       pInfo);
+#endif
+
+
+#define VK_AMD_shader_image_load_store_lod 1
+#define VK_AMD_SHADER_IMAGE_LOAD_STORE_LOD_SPEC_VERSION 1
+#define VK_AMD_SHADER_IMAGE_LOAD_STORE_LOD_EXTENSION_NAME "VK_AMD_shader_image_load_store_lod"
+
+
+#define VK_NV_corner_sampled_image 1
+#define VK_NV_CORNER_SAMPLED_IMAGE_SPEC_VERSION 2
+#define VK_NV_CORNER_SAMPLED_IMAGE_EXTENSION_NAME "VK_NV_corner_sampled_image"
+typedef struct VkPhysicalDeviceCornerSampledImageFeaturesNV {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           cornerSampledImage;
+} VkPhysicalDeviceCornerSampledImageFeaturesNV;
+
+
+
+#define VK_IMG_format_pvrtc 1
+#define VK_IMG_FORMAT_PVRTC_SPEC_VERSION  1
+#define VK_IMG_FORMAT_PVRTC_EXTENSION_NAME "VK_IMG_format_pvrtc"
+
+
+#define VK_NV_external_memory_capabilities 1
+#define VK_NV_EXTERNAL_MEMORY_CAPABILITIES_SPEC_VERSION 1
+#define VK_NV_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME "VK_NV_external_memory_capabilities"
+
+typedef enum VkExternalMemoryHandleTypeFlagBitsNV {
+    VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT_NV = 0x00000001,
+    VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_KMT_BIT_NV = 0x00000002,
+    VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D11_IMAGE_BIT_NV = 0x00000004,
+    VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D11_IMAGE_KMT_BIT_NV = 0x00000008,
+    VK_EXTERNAL_MEMORY_HANDLE_TYPE_FLAG_BITS_MAX_ENUM_NV = 0x7FFFFFFF
+} VkExternalMemoryHandleTypeFlagBitsNV;
+typedef VkFlags VkExternalMemoryHandleTypeFlagsNV;
+
+typedef enum VkExternalMemoryFeatureFlagBitsNV {
+    VK_EXTERNAL_MEMORY_FEATURE_DEDICATED_ONLY_BIT_NV = 0x00000001,
+    VK_EXTERNAL_MEMORY_FEATURE_EXPORTABLE_BIT_NV = 0x00000002,
+    VK_EXTERNAL_MEMORY_FEATURE_IMPORTABLE_BIT_NV = 0x00000004,
+    VK_EXTERNAL_MEMORY_FEATURE_FLAG_BITS_MAX_ENUM_NV = 0x7FFFFFFF
+} VkExternalMemoryFeatureFlagBitsNV;
+typedef VkFlags VkExternalMemoryFeatureFlagsNV;
+typedef struct VkExternalImageFormatPropertiesNV {
+    VkImageFormatProperties              imageFormatProperties;
+    VkExternalMemoryFeatureFlagsNV       externalMemoryFeatures;
+    VkExternalMemoryHandleTypeFlagsNV    exportFromImportedHandleTypes;
+    VkExternalMemoryHandleTypeFlagsNV    compatibleHandleTypes;
+} VkExternalImageFormatPropertiesNV;
+
+typedef VkResult (VKAPI_PTR *PFN_vkGetPhysicalDeviceExternalImageFormatPropertiesNV)(VkPhysicalDevice physicalDevice, VkFormat format, VkImageType type, VkImageTiling tiling, VkImageUsageFlags usage, VkImageCreateFlags flags, VkExternalMemoryHandleTypeFlagsNV externalHandleType, VkExternalImageFormatPropertiesNV* pExternalImageFormatProperties);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceExternalImageFormatPropertiesNV(
+    VkPhysicalDevice                            physicalDevice,
+    VkFormat                                    format,
+    VkImageType                                 type,
+    VkImageTiling                               tiling,
+    VkImageUsageFlags                           usage,
+    VkImageCreateFlags                          flags,
+    VkExternalMemoryHandleTypeFlagsNV           externalHandleType,
+    VkExternalImageFormatPropertiesNV*          pExternalImageFormatProperties);
+#endif
+
+
+#define VK_NV_external_memory 1
+#define VK_NV_EXTERNAL_MEMORY_SPEC_VERSION 1
+#define VK_NV_EXTERNAL_MEMORY_EXTENSION_NAME "VK_NV_external_memory"
+typedef struct VkExternalMemoryImageCreateInfoNV {
+    VkStructureType                      sType;
+    const void*                          pNext;
+    VkExternalMemoryHandleTypeFlagsNV    handleTypes;
+} VkExternalMemoryImageCreateInfoNV;
+
+typedef struct VkExportMemoryAllocateInfoNV {
+    VkStructureType                      sType;
+    const void*                          pNext;
+    VkExternalMemoryHandleTypeFlagsNV    handleTypes;
+} VkExportMemoryAllocateInfoNV;
+
+
+
+#define VK_EXT_validation_flags 1
+#define VK_EXT_VALIDATION_FLAGS_SPEC_VERSION 2
+#define VK_EXT_VALIDATION_FLAGS_EXTENSION_NAME "VK_EXT_validation_flags"
+
+typedef enum VkValidationCheckEXT {
+    VK_VALIDATION_CHECK_ALL_EXT = 0,
+    VK_VALIDATION_CHECK_SHADERS_EXT = 1,
+    VK_VALIDATION_CHECK_MAX_ENUM_EXT = 0x7FFFFFFF
+} VkValidationCheckEXT;
+typedef struct VkValidationFlagsEXT {
+    VkStructureType                sType;
+    const void*                    pNext;
+    uint32_t                       disabledValidationCheckCount;
+    const VkValidationCheckEXT*    pDisabledValidationChecks;
+} VkValidationFlagsEXT;
+
+
+
+#define VK_EXT_shader_subgroup_ballot 1
+#define VK_EXT_SHADER_SUBGROUP_BALLOT_SPEC_VERSION 1
+#define VK_EXT_SHADER_SUBGROUP_BALLOT_EXTENSION_NAME "VK_EXT_shader_subgroup_ballot"
+
+
+#define VK_EXT_shader_subgroup_vote 1
+#define VK_EXT_SHADER_SUBGROUP_VOTE_SPEC_VERSION 1
+#define VK_EXT_SHADER_SUBGROUP_VOTE_EXTENSION_NAME "VK_EXT_shader_subgroup_vote"
+
+
+#define VK_EXT_texture_compression_astc_hdr 1
+#define VK_EXT_TEXTURE_COMPRESSION_ASTC_HDR_SPEC_VERSION 1
+#define VK_EXT_TEXTURE_COMPRESSION_ASTC_HDR_EXTENSION_NAME "VK_EXT_texture_compression_astc_hdr"
+typedef struct VkPhysicalDeviceTextureCompressionASTCHDRFeaturesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           textureCompressionASTC_HDR;
+} VkPhysicalDeviceTextureCompressionASTCHDRFeaturesEXT;
+
+
+
+#define VK_EXT_astc_decode_mode 1
+#define VK_EXT_ASTC_DECODE_MODE_SPEC_VERSION 1
+#define VK_EXT_ASTC_DECODE_MODE_EXTENSION_NAME "VK_EXT_astc_decode_mode"
+typedef struct VkImageViewASTCDecodeModeEXT {
+    VkStructureType    sType;
+    const void*        pNext;
+    VkFormat           decodeMode;
+} VkImageViewASTCDecodeModeEXT;
+
+typedef struct VkPhysicalDeviceASTCDecodeFeaturesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           decodeModeSharedExponent;
+} VkPhysicalDeviceASTCDecodeFeaturesEXT;
+
+
+
+#define VK_EXT_conditional_rendering 1
+#define VK_EXT_CONDITIONAL_RENDERING_SPEC_VERSION 2
+#define VK_EXT_CONDITIONAL_RENDERING_EXTENSION_NAME "VK_EXT_conditional_rendering"
+
+typedef enum VkConditionalRenderingFlagBitsEXT {
+    VK_CONDITIONAL_RENDERING_INVERTED_BIT_EXT = 0x00000001,
+    VK_CONDITIONAL_RENDERING_FLAG_BITS_MAX_ENUM_EXT = 0x7FFFFFFF
+} VkConditionalRenderingFlagBitsEXT;
+typedef VkFlags VkConditionalRenderingFlagsEXT;
+typedef struct VkConditionalRenderingBeginInfoEXT {
+    VkStructureType                   sType;
+    const void*                       pNext;
+    VkBuffer                          buffer;
+    VkDeviceSize                      offset;
+    VkConditionalRenderingFlagsEXT    flags;
+} VkConditionalRenderingBeginInfoEXT;
+
+typedef struct VkPhysicalDeviceConditionalRenderingFeaturesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           conditionalRendering;
+    VkBool32           inheritedConditionalRendering;
+} VkPhysicalDeviceConditionalRenderingFeaturesEXT;
+
+typedef struct VkCommandBufferInheritanceConditionalRenderingInfoEXT {
+    VkStructureType    sType;
+    const void*        pNext;
+    VkBool32           conditionalRenderingEnable;
+} VkCommandBufferInheritanceConditionalRenderingInfoEXT;
+
+typedef void (VKAPI_PTR *PFN_vkCmdBeginConditionalRenderingEXT)(VkCommandBuffer commandBuffer, const VkConditionalRenderingBeginInfoEXT* pConditionalRenderingBegin);
+typedef void (VKAPI_PTR *PFN_vkCmdEndConditionalRenderingEXT)(VkCommandBuffer commandBuffer);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR void VKAPI_CALL vkCmdBeginConditionalRenderingEXT(
+    VkCommandBuffer                             commandBuffer,
+    const VkConditionalRenderingBeginInfoEXT*   pConditionalRenderingBegin);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdEndConditionalRenderingEXT(
+    VkCommandBuffer                             commandBuffer);
+#endif
+
+
+#define VK_NV_clip_space_w_scaling 1
+#define VK_NV_CLIP_SPACE_W_SCALING_SPEC_VERSION 1
+#define VK_NV_CLIP_SPACE_W_SCALING_EXTENSION_NAME "VK_NV_clip_space_w_scaling"
+typedef struct VkViewportWScalingNV {
+    float    xcoeff;
+    float    ycoeff;
+} VkViewportWScalingNV;
+
+typedef struct VkPipelineViewportWScalingStateCreateInfoNV {
+    VkStructureType                sType;
+    const void*                    pNext;
+    VkBool32                       viewportWScalingEnable;
+    uint32_t                       viewportCount;
+    const VkViewportWScalingNV*    pViewportWScalings;
+} VkPipelineViewportWScalingStateCreateInfoNV;
+
+typedef void (VKAPI_PTR *PFN_vkCmdSetViewportWScalingNV)(VkCommandBuffer commandBuffer, uint32_t firstViewport, uint32_t viewportCount, const VkViewportWScalingNV* pViewportWScalings);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR void VKAPI_CALL vkCmdSetViewportWScalingNV(
+    VkCommandBuffer                             commandBuffer,
+    uint32_t                                    firstViewport,
+    uint32_t                                    viewportCount,
+    const VkViewportWScalingNV*                 pViewportWScalings);
+#endif
+
+
+#define VK_EXT_direct_mode_display 1
+#define VK_EXT_DIRECT_MODE_DISPLAY_SPEC_VERSION 1
+#define VK_EXT_DIRECT_MODE_DISPLAY_EXTENSION_NAME "VK_EXT_direct_mode_display"
+typedef VkResult (VKAPI_PTR *PFN_vkReleaseDisplayEXT)(VkPhysicalDevice physicalDevice, VkDisplayKHR display);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkReleaseDisplayEXT(
+    VkPhysicalDevice                            physicalDevice,
+    VkDisplayKHR                                display);
+#endif
+
+
+#define VK_EXT_display_surface_counter 1
+#define VK_EXT_DISPLAY_SURFACE_COUNTER_SPEC_VERSION 1
+#define VK_EXT_DISPLAY_SURFACE_COUNTER_EXTENSION_NAME "VK_EXT_display_surface_counter"
+
+typedef enum VkSurfaceCounterFlagBitsEXT {
+    VK_SURFACE_COUNTER_VBLANK_BIT_EXT = 0x00000001,
+    VK_SURFACE_COUNTER_VBLANK_EXT = VK_SURFACE_COUNTER_VBLANK_BIT_EXT,
+    VK_SURFACE_COUNTER_FLAG_BITS_MAX_ENUM_EXT = 0x7FFFFFFF
+} VkSurfaceCounterFlagBitsEXT;
+typedef VkFlags VkSurfaceCounterFlagsEXT;
+typedef struct VkSurfaceCapabilities2EXT {
+    VkStructureType                  sType;
+    void*                            pNext;
+    uint32_t                         minImageCount;
+    uint32_t                         maxImageCount;
+    VkExtent2D                       currentExtent;
+    VkExtent2D                       minImageExtent;
+    VkExtent2D                       maxImageExtent;
+    uint32_t                         maxImageArrayLayers;
+    VkSurfaceTransformFlagsKHR       supportedTransforms;
+    VkSurfaceTransformFlagBitsKHR    currentTransform;
+    VkCompositeAlphaFlagsKHR         supportedCompositeAlpha;
+    VkImageUsageFlags                supportedUsageFlags;
+    VkSurfaceCounterFlagsEXT         supportedSurfaceCounters;
+} VkSurfaceCapabilities2EXT;
+
+typedef VkResult (VKAPI_PTR *PFN_vkGetPhysicalDeviceSurfaceCapabilities2EXT)(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, VkSurfaceCapabilities2EXT* pSurfaceCapabilities);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceSurfaceCapabilities2EXT(
+    VkPhysicalDevice                            physicalDevice,
+    VkSurfaceKHR                                surface,
+    VkSurfaceCapabilities2EXT*                  pSurfaceCapabilities);
+#endif
+
+
+#define VK_EXT_display_control 1
+#define VK_EXT_DISPLAY_CONTROL_SPEC_VERSION 1
+#define VK_EXT_DISPLAY_CONTROL_EXTENSION_NAME "VK_EXT_display_control"
+
+typedef enum VkDisplayPowerStateEXT {
+    VK_DISPLAY_POWER_STATE_OFF_EXT = 0,
+    VK_DISPLAY_POWER_STATE_SUSPEND_EXT = 1,
+    VK_DISPLAY_POWER_STATE_ON_EXT = 2,
+    VK_DISPLAY_POWER_STATE_MAX_ENUM_EXT = 0x7FFFFFFF
+} VkDisplayPowerStateEXT;
+
+typedef enum VkDeviceEventTypeEXT {
+    VK_DEVICE_EVENT_TYPE_DISPLAY_HOTPLUG_EXT = 0,
+    VK_DEVICE_EVENT_TYPE_MAX_ENUM_EXT = 0x7FFFFFFF
+} VkDeviceEventTypeEXT;
+
+typedef enum VkDisplayEventTypeEXT {
+    VK_DISPLAY_EVENT_TYPE_FIRST_PIXEL_OUT_EXT = 0,
+    VK_DISPLAY_EVENT_TYPE_MAX_ENUM_EXT = 0x7FFFFFFF
+} VkDisplayEventTypeEXT;
+typedef struct VkDisplayPowerInfoEXT {
+    VkStructureType           sType;
+    const void*               pNext;
+    VkDisplayPowerStateEXT    powerState;
+} VkDisplayPowerInfoEXT;
+
+typedef struct VkDeviceEventInfoEXT {
+    VkStructureType         sType;
+    const void*             pNext;
+    VkDeviceEventTypeEXT    deviceEvent;
+} VkDeviceEventInfoEXT;
+
+typedef struct VkDisplayEventInfoEXT {
+    VkStructureType          sType;
+    const void*              pNext;
+    VkDisplayEventTypeEXT    displayEvent;
+} VkDisplayEventInfoEXT;
+
+typedef struct VkSwapchainCounterCreateInfoEXT {
+    VkStructureType             sType;
+    const void*                 pNext;
+    VkSurfaceCounterFlagsEXT    surfaceCounters;
+} VkSwapchainCounterCreateInfoEXT;
+
+typedef VkResult (VKAPI_PTR *PFN_vkDisplayPowerControlEXT)(VkDevice device, VkDisplayKHR display, const VkDisplayPowerInfoEXT* pDisplayPowerInfo);
+typedef VkResult (VKAPI_PTR *PFN_vkRegisterDeviceEventEXT)(VkDevice device, const VkDeviceEventInfoEXT* pDeviceEventInfo, const VkAllocationCallbacks* pAllocator, VkFence* pFence);
+typedef VkResult (VKAPI_PTR *PFN_vkRegisterDisplayEventEXT)(VkDevice device, VkDisplayKHR display, const VkDisplayEventInfoEXT* pDisplayEventInfo, const VkAllocationCallbacks* pAllocator, VkFence* pFence);
+typedef VkResult (VKAPI_PTR *PFN_vkGetSwapchainCounterEXT)(VkDevice device, VkSwapchainKHR swapchain, VkSurfaceCounterFlagBitsEXT counter, uint64_t* pCounterValue);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkDisplayPowerControlEXT(
+    VkDevice                                    device,
+    VkDisplayKHR                                display,
+    const VkDisplayPowerInfoEXT*                pDisplayPowerInfo);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkRegisterDeviceEventEXT(
+    VkDevice                                    device,
+    const VkDeviceEventInfoEXT*                 pDeviceEventInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkFence*                                    pFence);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkRegisterDisplayEventEXT(
+    VkDevice                                    device,
+    VkDisplayKHR                                display,
+    const VkDisplayEventInfoEXT*                pDisplayEventInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkFence*                                    pFence);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetSwapchainCounterEXT(
+    VkDevice                                    device,
+    VkSwapchainKHR                              swapchain,
+    VkSurfaceCounterFlagBitsEXT                 counter,
+    uint64_t*                                   pCounterValue);
+#endif
+
+
+#define VK_GOOGLE_display_timing 1
+#define VK_GOOGLE_DISPLAY_TIMING_SPEC_VERSION 1
+#define VK_GOOGLE_DISPLAY_TIMING_EXTENSION_NAME "VK_GOOGLE_display_timing"
+typedef struct VkRefreshCycleDurationGOOGLE {
+    uint64_t    refreshDuration;
+} VkRefreshCycleDurationGOOGLE;
+
+typedef struct VkPastPresentationTimingGOOGLE {
+    uint32_t    presentID;
+    uint64_t    desiredPresentTime;
+    uint64_t    actualPresentTime;
+    uint64_t    earliestPresentTime;
+    uint64_t    presentMargin;
+} VkPastPresentationTimingGOOGLE;
+
+typedef struct VkPresentTimeGOOGLE {
+    uint32_t    presentID;
+    uint64_t    desiredPresentTime;
+} VkPresentTimeGOOGLE;
+
+typedef struct VkPresentTimesInfoGOOGLE {
+    VkStructureType               sType;
+    const void*                   pNext;
+    uint32_t                      swapchainCount;
+    const VkPresentTimeGOOGLE*    pTimes;
+} VkPresentTimesInfoGOOGLE;
+
+typedef VkResult (VKAPI_PTR *PFN_vkGetRefreshCycleDurationGOOGLE)(VkDevice device, VkSwapchainKHR swapchain, VkRefreshCycleDurationGOOGLE* pDisplayTimingProperties);
+typedef VkResult (VKAPI_PTR *PFN_vkGetPastPresentationTimingGOOGLE)(VkDevice device, VkSwapchainKHR swapchain, uint32_t* pPresentationTimingCount, VkPastPresentationTimingGOOGLE* pPresentationTimings);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkGetRefreshCycleDurationGOOGLE(
+    VkDevice                                    device,
+    VkSwapchainKHR                              swapchain,
+    VkRefreshCycleDurationGOOGLE*               pDisplayTimingProperties);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetPastPresentationTimingGOOGLE(
+    VkDevice                                    device,
+    VkSwapchainKHR                              swapchain,
+    uint32_t*                                   pPresentationTimingCount,
+    VkPastPresentationTimingGOOGLE*             pPresentationTimings);
+#endif
+
+
+#define VK_NV_sample_mask_override_coverage 1
+#define VK_NV_SAMPLE_MASK_OVERRIDE_COVERAGE_SPEC_VERSION 1
+#define VK_NV_SAMPLE_MASK_OVERRIDE_COVERAGE_EXTENSION_NAME "VK_NV_sample_mask_override_coverage"
+
+
+#define VK_NV_geometry_shader_passthrough 1
+#define VK_NV_GEOMETRY_SHADER_PASSTHROUGH_SPEC_VERSION 1
+#define VK_NV_GEOMETRY_SHADER_PASSTHROUGH_EXTENSION_NAME "VK_NV_geometry_shader_passthrough"
+
+
+#define VK_NV_viewport_array2 1
+#define VK_NV_VIEWPORT_ARRAY_2_SPEC_VERSION 1
+#define VK_NV_VIEWPORT_ARRAY_2_EXTENSION_NAME "VK_NV_viewport_array2"
+#define VK_NV_VIEWPORT_ARRAY2_SPEC_VERSION VK_NV_VIEWPORT_ARRAY_2_SPEC_VERSION
+#define VK_NV_VIEWPORT_ARRAY2_EXTENSION_NAME VK_NV_VIEWPORT_ARRAY_2_EXTENSION_NAME
+
+
+#define VK_NVX_multiview_per_view_attributes 1
+#define VK_NVX_MULTIVIEW_PER_VIEW_ATTRIBUTES_SPEC_VERSION 1
+#define VK_NVX_MULTIVIEW_PER_VIEW_ATTRIBUTES_EXTENSION_NAME "VK_NVX_multiview_per_view_attributes"
+typedef struct VkPhysicalDeviceMultiviewPerViewAttributesPropertiesNVX {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           perViewPositionAllComponents;
+} VkPhysicalDeviceMultiviewPerViewAttributesPropertiesNVX;
+
+
+
+#define VK_NV_viewport_swizzle 1
+#define VK_NV_VIEWPORT_SWIZZLE_SPEC_VERSION 1
+#define VK_NV_VIEWPORT_SWIZZLE_EXTENSION_NAME "VK_NV_viewport_swizzle"
+
+typedef enum VkViewportCoordinateSwizzleNV {
+    VK_VIEWPORT_COORDINATE_SWIZZLE_POSITIVE_X_NV = 0,
+    VK_VIEWPORT_COORDINATE_SWIZZLE_NEGATIVE_X_NV = 1,
+    VK_VIEWPORT_COORDINATE_SWIZZLE_POSITIVE_Y_NV = 2,
+    VK_VIEWPORT_COORDINATE_SWIZZLE_NEGATIVE_Y_NV = 3,
+    VK_VIEWPORT_COORDINATE_SWIZZLE_POSITIVE_Z_NV = 4,
+    VK_VIEWPORT_COORDINATE_SWIZZLE_NEGATIVE_Z_NV = 5,
+    VK_VIEWPORT_COORDINATE_SWIZZLE_POSITIVE_W_NV = 6,
+    VK_VIEWPORT_COORDINATE_SWIZZLE_NEGATIVE_W_NV = 7,
+    VK_VIEWPORT_COORDINATE_SWIZZLE_MAX_ENUM_NV = 0x7FFFFFFF
+} VkViewportCoordinateSwizzleNV;
+typedef VkFlags VkPipelineViewportSwizzleStateCreateFlagsNV;
+typedef struct VkViewportSwizzleNV {
+    VkViewportCoordinateSwizzleNV    x;
+    VkViewportCoordinateSwizzleNV    y;
+    VkViewportCoordinateSwizzleNV    z;
+    VkViewportCoordinateSwizzleNV    w;
+} VkViewportSwizzleNV;
+
+typedef struct VkPipelineViewportSwizzleStateCreateInfoNV {
+    VkStructureType                                sType;
+    const void*                                    pNext;
+    VkPipelineViewportSwizzleStateCreateFlagsNV    flags;
+    uint32_t                                       viewportCount;
+    const VkViewportSwizzleNV*                     pViewportSwizzles;
+} VkPipelineViewportSwizzleStateCreateInfoNV;
+
+
+
+#define VK_EXT_discard_rectangles 1
+#define VK_EXT_DISCARD_RECTANGLES_SPEC_VERSION 1
+#define VK_EXT_DISCARD_RECTANGLES_EXTENSION_NAME "VK_EXT_discard_rectangles"
+
+typedef enum VkDiscardRectangleModeEXT {
+    VK_DISCARD_RECTANGLE_MODE_INCLUSIVE_EXT = 0,
+    VK_DISCARD_RECTANGLE_MODE_EXCLUSIVE_EXT = 1,
+    VK_DISCARD_RECTANGLE_MODE_MAX_ENUM_EXT = 0x7FFFFFFF
+} VkDiscardRectangleModeEXT;
+typedef VkFlags VkPipelineDiscardRectangleStateCreateFlagsEXT;
+typedef struct VkPhysicalDeviceDiscardRectanglePropertiesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    uint32_t           maxDiscardRectangles;
+} VkPhysicalDeviceDiscardRectanglePropertiesEXT;
+
+typedef struct VkPipelineDiscardRectangleStateCreateInfoEXT {
+    VkStructureType                                  sType;
+    const void*                                      pNext;
+    VkPipelineDiscardRectangleStateCreateFlagsEXT    flags;
+    VkDiscardRectangleModeEXT                        discardRectangleMode;
+    uint32_t                                         discardRectangleCount;
+    const VkRect2D*                                  pDiscardRectangles;
+} VkPipelineDiscardRectangleStateCreateInfoEXT;
+
+typedef void (VKAPI_PTR *PFN_vkCmdSetDiscardRectangleEXT)(VkCommandBuffer commandBuffer, uint32_t firstDiscardRectangle, uint32_t discardRectangleCount, const VkRect2D* pDiscardRectangles);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR void VKAPI_CALL vkCmdSetDiscardRectangleEXT(
+    VkCommandBuffer                             commandBuffer,
+    uint32_t                                    firstDiscardRectangle,
+    uint32_t                                    discardRectangleCount,
+    const VkRect2D*                             pDiscardRectangles);
+#endif
+
+
+#define VK_EXT_conservative_rasterization 1
+#define VK_EXT_CONSERVATIVE_RASTERIZATION_SPEC_VERSION 1
+#define VK_EXT_CONSERVATIVE_RASTERIZATION_EXTENSION_NAME "VK_EXT_conservative_rasterization"
+
+typedef enum VkConservativeRasterizationModeEXT {
+    VK_CONSERVATIVE_RASTERIZATION_MODE_DISABLED_EXT = 0,
+    VK_CONSERVATIVE_RASTERIZATION_MODE_OVERESTIMATE_EXT = 1,
+    VK_CONSERVATIVE_RASTERIZATION_MODE_UNDERESTIMATE_EXT = 2,
+    VK_CONSERVATIVE_RASTERIZATION_MODE_MAX_ENUM_EXT = 0x7FFFFFFF
+} VkConservativeRasterizationModeEXT;
+typedef VkFlags VkPipelineRasterizationConservativeStateCreateFlagsEXT;
+typedef struct VkPhysicalDeviceConservativeRasterizationPropertiesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    float              primitiveOverestimationSize;
+    float              maxExtraPrimitiveOverestimationSize;
+    float              extraPrimitiveOverestimationSizeGranularity;
+    VkBool32           primitiveUnderestimation;
+    VkBool32           conservativePointAndLineRasterization;
+    VkBool32           degenerateTrianglesRasterized;
+    VkBool32           degenerateLinesRasterized;
+    VkBool32           fullyCoveredFragmentShaderInputVariable;
+    VkBool32           conservativeRasterizationPostDepthCoverage;
+} VkPhysicalDeviceConservativeRasterizationPropertiesEXT;
+
+typedef struct VkPipelineRasterizationConservativeStateCreateInfoEXT {
+    VkStructureType                                           sType;
+    const void*                                               pNext;
+    VkPipelineRasterizationConservativeStateCreateFlagsEXT    flags;
+    VkConservativeRasterizationModeEXT                        conservativeRasterizationMode;
+    float                                                     extraPrimitiveOverestimationSize;
+} VkPipelineRasterizationConservativeStateCreateInfoEXT;
+
+
+
+#define VK_EXT_depth_clip_enable 1
+#define VK_EXT_DEPTH_CLIP_ENABLE_SPEC_VERSION 1
+#define VK_EXT_DEPTH_CLIP_ENABLE_EXTENSION_NAME "VK_EXT_depth_clip_enable"
+typedef VkFlags VkPipelineRasterizationDepthClipStateCreateFlagsEXT;
+typedef struct VkPhysicalDeviceDepthClipEnableFeaturesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           depthClipEnable;
+} VkPhysicalDeviceDepthClipEnableFeaturesEXT;
+
+typedef struct VkPipelineRasterizationDepthClipStateCreateInfoEXT {
+    VkStructureType                                        sType;
+    const void*                                            pNext;
+    VkPipelineRasterizationDepthClipStateCreateFlagsEXT    flags;
+    VkBool32                                               depthClipEnable;
+} VkPipelineRasterizationDepthClipStateCreateInfoEXT;
+
+
+
+#define VK_EXT_swapchain_colorspace 1
+#define VK_EXT_SWAPCHAIN_COLOR_SPACE_SPEC_VERSION 4
+#define VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME "VK_EXT_swapchain_colorspace"
+
+
+#define VK_EXT_hdr_metadata 1
+#define VK_EXT_HDR_METADATA_SPEC_VERSION  2
+#define VK_EXT_HDR_METADATA_EXTENSION_NAME "VK_EXT_hdr_metadata"
+typedef struct VkXYColorEXT {
+    float    x;
+    float    y;
+} VkXYColorEXT;
+
+typedef struct VkHdrMetadataEXT {
+    VkStructureType    sType;
+    const void*        pNext;
+    VkXYColorEXT       displayPrimaryRed;
+    VkXYColorEXT       displayPrimaryGreen;
+    VkXYColorEXT       displayPrimaryBlue;
+    VkXYColorEXT       whitePoint;
+    float              maxLuminance;
+    float              minLuminance;
+    float              maxContentLightLevel;
+    float              maxFrameAverageLightLevel;
+} VkHdrMetadataEXT;
+
+typedef void (VKAPI_PTR *PFN_vkSetHdrMetadataEXT)(VkDevice device, uint32_t swapchainCount, const VkSwapchainKHR* pSwapchains, const VkHdrMetadataEXT* pMetadata);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR void VKAPI_CALL vkSetHdrMetadataEXT(
+    VkDevice                                    device,
+    uint32_t                                    swapchainCount,
+    const VkSwapchainKHR*                       pSwapchains,
+    const VkHdrMetadataEXT*                     pMetadata);
+#endif
+
+
+#define VK_EXT_external_memory_dma_buf 1
+#define VK_EXT_EXTERNAL_MEMORY_DMA_BUF_SPEC_VERSION 1
+#define VK_EXT_EXTERNAL_MEMORY_DMA_BUF_EXTENSION_NAME "VK_EXT_external_memory_dma_buf"
+
+
+#define VK_EXT_queue_family_foreign 1
+#define VK_EXT_QUEUE_FAMILY_FOREIGN_SPEC_VERSION 1
+#define VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME "VK_EXT_queue_family_foreign"
+#define VK_QUEUE_FAMILY_FOREIGN_EXT       (~2U)
+
+
+#define VK_EXT_debug_utils 1
+VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkDebugUtilsMessengerEXT)
+#define VK_EXT_DEBUG_UTILS_SPEC_VERSION   2
+#define VK_EXT_DEBUG_UTILS_EXTENSION_NAME "VK_EXT_debug_utils"
+typedef VkFlags VkDebugUtilsMessengerCallbackDataFlagsEXT;
+
+typedef enum VkDebugUtilsMessageSeverityFlagBitsEXT {
+    VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT = 0x00000001,
+    VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT = 0x00000010,
+    VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT = 0x00000100,
+    VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT = 0x00001000,
+    VK_DEBUG_UTILS_MESSAGE_SEVERITY_FLAG_BITS_MAX_ENUM_EXT = 0x7FFFFFFF
+} VkDebugUtilsMessageSeverityFlagBitsEXT;
+
+typedef enum VkDebugUtilsMessageTypeFlagBitsEXT {
+    VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT = 0x00000001,
+    VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT = 0x00000002,
+    VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT = 0x00000004,
+    VK_DEBUG_UTILS_MESSAGE_TYPE_FLAG_BITS_MAX_ENUM_EXT = 0x7FFFFFFF
+} VkDebugUtilsMessageTypeFlagBitsEXT;
+typedef VkFlags VkDebugUtilsMessageTypeFlagsEXT;
+typedef VkFlags VkDebugUtilsMessageSeverityFlagsEXT;
+typedef VkFlags VkDebugUtilsMessengerCreateFlagsEXT;
+typedef struct VkDebugUtilsLabelEXT {
+    VkStructureType    sType;
+    const void*        pNext;
+    const char*        pLabelName;
+    float              color[4];
+} VkDebugUtilsLabelEXT;
+
+typedef struct VkDebugUtilsObjectNameInfoEXT {
+    VkStructureType    sType;
+    const void*        pNext;
+    VkObjectType       objectType;
+    uint64_t           objectHandle;
+    const char*        pObjectName;
+} VkDebugUtilsObjectNameInfoEXT;
+
+typedef struct VkDebugUtilsMessengerCallbackDataEXT {
+    VkStructureType                              sType;
+    const void*                                  pNext;
+    VkDebugUtilsMessengerCallbackDataFlagsEXT    flags;
+    const char*                                  pMessageIdName;
+    int32_t                                      messageIdNumber;
+    const char*                                  pMessage;
+    uint32_t                                     queueLabelCount;
+    const VkDebugUtilsLabelEXT*                  pQueueLabels;
+    uint32_t                                     cmdBufLabelCount;
+    const VkDebugUtilsLabelEXT*                  pCmdBufLabels;
+    uint32_t                                     objectCount;
+    const VkDebugUtilsObjectNameInfoEXT*         pObjects;
+} VkDebugUtilsMessengerCallbackDataEXT;
+
+typedef VkBool32 (VKAPI_PTR *PFN_vkDebugUtilsMessengerCallbackEXT)(
+    VkDebugUtilsMessageSeverityFlagBitsEXT           messageSeverity,
+    VkDebugUtilsMessageTypeFlagsEXT                  messageTypes,
+    const VkDebugUtilsMessengerCallbackDataEXT*      pCallbackData,
+    void*                                            pUserData);
+
+typedef struct VkDebugUtilsMessengerCreateInfoEXT {
+    VkStructureType                         sType;
+    const void*                             pNext;
+    VkDebugUtilsMessengerCreateFlagsEXT     flags;
+    VkDebugUtilsMessageSeverityFlagsEXT     messageSeverity;
+    VkDebugUtilsMessageTypeFlagsEXT         messageType;
+    PFN_vkDebugUtilsMessengerCallbackEXT    pfnUserCallback;
+    void*                                   pUserData;
+} VkDebugUtilsMessengerCreateInfoEXT;
+
+typedef struct VkDebugUtilsObjectTagInfoEXT {
+    VkStructureType    sType;
+    const void*        pNext;
+    VkObjectType       objectType;
+    uint64_t           objectHandle;
+    uint64_t           tagName;
+    size_t             tagSize;
+    const void*        pTag;
+} VkDebugUtilsObjectTagInfoEXT;
+
+typedef VkResult (VKAPI_PTR *PFN_vkSetDebugUtilsObjectNameEXT)(VkDevice device, const VkDebugUtilsObjectNameInfoEXT* pNameInfo);
+typedef VkResult (VKAPI_PTR *PFN_vkSetDebugUtilsObjectTagEXT)(VkDevice device, const VkDebugUtilsObjectTagInfoEXT* pTagInfo);
+typedef void (VKAPI_PTR *PFN_vkQueueBeginDebugUtilsLabelEXT)(VkQueue queue, const VkDebugUtilsLabelEXT* pLabelInfo);
+typedef void (VKAPI_PTR *PFN_vkQueueEndDebugUtilsLabelEXT)(VkQueue queue);
+typedef void (VKAPI_PTR *PFN_vkQueueInsertDebugUtilsLabelEXT)(VkQueue queue, const VkDebugUtilsLabelEXT* pLabelInfo);
+typedef void (VKAPI_PTR *PFN_vkCmdBeginDebugUtilsLabelEXT)(VkCommandBuffer commandBuffer, const VkDebugUtilsLabelEXT* pLabelInfo);
+typedef void (VKAPI_PTR *PFN_vkCmdEndDebugUtilsLabelEXT)(VkCommandBuffer commandBuffer);
+typedef void (VKAPI_PTR *PFN_vkCmdInsertDebugUtilsLabelEXT)(VkCommandBuffer commandBuffer, const VkDebugUtilsLabelEXT* pLabelInfo);
+typedef VkResult (VKAPI_PTR *PFN_vkCreateDebugUtilsMessengerEXT)(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pMessenger);
+typedef void (VKAPI_PTR *PFN_vkDestroyDebugUtilsMessengerEXT)(VkInstance instance, VkDebugUtilsMessengerEXT messenger, const VkAllocationCallbacks* pAllocator);
+typedef void (VKAPI_PTR *PFN_vkSubmitDebugUtilsMessageEXT)(VkInstance instance, VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageTypes, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkSetDebugUtilsObjectNameEXT(
+    VkDevice                                    device,
+    const VkDebugUtilsObjectNameInfoEXT*        pNameInfo);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkSetDebugUtilsObjectTagEXT(
+    VkDevice                                    device,
+    const VkDebugUtilsObjectTagInfoEXT*         pTagInfo);
+
+VKAPI_ATTR void VKAPI_CALL vkQueueBeginDebugUtilsLabelEXT(
+    VkQueue                                     queue,
+    const VkDebugUtilsLabelEXT*                 pLabelInfo);
+
+VKAPI_ATTR void VKAPI_CALL vkQueueEndDebugUtilsLabelEXT(
+    VkQueue                                     queue);
+
+VKAPI_ATTR void VKAPI_CALL vkQueueInsertDebugUtilsLabelEXT(
+    VkQueue                                     queue,
+    const VkDebugUtilsLabelEXT*                 pLabelInfo);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdBeginDebugUtilsLabelEXT(
+    VkCommandBuffer                             commandBuffer,
+    const VkDebugUtilsLabelEXT*                 pLabelInfo);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdEndDebugUtilsLabelEXT(
+    VkCommandBuffer                             commandBuffer);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdInsertDebugUtilsLabelEXT(
+    VkCommandBuffer                             commandBuffer,
+    const VkDebugUtilsLabelEXT*                 pLabelInfo);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateDebugUtilsMessengerEXT(
+    VkInstance                                  instance,
+    const VkDebugUtilsMessengerCreateInfoEXT*   pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkDebugUtilsMessengerEXT*                   pMessenger);
+
+VKAPI_ATTR void VKAPI_CALL vkDestroyDebugUtilsMessengerEXT(
+    VkInstance                                  instance,
+    VkDebugUtilsMessengerEXT                    messenger,
+    const VkAllocationCallbacks*                pAllocator);
+
+VKAPI_ATTR void VKAPI_CALL vkSubmitDebugUtilsMessageEXT(
+    VkInstance                                  instance,
+    VkDebugUtilsMessageSeverityFlagBitsEXT      messageSeverity,
+    VkDebugUtilsMessageTypeFlagsEXT             messageTypes,
+    const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData);
+#endif
+
+
+#define VK_EXT_sampler_filter_minmax 1
+#define VK_EXT_SAMPLER_FILTER_MINMAX_SPEC_VERSION 2
+#define VK_EXT_SAMPLER_FILTER_MINMAX_EXTENSION_NAME "VK_EXT_sampler_filter_minmax"
+typedef VkSamplerReductionMode VkSamplerReductionModeEXT;
+
+typedef VkSamplerReductionModeCreateInfo VkSamplerReductionModeCreateInfoEXT;
+
+typedef VkPhysicalDeviceSamplerFilterMinmaxProperties VkPhysicalDeviceSamplerFilterMinmaxPropertiesEXT;
+
+
+
+#define VK_AMD_gpu_shader_int16 1
+#define VK_AMD_GPU_SHADER_INT16_SPEC_VERSION 2
+#define VK_AMD_GPU_SHADER_INT16_EXTENSION_NAME "VK_AMD_gpu_shader_int16"
+
+
+#define VK_AMD_mixed_attachment_samples 1
+#define VK_AMD_MIXED_ATTACHMENT_SAMPLES_SPEC_VERSION 1
+#define VK_AMD_MIXED_ATTACHMENT_SAMPLES_EXTENSION_NAME "VK_AMD_mixed_attachment_samples"
+
+
+#define VK_AMD_shader_fragment_mask 1
+#define VK_AMD_SHADER_FRAGMENT_MASK_SPEC_VERSION 1
+#define VK_AMD_SHADER_FRAGMENT_MASK_EXTENSION_NAME "VK_AMD_shader_fragment_mask"
+
+
+#define VK_EXT_inline_uniform_block 1
+#define VK_EXT_INLINE_UNIFORM_BLOCK_SPEC_VERSION 1
+#define VK_EXT_INLINE_UNIFORM_BLOCK_EXTENSION_NAME "VK_EXT_inline_uniform_block"
+typedef struct VkPhysicalDeviceInlineUniformBlockFeaturesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           inlineUniformBlock;
+    VkBool32           descriptorBindingInlineUniformBlockUpdateAfterBind;
+} VkPhysicalDeviceInlineUniformBlockFeaturesEXT;
+
+typedef struct VkPhysicalDeviceInlineUniformBlockPropertiesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    uint32_t           maxInlineUniformBlockSize;
+    uint32_t           maxPerStageDescriptorInlineUniformBlocks;
+    uint32_t           maxPerStageDescriptorUpdateAfterBindInlineUniformBlocks;
+    uint32_t           maxDescriptorSetInlineUniformBlocks;
+    uint32_t           maxDescriptorSetUpdateAfterBindInlineUniformBlocks;
+} VkPhysicalDeviceInlineUniformBlockPropertiesEXT;
+
+typedef struct VkWriteDescriptorSetInlineUniformBlockEXT {
+    VkStructureType    sType;
+    const void*        pNext;
+    uint32_t           dataSize;
+    const void*        pData;
+} VkWriteDescriptorSetInlineUniformBlockEXT;
+
+typedef struct VkDescriptorPoolInlineUniformBlockCreateInfoEXT {
+    VkStructureType    sType;
+    const void*        pNext;
+    uint32_t           maxInlineUniformBlockBindings;
+} VkDescriptorPoolInlineUniformBlockCreateInfoEXT;
+
+
+
+#define VK_EXT_shader_stencil_export 1
+#define VK_EXT_SHADER_STENCIL_EXPORT_SPEC_VERSION 1
+#define VK_EXT_SHADER_STENCIL_EXPORT_EXTENSION_NAME "VK_EXT_shader_stencil_export"
+
+
+#define VK_EXT_sample_locations 1
+#define VK_EXT_SAMPLE_LOCATIONS_SPEC_VERSION 1
+#define VK_EXT_SAMPLE_LOCATIONS_EXTENSION_NAME "VK_EXT_sample_locations"
+typedef struct VkSampleLocationEXT {
+    float    x;
+    float    y;
+} VkSampleLocationEXT;
+
+typedef struct VkSampleLocationsInfoEXT {
+    VkStructureType               sType;
+    const void*                   pNext;
+    VkSampleCountFlagBits         sampleLocationsPerPixel;
+    VkExtent2D                    sampleLocationGridSize;
+    uint32_t                      sampleLocationsCount;
+    const VkSampleLocationEXT*    pSampleLocations;
+} VkSampleLocationsInfoEXT;
+
+typedef struct VkAttachmentSampleLocationsEXT {
+    uint32_t                    attachmentIndex;
+    VkSampleLocationsInfoEXT    sampleLocationsInfo;
+} VkAttachmentSampleLocationsEXT;
+
+typedef struct VkSubpassSampleLocationsEXT {
+    uint32_t                    subpassIndex;
+    VkSampleLocationsInfoEXT    sampleLocationsInfo;
+} VkSubpassSampleLocationsEXT;
+
+typedef struct VkRenderPassSampleLocationsBeginInfoEXT {
+    VkStructureType                          sType;
+    const void*                              pNext;
+    uint32_t                                 attachmentInitialSampleLocationsCount;
+    const VkAttachmentSampleLocationsEXT*    pAttachmentInitialSampleLocations;
+    uint32_t                                 postSubpassSampleLocationsCount;
+    const VkSubpassSampleLocationsEXT*       pPostSubpassSampleLocations;
+} VkRenderPassSampleLocationsBeginInfoEXT;
+
+typedef struct VkPipelineSampleLocationsStateCreateInfoEXT {
+    VkStructureType             sType;
+    const void*                 pNext;
+    VkBool32                    sampleLocationsEnable;
+    VkSampleLocationsInfoEXT    sampleLocationsInfo;
+} VkPipelineSampleLocationsStateCreateInfoEXT;
+
+typedef struct VkPhysicalDeviceSampleLocationsPropertiesEXT {
+    VkStructureType       sType;
+    void*                 pNext;
+    VkSampleCountFlags    sampleLocationSampleCounts;
+    VkExtent2D            maxSampleLocationGridSize;
+    float                 sampleLocationCoordinateRange[2];
+    uint32_t              sampleLocationSubPixelBits;
+    VkBool32              variableSampleLocations;
+} VkPhysicalDeviceSampleLocationsPropertiesEXT;
+
+typedef struct VkMultisamplePropertiesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    VkExtent2D         maxSampleLocationGridSize;
+} VkMultisamplePropertiesEXT;
+
+typedef void (VKAPI_PTR *PFN_vkCmdSetSampleLocationsEXT)(VkCommandBuffer commandBuffer, const VkSampleLocationsInfoEXT* pSampleLocationsInfo);
+typedef void (VKAPI_PTR *PFN_vkGetPhysicalDeviceMultisamplePropertiesEXT)(VkPhysicalDevice physicalDevice, VkSampleCountFlagBits samples, VkMultisamplePropertiesEXT* pMultisampleProperties);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR void VKAPI_CALL vkCmdSetSampleLocationsEXT(
+    VkCommandBuffer                             commandBuffer,
+    const VkSampleLocationsInfoEXT*             pSampleLocationsInfo);
+
+VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceMultisamplePropertiesEXT(
+    VkPhysicalDevice                            physicalDevice,
+    VkSampleCountFlagBits                       samples,
+    VkMultisamplePropertiesEXT*                 pMultisampleProperties);
+#endif
+
+
+#define VK_EXT_blend_operation_advanced 1
+#define VK_EXT_BLEND_OPERATION_ADVANCED_SPEC_VERSION 2
+#define VK_EXT_BLEND_OPERATION_ADVANCED_EXTENSION_NAME "VK_EXT_blend_operation_advanced"
+
+typedef enum VkBlendOverlapEXT {
+    VK_BLEND_OVERLAP_UNCORRELATED_EXT = 0,
+    VK_BLEND_OVERLAP_DISJOINT_EXT = 1,
+    VK_BLEND_OVERLAP_CONJOINT_EXT = 2,
+    VK_BLEND_OVERLAP_MAX_ENUM_EXT = 0x7FFFFFFF
+} VkBlendOverlapEXT;
+typedef struct VkPhysicalDeviceBlendOperationAdvancedFeaturesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           advancedBlendCoherentOperations;
+} VkPhysicalDeviceBlendOperationAdvancedFeaturesEXT;
+
+typedef struct VkPhysicalDeviceBlendOperationAdvancedPropertiesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    uint32_t           advancedBlendMaxColorAttachments;
+    VkBool32           advancedBlendIndependentBlend;
+    VkBool32           advancedBlendNonPremultipliedSrcColor;
+    VkBool32           advancedBlendNonPremultipliedDstColor;
+    VkBool32           advancedBlendCorrelatedOverlap;
+    VkBool32           advancedBlendAllOperations;
+} VkPhysicalDeviceBlendOperationAdvancedPropertiesEXT;
+
+typedef struct VkPipelineColorBlendAdvancedStateCreateInfoEXT {
+    VkStructureType      sType;
+    const void*          pNext;
+    VkBool32             srcPremultiplied;
+    VkBool32             dstPremultiplied;
+    VkBlendOverlapEXT    blendOverlap;
+} VkPipelineColorBlendAdvancedStateCreateInfoEXT;
+
+
+
+#define VK_NV_fragment_coverage_to_color 1
+#define VK_NV_FRAGMENT_COVERAGE_TO_COLOR_SPEC_VERSION 1
+#define VK_NV_FRAGMENT_COVERAGE_TO_COLOR_EXTENSION_NAME "VK_NV_fragment_coverage_to_color"
+typedef VkFlags VkPipelineCoverageToColorStateCreateFlagsNV;
+typedef struct VkPipelineCoverageToColorStateCreateInfoNV {
+    VkStructureType                                sType;
+    const void*                                    pNext;
+    VkPipelineCoverageToColorStateCreateFlagsNV    flags;
+    VkBool32                                       coverageToColorEnable;
+    uint32_t                                       coverageToColorLocation;
+} VkPipelineCoverageToColorStateCreateInfoNV;
+
+
+
+#define VK_NV_framebuffer_mixed_samples 1
+#define VK_NV_FRAMEBUFFER_MIXED_SAMPLES_SPEC_VERSION 1
+#define VK_NV_FRAMEBUFFER_MIXED_SAMPLES_EXTENSION_NAME "VK_NV_framebuffer_mixed_samples"
+
+typedef enum VkCoverageModulationModeNV {
+    VK_COVERAGE_MODULATION_MODE_NONE_NV = 0,
+    VK_COVERAGE_MODULATION_MODE_RGB_NV = 1,
+    VK_COVERAGE_MODULATION_MODE_ALPHA_NV = 2,
+    VK_COVERAGE_MODULATION_MODE_RGBA_NV = 3,
+    VK_COVERAGE_MODULATION_MODE_MAX_ENUM_NV = 0x7FFFFFFF
+} VkCoverageModulationModeNV;
+typedef VkFlags VkPipelineCoverageModulationStateCreateFlagsNV;
+typedef struct VkPipelineCoverageModulationStateCreateInfoNV {
+    VkStructureType                                   sType;
+    const void*                                       pNext;
+    VkPipelineCoverageModulationStateCreateFlagsNV    flags;
+    VkCoverageModulationModeNV                        coverageModulationMode;
+    VkBool32                                          coverageModulationTableEnable;
+    uint32_t                                          coverageModulationTableCount;
+    const float*                                      pCoverageModulationTable;
+} VkPipelineCoverageModulationStateCreateInfoNV;
+
+
+
+#define VK_NV_fill_rectangle 1
+#define VK_NV_FILL_RECTANGLE_SPEC_VERSION 1
+#define VK_NV_FILL_RECTANGLE_EXTENSION_NAME "VK_NV_fill_rectangle"
+
+
+#define VK_NV_shader_sm_builtins 1
+#define VK_NV_SHADER_SM_BUILTINS_SPEC_VERSION 1
+#define VK_NV_SHADER_SM_BUILTINS_EXTENSION_NAME "VK_NV_shader_sm_builtins"
+typedef struct VkPhysicalDeviceShaderSMBuiltinsPropertiesNV {
+    VkStructureType    sType;
+    void*              pNext;
+    uint32_t           shaderSMCount;
+    uint32_t           shaderWarpsPerSM;
+} VkPhysicalDeviceShaderSMBuiltinsPropertiesNV;
+
+typedef struct VkPhysicalDeviceShaderSMBuiltinsFeaturesNV {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           shaderSMBuiltins;
+} VkPhysicalDeviceShaderSMBuiltinsFeaturesNV;
+
+
+
+#define VK_EXT_post_depth_coverage 1
+#define VK_EXT_POST_DEPTH_COVERAGE_SPEC_VERSION 1
+#define VK_EXT_POST_DEPTH_COVERAGE_EXTENSION_NAME "VK_EXT_post_depth_coverage"
+
+
+#define VK_EXT_image_drm_format_modifier 1
+#define VK_EXT_IMAGE_DRM_FORMAT_MODIFIER_SPEC_VERSION 2
+#define VK_EXT_IMAGE_DRM_FORMAT_MODIFIER_EXTENSION_NAME "VK_EXT_image_drm_format_modifier"
+typedef struct VkDrmFormatModifierPropertiesEXT {
+    uint64_t                drmFormatModifier;
+    uint32_t                drmFormatModifierPlaneCount;
+    VkFormatFeatureFlags    drmFormatModifierTilingFeatures;
+} VkDrmFormatModifierPropertiesEXT;
+
+typedef struct VkDrmFormatModifierPropertiesListEXT {
+    VkStructureType                      sType;
+    void*                                pNext;
+    uint32_t                             drmFormatModifierCount;
+    VkDrmFormatModifierPropertiesEXT*    pDrmFormatModifierProperties;
+} VkDrmFormatModifierPropertiesListEXT;
+
+typedef struct VkPhysicalDeviceImageDrmFormatModifierInfoEXT {
+    VkStructureType    sType;
+    const void*        pNext;
+    uint64_t           drmFormatModifier;
+    VkSharingMode      sharingMode;
+    uint32_t           queueFamilyIndexCount;
+    const uint32_t*    pQueueFamilyIndices;
+} VkPhysicalDeviceImageDrmFormatModifierInfoEXT;
+
+typedef struct VkImageDrmFormatModifierListCreateInfoEXT {
+    VkStructureType    sType;
+    const void*        pNext;
+    uint32_t           drmFormatModifierCount;
+    const uint64_t*    pDrmFormatModifiers;
+} VkImageDrmFormatModifierListCreateInfoEXT;
+
+typedef struct VkImageDrmFormatModifierExplicitCreateInfoEXT {
+    VkStructureType               sType;
+    const void*                   pNext;
+    uint64_t                      drmFormatModifier;
+    uint32_t                      drmFormatModifierPlaneCount;
+    const VkSubresourceLayout*    pPlaneLayouts;
+} VkImageDrmFormatModifierExplicitCreateInfoEXT;
+
+typedef struct VkImageDrmFormatModifierPropertiesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    uint64_t           drmFormatModifier;
+} VkImageDrmFormatModifierPropertiesEXT;
+
+typedef struct VkDrmFormatModifierProperties2EXT {
+    uint64_t                    drmFormatModifier;
+    uint32_t                    drmFormatModifierPlaneCount;
+    VkFormatFeatureFlags2KHR    drmFormatModifierTilingFeatures;
+} VkDrmFormatModifierProperties2EXT;
+
+typedef struct VkDrmFormatModifierPropertiesList2EXT {
+    VkStructureType                       sType;
+    void*                                 pNext;
+    uint32_t                              drmFormatModifierCount;
+    VkDrmFormatModifierProperties2EXT*    pDrmFormatModifierProperties;
+} VkDrmFormatModifierPropertiesList2EXT;
+
+typedef VkResult (VKAPI_PTR *PFN_vkGetImageDrmFormatModifierPropertiesEXT)(VkDevice device, VkImage image, VkImageDrmFormatModifierPropertiesEXT* pProperties);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkGetImageDrmFormatModifierPropertiesEXT(
+    VkDevice                                    device,
+    VkImage                                     image,
+    VkImageDrmFormatModifierPropertiesEXT*      pProperties);
+#endif
+
+
+#define VK_EXT_validation_cache 1
+VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkValidationCacheEXT)
+#define VK_EXT_VALIDATION_CACHE_SPEC_VERSION 1
+#define VK_EXT_VALIDATION_CACHE_EXTENSION_NAME "VK_EXT_validation_cache"
+
+typedef enum VkValidationCacheHeaderVersionEXT {
+    VK_VALIDATION_CACHE_HEADER_VERSION_ONE_EXT = 1,
+    VK_VALIDATION_CACHE_HEADER_VERSION_MAX_ENUM_EXT = 0x7FFFFFFF
+} VkValidationCacheHeaderVersionEXT;
+typedef VkFlags VkValidationCacheCreateFlagsEXT;
+typedef struct VkValidationCacheCreateInfoEXT {
+    VkStructureType                    sType;
+    const void*                        pNext;
+    VkValidationCacheCreateFlagsEXT    flags;
+    size_t                             initialDataSize;
+    const void*                        pInitialData;
+} VkValidationCacheCreateInfoEXT;
+
+typedef struct VkShaderModuleValidationCacheCreateInfoEXT {
+    VkStructureType         sType;
+    const void*             pNext;
+    VkValidationCacheEXT    validationCache;
+} VkShaderModuleValidationCacheCreateInfoEXT;
+
+typedef VkResult (VKAPI_PTR *PFN_vkCreateValidationCacheEXT)(VkDevice device, const VkValidationCacheCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkValidationCacheEXT* pValidationCache);
+typedef void (VKAPI_PTR *PFN_vkDestroyValidationCacheEXT)(VkDevice device, VkValidationCacheEXT validationCache, const VkAllocationCallbacks* pAllocator);
+typedef VkResult (VKAPI_PTR *PFN_vkMergeValidationCachesEXT)(VkDevice device, VkValidationCacheEXT dstCache, uint32_t srcCacheCount, const VkValidationCacheEXT* pSrcCaches);
+typedef VkResult (VKAPI_PTR *PFN_vkGetValidationCacheDataEXT)(VkDevice device, VkValidationCacheEXT validationCache, size_t* pDataSize, void* pData);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateValidationCacheEXT(
+    VkDevice                                    device,
+    const VkValidationCacheCreateInfoEXT*       pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkValidationCacheEXT*                       pValidationCache);
+
+VKAPI_ATTR void VKAPI_CALL vkDestroyValidationCacheEXT(
+    VkDevice                                    device,
+    VkValidationCacheEXT                        validationCache,
+    const VkAllocationCallbacks*                pAllocator);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkMergeValidationCachesEXT(
+    VkDevice                                    device,
+    VkValidationCacheEXT                        dstCache,
+    uint32_t                                    srcCacheCount,
+    const VkValidationCacheEXT*                 pSrcCaches);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetValidationCacheDataEXT(
+    VkDevice                                    device,
+    VkValidationCacheEXT                        validationCache,
+    size_t*                                     pDataSize,
+    void*                                       pData);
+#endif
+
+
+#define VK_EXT_descriptor_indexing 1
+#define VK_EXT_DESCRIPTOR_INDEXING_SPEC_VERSION 2
+#define VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME "VK_EXT_descriptor_indexing"
+typedef VkDescriptorBindingFlagBits VkDescriptorBindingFlagBitsEXT;
+
+typedef VkDescriptorBindingFlags VkDescriptorBindingFlagsEXT;
+
+typedef VkDescriptorSetLayoutBindingFlagsCreateInfo VkDescriptorSetLayoutBindingFlagsCreateInfoEXT;
+
+typedef VkPhysicalDeviceDescriptorIndexingFeatures VkPhysicalDeviceDescriptorIndexingFeaturesEXT;
+
+typedef VkPhysicalDeviceDescriptorIndexingProperties VkPhysicalDeviceDescriptorIndexingPropertiesEXT;
+
+typedef VkDescriptorSetVariableDescriptorCountAllocateInfo VkDescriptorSetVariableDescriptorCountAllocateInfoEXT;
+
+typedef VkDescriptorSetVariableDescriptorCountLayoutSupport VkDescriptorSetVariableDescriptorCountLayoutSupportEXT;
+
+
+
+#define VK_EXT_shader_viewport_index_layer 1
+#define VK_EXT_SHADER_VIEWPORT_INDEX_LAYER_SPEC_VERSION 1
+#define VK_EXT_SHADER_VIEWPORT_INDEX_LAYER_EXTENSION_NAME "VK_EXT_shader_viewport_index_layer"
+
+
+#define VK_NV_shading_rate_image 1
+#define VK_NV_SHADING_RATE_IMAGE_SPEC_VERSION 3
+#define VK_NV_SHADING_RATE_IMAGE_EXTENSION_NAME "VK_NV_shading_rate_image"
+
+typedef enum VkShadingRatePaletteEntryNV {
+    VK_SHADING_RATE_PALETTE_ENTRY_NO_INVOCATIONS_NV = 0,
+    VK_SHADING_RATE_PALETTE_ENTRY_16_INVOCATIONS_PER_PIXEL_NV = 1,
+    VK_SHADING_RATE_PALETTE_ENTRY_8_INVOCATIONS_PER_PIXEL_NV = 2,
+    VK_SHADING_RATE_PALETTE_ENTRY_4_INVOCATIONS_PER_PIXEL_NV = 3,
+    VK_SHADING_RATE_PALETTE_ENTRY_2_INVOCATIONS_PER_PIXEL_NV = 4,
+    VK_SHADING_RATE_PALETTE_ENTRY_1_INVOCATION_PER_PIXEL_NV = 5,
+    VK_SHADING_RATE_PALETTE_ENTRY_1_INVOCATION_PER_2X1_PIXELS_NV = 6,
+    VK_SHADING_RATE_PALETTE_ENTRY_1_INVOCATION_PER_1X2_PIXELS_NV = 7,
+    VK_SHADING_RATE_PALETTE_ENTRY_1_INVOCATION_PER_2X2_PIXELS_NV = 8,
+    VK_SHADING_RATE_PALETTE_ENTRY_1_INVOCATION_PER_4X2_PIXELS_NV = 9,
+    VK_SHADING_RATE_PALETTE_ENTRY_1_INVOCATION_PER_2X4_PIXELS_NV = 10,
+    VK_SHADING_RATE_PALETTE_ENTRY_1_INVOCATION_PER_4X4_PIXELS_NV = 11,
+    VK_SHADING_RATE_PALETTE_ENTRY_MAX_ENUM_NV = 0x7FFFFFFF
+} VkShadingRatePaletteEntryNV;
+
+typedef enum VkCoarseSampleOrderTypeNV {
+    VK_COARSE_SAMPLE_ORDER_TYPE_DEFAULT_NV = 0,
+    VK_COARSE_SAMPLE_ORDER_TYPE_CUSTOM_NV = 1,
+    VK_COARSE_SAMPLE_ORDER_TYPE_PIXEL_MAJOR_NV = 2,
+    VK_COARSE_SAMPLE_ORDER_TYPE_SAMPLE_MAJOR_NV = 3,
+    VK_COARSE_SAMPLE_ORDER_TYPE_MAX_ENUM_NV = 0x7FFFFFFF
+} VkCoarseSampleOrderTypeNV;
+typedef struct VkShadingRatePaletteNV {
+    uint32_t                              shadingRatePaletteEntryCount;
+    const VkShadingRatePaletteEntryNV*    pShadingRatePaletteEntries;
+} VkShadingRatePaletteNV;
+
+typedef struct VkPipelineViewportShadingRateImageStateCreateInfoNV {
+    VkStructureType                  sType;
+    const void*                      pNext;
+    VkBool32                         shadingRateImageEnable;
+    uint32_t                         viewportCount;
+    const VkShadingRatePaletteNV*    pShadingRatePalettes;
+} VkPipelineViewportShadingRateImageStateCreateInfoNV;
+
+typedef struct VkPhysicalDeviceShadingRateImageFeaturesNV {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           shadingRateImage;
+    VkBool32           shadingRateCoarseSampleOrder;
+} VkPhysicalDeviceShadingRateImageFeaturesNV;
+
+typedef struct VkPhysicalDeviceShadingRateImagePropertiesNV {
+    VkStructureType    sType;
+    void*              pNext;
+    VkExtent2D         shadingRateTexelSize;
+    uint32_t           shadingRatePaletteSize;
+    uint32_t           shadingRateMaxCoarseSamples;
+} VkPhysicalDeviceShadingRateImagePropertiesNV;
+
+typedef struct VkCoarseSampleLocationNV {
+    uint32_t    pixelX;
+    uint32_t    pixelY;
+    uint32_t    sample;
+} VkCoarseSampleLocationNV;
+
+typedef struct VkCoarseSampleOrderCustomNV {
+    VkShadingRatePaletteEntryNV        shadingRate;
+    uint32_t                           sampleCount;
+    uint32_t                           sampleLocationCount;
+    const VkCoarseSampleLocationNV*    pSampleLocations;
+} VkCoarseSampleOrderCustomNV;
+
+typedef struct VkPipelineViewportCoarseSampleOrderStateCreateInfoNV {
+    VkStructureType                       sType;
+    const void*                           pNext;
+    VkCoarseSampleOrderTypeNV             sampleOrderType;
+    uint32_t                              customSampleOrderCount;
+    const VkCoarseSampleOrderCustomNV*    pCustomSampleOrders;
+} VkPipelineViewportCoarseSampleOrderStateCreateInfoNV;
+
+typedef void (VKAPI_PTR *PFN_vkCmdBindShadingRateImageNV)(VkCommandBuffer commandBuffer, VkImageView imageView, VkImageLayout imageLayout);
+typedef void (VKAPI_PTR *PFN_vkCmdSetViewportShadingRatePaletteNV)(VkCommandBuffer commandBuffer, uint32_t firstViewport, uint32_t viewportCount, const VkShadingRatePaletteNV* pShadingRatePalettes);
+typedef void (VKAPI_PTR *PFN_vkCmdSetCoarseSampleOrderNV)(VkCommandBuffer commandBuffer, VkCoarseSampleOrderTypeNV sampleOrderType, uint32_t customSampleOrderCount, const VkCoarseSampleOrderCustomNV* pCustomSampleOrders);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR void VKAPI_CALL vkCmdBindShadingRateImageNV(
+    VkCommandBuffer                             commandBuffer,
+    VkImageView                                 imageView,
+    VkImageLayout                               imageLayout);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdSetViewportShadingRatePaletteNV(
+    VkCommandBuffer                             commandBuffer,
+    uint32_t                                    firstViewport,
+    uint32_t                                    viewportCount,
+    const VkShadingRatePaletteNV*               pShadingRatePalettes);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdSetCoarseSampleOrderNV(
+    VkCommandBuffer                             commandBuffer,
+    VkCoarseSampleOrderTypeNV                   sampleOrderType,
+    uint32_t                                    customSampleOrderCount,
+    const VkCoarseSampleOrderCustomNV*          pCustomSampleOrders);
+#endif
+
+
+#define VK_NV_ray_tracing 1
+VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkAccelerationStructureNV)
+#define VK_NV_RAY_TRACING_SPEC_VERSION    3
+#define VK_NV_RAY_TRACING_EXTENSION_NAME  "VK_NV_ray_tracing"
+#define VK_SHADER_UNUSED_KHR              (~0U)
+#define VK_SHADER_UNUSED_NV               VK_SHADER_UNUSED_KHR
+
+typedef enum VkRayTracingShaderGroupTypeKHR {
+    VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR = 0,
+    VK_RAY_TRACING_SHADER_GROUP_TYPE_TRIANGLES_HIT_GROUP_KHR = 1,
+    VK_RAY_TRACING_SHADER_GROUP_TYPE_PROCEDURAL_HIT_GROUP_KHR = 2,
+    VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_NV = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR,
+    VK_RAY_TRACING_SHADER_GROUP_TYPE_TRIANGLES_HIT_GROUP_NV = VK_RAY_TRACING_SHADER_GROUP_TYPE_TRIANGLES_HIT_GROUP_KHR,
+    VK_RAY_TRACING_SHADER_GROUP_TYPE_PROCEDURAL_HIT_GROUP_NV = VK_RAY_TRACING_SHADER_GROUP_TYPE_PROCEDURAL_HIT_GROUP_KHR,
+    VK_RAY_TRACING_SHADER_GROUP_TYPE_MAX_ENUM_KHR = 0x7FFFFFFF
+} VkRayTracingShaderGroupTypeKHR;
+typedef VkRayTracingShaderGroupTypeKHR VkRayTracingShaderGroupTypeNV;
+
+
+typedef enum VkGeometryTypeKHR {
+    VK_GEOMETRY_TYPE_TRIANGLES_KHR = 0,
+    VK_GEOMETRY_TYPE_AABBS_KHR = 1,
+    VK_GEOMETRY_TYPE_INSTANCES_KHR = 2,
+    VK_GEOMETRY_TYPE_TRIANGLES_NV = VK_GEOMETRY_TYPE_TRIANGLES_KHR,
+    VK_GEOMETRY_TYPE_AABBS_NV = VK_GEOMETRY_TYPE_AABBS_KHR,
+    VK_GEOMETRY_TYPE_MAX_ENUM_KHR = 0x7FFFFFFF
+} VkGeometryTypeKHR;
+typedef VkGeometryTypeKHR VkGeometryTypeNV;
+
+
+typedef enum VkAccelerationStructureTypeKHR {
+    VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR = 0,
+    VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR = 1,
+    VK_ACCELERATION_STRUCTURE_TYPE_GENERIC_KHR = 2,
+    VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_NV = VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR,
+    VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_NV = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR,
+    VK_ACCELERATION_STRUCTURE_TYPE_MAX_ENUM_KHR = 0x7FFFFFFF
+} VkAccelerationStructureTypeKHR;
+typedef VkAccelerationStructureTypeKHR VkAccelerationStructureTypeNV;
+
+
+typedef enum VkCopyAccelerationStructureModeKHR {
+    VK_COPY_ACCELERATION_STRUCTURE_MODE_CLONE_KHR = 0,
+    VK_COPY_ACCELERATION_STRUCTURE_MODE_COMPACT_KHR = 1,
+    VK_COPY_ACCELERATION_STRUCTURE_MODE_SERIALIZE_KHR = 2,
+    VK_COPY_ACCELERATION_STRUCTURE_MODE_DESERIALIZE_KHR = 3,
+    VK_COPY_ACCELERATION_STRUCTURE_MODE_CLONE_NV = VK_COPY_ACCELERATION_STRUCTURE_MODE_CLONE_KHR,
+    VK_COPY_ACCELERATION_STRUCTURE_MODE_COMPACT_NV = VK_COPY_ACCELERATION_STRUCTURE_MODE_COMPACT_KHR,
+    VK_COPY_ACCELERATION_STRUCTURE_MODE_MAX_ENUM_KHR = 0x7FFFFFFF
+} VkCopyAccelerationStructureModeKHR;
+typedef VkCopyAccelerationStructureModeKHR VkCopyAccelerationStructureModeNV;
+
+
+typedef enum VkAccelerationStructureMemoryRequirementsTypeNV {
+    VK_ACCELERATION_STRUCTURE_MEMORY_REQUIREMENTS_TYPE_OBJECT_NV = 0,
+    VK_ACCELERATION_STRUCTURE_MEMORY_REQUIREMENTS_TYPE_BUILD_SCRATCH_NV = 1,
+    VK_ACCELERATION_STRUCTURE_MEMORY_REQUIREMENTS_TYPE_UPDATE_SCRATCH_NV = 2,
+    VK_ACCELERATION_STRUCTURE_MEMORY_REQUIREMENTS_TYPE_MAX_ENUM_NV = 0x7FFFFFFF
+} VkAccelerationStructureMemoryRequirementsTypeNV;
+
+typedef enum VkGeometryFlagBitsKHR {
+    VK_GEOMETRY_OPAQUE_BIT_KHR = 0x00000001,
+    VK_GEOMETRY_NO_DUPLICATE_ANY_HIT_INVOCATION_BIT_KHR = 0x00000002,
+    VK_GEOMETRY_OPAQUE_BIT_NV = VK_GEOMETRY_OPAQUE_BIT_KHR,
+    VK_GEOMETRY_NO_DUPLICATE_ANY_HIT_INVOCATION_BIT_NV = VK_GEOMETRY_NO_DUPLICATE_ANY_HIT_INVOCATION_BIT_KHR,
+    VK_GEOMETRY_FLAG_BITS_MAX_ENUM_KHR = 0x7FFFFFFF
+} VkGeometryFlagBitsKHR;
+typedef VkFlags VkGeometryFlagsKHR;
+typedef VkGeometryFlagsKHR VkGeometryFlagsNV;
+
+typedef VkGeometryFlagBitsKHR VkGeometryFlagBitsNV;
+
+
+typedef enum VkGeometryInstanceFlagBitsKHR {
+    VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR = 0x00000001,
+    VK_GEOMETRY_INSTANCE_TRIANGLE_FLIP_FACING_BIT_KHR = 0x00000002,
+    VK_GEOMETRY_INSTANCE_FORCE_OPAQUE_BIT_KHR = 0x00000004,
+    VK_GEOMETRY_INSTANCE_FORCE_NO_OPAQUE_BIT_KHR = 0x00000008,
+    VK_GEOMETRY_INSTANCE_TRIANGLE_FRONT_COUNTERCLOCKWISE_BIT_KHR = VK_GEOMETRY_INSTANCE_TRIANGLE_FLIP_FACING_BIT_KHR,
+    VK_GEOMETRY_INSTANCE_TRIANGLE_CULL_DISABLE_BIT_NV = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR,
+    VK_GEOMETRY_INSTANCE_TRIANGLE_FRONT_COUNTERCLOCKWISE_BIT_NV = VK_GEOMETRY_INSTANCE_TRIANGLE_FRONT_COUNTERCLOCKWISE_BIT_KHR,
+    VK_GEOMETRY_INSTANCE_FORCE_OPAQUE_BIT_NV = VK_GEOMETRY_INSTANCE_FORCE_OPAQUE_BIT_KHR,
+    VK_GEOMETRY_INSTANCE_FORCE_NO_OPAQUE_BIT_NV = VK_GEOMETRY_INSTANCE_FORCE_NO_OPAQUE_BIT_KHR,
+    VK_GEOMETRY_INSTANCE_FLAG_BITS_MAX_ENUM_KHR = 0x7FFFFFFF
+} VkGeometryInstanceFlagBitsKHR;
+typedef VkFlags VkGeometryInstanceFlagsKHR;
+typedef VkGeometryInstanceFlagsKHR VkGeometryInstanceFlagsNV;
+
+typedef VkGeometryInstanceFlagBitsKHR VkGeometryInstanceFlagBitsNV;
+
+
+typedef enum VkBuildAccelerationStructureFlagBitsKHR {
+    VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_UPDATE_BIT_KHR = 0x00000001,
+    VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_COMPACTION_BIT_KHR = 0x00000002,
+    VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR = 0x00000004,
+    VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_BUILD_BIT_KHR = 0x00000008,
+    VK_BUILD_ACCELERATION_STRUCTURE_LOW_MEMORY_BIT_KHR = 0x00000010,
+    VK_BUILD_ACCELERATION_STRUCTURE_MOTION_BIT_NV = 0x00000020,
+    VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_UPDATE_BIT_NV = VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_UPDATE_BIT_KHR,
+    VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_COMPACTION_BIT_NV = VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_COMPACTION_BIT_KHR,
+    VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_NV = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR,
+    VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_BUILD_BIT_NV = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_BUILD_BIT_KHR,
+    VK_BUILD_ACCELERATION_STRUCTURE_LOW_MEMORY_BIT_NV = VK_BUILD_ACCELERATION_STRUCTURE_LOW_MEMORY_BIT_KHR,
+    VK_BUILD_ACCELERATION_STRUCTURE_FLAG_BITS_MAX_ENUM_KHR = 0x7FFFFFFF
+} VkBuildAccelerationStructureFlagBitsKHR;
+typedef VkFlags VkBuildAccelerationStructureFlagsKHR;
+typedef VkBuildAccelerationStructureFlagsKHR VkBuildAccelerationStructureFlagsNV;
+
+typedef VkBuildAccelerationStructureFlagBitsKHR VkBuildAccelerationStructureFlagBitsNV;
+
+typedef struct VkRayTracingShaderGroupCreateInfoNV {
+    VkStructureType                   sType;
+    const void*                       pNext;
+    VkRayTracingShaderGroupTypeKHR    type;
+    uint32_t                          generalShader;
+    uint32_t                          closestHitShader;
+    uint32_t                          anyHitShader;
+    uint32_t                          intersectionShader;
+} VkRayTracingShaderGroupCreateInfoNV;
+
+typedef struct VkRayTracingPipelineCreateInfoNV {
+    VkStructureType                               sType;
+    const void*                                   pNext;
+    VkPipelineCreateFlags                         flags;
+    uint32_t                                      stageCount;
+    const VkPipelineShaderStageCreateInfo*        pStages;
+    uint32_t                                      groupCount;
+    const VkRayTracingShaderGroupCreateInfoNV*    pGroups;
+    uint32_t                                      maxRecursionDepth;
+    VkPipelineLayout                              layout;
+    VkPipeline                                    basePipelineHandle;
+    int32_t                                       basePipelineIndex;
+} VkRayTracingPipelineCreateInfoNV;
+
+typedef struct VkGeometryTrianglesNV {
+    VkStructureType    sType;
+    const void*        pNext;
+    VkBuffer           vertexData;
+    VkDeviceSize       vertexOffset;
+    uint32_t           vertexCount;
+    VkDeviceSize       vertexStride;
+    VkFormat           vertexFormat;
+    VkBuffer           indexData;
+    VkDeviceSize       indexOffset;
+    uint32_t           indexCount;
+    VkIndexType        indexType;
+    VkBuffer           transformData;
+    VkDeviceSize       transformOffset;
+} VkGeometryTrianglesNV;
+
+typedef struct VkGeometryAABBNV {
+    VkStructureType    sType;
+    const void*        pNext;
+    VkBuffer           aabbData;
+    uint32_t           numAABBs;
+    uint32_t           stride;
+    VkDeviceSize       offset;
+} VkGeometryAABBNV;
+
+typedef struct VkGeometryDataNV {
+    VkGeometryTrianglesNV    triangles;
+    VkGeometryAABBNV         aabbs;
+} VkGeometryDataNV;
+
+typedef struct VkGeometryNV {
+    VkStructureType       sType;
+    const void*           pNext;
+    VkGeometryTypeKHR     geometryType;
+    VkGeometryDataNV      geometry;
+    VkGeometryFlagsKHR    flags;
+} VkGeometryNV;
+
+typedef struct VkAccelerationStructureInfoNV {
+    VkStructureType                        sType;
+    const void*                            pNext;
+    VkAccelerationStructureTypeNV          type;
+    VkBuildAccelerationStructureFlagsNV    flags;
+    uint32_t                               instanceCount;
+    uint32_t                               geometryCount;
+    const VkGeometryNV*                    pGeometries;
+} VkAccelerationStructureInfoNV;
+
+typedef struct VkAccelerationStructureCreateInfoNV {
+    VkStructureType                  sType;
+    const void*                      pNext;
+    VkDeviceSize                     compactedSize;
+    VkAccelerationStructureInfoNV    info;
+} VkAccelerationStructureCreateInfoNV;
+
+typedef struct VkBindAccelerationStructureMemoryInfoNV {
+    VkStructureType              sType;
+    const void*                  pNext;
+    VkAccelerationStructureNV    accelerationStructure;
+    VkDeviceMemory               memory;
+    VkDeviceSize                 memoryOffset;
+    uint32_t                     deviceIndexCount;
+    const uint32_t*              pDeviceIndices;
+} VkBindAccelerationStructureMemoryInfoNV;
+
+typedef struct VkWriteDescriptorSetAccelerationStructureNV {
+    VkStructureType                     sType;
+    const void*                         pNext;
+    uint32_t                            accelerationStructureCount;
+    const VkAccelerationStructureNV*    pAccelerationStructures;
+} VkWriteDescriptorSetAccelerationStructureNV;
+
+typedef struct VkAccelerationStructureMemoryRequirementsInfoNV {
+    VkStructureType                                    sType;
+    const void*                                        pNext;
+    VkAccelerationStructureMemoryRequirementsTypeNV    type;
+    VkAccelerationStructureNV                          accelerationStructure;
+} VkAccelerationStructureMemoryRequirementsInfoNV;
+
+typedef struct VkPhysicalDeviceRayTracingPropertiesNV {
+    VkStructureType    sType;
+    void*              pNext;
+    uint32_t           shaderGroupHandleSize;
+    uint32_t           maxRecursionDepth;
+    uint32_t           maxShaderGroupStride;
+    uint32_t           shaderGroupBaseAlignment;
+    uint64_t           maxGeometryCount;
+    uint64_t           maxInstanceCount;
+    uint64_t           maxTriangleCount;
+    uint32_t           maxDescriptorSetAccelerationStructures;
+} VkPhysicalDeviceRayTracingPropertiesNV;
+
+typedef struct VkTransformMatrixKHR {
+    float    matrix[3][4];
+} VkTransformMatrixKHR;
+
+typedef VkTransformMatrixKHR VkTransformMatrixNV;
+
+typedef struct VkAabbPositionsKHR {
+    float    minX;
+    float    minY;
+    float    minZ;
+    float    maxX;
+    float    maxY;
+    float    maxZ;
+} VkAabbPositionsKHR;
+
+typedef VkAabbPositionsKHR VkAabbPositionsNV;
+
+typedef struct VkAccelerationStructureInstanceKHR {
+    VkTransformMatrixKHR          transform;
+    uint32_t                      instanceCustomIndex:24;
+    uint32_t                      mask:8;
+    uint32_t                      instanceShaderBindingTableRecordOffset:24;
+    VkGeometryInstanceFlagsKHR    flags:8;
+    uint64_t                      accelerationStructureReference;
+} VkAccelerationStructureInstanceKHR;
+
+typedef VkAccelerationStructureInstanceKHR VkAccelerationStructureInstanceNV;
+
+typedef VkResult (VKAPI_PTR *PFN_vkCreateAccelerationStructureNV)(VkDevice device, const VkAccelerationStructureCreateInfoNV* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkAccelerationStructureNV* pAccelerationStructure);
+typedef void (VKAPI_PTR *PFN_vkDestroyAccelerationStructureNV)(VkDevice device, VkAccelerationStructureNV accelerationStructure, const VkAllocationCallbacks* pAllocator);
+typedef void (VKAPI_PTR *PFN_vkGetAccelerationStructureMemoryRequirementsNV)(VkDevice device, const VkAccelerationStructureMemoryRequirementsInfoNV* pInfo, VkMemoryRequirements2KHR* pMemoryRequirements);
+typedef VkResult (VKAPI_PTR *PFN_vkBindAccelerationStructureMemoryNV)(VkDevice device, uint32_t bindInfoCount, const VkBindAccelerationStructureMemoryInfoNV* pBindInfos);
+typedef void (VKAPI_PTR *PFN_vkCmdBuildAccelerationStructureNV)(VkCommandBuffer commandBuffer, const VkAccelerationStructureInfoNV* pInfo, VkBuffer instanceData, VkDeviceSize instanceOffset, VkBool32 update, VkAccelerationStructureNV dst, VkAccelerationStructureNV src, VkBuffer scratch, VkDeviceSize scratchOffset);
+typedef void (VKAPI_PTR *PFN_vkCmdCopyAccelerationStructureNV)(VkCommandBuffer commandBuffer, VkAccelerationStructureNV dst, VkAccelerationStructureNV src, VkCopyAccelerationStructureModeKHR mode);
+typedef void (VKAPI_PTR *PFN_vkCmdTraceRaysNV)(VkCommandBuffer commandBuffer, VkBuffer raygenShaderBindingTableBuffer, VkDeviceSize raygenShaderBindingOffset, VkBuffer missShaderBindingTableBuffer, VkDeviceSize missShaderBindingOffset, VkDeviceSize missShaderBindingStride, VkBuffer hitShaderBindingTableBuffer, VkDeviceSize hitShaderBindingOffset, VkDeviceSize hitShaderBindingStride, VkBuffer callableShaderBindingTableBuffer, VkDeviceSize callableShaderBindingOffset, VkDeviceSize callableShaderBindingStride, uint32_t width, uint32_t height, uint32_t depth);
+typedef VkResult (VKAPI_PTR *PFN_vkCreateRayTracingPipelinesNV)(VkDevice device, VkPipelineCache pipelineCache, uint32_t createInfoCount, const VkRayTracingPipelineCreateInfoNV* pCreateInfos, const VkAllocationCallbacks* pAllocator, VkPipeline* pPipelines);
+typedef VkResult (VKAPI_PTR *PFN_vkGetRayTracingShaderGroupHandlesKHR)(VkDevice device, VkPipeline pipeline, uint32_t firstGroup, uint32_t groupCount, size_t dataSize, void* pData);
+typedef VkResult (VKAPI_PTR *PFN_vkGetRayTracingShaderGroupHandlesNV)(VkDevice device, VkPipeline pipeline, uint32_t firstGroup, uint32_t groupCount, size_t dataSize, void* pData);
+typedef VkResult (VKAPI_PTR *PFN_vkGetAccelerationStructureHandleNV)(VkDevice device, VkAccelerationStructureNV accelerationStructure, size_t dataSize, void* pData);
+typedef void (VKAPI_PTR *PFN_vkCmdWriteAccelerationStructuresPropertiesNV)(VkCommandBuffer commandBuffer, uint32_t accelerationStructureCount, const VkAccelerationStructureNV* pAccelerationStructures, VkQueryType queryType, VkQueryPool queryPool, uint32_t firstQuery);
+typedef VkResult (VKAPI_PTR *PFN_vkCompileDeferredNV)(VkDevice device, VkPipeline pipeline, uint32_t shader);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateAccelerationStructureNV(
+    VkDevice                                    device,
+    const VkAccelerationStructureCreateInfoNV*  pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkAccelerationStructureNV*                  pAccelerationStructure);
+
+VKAPI_ATTR void VKAPI_CALL vkDestroyAccelerationStructureNV(
+    VkDevice                                    device,
+    VkAccelerationStructureNV                   accelerationStructure,
+    const VkAllocationCallbacks*                pAllocator);
+
+VKAPI_ATTR void VKAPI_CALL vkGetAccelerationStructureMemoryRequirementsNV(
+    VkDevice                                    device,
+    const VkAccelerationStructureMemoryRequirementsInfoNV* pInfo,
+    VkMemoryRequirements2KHR*                   pMemoryRequirements);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkBindAccelerationStructureMemoryNV(
+    VkDevice                                    device,
+    uint32_t                                    bindInfoCount,
+    const VkBindAccelerationStructureMemoryInfoNV* pBindInfos);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdBuildAccelerationStructureNV(
+    VkCommandBuffer                             commandBuffer,
+    const VkAccelerationStructureInfoNV*        pInfo,
+    VkBuffer                                    instanceData,
+    VkDeviceSize                                instanceOffset,
+    VkBool32                                    update,
+    VkAccelerationStructureNV                   dst,
+    VkAccelerationStructureNV                   src,
+    VkBuffer                                    scratch,
+    VkDeviceSize                                scratchOffset);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdCopyAccelerationStructureNV(
+    VkCommandBuffer                             commandBuffer,
+    VkAccelerationStructureNV                   dst,
+    VkAccelerationStructureNV                   src,
+    VkCopyAccelerationStructureModeKHR          mode);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdTraceRaysNV(
+    VkCommandBuffer                             commandBuffer,
+    VkBuffer                                    raygenShaderBindingTableBuffer,
+    VkDeviceSize                                raygenShaderBindingOffset,
+    VkBuffer                                    missShaderBindingTableBuffer,
+    VkDeviceSize                                missShaderBindingOffset,
+    VkDeviceSize                                missShaderBindingStride,
+    VkBuffer                                    hitShaderBindingTableBuffer,
+    VkDeviceSize                                hitShaderBindingOffset,
+    VkDeviceSize                                hitShaderBindingStride,
+    VkBuffer                                    callableShaderBindingTableBuffer,
+    VkDeviceSize                                callableShaderBindingOffset,
+    VkDeviceSize                                callableShaderBindingStride,
+    uint32_t                                    width,
+    uint32_t                                    height,
+    uint32_t                                    depth);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateRayTracingPipelinesNV(
+    VkDevice                                    device,
+    VkPipelineCache                             pipelineCache,
+    uint32_t                                    createInfoCount,
+    const VkRayTracingPipelineCreateInfoNV*     pCreateInfos,
+    const VkAllocationCallbacks*                pAllocator,
+    VkPipeline*                                 pPipelines);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetRayTracingShaderGroupHandlesKHR(
+    VkDevice                                    device,
+    VkPipeline                                  pipeline,
+    uint32_t                                    firstGroup,
+    uint32_t                                    groupCount,
+    size_t                                      dataSize,
+    void*                                       pData);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetRayTracingShaderGroupHandlesNV(
+    VkDevice                                    device,
+    VkPipeline                                  pipeline,
+    uint32_t                                    firstGroup,
+    uint32_t                                    groupCount,
+    size_t                                      dataSize,
+    void*                                       pData);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetAccelerationStructureHandleNV(
+    VkDevice                                    device,
+    VkAccelerationStructureNV                   accelerationStructure,
+    size_t                                      dataSize,
+    void*                                       pData);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdWriteAccelerationStructuresPropertiesNV(
+    VkCommandBuffer                             commandBuffer,
+    uint32_t                                    accelerationStructureCount,
+    const VkAccelerationStructureNV*            pAccelerationStructures,
+    VkQueryType                                 queryType,
+    VkQueryPool                                 queryPool,
+    uint32_t                                    firstQuery);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkCompileDeferredNV(
+    VkDevice                                    device,
+    VkPipeline                                  pipeline,
+    uint32_t                                    shader);
+#endif
+
+
+#define VK_NV_representative_fragment_test 1
+#define VK_NV_REPRESENTATIVE_FRAGMENT_TEST_SPEC_VERSION 2
+#define VK_NV_REPRESENTATIVE_FRAGMENT_TEST_EXTENSION_NAME "VK_NV_representative_fragment_test"
+typedef struct VkPhysicalDeviceRepresentativeFragmentTestFeaturesNV {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           representativeFragmentTest;
+} VkPhysicalDeviceRepresentativeFragmentTestFeaturesNV;
+
+typedef struct VkPipelineRepresentativeFragmentTestStateCreateInfoNV {
+    VkStructureType    sType;
+    const void*        pNext;
+    VkBool32           representativeFragmentTestEnable;
+} VkPipelineRepresentativeFragmentTestStateCreateInfoNV;
+
+
+
+#define VK_EXT_filter_cubic 1
+#define VK_EXT_FILTER_CUBIC_SPEC_VERSION  3
+#define VK_EXT_FILTER_CUBIC_EXTENSION_NAME "VK_EXT_filter_cubic"
+typedef struct VkPhysicalDeviceImageViewImageFormatInfoEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    VkImageViewType    imageViewType;
+} VkPhysicalDeviceImageViewImageFormatInfoEXT;
+
+typedef struct VkFilterCubicImageViewImageFormatPropertiesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           filterCubic;
+    VkBool32           filterCubicMinmax;
+} VkFilterCubicImageViewImageFormatPropertiesEXT;
+
+
+
+#define VK_QCOM_render_pass_shader_resolve 1
+#define VK_QCOM_RENDER_PASS_SHADER_RESOLVE_SPEC_VERSION 4
+#define VK_QCOM_RENDER_PASS_SHADER_RESOLVE_EXTENSION_NAME "VK_QCOM_render_pass_shader_resolve"
+
+
+#define VK_EXT_global_priority 1
+#define VK_EXT_GLOBAL_PRIORITY_SPEC_VERSION 2
+#define VK_EXT_GLOBAL_PRIORITY_EXTENSION_NAME "VK_EXT_global_priority"
+
+typedef enum VkQueueGlobalPriorityEXT {
+    VK_QUEUE_GLOBAL_PRIORITY_LOW_EXT = 128,
+    VK_QUEUE_GLOBAL_PRIORITY_MEDIUM_EXT = 256,
+    VK_QUEUE_GLOBAL_PRIORITY_HIGH_EXT = 512,
+    VK_QUEUE_GLOBAL_PRIORITY_REALTIME_EXT = 1024,
+    VK_QUEUE_GLOBAL_PRIORITY_MAX_ENUM_EXT = 0x7FFFFFFF
+} VkQueueGlobalPriorityEXT;
+typedef struct VkDeviceQueueGlobalPriorityCreateInfoEXT {
+    VkStructureType             sType;
+    const void*                 pNext;
+    VkQueueGlobalPriorityEXT    globalPriority;
+} VkDeviceQueueGlobalPriorityCreateInfoEXT;
+
+
+
+#define VK_EXT_external_memory_host 1
+#define VK_EXT_EXTERNAL_MEMORY_HOST_SPEC_VERSION 1
+#define VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME "VK_EXT_external_memory_host"
+typedef struct VkImportMemoryHostPointerInfoEXT {
+    VkStructureType                       sType;
+    const void*                           pNext;
+    VkExternalMemoryHandleTypeFlagBits    handleType;
+    void*                                 pHostPointer;
+} VkImportMemoryHostPointerInfoEXT;
+
+typedef struct VkMemoryHostPointerPropertiesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    uint32_t           memoryTypeBits;
+} VkMemoryHostPointerPropertiesEXT;
+
+typedef struct VkPhysicalDeviceExternalMemoryHostPropertiesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    VkDeviceSize       minImportedHostPointerAlignment;
+} VkPhysicalDeviceExternalMemoryHostPropertiesEXT;
+
+typedef VkResult (VKAPI_PTR *PFN_vkGetMemoryHostPointerPropertiesEXT)(VkDevice device, VkExternalMemoryHandleTypeFlagBits handleType, const void* pHostPointer, VkMemoryHostPointerPropertiesEXT* pMemoryHostPointerProperties);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkGetMemoryHostPointerPropertiesEXT(
+    VkDevice                                    device,
+    VkExternalMemoryHandleTypeFlagBits          handleType,
+    const void*                                 pHostPointer,
+    VkMemoryHostPointerPropertiesEXT*           pMemoryHostPointerProperties);
+#endif
+
+
+#define VK_AMD_buffer_marker 1
+#define VK_AMD_BUFFER_MARKER_SPEC_VERSION 1
+#define VK_AMD_BUFFER_MARKER_EXTENSION_NAME "VK_AMD_buffer_marker"
+typedef void (VKAPI_PTR *PFN_vkCmdWriteBufferMarkerAMD)(VkCommandBuffer commandBuffer, VkPipelineStageFlagBits pipelineStage, VkBuffer dstBuffer, VkDeviceSize dstOffset, uint32_t marker);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR void VKAPI_CALL vkCmdWriteBufferMarkerAMD(
+    VkCommandBuffer                             commandBuffer,
+    VkPipelineStageFlagBits                     pipelineStage,
+    VkBuffer                                    dstBuffer,
+    VkDeviceSize                                dstOffset,
+    uint32_t                                    marker);
+#endif
+
+
+#define VK_AMD_pipeline_compiler_control 1
+#define VK_AMD_PIPELINE_COMPILER_CONTROL_SPEC_VERSION 1
+#define VK_AMD_PIPELINE_COMPILER_CONTROL_EXTENSION_NAME "VK_AMD_pipeline_compiler_control"
+
+typedef enum VkPipelineCompilerControlFlagBitsAMD {
+    VK_PIPELINE_COMPILER_CONTROL_FLAG_BITS_MAX_ENUM_AMD = 0x7FFFFFFF
+} VkPipelineCompilerControlFlagBitsAMD;
+typedef VkFlags VkPipelineCompilerControlFlagsAMD;
+typedef struct VkPipelineCompilerControlCreateInfoAMD {
+    VkStructureType                      sType;
+    const void*                          pNext;
+    VkPipelineCompilerControlFlagsAMD    compilerControlFlags;
+} VkPipelineCompilerControlCreateInfoAMD;
+
+
+
+#define VK_EXT_calibrated_timestamps 1
+#define VK_EXT_CALIBRATED_TIMESTAMPS_SPEC_VERSION 2
+#define VK_EXT_CALIBRATED_TIMESTAMPS_EXTENSION_NAME "VK_EXT_calibrated_timestamps"
+
+typedef enum VkTimeDomainEXT {
+    VK_TIME_DOMAIN_DEVICE_EXT = 0,
+    VK_TIME_DOMAIN_CLOCK_MONOTONIC_EXT = 1,
+    VK_TIME_DOMAIN_CLOCK_MONOTONIC_RAW_EXT = 2,
+    VK_TIME_DOMAIN_QUERY_PERFORMANCE_COUNTER_EXT = 3,
+    VK_TIME_DOMAIN_MAX_ENUM_EXT = 0x7FFFFFFF
+} VkTimeDomainEXT;
+typedef struct VkCalibratedTimestampInfoEXT {
+    VkStructureType    sType;
+    const void*        pNext;
+    VkTimeDomainEXT    timeDomain;
+} VkCalibratedTimestampInfoEXT;
+
+typedef VkResult (VKAPI_PTR *PFN_vkGetPhysicalDeviceCalibrateableTimeDomainsEXT)(VkPhysicalDevice physicalDevice, uint32_t* pTimeDomainCount, VkTimeDomainEXT* pTimeDomains);
+typedef VkResult (VKAPI_PTR *PFN_vkGetCalibratedTimestampsEXT)(VkDevice device, uint32_t timestampCount, const VkCalibratedTimestampInfoEXT* pTimestampInfos, uint64_t* pTimestamps, uint64_t* pMaxDeviation);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceCalibrateableTimeDomainsEXT(
+    VkPhysicalDevice                            physicalDevice,
+    uint32_t*                                   pTimeDomainCount,
+    VkTimeDomainEXT*                            pTimeDomains);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetCalibratedTimestampsEXT(
+    VkDevice                                    device,
+    uint32_t                                    timestampCount,
+    const VkCalibratedTimestampInfoEXT*         pTimestampInfos,
+    uint64_t*                                   pTimestamps,
+    uint64_t*                                   pMaxDeviation);
+#endif
+
+
+#define VK_AMD_shader_core_properties 1
+#define VK_AMD_SHADER_CORE_PROPERTIES_SPEC_VERSION 2
+#define VK_AMD_SHADER_CORE_PROPERTIES_EXTENSION_NAME "VK_AMD_shader_core_properties"
+typedef struct VkPhysicalDeviceShaderCorePropertiesAMD {
+    VkStructureType    sType;
+    void*              pNext;
+    uint32_t           shaderEngineCount;
+    uint32_t           shaderArraysPerEngineCount;
+    uint32_t           computeUnitsPerShaderArray;
+    uint32_t           simdPerComputeUnit;
+    uint32_t           wavefrontsPerSimd;
+    uint32_t           wavefrontSize;
+    uint32_t           sgprsPerSimd;
+    uint32_t           minSgprAllocation;
+    uint32_t           maxSgprAllocation;
+    uint32_t           sgprAllocationGranularity;
+    uint32_t           vgprsPerSimd;
+    uint32_t           minVgprAllocation;
+    uint32_t           maxVgprAllocation;
+    uint32_t           vgprAllocationGranularity;
+} VkPhysicalDeviceShaderCorePropertiesAMD;
+
+
+
+#define VK_AMD_memory_overallocation_behavior 1
+#define VK_AMD_MEMORY_OVERALLOCATION_BEHAVIOR_SPEC_VERSION 1
+#define VK_AMD_MEMORY_OVERALLOCATION_BEHAVIOR_EXTENSION_NAME "VK_AMD_memory_overallocation_behavior"
+
+typedef enum VkMemoryOverallocationBehaviorAMD {
+    VK_MEMORY_OVERALLOCATION_BEHAVIOR_DEFAULT_AMD = 0,
+    VK_MEMORY_OVERALLOCATION_BEHAVIOR_ALLOWED_AMD = 1,
+    VK_MEMORY_OVERALLOCATION_BEHAVIOR_DISALLOWED_AMD = 2,
+    VK_MEMORY_OVERALLOCATION_BEHAVIOR_MAX_ENUM_AMD = 0x7FFFFFFF
+} VkMemoryOverallocationBehaviorAMD;
+typedef struct VkDeviceMemoryOverallocationCreateInfoAMD {
+    VkStructureType                      sType;
+    const void*                          pNext;
+    VkMemoryOverallocationBehaviorAMD    overallocationBehavior;
+} VkDeviceMemoryOverallocationCreateInfoAMD;
+
+
+
+#define VK_EXT_vertex_attribute_divisor 1
+#define VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_SPEC_VERSION 3
+#define VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME "VK_EXT_vertex_attribute_divisor"
+typedef struct VkPhysicalDeviceVertexAttributeDivisorPropertiesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    uint32_t           maxVertexAttribDivisor;
+} VkPhysicalDeviceVertexAttributeDivisorPropertiesEXT;
+
+typedef struct VkVertexInputBindingDivisorDescriptionEXT {
+    uint32_t    binding;
+    uint32_t    divisor;
+} VkVertexInputBindingDivisorDescriptionEXT;
+
+typedef struct VkPipelineVertexInputDivisorStateCreateInfoEXT {
+    VkStructureType                                     sType;
+    const void*                                         pNext;
+    uint32_t                                            vertexBindingDivisorCount;
+    const VkVertexInputBindingDivisorDescriptionEXT*    pVertexBindingDivisors;
+} VkPipelineVertexInputDivisorStateCreateInfoEXT;
+
+typedef struct VkPhysicalDeviceVertexAttributeDivisorFeaturesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           vertexAttributeInstanceRateDivisor;
+    VkBool32           vertexAttributeInstanceRateZeroDivisor;
+} VkPhysicalDeviceVertexAttributeDivisorFeaturesEXT;
+
+
+
+#define VK_EXT_pipeline_creation_feedback 1
+#define VK_EXT_PIPELINE_CREATION_FEEDBACK_SPEC_VERSION 1
+#define VK_EXT_PIPELINE_CREATION_FEEDBACK_EXTENSION_NAME "VK_EXT_pipeline_creation_feedback"
+
+typedef enum VkPipelineCreationFeedbackFlagBitsEXT {
+    VK_PIPELINE_CREATION_FEEDBACK_VALID_BIT_EXT = 0x00000001,
+    VK_PIPELINE_CREATION_FEEDBACK_APPLICATION_PIPELINE_CACHE_HIT_BIT_EXT = 0x00000002,
+    VK_PIPELINE_CREATION_FEEDBACK_BASE_PIPELINE_ACCELERATION_BIT_EXT = 0x00000004,
+    VK_PIPELINE_CREATION_FEEDBACK_FLAG_BITS_MAX_ENUM_EXT = 0x7FFFFFFF
+} VkPipelineCreationFeedbackFlagBitsEXT;
+typedef VkFlags VkPipelineCreationFeedbackFlagsEXT;
+typedef struct VkPipelineCreationFeedbackEXT {
+    VkPipelineCreationFeedbackFlagsEXT    flags;
+    uint64_t                              duration;
+} VkPipelineCreationFeedbackEXT;
+
+typedef struct VkPipelineCreationFeedbackCreateInfoEXT {
+    VkStructureType                   sType;
+    const void*                       pNext;
+    VkPipelineCreationFeedbackEXT*    pPipelineCreationFeedback;
+    uint32_t                          pipelineStageCreationFeedbackCount;
+    VkPipelineCreationFeedbackEXT*    pPipelineStageCreationFeedbacks;
+} VkPipelineCreationFeedbackCreateInfoEXT;
+
+
+
+#define VK_NV_shader_subgroup_partitioned 1
+#define VK_NV_SHADER_SUBGROUP_PARTITIONED_SPEC_VERSION 1
+#define VK_NV_SHADER_SUBGROUP_PARTITIONED_EXTENSION_NAME "VK_NV_shader_subgroup_partitioned"
+
+
+#define VK_NV_compute_shader_derivatives 1
+#define VK_NV_COMPUTE_SHADER_DERIVATIVES_SPEC_VERSION 1
+#define VK_NV_COMPUTE_SHADER_DERIVATIVES_EXTENSION_NAME "VK_NV_compute_shader_derivatives"
+typedef struct VkPhysicalDeviceComputeShaderDerivativesFeaturesNV {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           computeDerivativeGroupQuads;
+    VkBool32           computeDerivativeGroupLinear;
+} VkPhysicalDeviceComputeShaderDerivativesFeaturesNV;
+
+
+
+#define VK_NV_mesh_shader 1
+#define VK_NV_MESH_SHADER_SPEC_VERSION    1
+#define VK_NV_MESH_SHADER_EXTENSION_NAME  "VK_NV_mesh_shader"
+typedef struct VkPhysicalDeviceMeshShaderFeaturesNV {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           taskShader;
+    VkBool32           meshShader;
+} VkPhysicalDeviceMeshShaderFeaturesNV;
+
+typedef struct VkPhysicalDeviceMeshShaderPropertiesNV {
+    VkStructureType    sType;
+    void*              pNext;
+    uint32_t           maxDrawMeshTasksCount;
+    uint32_t           maxTaskWorkGroupInvocations;
+    uint32_t           maxTaskWorkGroupSize[3];
+    uint32_t           maxTaskTotalMemorySize;
+    uint32_t           maxTaskOutputCount;
+    uint32_t           maxMeshWorkGroupInvocations;
+    uint32_t           maxMeshWorkGroupSize[3];
+    uint32_t           maxMeshTotalMemorySize;
+    uint32_t           maxMeshOutputVertices;
+    uint32_t           maxMeshOutputPrimitives;
+    uint32_t           maxMeshMultiviewViewCount;
+    uint32_t           meshOutputPerVertexGranularity;
+    uint32_t           meshOutputPerPrimitiveGranularity;
+} VkPhysicalDeviceMeshShaderPropertiesNV;
+
+typedef struct VkDrawMeshTasksIndirectCommandNV {
+    uint32_t    taskCount;
+    uint32_t    firstTask;
+} VkDrawMeshTasksIndirectCommandNV;
+
+typedef void (VKAPI_PTR *PFN_vkCmdDrawMeshTasksNV)(VkCommandBuffer commandBuffer, uint32_t taskCount, uint32_t firstTask);
+typedef void (VKAPI_PTR *PFN_vkCmdDrawMeshTasksIndirectNV)(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset, uint32_t drawCount, uint32_t stride);
+typedef void (VKAPI_PTR *PFN_vkCmdDrawMeshTasksIndirectCountNV)(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset, VkBuffer countBuffer, VkDeviceSize countBufferOffset, uint32_t maxDrawCount, uint32_t stride);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR void VKAPI_CALL vkCmdDrawMeshTasksNV(
+    VkCommandBuffer                             commandBuffer,
+    uint32_t                                    taskCount,
+    uint32_t                                    firstTask);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdDrawMeshTasksIndirectNV(
+    VkCommandBuffer                             commandBuffer,
+    VkBuffer                                    buffer,
+    VkDeviceSize                                offset,
+    uint32_t                                    drawCount,
+    uint32_t                                    stride);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdDrawMeshTasksIndirectCountNV(
+    VkCommandBuffer                             commandBuffer,
+    VkBuffer                                    buffer,
+    VkDeviceSize                                offset,
+    VkBuffer                                    countBuffer,
+    VkDeviceSize                                countBufferOffset,
+    uint32_t                                    maxDrawCount,
+    uint32_t                                    stride);
+#endif
+
+
+#define VK_NV_fragment_shader_barycentric 1
+#define VK_NV_FRAGMENT_SHADER_BARYCENTRIC_SPEC_VERSION 1
+#define VK_NV_FRAGMENT_SHADER_BARYCENTRIC_EXTENSION_NAME "VK_NV_fragment_shader_barycentric"
+typedef struct VkPhysicalDeviceFragmentShaderBarycentricFeaturesNV {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           fragmentShaderBarycentric;
+} VkPhysicalDeviceFragmentShaderBarycentricFeaturesNV;
+
+
+
+#define VK_NV_shader_image_footprint 1
+#define VK_NV_SHADER_IMAGE_FOOTPRINT_SPEC_VERSION 2
+#define VK_NV_SHADER_IMAGE_FOOTPRINT_EXTENSION_NAME "VK_NV_shader_image_footprint"
+typedef struct VkPhysicalDeviceShaderImageFootprintFeaturesNV {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           imageFootprint;
+} VkPhysicalDeviceShaderImageFootprintFeaturesNV;
+
+
+
+#define VK_NV_scissor_exclusive 1
+#define VK_NV_SCISSOR_EXCLUSIVE_SPEC_VERSION 1
+#define VK_NV_SCISSOR_EXCLUSIVE_EXTENSION_NAME "VK_NV_scissor_exclusive"
+typedef struct VkPipelineViewportExclusiveScissorStateCreateInfoNV {
+    VkStructureType    sType;
+    const void*        pNext;
+    uint32_t           exclusiveScissorCount;
+    const VkRect2D*    pExclusiveScissors;
+} VkPipelineViewportExclusiveScissorStateCreateInfoNV;
+
+typedef struct VkPhysicalDeviceExclusiveScissorFeaturesNV {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           exclusiveScissor;
+} VkPhysicalDeviceExclusiveScissorFeaturesNV;
+
+typedef void (VKAPI_PTR *PFN_vkCmdSetExclusiveScissorNV)(VkCommandBuffer commandBuffer, uint32_t firstExclusiveScissor, uint32_t exclusiveScissorCount, const VkRect2D* pExclusiveScissors);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR void VKAPI_CALL vkCmdSetExclusiveScissorNV(
+    VkCommandBuffer                             commandBuffer,
+    uint32_t                                    firstExclusiveScissor,
+    uint32_t                                    exclusiveScissorCount,
+    const VkRect2D*                             pExclusiveScissors);
+#endif
+
+
+#define VK_NV_device_diagnostic_checkpoints 1
+#define VK_NV_DEVICE_DIAGNOSTIC_CHECKPOINTS_SPEC_VERSION 2
+#define VK_NV_DEVICE_DIAGNOSTIC_CHECKPOINTS_EXTENSION_NAME "VK_NV_device_diagnostic_checkpoints"
+typedef struct VkQueueFamilyCheckpointPropertiesNV {
+    VkStructureType         sType;
+    void*                   pNext;
+    VkPipelineStageFlags    checkpointExecutionStageMask;
+} VkQueueFamilyCheckpointPropertiesNV;
+
+typedef struct VkCheckpointDataNV {
+    VkStructureType            sType;
+    void*                      pNext;
+    VkPipelineStageFlagBits    stage;
+    void*                      pCheckpointMarker;
+} VkCheckpointDataNV;
+
+typedef void (VKAPI_PTR *PFN_vkCmdSetCheckpointNV)(VkCommandBuffer commandBuffer, const void* pCheckpointMarker);
+typedef void (VKAPI_PTR *PFN_vkGetQueueCheckpointDataNV)(VkQueue queue, uint32_t* pCheckpointDataCount, VkCheckpointDataNV* pCheckpointData);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR void VKAPI_CALL vkCmdSetCheckpointNV(
+    VkCommandBuffer                             commandBuffer,
+    const void*                                 pCheckpointMarker);
+
+VKAPI_ATTR void VKAPI_CALL vkGetQueueCheckpointDataNV(
+    VkQueue                                     queue,
+    uint32_t*                                   pCheckpointDataCount,
+    VkCheckpointDataNV*                         pCheckpointData);
+#endif
+
+
+#define VK_INTEL_shader_integer_functions2 1
+#define VK_INTEL_SHADER_INTEGER_FUNCTIONS_2_SPEC_VERSION 1
+#define VK_INTEL_SHADER_INTEGER_FUNCTIONS_2_EXTENSION_NAME "VK_INTEL_shader_integer_functions2"
+typedef struct VkPhysicalDeviceShaderIntegerFunctions2FeaturesINTEL {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           shaderIntegerFunctions2;
+} VkPhysicalDeviceShaderIntegerFunctions2FeaturesINTEL;
+
+
+
+#define VK_INTEL_performance_query 1
+VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkPerformanceConfigurationINTEL)
+#define VK_INTEL_PERFORMANCE_QUERY_SPEC_VERSION 2
+#define VK_INTEL_PERFORMANCE_QUERY_EXTENSION_NAME "VK_INTEL_performance_query"
+
+typedef enum VkPerformanceConfigurationTypeINTEL {
+    VK_PERFORMANCE_CONFIGURATION_TYPE_COMMAND_QUEUE_METRICS_DISCOVERY_ACTIVATED_INTEL = 0,
+    VK_PERFORMANCE_CONFIGURATION_TYPE_MAX_ENUM_INTEL = 0x7FFFFFFF
+} VkPerformanceConfigurationTypeINTEL;
+
+typedef enum VkQueryPoolSamplingModeINTEL {
+    VK_QUERY_POOL_SAMPLING_MODE_MANUAL_INTEL = 0,
+    VK_QUERY_POOL_SAMPLING_MODE_MAX_ENUM_INTEL = 0x7FFFFFFF
+} VkQueryPoolSamplingModeINTEL;
+
+typedef enum VkPerformanceOverrideTypeINTEL {
+    VK_PERFORMANCE_OVERRIDE_TYPE_NULL_HARDWARE_INTEL = 0,
+    VK_PERFORMANCE_OVERRIDE_TYPE_FLUSH_GPU_CACHES_INTEL = 1,
+    VK_PERFORMANCE_OVERRIDE_TYPE_MAX_ENUM_INTEL = 0x7FFFFFFF
+} VkPerformanceOverrideTypeINTEL;
+
+typedef enum VkPerformanceParameterTypeINTEL {
+    VK_PERFORMANCE_PARAMETER_TYPE_HW_COUNTERS_SUPPORTED_INTEL = 0,
+    VK_PERFORMANCE_PARAMETER_TYPE_STREAM_MARKER_VALID_BITS_INTEL = 1,
+    VK_PERFORMANCE_PARAMETER_TYPE_MAX_ENUM_INTEL = 0x7FFFFFFF
+} VkPerformanceParameterTypeINTEL;
+
+typedef enum VkPerformanceValueTypeINTEL {
+    VK_PERFORMANCE_VALUE_TYPE_UINT32_INTEL = 0,
+    VK_PERFORMANCE_VALUE_TYPE_UINT64_INTEL = 1,
+    VK_PERFORMANCE_VALUE_TYPE_FLOAT_INTEL = 2,
+    VK_PERFORMANCE_VALUE_TYPE_BOOL_INTEL = 3,
+    VK_PERFORMANCE_VALUE_TYPE_STRING_INTEL = 4,
+    VK_PERFORMANCE_VALUE_TYPE_MAX_ENUM_INTEL = 0x7FFFFFFF
+} VkPerformanceValueTypeINTEL;
+typedef union VkPerformanceValueDataINTEL {
+    uint32_t       value32;
+    uint64_t       value64;
+    float          valueFloat;
+    VkBool32       valueBool;
+    const char*    valueString;
+} VkPerformanceValueDataINTEL;
+
+typedef struct VkPerformanceValueINTEL {
+    VkPerformanceValueTypeINTEL    type;
+    VkPerformanceValueDataINTEL    data;
+} VkPerformanceValueINTEL;
+
+typedef struct VkInitializePerformanceApiInfoINTEL {
+    VkStructureType    sType;
+    const void*        pNext;
+    void*              pUserData;
+} VkInitializePerformanceApiInfoINTEL;
+
+typedef struct VkQueryPoolPerformanceQueryCreateInfoINTEL {
+    VkStructureType                 sType;
+    const void*                     pNext;
+    VkQueryPoolSamplingModeINTEL    performanceCountersSampling;
+} VkQueryPoolPerformanceQueryCreateInfoINTEL;
+
+typedef VkQueryPoolPerformanceQueryCreateInfoINTEL VkQueryPoolCreateInfoINTEL;
+
+typedef struct VkPerformanceMarkerInfoINTEL {
+    VkStructureType    sType;
+    const void*        pNext;
+    uint64_t           marker;
+} VkPerformanceMarkerInfoINTEL;
+
+typedef struct VkPerformanceStreamMarkerInfoINTEL {
+    VkStructureType    sType;
+    const void*        pNext;
+    uint32_t           marker;
+} VkPerformanceStreamMarkerInfoINTEL;
+
+typedef struct VkPerformanceOverrideInfoINTEL {
+    VkStructureType                   sType;
+    const void*                       pNext;
+    VkPerformanceOverrideTypeINTEL    type;
+    VkBool32                          enable;
+    uint64_t                          parameter;
+} VkPerformanceOverrideInfoINTEL;
+
+typedef struct VkPerformanceConfigurationAcquireInfoINTEL {
+    VkStructureType                        sType;
+    const void*                            pNext;
+    VkPerformanceConfigurationTypeINTEL    type;
+} VkPerformanceConfigurationAcquireInfoINTEL;
+
+typedef VkResult (VKAPI_PTR *PFN_vkInitializePerformanceApiINTEL)(VkDevice device, const VkInitializePerformanceApiInfoINTEL* pInitializeInfo);
+typedef void (VKAPI_PTR *PFN_vkUninitializePerformanceApiINTEL)(VkDevice device);
+typedef VkResult (VKAPI_PTR *PFN_vkCmdSetPerformanceMarkerINTEL)(VkCommandBuffer commandBuffer, const VkPerformanceMarkerInfoINTEL* pMarkerInfo);
+typedef VkResult (VKAPI_PTR *PFN_vkCmdSetPerformanceStreamMarkerINTEL)(VkCommandBuffer commandBuffer, const VkPerformanceStreamMarkerInfoINTEL* pMarkerInfo);
+typedef VkResult (VKAPI_PTR *PFN_vkCmdSetPerformanceOverrideINTEL)(VkCommandBuffer commandBuffer, const VkPerformanceOverrideInfoINTEL* pOverrideInfo);
+typedef VkResult (VKAPI_PTR *PFN_vkAcquirePerformanceConfigurationINTEL)(VkDevice device, const VkPerformanceConfigurationAcquireInfoINTEL* pAcquireInfo, VkPerformanceConfigurationINTEL* pConfiguration);
+typedef VkResult (VKAPI_PTR *PFN_vkReleasePerformanceConfigurationINTEL)(VkDevice device, VkPerformanceConfigurationINTEL configuration);
+typedef VkResult (VKAPI_PTR *PFN_vkQueueSetPerformanceConfigurationINTEL)(VkQueue queue, VkPerformanceConfigurationINTEL configuration);
+typedef VkResult (VKAPI_PTR *PFN_vkGetPerformanceParameterINTEL)(VkDevice device, VkPerformanceParameterTypeINTEL parameter, VkPerformanceValueINTEL* pValue);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkInitializePerformanceApiINTEL(
+    VkDevice                                    device,
+    const VkInitializePerformanceApiInfoINTEL*  pInitializeInfo);
+
+VKAPI_ATTR void VKAPI_CALL vkUninitializePerformanceApiINTEL(
+    VkDevice                                    device);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkCmdSetPerformanceMarkerINTEL(
+    VkCommandBuffer                             commandBuffer,
+    const VkPerformanceMarkerInfoINTEL*         pMarkerInfo);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkCmdSetPerformanceStreamMarkerINTEL(
+    VkCommandBuffer                             commandBuffer,
+    const VkPerformanceStreamMarkerInfoINTEL*   pMarkerInfo);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkCmdSetPerformanceOverrideINTEL(
+    VkCommandBuffer                             commandBuffer,
+    const VkPerformanceOverrideInfoINTEL*       pOverrideInfo);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkAcquirePerformanceConfigurationINTEL(
+    VkDevice                                    device,
+    const VkPerformanceConfigurationAcquireInfoINTEL* pAcquireInfo,
+    VkPerformanceConfigurationINTEL*            pConfiguration);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkReleasePerformanceConfigurationINTEL(
+    VkDevice                                    device,
+    VkPerformanceConfigurationINTEL             configuration);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkQueueSetPerformanceConfigurationINTEL(
+    VkQueue                                     queue,
+    VkPerformanceConfigurationINTEL             configuration);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetPerformanceParameterINTEL(
+    VkDevice                                    device,
+    VkPerformanceParameterTypeINTEL             parameter,
+    VkPerformanceValueINTEL*                    pValue);
+#endif
+
+
+#define VK_EXT_pci_bus_info 1
+#define VK_EXT_PCI_BUS_INFO_SPEC_VERSION  2
+#define VK_EXT_PCI_BUS_INFO_EXTENSION_NAME "VK_EXT_pci_bus_info"
+typedef struct VkPhysicalDevicePCIBusInfoPropertiesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    uint32_t           pciDomain;
+    uint32_t           pciBus;
+    uint32_t           pciDevice;
+    uint32_t           pciFunction;
+} VkPhysicalDevicePCIBusInfoPropertiesEXT;
+
+
+
+#define VK_AMD_display_native_hdr 1
+#define VK_AMD_DISPLAY_NATIVE_HDR_SPEC_VERSION 1
+#define VK_AMD_DISPLAY_NATIVE_HDR_EXTENSION_NAME "VK_AMD_display_native_hdr"
+typedef struct VkDisplayNativeHdrSurfaceCapabilitiesAMD {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           localDimmingSupport;
+} VkDisplayNativeHdrSurfaceCapabilitiesAMD;
+
+typedef struct VkSwapchainDisplayNativeHdrCreateInfoAMD {
+    VkStructureType    sType;
+    const void*        pNext;
+    VkBool32           localDimmingEnable;
+} VkSwapchainDisplayNativeHdrCreateInfoAMD;
+
+typedef void (VKAPI_PTR *PFN_vkSetLocalDimmingAMD)(VkDevice device, VkSwapchainKHR swapChain, VkBool32 localDimmingEnable);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR void VKAPI_CALL vkSetLocalDimmingAMD(
+    VkDevice                                    device,
+    VkSwapchainKHR                              swapChain,
+    VkBool32                                    localDimmingEnable);
+#endif
+
+
+#define VK_EXT_fragment_density_map 1
+#define VK_EXT_FRAGMENT_DENSITY_MAP_SPEC_VERSION 2
+#define VK_EXT_FRAGMENT_DENSITY_MAP_EXTENSION_NAME "VK_EXT_fragment_density_map"
+typedef struct VkPhysicalDeviceFragmentDensityMapFeaturesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           fragmentDensityMap;
+    VkBool32           fragmentDensityMapDynamic;
+    VkBool32           fragmentDensityMapNonSubsampledImages;
+} VkPhysicalDeviceFragmentDensityMapFeaturesEXT;
+
+typedef struct VkPhysicalDeviceFragmentDensityMapPropertiesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    VkExtent2D         minFragmentDensityTexelSize;
+    VkExtent2D         maxFragmentDensityTexelSize;
+    VkBool32           fragmentDensityInvocations;
+} VkPhysicalDeviceFragmentDensityMapPropertiesEXT;
+
+typedef struct VkRenderPassFragmentDensityMapCreateInfoEXT {
+    VkStructureType          sType;
+    const void*              pNext;
+    VkAttachmentReference    fragmentDensityMapAttachment;
+} VkRenderPassFragmentDensityMapCreateInfoEXT;
+
+
+
+#define VK_EXT_scalar_block_layout 1
+#define VK_EXT_SCALAR_BLOCK_LAYOUT_SPEC_VERSION 1
+#define VK_EXT_SCALAR_BLOCK_LAYOUT_EXTENSION_NAME "VK_EXT_scalar_block_layout"
+typedef VkPhysicalDeviceScalarBlockLayoutFeatures VkPhysicalDeviceScalarBlockLayoutFeaturesEXT;
+
+
+
+#define VK_GOOGLE_hlsl_functionality1 1
+#define VK_GOOGLE_HLSL_FUNCTIONALITY_1_SPEC_VERSION 1
+#define VK_GOOGLE_HLSL_FUNCTIONALITY_1_EXTENSION_NAME "VK_GOOGLE_hlsl_functionality1"
+#define VK_GOOGLE_HLSL_FUNCTIONALITY1_SPEC_VERSION VK_GOOGLE_HLSL_FUNCTIONALITY_1_SPEC_VERSION
+#define VK_GOOGLE_HLSL_FUNCTIONALITY1_EXTENSION_NAME VK_GOOGLE_HLSL_FUNCTIONALITY_1_EXTENSION_NAME
+
+
+#define VK_GOOGLE_decorate_string 1
+#define VK_GOOGLE_DECORATE_STRING_SPEC_VERSION 1
+#define VK_GOOGLE_DECORATE_STRING_EXTENSION_NAME "VK_GOOGLE_decorate_string"
+
+
+#define VK_EXT_subgroup_size_control 1
+#define VK_EXT_SUBGROUP_SIZE_CONTROL_SPEC_VERSION 2
+#define VK_EXT_SUBGROUP_SIZE_CONTROL_EXTENSION_NAME "VK_EXT_subgroup_size_control"
+typedef struct VkPhysicalDeviceSubgroupSizeControlFeaturesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           subgroupSizeControl;
+    VkBool32           computeFullSubgroups;
+} VkPhysicalDeviceSubgroupSizeControlFeaturesEXT;
+
+typedef struct VkPhysicalDeviceSubgroupSizeControlPropertiesEXT {
+    VkStructureType       sType;
+    void*                 pNext;
+    uint32_t              minSubgroupSize;
+    uint32_t              maxSubgroupSize;
+    uint32_t              maxComputeWorkgroupSubgroups;
+    VkShaderStageFlags    requiredSubgroupSizeStages;
+} VkPhysicalDeviceSubgroupSizeControlPropertiesEXT;
+
+typedef struct VkPipelineShaderStageRequiredSubgroupSizeCreateInfoEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    uint32_t           requiredSubgroupSize;
+} VkPipelineShaderStageRequiredSubgroupSizeCreateInfoEXT;
+
+
+
+#define VK_AMD_shader_core_properties2 1
+#define VK_AMD_SHADER_CORE_PROPERTIES_2_SPEC_VERSION 1
+#define VK_AMD_SHADER_CORE_PROPERTIES_2_EXTENSION_NAME "VK_AMD_shader_core_properties2"
+
+typedef enum VkShaderCorePropertiesFlagBitsAMD {
+    VK_SHADER_CORE_PROPERTIES_FLAG_BITS_MAX_ENUM_AMD = 0x7FFFFFFF
+} VkShaderCorePropertiesFlagBitsAMD;
+typedef VkFlags VkShaderCorePropertiesFlagsAMD;
+typedef struct VkPhysicalDeviceShaderCoreProperties2AMD {
+    VkStructureType                   sType;
+    void*                             pNext;
+    VkShaderCorePropertiesFlagsAMD    shaderCoreFeatures;
+    uint32_t                          activeComputeUnitCount;
+} VkPhysicalDeviceShaderCoreProperties2AMD;
+
+
+
+#define VK_AMD_device_coherent_memory 1
+#define VK_AMD_DEVICE_COHERENT_MEMORY_SPEC_VERSION 1
+#define VK_AMD_DEVICE_COHERENT_MEMORY_EXTENSION_NAME "VK_AMD_device_coherent_memory"
+typedef struct VkPhysicalDeviceCoherentMemoryFeaturesAMD {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           deviceCoherentMemory;
+} VkPhysicalDeviceCoherentMemoryFeaturesAMD;
+
+
+
+#define VK_EXT_shader_image_atomic_int64 1
+#define VK_EXT_SHADER_IMAGE_ATOMIC_INT64_SPEC_VERSION 1
+#define VK_EXT_SHADER_IMAGE_ATOMIC_INT64_EXTENSION_NAME "VK_EXT_shader_image_atomic_int64"
+typedef struct VkPhysicalDeviceShaderImageAtomicInt64FeaturesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           shaderImageInt64Atomics;
+    VkBool32           sparseImageInt64Atomics;
+} VkPhysicalDeviceShaderImageAtomicInt64FeaturesEXT;
+
+
+
+#define VK_EXT_memory_budget 1
+#define VK_EXT_MEMORY_BUDGET_SPEC_VERSION 1
+#define VK_EXT_MEMORY_BUDGET_EXTENSION_NAME "VK_EXT_memory_budget"
+typedef struct VkPhysicalDeviceMemoryBudgetPropertiesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    VkDeviceSize       heapBudget[VK_MAX_MEMORY_HEAPS];
+    VkDeviceSize       heapUsage[VK_MAX_MEMORY_HEAPS];
+} VkPhysicalDeviceMemoryBudgetPropertiesEXT;
+
+
+
+#define VK_EXT_memory_priority 1
+#define VK_EXT_MEMORY_PRIORITY_SPEC_VERSION 1
+#define VK_EXT_MEMORY_PRIORITY_EXTENSION_NAME "VK_EXT_memory_priority"
+typedef struct VkPhysicalDeviceMemoryPriorityFeaturesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           memoryPriority;
+} VkPhysicalDeviceMemoryPriorityFeaturesEXT;
+
+typedef struct VkMemoryPriorityAllocateInfoEXT {
+    VkStructureType    sType;
+    const void*        pNext;
+    float              priority;
+} VkMemoryPriorityAllocateInfoEXT;
+
+
+
+#define VK_NV_dedicated_allocation_image_aliasing 1
+#define VK_NV_DEDICATED_ALLOCATION_IMAGE_ALIASING_SPEC_VERSION 1
+#define VK_NV_DEDICATED_ALLOCATION_IMAGE_ALIASING_EXTENSION_NAME "VK_NV_dedicated_allocation_image_aliasing"
+typedef struct VkPhysicalDeviceDedicatedAllocationImageAliasingFeaturesNV {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           dedicatedAllocationImageAliasing;
+} VkPhysicalDeviceDedicatedAllocationImageAliasingFeaturesNV;
+
+
+
+#define VK_EXT_buffer_device_address 1
+#define VK_EXT_BUFFER_DEVICE_ADDRESS_SPEC_VERSION 2
+#define VK_EXT_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME "VK_EXT_buffer_device_address"
+typedef struct VkPhysicalDeviceBufferDeviceAddressFeaturesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           bufferDeviceAddress;
+    VkBool32           bufferDeviceAddressCaptureReplay;
+    VkBool32           bufferDeviceAddressMultiDevice;
+} VkPhysicalDeviceBufferDeviceAddressFeaturesEXT;
+
+typedef VkPhysicalDeviceBufferDeviceAddressFeaturesEXT VkPhysicalDeviceBufferAddressFeaturesEXT;
+
+typedef VkBufferDeviceAddressInfo VkBufferDeviceAddressInfoEXT;
+
+typedef struct VkBufferDeviceAddressCreateInfoEXT {
+    VkStructureType    sType;
+    const void*        pNext;
+    VkDeviceAddress    deviceAddress;
+} VkBufferDeviceAddressCreateInfoEXT;
+
+typedef VkDeviceAddress (VKAPI_PTR *PFN_vkGetBufferDeviceAddressEXT)(VkDevice device, const VkBufferDeviceAddressInfo* pInfo);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkDeviceAddress VKAPI_CALL vkGetBufferDeviceAddressEXT(
+    VkDevice                                    device,
+    const VkBufferDeviceAddressInfo*            pInfo);
+#endif
+
+
+#define VK_EXT_tooling_info 1
+#define VK_EXT_TOOLING_INFO_SPEC_VERSION  1
+#define VK_EXT_TOOLING_INFO_EXTENSION_NAME "VK_EXT_tooling_info"
+
+typedef enum VkToolPurposeFlagBitsEXT {
+    VK_TOOL_PURPOSE_VALIDATION_BIT_EXT = 0x00000001,
+    VK_TOOL_PURPOSE_PROFILING_BIT_EXT = 0x00000002,
+    VK_TOOL_PURPOSE_TRACING_BIT_EXT = 0x00000004,
+    VK_TOOL_PURPOSE_ADDITIONAL_FEATURES_BIT_EXT = 0x00000008,
+    VK_TOOL_PURPOSE_MODIFYING_FEATURES_BIT_EXT = 0x00000010,
+    VK_TOOL_PURPOSE_DEBUG_REPORTING_BIT_EXT = 0x00000020,
+    VK_TOOL_PURPOSE_DEBUG_MARKERS_BIT_EXT = 0x00000040,
+    VK_TOOL_PURPOSE_FLAG_BITS_MAX_ENUM_EXT = 0x7FFFFFFF
+} VkToolPurposeFlagBitsEXT;
+typedef VkFlags VkToolPurposeFlagsEXT;
+typedef struct VkPhysicalDeviceToolPropertiesEXT {
+    VkStructureType          sType;
+    void*                    pNext;
+    char                     name[VK_MAX_EXTENSION_NAME_SIZE];
+    char                     version[VK_MAX_EXTENSION_NAME_SIZE];
+    VkToolPurposeFlagsEXT    purposes;
+    char                     description[VK_MAX_DESCRIPTION_SIZE];
+    char                     layer[VK_MAX_EXTENSION_NAME_SIZE];
+} VkPhysicalDeviceToolPropertiesEXT;
+
+typedef VkResult (VKAPI_PTR *PFN_vkGetPhysicalDeviceToolPropertiesEXT)(VkPhysicalDevice physicalDevice, uint32_t* pToolCount, VkPhysicalDeviceToolPropertiesEXT* pToolProperties);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceToolPropertiesEXT(
+    VkPhysicalDevice                            physicalDevice,
+    uint32_t*                                   pToolCount,
+    VkPhysicalDeviceToolPropertiesEXT*          pToolProperties);
+#endif
+
+
+#define VK_EXT_separate_stencil_usage 1
+#define VK_EXT_SEPARATE_STENCIL_USAGE_SPEC_VERSION 1
+#define VK_EXT_SEPARATE_STENCIL_USAGE_EXTENSION_NAME "VK_EXT_separate_stencil_usage"
+typedef VkImageStencilUsageCreateInfo VkImageStencilUsageCreateInfoEXT;
+
+
+
+#define VK_EXT_validation_features 1
+#define VK_EXT_VALIDATION_FEATURES_SPEC_VERSION 5
+#define VK_EXT_VALIDATION_FEATURES_EXTENSION_NAME "VK_EXT_validation_features"
+
+typedef enum VkValidationFeatureEnableEXT {
+    VK_VALIDATION_FEATURE_ENABLE_GPU_ASSISTED_EXT = 0,
+    VK_VALIDATION_FEATURE_ENABLE_GPU_ASSISTED_RESERVE_BINDING_SLOT_EXT = 1,
+    VK_VALIDATION_FEATURE_ENABLE_BEST_PRACTICES_EXT = 2,
+    VK_VALIDATION_FEATURE_ENABLE_DEBUG_PRINTF_EXT = 3,
+    VK_VALIDATION_FEATURE_ENABLE_SYNCHRONIZATION_VALIDATION_EXT = 4,
+    VK_VALIDATION_FEATURE_ENABLE_MAX_ENUM_EXT = 0x7FFFFFFF
+} VkValidationFeatureEnableEXT;
+
+typedef enum VkValidationFeatureDisableEXT {
+    VK_VALIDATION_FEATURE_DISABLE_ALL_EXT = 0,
+    VK_VALIDATION_FEATURE_DISABLE_SHADERS_EXT = 1,
+    VK_VALIDATION_FEATURE_DISABLE_THREAD_SAFETY_EXT = 2,
+    VK_VALIDATION_FEATURE_DISABLE_API_PARAMETERS_EXT = 3,
+    VK_VALIDATION_FEATURE_DISABLE_OBJECT_LIFETIMES_EXT = 4,
+    VK_VALIDATION_FEATURE_DISABLE_CORE_CHECKS_EXT = 5,
+    VK_VALIDATION_FEATURE_DISABLE_UNIQUE_HANDLES_EXT = 6,
+    VK_VALIDATION_FEATURE_DISABLE_SHADER_VALIDATION_CACHE_EXT = 7,
+    VK_VALIDATION_FEATURE_DISABLE_MAX_ENUM_EXT = 0x7FFFFFFF
+} VkValidationFeatureDisableEXT;
+typedef struct VkValidationFeaturesEXT {
+    VkStructureType                         sType;
+    const void*                             pNext;
+    uint32_t                                enabledValidationFeatureCount;
+    const VkValidationFeatureEnableEXT*     pEnabledValidationFeatures;
+    uint32_t                                disabledValidationFeatureCount;
+    const VkValidationFeatureDisableEXT*    pDisabledValidationFeatures;
+} VkValidationFeaturesEXT;
+
+
+
+#define VK_NV_cooperative_matrix 1
+#define VK_NV_COOPERATIVE_MATRIX_SPEC_VERSION 1
+#define VK_NV_COOPERATIVE_MATRIX_EXTENSION_NAME "VK_NV_cooperative_matrix"
+
+typedef enum VkComponentTypeNV {
+    VK_COMPONENT_TYPE_FLOAT16_NV = 0,
+    VK_COMPONENT_TYPE_FLOAT32_NV = 1,
+    VK_COMPONENT_TYPE_FLOAT64_NV = 2,
+    VK_COMPONENT_TYPE_SINT8_NV = 3,
+    VK_COMPONENT_TYPE_SINT16_NV = 4,
+    VK_COMPONENT_TYPE_SINT32_NV = 5,
+    VK_COMPONENT_TYPE_SINT64_NV = 6,
+    VK_COMPONENT_TYPE_UINT8_NV = 7,
+    VK_COMPONENT_TYPE_UINT16_NV = 8,
+    VK_COMPONENT_TYPE_UINT32_NV = 9,
+    VK_COMPONENT_TYPE_UINT64_NV = 10,
+    VK_COMPONENT_TYPE_MAX_ENUM_NV = 0x7FFFFFFF
+} VkComponentTypeNV;
+
+typedef enum VkScopeNV {
+    VK_SCOPE_DEVICE_NV = 1,
+    VK_SCOPE_WORKGROUP_NV = 2,
+    VK_SCOPE_SUBGROUP_NV = 3,
+    VK_SCOPE_QUEUE_FAMILY_NV = 5,
+    VK_SCOPE_MAX_ENUM_NV = 0x7FFFFFFF
+} VkScopeNV;
+typedef struct VkCooperativeMatrixPropertiesNV {
+    VkStructureType      sType;
+    void*                pNext;
+    uint32_t             MSize;
+    uint32_t             NSize;
+    uint32_t             KSize;
+    VkComponentTypeNV    AType;
+    VkComponentTypeNV    BType;
+    VkComponentTypeNV    CType;
+    VkComponentTypeNV    DType;
+    VkScopeNV            scope;
+} VkCooperativeMatrixPropertiesNV;
+
+typedef struct VkPhysicalDeviceCooperativeMatrixFeaturesNV {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           cooperativeMatrix;
+    VkBool32           cooperativeMatrixRobustBufferAccess;
+} VkPhysicalDeviceCooperativeMatrixFeaturesNV;
+
+typedef struct VkPhysicalDeviceCooperativeMatrixPropertiesNV {
+    VkStructureType       sType;
+    void*                 pNext;
+    VkShaderStageFlags    cooperativeMatrixSupportedStages;
+} VkPhysicalDeviceCooperativeMatrixPropertiesNV;
+
+typedef VkResult (VKAPI_PTR *PFN_vkGetPhysicalDeviceCooperativeMatrixPropertiesNV)(VkPhysicalDevice physicalDevice, uint32_t* pPropertyCount, VkCooperativeMatrixPropertiesNV* pProperties);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceCooperativeMatrixPropertiesNV(
+    VkPhysicalDevice                            physicalDevice,
+    uint32_t*                                   pPropertyCount,
+    VkCooperativeMatrixPropertiesNV*            pProperties);
+#endif
+
+
+#define VK_NV_coverage_reduction_mode 1
+#define VK_NV_COVERAGE_REDUCTION_MODE_SPEC_VERSION 1
+#define VK_NV_COVERAGE_REDUCTION_MODE_EXTENSION_NAME "VK_NV_coverage_reduction_mode"
+
+typedef enum VkCoverageReductionModeNV {
+    VK_COVERAGE_REDUCTION_MODE_MERGE_NV = 0,
+    VK_COVERAGE_REDUCTION_MODE_TRUNCATE_NV = 1,
+    VK_COVERAGE_REDUCTION_MODE_MAX_ENUM_NV = 0x7FFFFFFF
+} VkCoverageReductionModeNV;
+typedef VkFlags VkPipelineCoverageReductionStateCreateFlagsNV;
+typedef struct VkPhysicalDeviceCoverageReductionModeFeaturesNV {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           coverageReductionMode;
+} VkPhysicalDeviceCoverageReductionModeFeaturesNV;
+
+typedef struct VkPipelineCoverageReductionStateCreateInfoNV {
+    VkStructureType                                  sType;
+    const void*                                      pNext;
+    VkPipelineCoverageReductionStateCreateFlagsNV    flags;
+    VkCoverageReductionModeNV                        coverageReductionMode;
+} VkPipelineCoverageReductionStateCreateInfoNV;
+
+typedef struct VkFramebufferMixedSamplesCombinationNV {
+    VkStructureType              sType;
+    void*                        pNext;
+    VkCoverageReductionModeNV    coverageReductionMode;
+    VkSampleCountFlagBits        rasterizationSamples;
+    VkSampleCountFlags           depthStencilSamples;
+    VkSampleCountFlags           colorSamples;
+} VkFramebufferMixedSamplesCombinationNV;
+
+typedef VkResult (VKAPI_PTR *PFN_vkGetPhysicalDeviceSupportedFramebufferMixedSamplesCombinationsNV)(VkPhysicalDevice physicalDevice, uint32_t* pCombinationCount, VkFramebufferMixedSamplesCombinationNV* pCombinations);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceSupportedFramebufferMixedSamplesCombinationsNV(
+    VkPhysicalDevice                            physicalDevice,
+    uint32_t*                                   pCombinationCount,
+    VkFramebufferMixedSamplesCombinationNV*     pCombinations);
+#endif
+
+
+#define VK_EXT_fragment_shader_interlock 1
+#define VK_EXT_FRAGMENT_SHADER_INTERLOCK_SPEC_VERSION 1
+#define VK_EXT_FRAGMENT_SHADER_INTERLOCK_EXTENSION_NAME "VK_EXT_fragment_shader_interlock"
+typedef struct VkPhysicalDeviceFragmentShaderInterlockFeaturesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           fragmentShaderSampleInterlock;
+    VkBool32           fragmentShaderPixelInterlock;
+    VkBool32           fragmentShaderShadingRateInterlock;
+} VkPhysicalDeviceFragmentShaderInterlockFeaturesEXT;
+
+
+
+#define VK_EXT_ycbcr_image_arrays 1
+#define VK_EXT_YCBCR_IMAGE_ARRAYS_SPEC_VERSION 1
+#define VK_EXT_YCBCR_IMAGE_ARRAYS_EXTENSION_NAME "VK_EXT_ycbcr_image_arrays"
+typedef struct VkPhysicalDeviceYcbcrImageArraysFeaturesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           ycbcrImageArrays;
+} VkPhysicalDeviceYcbcrImageArraysFeaturesEXT;
+
+
+
+#define VK_EXT_provoking_vertex 1
+#define VK_EXT_PROVOKING_VERTEX_SPEC_VERSION 1
+#define VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME "VK_EXT_provoking_vertex"
+
+typedef enum VkProvokingVertexModeEXT {
+    VK_PROVOKING_VERTEX_MODE_FIRST_VERTEX_EXT = 0,
+    VK_PROVOKING_VERTEX_MODE_LAST_VERTEX_EXT = 1,
+    VK_PROVOKING_VERTEX_MODE_MAX_ENUM_EXT = 0x7FFFFFFF
+} VkProvokingVertexModeEXT;
+typedef struct VkPhysicalDeviceProvokingVertexFeaturesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           provokingVertexLast;
+    VkBool32           transformFeedbackPreservesProvokingVertex;
+} VkPhysicalDeviceProvokingVertexFeaturesEXT;
+
+typedef struct VkPhysicalDeviceProvokingVertexPropertiesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           provokingVertexModePerPipeline;
+    VkBool32           transformFeedbackPreservesTriangleFanProvokingVertex;
+} VkPhysicalDeviceProvokingVertexPropertiesEXT;
+
+typedef struct VkPipelineRasterizationProvokingVertexStateCreateInfoEXT {
+    VkStructureType             sType;
+    const void*                 pNext;
+    VkProvokingVertexModeEXT    provokingVertexMode;
+} VkPipelineRasterizationProvokingVertexStateCreateInfoEXT;
+
+
+
+#define VK_EXT_headless_surface 1
+#define VK_EXT_HEADLESS_SURFACE_SPEC_VERSION 1
+#define VK_EXT_HEADLESS_SURFACE_EXTENSION_NAME "VK_EXT_headless_surface"
+typedef VkFlags VkHeadlessSurfaceCreateFlagsEXT;
+typedef struct VkHeadlessSurfaceCreateInfoEXT {
+    VkStructureType                    sType;
+    const void*                        pNext;
+    VkHeadlessSurfaceCreateFlagsEXT    flags;
+} VkHeadlessSurfaceCreateInfoEXT;
+
+typedef VkResult (VKAPI_PTR *PFN_vkCreateHeadlessSurfaceEXT)(VkInstance instance, const VkHeadlessSurfaceCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateHeadlessSurfaceEXT(
+    VkInstance                                  instance,
+    const VkHeadlessSurfaceCreateInfoEXT*       pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkSurfaceKHR*                               pSurface);
+#endif
+
+
+#define VK_EXT_line_rasterization 1
+#define VK_EXT_LINE_RASTERIZATION_SPEC_VERSION 1
+#define VK_EXT_LINE_RASTERIZATION_EXTENSION_NAME "VK_EXT_line_rasterization"
+
+typedef enum VkLineRasterizationModeEXT {
+    VK_LINE_RASTERIZATION_MODE_DEFAULT_EXT = 0,
+    VK_LINE_RASTERIZATION_MODE_RECTANGULAR_EXT = 1,
+    VK_LINE_RASTERIZATION_MODE_BRESENHAM_EXT = 2,
+    VK_LINE_RASTERIZATION_MODE_RECTANGULAR_SMOOTH_EXT = 3,
+    VK_LINE_RASTERIZATION_MODE_MAX_ENUM_EXT = 0x7FFFFFFF
+} VkLineRasterizationModeEXT;
+typedef struct VkPhysicalDeviceLineRasterizationFeaturesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           rectangularLines;
+    VkBool32           bresenhamLines;
+    VkBool32           smoothLines;
+    VkBool32           stippledRectangularLines;
+    VkBool32           stippledBresenhamLines;
+    VkBool32           stippledSmoothLines;
+} VkPhysicalDeviceLineRasterizationFeaturesEXT;
+
+typedef struct VkPhysicalDeviceLineRasterizationPropertiesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    uint32_t           lineSubPixelPrecisionBits;
+} VkPhysicalDeviceLineRasterizationPropertiesEXT;
+
+typedef struct VkPipelineRasterizationLineStateCreateInfoEXT {
+    VkStructureType               sType;
+    const void*                   pNext;
+    VkLineRasterizationModeEXT    lineRasterizationMode;
+    VkBool32                      stippledLineEnable;
+    uint32_t                      lineStippleFactor;
+    uint16_t                      lineStipplePattern;
+} VkPipelineRasterizationLineStateCreateInfoEXT;
+
+typedef void (VKAPI_PTR *PFN_vkCmdSetLineStippleEXT)(VkCommandBuffer commandBuffer, uint32_t lineStippleFactor, uint16_t lineStipplePattern);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR void VKAPI_CALL vkCmdSetLineStippleEXT(
+    VkCommandBuffer                             commandBuffer,
+    uint32_t                                    lineStippleFactor,
+    uint16_t                                    lineStipplePattern);
+#endif
+
+
+#define VK_EXT_shader_atomic_float 1
+#define VK_EXT_SHADER_ATOMIC_FLOAT_SPEC_VERSION 1
+#define VK_EXT_SHADER_ATOMIC_FLOAT_EXTENSION_NAME "VK_EXT_shader_atomic_float"
+typedef struct VkPhysicalDeviceShaderAtomicFloatFeaturesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           shaderBufferFloat32Atomics;
+    VkBool32           shaderBufferFloat32AtomicAdd;
+    VkBool32           shaderBufferFloat64Atomics;
+    VkBool32           shaderBufferFloat64AtomicAdd;
+    VkBool32           shaderSharedFloat32Atomics;
+    VkBool32           shaderSharedFloat32AtomicAdd;
+    VkBool32           shaderSharedFloat64Atomics;
+    VkBool32           shaderSharedFloat64AtomicAdd;
+    VkBool32           shaderImageFloat32Atomics;
+    VkBool32           shaderImageFloat32AtomicAdd;
+    VkBool32           sparseImageFloat32Atomics;
+    VkBool32           sparseImageFloat32AtomicAdd;
+} VkPhysicalDeviceShaderAtomicFloatFeaturesEXT;
+
+
+
+#define VK_EXT_host_query_reset 1
+#define VK_EXT_HOST_QUERY_RESET_SPEC_VERSION 1
+#define VK_EXT_HOST_QUERY_RESET_EXTENSION_NAME "VK_EXT_host_query_reset"
+typedef VkPhysicalDeviceHostQueryResetFeatures VkPhysicalDeviceHostQueryResetFeaturesEXT;
+
+typedef void (VKAPI_PTR *PFN_vkResetQueryPoolEXT)(VkDevice device, VkQueryPool queryPool, uint32_t firstQuery, uint32_t queryCount);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR void VKAPI_CALL vkResetQueryPoolEXT(
+    VkDevice                                    device,
+    VkQueryPool                                 queryPool,
+    uint32_t                                    firstQuery,
+    uint32_t                                    queryCount);
+#endif
+
+
+#define VK_EXT_index_type_uint8 1
+#define VK_EXT_INDEX_TYPE_UINT8_SPEC_VERSION 1
+#define VK_EXT_INDEX_TYPE_UINT8_EXTENSION_NAME "VK_EXT_index_type_uint8"
+typedef struct VkPhysicalDeviceIndexTypeUint8FeaturesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           indexTypeUint8;
+} VkPhysicalDeviceIndexTypeUint8FeaturesEXT;
+
+
+
+#define VK_EXT_extended_dynamic_state 1
+#define VK_EXT_EXTENDED_DYNAMIC_STATE_SPEC_VERSION 1
+#define VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME "VK_EXT_extended_dynamic_state"
+typedef struct VkPhysicalDeviceExtendedDynamicStateFeaturesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           extendedDynamicState;
+} VkPhysicalDeviceExtendedDynamicStateFeaturesEXT;
+
+typedef void (VKAPI_PTR *PFN_vkCmdSetCullModeEXT)(VkCommandBuffer commandBuffer, VkCullModeFlags cullMode);
+typedef void (VKAPI_PTR *PFN_vkCmdSetFrontFaceEXT)(VkCommandBuffer commandBuffer, VkFrontFace frontFace);
+typedef void (VKAPI_PTR *PFN_vkCmdSetPrimitiveTopologyEXT)(VkCommandBuffer commandBuffer, VkPrimitiveTopology primitiveTopology);
+typedef void (VKAPI_PTR *PFN_vkCmdSetViewportWithCountEXT)(VkCommandBuffer commandBuffer, uint32_t viewportCount, const VkViewport* pViewports);
+typedef void (VKAPI_PTR *PFN_vkCmdSetScissorWithCountEXT)(VkCommandBuffer commandBuffer, uint32_t scissorCount, const VkRect2D* pScissors);
+typedef void (VKAPI_PTR *PFN_vkCmdBindVertexBuffers2EXT)(VkCommandBuffer commandBuffer, uint32_t firstBinding, uint32_t bindingCount, const VkBuffer* pBuffers, const VkDeviceSize* pOffsets, const VkDeviceSize* pSizes, const VkDeviceSize* pStrides);
+typedef void (VKAPI_PTR *PFN_vkCmdSetDepthTestEnableEXT)(VkCommandBuffer commandBuffer, VkBool32 depthTestEnable);
+typedef void (VKAPI_PTR *PFN_vkCmdSetDepthWriteEnableEXT)(VkCommandBuffer commandBuffer, VkBool32 depthWriteEnable);
+typedef void (VKAPI_PTR *PFN_vkCmdSetDepthCompareOpEXT)(VkCommandBuffer commandBuffer, VkCompareOp depthCompareOp);
+typedef void (VKAPI_PTR *PFN_vkCmdSetDepthBoundsTestEnableEXT)(VkCommandBuffer commandBuffer, VkBool32 depthBoundsTestEnable);
+typedef void (VKAPI_PTR *PFN_vkCmdSetStencilTestEnableEXT)(VkCommandBuffer commandBuffer, VkBool32 stencilTestEnable);
+typedef void (VKAPI_PTR *PFN_vkCmdSetStencilOpEXT)(VkCommandBuffer commandBuffer, VkStencilFaceFlags faceMask, VkStencilOp failOp, VkStencilOp passOp, VkStencilOp depthFailOp, VkCompareOp compareOp);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR void VKAPI_CALL vkCmdSetCullModeEXT(
+    VkCommandBuffer                             commandBuffer,
+    VkCullModeFlags                             cullMode);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdSetFrontFaceEXT(
+    VkCommandBuffer                             commandBuffer,
+    VkFrontFace                                 frontFace);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdSetPrimitiveTopologyEXT(
+    VkCommandBuffer                             commandBuffer,
+    VkPrimitiveTopology                         primitiveTopology);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdSetViewportWithCountEXT(
+    VkCommandBuffer                             commandBuffer,
+    uint32_t                                    viewportCount,
+    const VkViewport*                           pViewports);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdSetScissorWithCountEXT(
+    VkCommandBuffer                             commandBuffer,
+    uint32_t                                    scissorCount,
+    const VkRect2D*                             pScissors);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdBindVertexBuffers2EXT(
+    VkCommandBuffer                             commandBuffer,
+    uint32_t                                    firstBinding,
+    uint32_t                                    bindingCount,
+    const VkBuffer*                             pBuffers,
+    const VkDeviceSize*                         pOffsets,
+    const VkDeviceSize*                         pSizes,
+    const VkDeviceSize*                         pStrides);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdSetDepthTestEnableEXT(
+    VkCommandBuffer                             commandBuffer,
+    VkBool32                                    depthTestEnable);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdSetDepthWriteEnableEXT(
+    VkCommandBuffer                             commandBuffer,
+    VkBool32                                    depthWriteEnable);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdSetDepthCompareOpEXT(
+    VkCommandBuffer                             commandBuffer,
+    VkCompareOp                                 depthCompareOp);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdSetDepthBoundsTestEnableEXT(
+    VkCommandBuffer                             commandBuffer,
+    VkBool32                                    depthBoundsTestEnable);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdSetStencilTestEnableEXT(
+    VkCommandBuffer                             commandBuffer,
+    VkBool32                                    stencilTestEnable);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdSetStencilOpEXT(
+    VkCommandBuffer                             commandBuffer,
+    VkStencilFaceFlags                          faceMask,
+    VkStencilOp                                 failOp,
+    VkStencilOp                                 passOp,
+    VkStencilOp                                 depthFailOp,
+    VkCompareOp                                 compareOp);
+#endif
+
+
+#define VK_EXT_shader_atomic_float2 1
+#define VK_EXT_SHADER_ATOMIC_FLOAT_2_SPEC_VERSION 1
+#define VK_EXT_SHADER_ATOMIC_FLOAT_2_EXTENSION_NAME "VK_EXT_shader_atomic_float2"
+typedef struct VkPhysicalDeviceShaderAtomicFloat2FeaturesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           shaderBufferFloat16Atomics;
+    VkBool32           shaderBufferFloat16AtomicAdd;
+    VkBool32           shaderBufferFloat16AtomicMinMax;
+    VkBool32           shaderBufferFloat32AtomicMinMax;
+    VkBool32           shaderBufferFloat64AtomicMinMax;
+    VkBool32           shaderSharedFloat16Atomics;
+    VkBool32           shaderSharedFloat16AtomicAdd;
+    VkBool32           shaderSharedFloat16AtomicMinMax;
+    VkBool32           shaderSharedFloat32AtomicMinMax;
+    VkBool32           shaderSharedFloat64AtomicMinMax;
+    VkBool32           shaderImageFloat32AtomicMinMax;
+    VkBool32           sparseImageFloat32AtomicMinMax;
+} VkPhysicalDeviceShaderAtomicFloat2FeaturesEXT;
+
+
+
+#define VK_EXT_shader_demote_to_helper_invocation 1
+#define VK_EXT_SHADER_DEMOTE_TO_HELPER_INVOCATION_SPEC_VERSION 1
+#define VK_EXT_SHADER_DEMOTE_TO_HELPER_INVOCATION_EXTENSION_NAME "VK_EXT_shader_demote_to_helper_invocation"
+typedef struct VkPhysicalDeviceShaderDemoteToHelperInvocationFeaturesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           shaderDemoteToHelperInvocation;
+} VkPhysicalDeviceShaderDemoteToHelperInvocationFeaturesEXT;
+
+
+
+#define VK_NV_device_generated_commands 1
+VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkIndirectCommandsLayoutNV)
+#define VK_NV_DEVICE_GENERATED_COMMANDS_SPEC_VERSION 3
+#define VK_NV_DEVICE_GENERATED_COMMANDS_EXTENSION_NAME "VK_NV_device_generated_commands"
+
+typedef enum VkIndirectCommandsTokenTypeNV {
+    VK_INDIRECT_COMMANDS_TOKEN_TYPE_SHADER_GROUP_NV = 0,
+    VK_INDIRECT_COMMANDS_TOKEN_TYPE_STATE_FLAGS_NV = 1,
+    VK_INDIRECT_COMMANDS_TOKEN_TYPE_INDEX_BUFFER_NV = 2,
+    VK_INDIRECT_COMMANDS_TOKEN_TYPE_VERTEX_BUFFER_NV = 3,
+    VK_INDIRECT_COMMANDS_TOKEN_TYPE_PUSH_CONSTANT_NV = 4,
+    VK_INDIRECT_COMMANDS_TOKEN_TYPE_DRAW_INDEXED_NV = 5,
+    VK_INDIRECT_COMMANDS_TOKEN_TYPE_DRAW_NV = 6,
+    VK_INDIRECT_COMMANDS_TOKEN_TYPE_DRAW_TASKS_NV = 7,
+    VK_INDIRECT_COMMANDS_TOKEN_TYPE_MAX_ENUM_NV = 0x7FFFFFFF
+} VkIndirectCommandsTokenTypeNV;
+
+typedef enum VkIndirectStateFlagBitsNV {
+    VK_INDIRECT_STATE_FLAG_FRONTFACE_BIT_NV = 0x00000001,
+    VK_INDIRECT_STATE_FLAG_BITS_MAX_ENUM_NV = 0x7FFFFFFF
+} VkIndirectStateFlagBitsNV;
+typedef VkFlags VkIndirectStateFlagsNV;
+
+typedef enum VkIndirectCommandsLayoutUsageFlagBitsNV {
+    VK_INDIRECT_COMMANDS_LAYOUT_USAGE_EXPLICIT_PREPROCESS_BIT_NV = 0x00000001,
+    VK_INDIRECT_COMMANDS_LAYOUT_USAGE_INDEXED_SEQUENCES_BIT_NV = 0x00000002,
+    VK_INDIRECT_COMMANDS_LAYOUT_USAGE_UNORDERED_SEQUENCES_BIT_NV = 0x00000004,
+    VK_INDIRECT_COMMANDS_LAYOUT_USAGE_FLAG_BITS_MAX_ENUM_NV = 0x7FFFFFFF
+} VkIndirectCommandsLayoutUsageFlagBitsNV;
+typedef VkFlags VkIndirectCommandsLayoutUsageFlagsNV;
+typedef struct VkPhysicalDeviceDeviceGeneratedCommandsPropertiesNV {
+    VkStructureType    sType;
+    void*              pNext;
+    uint32_t           maxGraphicsShaderGroupCount;
+    uint32_t           maxIndirectSequenceCount;
+    uint32_t           maxIndirectCommandsTokenCount;
+    uint32_t           maxIndirectCommandsStreamCount;
+    uint32_t           maxIndirectCommandsTokenOffset;
+    uint32_t           maxIndirectCommandsStreamStride;
+    uint32_t           minSequencesCountBufferOffsetAlignment;
+    uint32_t           minSequencesIndexBufferOffsetAlignment;
+    uint32_t           minIndirectCommandsBufferOffsetAlignment;
+} VkPhysicalDeviceDeviceGeneratedCommandsPropertiesNV;
+
+typedef struct VkPhysicalDeviceDeviceGeneratedCommandsFeaturesNV {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           deviceGeneratedCommands;
+} VkPhysicalDeviceDeviceGeneratedCommandsFeaturesNV;
+
+typedef struct VkGraphicsShaderGroupCreateInfoNV {
+    VkStructureType                                 sType;
+    const void*                                     pNext;
+    uint32_t                                        stageCount;
+    const VkPipelineShaderStageCreateInfo*          pStages;
+    const VkPipelineVertexInputStateCreateInfo*     pVertexInputState;
+    const VkPipelineTessellationStateCreateInfo*    pTessellationState;
+} VkGraphicsShaderGroupCreateInfoNV;
+
+typedef struct VkGraphicsPipelineShaderGroupsCreateInfoNV {
+    VkStructureType                             sType;
+    const void*                                 pNext;
+    uint32_t                                    groupCount;
+    const VkGraphicsShaderGroupCreateInfoNV*    pGroups;
+    uint32_t                                    pipelineCount;
+    const VkPipeline*                           pPipelines;
+} VkGraphicsPipelineShaderGroupsCreateInfoNV;
+
+typedef struct VkBindShaderGroupIndirectCommandNV {
+    uint32_t    groupIndex;
+} VkBindShaderGroupIndirectCommandNV;
+
+typedef struct VkBindIndexBufferIndirectCommandNV {
+    VkDeviceAddress    bufferAddress;
+    uint32_t           size;
+    VkIndexType        indexType;
+} VkBindIndexBufferIndirectCommandNV;
+
+typedef struct VkBindVertexBufferIndirectCommandNV {
+    VkDeviceAddress    bufferAddress;
+    uint32_t           size;
+    uint32_t           stride;
+} VkBindVertexBufferIndirectCommandNV;
+
+typedef struct VkSetStateFlagsIndirectCommandNV {
+    uint32_t    data;
+} VkSetStateFlagsIndirectCommandNV;
+
+typedef struct VkIndirectCommandsStreamNV {
+    VkBuffer        buffer;
+    VkDeviceSize    offset;
+} VkIndirectCommandsStreamNV;
+
+typedef struct VkIndirectCommandsLayoutTokenNV {
+    VkStructureType                  sType;
+    const void*                      pNext;
+    VkIndirectCommandsTokenTypeNV    tokenType;
+    uint32_t                         stream;
+    uint32_t                         offset;
+    uint32_t                         vertexBindingUnit;
+    VkBool32                         vertexDynamicStride;
+    VkPipelineLayout                 pushconstantPipelineLayout;
+    VkShaderStageFlags               pushconstantShaderStageFlags;
+    uint32_t                         pushconstantOffset;
+    uint32_t                         pushconstantSize;
+    VkIndirectStateFlagsNV           indirectStateFlags;
+    uint32_t                         indexTypeCount;
+    const VkIndexType*               pIndexTypes;
+    const uint32_t*                  pIndexTypeValues;
+} VkIndirectCommandsLayoutTokenNV;
+
+typedef struct VkIndirectCommandsLayoutCreateInfoNV {
+    VkStructureType                           sType;
+    const void*                               pNext;
+    VkIndirectCommandsLayoutUsageFlagsNV      flags;
+    VkPipelineBindPoint                       pipelineBindPoint;
+    uint32_t                                  tokenCount;
+    const VkIndirectCommandsLayoutTokenNV*    pTokens;
+    uint32_t                                  streamCount;
+    const uint32_t*                           pStreamStrides;
+} VkIndirectCommandsLayoutCreateInfoNV;
+
+typedef struct VkGeneratedCommandsInfoNV {
+    VkStructureType                      sType;
+    const void*                          pNext;
+    VkPipelineBindPoint                  pipelineBindPoint;
+    VkPipeline                           pipeline;
+    VkIndirectCommandsLayoutNV           indirectCommandsLayout;
+    uint32_t                             streamCount;
+    const VkIndirectCommandsStreamNV*    pStreams;
+    uint32_t                             sequencesCount;
+    VkBuffer                             preprocessBuffer;
+    VkDeviceSize                         preprocessOffset;
+    VkDeviceSize                         preprocessSize;
+    VkBuffer                             sequencesCountBuffer;
+    VkDeviceSize                         sequencesCountOffset;
+    VkBuffer                             sequencesIndexBuffer;
+    VkDeviceSize                         sequencesIndexOffset;
+} VkGeneratedCommandsInfoNV;
+
+typedef struct VkGeneratedCommandsMemoryRequirementsInfoNV {
+    VkStructureType               sType;
+    const void*                   pNext;
+    VkPipelineBindPoint           pipelineBindPoint;
+    VkPipeline                    pipeline;
+    VkIndirectCommandsLayoutNV    indirectCommandsLayout;
+    uint32_t                      maxSequencesCount;
+} VkGeneratedCommandsMemoryRequirementsInfoNV;
+
+typedef void (VKAPI_PTR *PFN_vkGetGeneratedCommandsMemoryRequirementsNV)(VkDevice device, const VkGeneratedCommandsMemoryRequirementsInfoNV* pInfo, VkMemoryRequirements2* pMemoryRequirements);
+typedef void (VKAPI_PTR *PFN_vkCmdPreprocessGeneratedCommandsNV)(VkCommandBuffer commandBuffer, const VkGeneratedCommandsInfoNV* pGeneratedCommandsInfo);
+typedef void (VKAPI_PTR *PFN_vkCmdExecuteGeneratedCommandsNV)(VkCommandBuffer commandBuffer, VkBool32 isPreprocessed, const VkGeneratedCommandsInfoNV* pGeneratedCommandsInfo);
+typedef void (VKAPI_PTR *PFN_vkCmdBindPipelineShaderGroupNV)(VkCommandBuffer commandBuffer, VkPipelineBindPoint pipelineBindPoint, VkPipeline pipeline, uint32_t groupIndex);
+typedef VkResult (VKAPI_PTR *PFN_vkCreateIndirectCommandsLayoutNV)(VkDevice device, const VkIndirectCommandsLayoutCreateInfoNV* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkIndirectCommandsLayoutNV* pIndirectCommandsLayout);
+typedef void (VKAPI_PTR *PFN_vkDestroyIndirectCommandsLayoutNV)(VkDevice device, VkIndirectCommandsLayoutNV indirectCommandsLayout, const VkAllocationCallbacks* pAllocator);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR void VKAPI_CALL vkGetGeneratedCommandsMemoryRequirementsNV(
+    VkDevice                                    device,
+    const VkGeneratedCommandsMemoryRequirementsInfoNV* pInfo,
+    VkMemoryRequirements2*                      pMemoryRequirements);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdPreprocessGeneratedCommandsNV(
+    VkCommandBuffer                             commandBuffer,
+    const VkGeneratedCommandsInfoNV*            pGeneratedCommandsInfo);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdExecuteGeneratedCommandsNV(
+    VkCommandBuffer                             commandBuffer,
+    VkBool32                                    isPreprocessed,
+    const VkGeneratedCommandsInfoNV*            pGeneratedCommandsInfo);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdBindPipelineShaderGroupNV(
+    VkCommandBuffer                             commandBuffer,
+    VkPipelineBindPoint                         pipelineBindPoint,
+    VkPipeline                                  pipeline,
+    uint32_t                                    groupIndex);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateIndirectCommandsLayoutNV(
+    VkDevice                                    device,
+    const VkIndirectCommandsLayoutCreateInfoNV* pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkIndirectCommandsLayoutNV*                 pIndirectCommandsLayout);
+
+VKAPI_ATTR void VKAPI_CALL vkDestroyIndirectCommandsLayoutNV(
+    VkDevice                                    device,
+    VkIndirectCommandsLayoutNV                  indirectCommandsLayout,
+    const VkAllocationCallbacks*                pAllocator);
+#endif
+
+
+#define VK_NV_inherited_viewport_scissor 1
+#define VK_NV_INHERITED_VIEWPORT_SCISSOR_SPEC_VERSION 1
+#define VK_NV_INHERITED_VIEWPORT_SCISSOR_EXTENSION_NAME "VK_NV_inherited_viewport_scissor"
+typedef struct VkPhysicalDeviceInheritedViewportScissorFeaturesNV {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           inheritedViewportScissor2D;
+} VkPhysicalDeviceInheritedViewportScissorFeaturesNV;
+
+typedef struct VkCommandBufferInheritanceViewportScissorInfoNV {
+    VkStructureType      sType;
+    const void*          pNext;
+    VkBool32             viewportScissor2D;
+    uint32_t             viewportDepthCount;
+    const VkViewport*    pViewportDepths;
+} VkCommandBufferInheritanceViewportScissorInfoNV;
+
+
+
+#define VK_EXT_texel_buffer_alignment 1
+#define VK_EXT_TEXEL_BUFFER_ALIGNMENT_SPEC_VERSION 1
+#define VK_EXT_TEXEL_BUFFER_ALIGNMENT_EXTENSION_NAME "VK_EXT_texel_buffer_alignment"
+typedef struct VkPhysicalDeviceTexelBufferAlignmentFeaturesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           texelBufferAlignment;
+} VkPhysicalDeviceTexelBufferAlignmentFeaturesEXT;
+
+typedef struct VkPhysicalDeviceTexelBufferAlignmentPropertiesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    VkDeviceSize       storageTexelBufferOffsetAlignmentBytes;
+    VkBool32           storageTexelBufferOffsetSingleTexelAlignment;
+    VkDeviceSize       uniformTexelBufferOffsetAlignmentBytes;
+    VkBool32           uniformTexelBufferOffsetSingleTexelAlignment;
+} VkPhysicalDeviceTexelBufferAlignmentPropertiesEXT;
+
+
+
+#define VK_QCOM_render_pass_transform 1
+#define VK_QCOM_RENDER_PASS_TRANSFORM_SPEC_VERSION 2
+#define VK_QCOM_RENDER_PASS_TRANSFORM_EXTENSION_NAME "VK_QCOM_render_pass_transform"
+typedef struct VkRenderPassTransformBeginInfoQCOM {
+    VkStructureType                  sType;
+    void*                            pNext;
+    VkSurfaceTransformFlagBitsKHR    transform;
+} VkRenderPassTransformBeginInfoQCOM;
+
+typedef struct VkCommandBufferInheritanceRenderPassTransformInfoQCOM {
+    VkStructureType                  sType;
+    void*                            pNext;
+    VkSurfaceTransformFlagBitsKHR    transform;
+    VkRect2D                         renderArea;
+} VkCommandBufferInheritanceRenderPassTransformInfoQCOM;
+
+
+
+#define VK_EXT_device_memory_report 1
+#define VK_EXT_DEVICE_MEMORY_REPORT_SPEC_VERSION 2
+#define VK_EXT_DEVICE_MEMORY_REPORT_EXTENSION_NAME "VK_EXT_device_memory_report"
+
+typedef enum VkDeviceMemoryReportEventTypeEXT {
+    VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_ALLOCATE_EXT = 0,
+    VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_FREE_EXT = 1,
+    VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_IMPORT_EXT = 2,
+    VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_UNIMPORT_EXT = 3,
+    VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_ALLOCATION_FAILED_EXT = 4,
+    VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_MAX_ENUM_EXT = 0x7FFFFFFF
+} VkDeviceMemoryReportEventTypeEXT;
+typedef VkFlags VkDeviceMemoryReportFlagsEXT;
+typedef struct VkPhysicalDeviceDeviceMemoryReportFeaturesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           deviceMemoryReport;
+} VkPhysicalDeviceDeviceMemoryReportFeaturesEXT;
+
+typedef struct VkDeviceMemoryReportCallbackDataEXT {
+    VkStructureType                     sType;
+    void*                               pNext;
+    VkDeviceMemoryReportFlagsEXT        flags;
+    VkDeviceMemoryReportEventTypeEXT    type;
+    uint64_t                            memoryObjectId;
+    VkDeviceSize                        size;
+    VkObjectType                        objectType;
+    uint64_t                            objectHandle;
+    uint32_t                            heapIndex;
+} VkDeviceMemoryReportCallbackDataEXT;
+
+typedef void (VKAPI_PTR *PFN_vkDeviceMemoryReportCallbackEXT)(
+    const VkDeviceMemoryReportCallbackDataEXT*  pCallbackData,
+    void*                                       pUserData);
+
+typedef struct VkDeviceDeviceMemoryReportCreateInfoEXT {
+    VkStructureType                        sType;
+    const void*                            pNext;
+    VkDeviceMemoryReportFlagsEXT           flags;
+    PFN_vkDeviceMemoryReportCallbackEXT    pfnUserCallback;
+    void*                                  pUserData;
+} VkDeviceDeviceMemoryReportCreateInfoEXT;
+
+
+
+#define VK_EXT_acquire_drm_display 1
+#define VK_EXT_ACQUIRE_DRM_DISPLAY_SPEC_VERSION 1
+#define VK_EXT_ACQUIRE_DRM_DISPLAY_EXTENSION_NAME "VK_EXT_acquire_drm_display"
+typedef VkResult (VKAPI_PTR *PFN_vkAcquireDrmDisplayEXT)(VkPhysicalDevice physicalDevice, int32_t drmFd, VkDisplayKHR display);
+typedef VkResult (VKAPI_PTR *PFN_vkGetDrmDisplayEXT)(VkPhysicalDevice physicalDevice, int32_t drmFd, uint32_t connectorId, VkDisplayKHR* display);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkAcquireDrmDisplayEXT(
+    VkPhysicalDevice                            physicalDevice,
+    int32_t                                     drmFd,
+    VkDisplayKHR                                display);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetDrmDisplayEXT(
+    VkPhysicalDevice                            physicalDevice,
+    int32_t                                     drmFd,
+    uint32_t                                    connectorId,
+    VkDisplayKHR*                               display);
+#endif
+
+
+#define VK_EXT_robustness2 1
+#define VK_EXT_ROBUSTNESS_2_SPEC_VERSION  1
+#define VK_EXT_ROBUSTNESS_2_EXTENSION_NAME "VK_EXT_robustness2"
+typedef struct VkPhysicalDeviceRobustness2FeaturesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           robustBufferAccess2;
+    VkBool32           robustImageAccess2;
+    VkBool32           nullDescriptor;
+} VkPhysicalDeviceRobustness2FeaturesEXT;
+
+typedef struct VkPhysicalDeviceRobustness2PropertiesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    VkDeviceSize       robustStorageBufferAccessSizeAlignment;
+    VkDeviceSize       robustUniformBufferAccessSizeAlignment;
+} VkPhysicalDeviceRobustness2PropertiesEXT;
+
+
+
+#define VK_EXT_custom_border_color 1
+#define VK_EXT_CUSTOM_BORDER_COLOR_SPEC_VERSION 12
+#define VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME "VK_EXT_custom_border_color"
+typedef struct VkSamplerCustomBorderColorCreateInfoEXT {
+    VkStructureType      sType;
+    const void*          pNext;
+    VkClearColorValue    customBorderColor;
+    VkFormat             format;
+} VkSamplerCustomBorderColorCreateInfoEXT;
+
+typedef struct VkPhysicalDeviceCustomBorderColorPropertiesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    uint32_t           maxCustomBorderColorSamplers;
+} VkPhysicalDeviceCustomBorderColorPropertiesEXT;
+
+typedef struct VkPhysicalDeviceCustomBorderColorFeaturesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           customBorderColors;
+    VkBool32           customBorderColorWithoutFormat;
+} VkPhysicalDeviceCustomBorderColorFeaturesEXT;
+
+
+
+#define VK_GOOGLE_user_type 1
+#define VK_GOOGLE_USER_TYPE_SPEC_VERSION  1
+#define VK_GOOGLE_USER_TYPE_EXTENSION_NAME "VK_GOOGLE_user_type"
+
+
+#define VK_EXT_private_data 1
+VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkPrivateDataSlotEXT)
+#define VK_EXT_PRIVATE_DATA_SPEC_VERSION  1
+#define VK_EXT_PRIVATE_DATA_EXTENSION_NAME "VK_EXT_private_data"
+
+typedef enum VkPrivateDataSlotCreateFlagBitsEXT {
+    VK_PRIVATE_DATA_SLOT_CREATE_FLAG_BITS_MAX_ENUM_EXT = 0x7FFFFFFF
+} VkPrivateDataSlotCreateFlagBitsEXT;
+typedef VkFlags VkPrivateDataSlotCreateFlagsEXT;
+typedef struct VkPhysicalDevicePrivateDataFeaturesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           privateData;
+} VkPhysicalDevicePrivateDataFeaturesEXT;
+
+typedef struct VkDevicePrivateDataCreateInfoEXT {
+    VkStructureType    sType;
+    const void*        pNext;
+    uint32_t           privateDataSlotRequestCount;
+} VkDevicePrivateDataCreateInfoEXT;
+
+typedef struct VkPrivateDataSlotCreateInfoEXT {
+    VkStructureType                    sType;
+    const void*                        pNext;
+    VkPrivateDataSlotCreateFlagsEXT    flags;
+} VkPrivateDataSlotCreateInfoEXT;
+
+typedef VkResult (VKAPI_PTR *PFN_vkCreatePrivateDataSlotEXT)(VkDevice device, const VkPrivateDataSlotCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkPrivateDataSlotEXT* pPrivateDataSlot);
+typedef void (VKAPI_PTR *PFN_vkDestroyPrivateDataSlotEXT)(VkDevice device, VkPrivateDataSlotEXT privateDataSlot, const VkAllocationCallbacks* pAllocator);
+typedef VkResult (VKAPI_PTR *PFN_vkSetPrivateDataEXT)(VkDevice device, VkObjectType objectType, uint64_t objectHandle, VkPrivateDataSlotEXT privateDataSlot, uint64_t data);
+typedef void (VKAPI_PTR *PFN_vkGetPrivateDataEXT)(VkDevice device, VkObjectType objectType, uint64_t objectHandle, VkPrivateDataSlotEXT privateDataSlot, uint64_t* pData);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkCreatePrivateDataSlotEXT(
+    VkDevice                                    device,
+    const VkPrivateDataSlotCreateInfoEXT*       pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkPrivateDataSlotEXT*                       pPrivateDataSlot);
+
+VKAPI_ATTR void VKAPI_CALL vkDestroyPrivateDataSlotEXT(
+    VkDevice                                    device,
+    VkPrivateDataSlotEXT                        privateDataSlot,
+    const VkAllocationCallbacks*                pAllocator);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkSetPrivateDataEXT(
+    VkDevice                                    device,
+    VkObjectType                                objectType,
+    uint64_t                                    objectHandle,
+    VkPrivateDataSlotEXT                        privateDataSlot,
+    uint64_t                                    data);
+
+VKAPI_ATTR void VKAPI_CALL vkGetPrivateDataEXT(
+    VkDevice                                    device,
+    VkObjectType                                objectType,
+    uint64_t                                    objectHandle,
+    VkPrivateDataSlotEXT                        privateDataSlot,
+    uint64_t*                                   pData);
+#endif
+
+
+#define VK_EXT_pipeline_creation_cache_control 1
+#define VK_EXT_PIPELINE_CREATION_CACHE_CONTROL_SPEC_VERSION 3
+#define VK_EXT_PIPELINE_CREATION_CACHE_CONTROL_EXTENSION_NAME "VK_EXT_pipeline_creation_cache_control"
+typedef struct VkPhysicalDevicePipelineCreationCacheControlFeaturesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           pipelineCreationCacheControl;
+} VkPhysicalDevicePipelineCreationCacheControlFeaturesEXT;
+
+
+
+#define VK_NV_device_diagnostics_config 1
+#define VK_NV_DEVICE_DIAGNOSTICS_CONFIG_SPEC_VERSION 1
+#define VK_NV_DEVICE_DIAGNOSTICS_CONFIG_EXTENSION_NAME "VK_NV_device_diagnostics_config"
+
+typedef enum VkDeviceDiagnosticsConfigFlagBitsNV {
+    VK_DEVICE_DIAGNOSTICS_CONFIG_ENABLE_SHADER_DEBUG_INFO_BIT_NV = 0x00000001,
+    VK_DEVICE_DIAGNOSTICS_CONFIG_ENABLE_RESOURCE_TRACKING_BIT_NV = 0x00000002,
+    VK_DEVICE_DIAGNOSTICS_CONFIG_ENABLE_AUTOMATIC_CHECKPOINTS_BIT_NV = 0x00000004,
+    VK_DEVICE_DIAGNOSTICS_CONFIG_FLAG_BITS_MAX_ENUM_NV = 0x7FFFFFFF
+} VkDeviceDiagnosticsConfigFlagBitsNV;
+typedef VkFlags VkDeviceDiagnosticsConfigFlagsNV;
+typedef struct VkPhysicalDeviceDiagnosticsConfigFeaturesNV {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           diagnosticsConfig;
+} VkPhysicalDeviceDiagnosticsConfigFeaturesNV;
+
+typedef struct VkDeviceDiagnosticsConfigCreateInfoNV {
+    VkStructureType                     sType;
+    const void*                         pNext;
+    VkDeviceDiagnosticsConfigFlagsNV    flags;
+} VkDeviceDiagnosticsConfigCreateInfoNV;
+
+
+
+#define VK_QCOM_render_pass_store_ops 1
+#define VK_QCOM_RENDER_PASS_STORE_OPS_SPEC_VERSION 2
+#define VK_QCOM_RENDER_PASS_STORE_OPS_EXTENSION_NAME "VK_QCOM_render_pass_store_ops"
+
+
+#define VK_NV_fragment_shading_rate_enums 1
+#define VK_NV_FRAGMENT_SHADING_RATE_ENUMS_SPEC_VERSION 1
+#define VK_NV_FRAGMENT_SHADING_RATE_ENUMS_EXTENSION_NAME "VK_NV_fragment_shading_rate_enums"
+
+typedef enum VkFragmentShadingRateTypeNV {
+    VK_FRAGMENT_SHADING_RATE_TYPE_FRAGMENT_SIZE_NV = 0,
+    VK_FRAGMENT_SHADING_RATE_TYPE_ENUMS_NV = 1,
+    VK_FRAGMENT_SHADING_RATE_TYPE_MAX_ENUM_NV = 0x7FFFFFFF
+} VkFragmentShadingRateTypeNV;
+
+typedef enum VkFragmentShadingRateNV {
+    VK_FRAGMENT_SHADING_RATE_1_INVOCATION_PER_PIXEL_NV = 0,
+    VK_FRAGMENT_SHADING_RATE_1_INVOCATION_PER_1X2_PIXELS_NV = 1,
+    VK_FRAGMENT_SHADING_RATE_1_INVOCATION_PER_2X1_PIXELS_NV = 4,
+    VK_FRAGMENT_SHADING_RATE_1_INVOCATION_PER_2X2_PIXELS_NV = 5,
+    VK_FRAGMENT_SHADING_RATE_1_INVOCATION_PER_2X4_PIXELS_NV = 6,
+    VK_FRAGMENT_SHADING_RATE_1_INVOCATION_PER_4X2_PIXELS_NV = 9,
+    VK_FRAGMENT_SHADING_RATE_1_INVOCATION_PER_4X4_PIXELS_NV = 10,
+    VK_FRAGMENT_SHADING_RATE_2_INVOCATIONS_PER_PIXEL_NV = 11,
+    VK_FRAGMENT_SHADING_RATE_4_INVOCATIONS_PER_PIXEL_NV = 12,
+    VK_FRAGMENT_SHADING_RATE_8_INVOCATIONS_PER_PIXEL_NV = 13,
+    VK_FRAGMENT_SHADING_RATE_16_INVOCATIONS_PER_PIXEL_NV = 14,
+    VK_FRAGMENT_SHADING_RATE_NO_INVOCATIONS_NV = 15,
+    VK_FRAGMENT_SHADING_RATE_MAX_ENUM_NV = 0x7FFFFFFF
+} VkFragmentShadingRateNV;
+typedef struct VkPhysicalDeviceFragmentShadingRateEnumsFeaturesNV {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           fragmentShadingRateEnums;
+    VkBool32           supersampleFragmentShadingRates;
+    VkBool32           noInvocationFragmentShadingRates;
+} VkPhysicalDeviceFragmentShadingRateEnumsFeaturesNV;
+
+typedef struct VkPhysicalDeviceFragmentShadingRateEnumsPropertiesNV {
+    VkStructureType          sType;
+    void*                    pNext;
+    VkSampleCountFlagBits    maxFragmentShadingRateInvocationCount;
+} VkPhysicalDeviceFragmentShadingRateEnumsPropertiesNV;
+
+typedef struct VkPipelineFragmentShadingRateEnumStateCreateInfoNV {
+    VkStructureType                       sType;
+    const void*                           pNext;
+    VkFragmentShadingRateTypeNV           shadingRateType;
+    VkFragmentShadingRateNV               shadingRate;
+    VkFragmentShadingRateCombinerOpKHR    combinerOps[2];
+} VkPipelineFragmentShadingRateEnumStateCreateInfoNV;
+
+typedef void (VKAPI_PTR *PFN_vkCmdSetFragmentShadingRateEnumNV)(VkCommandBuffer           commandBuffer, VkFragmentShadingRateNV                     shadingRate, const VkFragmentShadingRateCombinerOpKHR    combinerOps[2]);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR void VKAPI_CALL vkCmdSetFragmentShadingRateEnumNV(
+    VkCommandBuffer                             commandBuffer,
+    VkFragmentShadingRateNV                     shadingRate,
+    const VkFragmentShadingRateCombinerOpKHR    combinerOps[2]);
+#endif
+
+
+#define VK_NV_ray_tracing_motion_blur 1
+#define VK_NV_RAY_TRACING_MOTION_BLUR_SPEC_VERSION 1
+#define VK_NV_RAY_TRACING_MOTION_BLUR_EXTENSION_NAME "VK_NV_ray_tracing_motion_blur"
+
+typedef enum VkAccelerationStructureMotionInstanceTypeNV {
+    VK_ACCELERATION_STRUCTURE_MOTION_INSTANCE_TYPE_STATIC_NV = 0,
+    VK_ACCELERATION_STRUCTURE_MOTION_INSTANCE_TYPE_MATRIX_MOTION_NV = 1,
+    VK_ACCELERATION_STRUCTURE_MOTION_INSTANCE_TYPE_SRT_MOTION_NV = 2,
+    VK_ACCELERATION_STRUCTURE_MOTION_INSTANCE_TYPE_MAX_ENUM_NV = 0x7FFFFFFF
+} VkAccelerationStructureMotionInstanceTypeNV;
+typedef VkFlags VkAccelerationStructureMotionInfoFlagsNV;
+typedef VkFlags VkAccelerationStructureMotionInstanceFlagsNV;
+typedef union VkDeviceOrHostAddressConstKHR {
+    VkDeviceAddress    deviceAddress;
+    const void*        hostAddress;
+} VkDeviceOrHostAddressConstKHR;
+
+typedef struct VkAccelerationStructureGeometryMotionTrianglesDataNV {
+    VkStructureType                  sType;
+    const void*                      pNext;
+    VkDeviceOrHostAddressConstKHR    vertexData;
+} VkAccelerationStructureGeometryMotionTrianglesDataNV;
+
+typedef struct VkAccelerationStructureMotionInfoNV {
+    VkStructureType                             sType;
+    const void*                                 pNext;
+    uint32_t                                    maxInstances;
+    VkAccelerationStructureMotionInfoFlagsNV    flags;
+} VkAccelerationStructureMotionInfoNV;
+
+typedef struct VkAccelerationStructureMatrixMotionInstanceNV {
+    VkTransformMatrixKHR          transformT0;
+    VkTransformMatrixKHR          transformT1;
+    uint32_t                      instanceCustomIndex:24;
+    uint32_t                      mask:8;
+    uint32_t                      instanceShaderBindingTableRecordOffset:24;
+    VkGeometryInstanceFlagsKHR    flags:8;
+    uint64_t                      accelerationStructureReference;
+} VkAccelerationStructureMatrixMotionInstanceNV;
+
+typedef struct VkSRTDataNV {
+    float    sx;
+    float    a;
+    float    b;
+    float    pvx;
+    float    sy;
+    float    c;
+    float    pvy;
+    float    sz;
+    float    pvz;
+    float    qx;
+    float    qy;
+    float    qz;
+    float    qw;
+    float    tx;
+    float    ty;
+    float    tz;
+} VkSRTDataNV;
+
+typedef struct VkAccelerationStructureSRTMotionInstanceNV {
+    VkSRTDataNV                   transformT0;
+    VkSRTDataNV                   transformT1;
+    uint32_t                      instanceCustomIndex:24;
+    uint32_t                      mask:8;
+    uint32_t                      instanceShaderBindingTableRecordOffset:24;
+    VkGeometryInstanceFlagsKHR    flags:8;
+    uint64_t                      accelerationStructureReference;
+} VkAccelerationStructureSRTMotionInstanceNV;
+
+typedef union VkAccelerationStructureMotionInstanceDataNV {
+    VkAccelerationStructureInstanceKHR               staticInstance;
+    VkAccelerationStructureMatrixMotionInstanceNV    matrixMotionInstance;
+    VkAccelerationStructureSRTMotionInstanceNV       srtMotionInstance;
+} VkAccelerationStructureMotionInstanceDataNV;
+
+typedef struct VkAccelerationStructureMotionInstanceNV {
+    VkAccelerationStructureMotionInstanceTypeNV     type;
+    VkAccelerationStructureMotionInstanceFlagsNV    flags;
+    VkAccelerationStructureMotionInstanceDataNV     data;
+} VkAccelerationStructureMotionInstanceNV;
+
+typedef struct VkPhysicalDeviceRayTracingMotionBlurFeaturesNV {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           rayTracingMotionBlur;
+    VkBool32           rayTracingMotionBlurPipelineTraceRaysIndirect;
+} VkPhysicalDeviceRayTracingMotionBlurFeaturesNV;
+
+
+
+#define VK_EXT_ycbcr_2plane_444_formats 1
+#define VK_EXT_YCBCR_2PLANE_444_FORMATS_SPEC_VERSION 1
+#define VK_EXT_YCBCR_2PLANE_444_FORMATS_EXTENSION_NAME "VK_EXT_ycbcr_2plane_444_formats"
+typedef struct VkPhysicalDeviceYcbcr2Plane444FormatsFeaturesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           ycbcr2plane444Formats;
+} VkPhysicalDeviceYcbcr2Plane444FormatsFeaturesEXT;
+
+
+
+#define VK_EXT_fragment_density_map2 1
+#define VK_EXT_FRAGMENT_DENSITY_MAP_2_SPEC_VERSION 1
+#define VK_EXT_FRAGMENT_DENSITY_MAP_2_EXTENSION_NAME "VK_EXT_fragment_density_map2"
+typedef struct VkPhysicalDeviceFragmentDensityMap2FeaturesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           fragmentDensityMapDeferred;
+} VkPhysicalDeviceFragmentDensityMap2FeaturesEXT;
+
+typedef struct VkPhysicalDeviceFragmentDensityMap2PropertiesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           subsampledLoads;
+    VkBool32           subsampledCoarseReconstructionEarlyAccess;
+    uint32_t           maxSubsampledArrayLayers;
+    uint32_t           maxDescriptorSetSubsampledSamplers;
+} VkPhysicalDeviceFragmentDensityMap2PropertiesEXT;
+
+
+
+#define VK_QCOM_rotated_copy_commands 1
+#define VK_QCOM_ROTATED_COPY_COMMANDS_SPEC_VERSION 1
+#define VK_QCOM_ROTATED_COPY_COMMANDS_EXTENSION_NAME "VK_QCOM_rotated_copy_commands"
+typedef struct VkCopyCommandTransformInfoQCOM {
+    VkStructureType                  sType;
+    const void*                      pNext;
+    VkSurfaceTransformFlagBitsKHR    transform;
+} VkCopyCommandTransformInfoQCOM;
+
+
+
+#define VK_EXT_image_robustness 1
+#define VK_EXT_IMAGE_ROBUSTNESS_SPEC_VERSION 1
+#define VK_EXT_IMAGE_ROBUSTNESS_EXTENSION_NAME "VK_EXT_image_robustness"
+typedef struct VkPhysicalDeviceImageRobustnessFeaturesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           robustImageAccess;
+} VkPhysicalDeviceImageRobustnessFeaturesEXT;
+
+
+
+#define VK_EXT_4444_formats 1
+#define VK_EXT_4444_FORMATS_SPEC_VERSION  1
+#define VK_EXT_4444_FORMATS_EXTENSION_NAME "VK_EXT_4444_formats"
+typedef struct VkPhysicalDevice4444FormatsFeaturesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           formatA4R4G4B4;
+    VkBool32           formatA4B4G4R4;
+} VkPhysicalDevice4444FormatsFeaturesEXT;
+
+
+
+#define VK_EXT_rgba10x6_formats 1
+#define VK_EXT_RGBA10X6_FORMATS_SPEC_VERSION 1
+#define VK_EXT_RGBA10X6_FORMATS_EXTENSION_NAME "VK_EXT_rgba10x6_formats"
+typedef struct VkPhysicalDeviceRGBA10X6FormatsFeaturesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           formatRgba10x6WithoutYCbCrSampler;
+} VkPhysicalDeviceRGBA10X6FormatsFeaturesEXT;
+
+
+
+#define VK_NV_acquire_winrt_display 1
+#define VK_NV_ACQUIRE_WINRT_DISPLAY_SPEC_VERSION 1
+#define VK_NV_ACQUIRE_WINRT_DISPLAY_EXTENSION_NAME "VK_NV_acquire_winrt_display"
+typedef VkResult (VKAPI_PTR *PFN_vkAcquireWinrtDisplayNV)(VkPhysicalDevice physicalDevice, VkDisplayKHR display);
+typedef VkResult (VKAPI_PTR *PFN_vkGetWinrtDisplayNV)(VkPhysicalDevice physicalDevice, uint32_t deviceRelativeId, VkDisplayKHR* pDisplay);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkAcquireWinrtDisplayNV(
+    VkPhysicalDevice                            physicalDevice,
+    VkDisplayKHR                                display);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetWinrtDisplayNV(
+    VkPhysicalDevice                            physicalDevice,
+    uint32_t                                    deviceRelativeId,
+    VkDisplayKHR*                               pDisplay);
+#endif
+
+
+#define VK_VALVE_mutable_descriptor_type 1
+#define VK_VALVE_MUTABLE_DESCRIPTOR_TYPE_SPEC_VERSION 1
+#define VK_VALVE_MUTABLE_DESCRIPTOR_TYPE_EXTENSION_NAME "VK_VALVE_mutable_descriptor_type"
+typedef struct VkPhysicalDeviceMutableDescriptorTypeFeaturesVALVE {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           mutableDescriptorType;
+} VkPhysicalDeviceMutableDescriptorTypeFeaturesVALVE;
+
+typedef struct VkMutableDescriptorTypeListVALVE {
+    uint32_t                   descriptorTypeCount;
+    const VkDescriptorType*    pDescriptorTypes;
+} VkMutableDescriptorTypeListVALVE;
+
+typedef struct VkMutableDescriptorTypeCreateInfoVALVE {
+    VkStructureType                            sType;
+    const void*                                pNext;
+    uint32_t                                   mutableDescriptorTypeListCount;
+    const VkMutableDescriptorTypeListVALVE*    pMutableDescriptorTypeLists;
+} VkMutableDescriptorTypeCreateInfoVALVE;
+
+
+
+#define VK_EXT_vertex_input_dynamic_state 1
+#define VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_SPEC_VERSION 2
+#define VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME "VK_EXT_vertex_input_dynamic_state"
+typedef struct VkPhysicalDeviceVertexInputDynamicStateFeaturesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           vertexInputDynamicState;
+} VkPhysicalDeviceVertexInputDynamicStateFeaturesEXT;
+
+typedef struct VkVertexInputBindingDescription2EXT {
+    VkStructureType      sType;
+    void*                pNext;
+    uint32_t             binding;
+    uint32_t             stride;
+    VkVertexInputRate    inputRate;
+    uint32_t             divisor;
+} VkVertexInputBindingDescription2EXT;
+
+typedef struct VkVertexInputAttributeDescription2EXT {
+    VkStructureType    sType;
+    void*              pNext;
+    uint32_t           location;
+    uint32_t           binding;
+    VkFormat           format;
+    uint32_t           offset;
+} VkVertexInputAttributeDescription2EXT;
+
+typedef void (VKAPI_PTR *PFN_vkCmdSetVertexInputEXT)(VkCommandBuffer commandBuffer, uint32_t vertexBindingDescriptionCount, const VkVertexInputBindingDescription2EXT* pVertexBindingDescriptions, uint32_t vertexAttributeDescriptionCount, const VkVertexInputAttributeDescription2EXT* pVertexAttributeDescriptions);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR void VKAPI_CALL vkCmdSetVertexInputEXT(
+    VkCommandBuffer                             commandBuffer,
+    uint32_t                                    vertexBindingDescriptionCount,
+    const VkVertexInputBindingDescription2EXT*  pVertexBindingDescriptions,
+    uint32_t                                    vertexAttributeDescriptionCount,
+    const VkVertexInputAttributeDescription2EXT* pVertexAttributeDescriptions);
+#endif
+
+
+#define VK_EXT_physical_device_drm 1
+#define VK_EXT_PHYSICAL_DEVICE_DRM_SPEC_VERSION 1
+#define VK_EXT_PHYSICAL_DEVICE_DRM_EXTENSION_NAME "VK_EXT_physical_device_drm"
+typedef struct VkPhysicalDeviceDrmPropertiesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           hasPrimary;
+    VkBool32           hasRender;
+    int64_t            primaryMajor;
+    int64_t            primaryMinor;
+    int64_t            renderMajor;
+    int64_t            renderMinor;
+} VkPhysicalDeviceDrmPropertiesEXT;
+
+
+
+#define VK_EXT_primitive_topology_list_restart 1
+#define VK_EXT_PRIMITIVE_TOPOLOGY_LIST_RESTART_SPEC_VERSION 1
+#define VK_EXT_PRIMITIVE_TOPOLOGY_LIST_RESTART_EXTENSION_NAME "VK_EXT_primitive_topology_list_restart"
+typedef struct VkPhysicalDevicePrimitiveTopologyListRestartFeaturesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           primitiveTopologyListRestart;
+    VkBool32           primitiveTopologyPatchListRestart;
+} VkPhysicalDevicePrimitiveTopologyListRestartFeaturesEXT;
+
+
+
+#define VK_HUAWEI_subpass_shading 1
+#define VK_HUAWEI_SUBPASS_SHADING_SPEC_VERSION 2
+#define VK_HUAWEI_SUBPASS_SHADING_EXTENSION_NAME "VK_HUAWEI_subpass_shading"
+typedef struct VkSubpassShadingPipelineCreateInfoHUAWEI {
+    VkStructureType    sType;
+    void*              pNext;
+    VkRenderPass       renderPass;
+    uint32_t           subpass;
+} VkSubpassShadingPipelineCreateInfoHUAWEI;
+
+typedef struct VkPhysicalDeviceSubpassShadingFeaturesHUAWEI {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           subpassShading;
+} VkPhysicalDeviceSubpassShadingFeaturesHUAWEI;
+
+typedef struct VkPhysicalDeviceSubpassShadingPropertiesHUAWEI {
+    VkStructureType    sType;
+    void*              pNext;
+    uint32_t           maxSubpassShadingWorkgroupSizeAspectRatio;
+} VkPhysicalDeviceSubpassShadingPropertiesHUAWEI;
+
+typedef VkResult (VKAPI_PTR *PFN_vkGetDeviceSubpassShadingMaxWorkgroupSizeHUAWEI)(VkDevice device, VkRenderPass renderpass, VkExtent2D* pMaxWorkgroupSize);
+typedef void (VKAPI_PTR *PFN_vkCmdSubpassShadingHUAWEI)(VkCommandBuffer commandBuffer);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkGetDeviceSubpassShadingMaxWorkgroupSizeHUAWEI(
+    VkDevice                                    device,
+    VkRenderPass                                renderpass,
+    VkExtent2D*                                 pMaxWorkgroupSize);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdSubpassShadingHUAWEI(
+    VkCommandBuffer                             commandBuffer);
+#endif
+
+
+#define VK_HUAWEI_invocation_mask 1
+#define VK_HUAWEI_INVOCATION_MASK_SPEC_VERSION 1
+#define VK_HUAWEI_INVOCATION_MASK_EXTENSION_NAME "VK_HUAWEI_invocation_mask"
+typedef struct VkPhysicalDeviceInvocationMaskFeaturesHUAWEI {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           invocationMask;
+} VkPhysicalDeviceInvocationMaskFeaturesHUAWEI;
+
+typedef void (VKAPI_PTR *PFN_vkCmdBindInvocationMaskHUAWEI)(VkCommandBuffer commandBuffer, VkImageView imageView, VkImageLayout imageLayout);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR void VKAPI_CALL vkCmdBindInvocationMaskHUAWEI(
+    VkCommandBuffer                             commandBuffer,
+    VkImageView                                 imageView,
+    VkImageLayout                               imageLayout);
+#endif
+
+
+#define VK_NV_external_memory_rdma 1
+typedef void* VkRemoteAddressNV;
+#define VK_NV_EXTERNAL_MEMORY_RDMA_SPEC_VERSION 1
+#define VK_NV_EXTERNAL_MEMORY_RDMA_EXTENSION_NAME "VK_NV_external_memory_rdma"
+typedef struct VkMemoryGetRemoteAddressInfoNV {
+    VkStructureType                       sType;
+    const void*                           pNext;
+    VkDeviceMemory                        memory;
+    VkExternalMemoryHandleTypeFlagBits    handleType;
+} VkMemoryGetRemoteAddressInfoNV;
+
+typedef struct VkPhysicalDeviceExternalMemoryRDMAFeaturesNV {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           externalMemoryRDMA;
+} VkPhysicalDeviceExternalMemoryRDMAFeaturesNV;
+
+typedef VkResult (VKAPI_PTR *PFN_vkGetMemoryRemoteAddressNV)(VkDevice device, const VkMemoryGetRemoteAddressInfoNV* pMemoryGetRemoteAddressInfo, VkRemoteAddressNV* pAddress);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkGetMemoryRemoteAddressNV(
+    VkDevice                                    device,
+    const VkMemoryGetRemoteAddressInfoNV*       pMemoryGetRemoteAddressInfo,
+    VkRemoteAddressNV*                          pAddress);
+#endif
+
+
+#define VK_EXT_extended_dynamic_state2 1
+#define VK_EXT_EXTENDED_DYNAMIC_STATE_2_SPEC_VERSION 1
+#define VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME "VK_EXT_extended_dynamic_state2"
+typedef struct VkPhysicalDeviceExtendedDynamicState2FeaturesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           extendedDynamicState2;
+    VkBool32           extendedDynamicState2LogicOp;
+    VkBool32           extendedDynamicState2PatchControlPoints;
+} VkPhysicalDeviceExtendedDynamicState2FeaturesEXT;
+
+typedef void (VKAPI_PTR *PFN_vkCmdSetPatchControlPointsEXT)(VkCommandBuffer commandBuffer, uint32_t patchControlPoints);
+typedef void (VKAPI_PTR *PFN_vkCmdSetRasterizerDiscardEnableEXT)(VkCommandBuffer commandBuffer, VkBool32 rasterizerDiscardEnable);
+typedef void (VKAPI_PTR *PFN_vkCmdSetDepthBiasEnableEXT)(VkCommandBuffer commandBuffer, VkBool32 depthBiasEnable);
+typedef void (VKAPI_PTR *PFN_vkCmdSetLogicOpEXT)(VkCommandBuffer commandBuffer, VkLogicOp logicOp);
+typedef void (VKAPI_PTR *PFN_vkCmdSetPrimitiveRestartEnableEXT)(VkCommandBuffer commandBuffer, VkBool32 primitiveRestartEnable);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR void VKAPI_CALL vkCmdSetPatchControlPointsEXT(
+    VkCommandBuffer                             commandBuffer,
+    uint32_t                                    patchControlPoints);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdSetRasterizerDiscardEnableEXT(
+    VkCommandBuffer                             commandBuffer,
+    VkBool32                                    rasterizerDiscardEnable);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdSetDepthBiasEnableEXT(
+    VkCommandBuffer                             commandBuffer,
+    VkBool32                                    depthBiasEnable);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdSetLogicOpEXT(
+    VkCommandBuffer                             commandBuffer,
+    VkLogicOp                                   logicOp);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdSetPrimitiveRestartEnableEXT(
+    VkCommandBuffer                             commandBuffer,
+    VkBool32                                    primitiveRestartEnable);
+#endif
+
+
+#define VK_EXT_color_write_enable 1
+#define VK_EXT_COLOR_WRITE_ENABLE_SPEC_VERSION 1
+#define VK_EXT_COLOR_WRITE_ENABLE_EXTENSION_NAME "VK_EXT_color_write_enable"
+typedef struct VkPhysicalDeviceColorWriteEnableFeaturesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           colorWriteEnable;
+} VkPhysicalDeviceColorWriteEnableFeaturesEXT;
+
+typedef struct VkPipelineColorWriteCreateInfoEXT {
+    VkStructureType    sType;
+    const void*        pNext;
+    uint32_t           attachmentCount;
+    const VkBool32*    pColorWriteEnables;
+} VkPipelineColorWriteCreateInfoEXT;
+
+typedef void                                    (VKAPI_PTR *PFN_vkCmdSetColorWriteEnableEXT)(VkCommandBuffer       commandBuffer, uint32_t                                attachmentCount, const VkBool32*   pColorWriteEnables);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR void                                    VKAPI_CALL vkCmdSetColorWriteEnableEXT(
+    VkCommandBuffer                             commandBuffer,
+    uint32_t                                    attachmentCount,
+    const VkBool32*                             pColorWriteEnables);
+#endif
+
+
+#define VK_EXT_global_priority_query 1
+#define VK_MAX_GLOBAL_PRIORITY_SIZE_EXT   16U
+#define VK_EXT_GLOBAL_PRIORITY_QUERY_SPEC_VERSION 1
+#define VK_EXT_GLOBAL_PRIORITY_QUERY_EXTENSION_NAME "VK_EXT_global_priority_query"
+typedef struct VkPhysicalDeviceGlobalPriorityQueryFeaturesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           globalPriorityQuery;
+} VkPhysicalDeviceGlobalPriorityQueryFeaturesEXT;
+
+typedef struct VkQueueFamilyGlobalPriorityPropertiesEXT {
+    VkStructureType             sType;
+    void*                       pNext;
+    uint32_t                    priorityCount;
+    VkQueueGlobalPriorityEXT    priorities[VK_MAX_GLOBAL_PRIORITY_SIZE_EXT];
+} VkQueueFamilyGlobalPriorityPropertiesEXT;
+
+
+
+#define VK_EXT_multi_draw 1
+#define VK_EXT_MULTI_DRAW_SPEC_VERSION    1
+#define VK_EXT_MULTI_DRAW_EXTENSION_NAME  "VK_EXT_multi_draw"
+typedef struct VkPhysicalDeviceMultiDrawFeaturesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           multiDraw;
+} VkPhysicalDeviceMultiDrawFeaturesEXT;
+
+typedef struct VkPhysicalDeviceMultiDrawPropertiesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    uint32_t           maxMultiDrawCount;
+} VkPhysicalDeviceMultiDrawPropertiesEXT;
+
+typedef struct VkMultiDrawInfoEXT {
+    uint32_t    firstVertex;
+    uint32_t    vertexCount;
+} VkMultiDrawInfoEXT;
+
+typedef struct VkMultiDrawIndexedInfoEXT {
+    uint32_t    firstIndex;
+    uint32_t    indexCount;
+    int32_t     vertexOffset;
+} VkMultiDrawIndexedInfoEXT;
+
+typedef void (VKAPI_PTR *PFN_vkCmdDrawMultiEXT)(VkCommandBuffer commandBuffer, uint32_t drawCount, const VkMultiDrawInfoEXT* pVertexInfo, uint32_t instanceCount, uint32_t firstInstance, uint32_t stride);
+typedef void (VKAPI_PTR *PFN_vkCmdDrawMultiIndexedEXT)(VkCommandBuffer commandBuffer, uint32_t drawCount, const VkMultiDrawIndexedInfoEXT* pIndexInfo, uint32_t instanceCount, uint32_t firstInstance, uint32_t stride, const int32_t* pVertexOffset);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR void VKAPI_CALL vkCmdDrawMultiEXT(
+    VkCommandBuffer                             commandBuffer,
+    uint32_t                                    drawCount,
+    const VkMultiDrawInfoEXT*                   pVertexInfo,
+    uint32_t                                    instanceCount,
+    uint32_t                                    firstInstance,
+    uint32_t                                    stride);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdDrawMultiIndexedEXT(
+    VkCommandBuffer                             commandBuffer,
+    uint32_t                                    drawCount,
+    const VkMultiDrawIndexedInfoEXT*            pIndexInfo,
+    uint32_t                                    instanceCount,
+    uint32_t                                    firstInstance,
+    uint32_t                                    stride,
+    const int32_t*                              pVertexOffset);
+#endif
+
+
+#define VK_EXT_load_store_op_none 1
+#define VK_EXT_LOAD_STORE_OP_NONE_SPEC_VERSION 1
+#define VK_EXT_LOAD_STORE_OP_NONE_EXTENSION_NAME "VK_EXT_load_store_op_none"
+
+
+#define VK_EXT_border_color_swizzle 1
+#define VK_EXT_BORDER_COLOR_SWIZZLE_SPEC_VERSION 1
+#define VK_EXT_BORDER_COLOR_SWIZZLE_EXTENSION_NAME "VK_EXT_border_color_swizzle"
+typedef struct VkPhysicalDeviceBorderColorSwizzleFeaturesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           borderColorSwizzle;
+    VkBool32           borderColorSwizzleFromImage;
+} VkPhysicalDeviceBorderColorSwizzleFeaturesEXT;
+
+typedef struct VkSamplerBorderColorComponentMappingCreateInfoEXT {
+    VkStructureType       sType;
+    const void*           pNext;
+    VkComponentMapping    components;
+    VkBool32              srgb;
+} VkSamplerBorderColorComponentMappingCreateInfoEXT;
+
+
+
+#define VK_EXT_pageable_device_local_memory 1
+#define VK_EXT_PAGEABLE_DEVICE_LOCAL_MEMORY_SPEC_VERSION 1
+#define VK_EXT_PAGEABLE_DEVICE_LOCAL_MEMORY_EXTENSION_NAME "VK_EXT_pageable_device_local_memory"
+typedef struct VkPhysicalDevicePageableDeviceLocalMemoryFeaturesEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           pageableDeviceLocalMemory;
+} VkPhysicalDevicePageableDeviceLocalMemoryFeaturesEXT;
+
+typedef void (VKAPI_PTR *PFN_vkSetDeviceMemoryPriorityEXT)(VkDevice       device, VkDeviceMemory memory, float          priority);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR void VKAPI_CALL vkSetDeviceMemoryPriorityEXT(
+    VkDevice                                    device,
+    VkDeviceMemory                              memory,
+    float                                       priority);
+#endif
+
+
+#define VK_KHR_acceleration_structure 1
+VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkAccelerationStructureKHR)
+#define VK_KHR_ACCELERATION_STRUCTURE_SPEC_VERSION 13
+#define VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME "VK_KHR_acceleration_structure"
+
+typedef enum VkBuildAccelerationStructureModeKHR {
+    VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR = 0,
+    VK_BUILD_ACCELERATION_STRUCTURE_MODE_UPDATE_KHR = 1,
+    VK_BUILD_ACCELERATION_STRUCTURE_MODE_MAX_ENUM_KHR = 0x7FFFFFFF
+} VkBuildAccelerationStructureModeKHR;
+
+typedef enum VkAccelerationStructureBuildTypeKHR {
+    VK_ACCELERATION_STRUCTURE_BUILD_TYPE_HOST_KHR = 0,
+    VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR = 1,
+    VK_ACCELERATION_STRUCTURE_BUILD_TYPE_HOST_OR_DEVICE_KHR = 2,
+    VK_ACCELERATION_STRUCTURE_BUILD_TYPE_MAX_ENUM_KHR = 0x7FFFFFFF
+} VkAccelerationStructureBuildTypeKHR;
+
+typedef enum VkAccelerationStructureCompatibilityKHR {
+    VK_ACCELERATION_STRUCTURE_COMPATIBILITY_COMPATIBLE_KHR = 0,
+    VK_ACCELERATION_STRUCTURE_COMPATIBILITY_INCOMPATIBLE_KHR = 1,
+    VK_ACCELERATION_STRUCTURE_COMPATIBILITY_MAX_ENUM_KHR = 0x7FFFFFFF
+} VkAccelerationStructureCompatibilityKHR;
+
+typedef enum VkAccelerationStructureCreateFlagBitsKHR {
+    VK_ACCELERATION_STRUCTURE_CREATE_DEVICE_ADDRESS_CAPTURE_REPLAY_BIT_KHR = 0x00000001,
+    VK_ACCELERATION_STRUCTURE_CREATE_MOTION_BIT_NV = 0x00000004,
+    VK_ACCELERATION_STRUCTURE_CREATE_FLAG_BITS_MAX_ENUM_KHR = 0x7FFFFFFF
+} VkAccelerationStructureCreateFlagBitsKHR;
+typedef VkFlags VkAccelerationStructureCreateFlagsKHR;
+typedef union VkDeviceOrHostAddressKHR {
+    VkDeviceAddress    deviceAddress;
+    void*              hostAddress;
+} VkDeviceOrHostAddressKHR;
+
+typedef struct VkAccelerationStructureBuildRangeInfoKHR {
+    uint32_t    primitiveCount;
+    uint32_t    primitiveOffset;
+    uint32_t    firstVertex;
+    uint32_t    transformOffset;
+} VkAccelerationStructureBuildRangeInfoKHR;
+
+typedef struct VkAccelerationStructureGeometryTrianglesDataKHR {
+    VkStructureType                  sType;
+    const void*                      pNext;
+    VkFormat                         vertexFormat;
+    VkDeviceOrHostAddressConstKHR    vertexData;
+    VkDeviceSize                     vertexStride;
+    uint32_t                         maxVertex;
+    VkIndexType                      indexType;
+    VkDeviceOrHostAddressConstKHR    indexData;
+    VkDeviceOrHostAddressConstKHR    transformData;
+} VkAccelerationStructureGeometryTrianglesDataKHR;
+
+typedef struct VkAccelerationStructureGeometryAabbsDataKHR {
+    VkStructureType                  sType;
+    const void*                      pNext;
+    VkDeviceOrHostAddressConstKHR    data;
+    VkDeviceSize                     stride;
+} VkAccelerationStructureGeometryAabbsDataKHR;
+
+typedef struct VkAccelerationStructureGeometryInstancesDataKHR {
+    VkStructureType                  sType;
+    const void*                      pNext;
+    VkBool32                         arrayOfPointers;
+    VkDeviceOrHostAddressConstKHR    data;
+} VkAccelerationStructureGeometryInstancesDataKHR;
+
+typedef union VkAccelerationStructureGeometryDataKHR {
+    VkAccelerationStructureGeometryTrianglesDataKHR    triangles;
+    VkAccelerationStructureGeometryAabbsDataKHR        aabbs;
+    VkAccelerationStructureGeometryInstancesDataKHR    instances;
+} VkAccelerationStructureGeometryDataKHR;
+
+typedef struct VkAccelerationStructureGeometryKHR {
+    VkStructureType                           sType;
+    const void*                               pNext;
+    VkGeometryTypeKHR                         geometryType;
+    VkAccelerationStructureGeometryDataKHR    geometry;
+    VkGeometryFlagsKHR                        flags;
+} VkAccelerationStructureGeometryKHR;
+
+typedef struct VkAccelerationStructureBuildGeometryInfoKHR {
+    VkStructureType                                     sType;
+    const void*                                         pNext;
+    VkAccelerationStructureTypeKHR                      type;
+    VkBuildAccelerationStructureFlagsKHR                flags;
+    VkBuildAccelerationStructureModeKHR                 mode;
+    VkAccelerationStructureKHR                          srcAccelerationStructure;
+    VkAccelerationStructureKHR                          dstAccelerationStructure;
+    uint32_t                                            geometryCount;
+    const VkAccelerationStructureGeometryKHR*           pGeometries;
+    const VkAccelerationStructureGeometryKHR* const*    ppGeometries;
+    VkDeviceOrHostAddressKHR                            scratchData;
+} VkAccelerationStructureBuildGeometryInfoKHR;
+
+typedef struct VkAccelerationStructureCreateInfoKHR {
+    VkStructureType                          sType;
+    const void*                              pNext;
+    VkAccelerationStructureCreateFlagsKHR    createFlags;
+    VkBuffer                                 buffer;
+    VkDeviceSize                             offset;
+    VkDeviceSize                             size;
+    VkAccelerationStructureTypeKHR           type;
+    VkDeviceAddress                          deviceAddress;
+} VkAccelerationStructureCreateInfoKHR;
+
+typedef struct VkWriteDescriptorSetAccelerationStructureKHR {
+    VkStructureType                      sType;
+    const void*                          pNext;
+    uint32_t                             accelerationStructureCount;
+    const VkAccelerationStructureKHR*    pAccelerationStructures;
+} VkWriteDescriptorSetAccelerationStructureKHR;
+
+typedef struct VkPhysicalDeviceAccelerationStructureFeaturesKHR {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           accelerationStructure;
+    VkBool32           accelerationStructureCaptureReplay;
+    VkBool32           accelerationStructureIndirectBuild;
+    VkBool32           accelerationStructureHostCommands;
+    VkBool32           descriptorBindingAccelerationStructureUpdateAfterBind;
+} VkPhysicalDeviceAccelerationStructureFeaturesKHR;
+
+typedef struct VkPhysicalDeviceAccelerationStructurePropertiesKHR {
+    VkStructureType    sType;
+    void*              pNext;
+    uint64_t           maxGeometryCount;
+    uint64_t           maxInstanceCount;
+    uint64_t           maxPrimitiveCount;
+    uint32_t           maxPerStageDescriptorAccelerationStructures;
+    uint32_t           maxPerStageDescriptorUpdateAfterBindAccelerationStructures;
+    uint32_t           maxDescriptorSetAccelerationStructures;
+    uint32_t           maxDescriptorSetUpdateAfterBindAccelerationStructures;
+    uint32_t           minAccelerationStructureScratchOffsetAlignment;
+} VkPhysicalDeviceAccelerationStructurePropertiesKHR;
+
+typedef struct VkAccelerationStructureDeviceAddressInfoKHR {
+    VkStructureType               sType;
+    const void*                   pNext;
+    VkAccelerationStructureKHR    accelerationStructure;
+} VkAccelerationStructureDeviceAddressInfoKHR;
+
+typedef struct VkAccelerationStructureVersionInfoKHR {
+    VkStructureType    sType;
+    const void*        pNext;
+    const uint8_t*     pVersionData;
+} VkAccelerationStructureVersionInfoKHR;
+
+typedef struct VkCopyAccelerationStructureToMemoryInfoKHR {
+    VkStructureType                       sType;
+    const void*                           pNext;
+    VkAccelerationStructureKHR            src;
+    VkDeviceOrHostAddressKHR              dst;
+    VkCopyAccelerationStructureModeKHR    mode;
+} VkCopyAccelerationStructureToMemoryInfoKHR;
+
+typedef struct VkCopyMemoryToAccelerationStructureInfoKHR {
+    VkStructureType                       sType;
+    const void*                           pNext;
+    VkDeviceOrHostAddressConstKHR         src;
+    VkAccelerationStructureKHR            dst;
+    VkCopyAccelerationStructureModeKHR    mode;
+} VkCopyMemoryToAccelerationStructureInfoKHR;
+
+typedef struct VkCopyAccelerationStructureInfoKHR {
+    VkStructureType                       sType;
+    const void*                           pNext;
+    VkAccelerationStructureKHR            src;
+    VkAccelerationStructureKHR            dst;
+    VkCopyAccelerationStructureModeKHR    mode;
+} VkCopyAccelerationStructureInfoKHR;
+
+typedef struct VkAccelerationStructureBuildSizesInfoKHR {
+    VkStructureType    sType;
+    const void*        pNext;
+    VkDeviceSize       accelerationStructureSize;
+    VkDeviceSize       updateScratchSize;
+    VkDeviceSize       buildScratchSize;
+} VkAccelerationStructureBuildSizesInfoKHR;
+
+typedef VkResult (VKAPI_PTR *PFN_vkCreateAccelerationStructureKHR)(VkDevice                                           device, const VkAccelerationStructureCreateInfoKHR*        pCreateInfo, const VkAllocationCallbacks*       pAllocator, VkAccelerationStructureKHR*                        pAccelerationStructure);
+typedef void (VKAPI_PTR *PFN_vkDestroyAccelerationStructureKHR)(VkDevice device, VkAccelerationStructureKHR accelerationStructure, const VkAllocationCallbacks* pAllocator);
+typedef void (VKAPI_PTR *PFN_vkCmdBuildAccelerationStructuresKHR)(VkCommandBuffer                                    commandBuffer, uint32_t infoCount, const VkAccelerationStructureBuildGeometryInfoKHR* pInfos, const VkAccelerationStructureBuildRangeInfoKHR* const* ppBuildRangeInfos);
+typedef void (VKAPI_PTR *PFN_vkCmdBuildAccelerationStructuresIndirectKHR)(VkCommandBuffer                  commandBuffer, uint32_t                                           infoCount, const VkAccelerationStructureBuildGeometryInfoKHR* pInfos, const VkDeviceAddress*             pIndirectDeviceAddresses, const uint32_t*                    pIndirectStrides, const uint32_t* const*             ppMaxPrimitiveCounts);
+typedef VkResult (VKAPI_PTR *PFN_vkBuildAccelerationStructuresKHR)(VkDevice                                           device, VkDeferredOperationKHR deferredOperation, uint32_t infoCount, const VkAccelerationStructureBuildGeometryInfoKHR* pInfos, const VkAccelerationStructureBuildRangeInfoKHR* const* ppBuildRangeInfos);
+typedef VkResult (VKAPI_PTR *PFN_vkCopyAccelerationStructureKHR)(VkDevice device, VkDeferredOperationKHR deferredOperation, const VkCopyAccelerationStructureInfoKHR* pInfo);
+typedef VkResult (VKAPI_PTR *PFN_vkCopyAccelerationStructureToMemoryKHR)(VkDevice device, VkDeferredOperationKHR deferredOperation, const VkCopyAccelerationStructureToMemoryInfoKHR* pInfo);
+typedef VkResult (VKAPI_PTR *PFN_vkCopyMemoryToAccelerationStructureKHR)(VkDevice device, VkDeferredOperationKHR deferredOperation, const VkCopyMemoryToAccelerationStructureInfoKHR* pInfo);
+typedef VkResult (VKAPI_PTR *PFN_vkWriteAccelerationStructuresPropertiesKHR)(VkDevice device, uint32_t accelerationStructureCount, const VkAccelerationStructureKHR* pAccelerationStructures, VkQueryType  queryType, size_t       dataSize, void* pData, size_t stride);
+typedef void (VKAPI_PTR *PFN_vkCmdCopyAccelerationStructureKHR)(VkCommandBuffer commandBuffer, const VkCopyAccelerationStructureInfoKHR* pInfo);
+typedef void (VKAPI_PTR *PFN_vkCmdCopyAccelerationStructureToMemoryKHR)(VkCommandBuffer commandBuffer, const VkCopyAccelerationStructureToMemoryInfoKHR* pInfo);
+typedef void (VKAPI_PTR *PFN_vkCmdCopyMemoryToAccelerationStructureKHR)(VkCommandBuffer commandBuffer, const VkCopyMemoryToAccelerationStructureInfoKHR* pInfo);
+typedef VkDeviceAddress (VKAPI_PTR *PFN_vkGetAccelerationStructureDeviceAddressKHR)(VkDevice device, const VkAccelerationStructureDeviceAddressInfoKHR* pInfo);
+typedef void (VKAPI_PTR *PFN_vkCmdWriteAccelerationStructuresPropertiesKHR)(VkCommandBuffer commandBuffer, uint32_t accelerationStructureCount, const VkAccelerationStructureKHR* pAccelerationStructures, VkQueryType queryType, VkQueryPool queryPool, uint32_t firstQuery);
+typedef void (VKAPI_PTR *PFN_vkGetDeviceAccelerationStructureCompatibilityKHR)(VkDevice device, const VkAccelerationStructureVersionInfoKHR* pVersionInfo, VkAccelerationStructureCompatibilityKHR* pCompatibility);
+typedef void (VKAPI_PTR *PFN_vkGetAccelerationStructureBuildSizesKHR)(VkDevice                                            device, VkAccelerationStructureBuildTypeKHR                 buildType, const VkAccelerationStructureBuildGeometryInfoKHR*  pBuildInfo, const uint32_t*  pMaxPrimitiveCounts, VkAccelerationStructureBuildSizesInfoKHR*           pSizeInfo);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateAccelerationStructureKHR(
+    VkDevice                                    device,
+    const VkAccelerationStructureCreateInfoKHR* pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkAccelerationStructureKHR*                 pAccelerationStructure);
+
+VKAPI_ATTR void VKAPI_CALL vkDestroyAccelerationStructureKHR(
+    VkDevice                                    device,
+    VkAccelerationStructureKHR                  accelerationStructure,
+    const VkAllocationCallbacks*                pAllocator);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdBuildAccelerationStructuresKHR(
+    VkCommandBuffer                             commandBuffer,
+    uint32_t                                    infoCount,
+    const VkAccelerationStructureBuildGeometryInfoKHR* pInfos,
+    const VkAccelerationStructureBuildRangeInfoKHR* const* ppBuildRangeInfos);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdBuildAccelerationStructuresIndirectKHR(
+    VkCommandBuffer                             commandBuffer,
+    uint32_t                                    infoCount,
+    const VkAccelerationStructureBuildGeometryInfoKHR* pInfos,
+    const VkDeviceAddress*                      pIndirectDeviceAddresses,
+    const uint32_t*                             pIndirectStrides,
+    const uint32_t* const*                      ppMaxPrimitiveCounts);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkBuildAccelerationStructuresKHR(
+    VkDevice                                    device,
+    VkDeferredOperationKHR                      deferredOperation,
+    uint32_t                                    infoCount,
+    const VkAccelerationStructureBuildGeometryInfoKHR* pInfos,
+    const VkAccelerationStructureBuildRangeInfoKHR* const* ppBuildRangeInfos);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkCopyAccelerationStructureKHR(
+    VkDevice                                    device,
+    VkDeferredOperationKHR                      deferredOperation,
+    const VkCopyAccelerationStructureInfoKHR*   pInfo);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkCopyAccelerationStructureToMemoryKHR(
+    VkDevice                                    device,
+    VkDeferredOperationKHR                      deferredOperation,
+    const VkCopyAccelerationStructureToMemoryInfoKHR* pInfo);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkCopyMemoryToAccelerationStructureKHR(
+    VkDevice                                    device,
+    VkDeferredOperationKHR                      deferredOperation,
+    const VkCopyMemoryToAccelerationStructureInfoKHR* pInfo);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkWriteAccelerationStructuresPropertiesKHR(
+    VkDevice                                    device,
+    uint32_t                                    accelerationStructureCount,
+    const VkAccelerationStructureKHR*           pAccelerationStructures,
+    VkQueryType                                 queryType,
+    size_t                                      dataSize,
+    void*                                       pData,
+    size_t                                      stride);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdCopyAccelerationStructureKHR(
+    VkCommandBuffer                             commandBuffer,
+    const VkCopyAccelerationStructureInfoKHR*   pInfo);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdCopyAccelerationStructureToMemoryKHR(
+    VkCommandBuffer                             commandBuffer,
+    const VkCopyAccelerationStructureToMemoryInfoKHR* pInfo);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdCopyMemoryToAccelerationStructureKHR(
+    VkCommandBuffer                             commandBuffer,
+    const VkCopyMemoryToAccelerationStructureInfoKHR* pInfo);
+
+VKAPI_ATTR VkDeviceAddress VKAPI_CALL vkGetAccelerationStructureDeviceAddressKHR(
+    VkDevice                                    device,
+    const VkAccelerationStructureDeviceAddressInfoKHR* pInfo);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdWriteAccelerationStructuresPropertiesKHR(
+    VkCommandBuffer                             commandBuffer,
+    uint32_t                                    accelerationStructureCount,
+    const VkAccelerationStructureKHR*           pAccelerationStructures,
+    VkQueryType                                 queryType,
+    VkQueryPool                                 queryPool,
+    uint32_t                                    firstQuery);
+
+VKAPI_ATTR void VKAPI_CALL vkGetDeviceAccelerationStructureCompatibilityKHR(
+    VkDevice                                    device,
+    const VkAccelerationStructureVersionInfoKHR* pVersionInfo,
+    VkAccelerationStructureCompatibilityKHR*    pCompatibility);
+
+VKAPI_ATTR void VKAPI_CALL vkGetAccelerationStructureBuildSizesKHR(
+    VkDevice                                    device,
+    VkAccelerationStructureBuildTypeKHR         buildType,
+    const VkAccelerationStructureBuildGeometryInfoKHR* pBuildInfo,
+    const uint32_t*                             pMaxPrimitiveCounts,
+    VkAccelerationStructureBuildSizesInfoKHR*   pSizeInfo);
+#endif
+
+
+#define VK_KHR_ray_tracing_pipeline 1
+#define VK_KHR_RAY_TRACING_PIPELINE_SPEC_VERSION 1
+#define VK_KHR_RAY_TRACING_PIPELINE_EXTENSION_NAME "VK_KHR_ray_tracing_pipeline"
+
+typedef enum VkShaderGroupShaderKHR {
+    VK_SHADER_GROUP_SHADER_GENERAL_KHR = 0,
+    VK_SHADER_GROUP_SHADER_CLOSEST_HIT_KHR = 1,
+    VK_SHADER_GROUP_SHADER_ANY_HIT_KHR = 2,
+    VK_SHADER_GROUP_SHADER_INTERSECTION_KHR = 3,
+    VK_SHADER_GROUP_SHADER_MAX_ENUM_KHR = 0x7FFFFFFF
+} VkShaderGroupShaderKHR;
+typedef struct VkRayTracingShaderGroupCreateInfoKHR {
+    VkStructureType                   sType;
+    const void*                       pNext;
+    VkRayTracingShaderGroupTypeKHR    type;
+    uint32_t                          generalShader;
+    uint32_t                          closestHitShader;
+    uint32_t                          anyHitShader;
+    uint32_t                          intersectionShader;
+    const void*                       pShaderGroupCaptureReplayHandle;
+} VkRayTracingShaderGroupCreateInfoKHR;
+
+typedef struct VkRayTracingPipelineInterfaceCreateInfoKHR {
+    VkStructureType    sType;
+    const void*        pNext;
+    uint32_t           maxPipelineRayPayloadSize;
+    uint32_t           maxPipelineRayHitAttributeSize;
+} VkRayTracingPipelineInterfaceCreateInfoKHR;
+
+typedef struct VkRayTracingPipelineCreateInfoKHR {
+    VkStructureType                                      sType;
+    const void*                                          pNext;
+    VkPipelineCreateFlags                                flags;
+    uint32_t                                             stageCount;
+    const VkPipelineShaderStageCreateInfo*               pStages;
+    uint32_t                                             groupCount;
+    const VkRayTracingShaderGroupCreateInfoKHR*          pGroups;
+    uint32_t                                             maxPipelineRayRecursionDepth;
+    const VkPipelineLibraryCreateInfoKHR*                pLibraryInfo;
+    const VkRayTracingPipelineInterfaceCreateInfoKHR*    pLibraryInterface;
+    const VkPipelineDynamicStateCreateInfo*              pDynamicState;
+    VkPipelineLayout                                     layout;
+    VkPipeline                                           basePipelineHandle;
+    int32_t                                              basePipelineIndex;
+} VkRayTracingPipelineCreateInfoKHR;
+
+typedef struct VkPhysicalDeviceRayTracingPipelineFeaturesKHR {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           rayTracingPipeline;
+    VkBool32           rayTracingPipelineShaderGroupHandleCaptureReplay;
+    VkBool32           rayTracingPipelineShaderGroupHandleCaptureReplayMixed;
+    VkBool32           rayTracingPipelineTraceRaysIndirect;
+    VkBool32           rayTraversalPrimitiveCulling;
+} VkPhysicalDeviceRayTracingPipelineFeaturesKHR;
+
+typedef struct VkPhysicalDeviceRayTracingPipelinePropertiesKHR {
+    VkStructureType    sType;
+    void*              pNext;
+    uint32_t           shaderGroupHandleSize;
+    uint32_t           maxRayRecursionDepth;
+    uint32_t           maxShaderGroupStride;
+    uint32_t           shaderGroupBaseAlignment;
+    uint32_t           shaderGroupHandleCaptureReplaySize;
+    uint32_t           maxRayDispatchInvocationCount;
+    uint32_t           shaderGroupHandleAlignment;
+    uint32_t           maxRayHitAttributeSize;
+} VkPhysicalDeviceRayTracingPipelinePropertiesKHR;
+
+typedef struct VkStridedDeviceAddressRegionKHR {
+    VkDeviceAddress    deviceAddress;
+    VkDeviceSize       stride;
+    VkDeviceSize       size;
+} VkStridedDeviceAddressRegionKHR;
+
+typedef struct VkTraceRaysIndirectCommandKHR {
+    uint32_t    width;
+    uint32_t    height;
+    uint32_t    depth;
+} VkTraceRaysIndirectCommandKHR;
+
+typedef void (VKAPI_PTR *PFN_vkCmdTraceRaysKHR)(VkCommandBuffer commandBuffer, const VkStridedDeviceAddressRegionKHR* pRaygenShaderBindingTable, const VkStridedDeviceAddressRegionKHR* pMissShaderBindingTable, const VkStridedDeviceAddressRegionKHR* pHitShaderBindingTable, const VkStridedDeviceAddressRegionKHR* pCallableShaderBindingTable, uint32_t width, uint32_t height, uint32_t depth);
+typedef VkResult (VKAPI_PTR *PFN_vkCreateRayTracingPipelinesKHR)(VkDevice device, VkDeferredOperationKHR deferredOperation, VkPipelineCache pipelineCache, uint32_t createInfoCount, const VkRayTracingPipelineCreateInfoKHR* pCreateInfos, const VkAllocationCallbacks* pAllocator, VkPipeline* pPipelines);
+typedef VkResult (VKAPI_PTR *PFN_vkGetRayTracingCaptureReplayShaderGroupHandlesKHR)(VkDevice device, VkPipeline pipeline, uint32_t firstGroup, uint32_t groupCount, size_t dataSize, void* pData);
+typedef void (VKAPI_PTR *PFN_vkCmdTraceRaysIndirectKHR)(VkCommandBuffer commandBuffer, const VkStridedDeviceAddressRegionKHR* pRaygenShaderBindingTable, const VkStridedDeviceAddressRegionKHR* pMissShaderBindingTable, const VkStridedDeviceAddressRegionKHR* pHitShaderBindingTable, const VkStridedDeviceAddressRegionKHR* pCallableShaderBindingTable, VkDeviceAddress indirectDeviceAddress);
+typedef VkDeviceSize (VKAPI_PTR *PFN_vkGetRayTracingShaderGroupStackSizeKHR)(VkDevice device, VkPipeline pipeline, uint32_t group, VkShaderGroupShaderKHR groupShader);
+typedef void (VKAPI_PTR *PFN_vkCmdSetRayTracingPipelineStackSizeKHR)(VkCommandBuffer commandBuffer, uint32_t pipelineStackSize);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR void VKAPI_CALL vkCmdTraceRaysKHR(
+    VkCommandBuffer                             commandBuffer,
+    const VkStridedDeviceAddressRegionKHR*      pRaygenShaderBindingTable,
+    const VkStridedDeviceAddressRegionKHR*      pMissShaderBindingTable,
+    const VkStridedDeviceAddressRegionKHR*      pHitShaderBindingTable,
+    const VkStridedDeviceAddressRegionKHR*      pCallableShaderBindingTable,
+    uint32_t                                    width,
+    uint32_t                                    height,
+    uint32_t                                    depth);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateRayTracingPipelinesKHR(
+    VkDevice                                    device,
+    VkDeferredOperationKHR                      deferredOperation,
+    VkPipelineCache                             pipelineCache,
+    uint32_t                                    createInfoCount,
+    const VkRayTracingPipelineCreateInfoKHR*    pCreateInfos,
+    const VkAllocationCallbacks*                pAllocator,
+    VkPipeline*                                 pPipelines);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetRayTracingCaptureReplayShaderGroupHandlesKHR(
+    VkDevice                                    device,
+    VkPipeline                                  pipeline,
+    uint32_t                                    firstGroup,
+    uint32_t                                    groupCount,
+    size_t                                      dataSize,
+    void*                                       pData);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdTraceRaysIndirectKHR(
+    VkCommandBuffer                             commandBuffer,
+    const VkStridedDeviceAddressRegionKHR*      pRaygenShaderBindingTable,
+    const VkStridedDeviceAddressRegionKHR*      pMissShaderBindingTable,
+    const VkStridedDeviceAddressRegionKHR*      pHitShaderBindingTable,
+    const VkStridedDeviceAddressRegionKHR*      pCallableShaderBindingTable,
+    VkDeviceAddress                             indirectDeviceAddress);
+
+VKAPI_ATTR VkDeviceSize VKAPI_CALL vkGetRayTracingShaderGroupStackSizeKHR(
+    VkDevice                                    device,
+    VkPipeline                                  pipeline,
+    uint32_t                                    group,
+    VkShaderGroupShaderKHR                      groupShader);
+
+VKAPI_ATTR void VKAPI_CALL vkCmdSetRayTracingPipelineStackSizeKHR(
+    VkCommandBuffer                             commandBuffer,
+    uint32_t                                    pipelineStackSize);
+#endif
+
+
+#define VK_KHR_ray_query 1
+#define VK_KHR_RAY_QUERY_SPEC_VERSION     1
+#define VK_KHR_RAY_QUERY_EXTENSION_NAME   "VK_KHR_ray_query"
+typedef struct VkPhysicalDeviceRayQueryFeaturesKHR {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           rayQuery;
+} VkPhysicalDeviceRayQueryFeaturesKHR;
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/external/Vulkan/external/vulkan/vulkan_directfb.h b/external/Vulkan/external/vulkan/vulkan_directfb.h
new file mode 100644
index 0000000000000000000000000000000000000000..8eaac6e48d1d7dbc2e78ded0d12fdc62d6dcd191
--- /dev/null
+++ b/external/Vulkan/external/vulkan/vulkan_directfb.h
@@ -0,0 +1,54 @@
+#ifndef VULKAN_DIRECTFB_H_
+#define VULKAN_DIRECTFB_H_ 1
+
+/*
+** Copyright 2015-2021 The Khronos Group Inc.
+**
+** SPDX-License-Identifier: Apache-2.0
+*/
+
+/*
+** This header is generated from the Khronos Vulkan XML API Registry.
+**
+*/
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+
+#define VK_EXT_directfb_surface 1
+#define VK_EXT_DIRECTFB_SURFACE_SPEC_VERSION 1
+#define VK_EXT_DIRECTFB_SURFACE_EXTENSION_NAME "VK_EXT_directfb_surface"
+typedef VkFlags VkDirectFBSurfaceCreateFlagsEXT;
+typedef struct VkDirectFBSurfaceCreateInfoEXT {
+    VkStructureType                    sType;
+    const void*                        pNext;
+    VkDirectFBSurfaceCreateFlagsEXT    flags;
+    IDirectFB*                         dfb;
+    IDirectFBSurface*                  surface;
+} VkDirectFBSurfaceCreateInfoEXT;
+
+typedef VkResult (VKAPI_PTR *PFN_vkCreateDirectFBSurfaceEXT)(VkInstance instance, const VkDirectFBSurfaceCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface);
+typedef VkBool32 (VKAPI_PTR *PFN_vkGetPhysicalDeviceDirectFBPresentationSupportEXT)(VkPhysicalDevice physicalDevice, uint32_t queueFamilyIndex, IDirectFB* dfb);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateDirectFBSurfaceEXT(
+    VkInstance                                  instance,
+    const VkDirectFBSurfaceCreateInfoEXT*       pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkSurfaceKHR*                               pSurface);
+
+VKAPI_ATTR VkBool32 VKAPI_CALL vkGetPhysicalDeviceDirectFBPresentationSupportEXT(
+    VkPhysicalDevice                            physicalDevice,
+    uint32_t                                    queueFamilyIndex,
+    IDirectFB*                                  dfb);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/external/Vulkan/external/vulkan/vulkan_fuchsia.h b/external/Vulkan/external/vulkan/vulkan_fuchsia.h
new file mode 100644
index 0000000000000000000000000000000000000000..44b4ace3e82c73c580e265841ac3a024600705bf
--- /dev/null
+++ b/external/Vulkan/external/vulkan/vulkan_fuchsia.h
@@ -0,0 +1,258 @@
+#ifndef VULKAN_FUCHSIA_H_
+#define VULKAN_FUCHSIA_H_ 1
+
+/*
+** Copyright 2015-2021 The Khronos Group Inc.
+**
+** SPDX-License-Identifier: Apache-2.0
+*/
+
+/*
+** This header is generated from the Khronos Vulkan XML API Registry.
+**
+*/
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+
+#define VK_FUCHSIA_imagepipe_surface 1
+#define VK_FUCHSIA_IMAGEPIPE_SURFACE_SPEC_VERSION 1
+#define VK_FUCHSIA_IMAGEPIPE_SURFACE_EXTENSION_NAME "VK_FUCHSIA_imagepipe_surface"
+typedef VkFlags VkImagePipeSurfaceCreateFlagsFUCHSIA;
+typedef struct VkImagePipeSurfaceCreateInfoFUCHSIA {
+    VkStructureType                         sType;
+    const void*                             pNext;
+    VkImagePipeSurfaceCreateFlagsFUCHSIA    flags;
+    zx_handle_t                             imagePipeHandle;
+} VkImagePipeSurfaceCreateInfoFUCHSIA;
+
+typedef VkResult (VKAPI_PTR *PFN_vkCreateImagePipeSurfaceFUCHSIA)(VkInstance instance, const VkImagePipeSurfaceCreateInfoFUCHSIA* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateImagePipeSurfaceFUCHSIA(
+    VkInstance                                  instance,
+    const VkImagePipeSurfaceCreateInfoFUCHSIA*  pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkSurfaceKHR*                               pSurface);
+#endif
+
+
+#define VK_FUCHSIA_external_memory 1
+#define VK_FUCHSIA_EXTERNAL_MEMORY_SPEC_VERSION 1
+#define VK_FUCHSIA_EXTERNAL_MEMORY_EXTENSION_NAME "VK_FUCHSIA_external_memory"
+typedef struct VkImportMemoryZirconHandleInfoFUCHSIA {
+    VkStructureType                       sType;
+    const void*                           pNext;
+    VkExternalMemoryHandleTypeFlagBits    handleType;
+    zx_handle_t                           handle;
+} VkImportMemoryZirconHandleInfoFUCHSIA;
+
+typedef struct VkMemoryZirconHandlePropertiesFUCHSIA {
+    VkStructureType    sType;
+    void*              pNext;
+    uint32_t           memoryTypeBits;
+} VkMemoryZirconHandlePropertiesFUCHSIA;
+
+typedef struct VkMemoryGetZirconHandleInfoFUCHSIA {
+    VkStructureType                       sType;
+    const void*                           pNext;
+    VkDeviceMemory                        memory;
+    VkExternalMemoryHandleTypeFlagBits    handleType;
+} VkMemoryGetZirconHandleInfoFUCHSIA;
+
+typedef VkResult (VKAPI_PTR *PFN_vkGetMemoryZirconHandleFUCHSIA)(VkDevice device, const VkMemoryGetZirconHandleInfoFUCHSIA* pGetZirconHandleInfo, zx_handle_t* pZirconHandle);
+typedef VkResult (VKAPI_PTR *PFN_vkGetMemoryZirconHandlePropertiesFUCHSIA)(VkDevice device, VkExternalMemoryHandleTypeFlagBits handleType, zx_handle_t zirconHandle, VkMemoryZirconHandlePropertiesFUCHSIA* pMemoryZirconHandleProperties);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkGetMemoryZirconHandleFUCHSIA(
+    VkDevice                                    device,
+    const VkMemoryGetZirconHandleInfoFUCHSIA*   pGetZirconHandleInfo,
+    zx_handle_t*                                pZirconHandle);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetMemoryZirconHandlePropertiesFUCHSIA(
+    VkDevice                                    device,
+    VkExternalMemoryHandleTypeFlagBits          handleType,
+    zx_handle_t                                 zirconHandle,
+    VkMemoryZirconHandlePropertiesFUCHSIA*      pMemoryZirconHandleProperties);
+#endif
+
+
+#define VK_FUCHSIA_external_semaphore 1
+#define VK_FUCHSIA_EXTERNAL_SEMAPHORE_SPEC_VERSION 1
+#define VK_FUCHSIA_EXTERNAL_SEMAPHORE_EXTENSION_NAME "VK_FUCHSIA_external_semaphore"
+typedef struct VkImportSemaphoreZirconHandleInfoFUCHSIA {
+    VkStructureType                          sType;
+    const void*                              pNext;
+    VkSemaphore                              semaphore;
+    VkSemaphoreImportFlags                   flags;
+    VkExternalSemaphoreHandleTypeFlagBits    handleType;
+    zx_handle_t                              zirconHandle;
+} VkImportSemaphoreZirconHandleInfoFUCHSIA;
+
+typedef struct VkSemaphoreGetZirconHandleInfoFUCHSIA {
+    VkStructureType                          sType;
+    const void*                              pNext;
+    VkSemaphore                              semaphore;
+    VkExternalSemaphoreHandleTypeFlagBits    handleType;
+} VkSemaphoreGetZirconHandleInfoFUCHSIA;
+
+typedef VkResult (VKAPI_PTR *PFN_vkImportSemaphoreZirconHandleFUCHSIA)(VkDevice device, const VkImportSemaphoreZirconHandleInfoFUCHSIA* pImportSemaphoreZirconHandleInfo);
+typedef VkResult (VKAPI_PTR *PFN_vkGetSemaphoreZirconHandleFUCHSIA)(VkDevice device, const VkSemaphoreGetZirconHandleInfoFUCHSIA* pGetZirconHandleInfo, zx_handle_t* pZirconHandle);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkImportSemaphoreZirconHandleFUCHSIA(
+    VkDevice                                    device,
+    const VkImportSemaphoreZirconHandleInfoFUCHSIA* pImportSemaphoreZirconHandleInfo);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetSemaphoreZirconHandleFUCHSIA(
+    VkDevice                                    device,
+    const VkSemaphoreGetZirconHandleInfoFUCHSIA* pGetZirconHandleInfo,
+    zx_handle_t*                                pZirconHandle);
+#endif
+
+
+#define VK_FUCHSIA_buffer_collection 1
+VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkBufferCollectionFUCHSIA)
+#define VK_FUCHSIA_BUFFER_COLLECTION_SPEC_VERSION 2
+#define VK_FUCHSIA_BUFFER_COLLECTION_EXTENSION_NAME "VK_FUCHSIA_buffer_collection"
+typedef VkFlags VkImageFormatConstraintsFlagsFUCHSIA;
+
+typedef enum VkImageConstraintsInfoFlagBitsFUCHSIA {
+    VK_IMAGE_CONSTRAINTS_INFO_CPU_READ_RARELY_FUCHSIA = 0x00000001,
+    VK_IMAGE_CONSTRAINTS_INFO_CPU_READ_OFTEN_FUCHSIA = 0x00000002,
+    VK_IMAGE_CONSTRAINTS_INFO_CPU_WRITE_RARELY_FUCHSIA = 0x00000004,
+    VK_IMAGE_CONSTRAINTS_INFO_CPU_WRITE_OFTEN_FUCHSIA = 0x00000008,
+    VK_IMAGE_CONSTRAINTS_INFO_PROTECTED_OPTIONAL_FUCHSIA = 0x00000010,
+    VK_IMAGE_CONSTRAINTS_INFO_FLAG_BITS_MAX_ENUM_FUCHSIA = 0x7FFFFFFF
+} VkImageConstraintsInfoFlagBitsFUCHSIA;
+typedef VkFlags VkImageConstraintsInfoFlagsFUCHSIA;
+typedef struct VkBufferCollectionCreateInfoFUCHSIA {
+    VkStructureType    sType;
+    const void*        pNext;
+    zx_handle_t        collectionToken;
+} VkBufferCollectionCreateInfoFUCHSIA;
+
+typedef struct VkImportMemoryBufferCollectionFUCHSIA {
+    VkStructureType              sType;
+    const void*                  pNext;
+    VkBufferCollectionFUCHSIA    collection;
+    uint32_t                     index;
+} VkImportMemoryBufferCollectionFUCHSIA;
+
+typedef struct VkBufferCollectionImageCreateInfoFUCHSIA {
+    VkStructureType              sType;
+    const void*                  pNext;
+    VkBufferCollectionFUCHSIA    collection;
+    uint32_t                     index;
+} VkBufferCollectionImageCreateInfoFUCHSIA;
+
+typedef struct VkBufferCollectionConstraintsInfoFUCHSIA {
+    VkStructureType    sType;
+    const void*        pNext;
+    uint32_t           minBufferCount;
+    uint32_t           maxBufferCount;
+    uint32_t           minBufferCountForCamping;
+    uint32_t           minBufferCountForDedicatedSlack;
+    uint32_t           minBufferCountForSharedSlack;
+} VkBufferCollectionConstraintsInfoFUCHSIA;
+
+typedef struct VkBufferConstraintsInfoFUCHSIA {
+    VkStructureType                             sType;
+    const void*                                 pNext;
+    VkBufferCreateInfo                          createInfo;
+    VkFormatFeatureFlags                        requiredFormatFeatures;
+    VkBufferCollectionConstraintsInfoFUCHSIA    bufferCollectionConstraints;
+} VkBufferConstraintsInfoFUCHSIA;
+
+typedef struct VkBufferCollectionBufferCreateInfoFUCHSIA {
+    VkStructureType              sType;
+    const void*                  pNext;
+    VkBufferCollectionFUCHSIA    collection;
+    uint32_t                     index;
+} VkBufferCollectionBufferCreateInfoFUCHSIA;
+
+typedef struct VkSysmemColorSpaceFUCHSIA {
+    VkStructureType    sType;
+    const void*        pNext;
+    uint32_t           colorSpace;
+} VkSysmemColorSpaceFUCHSIA;
+
+typedef struct VkBufferCollectionPropertiesFUCHSIA {
+    VkStructureType                  sType;
+    void*                            pNext;
+    uint32_t                         memoryTypeBits;
+    uint32_t                         bufferCount;
+    uint32_t                         createInfoIndex;
+    uint64_t                         sysmemPixelFormat;
+    VkFormatFeatureFlags             formatFeatures;
+    VkSysmemColorSpaceFUCHSIA        sysmemColorSpaceIndex;
+    VkComponentMapping               samplerYcbcrConversionComponents;
+    VkSamplerYcbcrModelConversion    suggestedYcbcrModel;
+    VkSamplerYcbcrRange              suggestedYcbcrRange;
+    VkChromaLocation                 suggestedXChromaOffset;
+    VkChromaLocation                 suggestedYChromaOffset;
+} VkBufferCollectionPropertiesFUCHSIA;
+
+typedef struct VkImageFormatConstraintsInfoFUCHSIA {
+    VkStructureType                         sType;
+    const void*                             pNext;
+    VkImageCreateInfo                       imageCreateInfo;
+    VkFormatFeatureFlags                    requiredFormatFeatures;
+    VkImageFormatConstraintsFlagsFUCHSIA    flags;
+    uint64_t                                sysmemPixelFormat;
+    uint32_t                                colorSpaceCount;
+    const VkSysmemColorSpaceFUCHSIA*        pColorSpaces;
+} VkImageFormatConstraintsInfoFUCHSIA;
+
+typedef struct VkImageConstraintsInfoFUCHSIA {
+    VkStructureType                               sType;
+    const void*                                   pNext;
+    uint32_t                                      formatConstraintsCount;
+    const VkImageFormatConstraintsInfoFUCHSIA*    pFormatConstraints;
+    VkBufferCollectionConstraintsInfoFUCHSIA      bufferCollectionConstraints;
+    VkImageConstraintsInfoFlagsFUCHSIA            flags;
+} VkImageConstraintsInfoFUCHSIA;
+
+typedef VkResult (VKAPI_PTR *PFN_vkCreateBufferCollectionFUCHSIA)(VkDevice device, const VkBufferCollectionCreateInfoFUCHSIA* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkBufferCollectionFUCHSIA* pCollection);
+typedef VkResult (VKAPI_PTR *PFN_vkSetBufferCollectionImageConstraintsFUCHSIA)(VkDevice device, VkBufferCollectionFUCHSIA collection, const VkImageConstraintsInfoFUCHSIA* pImageConstraintsInfo);
+typedef VkResult (VKAPI_PTR *PFN_vkSetBufferCollectionBufferConstraintsFUCHSIA)(VkDevice device, VkBufferCollectionFUCHSIA collection, const VkBufferConstraintsInfoFUCHSIA* pBufferConstraintsInfo);
+typedef void (VKAPI_PTR *PFN_vkDestroyBufferCollectionFUCHSIA)(VkDevice device, VkBufferCollectionFUCHSIA collection, const VkAllocationCallbacks* pAllocator);
+typedef VkResult (VKAPI_PTR *PFN_vkGetBufferCollectionPropertiesFUCHSIA)(VkDevice device, VkBufferCollectionFUCHSIA collection, VkBufferCollectionPropertiesFUCHSIA* pProperties);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateBufferCollectionFUCHSIA(
+    VkDevice                                    device,
+    const VkBufferCollectionCreateInfoFUCHSIA*  pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkBufferCollectionFUCHSIA*                  pCollection);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkSetBufferCollectionImageConstraintsFUCHSIA(
+    VkDevice                                    device,
+    VkBufferCollectionFUCHSIA                   collection,
+    const VkImageConstraintsInfoFUCHSIA*        pImageConstraintsInfo);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkSetBufferCollectionBufferConstraintsFUCHSIA(
+    VkDevice                                    device,
+    VkBufferCollectionFUCHSIA                   collection,
+    const VkBufferConstraintsInfoFUCHSIA*       pBufferConstraintsInfo);
+
+VKAPI_ATTR void VKAPI_CALL vkDestroyBufferCollectionFUCHSIA(
+    VkDevice                                    device,
+    VkBufferCollectionFUCHSIA                   collection,
+    const VkAllocationCallbacks*                pAllocator);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetBufferCollectionPropertiesFUCHSIA(
+    VkDevice                                    device,
+    VkBufferCollectionFUCHSIA                   collection,
+    VkBufferCollectionPropertiesFUCHSIA*        pProperties);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/external/Vulkan/external/vulkan/vulkan_ggp.h b/external/Vulkan/external/vulkan/vulkan_ggp.h
new file mode 100644
index 0000000000000000000000000000000000000000..9a6a582c5b08ffd7c55b62e9f6033bf707016b6c
--- /dev/null
+++ b/external/Vulkan/external/vulkan/vulkan_ggp.h
@@ -0,0 +1,58 @@
+#ifndef VULKAN_GGP_H_
+#define VULKAN_GGP_H_ 1
+
+/*
+** Copyright 2015-2021 The Khronos Group Inc.
+**
+** SPDX-License-Identifier: Apache-2.0
+*/
+
+/*
+** This header is generated from the Khronos Vulkan XML API Registry.
+**
+*/
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+
+#define VK_GGP_stream_descriptor_surface 1
+#define VK_GGP_STREAM_DESCRIPTOR_SURFACE_SPEC_VERSION 1
+#define VK_GGP_STREAM_DESCRIPTOR_SURFACE_EXTENSION_NAME "VK_GGP_stream_descriptor_surface"
+typedef VkFlags VkStreamDescriptorSurfaceCreateFlagsGGP;
+typedef struct VkStreamDescriptorSurfaceCreateInfoGGP {
+    VkStructureType                            sType;
+    const void*                                pNext;
+    VkStreamDescriptorSurfaceCreateFlagsGGP    flags;
+    GgpStreamDescriptor                        streamDescriptor;
+} VkStreamDescriptorSurfaceCreateInfoGGP;
+
+typedef VkResult (VKAPI_PTR *PFN_vkCreateStreamDescriptorSurfaceGGP)(VkInstance instance, const VkStreamDescriptorSurfaceCreateInfoGGP* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateStreamDescriptorSurfaceGGP(
+    VkInstance                                  instance,
+    const VkStreamDescriptorSurfaceCreateInfoGGP* pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkSurfaceKHR*                               pSurface);
+#endif
+
+
+#define VK_GGP_frame_token 1
+#define VK_GGP_FRAME_TOKEN_SPEC_VERSION   1
+#define VK_GGP_FRAME_TOKEN_EXTENSION_NAME "VK_GGP_frame_token"
+typedef struct VkPresentFrameTokenGGP {
+    VkStructureType    sType;
+    const void*        pNext;
+    GgpFrameToken      frameToken;
+} VkPresentFrameTokenGGP;
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/external/Vulkan/external/vulkan/vulkan_ios.h b/external/Vulkan/external/vulkan/vulkan_ios.h
new file mode 100644
index 0000000000000000000000000000000000000000..6e7e6afea6ce9c3d3755db5c81596b2ad864cad3
--- /dev/null
+++ b/external/Vulkan/external/vulkan/vulkan_ios.h
@@ -0,0 +1,47 @@
+#ifndef VULKAN_IOS_H_
+#define VULKAN_IOS_H_ 1
+
+/*
+** Copyright 2015-2021 The Khronos Group Inc.
+**
+** SPDX-License-Identifier: Apache-2.0
+*/
+
+/*
+** This header is generated from the Khronos Vulkan XML API Registry.
+**
+*/
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+
+#define VK_MVK_ios_surface 1
+#define VK_MVK_IOS_SURFACE_SPEC_VERSION   3
+#define VK_MVK_IOS_SURFACE_EXTENSION_NAME "VK_MVK_ios_surface"
+typedef VkFlags VkIOSSurfaceCreateFlagsMVK;
+typedef struct VkIOSSurfaceCreateInfoMVK {
+    VkStructureType               sType;
+    const void*                   pNext;
+    VkIOSSurfaceCreateFlagsMVK    flags;
+    const void*                   pView;
+} VkIOSSurfaceCreateInfoMVK;
+
+typedef VkResult (VKAPI_PTR *PFN_vkCreateIOSSurfaceMVK)(VkInstance instance, const VkIOSSurfaceCreateInfoMVK* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateIOSSurfaceMVK(
+    VkInstance                                  instance,
+    const VkIOSSurfaceCreateInfoMVK*            pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkSurfaceKHR*                               pSurface);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/external/Vulkan/external/vulkan/vulkan_macos.h b/external/Vulkan/external/vulkan/vulkan_macos.h
new file mode 100644
index 0000000000000000000000000000000000000000..c49b123d078e932f4fb3d5ebc24445a474d31f2b
--- /dev/null
+++ b/external/Vulkan/external/vulkan/vulkan_macos.h
@@ -0,0 +1,47 @@
+#ifndef VULKAN_MACOS_H_
+#define VULKAN_MACOS_H_ 1
+
+/*
+** Copyright 2015-2021 The Khronos Group Inc.
+**
+** SPDX-License-Identifier: Apache-2.0
+*/
+
+/*
+** This header is generated from the Khronos Vulkan XML API Registry.
+**
+*/
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+
+#define VK_MVK_macos_surface 1
+#define VK_MVK_MACOS_SURFACE_SPEC_VERSION 3
+#define VK_MVK_MACOS_SURFACE_EXTENSION_NAME "VK_MVK_macos_surface"
+typedef VkFlags VkMacOSSurfaceCreateFlagsMVK;
+typedef struct VkMacOSSurfaceCreateInfoMVK {
+    VkStructureType                 sType;
+    const void*                     pNext;
+    VkMacOSSurfaceCreateFlagsMVK    flags;
+    const void*                     pView;
+} VkMacOSSurfaceCreateInfoMVK;
+
+typedef VkResult (VKAPI_PTR *PFN_vkCreateMacOSSurfaceMVK)(VkInstance instance, const VkMacOSSurfaceCreateInfoMVK* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateMacOSSurfaceMVK(
+    VkInstance                                  instance,
+    const VkMacOSSurfaceCreateInfoMVK*          pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkSurfaceKHR*                               pSurface);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/external/Vulkan/external/vulkan/vulkan_metal.h b/external/Vulkan/external/vulkan/vulkan_metal.h
new file mode 100644
index 0000000000000000000000000000000000000000..5cf4a703acc5be9d0355d38e9c1d9bb7adf39ac6
--- /dev/null
+++ b/external/Vulkan/external/vulkan/vulkan_metal.h
@@ -0,0 +1,54 @@
+#ifndef VULKAN_METAL_H_
+#define VULKAN_METAL_H_ 1
+
+/*
+** Copyright 2015-2021 The Khronos Group Inc.
+**
+** SPDX-License-Identifier: Apache-2.0
+*/
+
+/*
+** This header is generated from the Khronos Vulkan XML API Registry.
+**
+*/
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+
+#define VK_EXT_metal_surface 1
+
+#ifdef __OBJC__
+@class CAMetalLayer;
+#else
+typedef void CAMetalLayer;
+#endif
+
+#define VK_EXT_METAL_SURFACE_SPEC_VERSION 1
+#define VK_EXT_METAL_SURFACE_EXTENSION_NAME "VK_EXT_metal_surface"
+typedef VkFlags VkMetalSurfaceCreateFlagsEXT;
+typedef struct VkMetalSurfaceCreateInfoEXT {
+    VkStructureType                 sType;
+    const void*                     pNext;
+    VkMetalSurfaceCreateFlagsEXT    flags;
+    const CAMetalLayer*             pLayer;
+} VkMetalSurfaceCreateInfoEXT;
+
+typedef VkResult (VKAPI_PTR *PFN_vkCreateMetalSurfaceEXT)(VkInstance instance, const VkMetalSurfaceCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateMetalSurfaceEXT(
+    VkInstance                                  instance,
+    const VkMetalSurfaceCreateInfoEXT*          pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkSurfaceKHR*                               pSurface);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/external/Vulkan/external/vulkan/vulkan_screen.h b/external/Vulkan/external/vulkan/vulkan_screen.h
new file mode 100644
index 0000000000000000000000000000000000000000..92ad9bfab41444791e7dd0f0366d017e028c8b3b
--- /dev/null
+++ b/external/Vulkan/external/vulkan/vulkan_screen.h
@@ -0,0 +1,54 @@
+#ifndef VULKAN_SCREEN_H_
+#define VULKAN_SCREEN_H_ 1
+
+/*
+** Copyright 2015-2021 The Khronos Group Inc.
+**
+** SPDX-License-Identifier: Apache-2.0
+*/
+
+/*
+** This header is generated from the Khronos Vulkan XML API Registry.
+**
+*/
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+
+#define VK_QNX_screen_surface 1
+#define VK_QNX_SCREEN_SURFACE_SPEC_VERSION 1
+#define VK_QNX_SCREEN_SURFACE_EXTENSION_NAME "VK_QNX_screen_surface"
+typedef VkFlags VkScreenSurfaceCreateFlagsQNX;
+typedef struct VkScreenSurfaceCreateInfoQNX {
+    VkStructureType                  sType;
+    const void*                      pNext;
+    VkScreenSurfaceCreateFlagsQNX    flags;
+    struct _screen_context*          context;
+    struct _screen_window*           window;
+} VkScreenSurfaceCreateInfoQNX;
+
+typedef VkResult (VKAPI_PTR *PFN_vkCreateScreenSurfaceQNX)(VkInstance instance, const VkScreenSurfaceCreateInfoQNX* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface);
+typedef VkBool32 (VKAPI_PTR *PFN_vkGetPhysicalDeviceScreenPresentationSupportQNX)(VkPhysicalDevice physicalDevice, uint32_t queueFamilyIndex, struct _screen_window* window);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateScreenSurfaceQNX(
+    VkInstance                                  instance,
+    const VkScreenSurfaceCreateInfoQNX*         pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkSurfaceKHR*                               pSurface);
+
+VKAPI_ATTR VkBool32 VKAPI_CALL vkGetPhysicalDeviceScreenPresentationSupportQNX(
+    VkPhysicalDevice                            physicalDevice,
+    uint32_t                                    queueFamilyIndex,
+    struct _screen_window*                      window);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/external/Vulkan/external/vulkan/vulkan_vi.h b/external/Vulkan/external/vulkan/vulkan_vi.h
new file mode 100644
index 0000000000000000000000000000000000000000..9e0dcca20013433c0c31db3228cbcab276db2f39
--- /dev/null
+++ b/external/Vulkan/external/vulkan/vulkan_vi.h
@@ -0,0 +1,47 @@
+#ifndef VULKAN_VI_H_
+#define VULKAN_VI_H_ 1
+
+/*
+** Copyright 2015-2021 The Khronos Group Inc.
+**
+** SPDX-License-Identifier: Apache-2.0
+*/
+
+/*
+** This header is generated from the Khronos Vulkan XML API Registry.
+**
+*/
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+
+#define VK_NN_vi_surface 1
+#define VK_NN_VI_SURFACE_SPEC_VERSION     1
+#define VK_NN_VI_SURFACE_EXTENSION_NAME   "VK_NN_vi_surface"
+typedef VkFlags VkViSurfaceCreateFlagsNN;
+typedef struct VkViSurfaceCreateInfoNN {
+    VkStructureType             sType;
+    const void*                 pNext;
+    VkViSurfaceCreateFlagsNN    flags;
+    void*                       window;
+} VkViSurfaceCreateInfoNN;
+
+typedef VkResult (VKAPI_PTR *PFN_vkCreateViSurfaceNN)(VkInstance instance, const VkViSurfaceCreateInfoNN* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateViSurfaceNN(
+    VkInstance                                  instance,
+    const VkViSurfaceCreateInfoNN*              pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkSurfaceKHR*                               pSurface);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/external/Vulkan/external/vulkan/vulkan_wayland.h b/external/Vulkan/external/vulkan/vulkan_wayland.h
new file mode 100644
index 0000000000000000000000000000000000000000..2a329be9ddea5ce87ff8128d7ba7d87946492bbb
--- /dev/null
+++ b/external/Vulkan/external/vulkan/vulkan_wayland.h
@@ -0,0 +1,54 @@
+#ifndef VULKAN_WAYLAND_H_
+#define VULKAN_WAYLAND_H_ 1
+
+/*
+** Copyright 2015-2021 The Khronos Group Inc.
+**
+** SPDX-License-Identifier: Apache-2.0
+*/
+
+/*
+** This header is generated from the Khronos Vulkan XML API Registry.
+**
+*/
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+
+#define VK_KHR_wayland_surface 1
+#define VK_KHR_WAYLAND_SURFACE_SPEC_VERSION 6
+#define VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME "VK_KHR_wayland_surface"
+typedef VkFlags VkWaylandSurfaceCreateFlagsKHR;
+typedef struct VkWaylandSurfaceCreateInfoKHR {
+    VkStructureType                   sType;
+    const void*                       pNext;
+    VkWaylandSurfaceCreateFlagsKHR    flags;
+    struct wl_display*                display;
+    struct wl_surface*                surface;
+} VkWaylandSurfaceCreateInfoKHR;
+
+typedef VkResult (VKAPI_PTR *PFN_vkCreateWaylandSurfaceKHR)(VkInstance instance, const VkWaylandSurfaceCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface);
+typedef VkBool32 (VKAPI_PTR *PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR)(VkPhysicalDevice physicalDevice, uint32_t queueFamilyIndex, struct wl_display* display);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateWaylandSurfaceKHR(
+    VkInstance                                  instance,
+    const VkWaylandSurfaceCreateInfoKHR*        pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkSurfaceKHR*                               pSurface);
+
+VKAPI_ATTR VkBool32 VKAPI_CALL vkGetPhysicalDeviceWaylandPresentationSupportKHR(
+    VkPhysicalDevice                            physicalDevice,
+    uint32_t                                    queueFamilyIndex,
+    struct wl_display*                          display);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/external/Vulkan/external/vulkan/vulkan_win32.h b/external/Vulkan/external/vulkan/vulkan_win32.h
new file mode 100644
index 0000000000000000000000000000000000000000..1b680f0b1a20d0595cd6d5ae2f2355d7b9e4efe3
--- /dev/null
+++ b/external/Vulkan/external/vulkan/vulkan_win32.h
@@ -0,0 +1,315 @@
+#ifndef VULKAN_WIN32_H_
+#define VULKAN_WIN32_H_ 1
+
+/*
+** Copyright 2015-2021 The Khronos Group Inc.
+**
+** SPDX-License-Identifier: Apache-2.0
+*/
+
+/*
+** This header is generated from the Khronos Vulkan XML API Registry.
+**
+*/
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+
+#define VK_KHR_win32_surface 1
+#define VK_KHR_WIN32_SURFACE_SPEC_VERSION 6
+#define VK_KHR_WIN32_SURFACE_EXTENSION_NAME "VK_KHR_win32_surface"
+typedef VkFlags VkWin32SurfaceCreateFlagsKHR;
+typedef struct VkWin32SurfaceCreateInfoKHR {
+    VkStructureType                 sType;
+    const void*                     pNext;
+    VkWin32SurfaceCreateFlagsKHR    flags;
+    HINSTANCE                       hinstance;
+    HWND                            hwnd;
+} VkWin32SurfaceCreateInfoKHR;
+
+typedef VkResult (VKAPI_PTR *PFN_vkCreateWin32SurfaceKHR)(VkInstance instance, const VkWin32SurfaceCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface);
+typedef VkBool32 (VKAPI_PTR *PFN_vkGetPhysicalDeviceWin32PresentationSupportKHR)(VkPhysicalDevice physicalDevice, uint32_t queueFamilyIndex);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateWin32SurfaceKHR(
+    VkInstance                                  instance,
+    const VkWin32SurfaceCreateInfoKHR*          pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkSurfaceKHR*                               pSurface);
+
+VKAPI_ATTR VkBool32 VKAPI_CALL vkGetPhysicalDeviceWin32PresentationSupportKHR(
+    VkPhysicalDevice                            physicalDevice,
+    uint32_t                                    queueFamilyIndex);
+#endif
+
+
+#define VK_KHR_external_memory_win32 1
+#define VK_KHR_EXTERNAL_MEMORY_WIN32_SPEC_VERSION 1
+#define VK_KHR_EXTERNAL_MEMORY_WIN32_EXTENSION_NAME "VK_KHR_external_memory_win32"
+typedef struct VkImportMemoryWin32HandleInfoKHR {
+    VkStructureType                       sType;
+    const void*                           pNext;
+    VkExternalMemoryHandleTypeFlagBits    handleType;
+    HANDLE                                handle;
+    LPCWSTR                               name;
+} VkImportMemoryWin32HandleInfoKHR;
+
+typedef struct VkExportMemoryWin32HandleInfoKHR {
+    VkStructureType               sType;
+    const void*                   pNext;
+    const SECURITY_ATTRIBUTES*    pAttributes;
+    DWORD                         dwAccess;
+    LPCWSTR                       name;
+} VkExportMemoryWin32HandleInfoKHR;
+
+typedef struct VkMemoryWin32HandlePropertiesKHR {
+    VkStructureType    sType;
+    void*              pNext;
+    uint32_t           memoryTypeBits;
+} VkMemoryWin32HandlePropertiesKHR;
+
+typedef struct VkMemoryGetWin32HandleInfoKHR {
+    VkStructureType                       sType;
+    const void*                           pNext;
+    VkDeviceMemory                        memory;
+    VkExternalMemoryHandleTypeFlagBits    handleType;
+} VkMemoryGetWin32HandleInfoKHR;
+
+typedef VkResult (VKAPI_PTR *PFN_vkGetMemoryWin32HandleKHR)(VkDevice device, const VkMemoryGetWin32HandleInfoKHR* pGetWin32HandleInfo, HANDLE* pHandle);
+typedef VkResult (VKAPI_PTR *PFN_vkGetMemoryWin32HandlePropertiesKHR)(VkDevice device, VkExternalMemoryHandleTypeFlagBits handleType, HANDLE handle, VkMemoryWin32HandlePropertiesKHR* pMemoryWin32HandleProperties);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkGetMemoryWin32HandleKHR(
+    VkDevice                                    device,
+    const VkMemoryGetWin32HandleInfoKHR*        pGetWin32HandleInfo,
+    HANDLE*                                     pHandle);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetMemoryWin32HandlePropertiesKHR(
+    VkDevice                                    device,
+    VkExternalMemoryHandleTypeFlagBits          handleType,
+    HANDLE                                      handle,
+    VkMemoryWin32HandlePropertiesKHR*           pMemoryWin32HandleProperties);
+#endif
+
+
+#define VK_KHR_win32_keyed_mutex 1
+#define VK_KHR_WIN32_KEYED_MUTEX_SPEC_VERSION 1
+#define VK_KHR_WIN32_KEYED_MUTEX_EXTENSION_NAME "VK_KHR_win32_keyed_mutex"
+typedef struct VkWin32KeyedMutexAcquireReleaseInfoKHR {
+    VkStructureType          sType;
+    const void*              pNext;
+    uint32_t                 acquireCount;
+    const VkDeviceMemory*    pAcquireSyncs;
+    const uint64_t*          pAcquireKeys;
+    const uint32_t*          pAcquireTimeouts;
+    uint32_t                 releaseCount;
+    const VkDeviceMemory*    pReleaseSyncs;
+    const uint64_t*          pReleaseKeys;
+} VkWin32KeyedMutexAcquireReleaseInfoKHR;
+
+
+
+#define VK_KHR_external_semaphore_win32 1
+#define VK_KHR_EXTERNAL_SEMAPHORE_WIN32_SPEC_VERSION 1
+#define VK_KHR_EXTERNAL_SEMAPHORE_WIN32_EXTENSION_NAME "VK_KHR_external_semaphore_win32"
+typedef struct VkImportSemaphoreWin32HandleInfoKHR {
+    VkStructureType                          sType;
+    const void*                              pNext;
+    VkSemaphore                              semaphore;
+    VkSemaphoreImportFlags                   flags;
+    VkExternalSemaphoreHandleTypeFlagBits    handleType;
+    HANDLE                                   handle;
+    LPCWSTR                                  name;
+} VkImportSemaphoreWin32HandleInfoKHR;
+
+typedef struct VkExportSemaphoreWin32HandleInfoKHR {
+    VkStructureType               sType;
+    const void*                   pNext;
+    const SECURITY_ATTRIBUTES*    pAttributes;
+    DWORD                         dwAccess;
+    LPCWSTR                       name;
+} VkExportSemaphoreWin32HandleInfoKHR;
+
+typedef struct VkD3D12FenceSubmitInfoKHR {
+    VkStructureType    sType;
+    const void*        pNext;
+    uint32_t           waitSemaphoreValuesCount;
+    const uint64_t*    pWaitSemaphoreValues;
+    uint32_t           signalSemaphoreValuesCount;
+    const uint64_t*    pSignalSemaphoreValues;
+} VkD3D12FenceSubmitInfoKHR;
+
+typedef struct VkSemaphoreGetWin32HandleInfoKHR {
+    VkStructureType                          sType;
+    const void*                              pNext;
+    VkSemaphore                              semaphore;
+    VkExternalSemaphoreHandleTypeFlagBits    handleType;
+} VkSemaphoreGetWin32HandleInfoKHR;
+
+typedef VkResult (VKAPI_PTR *PFN_vkImportSemaphoreWin32HandleKHR)(VkDevice device, const VkImportSemaphoreWin32HandleInfoKHR* pImportSemaphoreWin32HandleInfo);
+typedef VkResult (VKAPI_PTR *PFN_vkGetSemaphoreWin32HandleKHR)(VkDevice device, const VkSemaphoreGetWin32HandleInfoKHR* pGetWin32HandleInfo, HANDLE* pHandle);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkImportSemaphoreWin32HandleKHR(
+    VkDevice                                    device,
+    const VkImportSemaphoreWin32HandleInfoKHR*  pImportSemaphoreWin32HandleInfo);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetSemaphoreWin32HandleKHR(
+    VkDevice                                    device,
+    const VkSemaphoreGetWin32HandleInfoKHR*     pGetWin32HandleInfo,
+    HANDLE*                                     pHandle);
+#endif
+
+
+#define VK_KHR_external_fence_win32 1
+#define VK_KHR_EXTERNAL_FENCE_WIN32_SPEC_VERSION 1
+#define VK_KHR_EXTERNAL_FENCE_WIN32_EXTENSION_NAME "VK_KHR_external_fence_win32"
+typedef struct VkImportFenceWin32HandleInfoKHR {
+    VkStructureType                      sType;
+    const void*                          pNext;
+    VkFence                              fence;
+    VkFenceImportFlags                   flags;
+    VkExternalFenceHandleTypeFlagBits    handleType;
+    HANDLE                               handle;
+    LPCWSTR                              name;
+} VkImportFenceWin32HandleInfoKHR;
+
+typedef struct VkExportFenceWin32HandleInfoKHR {
+    VkStructureType               sType;
+    const void*                   pNext;
+    const SECURITY_ATTRIBUTES*    pAttributes;
+    DWORD                         dwAccess;
+    LPCWSTR                       name;
+} VkExportFenceWin32HandleInfoKHR;
+
+typedef struct VkFenceGetWin32HandleInfoKHR {
+    VkStructureType                      sType;
+    const void*                          pNext;
+    VkFence                              fence;
+    VkExternalFenceHandleTypeFlagBits    handleType;
+} VkFenceGetWin32HandleInfoKHR;
+
+typedef VkResult (VKAPI_PTR *PFN_vkImportFenceWin32HandleKHR)(VkDevice device, const VkImportFenceWin32HandleInfoKHR* pImportFenceWin32HandleInfo);
+typedef VkResult (VKAPI_PTR *PFN_vkGetFenceWin32HandleKHR)(VkDevice device, const VkFenceGetWin32HandleInfoKHR* pGetWin32HandleInfo, HANDLE* pHandle);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkImportFenceWin32HandleKHR(
+    VkDevice                                    device,
+    const VkImportFenceWin32HandleInfoKHR*      pImportFenceWin32HandleInfo);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetFenceWin32HandleKHR(
+    VkDevice                                    device,
+    const VkFenceGetWin32HandleInfoKHR*         pGetWin32HandleInfo,
+    HANDLE*                                     pHandle);
+#endif
+
+
+#define VK_NV_external_memory_win32 1
+#define VK_NV_EXTERNAL_MEMORY_WIN32_SPEC_VERSION 1
+#define VK_NV_EXTERNAL_MEMORY_WIN32_EXTENSION_NAME "VK_NV_external_memory_win32"
+typedef struct VkImportMemoryWin32HandleInfoNV {
+    VkStructureType                      sType;
+    const void*                          pNext;
+    VkExternalMemoryHandleTypeFlagsNV    handleType;
+    HANDLE                               handle;
+} VkImportMemoryWin32HandleInfoNV;
+
+typedef struct VkExportMemoryWin32HandleInfoNV {
+    VkStructureType               sType;
+    const void*                   pNext;
+    const SECURITY_ATTRIBUTES*    pAttributes;
+    DWORD                         dwAccess;
+} VkExportMemoryWin32HandleInfoNV;
+
+typedef VkResult (VKAPI_PTR *PFN_vkGetMemoryWin32HandleNV)(VkDevice device, VkDeviceMemory memory, VkExternalMemoryHandleTypeFlagsNV handleType, HANDLE* pHandle);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkGetMemoryWin32HandleNV(
+    VkDevice                                    device,
+    VkDeviceMemory                              memory,
+    VkExternalMemoryHandleTypeFlagsNV           handleType,
+    HANDLE*                                     pHandle);
+#endif
+
+
+#define VK_NV_win32_keyed_mutex 1
+#define VK_NV_WIN32_KEYED_MUTEX_SPEC_VERSION 2
+#define VK_NV_WIN32_KEYED_MUTEX_EXTENSION_NAME "VK_NV_win32_keyed_mutex"
+typedef struct VkWin32KeyedMutexAcquireReleaseInfoNV {
+    VkStructureType          sType;
+    const void*              pNext;
+    uint32_t                 acquireCount;
+    const VkDeviceMemory*    pAcquireSyncs;
+    const uint64_t*          pAcquireKeys;
+    const uint32_t*          pAcquireTimeoutMilliseconds;
+    uint32_t                 releaseCount;
+    const VkDeviceMemory*    pReleaseSyncs;
+    const uint64_t*          pReleaseKeys;
+} VkWin32KeyedMutexAcquireReleaseInfoNV;
+
+
+
+#define VK_EXT_full_screen_exclusive 1
+#define VK_EXT_FULL_SCREEN_EXCLUSIVE_SPEC_VERSION 4
+#define VK_EXT_FULL_SCREEN_EXCLUSIVE_EXTENSION_NAME "VK_EXT_full_screen_exclusive"
+
+typedef enum VkFullScreenExclusiveEXT {
+    VK_FULL_SCREEN_EXCLUSIVE_DEFAULT_EXT = 0,
+    VK_FULL_SCREEN_EXCLUSIVE_ALLOWED_EXT = 1,
+    VK_FULL_SCREEN_EXCLUSIVE_DISALLOWED_EXT = 2,
+    VK_FULL_SCREEN_EXCLUSIVE_APPLICATION_CONTROLLED_EXT = 3,
+    VK_FULL_SCREEN_EXCLUSIVE_MAX_ENUM_EXT = 0x7FFFFFFF
+} VkFullScreenExclusiveEXT;
+typedef struct VkSurfaceFullScreenExclusiveInfoEXT {
+    VkStructureType             sType;
+    void*                       pNext;
+    VkFullScreenExclusiveEXT    fullScreenExclusive;
+} VkSurfaceFullScreenExclusiveInfoEXT;
+
+typedef struct VkSurfaceCapabilitiesFullScreenExclusiveEXT {
+    VkStructureType    sType;
+    void*              pNext;
+    VkBool32           fullScreenExclusiveSupported;
+} VkSurfaceCapabilitiesFullScreenExclusiveEXT;
+
+typedef struct VkSurfaceFullScreenExclusiveWin32InfoEXT {
+    VkStructureType    sType;
+    const void*        pNext;
+    HMONITOR           hmonitor;
+} VkSurfaceFullScreenExclusiveWin32InfoEXT;
+
+typedef VkResult (VKAPI_PTR *PFN_vkGetPhysicalDeviceSurfacePresentModes2EXT)(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceSurfaceInfo2KHR* pSurfaceInfo, uint32_t* pPresentModeCount, VkPresentModeKHR* pPresentModes);
+typedef VkResult (VKAPI_PTR *PFN_vkAcquireFullScreenExclusiveModeEXT)(VkDevice device, VkSwapchainKHR swapchain);
+typedef VkResult (VKAPI_PTR *PFN_vkReleaseFullScreenExclusiveModeEXT)(VkDevice device, VkSwapchainKHR swapchain);
+typedef VkResult (VKAPI_PTR *PFN_vkGetDeviceGroupSurfacePresentModes2EXT)(VkDevice device, const VkPhysicalDeviceSurfaceInfo2KHR* pSurfaceInfo, VkDeviceGroupPresentModeFlagsKHR* pModes);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceSurfacePresentModes2EXT(
+    VkPhysicalDevice                            physicalDevice,
+    const VkPhysicalDeviceSurfaceInfo2KHR*      pSurfaceInfo,
+    uint32_t*                                   pPresentModeCount,
+    VkPresentModeKHR*                           pPresentModes);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkAcquireFullScreenExclusiveModeEXT(
+    VkDevice                                    device,
+    VkSwapchainKHR                              swapchain);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkReleaseFullScreenExclusiveModeEXT(
+    VkDevice                                    device,
+    VkSwapchainKHR                              swapchain);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetDeviceGroupSurfacePresentModes2EXT(
+    VkDevice                                    device,
+    const VkPhysicalDeviceSurfaceInfo2KHR*      pSurfaceInfo,
+    VkDeviceGroupPresentModeFlagsKHR*           pModes);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/external/Vulkan/external/vulkan/vulkan_xcb.h b/external/Vulkan/external/vulkan/vulkan_xcb.h
new file mode 100644
index 0000000000000000000000000000000000000000..5ba2ad850a2d750619197851a414d07025ae4dcd
--- /dev/null
+++ b/external/Vulkan/external/vulkan/vulkan_xcb.h
@@ -0,0 +1,55 @@
+#ifndef VULKAN_XCB_H_
+#define VULKAN_XCB_H_ 1
+
+/*
+** Copyright 2015-2021 The Khronos Group Inc.
+**
+** SPDX-License-Identifier: Apache-2.0
+*/
+
+/*
+** This header is generated from the Khronos Vulkan XML API Registry.
+**
+*/
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+
+#define VK_KHR_xcb_surface 1
+#define VK_KHR_XCB_SURFACE_SPEC_VERSION   6
+#define VK_KHR_XCB_SURFACE_EXTENSION_NAME "VK_KHR_xcb_surface"
+typedef VkFlags VkXcbSurfaceCreateFlagsKHR;
+typedef struct VkXcbSurfaceCreateInfoKHR {
+    VkStructureType               sType;
+    const void*                   pNext;
+    VkXcbSurfaceCreateFlagsKHR    flags;
+    xcb_connection_t*             connection;
+    xcb_window_t                  window;
+} VkXcbSurfaceCreateInfoKHR;
+
+typedef VkResult (VKAPI_PTR *PFN_vkCreateXcbSurfaceKHR)(VkInstance instance, const VkXcbSurfaceCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface);
+typedef VkBool32 (VKAPI_PTR *PFN_vkGetPhysicalDeviceXcbPresentationSupportKHR)(VkPhysicalDevice physicalDevice, uint32_t queueFamilyIndex, xcb_connection_t* connection, xcb_visualid_t visual_id);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateXcbSurfaceKHR(
+    VkInstance                                  instance,
+    const VkXcbSurfaceCreateInfoKHR*            pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkSurfaceKHR*                               pSurface);
+
+VKAPI_ATTR VkBool32 VKAPI_CALL vkGetPhysicalDeviceXcbPresentationSupportKHR(
+    VkPhysicalDevice                            physicalDevice,
+    uint32_t                                    queueFamilyIndex,
+    xcb_connection_t*                           connection,
+    xcb_visualid_t                              visual_id);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/external/Vulkan/external/vulkan/vulkan_xlib.h b/external/Vulkan/external/vulkan/vulkan_xlib.h
new file mode 100644
index 0000000000000000000000000000000000000000..75c75dc2e3803cd7cb719e3d80c860099a3d82ed
--- /dev/null
+++ b/external/Vulkan/external/vulkan/vulkan_xlib.h
@@ -0,0 +1,55 @@
+#ifndef VULKAN_XLIB_H_
+#define VULKAN_XLIB_H_ 1
+
+/*
+** Copyright 2015-2021 The Khronos Group Inc.
+**
+** SPDX-License-Identifier: Apache-2.0
+*/
+
+/*
+** This header is generated from the Khronos Vulkan XML API Registry.
+**
+*/
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+
+#define VK_KHR_xlib_surface 1
+#define VK_KHR_XLIB_SURFACE_SPEC_VERSION  6
+#define VK_KHR_XLIB_SURFACE_EXTENSION_NAME "VK_KHR_xlib_surface"
+typedef VkFlags VkXlibSurfaceCreateFlagsKHR;
+typedef struct VkXlibSurfaceCreateInfoKHR {
+    VkStructureType                sType;
+    const void*                    pNext;
+    VkXlibSurfaceCreateFlagsKHR    flags;
+    Display*                       dpy;
+    Window                         window;
+} VkXlibSurfaceCreateInfoKHR;
+
+typedef VkResult (VKAPI_PTR *PFN_vkCreateXlibSurfaceKHR)(VkInstance instance, const VkXlibSurfaceCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface);
+typedef VkBool32 (VKAPI_PTR *PFN_vkGetPhysicalDeviceXlibPresentationSupportKHR)(VkPhysicalDevice physicalDevice, uint32_t queueFamilyIndex, Display* dpy, VisualID visualID);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkCreateXlibSurfaceKHR(
+    VkInstance                                  instance,
+    const VkXlibSurfaceCreateInfoKHR*           pCreateInfo,
+    const VkAllocationCallbacks*                pAllocator,
+    VkSurfaceKHR*                               pSurface);
+
+VKAPI_ATTR VkBool32 VKAPI_CALL vkGetPhysicalDeviceXlibPresentationSupportKHR(
+    VkPhysicalDevice                            physicalDevice,
+    uint32_t                                    queueFamilyIndex,
+    Display*                                    dpy,
+    VisualID                                    visualID);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/external/Vulkan/external/vulkan/vulkan_xlib_xrandr.h b/external/Vulkan/external/vulkan/vulkan_xlib_xrandr.h
new file mode 100644
index 0000000000000000000000000000000000000000..fa274934226d6eae1b6d37178a2a082dfa3424bb
--- /dev/null
+++ b/external/Vulkan/external/vulkan/vulkan_xlib_xrandr.h
@@ -0,0 +1,45 @@
+#ifndef VULKAN_XLIB_XRANDR_H_
+#define VULKAN_XLIB_XRANDR_H_ 1
+
+/*
+** Copyright 2015-2021 The Khronos Group Inc.
+**
+** SPDX-License-Identifier: Apache-2.0
+*/
+
+/*
+** This header is generated from the Khronos Vulkan XML API Registry.
+**
+*/
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+
+#define VK_EXT_acquire_xlib_display 1
+#define VK_EXT_ACQUIRE_XLIB_DISPLAY_SPEC_VERSION 1
+#define VK_EXT_ACQUIRE_XLIB_DISPLAY_EXTENSION_NAME "VK_EXT_acquire_xlib_display"
+typedef VkResult (VKAPI_PTR *PFN_vkAcquireXlibDisplayEXT)(VkPhysicalDevice physicalDevice, Display* dpy, VkDisplayKHR display);
+typedef VkResult (VKAPI_PTR *PFN_vkGetRandROutputDisplayEXT)(VkPhysicalDevice physicalDevice, Display* dpy, RROutput rrOutput, VkDisplayKHR* pDisplay);
+
+#ifndef VK_NO_PROTOTYPES
+VKAPI_ATTR VkResult VKAPI_CALL vkAcquireXlibDisplayEXT(
+    VkPhysicalDevice                            physicalDevice,
+    Display*                                    dpy,
+    VkDisplayKHR                                display);
+
+VKAPI_ATTR VkResult VKAPI_CALL vkGetRandROutputDisplayEXT(
+    VkPhysicalDevice                            physicalDevice,
+    Display*                                    dpy,
+    RROutput                                    rrOutput,
+    VkDisplayKHR*                               pDisplay);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/external/Vulkan/images/androidlogo.png b/external/Vulkan/images/androidlogo.png
new file mode 100644
index 0000000000000000000000000000000000000000..40bf934bb5867e7d1ffa8e8cc705c4b7f1ba5c25
Binary files /dev/null and b/external/Vulkan/images/androidlogo.png differ
diff --git a/external/Vulkan/images/applelogo.png b/external/Vulkan/images/applelogo.png
new file mode 100644
index 0000000000000000000000000000000000000000..31f83b0458f2b0d1e82ad95bb878bc4347a9f41c
Binary files /dev/null and b/external/Vulkan/images/applelogo.png differ
diff --git a/external/Vulkan/images/linuxlogo.png b/external/Vulkan/images/linuxlogo.png
new file mode 100644
index 0000000000000000000000000000000000000000..bdc5b1d73e1f860c669819fba0a4c949c93e61b3
Binary files /dev/null and b/external/Vulkan/images/linuxlogo.png differ
diff --git a/external/Vulkan/images/vulkanlogo.png b/external/Vulkan/images/vulkanlogo.png
new file mode 100644
index 0000000000000000000000000000000000000000..09bbb8687cd0240ec174a58888ca317a2852f4f1
Binary files /dev/null and b/external/Vulkan/images/vulkanlogo.png differ
diff --git a/external/Vulkan/images/vulkanlogoscene.png b/external/Vulkan/images/vulkanlogoscene.png
new file mode 100644
index 0000000000000000000000000000000000000000..c44bba44192ba32abb66b0f631edb5338cede528
Binary files /dev/null and b/external/Vulkan/images/vulkanlogoscene.png differ
diff --git a/external/Vulkan/images/windowslogo.png b/external/Vulkan/images/windowslogo.png
new file mode 100644
index 0000000000000000000000000000000000000000..ecbbd20d049c4e079a2f43f26d2e821aa9da9619
Binary files /dev/null and b/external/Vulkan/images/windowslogo.png differ
diff --git a/external/Vulkan/libs/vulkan/libvulkan.so b/external/Vulkan/libs/vulkan/libvulkan.so
new file mode 120000
index 0000000000000000000000000000000000000000..2a67c929c81c56294f43c50e47817ef64c82ffcf
--- /dev/null
+++ b/external/Vulkan/libs/vulkan/libvulkan.so
@@ -0,0 +1 @@
+libvulkan.so.1
\ No newline at end of file
diff --git a/external/Vulkan/libs/vulkan/libvulkan.so.1 b/external/Vulkan/libs/vulkan/libvulkan.so.1
new file mode 120000
index 0000000000000000000000000000000000000000..539d42aa08cbdb0bfd688732163e33ff7588987c
--- /dev/null
+++ b/external/Vulkan/libs/vulkan/libvulkan.so.1
@@ -0,0 +1 @@
+libvulkan.so.1.1.73
\ No newline at end of file
diff --git a/external/Vulkan/libs/vulkan/libvulkan.so.1.1.73 b/external/Vulkan/libs/vulkan/libvulkan.so.1.1.73
new file mode 100755
index 0000000000000000000000000000000000000000..8702e1faf6621358d8f4b7a2351534f6059a64b1
Binary files /dev/null and b/external/Vulkan/libs/vulkan/libvulkan.so.1.1.73 differ
diff --git a/external/Vulkan/libs/vulkan/vulkan-1.lib b/external/Vulkan/libs/vulkan/vulkan-1.lib
new file mode 100644
index 0000000000000000000000000000000000000000..587229f7620eb45687c421a893eb14f170e9ede0
Binary files /dev/null and b/external/Vulkan/libs/vulkan/vulkan-1.lib differ
diff --git a/external/Vulkan/screenshots/bloom.jpg b/external/Vulkan/screenshots/bloom.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..ff2a343966d37b283d1dcfd4b321cdb37948f93e
Binary files /dev/null and b/external/Vulkan/screenshots/bloom.jpg differ
diff --git a/external/Vulkan/screenshots/computecloth.jpg b/external/Vulkan/screenshots/computecloth.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..6a15960c102f0a22438e9433c5679abe5710d601
Binary files /dev/null and b/external/Vulkan/screenshots/computecloth.jpg differ
diff --git a/external/Vulkan/screenshots/computecullandlod.jpg b/external/Vulkan/screenshots/computecullandlod.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..43ccfd7f1e0f7067c44ea4f39d71c20827026e91
Binary files /dev/null and b/external/Vulkan/screenshots/computecullandlod.jpg differ
diff --git a/external/Vulkan/screenshots/computenbody.jpg b/external/Vulkan/screenshots/computenbody.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..d238b6d80a81c5c7c8bba4e1ac9b64316e4451b4
Binary files /dev/null and b/external/Vulkan/screenshots/computenbody.jpg differ
diff --git a/external/Vulkan/screenshots/computeparticles.jpg b/external/Vulkan/screenshots/computeparticles.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..51b4330cd8b89fcbc8dce2163de32f65d305a3ad
Binary files /dev/null and b/external/Vulkan/screenshots/computeparticles.jpg differ
diff --git a/external/Vulkan/screenshots/computeraytracing.jpg b/external/Vulkan/screenshots/computeraytracing.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..a8ae316883341e5bf5ba0ff7a8ea68d024bc9d03
Binary files /dev/null and b/external/Vulkan/screenshots/computeraytracing.jpg differ
diff --git a/external/Vulkan/screenshots/computeshader.jpg b/external/Vulkan/screenshots/computeshader.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..f9b28649a57df854687b2d6ff164b3fbc7827568
Binary files /dev/null and b/external/Vulkan/screenshots/computeshader.jpg differ
diff --git a/external/Vulkan/screenshots/conditionalrender.jpg b/external/Vulkan/screenshots/conditionalrender.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..1c38d79eba7b516aaaa26bb4ce4a77db051ec836
Binary files /dev/null and b/external/Vulkan/screenshots/conditionalrender.jpg differ
diff --git a/external/Vulkan/screenshots/conservativeraster.jpg b/external/Vulkan/screenshots/conservativeraster.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..c2ae34ffa40d873e466928c02b437181b89672dc
Binary files /dev/null and b/external/Vulkan/screenshots/conservativeraster.jpg differ
diff --git a/external/Vulkan/screenshots/debugmarker.jpg b/external/Vulkan/screenshots/debugmarker.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..717aea33cc932fdd72e4b7798404308ca22eb24c
Binary files /dev/null and b/external/Vulkan/screenshots/debugmarker.jpg differ
diff --git a/external/Vulkan/screenshots/deferred.jpg b/external/Vulkan/screenshots/deferred.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..aabcaa33e392ead1800076dac280b082ad8deb32
Binary files /dev/null and b/external/Vulkan/screenshots/deferred.jpg differ
diff --git a/external/Vulkan/screenshots/deferredmultisampling.jpg b/external/Vulkan/screenshots/deferredmultisampling.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..08721dfa9226f7f0f6fa0338da6adb3d586893a4
Binary files /dev/null and b/external/Vulkan/screenshots/deferredmultisampling.jpg differ
diff --git a/external/Vulkan/screenshots/deferredshadows.jpg b/external/Vulkan/screenshots/deferredshadows.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..b302be646cb9da853283a54365dc0452b5aa4cb3
Binary files /dev/null and b/external/Vulkan/screenshots/deferredshadows.jpg differ
diff --git a/external/Vulkan/screenshots/descriptorsets.jpg b/external/Vulkan/screenshots/descriptorsets.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..6ef3269c80fa7700294743c83a9f77b04b5ab91f
Binary files /dev/null and b/external/Vulkan/screenshots/descriptorsets.jpg differ
diff --git a/external/Vulkan/screenshots/displacement.jpg b/external/Vulkan/screenshots/displacement.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..027986f738ee2808017d02ced97559229ded4dcf
Binary files /dev/null and b/external/Vulkan/screenshots/displacement.jpg differ
diff --git a/external/Vulkan/screenshots/distancefieldfonts.jpg b/external/Vulkan/screenshots/distancefieldfonts.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..902f6dccbaf796701eba4347c18ccfca024c5b9d
Binary files /dev/null and b/external/Vulkan/screenshots/distancefieldfonts.jpg differ
diff --git a/external/Vulkan/screenshots/dynamicuniformbuffer.jpg b/external/Vulkan/screenshots/dynamicuniformbuffer.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..0fbb0debc6672a31d47fd5eb255f92f10ba9e463
Binary files /dev/null and b/external/Vulkan/screenshots/dynamicuniformbuffer.jpg differ
diff --git a/external/Vulkan/screenshots/ext_debugmarker.jpg b/external/Vulkan/screenshots/ext_debugmarker.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..c5b61b2a1f82528f9b535d5a5a3b624e17158e2e
Binary files /dev/null and b/external/Vulkan/screenshots/ext_debugmarker.jpg differ
diff --git a/external/Vulkan/screenshots/gears.jpg b/external/Vulkan/screenshots/gears.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..0b6a017713cbc3136c6a7ec9f95cabb13054a594
Binary files /dev/null and b/external/Vulkan/screenshots/gears.jpg differ
diff --git a/external/Vulkan/screenshots/geometryshader.jpg b/external/Vulkan/screenshots/geometryshader.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..ba1d0cd2892f33f91f0e1758c50bb1ec6ff7dcd1
Binary files /dev/null and b/external/Vulkan/screenshots/geometryshader.jpg differ
diff --git a/external/Vulkan/screenshots/gltfloading.jpg b/external/Vulkan/screenshots/gltfloading.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..3e9bc42f1e2927e93c39f328d0369643f42a614d
Binary files /dev/null and b/external/Vulkan/screenshots/gltfloading.jpg differ
diff --git a/external/Vulkan/screenshots/gltfscenerendering.jpg b/external/Vulkan/screenshots/gltfscenerendering.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..56702ec9a909f9fb2c4af6fbc9c48fae2b9604a3
Binary files /dev/null and b/external/Vulkan/screenshots/gltfscenerendering.jpg differ
diff --git a/external/Vulkan/screenshots/gltfskinning.jpg b/external/Vulkan/screenshots/gltfskinning.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..9872cb464899320887fe555ba6679a6198821d6f
Binary files /dev/null and b/external/Vulkan/screenshots/gltfskinning.jpg differ
diff --git a/external/Vulkan/screenshots/hdr.jpg b/external/Vulkan/screenshots/hdr.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..6398271e6e94b5f4c14960b3215983d065b4d322
Binary files /dev/null and b/external/Vulkan/screenshots/hdr.jpg differ
diff --git a/external/Vulkan/screenshots/imgui.jpg b/external/Vulkan/screenshots/imgui.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..0e4c1ff9ac43a490743a977f52a6aecc4b05674a
Binary files /dev/null and b/external/Vulkan/screenshots/imgui.jpg differ
diff --git a/external/Vulkan/screenshots/indirectdraw.jpg b/external/Vulkan/screenshots/indirectdraw.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..cd55748a084df673706e19a32f5a913cefbca4dc
Binary files /dev/null and b/external/Vulkan/screenshots/indirectdraw.jpg differ
diff --git a/external/Vulkan/screenshots/inlineuniformblocks.jpg b/external/Vulkan/screenshots/inlineuniformblocks.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..29af8463832bb56c6c3191e90261cfd060b30f75
Binary files /dev/null and b/external/Vulkan/screenshots/inlineuniformblocks.jpg differ
diff --git a/external/Vulkan/screenshots/inputattachments.jpg b/external/Vulkan/screenshots/inputattachments.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..46f5f0d8a5f998a130c33233f18f11959aac73cd
Binary files /dev/null and b/external/Vulkan/screenshots/inputattachments.jpg differ
diff --git a/external/Vulkan/screenshots/instancing.jpg b/external/Vulkan/screenshots/instancing.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..6b44fd63aa6dd60aaf7e07acfc6c22c3ca75caf7
Binary files /dev/null and b/external/Vulkan/screenshots/instancing.jpg differ
diff --git a/external/Vulkan/screenshots/multisampling.jpg b/external/Vulkan/screenshots/multisampling.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..085bec3913f98b1f5321e7573ef85fe69b796b2b
Binary files /dev/null and b/external/Vulkan/screenshots/multisampling.jpg differ
diff --git a/external/Vulkan/screenshots/multithreading.jpg b/external/Vulkan/screenshots/multithreading.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..80e47796048f6ff7b303bb3331d66a22f603e98f
Binary files /dev/null and b/external/Vulkan/screenshots/multithreading.jpg differ
diff --git a/external/Vulkan/screenshots/multiview.jpg b/external/Vulkan/screenshots/multiview.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..5f5463d063ea0cbe28d6c40add2511f3e5f9d4a3
Binary files /dev/null and b/external/Vulkan/screenshots/multiview.jpg differ
diff --git a/external/Vulkan/screenshots/negativeviewportheight.jpg b/external/Vulkan/screenshots/negativeviewportheight.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..afe7a4d07f0cea281e91875f7740b3e4271545c1
Binary files /dev/null and b/external/Vulkan/screenshots/negativeviewportheight.jpg differ
diff --git a/external/Vulkan/screenshots/nv_ray_tracing_basic.jpg b/external/Vulkan/screenshots/nv_ray_tracing_basic.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..b0841856149a5bd016eb3e1d976ad60013036bc0
Binary files /dev/null and b/external/Vulkan/screenshots/nv_ray_tracing_basic.jpg differ
diff --git a/external/Vulkan/screenshots/nv_ray_tracing_reflections.jpg b/external/Vulkan/screenshots/nv_ray_tracing_reflections.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..f571dfebd3939ffa6fc867e89d71aa76e700993e
Binary files /dev/null and b/external/Vulkan/screenshots/nv_ray_tracing_reflections.jpg differ
diff --git a/external/Vulkan/screenshots/nv_ray_tracing_shadows.jpg b/external/Vulkan/screenshots/nv_ray_tracing_shadows.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..76bd0c1754c6bc5b5879c572bfafa23705b9635d
Binary files /dev/null and b/external/Vulkan/screenshots/nv_ray_tracing_shadows.jpg differ
diff --git a/external/Vulkan/screenshots/occlusionquery.jpg b/external/Vulkan/screenshots/occlusionquery.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..4aae51a72aedfe7a858f3fe3677de06602248d23
Binary files /dev/null and b/external/Vulkan/screenshots/occlusionquery.jpg differ
diff --git a/external/Vulkan/screenshots/offscreen.jpg b/external/Vulkan/screenshots/offscreen.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..61f4e41b943415e2b4d079a116da6848e2caaea4
Binary files /dev/null and b/external/Vulkan/screenshots/offscreen.jpg differ
diff --git a/external/Vulkan/screenshots/oit.jpg b/external/Vulkan/screenshots/oit.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..24d10ac2a800ac447921a207f78c698407798edc
Binary files /dev/null and b/external/Vulkan/screenshots/oit.jpg differ
diff --git a/external/Vulkan/screenshots/parallaxmapping.jpg b/external/Vulkan/screenshots/parallaxmapping.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..fe4d9f062c10b23312b4d927a6be0c380075456f
Binary files /dev/null and b/external/Vulkan/screenshots/parallaxmapping.jpg differ
diff --git a/external/Vulkan/screenshots/particlefire.jpg b/external/Vulkan/screenshots/particlefire.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..a28c656d3b49aa5f1a3e7c3e9abb8cf6417cd448
Binary files /dev/null and b/external/Vulkan/screenshots/particlefire.jpg differ
diff --git a/external/Vulkan/screenshots/pbrbasic.jpg b/external/Vulkan/screenshots/pbrbasic.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..33609bd219ed1dc0ebde68cf28f79173c972ef62
Binary files /dev/null and b/external/Vulkan/screenshots/pbrbasic.jpg differ
diff --git a/external/Vulkan/screenshots/pbribl.jpg b/external/Vulkan/screenshots/pbribl.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..8920bf6728edff57fed4027045d4018cea1a52c1
Binary files /dev/null and b/external/Vulkan/screenshots/pbribl.jpg differ
diff --git a/external/Vulkan/screenshots/pbrtexture.jpg b/external/Vulkan/screenshots/pbrtexture.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..36921d72575c3715572e20aa6f32ce040cdcfce0
Binary files /dev/null and b/external/Vulkan/screenshots/pbrtexture.jpg differ
diff --git a/external/Vulkan/screenshots/pipelines.jpg b/external/Vulkan/screenshots/pipelines.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..607b8259bb4a06e678ea47473c6f8186c9cd1641
Binary files /dev/null and b/external/Vulkan/screenshots/pipelines.jpg differ
diff --git a/external/Vulkan/screenshots/pipelinestatistics.jpg b/external/Vulkan/screenshots/pipelinestatistics.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..5b9802c4a1a32439779c83d1cbb63cbb02e53e55
Binary files /dev/null and b/external/Vulkan/screenshots/pipelinestatistics.jpg differ
diff --git a/external/Vulkan/screenshots/pushconstants.jpg b/external/Vulkan/screenshots/pushconstants.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..77f5b11b279d0e5efd03c49561857251f6a59bb8
Binary files /dev/null and b/external/Vulkan/screenshots/pushconstants.jpg differ
diff --git a/external/Vulkan/screenshots/pushdescriptors.jpg b/external/Vulkan/screenshots/pushdescriptors.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..b47f784e5092c779946944629e8424b29f78eff5
Binary files /dev/null and b/external/Vulkan/screenshots/pushdescriptors.jpg differ
diff --git a/external/Vulkan/screenshots/radialblur.jpg b/external/Vulkan/screenshots/radialblur.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..af137e88d3019caea10132183722cf770ed56326
Binary files /dev/null and b/external/Vulkan/screenshots/radialblur.jpg differ
diff --git a/external/Vulkan/screenshots/screenshot.jpg b/external/Vulkan/screenshots/screenshot.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..fb6cce7e22d9d741db1287c86a6a7eb260c8d226
Binary files /dev/null and b/external/Vulkan/screenshots/screenshot.jpg differ
diff --git a/external/Vulkan/screenshots/shadowmapping.jpg b/external/Vulkan/screenshots/shadowmapping.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..6ccafadc95437591ddf90ea8b2c2054b5dd92f75
Binary files /dev/null and b/external/Vulkan/screenshots/shadowmapping.jpg differ
diff --git a/external/Vulkan/screenshots/shadowmappingcascade.jpg b/external/Vulkan/screenshots/shadowmappingcascade.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..d2ac5ba7b7062a1c31648bf3867a8e20cbe5101f
Binary files /dev/null and b/external/Vulkan/screenshots/shadowmappingcascade.jpg differ
diff --git a/external/Vulkan/screenshots/shadowmappingomni.jpg b/external/Vulkan/screenshots/shadowmappingomni.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..51ccad5c7b83d4c5ede2bb61e46be90ab8096ad8
Binary files /dev/null and b/external/Vulkan/screenshots/shadowmappingomni.jpg differ
diff --git a/external/Vulkan/screenshots/specializationconstants.jpg b/external/Vulkan/screenshots/specializationconstants.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..9439c028b60da960531f08ec0353f9385d8f65bc
Binary files /dev/null and b/external/Vulkan/screenshots/specializationconstants.jpg differ
diff --git a/external/Vulkan/screenshots/sphericalenvmapping.jpg b/external/Vulkan/screenshots/sphericalenvmapping.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..511368a33137ebed68c459770ee203a0819532d6
Binary files /dev/null and b/external/Vulkan/screenshots/sphericalenvmapping.jpg differ
diff --git a/external/Vulkan/screenshots/ssao.jpg b/external/Vulkan/screenshots/ssao.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..1f1e1eb7096a51a7ffb82df79eba65fd92b86618
Binary files /dev/null and b/external/Vulkan/screenshots/ssao.jpg differ
diff --git a/external/Vulkan/screenshots/stencilbuffer.jpg b/external/Vulkan/screenshots/stencilbuffer.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..16a5f1037070b43527a96a54a1c904debfb7ccdd
Binary files /dev/null and b/external/Vulkan/screenshots/stencilbuffer.jpg differ
diff --git a/external/Vulkan/screenshots/subpasses.jpg b/external/Vulkan/screenshots/subpasses.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..2110fc33788b8b0d7ba89c7f8d3b4b72d8b9df59
Binary files /dev/null and b/external/Vulkan/screenshots/subpasses.jpg differ
diff --git a/external/Vulkan/screenshots/terraintessellation.jpg b/external/Vulkan/screenshots/terraintessellation.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..b16ca2ac25d56ed7d58259dd40e505ac22847218
Binary files /dev/null and b/external/Vulkan/screenshots/terraintessellation.jpg differ
diff --git a/external/Vulkan/screenshots/tessellation.jpg b/external/Vulkan/screenshots/tessellation.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..f82397e59cd94c1c52872085cb1a33a2d581d815
Binary files /dev/null and b/external/Vulkan/screenshots/tessellation.jpg differ
diff --git a/external/Vulkan/screenshots/textoverlay.jpg b/external/Vulkan/screenshots/textoverlay.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..3795a82c083aea51fb072dc29c4dfab7d1bc4fb5
Binary files /dev/null and b/external/Vulkan/screenshots/textoverlay.jpg differ
diff --git a/external/Vulkan/screenshots/texture.jpg b/external/Vulkan/screenshots/texture.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..cf53e16b4ceacd189e58ac4f577b83f3d539fb0f
Binary files /dev/null and b/external/Vulkan/screenshots/texture.jpg differ
diff --git a/external/Vulkan/screenshots/texture3d.jpg b/external/Vulkan/screenshots/texture3d.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..61845bd3bdac262f894483320ac28c4b7f40a05e
Binary files /dev/null and b/external/Vulkan/screenshots/texture3d.jpg differ
diff --git a/external/Vulkan/screenshots/texturearray.jpg b/external/Vulkan/screenshots/texturearray.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..b3a89af278d8ab5b223da03670cf90352f345f86
Binary files /dev/null and b/external/Vulkan/screenshots/texturearray.jpg differ
diff --git a/external/Vulkan/screenshots/texturecubemap.jpg b/external/Vulkan/screenshots/texturecubemap.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..cb518575c7f7c573267a716bd3ab320f522e7e8e
Binary files /dev/null and b/external/Vulkan/screenshots/texturecubemap.jpg differ
diff --git a/external/Vulkan/screenshots/texturecubemaparray.jpg b/external/Vulkan/screenshots/texturecubemaparray.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..f20b56ec597e225f67749f331046593df39bfba0
Binary files /dev/null and b/external/Vulkan/screenshots/texturecubemaparray.jpg differ
diff --git a/external/Vulkan/screenshots/texturemipmapgen.jpg b/external/Vulkan/screenshots/texturemipmapgen.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..df61a56bd22e5ea20e387e500e2feb3e74308738
Binary files /dev/null and b/external/Vulkan/screenshots/texturemipmapgen.jpg differ
diff --git a/external/Vulkan/screenshots/texturesparseresidency.jpg b/external/Vulkan/screenshots/texturesparseresidency.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..2c0fba2f8b37787f9c5095ea45f930bf882b4a11
Binary files /dev/null and b/external/Vulkan/screenshots/texturesparseresidency.jpg differ
diff --git a/external/Vulkan/screenshots/triangle.jpg b/external/Vulkan/screenshots/triangle.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..18c5d128df05b019b6259aa83f3702561e965707
Binary files /dev/null and b/external/Vulkan/screenshots/triangle.jpg differ
diff --git a/external/Vulkan/screenshots/variablerateshading.jpg b/external/Vulkan/screenshots/variablerateshading.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..4d395bcb7c3cc6d089ef9ee7071e88a09591169c
Binary files /dev/null and b/external/Vulkan/screenshots/variablerateshading.jpg differ
diff --git a/external/Vulkan/screenshots/viewportarray.jpg b/external/Vulkan/screenshots/viewportarray.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..23f0efcfbdbe150644e694fa3ef054a5b372299f
Binary files /dev/null and b/external/Vulkan/screenshots/viewportarray.jpg differ
diff --git a/external/Vulkan/screenshots/vulkanscene.jpg b/external/Vulkan/screenshots/vulkanscene.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..cee560fea5eb0908f82fc2f6ccb091ab5718d932
Binary files /dev/null and b/external/Vulkan/screenshots/vulkanscene.jpg differ
diff --git a/external/Vulkan/xcode/MVKExample.cpp b/external/Vulkan/xcode/MVKExample.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..6db1c1cdfb56282ba3db8e02be2e1369a03a9576
--- /dev/null
+++ b/external/Vulkan/xcode/MVKExample.cpp
@@ -0,0 +1,30 @@
+/*
+ *  MVKExample.cpp
+ *
+ *  Copyright (c) 2016-2017 The Brenwill Workshop Ltd.
+ *  This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+
+
+#include "MVKExample.h"
+#include "examples.h"
+
+void MVKExample::renderFrame() {
+    _vulkanExample->renderFrame();
+}
+
+void MVKExample::keyPressed(uint32_t keyCode) {
+    _vulkanExample->keyPressed(keyCode);
+}
+
+MVKExample::MVKExample(void* view) {
+    _vulkanExample = new VulkanExample();
+    _vulkanExample->initVulkan();
+    _vulkanExample->setupWindow(view);
+    _vulkanExample->initSwapchain();
+    _vulkanExample->prepare();
+}
+
+MVKExample::~MVKExample() {
+    delete _vulkanExample;
+}
diff --git a/external/Vulkan/xcode/MVKExample.h b/external/Vulkan/xcode/MVKExample.h
new file mode 100644
index 0000000000000000000000000000000000000000..93d921ec6e2d82d91ca4d4640055dc0496c77073
--- /dev/null
+++ b/external/Vulkan/xcode/MVKExample.h
@@ -0,0 +1,27 @@
+/*
+ *  MVKExample.h
+ *
+ *  Copyright (c) 2016-2017 The Brenwill Workshop Ltd.
+ *  This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+
+#pragma once
+
+#include "vulkanexamplebase.h"
+
+// Wrapper class for the SW VulkanExample instance.
+class MVKExample {
+
+public:
+    void renderFrame();
+    void keyPressed(uint32_t keyCode);
+
+    MVKExample(void* view);
+    ~MVKExample();
+
+protected:
+    VulkanExampleBase* _vulkanExample;
+};
+
+
+
diff --git a/external/Vulkan/xcode/MoltenVK b/external/Vulkan/xcode/MoltenVK
new file mode 120000
index 0000000000000000000000000000000000000000..b8175b83f0e17d952072f3c062530b28bf0616f8
--- /dev/null
+++ b/external/Vulkan/xcode/MoltenVK
@@ -0,0 +1 @@
+../../../../../Molten/Package/Latest/MoltenVK
\ No newline at end of file
diff --git a/external/Vulkan/xcode/README_MoltenVK_Examples.md b/external/Vulkan/xcode/README_MoltenVK_Examples.md
new file mode 100755
index 0000000000000000000000000000000000000000..9aada1dfa78ad0d9ab5bbe1f726694a5e5d2519d
--- /dev/null
+++ b/external/Vulkan/xcode/README_MoltenVK_Examples.md
@@ -0,0 +1,75 @@
+<a class="site-logo" href="https://www.moltengl.com/moltenvk/" title="MoltenVK">
+	<img src="images/MoltenVK-Logo-Banner.png" alt="MoltenVK Home" style="width:256px;height:auto">
+</a>
+
+#MoltenVK Vulkan Examples
+
+Copyright (c) 2016-2017 [The Brenwill Workshop Ltd.](http://www.brenwill.com).
+This document is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+
+*This document is written in [Markdown](http://en.wikipedia.org/wiki/Markdown) format.
+For best results, use a Markdown reader.*
+
+
+<a name="intro"></a>
+
+Introduction
+------------
+
+The *Xcode* project in this folder builds and runs the *Vulkan* examples in this
+repository on *iOS* and *macOS*, using the **MoltenVK** *Vulkan* driver.
+
+
+
+<a name="installing-moltenvk"></a>
+
+Installing MoltenVK
+-------------------
+
+The examples in this repository can be run on *iOS* and *macOS* by using
+the [**MoltenVK**](http://www.moltengl.com/moltenvk/) *Vulkan* driver.
+
+These examples require **MoltenVK 0.18.0** or greater.
+
+Follow these instructions to install **MoltenVK**:
+
+1. [Download](https://moltengl.com/free-trial/) the **Molten** free evaluation trial.
+   This free trial includes **MoltenVK**, is full-featured, and is not time-limited.
+   You must purchase a license if you wish to use **MoltenVK** for a production
+   application or game, but you can use the evaluation version to run these examples.
+
+2. Unzip the **Molten** package, and move it to a folder outside this repository.
+
+3. Open a *Terminal* session and navigate to the directory containing this document,
+   remove the existing `MoltenVK` symbolic link in this directory, and create a new
+   symbolic link pointing to the `MoltenVK` directory in the **Molten** package:
+
+   		cd path-to-this-directory
+		rm MoltenVK
+		ln -s path-to-Molten-package/MoltenVK
+
+<a name="running-examples"></a>
+
+Running the Vulkan Examples
+---------------------------
+
+The single `examples.xcodeproj` *Xcode* project can be used to run any of the examples
+in this repository on either *iOS* or *macOS*. To do so, follow these instructions:
+
+1. Open the `examples.xcodeproj` *Xcode* project.
+
+2. Specify which of the many examples within this respository you wish to run, by opening
+   the `examples.h` file within *Xcode*, and following the instructions in the comments
+   within that file to indicate which of the examples you wish to run.
+
+3. Run either the `examples-iOS` or `examples-macOS` *Xcode Scheme* to run the example in *iOS*
+   or *macOS*, repectively.
+
+4. Many of the examples include an option to press keys to control the display of features
+   and scene components:
+
+   - On *iOS*, tap on the scene to display the keyboard. Tap again on the scene to hide the keyboard.
+   - On both *iOS* and *macOS*, use the numeric keys (*1, 2, 3...*) instead of function keys (*F1, F2, F3...*).
+   - On both *iOS* and *macOS*, use the regular keyboard *+* and *-* keys instead of the numpad *+* and *-* keys.
+   - On both *iOS* and *macOS*, use the *delete* key instead of the *escape* key.
+
diff --git a/external/Vulkan/xcode/examples.h b/external/Vulkan/xcode/examples.h
new file mode 100644
index 0000000000000000000000000000000000000000..20b7ab8973d0fc5faee49ffe53c75ee2cfc22418
--- /dev/null
+++ b/external/Vulkan/xcode/examples.h
@@ -0,0 +1,190 @@
+/*
+ *  examples.h
+ *
+ *  Copyright (c) 2016-2017 The Brenwill Workshop Ltd.
+ *  This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ *
+ *
+ * Loads the appropriate example code, as indicated by the appropriate compiler build setting below.
+ *
+ * To select an example to run, define one (and only one) of the macros below, either by
+ * adding a #define XXX statement at the top of this file, or more flexibily, by adding the
+ * macro value to the Preprocessor Macros (aka GCC_PREPROCESSOR_DEFINITIONS) compiler setting.
+ *
+ * To add a compiler setting, select the project in the Xcode Project Navigator panel,
+ * select the Build Settings panel, and add the value to the Preprocessor Macros
+ * (aka GCC_PREPROCESSOR_DEFINITIONS) entry.
+ *
+ * For example, to run the pipelines example, you would add the MVK_pipelines define macro
+ * to the Preprocessor Macros (aka GCC_PREPROCESSOR_DEFINITIONS) entry of the Xcode project,
+ * overwriting any otheor value there.
+ *
+ * If you choose to add a #define statement to this file, be sure to clear the existing macro
+ * from the Preprocessor Macros (aka GCC_PREPROCESSOR_DEFINITIONS) compiler setting in Xcode.
+ */
+
+
+// In the list below, the comments indicate entries that,
+// under certain conditions, that may not run as expected.
+
+#define MVK_vulkanscene
+
+// BASICS
+
+#ifdef MVK_pipelines
+#   include "../examples/pipelines/pipelines.cpp"
+#endif
+
+#ifdef MVK_texture
+#   include "../examples/texture/texture.cpp"
+#endif
+
+// Does not run. Metal does not support passing matrices between shader stages.
+#ifdef MVK_texturecubemap
+#   include "../examples/texturecubemap/texturecubemap.cpp"
+#endif
+
+// Runs in Release mode. Does not run in Debug mode, as Metal validation will
+// assert that UBO buffer length is too short for UBO size declared in shader.
+#ifdef MVK_texturearray
+#   include "../examples/texturearray/texturearray.cpp"
+#endif
+
+#ifdef MVK_mesh
+#   include "../examples/mesh/mesh.cpp"
+#endif
+
+#ifdef MVK_dynamicuniformbuffer
+#   include "../examples/dynamicuniformbuffer/dynamicuniformbuffer.cpp"
+#endif
+
+// Does not run. Metal does not support passing arrays between shader stages.
+#ifdef MVK_pushconstants
+#   include "../examples/pushconstants/pushconstants.cpp"
+#endif
+
+#ifdef MVK_specializationconstants
+#   include "../examples/specializationconstants/specializationconstants.cpp"
+#endif
+
+#ifdef MVK_offscreen
+#   include "../examples/offscreen/offscreen.cpp"
+#endif
+
+#ifdef MVK_radialblur
+#   include "../examples/radialblur/radialblur.cpp"
+#endif
+
+#ifdef MVK_textoverlay
+#   include "../examples/textoverlay/textoverlay.cpp"
+#endif
+
+#ifdef MVK_particlefire
+#   include "../examples/particlefire/particlefire.cpp"
+#endif
+
+
+// ADVANCED
+
+#ifdef MVK_multithreading
+#   include "../examples/multithreading/multithreading.cpp"
+#endif
+
+#ifdef MVK_scenerendering
+#   include "../examples/scenerendering/scenerendering.cpp"
+#endif
+
+#ifdef MVK_instancing
+#   include "../examples/instancing/instancing.cpp"
+#endif
+
+#ifdef MVK_indirectdraw
+#   include "../examples/indirectdraw/indirectdraw.cpp"
+#endif
+
+// Does not run. Metal does not support passing matrices between shader stages.
+#ifdef MVK_hdr
+#   include "../examples/hdr/hdr.cpp"
+#endif
+
+#ifdef MVK_occlusionquery
+#   include "../examples/occlusionquery/occlusionquery.cpp"
+#endif
+
+// Does not run. Sampler arrays require Metal 2.
+#ifdef MVK_texturemipmapgen
+#   include "../examples/texturemipmapgen/texturemipmapgen.cpp"
+#endif
+
+#ifdef MVK_multisampling
+#   include "../examples/multisampling/multisampling.cpp"
+#endif
+
+#ifdef MVK_shadowmapping
+#   include "../examples/shadowmapping/shadowmapping.cpp"
+#endif
+
+#ifdef MVK_shadowmappingomni
+#   include "../examples/shadowmappingomni/shadowmappingomni.cpp"
+#endif
+
+#ifdef MVK_skeletalanimation
+#   include "../examples/skeletalanimation/skeletalanimation.cpp"
+#endif
+
+#ifdef MVK_bloom
+#   include "../examples/bloom/bloom.cpp"
+#endif
+
+// Runs in Release mode. Debug mode Metal validation will assert
+// UBO buffer length is too short for UBO size declared in shader.
+#ifdef MVK_deferred
+#   include "../examples/deferred/deferred.cpp"
+#endif
+
+// Does not run. Metal does not support geometry shaders.
+#ifdef MVK_deferredshadows
+#   include "../examples/deferredshadows/deferredshadows.cpp"
+#endif
+
+// Runs in Release mode, but does not display content.
+// Metal does not support the use of specialization constants to set array lengths,
+#ifdef MVK_ssao
+#   include "../examples/ssao/ssao.cpp"
+#endif
+
+
+// COMPUTE - Currently unsupported by MoltenVK
+
+
+// TESSELLATION - Currently unsupported by MoltenVK
+
+
+// GEOMETRY SHADER - Unsupported by Metal
+
+
+// EXTENSIONS - Currently unsupported by MoltenVK
+
+
+// MISC
+
+#ifdef MVK_parallaxmapping
+#   include "../examples/parallaxmapping/parallaxmapping.cpp"
+#endif
+
+#ifdef MVK_sphericalenvmapping
+#   include "../examples/sphericalenvmapping/sphericalenvmapping.cpp"
+#endif
+
+#ifdef MVK_gears
+#   include "../examples/gears/gears.cpp"
+#endif
+
+#ifdef MVK_distancefieldfonts
+#   include "../examples/distancefieldfonts/distancefieldfonts.cpp"
+#endif
+
+#ifdef MVK_vulkanscene
+#   include "../examples/vulkanscene/vulkanscene.cpp"
+#endif
+
diff --git a/external/Vulkan/xcode/examples.xcodeproj/project.pbxproj b/external/Vulkan/xcode/examples.xcodeproj/project.pbxproj
new file mode 100644
index 0000000000000000000000000000000000000000..ba6eec408a2ed5b699784c71a02a2f397a835c08
--- /dev/null
+++ b/external/Vulkan/xcode/examples.xcodeproj/project.pbxproj
@@ -0,0 +1,700 @@
+// !$*UTF8*$!
+{
+	archiveVersion = 1;
+	classes = {
+	};
+	objectVersion = 46;
+	objects = {
+
+/* Begin PBXBuildFile section */
+		A951FF171E9C349000FA9144 /* VulkanDebug.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A951FF071E9C349000FA9144 /* VulkanDebug.cpp */; };
+		A951FF181E9C349000FA9144 /* VulkanDebug.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A951FF071E9C349000FA9144 /* VulkanDebug.cpp */; };
+		A951FF191E9C349000FA9144 /* vulkanexamplebase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A951FF0A1E9C349000FA9144 /* vulkanexamplebase.cpp */; };
+		A951FF1A1E9C349000FA9144 /* vulkanexamplebase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A951FF0A1E9C349000FA9144 /* vulkanexamplebase.cpp */; };
+		A951FF1B1E9C349000FA9144 /* VulkanTools.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A951FF131E9C349000FA9144 /* VulkanTools.cpp */; };
+		A951FF1C1E9C349000FA9144 /* VulkanTools.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A951FF131E9C349000FA9144 /* VulkanTools.cpp */; };
+		A9532B761EF99894000A09E2 /* libMoltenVK.dylib in CopyFiles */ = {isa = PBXBuildFile; fileRef = A9532B751EF99894000A09E2 /* libMoltenVK.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
+		A9532B771EF9991A000A09E2 /* libMoltenVK.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = A9532B751EF99894000A09E2 /* libMoltenVK.dylib */; };
+		A9532B781EF99937000A09E2 /* libMoltenVK.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = A9581BAB1EEB64EC00247309 /* libMoltenVK.dylib */; };
+		A9581BA81EEB648C00247309 /* libzlibstatic.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A9581BA71EEB648C00247309 /* libzlibstatic.a */; };
+		A9581BAC1EEB64EC00247309 /* libMoltenVK.dylib in CopyFiles */ = {isa = PBXBuildFile; fileRef = A9581BAB1EEB64EC00247309 /* libMoltenVK.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
+		A98703D91E9D382A0066959C /* data in Resources */ = {isa = PBXBuildFile; fileRef = A98703D81E9D382A0066959C /* data */; };
+		A98703DA1E9D382A0066959C /* data in Resources */ = {isa = PBXBuildFile; fileRef = A98703D81E9D382A0066959C /* data */; };
+		A9B67B781C3AAE9800373FFD /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = A9B67B6C1C3AAE9800373FFD /* AppDelegate.m */; };
+		A9B67B7A1C3AAE9800373FFD /* DemoViewController.mm in Sources */ = {isa = PBXBuildFile; fileRef = A9B67B6F1C3AAE9800373FFD /* DemoViewController.mm */; };
+		A9B67B7C1C3AAE9800373FFD /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = A9B67B711C3AAE9800373FFD /* main.m */; };
+		A9B67B7D1C3AAE9800373FFD /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = A9B67B741C3AAE9800373FFD /* Default-568h@2x.png */; };
+		A9B67B7E1C3AAE9800373FFD /* Default~ipad.png in Resources */ = {isa = PBXBuildFile; fileRef = A9B67B751C3AAE9800373FFD /* Default~ipad.png */; };
+		A9B67B7F1C3AAE9800373FFD /* Icon.png in Resources */ = {isa = PBXBuildFile; fileRef = A9B67B761C3AAE9800373FFD /* Icon.png */; };
+		A9B67B801C3AAE9800373FFD /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A9B67B771C3AAE9800373FFD /* Main.storyboard */; };
+		A9B67B8C1C3AAEA200373FFD /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = A9B67B831C3AAEA200373FFD /* AppDelegate.m */; };
+		A9B67B8D1C3AAEA200373FFD /* DemoViewController.mm in Sources */ = {isa = PBXBuildFile; fileRef = A9B67B851C3AAEA200373FFD /* DemoViewController.mm */; };
+		A9B67B8F1C3AAEA200373FFD /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = A9B67B871C3AAEA200373FFD /* main.m */; };
+		A9B67B901C3AAEA200373FFD /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A9B67B8A1C3AAEA200373FFD /* Main.storyboard */; };
+		A9B67B911C3AAEA200373FFD /* macOS.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A9B67B8B1C3AAEA200373FFD /* macOS.xcassets */; };
+		A9BC9B1C1EE8421F00384233 /* MVKExample.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A9BC9B1A1EE8421F00384233 /* MVKExample.cpp */; };
+		A9BC9B1D1EE8421F00384233 /* MVKExample.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A9BC9B1A1EE8421F00384233 /* MVKExample.cpp */; };
+		C9788FD52044D78D00AB0892 /* VulkanAndroid.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C9788FD32044D78D00AB0892 /* VulkanAndroid.cpp */; };
+		C9A79EF12045045E00696219 /* stb_textedit.h in Sources */ = {isa = PBXBuildFile; fileRef = C9A79EE82045045D00696219 /* stb_textedit.h */; };
+		C9A79EF22045045E00696219 /* imconfig.h in Sources */ = {isa = PBXBuildFile; fileRef = C9A79EE92045045D00696219 /* imconfig.h */; };
+		C9A79EF32045045E00696219 /* imgui_demo.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C9A79EEA2045045D00696219 /* imgui_demo.cpp */; };
+		C9A79EF42045045E00696219 /* imgui_draw.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C9A79EEB2045045D00696219 /* imgui_draw.cpp */; };
+		C9A79EF52045045E00696219 /* imgui_internal.h in Sources */ = {isa = PBXBuildFile; fileRef = C9A79EEC2045045E00696219 /* imgui_internal.h */; };
+		C9A79EF62045045E00696219 /* stb_rect_pack.h in Sources */ = {isa = PBXBuildFile; fileRef = C9A79EED2045045E00696219 /* stb_rect_pack.h */; };
+		C9A79EF72045045E00696219 /* imgui.h in Sources */ = {isa = PBXBuildFile; fileRef = C9A79EEE2045045E00696219 /* imgui.h */; };
+		C9A79EF82045045E00696219 /* stb_truetype.h in Sources */ = {isa = PBXBuildFile; fileRef = C9A79EEF2045045E00696219 /* stb_truetype.h */; };
+		C9A79EF92045045E00696219 /* imgui.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C9A79EF02045045E00696219 /* imgui.cpp */; };
+		C9A79EFC204504E000696219 /* VulkanUIOverlay.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C9A79EFB204504E000696219 /* VulkanUIOverlay.cpp */; };
+		C9A79EFD2045051D00696219 /* VulkanUIOverlay.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C9A79EFB204504E000696219 /* VulkanUIOverlay.cpp */; };
+		C9A79EFE2045051D00696219 /* VulkanUIOverlay.h in Sources */ = {isa = PBXBuildFile; fileRef = C9A79EFA204504E000696219 /* VulkanUIOverlay.h */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXCopyFilesBuildPhase section */
+		A91227BB1E9D5E9D00108018 /* CopyFiles */ = {
+			isa = PBXCopyFilesBuildPhase;
+			buildActionMask = 2147483647;
+			dstPath = "";
+			dstSubfolderSpec = 6;
+			files = (
+				A9581BAC1EEB64EC00247309 /* libMoltenVK.dylib in CopyFiles */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		A9532B741EF9987C000A09E2 /* CopyFiles */ = {
+			isa = PBXCopyFilesBuildPhase;
+			buildActionMask = 2147483647;
+			dstPath = "";
+			dstSubfolderSpec = 6;
+			files = (
+				A9532B761EF99894000A09E2 /* libMoltenVK.dylib in CopyFiles */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXCopyFilesBuildPhase section */
+
+/* Begin PBXFileReference section */
+		1D30AB110D05D00D00671497 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
+		1D3623EB0D0F72F000981E51 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
+		1D6058910D05DD3D006BFB54 /* examples.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = examples.app; sourceTree = BUILT_PRODUCTS_DIR; };
+		2D500B990D5A79CF00DBA0E3 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
+		A92F37071C7E1B2B008F8BC9 /* examples.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = examples.h; sourceTree = "<group>"; };
+		A94A67231B7BDE9B00F6D7C4 /* MetalGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MetalGL.framework; path = ../../MetalGL/macOS/MetalGL.framework; sourceTree = "<group>"; };
+		A94A67241B7BDE9B00F6D7C4 /* MetalGLShaderConverter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MetalGLShaderConverter.framework; path = ../../MetalGLShaderConverter/macOS/MetalGLShaderConverter.framework; sourceTree = "<group>"; };
+		A951FF001E9C349000FA9144 /* camera.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = camera.hpp; sourceTree = "<group>"; };
+		A951FF011E9C349000FA9144 /* frustum.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = frustum.hpp; sourceTree = "<group>"; };
+		A951FF021E9C349000FA9144 /* keycodes.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = keycodes.hpp; sourceTree = "<group>"; };
+		A951FF031E9C349000FA9144 /* threadpool.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = threadpool.hpp; sourceTree = "<group>"; };
+		A951FF061E9C349000FA9144 /* VulkanBuffer.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = VulkanBuffer.hpp; sourceTree = "<group>"; };
+		A951FF071E9C349000FA9144 /* VulkanDebug.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = VulkanDebug.cpp; sourceTree = "<group>"; };
+		A951FF081E9C349000FA9144 /* VulkanDebug.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VulkanDebug.h; sourceTree = "<group>"; };
+		A951FF091E9C349000FA9144 /* VulkanDevice.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = VulkanDevice.hpp; sourceTree = "<group>"; };
+		A951FF0A1E9C349000FA9144 /* vulkanexamplebase.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = vulkanexamplebase.cpp; sourceTree = "<group>"; };
+		A951FF0B1E9C349000FA9144 /* vulkanexamplebase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = vulkanexamplebase.h; sourceTree = "<group>"; };
+		A951FF0C1E9C349000FA9144 /* VulkanFrameBuffer.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = VulkanFrameBuffer.hpp; sourceTree = "<group>"; };
+		A951FF0D1E9C349000FA9144 /* VulkanHeightmap.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = VulkanHeightmap.hpp; sourceTree = "<group>"; };
+		A951FF0E1E9C349000FA9144 /* VulkanInitializers.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = VulkanInitializers.hpp; sourceTree = "<group>"; };
+		A951FF0F1E9C349000FA9144 /* VulkanModel.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = VulkanModel.hpp; sourceTree = "<group>"; };
+		A951FF101E9C349000FA9144 /* VulkanSwapChain.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = VulkanSwapChain.hpp; sourceTree = "<group>"; };
+		A951FF121E9C349000FA9144 /* VulkanTexture.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = VulkanTexture.hpp; sourceTree = "<group>"; };
+		A951FF131E9C349000FA9144 /* VulkanTools.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = VulkanTools.cpp; sourceTree = "<group>"; };
+		A951FF141E9C349000FA9144 /* VulkanTools.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VulkanTools.h; sourceTree = "<group>"; };
+		A9532B751EF99894000A09E2 /* libMoltenVK.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libMoltenVK.dylib; path = MoltenVK/iOS/libMoltenVK.dylib; sourceTree = "<group>"; };
+		A9581BAB1EEB64EC00247309 /* libMoltenVK.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libMoltenVK.dylib; path = MoltenVK/macOS/libMoltenVK.dylib; sourceTree = "<group>"; };
+		A977BCFE1B66BB010067E5BF /* examples.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = examples.app; sourceTree = BUILT_PRODUCTS_DIR; };
+		A977BD211B67186B0067E5BF /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/System/Library/Frameworks/AppKit.framework; sourceTree = DEVELOPER_DIR; };
+		A977BD221B67186B0067E5BF /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/System/Library/Frameworks/CoreGraphics.framework; sourceTree = DEVELOPER_DIR; };
+		A977BD231B67186B0067E5BF /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; };
+		A977BD251B67186B0067E5BF /* Metal.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Metal.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/System/Library/Frameworks/Metal.framework; sourceTree = DEVELOPER_DIR; };
+		A977BD261B67186B0067E5BF /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/System/Library/Frameworks/QuartzCore.framework; sourceTree = DEVELOPER_DIR; };
+		A98703D81E9D382A0066959C /* data */ = {isa = PBXFileReference; lastKnownFileType = folder; name = data; path = ../data; sourceTree = "<group>"; };
+		A9A222171B5D69F40050A5F9 /* Metal.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Metal.framework; path = System/Library/Frameworks/Metal.framework; sourceTree = SDKROOT; };
+		A9B5D09B1CF8830B00D7CBDD /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; };
+		A9B67B6B1C3AAE9800373FFD /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
+		A9B67B6C1C3AAE9800373FFD /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
+		A9B67B6E1C3AAE9800373FFD /* DemoViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DemoViewController.h; sourceTree = "<group>"; };
+		A9B67B6F1C3AAE9800373FFD /* DemoViewController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DemoViewController.mm; sourceTree = "<group>"; };
+		A9B67B701C3AAE9800373FFD /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+		A9B67B711C3AAE9800373FFD /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
+		A9B67B721C3AAE9800373FFD /* Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Prefix.pch; sourceTree = "<group>"; };
+		A9B67B741C3AAE9800373FFD /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = "<group>"; };
+		A9B67B751C3AAE9800373FFD /* Default~ipad.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default~ipad.png"; sourceTree = "<group>"; };
+		A9B67B761C3AAE9800373FFD /* Icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Icon.png; sourceTree = "<group>"; };
+		A9B67B771C3AAE9800373FFD /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = "<group>"; };
+		A9B67B821C3AAEA200373FFD /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
+		A9B67B831C3AAEA200373FFD /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
+		A9B67B841C3AAEA200373FFD /* DemoViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DemoViewController.h; sourceTree = "<group>"; };
+		A9B67B851C3AAEA200373FFD /* DemoViewController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DemoViewController.mm; sourceTree = "<group>"; };
+		A9B67B861C3AAEA200373FFD /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+		A9B67B871C3AAEA200373FFD /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
+		A9B67B881C3AAEA200373FFD /* Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Prefix.pch; sourceTree = "<group>"; };
+		A9B67B8A1C3AAEA200373FFD /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = "<group>"; };
+		A9B67B8B1C3AAEA200373FFD /* macOS.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = macOS.xcassets; sourceTree = "<group>"; };
+		A9B6B7641C0F795D00A9E33A /* CoreAudio.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreAudio.framework; path = System/Library/Frameworks/CoreAudio.framework; sourceTree = SDKROOT; };
+		A9BC9B1A1EE8421F00384233 /* MVKExample.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MVKExample.cpp; sourceTree = "<group>"; };
+		A9BC9B1B1EE8421F00384233 /* MVKExample.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVKExample.h; sourceTree = "<group>"; };
+		A9CDEA271B6A782C00F7B008 /* GLKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GLKit.framework; path = System/Library/Frameworks/GLKit.framework; sourceTree = SDKROOT; };
+		A9E264761B671B0A00FE691A /* libc++.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = "libc++.dylib"; path = "Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk/usr/lib/libc++.dylib"; sourceTree = DEVELOPER_DIR; };
+		C9788FD02044D78D00AB0892 /* benchmark.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = benchmark.hpp; sourceTree = "<group>"; };
+		C9788FD22044D78D00AB0892 /* VulkanAndroid.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VulkanAndroid.h; sourceTree = "<group>"; };
+		C9788FD32044D78D00AB0892 /* VulkanAndroid.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = VulkanAndroid.cpp; sourceTree = "<group>"; };
+		C9A79EE82045045D00696219 /* stb_textedit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = stb_textedit.h; path = ../external/imgui/stb_textedit.h; sourceTree = "<group>"; };
+		C9A79EE92045045D00696219 /* imconfig.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = imconfig.h; path = ../external/imgui/imconfig.h; sourceTree = "<group>"; };
+		C9A79EEA2045045D00696219 /* imgui_demo.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = imgui_demo.cpp; path = ../external/imgui/imgui_demo.cpp; sourceTree = "<group>"; };
+		C9A79EEB2045045D00696219 /* imgui_draw.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = imgui_draw.cpp; path = ../external/imgui/imgui_draw.cpp; sourceTree = "<group>"; };
+		C9A79EEC2045045E00696219 /* imgui_internal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = imgui_internal.h; path = ../external/imgui/imgui_internal.h; sourceTree = "<group>"; };
+		C9A79EED2045045E00696219 /* stb_rect_pack.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = stb_rect_pack.h; path = ../external/imgui/stb_rect_pack.h; sourceTree = "<group>"; };
+		C9A79EEE2045045E00696219 /* imgui.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = imgui.h; path = ../external/imgui/imgui.h; sourceTree = "<group>"; };
+		C9A79EEF2045045E00696219 /* stb_truetype.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = stb_truetype.h; path = ../external/imgui/stb_truetype.h; sourceTree = "<group>"; };
+		C9A79EF02045045E00696219 /* imgui.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = imgui.cpp; path = ../external/imgui/imgui.cpp; sourceTree = "<group>"; };
+		C9A79EFA204504E000696219 /* VulkanUIOverlay.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VulkanUIOverlay.h; sourceTree = "<group>"; };
+		C9A79EFB204504E000696219 /* VulkanUIOverlay.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = VulkanUIOverlay.cpp; sourceTree = "<group>"; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+		1D60588F0D05DD3D006BFB54 /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				A9532B771EF9991A000A09E2 /* libMoltenVK.dylib in Frameworks */,
+				A9581BA81EEB648C00247309 /* libzlibstatic.a in Frameworks */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		A977BCF11B66BB010067E5BF /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				A9532B781EF99937000A09E2 /* libMoltenVK.dylib in Frameworks */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+		19C28FACFE9D520D11CA2CBB /* Products */ = {
+			isa = PBXGroup;
+			children = (
+				1D6058910D05DD3D006BFB54 /* examples.app */,
+				A977BCFE1B66BB010067E5BF /* examples.app */,
+			);
+			name = Products;
+			sourceTree = "<group>";
+		};
+		29B97314FDCFA39411CA2CEA /* CustomTemplate */ = {
+			isa = PBXGroup;
+			children = (
+				C9A79EE92045045D00696219 /* imconfig.h */,
+				C9A79EEA2045045D00696219 /* imgui_demo.cpp */,
+				C9A79EEB2045045D00696219 /* imgui_draw.cpp */,
+				C9A79EEC2045045E00696219 /* imgui_internal.h */,
+				C9A79EF02045045E00696219 /* imgui.cpp */,
+				C9A79EEE2045045E00696219 /* imgui.h */,
+				C9A79EED2045045E00696219 /* stb_rect_pack.h */,
+				C9A79EE82045045D00696219 /* stb_textedit.h */,
+				C9A79EEF2045045E00696219 /* stb_truetype.h */,
+				A92F37071C7E1B2B008F8BC9 /* examples.h */,
+				A9BC9B1B1EE8421F00384233 /* MVKExample.h */,
+				A9BC9B1A1EE8421F00384233 /* MVKExample.cpp */,
+				A951FEFF1E9C349000FA9144 /* base */,
+				A98703D81E9D382A0066959C /* data */,
+				A9B67B6A1C3AAE9800373FFD /* iOS */,
+				A9B67B811C3AAEA200373FFD /* macOS */,
+				29B97323FDCFA39411CA2CEA /* Frameworks */,
+				19C28FACFE9D520D11CA2CBB /* Products */,
+			);
+			name = CustomTemplate;
+			sourceTree = "<group>";
+		};
+		29B97323FDCFA39411CA2CEA /* Frameworks */ = {
+			isa = PBXGroup;
+			children = (
+				A9581BAB1EEB64EC00247309 /* libMoltenVK.dylib */,
+				A9532B751EF99894000A09E2 /* libMoltenVK.dylib */,
+				A9581BA71EEB648C00247309 /* libzlibstatic.a */,
+				A9B5D09B1CF8830B00D7CBDD /* libc++.tbd */,
+				A9B6B7641C0F795D00A9E33A /* CoreAudio.framework */,
+				A9ADEC601B6EC2EB00DBA48C /* iOS */,
+				A9ADEC611B6EC2F300DBA48C /* macOS */,
+			);
+			name = Frameworks;
+			sourceTree = "<group>";
+		};
+		A951FEFF1E9C349000FA9144 /* base */ = {
+			isa = PBXGroup;
+			children = (
+				C9A79EFB204504E000696219 /* VulkanUIOverlay.cpp */,
+				C9A79EFA204504E000696219 /* VulkanUIOverlay.h */,
+				C9788FD02044D78D00AB0892 /* benchmark.hpp */,
+				C9788FD32044D78D00AB0892 /* VulkanAndroid.cpp */,
+				C9788FD22044D78D00AB0892 /* VulkanAndroid.h */,
+				A951FF001E9C349000FA9144 /* camera.hpp */,
+				A951FF011E9C349000FA9144 /* frustum.hpp */,
+				A951FF021E9C349000FA9144 /* keycodes.hpp */,
+				A951FF031E9C349000FA9144 /* threadpool.hpp */,
+				A951FF061E9C349000FA9144 /* VulkanBuffer.hpp */,
+				A951FF071E9C349000FA9144 /* VulkanDebug.cpp */,
+				A951FF081E9C349000FA9144 /* VulkanDebug.h */,
+				A951FF091E9C349000FA9144 /* VulkanDevice.hpp */,
+				A951FF0A1E9C349000FA9144 /* vulkanexamplebase.cpp */,
+				A951FF0B1E9C349000FA9144 /* vulkanexamplebase.h */,
+				A951FF0C1E9C349000FA9144 /* VulkanFrameBuffer.hpp */,
+				A951FF0D1E9C349000FA9144 /* VulkanHeightmap.hpp */,
+				A951FF0E1E9C349000FA9144 /* VulkanInitializers.hpp */,
+				A951FF0F1E9C349000FA9144 /* VulkanModel.hpp */,
+				A951FF101E9C349000FA9144 /* VulkanSwapChain.hpp */,
+				A951FF121E9C349000FA9144 /* VulkanTexture.hpp */,
+				A951FF131E9C349000FA9144 /* VulkanTools.cpp */,
+				A951FF141E9C349000FA9144 /* VulkanTools.h */,
+			);
+			name = base;
+			path = ../base;
+			sourceTree = "<group>";
+		};
+		A9ADEC601B6EC2EB00DBA48C /* iOS */ = {
+			isa = PBXGroup;
+			children = (
+				A9A222171B5D69F40050A5F9 /* Metal.framework */,
+				1D3623EB0D0F72F000981E51 /* CoreGraphics.framework */,
+				2D500B990D5A79CF00DBA0E3 /* QuartzCore.framework */,
+				1D30AB110D05D00D00671497 /* Foundation.framework */,
+				A9CDEA271B6A782C00F7B008 /* GLKit.framework */,
+			);
+			name = iOS;
+			sourceTree = "<group>";
+		};
+		A9ADEC611B6EC2F300DBA48C /* macOS */ = {
+			isa = PBXGroup;
+			children = (
+				A94A67231B7BDE9B00F6D7C4 /* MetalGL.framework */,
+				A94A67241B7BDE9B00F6D7C4 /* MetalGLShaderConverter.framework */,
+				A9E264761B671B0A00FE691A /* libc++.dylib */,
+				A977BD251B67186B0067E5BF /* Metal.framework */,
+				A977BD261B67186B0067E5BF /* QuartzCore.framework */,
+				A977BD211B67186B0067E5BF /* AppKit.framework */,
+				A977BD221B67186B0067E5BF /* CoreGraphics.framework */,
+				A977BD231B67186B0067E5BF /* Foundation.framework */,
+			);
+			name = macOS;
+			sourceTree = "<group>";
+		};
+		A9B67B6A1C3AAE9800373FFD /* iOS */ = {
+			isa = PBXGroup;
+			children = (
+				A9B67B6B1C3AAE9800373FFD /* AppDelegate.h */,
+				A9B67B6C1C3AAE9800373FFD /* AppDelegate.m */,
+				A9B67B6E1C3AAE9800373FFD /* DemoViewController.h */,
+				A9B67B6F1C3AAE9800373FFD /* DemoViewController.mm */,
+				A9B67B701C3AAE9800373FFD /* Info.plist */,
+				A9B67B711C3AAE9800373FFD /* main.m */,
+				A9B67B721C3AAE9800373FFD /* Prefix.pch */,
+				A9B67B731C3AAE9800373FFD /* Resources */,
+			);
+			path = iOS;
+			sourceTree = "<group>";
+		};
+		A9B67B731C3AAE9800373FFD /* Resources */ = {
+			isa = PBXGroup;
+			children = (
+				A9B67B741C3AAE9800373FFD /* Default-568h@2x.png */,
+				A9B67B751C3AAE9800373FFD /* Default~ipad.png */,
+				A9B67B761C3AAE9800373FFD /* Icon.png */,
+				A9B67B771C3AAE9800373FFD /* Main.storyboard */,
+			);
+			path = Resources;
+			sourceTree = "<group>";
+		};
+		A9B67B811C3AAEA200373FFD /* macOS */ = {
+			isa = PBXGroup;
+			children = (
+				A9B67B821C3AAEA200373FFD /* AppDelegate.h */,
+				A9B67B831C3AAEA200373FFD /* AppDelegate.m */,
+				A9B67B841C3AAEA200373FFD /* DemoViewController.h */,
+				A9B67B851C3AAEA200373FFD /* DemoViewController.mm */,
+				A9B67B861C3AAEA200373FFD /* Info.plist */,
+				A9B67B871C3AAEA200373FFD /* main.m */,
+				A9B67B881C3AAEA200373FFD /* Prefix.pch */,
+				A9B67B891C3AAEA200373FFD /* Resources */,
+			);
+			path = macOS;
+			sourceTree = "<group>";
+		};
+		A9B67B891C3AAEA200373FFD /* Resources */ = {
+			isa = PBXGroup;
+			children = (
+				A9B67B8A1C3AAEA200373FFD /* Main.storyboard */,
+				A9B67B8B1C3AAEA200373FFD /* macOS.xcassets */,
+			);
+			path = Resources;
+			sourceTree = "<group>";
+		};
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+		1D6058900D05DD3D006BFB54 /* examples-ios */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = 1D6058960D05DD3E006BFB54 /* Build configuration list for PBXNativeTarget "examples-ios" */;
+			buildPhases = (
+				1D60588D0D05DD3D006BFB54 /* Resources */,
+				1D60588E0D05DD3D006BFB54 /* Sources */,
+				1D60588F0D05DD3D006BFB54 /* Frameworks */,
+				A9532B741EF9987C000A09E2 /* CopyFiles */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+			);
+			name = "examples-ios";
+			productName = foo;
+			productReference = 1D6058910D05DD3D006BFB54 /* examples.app */;
+			productType = "com.apple.product-type.application";
+		};
+		A977BCBD1B66BB010067E5BF /* examples-macos */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = A977BCFB1B66BB010067E5BF /* Build configuration list for PBXNativeTarget "examples-macos" */;
+			buildPhases = (
+				A977BCBE1B66BB010067E5BF /* Resources */,
+				A977BCC91B66BB010067E5BF /* Sources */,
+				A977BCF11B66BB010067E5BF /* Frameworks */,
+				A91227BB1E9D5E9D00108018 /* CopyFiles */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+			);
+			name = "examples-macos";
+			productName = foo;
+			productReference = A977BCFE1B66BB010067E5BF /* examples.app */;
+			productType = "com.apple.product-type.application";
+		};
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+		29B97313FDCFA39411CA2CEA /* Project object */ = {
+			isa = PBXProject;
+			attributes = {
+				LastUpgradeCheck = 0920;
+				TargetAttributes = {
+					1D6058900D05DD3D006BFB54 = {
+						DevelopmentTeam = VU3TCKU48B;
+					};
+					A977BCBD1B66BB010067E5BF = {
+						DevelopmentTeam = VU3TCKU48B;
+					};
+				};
+			};
+			buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "examples" */;
+			compatibilityVersion = "Xcode 3.2";
+			developmentRegion = English;
+			hasScannedForEncodings = 1;
+			knownRegions = (
+				English,
+				Japanese,
+				French,
+				German,
+			);
+			mainGroup = 29B97314FDCFA39411CA2CEA /* CustomTemplate */;
+			projectDirPath = "";
+			projectRoot = "";
+			targets = (
+				1D6058900D05DD3D006BFB54 /* examples-ios */,
+				A977BCBD1B66BB010067E5BF /* examples-macos */,
+			);
+		};
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+		1D60588D0D05DD3D006BFB54 /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				A9B67B7F1C3AAE9800373FFD /* Icon.png in Resources */,
+				A9B67B801C3AAE9800373FFD /* Main.storyboard in Resources */,
+				A9B67B7E1C3AAE9800373FFD /* Default~ipad.png in Resources */,
+				A9B67B7D1C3AAE9800373FFD /* Default-568h@2x.png in Resources */,
+				A98703D91E9D382A0066959C /* data in Resources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		A977BCBE1B66BB010067E5BF /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				A9B67B911C3AAEA200373FFD /* macOS.xcassets in Resources */,
+				A98703DA1E9D382A0066959C /* data in Resources */,
+				A9B67B901C3AAEA200373FFD /* Main.storyboard in Resources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+		1D60588E0D05DD3D006BFB54 /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				A951FF171E9C349000FA9144 /* VulkanDebug.cpp in Sources */,
+				A9BC9B1C1EE8421F00384233 /* MVKExample.cpp in Sources */,
+				C9A79EFC204504E000696219 /* VulkanUIOverlay.cpp in Sources */,
+				A9B67B7A1C3AAE9800373FFD /* DemoViewController.mm in Sources */,
+				A9B67B781C3AAE9800373FFD /* AppDelegate.m in Sources */,
+				C9788FD52044D78D00AB0892 /* VulkanAndroid.cpp in Sources */,
+				A951FF1B1E9C349000FA9144 /* VulkanTools.cpp in Sources */,
+				A951FF191E9C349000FA9144 /* vulkanexamplebase.cpp in Sources */,
+				A9B67B7C1C3AAE9800373FFD /* main.m in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		A977BCC91B66BB010067E5BF /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				C9A79EFD2045051D00696219 /* VulkanUIOverlay.cpp in Sources */,
+				C9A79EFE2045051D00696219 /* VulkanUIOverlay.h in Sources */,
+				C9A79EF12045045E00696219 /* stb_textedit.h in Sources */,
+				C9A79EF22045045E00696219 /* imconfig.h in Sources */,
+				C9A79EF32045045E00696219 /* imgui_demo.cpp in Sources */,
+				C9A79EF42045045E00696219 /* imgui_draw.cpp in Sources */,
+				C9A79EF52045045E00696219 /* imgui_internal.h in Sources */,
+				C9A79EF62045045E00696219 /* stb_rect_pack.h in Sources */,
+				C9A79EF72045045E00696219 /* imgui.h in Sources */,
+				C9A79EF82045045E00696219 /* stb_truetype.h in Sources */,
+				C9A79EF92045045E00696219 /* imgui.cpp in Sources */,
+				A951FF181E9C349000FA9144 /* VulkanDebug.cpp in Sources */,
+				A9BC9B1D1EE8421F00384233 /* MVKExample.cpp in Sources */,
+				A9B67B8C1C3AAEA200373FFD /* AppDelegate.m in Sources */,
+				A9B67B8F1C3AAEA200373FFD /* main.m in Sources */,
+				A951FF1C1E9C349000FA9144 /* VulkanTools.cpp in Sources */,
+				A951FF1A1E9C349000FA9144 /* vulkanexamplebase.cpp in Sources */,
+				A9B67B8D1C3AAEA200373FFD /* DemoViewController.mm in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXSourcesBuildPhase section */
+
+/* Begin XCBuildConfiguration section */
+		1D6058940D05DD3E006BFB54 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				CODE_SIGN_IDENTITY = "iPhone Developer";
+				DEVELOPMENT_TEAM = VU3TCKU48B;
+				GCC_PREFIX_HEADER = "$(SRCROOT)/iOS/Prefix.pch";
+				GCC_PREPROCESSOR_DEFINITIONS = (
+					"$(inherited)",
+					"DEBUG=1",
+					_DEBUG,
+					VK_USE_PLATFORM_IOS_MVK,
+				);
+				GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
+				INFOPLIST_FILE = "$(SRCROOT)/iOS/Info.plist";
+				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+				LD_RUNPATH_SEARCH_PATHS = "@executable_path";
+				LIBRARY_SEARCH_PATHS = (
+					"\"$(SRCROOT)/MoltenVK/iOS\"",
+				);
+				SDKROOT = iphoneos;
+				TARGETED_DEVICE_FAMILY = "1,2";
+				VALID_ARCHS = arm64;
+			};
+			name = Debug;
+		};
+		1D6058950D05DD3E006BFB54 /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				CODE_SIGN_IDENTITY = "iPhone Developer";
+				DEVELOPMENT_TEAM = VU3TCKU48B;
+				GCC_PREFIX_HEADER = "$(SRCROOT)/iOS/Prefix.pch";
+				GCC_PREPROCESSOR_DEFINITIONS = (
+					"$(inherited)",
+					VK_USE_PLATFORM_IOS_MVK,
+				);
+				GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
+				INFOPLIST_FILE = "$(SRCROOT)/iOS/Info.plist";
+				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+				LD_RUNPATH_SEARCH_PATHS = "@executable_path";
+				LIBRARY_SEARCH_PATHS = (
+					"\"$(SRCROOT)/MoltenVK/iOS\"",
+				);
+				SDKROOT = iphoneos;
+				TARGETED_DEVICE_FAMILY = "1,2";
+				VALID_ARCHS = arm64;
+			};
+			name = Release;
+		};
+		A977BCFC1B66BB010067E5BF /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ARCHS = "$(ARCHS_STANDARD)";
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				COMBINE_HIDPI_IMAGES = YES;
+				GCC_PREFIX_HEADER = "$(SRCROOT)/macOS/Prefix.pch";
+				GCC_PREPROCESSOR_DEFINITIONS = (
+					"DEBUG=1",
+					_DEBUG,
+					VK_USE_PLATFORM_MACOS_MVK,
+				);
+				"GCC_PREPROCESSOR_DEFINITIONS[arch=*]" = (
+					"DEBUG=1",
+					_DEBUG,
+					VK_USE_PLATFORM_MACOS_MVK,
+				);
+				INFOPLIST_FILE = "$(SRCROOT)/macOS/Info.plist";
+				LD_RUNPATH_SEARCH_PATHS = "@executable_path";
+				LIBRARY_SEARCH_PATHS = (
+					"\"$(SRCROOT)/MoltenVK/macOS\"",
+					"$(PROJECT_DIR)/MoltenVK/macOS",
+				);
+				MACOSX_DEPLOYMENT_TARGET = 10.11;
+				SDKROOT = macosx;
+			};
+			name = Debug;
+		};
+		A977BCFD1B66BB010067E5BF /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ARCHS = "$(ARCHS_STANDARD)";
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				COMBINE_HIDPI_IMAGES = YES;
+				GCC_PREFIX_HEADER = "$(SRCROOT)/macOS/Prefix.pch";
+				GCC_PREPROCESSOR_DEFINITIONS = VK_USE_PLATFORM_MACOS_MVK;
+				"GCC_PREPROCESSOR_DEFINITIONS[arch=*]" = VK_USE_PLATFORM_MACOS_MVK;
+				INFOPLIST_FILE = "$(SRCROOT)/macOS/Info.plist";
+				LD_RUNPATH_SEARCH_PATHS = "@executable_path";
+				LIBRARY_SEARCH_PATHS = (
+					"\"$(SRCROOT)/MoltenVK/macOS\"",
+					"$(PROJECT_DIR)/MoltenVK/macOS",
+				);
+				MACOSX_DEPLOYMENT_TARGET = 10.11;
+				SDKROOT = macosx;
+			};
+			name = Release;
+		};
+		C01FCF4F08A954540054247B /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
+				CLANG_CXX_LANGUAGE_STANDARD = "c++0x";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = NO;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_COMMA = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				COPY_PHASE_STRIP = NO;
+				ENABLE_BITCODE = NO;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				ENABLE_TESTABILITY = YES;
+				GCC_C_LANGUAGE_STANDARD = c99;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_OPTIMIZATION_LEVEL = 0;
+				GCC_PRECOMPILE_PREFIX_HEADER = YES;
+				GCC_PREPROCESSOR_DEFINITIONS = MVK_vulkanscene;
+				GCC_SYMBOLS_PRIVATE_EXTERN = NO;
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				HEADER_SEARCH_PATHS = (
+					"\"$(SRCROOT)/MoltenVK/include\"",
+					"\"$(SRCROOT)/../external\"/**",
+				);
+				ONLY_ACTIVE_ARCH = YES;
+				PRODUCT_BUNDLE_IDENTIFIER = "com.moltenvk.${PRODUCT_NAME:identifier}";
+				PRODUCT_NAME = "${PROJECT}";
+			};
+			name = Debug;
+		};
+		C01FCF5008A954540054247B /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
+				CLANG_CXX_LANGUAGE_STANDARD = "c++0x";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = NO;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_COMMA = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				ENABLE_BITCODE = NO;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				GCC_C_LANGUAGE_STANDARD = c99;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_PRECOMPILE_PREFIX_HEADER = YES;
+				GCC_PREPROCESSOR_DEFINITIONS = MVK_vulkanscene;
+				GCC_SYMBOLS_PRIVATE_EXTERN = NO;
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				HEADER_SEARCH_PATHS = (
+					"\"$(SRCROOT)/MoltenVK/include\"",
+					"\"$(SRCROOT)/../external\"/**",
+				);
+				PRODUCT_BUNDLE_IDENTIFIER = "com.moltenvk.${PRODUCT_NAME:identifier}";
+				PRODUCT_NAME = "${PROJECT}";
+			};
+			name = Release;
+		};
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+		1D6058960D05DD3E006BFB54 /* Build configuration list for PBXNativeTarget "examples-ios" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				1D6058940D05DD3E006BFB54 /* Debug */,
+				1D6058950D05DD3E006BFB54 /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		A977BCFB1B66BB010067E5BF /* Build configuration list for PBXNativeTarget "examples-macos" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				A977BCFC1B66BB010067E5BF /* Debug */,
+				A977BCFD1B66BB010067E5BF /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		C01FCF4E08A954540054247B /* Build configuration list for PBXProject "examples" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				C01FCF4F08A954540054247B /* Debug */,
+				C01FCF5008A954540054247B /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+/* End XCConfigurationList section */
+	};
+	rootObject = 29B97313FDCFA39411CA2CEA /* Project object */;
+}
diff --git a/external/Vulkan/xcode/examples.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/external/Vulkan/xcode/examples.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000000000000000000000000000000000000..b4eb44440ec8db6ac3e83cbe9ecd731ca047ba90
--- /dev/null
+++ b/external/Vulkan/xcode/examples.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,6342 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+   version = "1.0">
+   <Group
+      location = "group:../../external"
+      name = "external">
+      <Group
+         location = "group:imgui"
+         name = "imgui">
+         <FileRef
+            location = "group:imgui.h">
+         </FileRef>
+         <FileRef
+            location = "group:imconfig.h">
+         </FileRef>
+         <FileRef
+            location = "group:LICENSE">
+         </FileRef>
+         <FileRef
+            location = "group:stb_rect_pack.h">
+         </FileRef>
+         <FileRef
+            location = "group:README.md">
+         </FileRef>
+         <FileRef
+            location = "group:stb_truetype.h">
+         </FileRef>
+         <FileRef
+            location = "group:imgui.cpp">
+         </FileRef>
+         <FileRef
+            location = "group:imgui_internal.h">
+         </FileRef>
+         <FileRef
+            location = "group:stb_textedit.h">
+         </FileRef>
+         <FileRef
+            location = "group:imgui_demo.cpp">
+         </FileRef>
+         <FileRef
+            location = "group:imgui_draw.cpp">
+         </FileRef>
+      </Group>
+      <Group
+         location = "group:gli"
+         name = "gli">
+         <FileRef
+            location = "group:CMakeLists.txt">
+         </FileRef>
+         <Group
+            location = "group:test"
+            name = "test">
+            <FileRef
+               location = "group:bug.cpp">
+            </FileRef>
+            <FileRef
+               location = "group:CMakeLists.txt">
+            </FileRef>
+            <Group
+               location = "group:core"
+               name = "core">
+               <FileRef
+                  location = "group:core_sampler2d.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_texture_1d_array.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_convert_access.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_fetch.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_generate_mipmaps.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:convert_sampler2d_array.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_sample.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_load_ktx.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_filter_1d.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:generate_mipmaps_sampler2d_array.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:CMakeLists.txt">
+               </FileRef>
+               <FileRef
+                  location = "group:core_texture.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_load_gen_1d.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_swizzle.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:generate_mipmaps_sampler1d.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_texture_cube_array.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_load.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:texture_lod_sampler_cube.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:convert_sampler_cube_array.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_storage.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_image.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:test_duplicate.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:texture_lod_sampler2d.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:texture_lod_sampler2d_array.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:convert_sampler3d.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_texture_3d.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gl.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_format.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_load_gen_1d_array.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_texture_2d.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:convert_sampler2d.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:test_size.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:texture_lod_sampler3d.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:test_make_texture.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_convert.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_sampler_clear.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_load_gen.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_load_gen_2d_array.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_load_gen_cube.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:transform.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_texture_1d.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_sampler_texel.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:convert_sampler1d.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_comparison.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:test_copy.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:convert_sampler_cube.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:texture_lod_sampler1d_array.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_flip.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:texture_lod_sampler1d.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:generate_mipmaps_sampler2d.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_texture_cube.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_sampler_wrap.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:generate_mipmaps_sampler1d_array.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_filter_2d.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_save.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:reduce.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_load_gen_2d.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_load_dds.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:test_copy_sub.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_load_gen_3d.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:texture_lod_sampler_cube_array.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_load_gen_cube_array.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_filter_3d.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:generate_mipmaps_sampler_cube_array.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_addressing.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:convert_sampler1d_array.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_clear.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:generate_mipmaps_sampler_cube.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_texture_2d_array.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_load_gen_rect.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:test_view.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:generate_mipmaps_sampler3d.cpp">
+               </FileRef>
+            </Group>
+            <FileRef
+               location = "group:core.cpp">
+            </FileRef>
+            <Group
+               location = "group:bug"
+               name = "bug">
+               <FileRef
+                  location = "group:bug.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:CMakeLists.txt">
+               </FileRef>
+            </Group>
+            <FileRef
+               location = "group:bug.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:main.cpp">
+            </FileRef>
+            <FileRef
+               location = "group:core.hpp">
+            </FileRef>
+         </Group>
+         <Group
+            location = "group:cmake"
+            name = "cmake">
+            <FileRef
+               location = "group:gliConfig.cmake.in">
+            </FileRef>
+            <FileRef
+               location = "group:gliBuildConfig.cmake.in">
+            </FileRef>
+            <FileRef
+               location = "group:GNUInstallDirs.cmake">
+            </FileRef>
+            <FileRef
+               location = "group:CMakePackageConfigHelpers.cmake">
+            </FileRef>
+         </Group>
+         <Group
+            location = "group:util"
+            name = "util">
+            <FileRef
+               location = "group:FindGLI.cmake">
+            </FileRef>
+         </Group>
+         <Group
+            location = "group:gli"
+            name = "gli">
+            <FileRef
+               location = "group:texture2d.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:sampler_cube_array.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:texture1d_array.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:load.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:sampler2d.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:generate_mipmaps.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:levels.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:reduce.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:CMakeLists.txt">
+            </FileRef>
+            <Group
+               location = "group:core"
+               name = "core">
+               <FileRef
+                  location = "group:dx.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:comparison.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:mipmaps_compute.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:sampler2d_array.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:load_kmg.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:save_ktx.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:storage_linear.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:load_dds.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:clear.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:sampler_cube.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:transform.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:file.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:duplicate.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:storage_linear.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:texture3d.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:file.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:clear.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:sampler3d.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:load.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:sampler2d.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:texture1d_array.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:texture2d.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:sampler_cube_array.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:filter_compute.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:reduce.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:coord.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:levels.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:generate_mipmaps.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:format.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:save.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:flip.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:texture2d_array.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:image.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:copy.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:convert.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:sampler1d.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:dummy.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:texture1d.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:texture_cube.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:convert_func.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:s3tc.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:storage.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:bc.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:filter.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:s3tc.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:sampler.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:texture_cube_array.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:sampler1d_array.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:texture.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:filter.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:view.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:workaround.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:bc.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:storage.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:save_dds.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:flip.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:make_texture.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:gl.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:load_ktx.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:save_kmg.inl">
+               </FileRef>
+            </Group>
+            <FileRef
+               location = "group:sampler3d.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:type.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:texture3d.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:save_ktx.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:gli.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:load_kmg.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:sampler2d_array.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:transform.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:duplicate.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:clear.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:sampler_cube.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:load_dds.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:target.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:comparison.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:dx.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:make_texture.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:save_dds.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:save_kmg.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:load_ktx.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:gl.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:sampler1d_array.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:texture.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:texture_cube_array.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:sampler.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:view.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:texture_cube.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:copy.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:image.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:texture2d_array.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:format.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:save.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:texture1d.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:convert.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:sampler1d.hpp">
+            </FileRef>
+         </Group>
+         <FileRef
+            location = "group:readme.md">
+         </FileRef>
+         <Group
+            location = "group:doc"
+            name = "doc">
+            <Group
+               location = "group:spec"
+               name = "spec">
+               <FileRef
+                  location = "group:Khronos-App.css">
+               </FileRef>
+               <FileRef
+                  location = "group:index.html">
+               </FileRef>
+               <FileRef
+                  location = "group:KhronosGroup-3D.png">
+               </FileRef>
+               <FileRef
+                  location = "group:logo-spec.png">
+               </FileRef>
+               <FileRef
+                  location = "group:logo-WD.png">
+               </FileRef>
+               <FileRef
+                  location = "group:default.css">
+               </FileRef>
+               <FileRef
+                  location = "group:generateTOC.js">
+               </FileRef>
+               <FileRef
+                  location = "group:Khronos-WD.css">
+               </FileRef>
+               <FileRef
+                  location = "group:jquery-1.3.2.min.js">
+               </FileRef>
+            </Group>
+            <Group
+               location = "group:manual"
+               name = "manual">
+               <FileRef
+                  location = "group:frontpage1.png">
+               </FileRef>
+               <FileRef
+                  location = "group:g-truc.png">
+               </FileRef>
+               <FileRef
+                  location = "group:frontpage2.png">
+               </FileRef>
+               <FileRef
+                  location = "group:logo-mini.png">
+               </FileRef>
+            </Group>
+            <Group
+               location = "group:theme"
+               name = "theme">
+               <FileRef
+                  location = "group:doxygen.css">
+               </FileRef>
+               <FileRef
+                  location = "group:tabs.css">
+               </FileRef>
+            </Group>
+            <Group
+               location = "group:api"
+               name = "api">
+               <FileRef
+                  location = "group:a00072_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00011.png">
+               </FileRef>
+               <FileRef
+                  location = "group:a00005.png">
+               </FileRef>
+               <FileRef
+                  location = "group:dir_934f46a345653ef2b3014a1b37a162c1.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00019.html">
+               </FileRef>
+               <FileRef
+                  location = "group:splitbar.png">
+               </FileRef>
+               <FileRef
+                  location = "group:namespacemembers_enum.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00058.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00030_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00023.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00040_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00074.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00062.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00004.png">
+               </FileRef>
+               <FileRef
+                  location = "group:a00010.png">
+               </FileRef>
+               <FileRef
+                  location = "group:a00035.html">
+               </FileRef>
+               <FileRef
+                  location = "group:doxygen.css">
+               </FileRef>
+               <FileRef
+                  location = "group:a00042.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00006.png">
+               </FileRef>
+               <FileRef
+                  location = "group:a00012.png">
+               </FileRef>
+               <FileRef
+                  location = "group:a00015.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00025_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00003.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00055_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00054.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00049_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00097.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00039_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00067_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00081.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00013.png">
+               </FileRef>
+               <FileRef
+                  location = "group:a00007.png">
+               </FileRef>
+               <FileRef
+                  location = "group:a00039.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00038.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00050_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00017.png">
+               </FileRef>
+               <FileRef
+                  location = "group:a00080.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00020_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00096.html">
+               </FileRef>
+               <FileRef
+                  location = "group:arrowright.png">
+               </FileRef>
+               <FileRef
+                  location = "group:a00079.html">
+               </FileRef>
+               <FileRef
+                  location = "group:index.html">
+               </FileRef>
+               <FileRef
+                  location = "group:functions.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00055.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00002.html">
+               </FileRef>
+               <FileRef
+                  location = "group:sync_off.png">
+               </FileRef>
+               <FileRef
+                  location = "group:a00062_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00014.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00043.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00016.png">
+               </FileRef>
+               <FileRef
+                  location = "group:namespacemembers_func.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00029_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00014.png">
+               </FileRef>
+               <FileRef
+                  location = "group:a00034.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00059_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00063.html">
+               </FileRef>
+               <FileRef
+                  location = "group:sync_on.png">
+               </FileRef>
+               <FileRef
+                  location = "group:a00022.html">
+               </FileRef>
+               <FileRef
+                  location = "group:dir_2c398834837487e57bd8c53ea39543b9.html">
+               </FileRef>
+               <FileRef
+                  location = "group:annotated.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00045_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00018.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00015.png">
+               </FileRef>
+               <FileRef
+                  location = "group:a00035_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:doc.png">
+               </FileRef>
+               <FileRef
+                  location = "group:a00046_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:hierarchy.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00036_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:arrowdown.png">
+               </FileRef>
+               <FileRef
+                  location = "group:a00013.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00068_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00005.html">
+               </FileRef>
+               <FileRef
+                  location = "group:bc_s.png">
+               </FileRef>
+               <FileRef
+                  location = "group:nav_g.png">
+               </FileRef>
+               <FileRef
+                  location = "group:a00052.html">
+               </FileRef>
+               <FileRef
+                  location = "group:nav_f.png">
+               </FileRef>
+               <FileRef
+                  location = "group:a00029.html">
+               </FileRef>
+               <FileRef
+                  location = "group:tabs.css">
+               </FileRef>
+               <FileRef
+                  location = "group:a00091.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00087.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00068.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00048.html">
+               </FileRef>
+               <FileRef
+                  location = "group:closed.png">
+               </FileRef>
+               <FileRef
+                  location = "group:a00009.html">
+               </FileRef>
+               <FileRef
+                  location = "group:doxygen.png">
+               </FileRef>
+               <FileRef
+                  location = "group:a00061_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00072.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00064.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00053_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:tab_s.png">
+               </FileRef>
+               <FileRef
+                  location = "group:a00033.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00023_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00064_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00065.html">
+               </FileRef>
+               <FileRef
+                  location = "group:dir_98f7f9d41f9d3029bd68cf237526a774.html">
+               </FileRef>
+               <FileRef
+                  location = "group:files.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00026_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00008.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00056_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:tab_a.png">
+               </FileRef>
+               <FileRef
+                  location = "group:a00049.html">
+               </FileRef>
+               <FileRef
+                  location = "group:functions_func.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00086.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00069.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00090.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00033_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00028.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00043_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00053.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00004.html">
+               </FileRef>
+               <FileRef
+                  location = "group:namespaces.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00012.html">
+               </FileRef>
+               <FileRef
+                  location = "group:tab_b.png">
+               </FileRef>
+               <FileRef
+                  location = "group:a00071_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:bdwn.png">
+               </FileRef>
+               <FileRef
+                  location = "group:a00085.html">
+               </FileRef>
+               <Group
+                  location = "group:search"
+                  name = "search">
+                  <FileRef
+                     location = "group:enums_2.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_3.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:searchdata.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_6.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_6.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_4.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:classes_1.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_9.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_d.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_a.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:search_r.png">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_7.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_a.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_8.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_1.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_c.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_2.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:enums_2.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_e.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_6.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:enums_3.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_3.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_a.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_b.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_9.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_0.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_d.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:enums_3.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_2.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_7.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_8.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:classes_0.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:search.css">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_5.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_7.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:classes_1.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_7.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_e.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_9.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_3.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_0.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_8.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_2.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_7.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:namespaces_0.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:classes_4.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_9.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_a.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_3.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_a.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_1.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_6.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:classes_0.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_8.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_d.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_6.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_2.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_5.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_5.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:classes_3.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_9.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_0.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_1.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_2.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:classes_2.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_3.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_8.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_1.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:classes_4.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_0.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_d.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_4.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_4.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:namespaces_0.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_5.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_0.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_b.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:classes_3.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_6.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_4.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:enums_0.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:search_l.png">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_1.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:pages_0.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:enums_0.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_4.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_a.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_3.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_c.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_c.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_8.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:search_m.png">
+                  </FileRef>
+                  <FileRef
+                     location = "group:enums_1.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:search.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_9.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_0.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_b.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_2.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_5.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:pages_0.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:mag_sel.png">
+                  </FileRef>
+                  <FileRef
+                     location = "group:enums_1.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_b.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:nomatches.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_4.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_5.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_7.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_1.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:classes_2.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_c.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:close.png">
+                  </FileRef>
+               </Group>
+               <FileRef
+                  location = "group:a00093.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00034_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00044_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00050.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00058_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:dir_b73546165ac7e018753d96ca90d34595.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00007.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00028_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00011.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00046.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00063_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00066.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00089.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00070.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00027.html">
+               </FileRef>
+               <FileRef
+                  location = "group:namespacemembers.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00021_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00051_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:dir_5dc556008ba3b473ea5d66774d4b1dbc.html">
+               </FileRef>
+               <FileRef
+                  location = "group:logo-mini.png">
+               </FileRef>
+               <FileRef
+                  location = "group:a00038_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00048_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:nav_h.png">
+               </FileRef>
+               <FileRef
+                  location = "group:a00066_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00026.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00071.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00067.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00088.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00054_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:tab_h.png">
+               </FileRef>
+               <FileRef
+                  location = "group:a00030.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00024_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00041_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00047.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00010.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00031_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00006.html">
+               </FileRef>
+               <FileRef
+                  location = "group:classes.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00051.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00092.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00073_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00084.html">
+               </FileRef>
+               <FileRef
+                  location = "group:dir_9344afb825aed5e2f5be1d2015dde43c.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00018.png">
+               </FileRef>
+               <FileRef
+                  location = "group:a00037.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00070_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:open.png">
+               </FileRef>
+               <FileRef
+                  location = "group:a00042_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00032_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00019.png">
+               </FileRef>
+               <FileRef
+                  location = "group:a00057_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00027_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00083.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00095.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00056.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00065_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:dir_ae6bff3f3cf3c65a1db429f9020bb7b3.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00001.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00017.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00040.html">
+               </FileRef>
+               <FileRef
+                  location = "group:folderclosed.png">
+               </FileRef>
+               <FileRef
+                  location = "group:a00041.html">
+               </FileRef>
+               <FileRef
+                  location = "group:dynsections.js">
+               </FileRef>
+               <FileRef
+                  location = "group:a00016.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00022_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00052_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:folderopen.png">
+               </FileRef>
+               <FileRef
+                  location = "group:a00057.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00094.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00060_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00082.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00009.png">
+               </FileRef>
+               <FileRef
+                  location = "group:a00020.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00037_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00047_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00061.html">
+               </FileRef>
+               <FileRef
+                  location = "group:jquery.js">
+               </FileRef>
+               <FileRef
+                  location = "group:a00008.png">
+               </FileRef>
+               <FileRef
+                  location = "group:a00036.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00069_source.html">
+               </FileRef>
+            </Group>
+            <FileRef
+               location = "group:man.doxy">
+            </FileRef>
+         </Group>
+         <Group
+            location = "group:external"
+            name = "external">
+            <Group
+               location = "group:glm"
+               name = "glm">
+               <FileRef
+                  location = "group:vec4.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:integer.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:vector_relational.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:mat2x3.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:mat4x4.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:mat2x2.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:exponential.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:vec2.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:CMakeLists.txt">
+               </FileRef>
+               <FileRef
+                  location = "group:vec3.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:fwd.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:mat4x3.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:mat4x2.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:mat2x4.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:packing.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:mat3x3.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:mat3x2.hpp">
+               </FileRef>
+               <Group
+                  location = "group:simd"
+                  name = "simd">
+                  <FileRef
+                     location = "group:vector_relational.h">
+                  </FileRef>
+                  <FileRef
+                     location = "group:matrix.h">
+                  </FileRef>
+                  <FileRef
+                     location = "group:packing.h">
+                  </FileRef>
+                  <FileRef
+                     location = "group:common.h">
+                  </FileRef>
+                  <FileRef
+                     location = "group:trigonometric.h">
+                  </FileRef>
+                  <FileRef
+                     location = "group:geometric.h">
+                  </FileRef>
+                  <FileRef
+                     location = "group:integer.h">
+                  </FileRef>
+                  <FileRef
+                     location = "group:platform.h">
+                  </FileRef>
+                  <FileRef
+                     location = "group:exponential.h">
+                  </FileRef>
+               </Group>
+               <FileRef
+                  location = "group:mat3x4.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:matrix.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:glm.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:trigonometric.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:geometric.hpp">
+               </FileRef>
+               <Group
+                  location = "group:detail"
+                  name = "detail">
+                  <FileRef
+                     location = "group:type_vec.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:func_geometric.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:func_exponential.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:func_trigonometric.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:func_integer_simd.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:func_vector_relational.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:func_packing.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:func_common.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:_fixes.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:type_half.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:func_integer.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:func_vector_relational_simd.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:func_matrix_simd.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:type_int.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:_noise.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:type_half.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:_features.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:func_integer.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:_vectorize.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:func_exponential.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:func_geometric.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:type_vec.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:func_common.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:func_packing.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:func_vector_relational.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:func_trigonometric.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:type_mat2x4.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:type_mat4x2.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:_swizzle.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:type_mat4x3.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:func_packing_simd.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:type_vec4.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:func_trigonometric_simd.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:precision.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:type_mat3x3.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:type_mat3x2.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:dummy.cpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:type_mat.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:setup.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:type_mat2x2.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:type_vec1.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:func_common_simd.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:func_matrix.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:type_mat4x4.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:func_geometric_simd.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:type_mat2x3.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:type_vec2.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:glm.cpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:type_gentype.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:type_mat3x4.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:type_vec3.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:type_mat4x4_simd.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:type_mat2x3.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:type_mat4x4.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:func_matrix.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:type_mat2x2.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:type_vec1.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:_swizzle_func.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:type_vec3.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:type_vec4_simd.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:type_mat3x4.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:func_exponential_simd.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:type_gentype.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:type_vec2.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:type_mat4x3.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:type_mat4x2.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:type_float.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:type_mat2x4.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:type_mat.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:type_mat3x2.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:type_mat3x3.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:type_vec4.hpp">
+                  </FileRef>
+               </Group>
+               <FileRef
+                  location = "group:ext.hpp">
+               </FileRef>
+               <Group
+                  location = "group:gtc"
+                  name = "gtc">
+                  <FileRef
+                     location = "group:integer.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:matrix_integer.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:matrix_inverse.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:quaternion.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:packing.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:matrix_transform.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:vec1.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:type_precision.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:bitfield.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:ulp.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:round.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:packing.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:quaternion_simd.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:ulp.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:round.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:matrix_transform.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:type_precision.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:bitfield.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:vec1.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:integer.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:quaternion.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:matrix_inverse.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:epsilon.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:random.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:matrix_access.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:type_ptr.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:reciprocal.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:constants.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:color_space.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:noise.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:color_space.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:noise.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:matrix_access.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:random.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:epsilon.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:constants.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:reciprocal.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:type_ptr.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:type_aligned.hpp">
+                  </FileRef>
+               </Group>
+               <FileRef
+                  location = "group:common.hpp">
+               </FileRef>
+               <Group
+                  location = "group:gtx"
+                  name = "gtx">
+                  <FileRef
+                     location = "group:vector_angle.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:log_base.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:matrix_query.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:extend.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:integer.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:associated_min_max.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:matrix_transform_2d.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:hash.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:rotate_vector.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:matrix_interpolation.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:quaternion.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:gradient_paint.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:string_cast.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:color_space_YCoCg.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:type_trait.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:extended_min_max.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:euler_angles.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:fast_square_root.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:raw_data.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:matrix_cross_product.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:spline.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:fast_trigonometry.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:optimum_pow.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:dual_quaternion.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:closest_point.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:number_precision.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:mixed_product.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:transform.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:bit.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:io.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:fast_trigonometry.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:optimum_pow.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:spline.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:raw_data.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:matrix_cross_product.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:euler_angles.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:fast_square_root.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:color_space_YCoCg.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:type_trait.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:extended_min_max.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:io.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:float_notmalize.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:transform.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:bit.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:number_precision.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:mixed_product.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:closest_point.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:dual_quaternion.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:associated_min_max.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:matrix_transform_2d.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:integer.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:log_base.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:matrix_query.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:vector_angle.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:extend.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:gradient_paint.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:quaternion.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:string_cast.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:rotate_vector.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:matrix_interpolation.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:hash.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:vector_query.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:polar_coordinates.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:orthonormalize.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:component_wise.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:normalize_dot.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:std_based_type.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:type_aligned.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:vec_swizzle.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:projection.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:compatibility.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:scalar_relational.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:wrap.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:rotate_normalized_axis.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:color_encoding.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:norm.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:matrix_major_storage.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:matrix_operation.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:color_space.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:normal.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:intersect.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:perpendicular.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:transform2.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:matrix_decompose.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:fast_exponential.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:common.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:handed_coordinate_space.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:intersect.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:perpendicular.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:matrix_operation.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:color_space.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:normal.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:color_encoding.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:matrix_major_storage.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:norm.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:handed_coordinate_space.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:common.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:range.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:fast_exponential.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:transform2.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:matrix_decompose.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:scalar_multiplication.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:std_based_type.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:orthonormalize.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:normalize_dot.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:component_wise.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:polar_coordinates.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:vector_query.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:scalar_relational.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:wrap.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:compatibility.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:rotate_normalized_axis.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:projection.inl">
+                  </FileRef>
+                  <FileRef
+                     location = "group:type_aligned.hpp">
+                  </FileRef>
+               </Group>
+            </Group>
+         </Group>
+         <FileRef
+            location = "group:manual.md">
+         </FileRef>
+         <Group
+            location = "group:data"
+            name = "data">
+            <FileRef
+               location = "group:cube_rgba8_unorm.ktx">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgb_etc2_srgb.ktx">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_r_eac_unorm.ktx">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgba_dxt5_srgb.ktx">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_r8_sint.dds">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgba16_sfloat.ktx">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgba_pvrtc2_4bpp_unorm.ktx">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgba8_snorm.dds">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgba_astc12x12_srgb.ktx">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_bgra8_unorm.dds">
+            </FileRef>
+            <FileRef
+               location = "group:kueken8_rgba_dxt1_unorm.dds">
+            </FileRef>
+            <FileRef
+               location = "group:test.dds">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgba_astc4x4_srgb.ktx">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgba_astc8x8_unorm.dds">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgba8_unorm.ktx">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgba_pvrtc2_2bpp_unorm.ktx">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgba_pvrtc2_2bpp_srgb.ktx">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_bgra8_srgb.dds">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgb_dxt1_srgb.ktx">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_r_ati1n_unorm.dds">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgba_dxt1_srgb.dds">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_r16_unorm.dds">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_r5g6b5_unorm.ktx">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgba8_srgb.dds">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_r8_uint.dds">
+            </FileRef>
+            <FileRef
+               location = "group:array_r8_uint.ktx">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rg11b10_ufloat.dds">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgba_dxt1_unorm.dds">
+            </FileRef>
+            <FileRef
+               location = "group:kueken8_bgr8_unorm.dds">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rg_ati2n_unorm_decompressed.dds">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgb9e5_ufloat.ktx">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_bgr8_unorm.dds">
+            </FileRef>
+            <FileRef
+               location = "group:kueken8_rgba8_srgb.dds">
+            </FileRef>
+            <FileRef
+               location = "group:kueken8_srgb8.jpg">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgb_dxt1_unorm.ktx">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rg_ati2n_unorm.dds">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_bgrx8_srgb.dds">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgb10a2u.dds">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgb5a1_unorm.dds">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_bgrx8_unorm.dds">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgba_dxt5_unorm.ktx">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgba_pvrtc2_4bpp_srgb.ktx">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgba4_unorm.ktx">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rg_eac_snorm.ktx">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgb_etc1_unorm.dds">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_bgra8_unorm.ktx">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgb8_srgb.ktx">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgba_atc_interpolate_unorm.dds">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgba_pvrtc2_4bpp_unorm.dds">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgba_atc_explicit_unorm.dds">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgb_atc_unorm.dds">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgba16_sfloat.dds">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgba_dxt5_unorm_decompressed.dds">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_a8_unorm.dds">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgba_dxt5_unorm1.dds">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgba_dxt5_srgb.dds">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgba_astc8x5_srgb.ktx">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgba_dxt5_unorm2.dds">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgb_etc2_srgb.dds">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_l8_unorm.dds">
+            </FileRef>
+            <FileRef
+               location = "group:cube_rgba8_unorm.dds">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_bgr8_srgb.dds">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_r_eac_snorm.ktx">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_bgra8_srgb.ktx">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgb10a2_unorm.dds">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgba_dxt1_unorm_decompressed.dds">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgb_etc2_unorm.dds">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgba8_unorm.dds">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgb8_unorm.ktx">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgba_astc4x4_srgb.dds">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgb5a1_unorm_.dds">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgb_pvrtc_4bpp_srgb.ktx">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgba_dxt3_unorm.dds">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgba_etc2_a1_srgb.ktx">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_srgb8.png">
+            </FileRef>
+            <FileRef
+               location = "group:kueken8_rgba8_srgb.ktx">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_srgb8.jpg">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgb9e5_ufloat.dds">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rg_eac_unorm.ktx">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgb_pvrtc_4bpp_unorm.dds">
+            </FileRef>
+            <FileRef
+               location = "group:array_r8_uint.dds">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rg11b10_ufloat.ktx">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_r5g6b5_unorm.dds">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgba8_srgb.ktx">
+            </FileRef>
+            <FileRef
+               location = "group:npot.ktx">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgb_etc1_unorm.ktx">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgba_dxt3_unorm_decompressed.dds">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgba_etc2_srgb.ktx">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgba4_unorm.dds">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgba_dxt5_unorm.dds">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_r_ati1n_unorm_decompressed.dds">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgb5a1_unorm.ktx">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgb8.jpg">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgb_pvrtc_2bpp_srgb.ktx">
+            </FileRef>
+            <FileRef
+               location = "group:kueken7_rgb_pvrtc_2bpp_unorm.dds">
+            </FileRef>
+         </Group>
+      </Group>
+      <Group
+         location = "group:stb"
+         name = "stb">
+         <FileRef
+            location = "group:stb_font_consolas_24_latin1.inl">
+         </FileRef>
+      </Group>
+      <Group
+         location = "group:vulkan"
+         name = "vulkan">
+         <FileRef
+            location = "group:vk_layer.h">
+         </FileRef>
+         <FileRef
+            location = "group:vk_icd.h">
+         </FileRef>
+         <FileRef
+            location = "group:vk_layer_dispatch_table.h">
+         </FileRef>
+         <FileRef
+            location = "group:vulkan.h">
+         </FileRef>
+         <FileRef
+            location = "group:vk_platform.h">
+         </FileRef>
+         <FileRef
+            location = "group:spirv.py">
+         </FileRef>
+         <FileRef
+            location = "group:spirv.lua">
+         </FileRef>
+         <FileRef
+            location = "group:spirv.hpp11">
+         </FileRef>
+         <FileRef
+            location = "group:spirv.hpp">
+         </FileRef>
+         <FileRef
+            location = "group:spirv.h">
+         </FileRef>
+         <FileRef
+            location = "group:GLSL.std.450.h">
+         </FileRef>
+         <FileRef
+            location = "group:vk_sdk_platform.h">
+         </FileRef>
+         <FileRef
+            location = "group:spirv.json">
+         </FileRef>
+      </Group>
+      <Group
+         location = "group:glm"
+         name = "glm">
+         <FileRef
+            location = "group:CMakeLists.txt">
+         </FileRef>
+         <Group
+            location = "group:test"
+            name = "test">
+            <FileRef
+               location = "group:glm.cppcheck">
+            </FileRef>
+            <FileRef
+               location = "group:CMakeLists.txt">
+            </FileRef>
+            <Group
+               location = "group:core"
+               name = "core">
+               <FileRef
+                  location = "group:core_func_integer_bit_count.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:CMakeLists.txt">
+               </FileRef>
+               <FileRef
+                  location = "group:core_type_float.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_func_integer.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_setup_force_cxx98.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_setup_message.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_func_common.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_type_length.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_setup_precision.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_func_exponential.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_setup_force_size_t_length.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_func_packing.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_func_geometric.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_type_ctor.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_func_trigonometric.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_func_integer_find_msb.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_func_swizzle.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_type_mat2x2.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_type_mat4x4.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_func_vector_relational.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_type_mat2x3.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_type_mat2x4.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_type_mat4x2.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_type_mat4x3.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_func_noise.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_type_int.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_type_vec1.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_type_mat3x2.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_type_mat3x3.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_force_unrestricted_gentype.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_func_integer_find_lsb.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_type_vec2.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_force_pure.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_type_vec3.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_type_mat3x4.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_type_aligned.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_type_vec4.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_func_matrix.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:core_type_cast.cpp">
+               </FileRef>
+            </Group>
+            <Group
+               location = "group:bug"
+               name = "bug">
+               <FileRef
+                  location = "group:CMakeLists.txt">
+               </FileRef>
+               <FileRef
+                  location = "group:bug_ms_vec_static.cpp">
+               </FileRef>
+            </Group>
+            <Group
+               location = "group:external"
+               name = "external">
+               <Group
+                  location = "group:gli"
+                  name = "gli">
+                  <FileRef
+                     location = "group:texture2d.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:sampler_cube_array.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:texture1d_array.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:load.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:sampler2d.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:generate_mipmaps.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:levels.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:reduce.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:CMakeLists.txt">
+                  </FileRef>
+                  <Group
+                     location = "group:core"
+                     name = "core">
+                     <FileRef
+                        location = "group:dx.inl">
+                     </FileRef>
+                     <FileRef
+                        location = "group:comparison.inl">
+                     </FileRef>
+                     <FileRef
+                        location = "group:mipmaps_compute.hpp">
+                     </FileRef>
+                     <FileRef
+                        location = "group:sampler2d_array.inl">
+                     </FileRef>
+                     <FileRef
+                        location = "group:load_kmg.inl">
+                     </FileRef>
+                     <FileRef
+                        location = "group:save_ktx.inl">
+                     </FileRef>
+                     <FileRef
+                        location = "group:storage_linear.hpp">
+                     </FileRef>
+                     <FileRef
+                        location = "group:load_dds.inl">
+                     </FileRef>
+                     <FileRef
+                        location = "group:clear.inl">
+                     </FileRef>
+                     <FileRef
+                        location = "group:sampler_cube.inl">
+                     </FileRef>
+                     <FileRef
+                        location = "group:transform.inl">
+                     </FileRef>
+                     <FileRef
+                        location = "group:file.inl">
+                     </FileRef>
+                     <FileRef
+                        location = "group:duplicate.inl">
+                     </FileRef>
+                     <FileRef
+                        location = "group:storage_linear.inl">
+                     </FileRef>
+                     <FileRef
+                        location = "group:texture3d.inl">
+                     </FileRef>
+                     <FileRef
+                        location = "group:file.hpp">
+                     </FileRef>
+                     <FileRef
+                        location = "group:clear.hpp">
+                     </FileRef>
+                     <FileRef
+                        location = "group:sampler3d.inl">
+                     </FileRef>
+                     <FileRef
+                        location = "group:load.inl">
+                     </FileRef>
+                     <FileRef
+                        location = "group:sampler2d.inl">
+                     </FileRef>
+                     <FileRef
+                        location = "group:texture1d_array.inl">
+                     </FileRef>
+                     <FileRef
+                        location = "group:texture2d.inl">
+                     </FileRef>
+                     <FileRef
+                        location = "group:sampler_cube_array.inl">
+                     </FileRef>
+                     <FileRef
+                        location = "group:filter_compute.hpp">
+                     </FileRef>
+                     <FileRef
+                        location = "group:reduce.inl">
+                     </FileRef>
+                     <FileRef
+                        location = "group:coord.hpp">
+                     </FileRef>
+                     <FileRef
+                        location = "group:levels.inl">
+                     </FileRef>
+                     <FileRef
+                        location = "group:generate_mipmaps.inl">
+                     </FileRef>
+                     <FileRef
+                        location = "group:format.inl">
+                     </FileRef>
+                     <FileRef
+                        location = "group:save.inl">
+                     </FileRef>
+                     <FileRef
+                        location = "group:flip.hpp">
+                     </FileRef>
+                     <FileRef
+                        location = "group:texture2d_array.inl">
+                     </FileRef>
+                     <FileRef
+                        location = "group:image.inl">
+                     </FileRef>
+                     <FileRef
+                        location = "group:copy.inl">
+                     </FileRef>
+                     <FileRef
+                        location = "group:convert.inl">
+                     </FileRef>
+                     <FileRef
+                        location = "group:sampler1d.inl">
+                     </FileRef>
+                     <FileRef
+                        location = "group:dummy.cpp">
+                     </FileRef>
+                     <FileRef
+                        location = "group:texture1d.inl">
+                     </FileRef>
+                     <FileRef
+                        location = "group:texture_cube.inl">
+                     </FileRef>
+                     <FileRef
+                        location = "group:convert_func.hpp">
+                     </FileRef>
+                     <FileRef
+                        location = "group:storage.hpp">
+                     </FileRef>
+                     <FileRef
+                        location = "group:filter.hpp">
+                     </FileRef>
+                     <FileRef
+                        location = "group:sampler.inl">
+                     </FileRef>
+                     <FileRef
+                        location = "group:texture_cube_array.inl">
+                     </FileRef>
+                     <FileRef
+                        location = "group:sampler1d_array.inl">
+                     </FileRef>
+                     <FileRef
+                        location = "group:texture.inl">
+                     </FileRef>
+                     <FileRef
+                        location = "group:filter.inl">
+                     </FileRef>
+                     <FileRef
+                        location = "group:view.inl">
+                     </FileRef>
+                     <FileRef
+                        location = "group:workaround.hpp">
+                     </FileRef>
+                     <FileRef
+                        location = "group:storage.inl">
+                     </FileRef>
+                     <FileRef
+                        location = "group:save_dds.inl">
+                     </FileRef>
+                     <FileRef
+                        location = "group:flip.inl">
+                     </FileRef>
+                     <FileRef
+                        location = "group:make_texture.inl">
+                     </FileRef>
+                     <FileRef
+                        location = "group:gl.inl">
+                     </FileRef>
+                     <FileRef
+                        location = "group:load_ktx.inl">
+                     </FileRef>
+                     <FileRef
+                        location = "group:save_kmg.inl">
+                     </FileRef>
+                  </Group>
+                  <FileRef
+                     location = "group:sampler3d.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:type.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:texture3d.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:save_ktx.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:gli.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:load_kmg.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:sampler2d_array.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:transform.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:duplicate.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:clear.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:sampler_cube.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:load_dds.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:target.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:comparison.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:dx.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:make_texture.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:save_dds.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:save_kmg.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:load_ktx.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:gl.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:sampler1d_array.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:texture.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:texture_cube_array.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:sampler.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:view.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:texture_cube.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:copy.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:image.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:texture2d_array.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:format.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:save.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:texture1d.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:convert.hpp">
+                  </FileRef>
+                  <FileRef
+                     location = "group:sampler1d.hpp">
+                  </FileRef>
+               </Group>
+            </Group>
+            <Group
+               location = "group:gtc"
+               name = "gtc">
+               <FileRef
+                  location = "group:gtc_packing.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtc_matrix_inverse.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:CMakeLists.txt">
+               </FileRef>
+               <FileRef
+                  location = "group:gtc_type_ptr.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtc_matrix_transform.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtc_type_precision.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtc_reciprocal.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtc_integer.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtc_noise.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtc_color_space.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtc_matrix_integer.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtc_user_defined_types.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtc_constants.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtc_type_aligned.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtc_random.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtc_epsilon.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtc_round.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtc_quaternion.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtc_matrix_access.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtc_bitfield.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtc_ulp.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtc_vec1.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtc_functions.cpp">
+               </FileRef>
+            </Group>
+            <Group
+               location = "group:gtx"
+               name = "gtx">
+               <FileRef
+                  location = "group:gtx_closest_point.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_number_precision.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_log_base.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_io.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_color_space.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_spline.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_scalar_multiplication.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_int_10_10_10_2.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:CMakeLists.txt">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_rotate_vector.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_vec_swizzle.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_exterior_product.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_matrix_query.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_intersect.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_color_encoding.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_matrix_factorisation.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_extend.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_euler_angle.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_quaternion.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_vector_angle.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_component_wise.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_orthonormalize.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_rotate_normalized_axis.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_type_trait.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_mixed_product.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_color_space_YCoCg.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_matrix_major_storage.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_fast_square_root.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_extended_min_max.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_fast_trigonometry.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_extented_min_max.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_simd_vec4.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_normal.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_matrix_operation.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_norm.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_handed_coordinate_space.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_type_aligned.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_matrix_interpolation.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_string_cast.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_wrap.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_projection.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_vector_query.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_random.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_compatibility.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_matrix_transform_2d.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_scalar_relational.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_polar_coordinates.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_normalize_dot.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_integer.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_simd_mat4.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_fast_exponential.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_dual_quaternion.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_matrix_decompose.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_range.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_common.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_perpendicular.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_gradient_paint.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_associated_min_max.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_matrix_cross_product.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gtx_optimum_pow.cpp">
+               </FileRef>
+            </Group>
+         </Group>
+         <Group
+            location = "group:cmake"
+            name = "cmake">
+            <FileRef
+               location = "group:glmConfig.cmake.in">
+            </FileRef>
+            <FileRef
+               location = "group:glmBuildConfig.cmake.in">
+            </FileRef>
+            <FileRef
+               location = "group:GNUInstallDirs.cmake">
+            </FileRef>
+            <FileRef
+               location = "group:CMakePackageConfigHelpers.cmake">
+            </FileRef>
+            <FileRef
+               location = "group:glm.pc.in">
+            </FileRef>
+         </Group>
+         <Group
+            location = "group:util"
+            name = "util">
+            <FileRef
+               location = "group:autoexp.vc2010.dat">
+            </FileRef>
+            <FileRef
+               location = "group:usertype.dat">
+            </FileRef>
+            <FileRef
+               location = "group:autoexp.txt">
+            </FileRef>
+            <FileRef
+               location = "group:glm.natvis">
+            </FileRef>
+            <Group
+               location = "group:conan-package"
+               name = "conan-package">
+               <Group
+                  location = "group:lib_licenses"
+                  name = "lib_licenses">
+                  <FileRef
+                     location = "group:LICENSE2.txt">
+                  </FileRef>
+                  <FileRef
+                     location = "group:LICENSE1.txt">
+                  </FileRef>
+               </Group>
+               <FileRef
+                  location = "group:conanfile.py">
+               </FileRef>
+               <FileRef
+                  location = "group:README.md">
+               </FileRef>
+               <Group
+                  location = "group:test_package"
+                  name = "test_package">
+                  <FileRef
+                     location = "group:CMakeLists.txt">
+                  </FileRef>
+                  <FileRef
+                     location = "group:conanfile.py">
+                  </FileRef>
+                  <FileRef
+                     location = "group:main.cpp">
+                  </FileRef>
+               </Group>
+            </Group>
+         </Group>
+         <FileRef
+            location = "group:readme.md">
+         </FileRef>
+         <Group
+            location = "group:doc"
+            name = "doc">
+            <Group
+               location = "group:manual"
+               name = "manual">
+               <FileRef
+                  location = "group:references-leosfortune2.jpg">
+               </FileRef>
+               <FileRef
+                  location = "group:frontpage1.png">
+               </FileRef>
+               <FileRef
+                  location = "group:g-truc.png">
+               </FileRef>
+               <FileRef
+                  location = "group:frontpage2.png">
+               </FileRef>
+               <FileRef
+                  location = "group:references-cinder.png">
+               </FileRef>
+               <FileRef
+                  location = "group:references-leosfortune.jpeg">
+               </FileRef>
+               <FileRef
+                  location = "group:random-ballrand.png">
+               </FileRef>
+               <FileRef
+                  location = "group:random-circularrand.png">
+               </FileRef>
+               <FileRef
+                  location = "group:random-linearrand.png">
+               </FileRef>
+               <FileRef
+                  location = "group:noise-perlin2.jpg">
+               </FileRef>
+               <FileRef
+                  location = "group:noise-perlin3.jpg">
+               </FileRef>
+               <FileRef
+                  location = "group:references-opencloth1.png">
+               </FileRef>
+               <FileRef
+                  location = "group:references-outerra1.jpg">
+               </FileRef>
+               <FileRef
+                  location = "group:references-outerra3.jpg">
+               </FileRef>
+               <FileRef
+                  location = "group:references-opencloth3.png">
+               </FileRef>
+               <FileRef
+                  location = "group:noise-perlin1.jpg">
+               </FileRef>
+               <FileRef
+                  location = "group:references-outerra2.jpg">
+               </FileRef>
+               <FileRef
+                  location = "group:random-sphericalrand.png">
+               </FileRef>
+               <FileRef
+                  location = "group:logo-mini.png">
+               </FileRef>
+               <FileRef
+                  location = "group:noise-simplex2.jpg">
+               </FileRef>
+               <FileRef
+                  location = "group:noise-perlin4.png">
+               </FileRef>
+               <FileRef
+                  location = "group:random-gaussrand.png">
+               </FileRef>
+               <FileRef
+                  location = "group:noise-perlin5.png">
+               </FileRef>
+               <FileRef
+                  location = "group:noise-simplex3.jpg">
+               </FileRef>
+               <FileRef
+                  location = "group:noise-simplex1.jpg">
+               </FileRef>
+               <FileRef
+                  location = "group:noise-perlin6.png">
+               </FileRef>
+               <FileRef
+                  location = "group:references-outerra4.jpg">
+               </FileRef>
+               <FileRef
+                  location = "group:random-diskrand.png">
+               </FileRef>
+               <FileRef
+                  location = "group:references-glsl4book.jpg">
+               </FileRef>
+            </Group>
+            <Group
+               location = "group:theme"
+               name = "theme">
+               <FileRef
+                  location = "group:doxygen.css">
+               </FileRef>
+               <FileRef
+                  location = "group:tabs.css">
+               </FileRef>
+            </Group>
+            <FileRef
+               location = "group:pages.doxy">
+            </FileRef>
+            <Group
+               location = "group:api"
+               name = "api">
+               <FileRef
+                  location = "group:a00072_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00222.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00110_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:dir_934f46a345653ef2b3014a1b37a162c1.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00002_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00019.html">
+               </FileRef>
+               <FileRef
+                  location = "group:splitbar.png">
+               </FileRef>
+               <FileRef
+                  location = "group:a00058.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00234.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00166.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00030_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00189.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00023.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00040_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00131.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00074.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00122_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00218.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00127.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00062.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00170.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00093_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00035.html">
+               </FileRef>
+               <FileRef
+                  location = "group:doxygen.css">
+               </FileRef>
+               <FileRef
+                  location = "group:a00042.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00107.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00086_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00119_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00015.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00150.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00025_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00003.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00146.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00055_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00054.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00111.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00137_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00185.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00049_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00214.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00097.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00039_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00078.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00067_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00202.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00081.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00105_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00017_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00193.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00039.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00192.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00132_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00038.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00050_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00080.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00020_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00203.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00096.html">
+               </FileRef>
+               <FileRef
+                  location = "group:arrowright.png">
+               </FileRef>
+               <FileRef
+                  location = "group:a00079.html">
+               </FileRef>
+               <FileRef
+                  location = "group:index.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00083_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00215.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00184.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00055.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00110.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00012_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00002.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00147.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00100_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:sync_off.png">
+               </FileRef>
+               <FileRef
+                  location = "group:a00062_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00014.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00151.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00043.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00106.html">
+               </FileRef>
+               <FileRef
+                  location = "group:modules.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00029_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00171.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00034.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00059_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00126.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00063.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00219.html">
+               </FileRef>
+               <FileRef
+                  location = "group:dir_48eca2e6cf73effdec262031e861eeb0.html">
+               </FileRef>
+               <FileRef
+                  location = "group:sync_on.png">
+               </FileRef>
+               <FileRef
+                  location = "group:a00007_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00130.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00075.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00115_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00077_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00167.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00188.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00022.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00235.html">
+               </FileRef>
+               <FileRef
+                  location = "group:dir_da256b9dd32ba43e2eaa8a2832c37f1b.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00109_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00096_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00059.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00127_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00045_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00018.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00223.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00035_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:doc.png">
+               </FileRef>
+               <FileRef
+                  location = "group:a00228.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00046_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00124_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00044.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00101.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00036_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:arrowdown.png">
+               </FileRef>
+               <FileRef
+                  location = "group:a00013.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00156.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00095_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00068_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00005.html">
+               </FileRef>
+               <FileRef
+                  location = "group:bc_s.png">
+               </FileRef>
+               <FileRef
+                  location = "group:nav_g.png">
+               </FileRef>
+               <FileRef
+                  location = "group:a00018_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00052.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00117.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00004_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:nav_f.png">
+               </FileRef>
+               <FileRef
+                  location = "group:a00029.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00183.html">
+               </FileRef>
+               <FileRef
+                  location = "group:tabs.css">
+               </FileRef>
+               <FileRef
+                  location = "group:a00074_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00212.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00116_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00091.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00089_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00204.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00087.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00068.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00138_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00195.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00048.html">
+               </FileRef>
+               <FileRef
+                  location = "group:closed.png">
+               </FileRef>
+               <FileRef
+                  location = "group:a00224.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00009.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00011_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:doxygen.png">
+               </FileRef>
+               <FileRef
+                  location = "group:a00061_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00232.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00103_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00160.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00080_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00025.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00137.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00072.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00121.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00064.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00053_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:tab_s.png">
+               </FileRef>
+               <FileRef
+                  location = "group:a00131_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00208.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00176.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00033.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00023_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00199.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00099_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00106_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00177.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00032.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00064_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00198.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00209.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00120.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00065.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00014_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00136.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00073.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00128_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00161.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00024.html">
+               </FileRef>
+               <FileRef
+                  location = "group:dir_98f7f9d41f9d3029bd68cf237526a774.html">
+               </FileRef>
+               <FileRef
+                  location = "group:files.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00233.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00026_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00134_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00008.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00056_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00008_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:tab_a.png">
+               </FileRef>
+               <FileRef
+                  location = "group:a00225.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00078_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00049.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00085_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00194.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00086.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00069.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00205.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00090_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00090.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00033_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00213.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00121_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00028.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00182.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00043_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00053.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00116.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00004.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00113_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00012.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00157.html">
+               </FileRef>
+               <FileRef
+                  location = "group:tab_b.png">
+               </FileRef>
+               <FileRef
+                  location = "group:a00071_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00045.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00100.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00229.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00001_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00197.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00178.html">
+               </FileRef>
+               <FileRef
+                  location = "group:bdwn.png">
+               </FileRef>
+               <FileRef
+                  location = "group:a00097_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00108_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00085.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00206.html">
+               </FileRef>
+               <Group
+                  location = "group:search"
+                  name = "search">
+                  <FileRef
+                     location = "group:functions_3.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:searchdata.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_6.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_f.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_6.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_11.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_4.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_9.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_14.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_d.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:groups_1.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_17.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_a.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:search_r.png">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_7.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:typedefs_9.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_a.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_8.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_1.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_c.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_15.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_2.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:typedefs_0.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_10.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:groups_8.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_e.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_6.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:typedefs_8.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_14.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_3.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:typedefs_1.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_a.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_b.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_9.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_0.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_11.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:groups_9.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_d.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_2.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:groups_0.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_16.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_7.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_10.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_8.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:search.css">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_5.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_e.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_7.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_16.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_d.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_7.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_e.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_17.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:typedefs_7.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_9.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:typedefs_6.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:groups_6.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_3.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_11.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_11.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_0.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_8.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:typedefs_3.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_13.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_2.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:typedefs_a.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:groups_2.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:groups_7.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_11.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_7.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:groups_6.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_9.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_10.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:typedefs_2.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_12.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_a.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:groups_3.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_3.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_a.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_1.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_10.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_10.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_6.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_e.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_16.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:typedefs_6.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:typedefs_7.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_8.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_d.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:groups_7.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_6.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_f.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_2.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:typedefs_a.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:groups_9.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_11.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:typedefs_1.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_b.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:typedefs_4.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:groups_0.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_5.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_e.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_5.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_13.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:groups_5.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_f.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:typedefs_8.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:typedefs_c.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:typedefs_5.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_15.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_9.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_0.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:groups_4.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_b.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_1.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_2.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_13.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:typedefs_b.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_13.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_12.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_12.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_3.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_c.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_8.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_1.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:typedefs_4.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_14.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:typedefs_b.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:typedefs_9.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:groups_5.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_12.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_0.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:groups_4.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:typedefs_c.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_d.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_4.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_10.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_f.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:typedefs_0.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_c.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:groups_1.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:typedefs_5.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:groups_8.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_4.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_5.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_15.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:groups_3.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_0.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_b.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_12.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_6.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_d.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_4.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_15.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:search_l.png">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_1.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:pages_0.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_13.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_4.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:typedefs_2.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_a.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_f.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_3.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_c.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_c.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_8.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:search_m.png">
+                  </FileRef>
+                  <FileRef
+                     location = "group:search.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_9.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_0.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_b.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_2.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_12.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_5.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:pages_0.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:typedefs_3.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:mag_sel.png">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_b.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:nomatches.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_4.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_14.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_14.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_5.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_e.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_7.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_1.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_16.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:functions_c.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:files_13.js">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_14.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:groups_2.html">
+                  </FileRef>
+                  <FileRef
+                     location = "group:close.png">
+                  </FileRef>
+                  <FileRef
+                     location = "group:all_f.js">
+                  </FileRef>
+               </Group>
+               <FileRef
+                  location = "group:a00093.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00139.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00034_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00210.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00181.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00044_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00126_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00115.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00050.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00058_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00007.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00028_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00154.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00011.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00076_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00114_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00103.html">
+               </FileRef>
+               <FileRef
+                  location = "group:dir_45973f864e07b2505003ae343b7c8af7.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00046.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00006_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00063_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00031.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00101_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00174.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00013_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00066.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00089.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00123.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00070.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00135.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00027.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00162.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00230.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00021_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00119.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00051_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00133_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00158.html">
+               </FileRef>
+               <FileRef
+                  location = "group:dir_e8f3c1046ba4b357711397765359cd18.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00082_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00226.html">
+               </FileRef>
+               <FileRef
+                  location = "group:logo-mini.png">
+               </FileRef>
+               <FileRef
+                  location = "group:a00038_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00227.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00159.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00048_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00016_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00104_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00118.html">
+               </FileRef>
+               <FileRef
+                  location = "group:nav_h.png">
+               </FileRef>
+               <FileRef
+                  location = "group:a00231.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00066_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00026.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00163.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00118_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00087_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00071.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00134.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00136_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00067.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00088.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00122.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00054_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:tab_h.png">
+               </FileRef>
+               <FileRef
+                  location = "group:a00030.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00175.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00024_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00123_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00102.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00041_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00047.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00155.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00010.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00031_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00092_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00006.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00114.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00180.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00003_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00211.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00111_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00092.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00138.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00073_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00207.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00084.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00196.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00179.html">
+               </FileRef>
+               <FileRef
+                  location = "group:dir_9344afb825aed5e2f5be1d2015dde43c.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00037.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00172.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00060.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00125.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00076.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00133.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00099.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00070_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00021.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00112_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00164.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00091_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00236.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00148.html">
+               </FileRef>
+               <FileRef
+                  location = "group:open.png">
+               </FileRef>
+               <FileRef
+                  location = "group:a00042_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00120_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00032_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00220.html">
+               </FileRef>
+               <FileRef
+                  location = "group:dir_304be5dfae1339a7705426c0b536faf2.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00109.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00191.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00057_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00135_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00027_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00129.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00200.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00084_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00095.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00216.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00079_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00187.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00168.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00009_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00113.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00015_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00056.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00065_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00001.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00107_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00098_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00152.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00017.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00129_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00105.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00040.html">
+               </FileRef>
+               <FileRef
+                  location = "group:folderclosed.png">
+               </FileRef>
+               <FileRef
+                  location = "group:a00104.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00041.html">
+               </FileRef>
+               <FileRef
+                  location = "group:dynsections.js">
+               </FileRef>
+               <FileRef
+                  location = "group:a00153.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00081_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00016.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00022_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00145.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00130_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00112.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00052_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:folderopen.png">
+               </FileRef>
+               <FileRef
+                  location = "group:a00057.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00186.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00169.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00217.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00094.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00201.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00102_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00060_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00128.html">
+               </FileRef>
+               <FileRef
+                  location = "group:dir_7997edb062bdde9a99cb6835d42b0d9d.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00082.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00010_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00190.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00108.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00088_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00117_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00075_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00221.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00005_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00149.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00139_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00020.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00165.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00037_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00077.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00132.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00098.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00125_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00047_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00061.html">
+               </FileRef>
+               <FileRef
+                  location = "group:jquery.js">
+               </FileRef>
+               <FileRef
+                  location = "group:a00124.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00019_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00036.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00173.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00069_source.html">
+               </FileRef>
+               <FileRef
+                  location = "group:a00094_source.html">
+               </FileRef>
+            </Group>
+            <FileRef
+               location = "group:man.doxy">
+            </FileRef>
+         </Group>
+         <FileRef
+            location = "group:manual.md">
+         </FileRef>
+         <Group
+            location = "group:glm"
+            name = "glm">
+            <FileRef
+               location = "group:vec4.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:integer.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:vector_relational.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:mat2x3.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:mat4x4.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:mat2x2.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:exponential.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:vec2.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:CMakeLists.txt">
+            </FileRef>
+            <FileRef
+               location = "group:vec3.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:fwd.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:mat4x3.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:mat4x2.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:mat2x4.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:packing.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:mat3x3.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:mat3x2.hpp">
+            </FileRef>
+            <Group
+               location = "group:simd"
+               name = "simd">
+               <FileRef
+                  location = "group:vector_relational.h">
+               </FileRef>
+               <FileRef
+                  location = "group:matrix.h">
+               </FileRef>
+               <FileRef
+                  location = "group:packing.h">
+               </FileRef>
+               <FileRef
+                  location = "group:common.h">
+               </FileRef>
+               <FileRef
+                  location = "group:trigonometric.h">
+               </FileRef>
+               <FileRef
+                  location = "group:geometric.h">
+               </FileRef>
+               <FileRef
+                  location = "group:integer.h">
+               </FileRef>
+               <FileRef
+                  location = "group:platform.h">
+               </FileRef>
+               <FileRef
+                  location = "group:exponential.h">
+               </FileRef>
+            </Group>
+            <FileRef
+               location = "group:mat3x4.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:matrix.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:glm.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:trigonometric.hpp">
+            </FileRef>
+            <FileRef
+               location = "group:geometric.hpp">
+            </FileRef>
+            <Group
+               location = "group:detail"
+               name = "detail">
+               <FileRef
+                  location = "group:type_vec.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:func_geometric.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:func_exponential.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:func_trigonometric.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:func_integer_simd.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:func_vector_relational.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:func_packing.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:func_common.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:_fixes.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:type_half.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:func_integer.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:func_vector_relational_simd.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:func_matrix_simd.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:type_int.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:_noise.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:type_half.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:_features.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:func_integer.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:_vectorize.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:func_exponential.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:func_geometric.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:type_vec.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:func_common.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:func_packing.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:func_vector_relational.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:func_trigonometric.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:type_mat2x4.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:type_mat4x2.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:_swizzle.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:type_mat4x3.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:func_packing_simd.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:type_vec4.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:func_trigonometric_simd.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:precision.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:type_mat3x3.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:type_mat3x2.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:dummy.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:type_mat.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:setup.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:type_mat2x2.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:type_vec1.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:func_common_simd.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:func_matrix.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:type_mat4x4.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:func_geometric_simd.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:type_mat2x3.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:type_vec2.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:glm.cpp">
+               </FileRef>
+               <FileRef
+                  location = "group:type_gentype.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:type_mat3x4.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:type_vec3.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:type_mat4x4_simd.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:type_mat2x3.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:type_mat4x4.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:func_matrix.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:type_mat2x2.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:type_vec1.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:_swizzle_func.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:type_vec3.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:type_vec4_simd.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:type_mat3x4.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:func_exponential_simd.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:type_gentype.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:type_vec2.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:type_mat4x3.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:type_mat4x2.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:type_float.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:type_mat2x4.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:type_mat.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:type_mat3x2.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:type_mat3x3.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:type_vec4.hpp">
+               </FileRef>
+            </Group>
+            <FileRef
+               location = "group:ext.hpp">
+            </FileRef>
+            <Group
+               location = "group:gtc"
+               name = "gtc">
+               <FileRef
+                  location = "group:integer.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:matrix_integer.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:functions.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:matrix_inverse.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:quaternion.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:packing.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:matrix_transform.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:vec1.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:type_precision.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:bitfield.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:ulp.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:round.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:packing.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:quaternion_simd.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:ulp.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:round.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:matrix_transform.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:type_precision.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:bitfield.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:vec1.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:integer.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:quaternion.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:functions.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:matrix_inverse.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:epsilon.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:random.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:matrix_access.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:type_ptr.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:reciprocal.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:constants.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:color_space.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:noise.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:color_space.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:noise.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:matrix_access.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:random.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:epsilon.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:constants.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:reciprocal.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:type_ptr.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:type_aligned.hpp">
+               </FileRef>
+            </Group>
+            <FileRef
+               location = "group:common.hpp">
+            </FileRef>
+            <Group
+               location = "group:gtx"
+               name = "gtx">
+               <FileRef
+                  location = "group:vector_angle.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:log_base.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:matrix_query.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:extend.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:integer.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:associated_min_max.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:matrix_transform_2d.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:hash.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:rotate_vector.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:matrix_interpolation.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:quaternion.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:exterior_product.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:gradient_paint.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:string_cast.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:color_space_YCoCg.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:type_trait.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:extended_min_max.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:euler_angles.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:fast_square_root.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:raw_data.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:matrix_cross_product.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:spline.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:fast_trigonometry.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:optimum_pow.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:dual_quaternion.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:closest_point.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:number_precision.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:mixed_product.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:transform.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:bit.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:io.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:fast_trigonometry.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:optimum_pow.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:spline.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:raw_data.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:matrix_cross_product.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:euler_angles.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:fast_square_root.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:color_space_YCoCg.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:type_trait.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:extended_min_max.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:io.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:float_notmalize.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:transform.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:bit.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:number_precision.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:mixed_product.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:closest_point.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:dual_quaternion.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:associated_min_max.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:matrix_transform_2d.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:integer.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:log_base.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:matrix_query.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:vector_angle.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:extend.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:gradient_paint.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:quaternion.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:exterior_product.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:string_cast.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:rotate_vector.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:matrix_interpolation.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:hash.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:vector_query.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:polar_coordinates.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:orthonormalize.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:component_wise.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:normalize_dot.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:std_based_type.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:matrix_factorisation.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:type_aligned.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:vec_swizzle.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:projection.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:compatibility.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:scalar_relational.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:wrap.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:rotate_normalized_axis.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:color_encoding.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:norm.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:matrix_major_storage.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:matrix_operation.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:color_space.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:normal.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:intersect.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:perpendicular.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:transform2.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:matrix_decompose.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:fast_exponential.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:common.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:handed_coordinate_space.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:intersect.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:perpendicular.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:matrix_operation.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:color_space.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:normal.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:color_encoding.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:matrix_major_storage.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:norm.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:handed_coordinate_space.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:common.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:range.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:fast_exponential.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:transform2.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:matrix_decompose.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:scalar_multiplication.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:std_based_type.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:matrix_factorisation.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:orthonormalize.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:normalize_dot.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:component_wise.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:polar_coordinates.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:vector_query.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:scalar_relational.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:wrap.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:compatibility.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:rotate_normalized_axis.hpp">
+               </FileRef>
+               <FileRef
+                  location = "group:projection.inl">
+               </FileRef>
+               <FileRef
+                  location = "group:type_aligned.hpp">
+               </FileRef>
+            </Group>
+         </Group>
+      </Group>
+   </Group>
+   <Group
+      location = "group:../../external/imgui"
+      name = "imgui">
+      <FileRef
+         location = "group:imgui.h">
+      </FileRef>
+      <FileRef
+         location = "group:imconfig.h">
+      </FileRef>
+      <FileRef
+         location = "group:LICENSE">
+      </FileRef>
+      <FileRef
+         location = "group:stb_rect_pack.h">
+      </FileRef>
+      <FileRef
+         location = "group:README.md">
+      </FileRef>
+      <FileRef
+         location = "group:stb_truetype.h">
+      </FileRef>
+      <FileRef
+         location = "group:imgui.cpp">
+      </FileRef>
+      <FileRef
+         location = "group:imgui_internal.h">
+      </FileRef>
+      <FileRef
+         location = "group:stb_textedit.h">
+      </FileRef>
+      <FileRef
+         location = "group:imgui_demo.cpp">
+      </FileRef>
+      <FileRef
+         location = "group:imgui_draw.cpp">
+      </FileRef>
+   </Group>
+   <FileRef
+      location = "self:">
+   </FileRef>
+</Workspace>
diff --git a/external/Vulkan/xcode/examples.xcodeproj/project.xcworkspace/xcshareddata/examples.xcscmblueprint b/external/Vulkan/xcode/examples.xcodeproj/project.xcworkspace/xcshareddata/examples.xcscmblueprint
new file mode 100644
index 0000000000000000000000000000000000000000..94c4fa0151130e89978390f9dc8e70b9149c5901
--- /dev/null
+++ b/external/Vulkan/xcode/examples.xcodeproj/project.xcworkspace/xcshareddata/examples.xcscmblueprint
@@ -0,0 +1,30 @@
+{
+  "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "5B3778DF088377325550251267213ED1422AF030",
+  "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : {
+
+  },
+  "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : {
+    "986064432ACD0B21E33CA886A95425627120351A" : 9223372036854775807,
+    "5B3778DF088377325550251267213ED1422AF030" : 9223372036854775807
+  },
+  "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "CD2D1537-0CC5-4969-BD35-AB1ED0A0FACA",
+  "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : {
+    "986064432ACD0B21E33CA886A95425627120351A" : "..\/..\/..\/Molten",
+    "5B3778DF088377325550251267213ED1422AF030" : "Vulkan-bw\/"
+  },
+  "DVTSourceControlWorkspaceBlueprintNameKey" : "examples",
+  "DVTSourceControlWorkspaceBlueprintVersion" : 204,
+  "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "xcode\/examples.xcodeproj",
+  "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [
+    {
+      "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/brenwill\/Vulkan.git",
+      "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
+      "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "5B3778DF088377325550251267213ED1422AF030"
+    },
+    {
+      "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/brenwill\/molten.git",
+      "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
+      "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "986064432ACD0B21E33CA886A95425627120351A"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/external/Vulkan/xcode/examples.xcodeproj/xcshareddata/xcschemes/examples-ios.xcscheme b/external/Vulkan/xcode/examples.xcodeproj/xcshareddata/xcschemes/examples-ios.xcscheme
new file mode 100644
index 0000000000000000000000000000000000000000..f7ec507fe57390cbfd2b77f45afd13ca50f8471a
--- /dev/null
+++ b/external/Vulkan/xcode/examples.xcodeproj/xcshareddata/xcschemes/examples-ios.xcscheme
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "0920"
+   version = "2.0">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "1D6058900D05DD3D006BFB54"
+               BuildableName = "examples.app"
+               BlueprintName = "examples-ios"
+               ReferencedContainer = "container:examples.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      language = ""
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <Testables>
+      </Testables>
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "1D6058900D05DD3D006BFB54"
+            BuildableName = "examples.app"
+            BlueprintName = "examples-ios"
+            ReferencedContainer = "container:examples.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+      <AdditionalOptions>
+      </AdditionalOptions>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Release"
+      selectedDebuggerIdentifier = ""
+      selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
+      language = ""
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugXPCServices = "NO"
+      debugServiceExtension = "internal"
+      enableGPUFrameCaptureMode = "3"
+      enableGPUValidationMode = "1"
+      allowLocationSimulation = "NO"
+      viewDebuggingEnabled = "No"
+      queueDebuggingEnabled = "No">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "1D6058900D05DD3D006BFB54"
+            BuildableName = "examples.app"
+            BlueprintName = "examples-ios"
+            ReferencedContainer = "container:examples.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+      <AdditionalOptions>
+      </AdditionalOptions>
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Release"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "1D6058900D05DD3D006BFB54"
+            BuildableName = "examples.app"
+            BlueprintName = "examples-ios"
+            ReferencedContainer = "container:examples.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>
diff --git a/external/Vulkan/xcode/examples.xcodeproj/xcshareddata/xcschemes/examples-macos.xcscheme b/external/Vulkan/xcode/examples.xcodeproj/xcshareddata/xcschemes/examples-macos.xcscheme
new file mode 100644
index 0000000000000000000000000000000000000000..19bea5e4ba1c4ec793e5ce1d789432b473f9322f
--- /dev/null
+++ b/external/Vulkan/xcode/examples.xcodeproj/xcshareddata/xcschemes/examples-macos.xcscheme
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "0920"
+   version = "2.0">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "A977BCBD1B66BB010067E5BF"
+               BuildableName = "examples.app"
+               BlueprintName = "examples-macos"
+               ReferencedContainer = "container:examples.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      language = ""
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <Testables>
+      </Testables>
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "A977BCBD1B66BB010067E5BF"
+            BuildableName = "examples.app"
+            BlueprintName = "examples-macos"
+            ReferencedContainer = "container:examples.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+      <AdditionalOptions>
+      </AdditionalOptions>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Release"
+      selectedDebuggerIdentifier = ""
+      selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
+      language = ""
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "NO"
+      debugXPCServices = "NO"
+      debugServiceExtension = "internal"
+      enableGPUFrameCaptureMode = "3"
+      enableGPUValidationMode = "1"
+      allowLocationSimulation = "NO"
+      viewDebuggingEnabled = "No"
+      queueDebuggingEnabled = "No">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "A977BCBD1B66BB010067E5BF"
+            BuildableName = "examples.app"
+            BlueprintName = "examples-macos"
+            ReferencedContainer = "container:examples.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+      <AdditionalOptions>
+      </AdditionalOptions>
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Release"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "A977BCBD1B66BB010067E5BF"
+            BuildableName = "examples.app"
+            BlueprintName = "examples-macos"
+            ReferencedContainer = "container:examples.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>
diff --git a/external/Vulkan/xcode/images/MoltenVK-Logo-Banner.png b/external/Vulkan/xcode/images/MoltenVK-Logo-Banner.png
new file mode 100755
index 0000000000000000000000000000000000000000..8bd880f9755d7c58f01302914672a1816b8aa003
Binary files /dev/null and b/external/Vulkan/xcode/images/MoltenVK-Logo-Banner.png differ
diff --git a/external/Vulkan/xcode/ios/AppDelegate.h b/external/Vulkan/xcode/ios/AppDelegate.h
new file mode 100644
index 0000000000000000000000000000000000000000..5023d3ddd4bf3f5efc02b0a0bb4f38515dea1380
--- /dev/null
+++ b/external/Vulkan/xcode/ios/AppDelegate.h
@@ -0,0 +1,15 @@
+/*
+ *  AppDelegate.h
+ *
+ *  Copyright (c) 2016-2017 The Brenwill Workshop Ltd.
+ *  This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+
+#import <UIKit/UIKit.h>
+
+@interface AppDelegate : UIResponder <UIApplicationDelegate>
+
+@property (strong, nonatomic) UIWindow *window;
+
+@end
+
diff --git a/external/Vulkan/xcode/ios/AppDelegate.m b/external/Vulkan/xcode/ios/AppDelegate.m
new file mode 100644
index 0000000000000000000000000000000000000000..8d69ac3b5f8ebb21f1026feb15e2f39d4c0ba340
--- /dev/null
+++ b/external/Vulkan/xcode/ios/AppDelegate.m
@@ -0,0 +1,44 @@
+/*
+ *  AppDelegate.m
+ *
+ *  Copyright (c) 2016-2017 The Brenwill Workshop Ltd.
+ *  This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+
+#import "AppDelegate.h"
+
+@interface AppDelegate ()
+
+@end
+
+@implementation AppDelegate
+
+
+- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
+	// Override point for customization after application launch.
+	return YES;
+}
+
+- (void)applicationWillResignActive:(UIApplication *)application {
+	// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
+	// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
+}
+
+- (void)applicationDidEnterBackground:(UIApplication *)application {
+	// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
+	// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
+}
+
+- (void)applicationWillEnterForeground:(UIApplication *)application {
+	// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
+}
+
+- (void)applicationDidBecomeActive:(UIApplication *)application {
+	// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
+}
+
+- (void)applicationWillTerminate:(UIApplication *)application {
+	// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
+}
+
+@end
diff --git a/external/Vulkan/xcode/ios/DemoViewController.h b/external/Vulkan/xcode/ios/DemoViewController.h
new file mode 100644
index 0000000000000000000000000000000000000000..81d04021473211a8ee1dfb811c91401d38e8ec9e
--- /dev/null
+++ b/external/Vulkan/xcode/ios/DemoViewController.h
@@ -0,0 +1,25 @@
+/*
+ *  DemoViewController.h
+ *
+ *  Copyright (c) 2016-2017 The Brenwill Workshop Ltd.
+ *  This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+
+#import <UIKit/UIKit.h>
+
+
+#pragma mark -
+#pragma mark DemoViewController
+
+/** The main view controller for the demo storyboard. */
+@interface DemoViewController : UIViewController <UIKeyInput>
+@end
+
+
+#pragma mark -
+#pragma mark DemoView
+
+/** The Metal-compatibile view for the demo Storyboard. */
+@interface DemoView : UIView
+@end
+
diff --git a/external/Vulkan/xcode/ios/DemoViewController.mm b/external/Vulkan/xcode/ios/DemoViewController.mm
new file mode 100644
index 0000000000000000000000000000000000000000..2629623ff3a7105580dfb611b142685b3a6fda67
--- /dev/null
+++ b/external/Vulkan/xcode/ios/DemoViewController.mm
@@ -0,0 +1,117 @@
+/*
+ *  DemoViewController.mm
+ *
+ *  Copyright (c) 2016-2017 The Brenwill Workshop Ltd.
+ *  This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+
+#import "DemoViewController.h"
+
+#include "MVKExample.h"
+
+
+const std::string VulkanExampleBase::getAssetPath() {
+    return [NSBundle.mainBundle.resourcePath stringByAppendingString: @"/data/"].UTF8String;
+}
+
+
+#pragma mark -
+#pragma mark DemoViewController
+
+@implementation DemoViewController {
+    MVKExample* _mvkExample;
+    CADisplayLink* _displayLink;
+    BOOL _viewHasAppeared;
+}
+
+/** Since this is a single-view app, init Vulkan when the view is loaded. */
+-(void) viewDidLoad {
+	[super viewDidLoad];
+
+    self.view.contentScaleFactor = UIScreen.mainScreen.nativeScale;
+
+    _mvkExample = new MVKExample(self.view);
+
+    uint32_t fps = 60;
+    _displayLink = [CADisplayLink displayLinkWithTarget: self selector: @selector(renderFrame)];
+    [_displayLink setFrameInterval: 60 / fps];
+    [_displayLink addToRunLoop: NSRunLoop.currentRunLoop forMode: NSDefaultRunLoopMode];
+
+    // Setup tap gesture to toggle virtual keyboard
+    UITapGestureRecognizer* tapSelector = [[UITapGestureRecognizer alloc]
+                                           initWithTarget: self action: @selector(handleTapGesture:)];
+    tapSelector.numberOfTapsRequired = 1;
+    tapSelector.cancelsTouchesInView = YES;
+    [self.view addGestureRecognizer: tapSelector];
+
+    _viewHasAppeared = NO;
+}
+
+-(void) viewDidAppear: (BOOL) animated {
+    [super viewDidAppear: animated];
+    _viewHasAppeared = YES;
+}
+
+-(BOOL) canBecomeFirstResponder { return _viewHasAppeared; }
+
+-(void) renderFrame {
+    _mvkExample->renderFrame();
+}
+
+-(void) dealloc {
+    delete _mvkExample;
+    [super dealloc];
+}
+
+// Toggle the display of the virtual keyboard
+-(void) toggleKeyboard {
+    if (self.isFirstResponder) {
+        [self resignFirstResponder];
+    } else {
+        [self becomeFirstResponder];
+    }
+}
+
+// Display and hide the keyboard by tapping on the view
+-(void) handleTapGesture: (UITapGestureRecognizer*) gestureRecognizer {
+    if (gestureRecognizer.state == UIGestureRecognizerStateEnded) {
+        [self toggleKeyboard];
+    }
+}
+
+// Handle keyboard input
+-(void) handleKeyboardInput: (unichar) keycode {
+    _mvkExample->keyPressed(keycode);
+}
+
+
+#pragma mark UIKeyInput methods
+
+// Returns whether text is available
+-(BOOL) hasText { return YES; }
+
+// A key on the keyboard has been pressed.
+-(void) insertText: (NSString*) text {
+    unichar keycode = (text.length > 0) ? [text characterAtIndex: 0] : 0;
+    [self handleKeyboardInput: keycode];
+}
+
+// The delete backward key has been pressed.
+-(void) deleteBackward {
+    [self handleKeyboardInput: 0x33];
+}
+
+
+@end
+
+
+#pragma mark -
+#pragma mark DemoView
+
+@implementation DemoView
+
+/** Returns a Metal-compatible layer. */
++(Class) layerClass { return [CAMetalLayer class]; }
+
+@end
+
diff --git a/external/Vulkan/xcode/ios/Info.plist b/external/Vulkan/xcode/ios/Info.plist
new file mode 100644
index 0000000000000000000000000000000000000000..eb0fa65a1df16008f18c7c9c038251437b6b06c6
--- /dev/null
+++ b/external/Vulkan/xcode/ios/Info.plist
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>English</string>
+	<key>CFBundleDisplayName</key>
+	<string>${PRODUCT_NAME}</string>
+	<key>CFBundleExecutable</key>
+	<string>${EXECUTABLE_NAME}</string>
+	<key>CFBundleIconFile</key>
+	<string>iOS/Resources/Icon.png</string>
+	<key>CFBundleIcons~ipad</key>
+	<dict/>
+	<key>CFBundleIdentifier</key>
+	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundlePackageType</key>
+	<string>APPL</string>
+	<key>CFBundleVersion</key>
+	<string>1.0</string>
+	<key>LSApplicationCategoryType</key>
+	<string></string>
+	<key>UIMainStoryboardFile</key>
+	<string>Main</string>
+	<key>UIRequiresFullScreen</key>
+	<true/>
+	<key>UIStatusBarHidden</key>
+	<true/>
+	<key>UISupportedInterfaceOrientations</key>
+	<array>
+		<string>UIInterfaceOrientationLandscapeLeft</string>
+		<string>UIInterfaceOrientationLandscapeRight</string>
+	</array>
+	<key>UISupportedInterfaceOrientations~ipad</key>
+	<array>
+		<string>UIInterfaceOrientationLandscapeRight</string>
+		<string>UIInterfaceOrientationLandscapeLeft</string>
+	</array>
+	<key>UIViewControllerBasedStatusBarAppearance</key>
+	<false/>
+</dict>
+</plist>
diff --git a/external/Vulkan/xcode/ios/Resources/Default-568h@2x.png b/external/Vulkan/xcode/ios/Resources/Default-568h@2x.png
new file mode 100755
index 0000000000000000000000000000000000000000..1669d7b6845195d9e11a1af63165c359f7041fc6
Binary files /dev/null and b/external/Vulkan/xcode/ios/Resources/Default-568h@2x.png differ
diff --git a/external/Vulkan/xcode/ios/Resources/Default~ipad.png b/external/Vulkan/xcode/ios/Resources/Default~ipad.png
new file mode 100755
index 0000000000000000000000000000000000000000..ec9a3dcd1436d136fff50d64f280f2ce084c0eaf
Binary files /dev/null and b/external/Vulkan/xcode/ios/Resources/Default~ipad.png differ
diff --git a/external/Vulkan/xcode/ios/Resources/Icon.png b/external/Vulkan/xcode/ios/Resources/Icon.png
new file mode 100755
index 0000000000000000000000000000000000000000..dc3b10f5ea97460001efe8fb45e8264812ba68ab
Binary files /dev/null and b/external/Vulkan/xcode/ios/Resources/Icon.png differ
diff --git a/external/Vulkan/xcode/ios/Resources/Main.storyboard b/external/Vulkan/xcode/ios/Resources/Main.storyboard
new file mode 100644
index 0000000000000000000000000000000000000000..6205491f9fbf87c21acfe3036cfef83ed9e8784a
--- /dev/null
+++ b/external/Vulkan/xcode/ios/Resources/Main.storyboard
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="9060" systemVersion="15A279b" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="vmV-Wi-8wg">
+    <dependencies>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9051"/>
+    </dependencies>
+    <scenes>
+        <!--Demo View Controller-->
+        <scene sceneID="UPC-Y9-dN2">
+            <objects>
+                <viewController id="vmV-Wi-8wg" customClass="DemoViewController" sceneMemberID="viewController">
+                    <layoutGuides>
+                        <viewControllerLayoutGuide type="top" id="klr-UD-pWY"/>
+                        <viewControllerLayoutGuide type="bottom" id="lMV-Es-kLU"/>
+                    </layoutGuides>
+                    <view key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" id="xKx-Gc-5Is" customClass="DemoView">
+                        <rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <animations/>
+                    </view>
+                </viewController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="ERB-qy-6pP" userLabel="First Responder" sceneMemberID="firstResponder"/>
+            </objects>
+            <point key="canvasLocation" x="404" y="555"/>
+        </scene>
+    </scenes>
+</document>
diff --git a/external/Vulkan/xcode/ios/main.m b/external/Vulkan/xcode/ios/main.m
new file mode 100644
index 0000000000000000000000000000000000000000..d179e4174b3c6a7290ddc6b579378fd816e5140b
--- /dev/null
+++ b/external/Vulkan/xcode/ios/main.m
@@ -0,0 +1,14 @@
+/******************************************************************************
+ *
+ * main.m
+ *
+ ******************************************************************************/
+
+#import <UIKit/UIKit.h>
+
+int main(int argc, char * argv[]) {
+	@autoreleasepool {
+		return UIApplicationMain(argc, argv, nil, @"AppDelegate");
+	}
+}
+
diff --git a/external/Vulkan/xcode/macos/AppDelegate.h b/external/Vulkan/xcode/macos/AppDelegate.h
new file mode 100644
index 0000000000000000000000000000000000000000..3eb638e981abf2222134c9293d95705b0419b9fe
--- /dev/null
+++ b/external/Vulkan/xcode/macos/AppDelegate.h
@@ -0,0 +1,12 @@
+/*
+ *  AppDelegate.h
+ *
+ *  Copyright (c) 2016-2017 The Brenwill Workshop Ltd.
+ *  This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+
+#import <Cocoa/Cocoa.h>
+
+@interface AppDelegate : NSObject <NSApplicationDelegate>
+
+@end
diff --git a/external/Vulkan/xcode/macos/AppDelegate.m b/external/Vulkan/xcode/macos/AppDelegate.m
new file mode 100644
index 0000000000000000000000000000000000000000..c3ce3b2403f5cf6a91e93dbb42a9ae9a8400e3fa
--- /dev/null
+++ b/external/Vulkan/xcode/macos/AppDelegate.m
@@ -0,0 +1,28 @@
+/*
+ *  AppDelegate.m
+ *
+ *  Copyright (c) 2016-2017 The Brenwill Workshop Ltd.
+ *  This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+
+#import "AppDelegate.h"
+
+@interface AppDelegate ()
+
+@end
+
+@implementation AppDelegate
+
+- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
+    // Insert code here to initialize your application
+}
+
+- (void)applicationWillTerminate:(NSNotification *)aNotification {
+    // Insert code here to tear down your application
+}
+
+- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender {
+    return YES;
+}
+
+@end
diff --git a/external/Vulkan/xcode/macos/DemoViewController.h b/external/Vulkan/xcode/macos/DemoViewController.h
new file mode 100644
index 0000000000000000000000000000000000000000..3cbedc489234eef11af364681cd72439aa6348b4
--- /dev/null
+++ b/external/Vulkan/xcode/macos/DemoViewController.h
@@ -0,0 +1,25 @@
+/*
+ *  DemoViewController.h
+ *
+ *  Copyright (c) 2016-2017 The Brenwill Workshop Ltd.
+ *  This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+
+#import <AppKit/AppKit.h>
+
+
+#pragma mark -
+#pragma mark DemoViewController
+
+/** The main view controller for the demo storyboard. */
+@interface DemoViewController : NSViewController
+@end
+
+
+#pragma mark -
+#pragma mark DemoView
+
+/** The Metal-compatibile view for the demo Storyboard. */
+@interface DemoView : NSView
+@end
+
diff --git a/external/Vulkan/xcode/macos/DemoViewController.mm b/external/Vulkan/xcode/macos/DemoViewController.mm
new file mode 100644
index 0000000000000000000000000000000000000000..657f0b5f022d2ae2662af74e4a23c81cb93f6f95
--- /dev/null
+++ b/external/Vulkan/xcode/macos/DemoViewController.mm
@@ -0,0 +1,86 @@
+/*
+ *  DemoViewController.mm
+ *
+ *  Copyright (c) 2016-2017 The Brenwill Workshop Ltd.
+ *  This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+
+#import "DemoViewController.h"
+#import <QuartzCore/CAMetalLayer.h>
+
+#include "MVKExample.h"
+
+
+const std::string VulkanExampleBase::getAssetPath() {
+    return [NSBundle.mainBundle.resourcePath stringByAppendingString: @"/data/"].UTF8String;
+}
+
+/** Rendering loop callback function for use with a CVDisplayLink. */
+static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink,
+                                    const CVTimeStamp* now,
+                                    const CVTimeStamp* outputTime,
+                                    CVOptionFlags flagsIn,
+                                    CVOptionFlags* flagsOut,
+                                    void* target) {
+    ((MVKExample*)target)->renderFrame();
+    return kCVReturnSuccess;
+}
+
+
+#pragma mark -
+#pragma mark DemoViewController
+
+@implementation DemoViewController {
+    MVKExample* _mvkExample;
+    CVDisplayLinkRef _displayLink;
+}
+
+/** Since this is a single-view app, initialize Vulkan during view loading. */
+-(void) viewDidLoad {
+	[super viewDidLoad];
+
+	self.view.wantsLayer = YES;		// Back the view with a layer created by the makeBackingLayer method.
+
+    _mvkExample = new MVKExample(self.view);
+
+    CVDisplayLinkCreateWithActiveCGDisplays(&_displayLink);
+    CVDisplayLinkSetOutputCallback(_displayLink, &DisplayLinkCallback, _mvkExample);
+    CVDisplayLinkStart(_displayLink);
+}
+
+-(void) dealloc {
+    CVDisplayLinkRelease(_displayLink);
+    delete _mvkExample;
+    [super dealloc];
+}
+
+// Handle keyboard input
+-(void) keyDown:(NSEvent*) theEvent {
+    _mvkExample->keyPressed(theEvent.keyCode);
+}
+
+@end
+
+
+#pragma mark -
+#pragma mark DemoView
+
+@implementation DemoView
+
+/** Indicates that the view wants to draw using the backing layer instead of using drawRect:.  */
+-(BOOL) wantsUpdateLayer { return YES; }
+
+/** Returns a Metal-compatible layer. */
++(Class) layerClass { return [CAMetalLayer class]; }
+
+/** If the wantsLayer property is set to YES, this method will be invoked to return a layer instance. */
+-(CALayer*) makeBackingLayer {
+    CALayer* layer = [self.class.layerClass layer];
+    CGSize viewScale = [self convertSizeToBacking: CGSizeMake(1.0, 1.0)];
+    layer.contentsScale = MIN(viewScale.width, viewScale.height);
+    return layer;
+}
+
+-(BOOL) acceptsFirstResponder { return YES; }
+
+@end
diff --git a/external/Vulkan/xcode/macos/Info.plist b/external/Vulkan/xcode/macos/Info.plist
new file mode 100644
index 0000000000000000000000000000000000000000..e921bd0e65199263a0c8d755c863cee035411c0d
--- /dev/null
+++ b/external/Vulkan/xcode/macos/Info.plist
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>en</string>
+	<key>CFBundleExecutable</key>
+	<string>${EXECUTABLE_NAME}</string>
+	<key>CFBundleIdentifier</key>
+	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>${PRODUCT_NAME}</string>
+	<key>CFBundlePackageType</key>
+	<string>APPL</string>
+	<key>CFBundleShortVersionString</key>
+	<string>1.0</string>
+	<key>CFBundleSignature</key>
+	<string>????</string>
+	<key>CFBundleVersion</key>
+	<string>1</string>
+	<key>LSApplicationCategoryType</key>
+	<string></string>
+	<key>LSMinimumSystemVersion</key>
+	<string>${MACOSX_DEPLOYMENT_TARGET}</string>
+	<key>NSHumanReadableCopyright</key>
+	<string>Copyright (c) 2016-2017 The Brenwill Workshop Ltd.</string>
+	<key>NSMainStoryboardFile</key>
+	<string>Main</string>
+	<key>NSPrincipalClass</key>
+	<string>NSApplication</string>
+</dict>
+</plist>
diff --git a/external/Vulkan/xcode/macos/Resources/Main.storyboard b/external/Vulkan/xcode/macos/Resources/Main.storyboard
new file mode 100644
index 0000000000000000000000000000000000000000..5291336637639eae8485ae0c15517434793e2ecc
--- /dev/null
+++ b/external/Vulkan/xcode/macos/Resources/Main.storyboard
@@ -0,0 +1,133 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="12118" systemVersion="16E195" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="B8D-0N-5wS">
+    <dependencies>
+        <deployment identifier="macosx"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="12118"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <scenes>
+        <!--Application-->
+        <scene sceneID="JPo-4y-FX3">
+            <objects>
+                <application id="hnw-xV-0zn" sceneMemberID="viewController">
+                    <menu key="mainMenu" title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
+                        <items>
+                            <menuItem title="MoltenVK Demo" id="1Xt-HY-uBw">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <menu key="submenu" title="MoltenVK Demo" systemMenu="apple" id="uQy-DD-JDr">
+                                    <items>
+                                        <menuItem title="About Demo" id="5kV-Vb-QxS">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="orderFrontStandardAboutPanel:" target="Ady-hI-5gd" id="Exp-CZ-Vem"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
+                                        <menuItem title="Hide Demo" keyEquivalent="h" id="Olw-nP-bQN">
+                                            <connections>
+                                                <action selector="hide:" target="Ady-hI-5gd" id="PnN-Uc-m68"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Hide Others" keyEquivalent="h" id="Vdr-fp-XzO">
+                                            <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
+                                            <connections>
+                                                <action selector="hideOtherApplications:" target="Ady-hI-5gd" id="VT4-aY-XCT"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Show All" id="Kd2-mp-pUS">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="unhideAllApplications:" target="Ady-hI-5gd" id="Dhg-Le-xox"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
+                                        <menuItem title="Quit Demo" keyEquivalent="q" id="4sb-4s-VLi">
+                                            <connections>
+                                                <action selector="terminate:" target="Ady-hI-5gd" id="Te7-pn-YzF"/>
+                                            </connections>
+                                        </menuItem>
+                                    </items>
+                                </menu>
+                            </menuItem>
+                            <menuItem title="Window" id="aUF-d1-5bR">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <menu key="submenu" title="Window" systemMenu="window" id="Td7-aD-5lo">
+                                    <items>
+                                        <menuItem title="Minimize" keyEquivalent="m" id="OY7-WF-poV">
+                                            <connections>
+                                                <action selector="performMiniaturize:" target="Ady-hI-5gd" id="VwT-WD-YPe"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Zoom" id="R4o-n2-Eq4">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="performZoom:" target="Ady-hI-5gd" id="DIl-cC-cCs"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem isSeparatorItem="YES" id="eu3-7i-yIM"/>
+                                        <menuItem title="Bring All to Front" id="LE2-aR-0XJ">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="arrangeInFront:" target="Ady-hI-5gd" id="DRN-fu-gQh"/>
+                                            </connections>
+                                        </menuItem>
+                                    </items>
+                                </menu>
+                            </menuItem>
+                            <menuItem title="Help" id="wpr-3q-Mcd">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <menu key="submenu" title="Help" systemMenu="help" id="F2S-fz-NVQ">
+                                    <items>
+                                        <menuItem title="MoltenVK Demo Help" keyEquivalent="?" id="FKE-Sm-Kum">
+                                            <connections>
+                                                <action selector="showHelp:" target="Ady-hI-5gd" id="y7X-2Q-9no"/>
+                                            </connections>
+                                        </menuItem>
+                                    </items>
+                                </menu>
+                            </menuItem>
+                        </items>
+                    </menu>
+                    <connections>
+                        <outlet property="delegate" destination="Voe-Tx-rLC" id="PrD-fu-P6m"/>
+                    </connections>
+                </application>
+                <customObject id="Voe-Tx-rLC" customClass="AppDelegate"/>
+                <customObject id="Ady-hI-5gd" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
+            </objects>
+            <point key="canvasLocation" x="83.5" y="-47"/>
+        </scene>
+        <!--Window Controller-->
+        <scene sceneID="R2V-B0-nI4">
+            <objects>
+                <windowController id="B8D-0N-5wS" sceneMemberID="viewController">
+                    <window key="window" title="MoltenVK Vulkan Example" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" animationBehavior="default" id="IQv-IB-iLA">
+                        <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
+                        <rect key="contentRect" x="1051" y="656" width="1024" height="768"/>
+                        <rect key="screenRect" x="0.0" y="0.0" width="2560" height="1417"/>
+                        <value key="minSize" type="size" width="1024" height="768"/>
+                        <value key="maxSize" type="size" width="1024" height="768"/>
+                    </window>
+                    <connections>
+                        <segue destination="XfG-lQ-9wD" kind="relationship" relationship="window.shadowedContentViewController" id="cq2-FE-JQM"/>
+                    </connections>
+                </windowController>
+                <customObject id="Oky-zY-oP4" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
+            </objects>
+            <point key="canvasLocation" x="83" y="146"/>
+        </scene>
+        <!--Demo View Controller-->
+        <scene sceneID="hIz-AP-VOD">
+            <objects>
+                <viewController id="XfG-lQ-9wD" customClass="DemoViewController" sceneMemberID="viewController">
+                    <view key="view" id="m2S-Jp-Qdl" customClass="DemoView">
+                        <rect key="frame" x="0.0" y="0.0" width="1024" height="768"/>
+                        <autoresizingMask key="autoresizingMask"/>
+                    </view>
+                </viewController>
+                <customObject id="rPt-NT-nkU" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
+            </objects>
+            <point key="canvasLocation" x="83" y="713"/>
+        </scene>
+    </scenes>
+</document>
diff --git a/external/Vulkan/xcode/macos/Resources/macOS.xcassets/AppIcon.appiconset/Contents.json b/external/Vulkan/xcode/macos/Resources/macOS.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000000000000000000000000000000000000..4124516b148bdfc6961f470344a699c91cb9f839
--- /dev/null
+++ b/external/Vulkan/xcode/macos/Resources/macOS.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,63 @@
+{
+  "images" : [
+    {
+      "size" : "16x16",
+      "idiom" : "mac",
+      "filename" : "Icon-16.png",
+      "scale" : "1x"
+    },
+    {
+      "idiom" : "mac",
+      "size" : "16x16",
+      "scale" : "2x"
+    },
+    {
+      "size" : "32x32",
+      "idiom" : "mac",
+      "filename" : "Icon-32.png",
+      "scale" : "1x"
+    },
+    {
+      "idiom" : "mac",
+      "size" : "32x32",
+      "scale" : "2x"
+    },
+    {
+      "size" : "128x128",
+      "idiom" : "mac",
+      "filename" : "Icon-128.png",
+      "scale" : "1x"
+    },
+    {
+      "idiom" : "mac",
+      "size" : "128x128",
+      "scale" : "2x"
+    },
+    {
+      "size" : "256x256",
+      "idiom" : "mac",
+      "filename" : "Icon-256.png",
+      "scale" : "1x"
+    },
+    {
+      "idiom" : "mac",
+      "size" : "256x256",
+      "scale" : "2x"
+    },
+    {
+      "size" : "512x512",
+      "idiom" : "mac",
+      "filename" : "Icon-512.png",
+      "scale" : "1x"
+    },
+    {
+      "idiom" : "mac",
+      "size" : "512x512",
+      "scale" : "2x"
+    }
+  ],
+  "info" : {
+    "version" : 1,
+    "author" : "xcode"
+  }
+}
\ No newline at end of file
diff --git a/external/Vulkan/xcode/macos/Resources/macOS.xcassets/AppIcon.appiconset/Icon-128.png b/external/Vulkan/xcode/macos/Resources/macOS.xcassets/AppIcon.appiconset/Icon-128.png
new file mode 100644
index 0000000000000000000000000000000000000000..8c55f1bea07d81a0366425784d928608f325c5c8
Binary files /dev/null and b/external/Vulkan/xcode/macos/Resources/macOS.xcassets/AppIcon.appiconset/Icon-128.png differ
diff --git a/external/Vulkan/xcode/macos/Resources/macOS.xcassets/AppIcon.appiconset/Icon-16.png b/external/Vulkan/xcode/macos/Resources/macOS.xcassets/AppIcon.appiconset/Icon-16.png
new file mode 100644
index 0000000000000000000000000000000000000000..583d636d60253168d1632b648a8ae5d34eaf4843
Binary files /dev/null and b/external/Vulkan/xcode/macos/Resources/macOS.xcassets/AppIcon.appiconset/Icon-16.png differ
diff --git a/external/Vulkan/xcode/macos/Resources/macOS.xcassets/AppIcon.appiconset/Icon-256.png b/external/Vulkan/xcode/macos/Resources/macOS.xcassets/AppIcon.appiconset/Icon-256.png
new file mode 100644
index 0000000000000000000000000000000000000000..bb35d46e2b820f590832ecfc1c9bb7fe381d3061
Binary files /dev/null and b/external/Vulkan/xcode/macos/Resources/macOS.xcassets/AppIcon.appiconset/Icon-256.png differ
diff --git a/external/Vulkan/xcode/macos/Resources/macOS.xcassets/AppIcon.appiconset/Icon-32.png b/external/Vulkan/xcode/macos/Resources/macOS.xcassets/AppIcon.appiconset/Icon-32.png
new file mode 100644
index 0000000000000000000000000000000000000000..d73e1f60edb674197e45a25e24457f2c6d4dbfb6
Binary files /dev/null and b/external/Vulkan/xcode/macos/Resources/macOS.xcassets/AppIcon.appiconset/Icon-32.png differ
diff --git a/external/Vulkan/xcode/macos/Resources/macOS.xcassets/AppIcon.appiconset/Icon-512.png b/external/Vulkan/xcode/macos/Resources/macOS.xcassets/AppIcon.appiconset/Icon-512.png
new file mode 100644
index 0000000000000000000000000000000000000000..f2d01ac262033a594300cd1f213562023a8cc873
Binary files /dev/null and b/external/Vulkan/xcode/macos/Resources/macOS.xcassets/AppIcon.appiconset/Icon-512.png differ
diff --git a/external/Vulkan/xcode/macos/Resources/macOS.xcassets/Contents.json b/external/Vulkan/xcode/macos/Resources/macOS.xcassets/Contents.json
new file mode 100644
index 0000000000000000000000000000000000000000..da4a164c918651cdd1e11dca5cc62c333f097601
--- /dev/null
+++ b/external/Vulkan/xcode/macos/Resources/macOS.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+  "info" : {
+    "version" : 1,
+    "author" : "xcode"
+  }
+}
\ No newline at end of file
diff --git a/external/Vulkan/xcode/macos/main.m b/external/Vulkan/xcode/macos/main.m
new file mode 100644
index 0000000000000000000000000000000000000000..01b929e174e47b31346ec3e7d8343fa9bec7183e
--- /dev/null
+++ b/external/Vulkan/xcode/macos/main.m
@@ -0,0 +1,12 @@
+/*
+ *  main.m
+ *
+ *  Copyright (c) 2016-2017 The Brenwill Workshop Ltd.
+ *  This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+ */
+
+#import <Cocoa/Cocoa.h>
+
+int main(int argc, const char * argv[]) {
+	return NSApplicationMain(argc, argv);
+}